DC.js conditional changing of bar chart colours - dc.js

I was wondering if anyone knew of any way to setting a bar colour to a certain colour based on a certain condition. So, for instance, say the value passes a certain predefined threshold, then that bar would change colour to red to indicate that it needed attention. Anyone have any clue how this would be done, or if it even can be done?

Doesn't matter, figured it out.
chart
.colorAccessor(function (d) {
if (d.value > 12) {
return "test1";
}
if (d.value > 9) {
return "test2";
}
if (d.value > 6) {
return "test3";
}
if (d.value > 3) {
return "test4";
}
})
.colors(d3.scale.ordinal().domain(["test1", "test2", "test3", "test4"])
.range(["red", "orange", "yellow", "green"]))
So you can have as many different value thresholds as you want, and a different colour for each threshold.

Related

dc.js exclude the brushed area and highlight rest

I'm not data-viz expert or d3, I have found plenty of examples to how to build brushing and zoom for example Mike.
They all have shown how to filter to the brushed area but I want to achieve to reverse of that effect, how?
Can someone through me ideas how to achieve it?
I don't know why I assumed you meant a bar chart when you linked to an area chart. You can ignore the highlighting section and skip to filtering if you're interested in doing this with line charts. There is no highlighting of line chart, just the brush itself.
Highlighting the bars in reverse
This isn't all that hard, but it's somewhat messy because we replace an undocumented function in the chart. Like most things in dc.js, if there isn't an option, you can usually replace the functionality (or add or change stuff once the chart has rendered/drawn).
Here there's a specific, public function which fades the deselected areas. It's called fadeDeselectedArea. (Actually it both fades and un-fades when the chart is ordinal, but we'll ignore that part.)
The original function looks like this:
_chart.fadeDeselectedArea = function () {
var bars = _chart.chartBodyG().selectAll('rect.bar');
var extent = _chart.brush().extent();
if (_chart.isOrdinal()) {
if (_chart.hasFilter()) {
bars.classed(dc.constants.SELECTED_CLASS, function (d) {
return _chart.hasFilter(d.x);
});
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return !_chart.hasFilter(d.x);
});
} else {
bars.classed(dc.constants.SELECTED_CLASS, false);
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
} else {
if (!_chart.brushIsEmpty(extent)) {
var start = extent[0];
var end = extent[1];
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return d.x < start || d.x >= end;
});
} else {
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
}
};
source link
We'll ignore the ordinal part because that's only individual selection, not brushed selection. Here is the reverse of the second part:
spendHistChart.fadeDeselectedArea = function () {
var _chart = this;
var bars = _chart.chartBodyG().selectAll('rect.bar');
var extent = _chart.brush().extent();
// only covering the non-ordinal (ranged brush) case here...
if (!_chart.brushIsEmpty(extent)) {
var start = extent[0];
var end = extent[1];
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return d.x >= start && d.x < end;
});
} else {
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
};
Creating a variable _chart is just to keep the code the same as much as possible. You can see that d.x >= start && d.x < end is exactly the opposite of d.x < start || d.x >= end
Reversing the filtering
We'll need to add a filterHandler to the chart in order to reverse the filtering. Again, we'll base it off the default behavior, but here there's a legitimate customization point so we don't have to replace a function, just supply one:
spendHistChart.filterHandler(function(dimension, filters) {
if(filters.length === 0)
dimension.filter(null);
else {
// assume one RangedFilter but apply in reverse
// this is less efficient than filterRange but it shouldn't
// matter much unless the data is huge
var filter = filters[0];
dimension.filterFunction(function(d) {
return !filter.isFiltered(d);
})
}
});
Again, we cut out the cases we don't care about. There is no reason to be general about something that has a specific purpose and it will only cause maintenance problems. The only two cases we care about are no filter and one range filter.
Here the RangedFilter already supplies a filter function, so we can just call it and not (!) the result. This will be slightly less efficient than the filterRange but crossfilter has no native support for multiple ranges (or the inverse of a range).
That's it! Fiddle here: http://jsfiddle.net/gordonwoodhull/46snsbc2/8/

d3 append invisible if data is not same as previous

d3 newb is trying something else:
I want to add a date label to my bar chart only if the date is not the same as in the previous bar.
bar.append("text")
.attr("visibility", function(d,i){
if(d.Datum == data[i-1].Datum) return "hidden"})
.attr("x", padding)
.attr("y", barHeight-2)
.attr("fill", "Gainsboro")
.text(function(d){ return d.Datum})
So I thought I add a visibility to my text label. however I cannot access the previous date from the data-object... probably this is an easy one for someone not newbie as me...
data example (before CSV import)
Datum,Name,Kategorie,Betrag
01/10/15,,Lohn,1586.7
02/10/15,,lunch,-4.55
So assuming that after d3 parses the text file you are left with data like:
var data = [
{
Datum: "01/10/15",
Name: "",
Kategorie: "Lohn",
Betrag: 1586.7
},{
...
}
];
I'd pre-process the data to contain a bool about whether or not it is the first instance of that date:
data.forEach(function(d,i){
d.isFirstInstanceOfDate = (i === 0 || d.Dataum !== data[i-1].Datum);
});
Then assuming that bar is a selection of gs elements (which already contain a rect), I'd filter them and only append the text on the first instance:
bar
.filter(function(d){
return d.isFirstInstanceOfDate
})
.append('text')
...
The index starts from 0. By subtracting 1 from it the first time you get an error. You need to check whether i > 0 so that you don't do out of bounds.
For example:
.style('visibility', function (d, i) {
if (i > 0) {
if (d.datetime === data[i - 1].datetime) {
return 'hidden';
}
}
return 'visible';
});
Here are two sample fiddles:
http://jsfiddle.net/00drii/y079vw7e/
http://jsfiddle.net/00drii/dmhs0gza/

How to set the width and height of a dcjs Heatmap based on the filtered group?

In the sample of my code below, I want to set the width and height based on the number of values in the filtered group. Is there a best way to do this? Should I be tracking a count in the group add/remove functions?
myHeatMap
.width(45 * 20)
.height(45 * 30)
.dimension(heatDim)
.group(heatGroup)
.title(function (d) {
return "Value: " + d.value;
})
.keyAccessor(function (d) { return d.key[0]; })
.valueAccessor(function (d) { return d.key[1]; })
.colorAccessor(function (d) { return d.value; })
.colors(["#ffffd9", "#edf8b1", "#c7e9b4", "#7fcdbb", "#41b6c4", "#1d91c0", "#225ea8", "#253494", "#081d58"])
.calculateColorDomain();
This is an interesting question. In the general case, one would want the heatmap to change size when the number of rows or columns changes.
If you're not concerned with that, I think doing a count of of the elements is about the best you can do, with d3.set one easy way to get this:
var data = heatGroup.all();
var ncols = d3.set(data.map(function(x) { return x.key[0]; })).size();
var nrows = d3.set(data.map(function(x) { return x.key[1]; })).size();
myHeatMap.width(ncols*45).height(nrows*45)
Feel free to file a feature request, or better yet, a PR, if you'd like to see the more general case. The heatmap is already doing something like this internally, although it doesn't have to compute the unique set since it relies on the ordinal scale for that.

Resorting Zoomable Treemaps

This question is abstract, but I'm trying to merge two functions of separate treemaps in d3js, and am asking for an appropriate strategy.
In summary, I want to take the resorting ability of this treemap (count,size):
http://strongriley.github.io/d3/ex/treemap.html
And apply that to the zooming function of this treemap:
http://bost.ocks.org/mike/treemap/
The intention is to allow dynamic transitions for resorting, and then click on a cell to zoom into another tier of the data while maintaining the selected sort. If anyone has suggestions, your help would be much appreciated.
In summary, I'm trying to resort the zoomable treemap by replacing the treemap.values data with the accumulate function. Here's some sample data for a child:
{"name": "DataUtil", "value": 3322,"value1": 45,"value2": 87}
By clicking a button, the accumulate function needs to re-assign data based on value,value1,value2, etc. to the full hierarchy. Then, the visualization needs to be updated.
First attempt (not working) can be found on this JSFiddle:
function accumulate(d, boo) {
if (boo == 0) {
return (d._children = d.children) ? d.value = d.children.reduce(function (p, v) {
return p + accumulate(v, 0);
}, 0) : d.value1;
}
if (boo == 1) {
return (d._children = d.children) ? d.value = d.children.reduce(function (p, v) {
return 1 + accumulate(v, 1);
}, 0) : d.value2;
}
}

d3js highlighting connected nodes

I want to list all the nodes that are connected to a node that I rollover with a mouse in a text field, been trying to use the filter function and it sort of does what I need since it affects the stroke width of the linked objects but I'd like to also output the node names too.
.on("mouseover", function(da) {
link.filter(function(db) { return da.group == db.groupColor; })
.transition()
.style("stroke-width", 8);
selectedText.text("Currently Selected: "+da.name+"is connecting to sensors: "+link.filter(function(db) { return da.group == db.groupColor; }) );
right now I am getting an output of "bike brian is connected to sensors: [object svgline element]"
but what I want is to be able to return all the names of the other nodes... does that make sense? so that it might say "bike brian is connected to sensors: accelerometer, gps, etc..."
Assuming your links are using standard network syntax, you could do the following:
var filteredLinks = link.filter(function(db) { return da.group == db.groupColor; });
var sensorList = '';
for (x in filteredLinks) {
if (filteredLinks[x]) {
filteredLinks[x].source.id == originalSensorID ? sensorList += filteredLinks[x].target.id : sensorList += filteredLinks[x].source.id
}
}
You'll probably want to add commas and spaces to the sensorList variable. It's a bit cumbersome, but I wasn't able to make the .each() method work with a filter, so it's the best I can come up with.

Resources