I am using the d3 Timeline plugin (https://github.com/jiahuang/d3-timeline) and I finally got it to where the viz can read my data. However, I now need to make it so that all entries for a particular student show on the same line. The viz is currently displaying each entry separately.
CSV data looks like this:
Start,End,Student,Event Type,tooltip
2015-May-27 20:08:15,2015-May-27 20:08:21,Student 338,Pretest,Student 338 spent 00:00:06 on Biological Chemistry test. Student scored 100%.
2015-May-27 20:08:21,2015-May-27 20:08:30,Student 338,Learning Path,Student 338 spent 00:00:09 on Biological Buffers Website.
2015-May-27 20:08:30,2015-May-27 20:08:34,Student 338,Learning Path,Student 338 spent 00:00:04 on Organic Molecules Textbook.
2015-May-27 20:08:34,2015-May-27 20:08:36,Student 338,Learning Path,Student 338 spent 00:00:03 on Nucleic Acid Structure and Function Textbook 2.
The viz file looks like this:
<script type="text/javascript">
window.onload = function() {
var width = 800;
var height = 700;
var parseDate = d3.time.format("%Y-%b-%d %H:%M:%S").parse; //With parseDate, I define what my time data looks like for d3
d3.csv("https://dl.dropboxusercontent.com/u/42615722/LeaP/biology-course2.csv", function(d){
return {
label : d.Student,
eventType: d["Event Type"],
times:[{"starting_time" : parseDate(d.Start),
"ending_time" : parseDate(d.End)}], //the timeline plugin is looking for a times array of objects with these keys so they must be defined.
info: d.tooltip
};
}, function(data) {
console.log(data);
function timelineRect() {
var colorScale = d3.scale.ordinal().range(['#ed5565','#ffce54','#48cfad','#5d9cec','#ec87c0','#ac92ec','#4fc1e9','#fc6e51','#a0d468','#656d78'])
.domain(['Pretest','Learning Path','Supplemental Info','Questions','Test Me','Remedial Page','Search Query','Recommended Reading','Search Results','Practice Questions']);
var chart = d3.timeline()
.colors( colorScale )
.colorProperty('event_type')
.rotateTicks(45)
.stack(); //necessary to unstack all of the labels
var svg = d3.select("#timeline1").append("svg").attr("width", width)
.datum(data).call(chart);
}
timelineRect();
});
}
</script>
</head>
<body>
<div>
<div id="timeline1"></div>
</div>
</body>
None of the examples define domain for this and, unfortunately, all of the example data is given in arrays, instead of from a CSV. How can I define the data so that each entry with the same student label will appear on the same timeline row?
Problem 1
You are not grouping your csv data this can be done like this:
What I mean is
//this will do the grouping
var k = d3.nest()
.key(function(d) {
return d.label;
}).entries(data);
var b = [];
//this will make it in the format expected by d3 time line
k.forEach(function(d) {
var ob = {};
ob.label = d.key;
ob.times = [];
b.push(ob);
d.values.forEach(function(v) {
ob.times.push(v.times);//collecting all the times
});
ob.times = [].concat.apply([], ob.times);
});
Problem 2
You have to define the tick format
.tickFormat({
format: d3.time.format("%d-%m"),
tickTime: d3.time.day,
tickInterval: 20,
tickSize: 6
})
Refer this for d3 time format
Your data set is very close on seconds and so the rectangle are coming very small..:( i tried my level best to span it up.. best of luck with it :)
Working code here
Hope this helps!
Related
I carried out join operation to help in smoothing out my images in a Landsat ImageCollection by getting at least 3 Images in a specified time window, obtain the median and then adding those images to the ImageCollection. I expected to obtain an ImageCollection that I could still carry out some filter functions to create a time series with the median images, but it didn't work.
// 1. selecting the time window
var days = 32;
var milli = ee.Number(days).multiply(1000*24*60*60)
console.log(milli)
var join = ee.Join.saveAll({
matchesKey:"images"
})
// 2. Apply the filter
var dif_filter = ee.Filter.maxDifference({
difference:milli,
leftField:"system:time_start",
rightField:"system:time_start"
})
// 3. Apply the join
var joined_collection = join.apply({
primary: original_collection,
secondary:original_collection,
condition:dif_filter
})
// Adding the median images to our collection
function medianCalculator(image){
var imageGetter = ee.ImageCollection.fromImages(image.get("images"))
var medianImage = imageGetter.reduce(ee.Reducer.median())
return ee.Image(image).addBands(medianImage).select("NDVI_median", "EVI_median", "NDVI", "EVI")
}
// collection with smoothed Images
var collection_2 = joined_collection.map(medianCalculator)
print(collection_2)
I wanted to carry out some filters on "collection_2" but there was an error, "Line 110: collection_2.filter(...).median is not a function". What am I missing.
I suspect that after performing the Join, the ImageCollection got turned into a FeatureCollection so I tried looking for ways of converting a FeatureCollection to an ImageCollection...to no avail.
Below is the code snippet for a barchart with colored bars:
var Dim2 = ndx.dimension(function(d){return [d.SNo, d.something ]});
var Group2 = Dim2.group().reduceSum(function(d){ return d.someId; });
var someColors = d3.scale.ordinal().domain(["a1","a2","a3","a4","a5","a6","a7","a8"])
.range(["#2980B9","#00FFFF","#008000","#FFC300","#FF5733","#D1AEF1","#C0C0C0","#000000"]);
barChart2
.height(250)
.width(1000)
.brushOn(false)
.mouseZoomable(true)
.x(d3.scale.linear().domain([600,800]))
.elasticY(false)
.dimension(Dim2)
.group(Group2)
.keyAccessor(function(d){ return d.key[0]; })
.valueAccessor(function(d){return d.value; })
.colors(someColors)
.colorAccessor(function(d){return d.key[1]; });
How do I add a legend to this chart?
Using composite keys in crossfilter is really tricky, and I don't recommend it unless you really need it.
Crossfilter only understands scalars, so even though you can produce dimension and group keys which are arrays, and retrieve them correctly, crossfilter is going to coerce those arrays to strings, and that can cause trouble.
Here, what is happening is that Group2.all() iterates over your data in string order, so you get keys in the order
[1, "a1"], [10, "a3"], [11, "a4"], [12, "a5"], [2, "a3"], ...
Without changing the shape of your data, one way around this is to sort the data in your legendables function:
barChart2.legendables = function() {
return Group2.all().sort((a,b) => a.key[0] - b.key[0])
.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
An unrelated problem is that dc.js takes the X domain very literally, so even though [1,12] contains all the values, the last bar was not shown because the right side ends right at 12 and the bar is drawn between 12 and 13.
So:
.x(d3.scale.linear().domain([1,13]))
Now the legend matches the data!
Fork of your fiddle (also with dc.css).
EDIT: Of course, you want the legend items unique, too. You can define uniq like this:
function uniq(a, kf) {
var seen = [];
return a.filter(x => seen[kf(x)] ? false : (seen[kf(x)] = true));
}
Adding a step to legendables:
barChart2.legendables = function() {
var vals = uniq(Group2.all(), kv => kv.key[1]),
sorted = vals.sort((a,b) => a.key[1] > b.key[1] ? 1 : -1);
// or in X order: sorted = vals.sort((a,b) => a.key[0] - b.key[0]);
return sorted.map(function(kv) {
return {
chart: barChart2,
name: kv.key[1],
color: barChart2.colors()(kv.key[1]) }; }) };
Note that we're sorting by the string value of d.something which lands in key[1]. As shown in the comment, sorting by x order (d.SNo, key[0]) is possible too. I wouldn't recommend sorting by y since that's a reduceSum.
Result, sorted and uniq'd:
New fiddle.
I guess this is somewhat of a stupid question. But I am struggling since quite a while with this, not finding what the right way of data formatting is for my data.
I have yearly data like this, which I want to have displayed as such - 2001, 2002, 2003, ...:
time,lat,lon,Npp_1km
Date.UTC(2001/1/1),15,-90,1.266112766
Date.UTC(2002/1/1),15,-90,1.166646809
Date.UTC(2003/1/1),15,-90,1.020591489
Date.UTC(2004/1/1),15,-90,1.016010638
Date.UTC(2005/12/31),15,-90,1.08053617
Date.UTC(2006/12/31),15,-90,1.181195745
and my Highcharts code which looks like this:
xAxis: {
labels: {
style: {
color: "#666666"
},
x: 0
},
gridLineWidth: 1,
gridLineDashStyle: 'Dot',
tickWidth: 0,
type: 'datetime'
},
But the xAxis doesn't display the years but instead some "00:00:00.001".
I have tried many different formats for the timestamp - "2001-1-1", "2001/1/1", "1/1/2001", "1-1-2001", "Date.UTC(2001/1/1)". I have changed the "dateTimeLabelFormats" as well. But all in vain. It doesn't spit out "2001 - 2002 - 2003 - 2004".
Here is a fiddle.
What is the right way to achieve this? Thanks for any hints!
You had problems with parsing your data. All of your x values was not correct - that is the reason of your issue. You need to parse it a little bit different if you want to get the correct data for your chart:
$.get('data.csv', function(data) {
var temp = []
// Split the lines
var lines = data.split('\n');
// For each line, split the record into seperate attributes
$.each(lines, function(lineNo, line) {
var items = line.split(',');
if (lineNo !== 0) {
items[0] = items[0].substring(items[0].indexOf('(') + 1, items[0].indexOf(')'));
var x = new Date(items[0]),
y = parseFloat(items[3]);
if (!isNaN(y)) {
x = x.getTime();
options.series[0].data.push([x, y]);
}
}
});
Here you can see an example how it can work:
http://jsfiddle.net/pcpq6mtr/4/
Regards,
A simple solution can be put categories in xAxis if the time is fixed.
xAxis {
categories:[2001,2002,2003,2004,2005,2006]
}
If the time is not fixed. categories need to be calculated dynamically.
I have four CSVs with the same header information, each representing a quarterly result within a year.
Therefore for one result I can load it and display it into a dataTable simple via
d3.csv("data/first-quarter"), function(dataQ1){
dataQ1.forEach(function(d){
d.callTypes = d['Call Types'];
d.callDesc = d['Call Description'];
d.callVol = d['Call Volume'];
d.quarter = 'Q1';
});
var facts = crossfilter(dataQ1);
var timeDimension = facts.dimension(function(d){
return d.quarter;
});
dataTable
... //data table attributes
dc.renderAll();
});
However complications arise when I try to retrieve from multiple sources and append the results.
One approach I took was to place all the file path names into an array and iterate through a forEach, with a flag to show when it was the last iteration to render the table. But this failed with a "Too many recursion" error.
And the next was to nest as such
d3.csv(filesPathNames[0], function(dataQ1){
d3.csv(filesPathNames[1], function(dataQ2){
d3.csv(filesPathNames[2], function(dataQ3){
d3.csv(filesPathNames[3], function(dataQ4){
But both of these methods seem to not work due to the fact that I can't simply add one CSV value to another. So I think where I'm having an issue is that I'm not sure how to concatenate dataQ1, dataQ2, dataQ3, and dataQ4 properly.
Is the only solution to manually append one to another with an added value of Q1, Q2, Q3, and Q4 as the time dimension?
Like Lars said, you can use the queue library. Here is an example of how this might work:
Step 1) Queue up your files:
<script type="text/javascript" src="http://d3js.org/queue.v1.min.js"></script>
var q = queue()
.defer(d3.csv, "data/first-quarter")
.defer(d3.csv, "data/second-quarter");
Step 2) Wait for the files to load:
q.await(function(error, q1data, q2data) {
Step 3) Add the data to crossfilter:
var ndx = crossfilter();
ndx.add(q1data.map(function(d) {
return { callTypes: d['Call Types'],
callDesc: d['Call Description'],
callVol: d['Call Volume'],
quarter: 'Q1'};
}));
ndx.add(q2data.map(function(d) {
return { callTypes: d['Call Types'],
callDesc: d['Call Description'],
callVol: d['Call Volume'],
quarter: 'Q2'};
}));
Step 4) Use your cross filter as you wish:
var timeDimension = ndx.dimension(function(d){
return d.quarter;
});
dataTable
... //data table attributes
dc.renderAll();
Here is an example using this approach with the dc.js library: https://github.com/dc-js/dc.js/blob/master/web/examples/composite.html
I've read through the Highcharts how-to, checked the demo galleries, searched google, read the X amount of exact similar threads here on stackoverflow yet I cannot get it to work.
I'm logging data in a csv file in the form of date,value.
Here's what the date looks like
1355417598678,22.25
1355417620144,22.25
1355417625616,22.312
1355417630851,22.375
1355417633906,22.437
1355417637134,22.437
1355417641239,22.5
1355417641775,22.562
1355417662373,22.125
1355417704368,21.625
And this is how far I've managed to get the code:
http://jsfiddle.net/whz7P/
This renders a chart, but with no series or data at all. I think I'm fudging things up while formatting the data so it can be interpreted in highcharts.
Anyone able to give a helping hand?
So, you have the following data structure, right ?
1355417598678,22.25
1355417620144,22.25
1355417625616,22.312
1355417630851,22.375
1355417633906,22.437
1355417637134,22.437
1355417641239,22.5
1355417641775,22.562
1355417662373,22.125
1355417704368,21.625
Then you split it into an array of lines, so each array item is a line.
Then for each line you do the following.
var items = line.split(';'); // wrong, use ','
But there ins't ; into the line, you should split using ,.
The result will be a multidimencional array which each item is an array with the following structure. It will be stored in a var named data.
"1355417598678","22.25" // date in utc, value
This is the expected data for each serie, so you can pass it directly to your serie.
var serie = {
data: data,
name: 'serie1' // chose a name
}
The result will be a working chart.
So everything can be resumed to the following.
var lines = data.split('\n');
lines = lines.map(function(line) {
var data = line.split(',');
data[1] = parseFloat(data[1]);
return data;
});
var series = {
data: lines,
name: 'serie1'
};
options.series.push(series);
Looking at your line.split part:
$.get('data.csv', function(data) {
// Split the lines
var lines = data.split('\n');
$.each(lines, function(lineNo, line) {
var items = line.split(';');
It looks like you are trying to split on a semi-colon (;) instead of a comma (,) which is what is in your sample CSV data.
You need to put
$(document).ready(function() {
in the 1st line, and
});
in the last line of the javascript to make this work.
Could you upload your csv file? Is it identical to what you wrote in your original post? I ran into the same problem, and it turns out there are errors in the data file.