Split stream (or path) into segments with D3JS - d3.js

Please consider this data:
var data = [];
data.segments = [
{ "id": "A", "start": 0, "end": 4},
{ "id": "B", "start": 5, "end": 9},
{ "id": "C", "start": 10, "end": 14},
];
data.stream = [
[ 0, 0, 0, 0, 0,
65, 60, 75, 85, 60,
20, 30, 20, 25, 15,
],
];
I want to display it as three distinct parts, where the A segment (ie: the first 5 entries in the stream) would be red (or whatever the color), the B segment (the middle 5 entries) green and the C segment (the last 5 entries) blue.
Here's what it would look like with help from a photo-editing program:
So far, I'm able to display data.stream as a stream. However, I'm stuck at breaking it into segments.
If my data was structured differently (as in this question), things would be easier. However, the way the data is structured right now is sort of ideal at it lets me separate the segment definitions from the stream data. This is useful as I want to be able to use different segments down the line. (You can look at those segments as sounds or words inside of an audio. Sometimes I would focus on individual sounds, sometimes on individual words, but the stream would always be the same.)
I put a working demo on JSFiddle here: http://jsfiddle.net/vsFhf/
How can I color the different parts of the stream?
Let me know if you need more details.
Thank you for the help-
Fabien

No matter what, you still need individual <path> elements for each segment. You could construct a segmented data array as #ValarDohaeris suggests. But, you can also do it without transforming the data:
Instead of binding to data.stream, you need to bind to data.segments, which will enable you to create that one <path> per segment. Then you call pathGenerator for each of those <paths>, passing in a slice of the stream you're rendering data.streams[0]. You'll also need to X-translate each <path> to the appropriate position, using your x scale function.
Here's the modified fiddle.

Would it help to split the data according to your segment definitions?
var segmentdata = data.segments.map(function(segment, i) {
return data.stream[0].slice(segment.start, segment.end + 1);
});
This will create:
segmentdata = [[0,0,0,0,0], [65,60,75,85,60], [20,30,20,25,15]]

Related

How can I color my scatter using a vector with values?

Well, I want to color my scatter using a vector with values. Actually, I want to use other dimension than the one used for creating the scatter.
Using these lines it gives a color to my scatter using the values given by the dimension that scatter is built on.
.colorAccessor(function(d) {return d.key[1]})
.colors(d3.scaleSequential(d3.interpolateOranges))
.colorDomain(y_range)
y_range = [y_min, y_max]
I tried to include the column for color in the dimension of the scatter, but it slows down the process of filtering. Something like this:
scatterDim = crossFilter.dimension(function(d) { return [d[it.variable[0]], d[it.variable[1]], d[it.color]]})
.colorAccessor(function(d) {return d.key[2]})
.colors(d3.scaleSequential(d3.interpolatePlasma))
.colorDomain([colorUnits[0], colorUnits[colorUnits.length - 1]]),
I want to have a different dimension for color:
colorDimension = crossFilter.dimension(function (d) { return d[it.color] }),
colorGroup = colorDimension.group().reduceCount(),
colorAll = colorGroup.all(),
colorUnits = [],
count = 0;
for(var color in colorAll)
{
colorUnits[count] = colorAll[color].key;
count++;
}
.colorAccessor(//some different code for my vector colorUnits or even for dimension?!//)
.colors(d3.scaleSequential(d3.interpolatePlasma))
.colorDomain([colorUnits[0], colorUnits[colorUnits.length - 1]]),
I would also like to know how to use scaleOrdinal for color. In case that the vector colorUnits contains strings.
The name "dimension" is a little confusing in crossfilter and dc.js. It isn't used to describe the "Y" (aggregated) values, or the color.
It really means, "I want to bin my data by this key, and filter on it."
The reason you will find color as a third element in dimension keys in many examples is that it's expedient. It's easier to change the keys than the aggregated values. But it doesn't really make sense.
The fact that your chart got slower when you added color to your dimension key tells me that you don't have a unique color for each X/Y pair. Instead of drawing a dot for each X/Y pair, you end up with a dot for each X/Y/color triplet.
You also don't need to create a separate color dimension unless you want to bin, aggregate, or filter on color.
Assuming you only want one dot per X/Y pair, you need to decide which color to use. Then you can change the reduction, instead of the key, to add this data:
scatterDim = crossFilter.dimension(function(d) {
return [d[it.variable[0]], d[it.variable[1]]];
}),
scatterGroup = scatterDim.group().reduce(
function(p, v) { // add
p.count++; // reduceCount equivalent
p.color = combine_colors(p.color, v[it.color]);
return p;
},
function(p, v) { // remove
p.count--;
// maybe adjust p.color
return p;
},
function() { // init
return {count: 0, color: null};
}
);
If you don't care which of the colors is used, you don't need combine_colors; just use v[it.color]. Otherwise, that's something you need to decide based on your application.
Now the scatter group has objects as its values, and you can change the scatter plot to take advantage of them:
scatterPlot
.existenceAccessor(d => d.value.count) // don't draw dot when it is zero
.colorAccessor(d => d.value.color)
If in fact you do want to draw all the dots with different colors, for example using opacity to allow overplotting, you probably need a canvas implementation of a scatter plot, because SVG is only good up to thousands of points. There is one in the works for dc.js but it needs to be ported to the latest APIs.
I would also like to know how to use scaleOrdinal for color. In case that the vector colorUnits contains strings.
Not sure what you mean here. scaleOrdinal takes strings as its domain, so
.colors(d3.scaleOrdinal(colorUnits, output_colors))
should work?
Example
Since I'm failing to communicate something or another, here is an example. The color strings come from an array since I don't have an example of your data or code:
const names = ["Zero", "One", "Two", "Three", "Four", "Five"];
speedSumGroup = runDimension.group()
.reduce(
function(p, v) { // add
p.count++; // reduceCount equivalent
p.color = names[+v.Expt];
return p;
},
// ... as before
);
chart
.colorAccessor(d => d.value.color)
.colors(d3.scaleOrdinal(names, d3.schemeCategory10))
Once again, if the method isn't working for you, the best way to figure it out is to log speedSumGroup.all(). I get:
[
{
"key": [
1,
850
],
"value": {
"count": 1,
"color": "One"
}
},
{
"key": [
1,
880
],
"value": {
"count": 1,
"color": "Three"
}
},
{
"key": [
1,
890
],
"value": {
"count": 2,
"color": "Five"
}
},
// ...
]
Example fiddle.

Getting custom colors and thresholds to work with D3 RelationshipGraph

Based on this example:
https://cdn.rawgit.com/hkelly93/d3-relationshipGraph/master/examples/index.html
D3 should allow me to create this chart and define colors and thresholds for when values change color. The function accepts some custom settings:
var graph = d3.select('#graph').relationshipGraph({
maxChildCount: 10,
valueKeyName: 'Story title',
thresholds: [6, 8, 10],
colors: ['red', 'yellow', 'green'],
showTooltips: true
})
But I'm not getting a graph with three colors when I load data fitting into all 3 ranges. I want 0-6 to appear red, 7-8 to appear yellow, and 9-10 to appear green. Here's the data loaded (excerpt):
[
{"parent": "2012-October", "organization": "WEWASAFO", "value": 10, "Story title": "NUTRITION"},
{"parent": "2012-April", "organization": "Jitegemee", "value": 5, "Story title": "Life in the street"},
{"parent": "2011-May", "organization": "KENYA YOUTH BUSINESS TRUST (KYBT)", "value": 2, "Story title": "BUSINESS"}
]
Everything else parses correctly, except combining custom colors an custom thresholds on the same chart. Either one alone works, but not both together.
The source repo is here with some docs:
https://github.com/hkelly93/d3-relationshipgraph
From that documentation:
thresholds: [100, 200, 300], // The thresholds for the color changes. If the values are strings, the colors are determined by the value of the child being equal to the threshold. If the thresholds are numbers, the color is determined by the value being less than the threshold.
colors: ['red', 'green', 'blue'], // The custom color set to use for the child blocks. These can be color names, HEX values, or RGBA values.
It doesn't explicitly state that the child colors correspond to the order that the thresholds appear. And all blocks appear red in this example.
I tested the code here: https://jsfiddle.net/cgrx3e9m/
This turned out to be a bug in the module itself. I notified the author and he fixed the way it sorts thresholds so it matches up with corresponding colors now.

Handling Category Collisions

Fiddle: https://jsfiddle.net/h7zf55b0/
I have a graph I am trying to design with the given data. The problem is I am getting category collision as its possible that multiple data points have the same date (which I use as the category axis).
{
'data': "18.46",
'date': "2015-12-31T09:36:00.0000000Z",
'type': "bid"
},
{
'data': "40.3918",
'date': "2015-12-31T09:36:00.0000000Z",
'type': "ask"
},
amcharts is handling this by drawing the columns over each other as shown in the fiddle.
Is there a way to make amcharts put these columns next to each other? Or stack them?
Ideally I want to be able to specify 'type' as a grouped element or something, so that the columns are labeled "40.3918 ASK 9:36am"
On date-based category axis, the chart tries to retain natural time scale. So if you have several columns on the same category, it draws them over each other.
You can disable this functionality by setting equalSpacing: true in your categoryAxis.
This way the chart rather than putting columns where they are supposed to be in time, will put each data point consecutively, regardless of the order and distance in time between them.
"categoryAxis": {
"gridPosition": "start",
"parseDates": true,
"equalSpacing": true,
"minPeriod": '60ss',
"axisAlpha": 0,
"gridAlpha": 0,
"position": "left"
}
As for what is shown in the balloons, you can use balloonText or, if you need some very custom functionality to generate balloon data balloonFunction.
Another option would be to combine those data points into same-category data points, as well as define a graph for each type:
https://jsfiddle.net/h7zf55b0/1/

d3.js force layout - filter and restoring nodes by attribute

I have been struggling with this issue for a long time with very little progress. I was going to put my code up here but it becomes quite long and convoluted and I'm still not sure I am even taking the correct approach so I thought backing up, showing the data and saying what I want to accomplish would be a approach to take.
Basically my goal is to create a d3 force layout. All data will be 'hard coded' into the page. I have done some network analysis on the nodes and have included those metrics in the dataset (eigenvector, betweeness, etc.) I want to be able to create the network vis, and have sliders that I can use to filter the network down by the various metrics. In other words I have a range slider that is set to the min and max of the network's “degree” metric (as an example) and as I adjust the slider values the network filters the nodes that fall outside those values. I want to be able to filter these nodes out and back in.
I've seen a number of examples of filtering and most are concerned with reducing the network and don't speak to the restoring the nodes. My attempts have resulted in either nothing happening, or multiple copies of existing nodes being created, or any number of behaviors, but not what I'm after. There is so many ways to 'skin the cat' in d3 that I keep going down paths that don't allow me (or I'm just not able to understand) to control the filtering the way I want.
I don't want to just control the visibility, I want the nodes to be removed and restored completely, and for the network to readjust smoothly.
Here is a sample of the data that I am using ...
var dataset = {
“directed”: false,
“graph”: [],
“nodes”: [
{
“category”: “new”,
“eigen”: 0.05923,
“between”: 0.0,
“close”: 0.25265,
“deg”: 1,
“id”: “Name1”,
“uid”: 100006190145565
},
{
“category”: “known”,
“eigen”: 0.00411,
“between”: 0.002002792177543483,
“close”: 0.19151,
“deg”: 3,
“id”: “Name2”,
“uid”: 100002598631097
},
{
“category”: “new”,
“eigen”: 0.0,
“between”: 0.0,
“close”: 0.06203,
“deg”: 1,
“id”: “Name3”,
“uid”: 727631862
},
{
“category”: “new”,
“eigen”: 0.00725,
“between”: 0.0,
“close”: 0.21037,
“deg”: 1,
“id”: “Name4”,
“uid”: 100008585823128
},
],
“links”: [
{
“source”: 0,
“target”: 1
},
{
“source”: 0,
“target”: 1
},
{
“source”: 0,
“target”: 3
}
],
“multigraph”: false
};
I can supply some of my code as well, but I think it would simply muddy the discussion as I have tried multiple approaches, none of which worked well, each of which just seemed to confuse me further when I would achieve partial results. Any help you can provide would be greatly appreciated.

How to handle duplicate values in d3.js

First I'm a d3.js noob :)
How you can see from the title I've got a problem with duplicated data and aggregate the values is no option, because the name represent different bus stops. In this example maybe the stops are on the fron side and the back side of a building.
And of course I like to show the names on the x-axis.
If i created an example and the result is a bloody mess, see jsFiddel.
x = index
name = bus stop name
n = value
I've got a json e.g.:
[{
"x": 0,
"name": "Corniche St / Abu Dhabi Police GHQ",
"n": 113
},
{
"x": 1,
"name": "Corniche St / Nation Towers",
"n": 116
},
{
"x": 2,
"name": "Zayed 1st St / Al Khalidiya Public Garden",
"n": 146
},
...
{
"x": 49,
"name": "Hamdan St / Tariq Bin Zeyad Mosque",
"n": 55
}]
The problem: It is possible that the name could appear more then once e.g.
{
"x": 1,
"name": "Corniche St / Nation Towers",
"n": 116
}
and
{
"x": 4,
"name": "Corniche St / Nation Towers",
"n": 105
}
I like to know is there a way to tell d3.js not to "delete" duplicated names and instead just show all names in sequence with their values.
Any ideas or suggestions are very welcome :) If you need more information let me know.
Thanks in advanced
Mario
Lars is right: the d3.ordinal scale is doing exactly what it should: treating duplicate values as repeat instances. See here for more details: https://github.com/mbostock/d3/wiki/Ordinal-Scales
You can use a regular linear scale instead, like this: http://jsfiddle.net/vy8vjy4r/2/
The changes are to make the scale linear and set the domain to be the length of your dataset.
var x = d3.scale.linear().domain([0,j_data.length]).range([0, width]),
When you pass a value to the scale, you simply pass the position in the list. I'm using the index - the i in function(d,i) - but you could have used the x in your dataset. (I didn't use it as it looks like you don't need it.)
.x(function (d,i) { return x(i); })
Hopefully this works for you.
Additional information on axis
Strictly speaking, I guess this should have been an additional question, but to get the text on the axis, you can simply add these two lines of code in where you modify the text in xAxisGroup, after .selectAll("text"):
.data(j_data.filter(function(d,i) { return !(i%5); }))
.text(function(d){ return d.name; })
The axis is displaying numbers every fifth item, so we choose every fifth item from the dataset. This gives us data that matches the existing labels, and we change the text to the .name value, see http://jsfiddle.net/vy8vjy4r/4/
This approach isn't particularly strong: it depends on D3 displaying every fifth stop, and for short or very long routes (or whatever these are) it might display all stops, or every tenth, etc. I would rather not use the D3 axis and build your own. For something like this, it shouldn't be too hard, although fitting all the names in might be hard in this space.
Try this: http://jsfiddle.net/vy8vjy4r/5/
Try this filter,
var names = [];
var result = [];
var indx=-1;
for(var i=0; i< j_data.length; i++){
indx = names.indexOf(j_data[i].name);
if(indx==-1){
names.push(j_data[i].name);
result.push(j_data[i]);
}
}
j_data= result;
Do this after your j_data array, it'll remove the duplicated objects from your j_data array. And see this http://jsfiddle.net/vy8vjy4r/1/
If it is not, what you are looking for, ask what change you need.

Resources