I need to add several vertical lines (say 10 or 20) to an nvd3 line chart.
The question here suggests adding a series for this, but I would need to add 20 series, overcrowding the legend and the interactive tooltip.
From what I understand this can't be done out-of-the-box (please correct me if I'm wrong), so my question is what is the easiest way of doing this:
Add D3 lines to the DOM (how would I go about scale, positioning them horizontally, etc?)
Add generic support for this in nvd3
Add support for hiding specific series from the legend and from the tooltip, and add 20 series
Any other idea?
Well, it turns out that it wasn't that hard. I chose option #3, and the following code changes to nv.d3.js got the job done:
In the legend model, change
function chart(selection) {
selection.each(function(data) {
... to
function chart(selection) {
selection.each(function(dataUnfiltered) {
var data = dataUnfiltered.filter(function (d) {
return !d.disableLegend;
});
and in the lineChart model, change:
interactiveLayer.dispatch.on('elementMousemove', function(e) {
lines.clearHighlights();
var singlePoint, pointIndex, pointXLocation, allData = [];
data
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled;
})
... to
interactiveLayer.dispatch.on('elementMousemove', function(e) {
lines.clearHighlights();
var singlePoint, pointIndex, pointXLocation, allData = [];
data
.filter(function(series, i) {
series.seriesIndex = i;
return !series.disabled && !series.disableTooltip;
})
(Obviously this second change would have to be done to each chart model you want to support, say also cumulativeLineChart and `stackedAreaChart).
This will enable you to specify, in addition to color, key, values, etc. also disableTooltip: true and/or disableLegend: true.
Hope this helps someone.
Related
https://blockbuilder.org/ninjakx/63295ea0a8052716644738d37d390e52
1)
When I click on focus ordinal bar((c2 of composite chart) it should keep the selected one as red and other as grey but it doesn't.
2)
When I click on pie chart I get red bars along with unfiltered bar(grey). Here clicking on red bar should filter other graphs it's doing that as you can see my table and pie chart is getting updated but When I click on gray bar data is also getting filtered but for pie chart it just add grey slices.
Line no. 284-324:
chart_11.fadeDeselectedArea = function (brushSelection) {
var _chart = this;
var bars = _chart.chartBodyG().selectAll('rect.bar');
if (chart_11Filter.length) {
bars.classed(dc.constants.SELECTED_CLASS, function (d) {
return chart_11Filter.includes(d.data.key);
});
bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
return !chart_11Filter.includes(d.data.key);
});
} else {
bars.classed(dc.constants.SELECTED_CLASS, false);
bars.classed(dc.constants.DESELECTED_CLASS, false);
}
};
chart_11.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
var i = chart_11Filter.indexOf(d.data.key);
if(i >= 0)
chart_11Filter.splice(i, 1);
else
chart_11Filter.push(d.data.key);
chart.applyFilter();
chart.redrawGroup();
});
});
If I use the above code then I get these things So I can think of these solutions.
I can change the colour of c2 bar on clicking by using the above code and applying it to c2.
also for the second graph I can use css to disable on clicking them or I
can make the filter to return none.
But when I tried the above solutions It didn't work. Problems were still the same.
If I make this function applicable only for c2 by replacing chart_11 with c2:
chart_11.fadeDeselectedArea = function (brushSelection) {
.
.
.
.
chart_11.on('pretransition', function(chart) {
.
.
.
I get this:
Edit:
chart_11.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click', null);
If I add this I will be able to disable clicking all bar. I have to make it only for C1.
chart_11.on('pretransition', function(chart) {
// chart.selectAll('rect.bar').on('click', null);
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
In this function my 2nd issue can be solved I guess. This function has to be customized. Accessing the child C2 and select its rect.bar and filter.
But unable to write the code for it.
This is getting to be a very hacky solution, combining two already hacky customizations of dc.js.
However, you weren't very far off; it is just a matter of restricting behaviors to c2 and cleaning out some irrelevant code.
I removed hide_second_chart because that's not necessary here, and removed the filterHandler for the same reason.
As you pointed out, fadeDeselectedArea has to be overridden on the parent; for some reason it doesn't fire on the children.
But this selection was empty, so nothing happened:
var bars = _chart.chartBodyG().selectAll('rect.bar');
I changed it to
var bars = c2.selectAll('rect.bar');
Also, the click handler should be specific to the second child, so this
chart_11.on('pretransition', function(chart) {
chart.selectAll('rect.bar').on('click.ordinal-select', function(d) {
becomes
c2.on('pretransition.click-handler', function(chart) {
chart.selectAll('.sub._1 rect.bar').on('click.ordinal-select', function(d) {
.sub._1 is CSS that will select only the second child chart.
We can use similar CSS to disable hover behaviors on the first child chart:
.dc-chart .sub._0 rect.bar:hover {
fill-opacity: 1;
}
.dc-chart .sub._0 rect.bar {
cursor: pointer;
}
Enable filterAll, as discussed in Unable to reset the focus ordinal bar chart:
chart_11.filterAll = function() {
chart_11Filter = [];
chart_11.filter(null);
};
Finally, it is confusing if the unfiltered chart is not the same color as deselected bars, so we change grey to #ccc:
.colors('#ccc')
Working fork of your block.
Hopefully the range/focus part still works, because otherwise this is making things much more complicated than they need to be!
I'm trying to create a bubble chart with dc.js that will have a bubble for each data row and will be filtered by other charts on the same page. The initial bubble chart is created correctly, but when items are filtered from another chart and added or removed from the group it looks like they are being applied to the wrong group. I'm not sure what I'm messing up on the grouping or dimensions. I've created an example fiddle here
There's simple pie chart to filter on filterColumn, a bubble chart that uses identifer1, a unique field, as the dimension and xVal, yVal, and rVal to display the data, and a dataTable to display the current records.
I've tried other custom groups functions, but switched to the example from the FAQ and still had problems.
var
filterPieChart=dc.pieChart("#filterPieChart"),
bubbleChart = dc.bubbleChart('#bubbleChart'),
dataTable = dc.dataTable('#data-table');
var
bubbleChartDim=ndx.dimension(dc.pluck("identifier1")),
filterPieChartDim=ndx.dimension(dc.pluck("filterColumn")),
allDim = ndx.dimension(function(d) {return d;});
var filterPieChartGroup=filterPieChartDim.group().reduceCount();
function reduceFieldsAdd(fields) {
return function(p, v) {
fields.forEach(function(f) {
p[f] += 1*v[f];
});
return p;
};
}
function reduceFieldsRemove(fields) {
return function(p, v) {
fields.forEach(function(f) {
p[f] -= 1*v[f];
});
return p;
};
}
function reduceFieldsInitial(fields) {
return function() {
var ret = {};
fields.forEach(function(f) {
ret[f] = 0;
});
return ret;
};
}
var fieldsToReduce=['xVal', 'yVal', 'rVal'];
var bubbleChartGroup = bubbleChartDim.group().reduce(
reduceFieldsAdd(fieldsToReduce),
reduceFieldsRemove(fieldsToReduce),
reduceFieldsInitial(fieldsToReduce)
);
filterPieChart
.dimension(filterPieChartDim)
.group(filterPieChartGroup)
...
;
bubbleChart
.dimension(bubbleChartDim)
.group(bubbleChartGroup)
.keyAccessor(function (p) { return p.value.xVal; })
.valueAccessor(function (p) { return p.value.yVal; })
.radiusValueAccessor(function (p) { return p.value.rVal; })
...
;
This was a frustrating one to debug. Your groups and reductions are fine, and that's the best way to plot one bubble for each row, using a unique identifier like that.
[It's annoying that you have to specify a complicated reduction, when the values will be either the original value or 0, but the alternatives aren't much better.]
The reductions are going crazy. Definitely not just original values and zero, some are going to other values, bigger or negative, and sometimes clicking a pie slice twice does not even return to the original state.
I put breakpoints in the reduce functions and noticed, as you did, that the values were being removed from the wrong groups. How could this be? Finally, by logging bubbleChartGroup.all() in a filtered handler for the pie chart, I noticed that the groups were out of order after the first rendering!
Your code is fine. But you've unearthed a new bug in dc.js, which I filed here.
In order to implement the sortBubbleSize feature, we sort the bubbles. Unfortunately we are also sorting crossfilter's internal array of groups, which it trusted us with. (group.all() returns an internal data structure which must never be modified.)
The fix will be easy; we just need to copy the array before sorting it. You can test it out in your code by commenting out sortBubbleSize and instead supplying the data function, which is what it does internally:
bubbleChart.data(function (group) {
var data = group.all().slice(0);
if (true) { // (_sortBubbleSize) {
// sort descending so smaller bubbles are on top
var radiusAccessor = bubbleChart.radiusValueAccessor();
data.sort(function (a, b) { return d3.descending(radiusAccessor(a), radiusAccessor(b)); });
}
return data;
});
Notice the .slice(0) at the top.
Hope to fix this in the next release, but this workaround is pretty solid in case it takes longer.
Here is a fiddle demonstrating the workaround.
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/
I want to implement motion charts similar to http://bost.ocks.org/mike/nations/ but using nvd3.js. I took the example scatter/bubble chart from nvd3.org and added one additional key to the data. I use this key as a frame number for the animation. you can find it here: http://jsfiddle.net/bartiosze/ny6vnznn/.
My idea was to change the data for the chart in a function called from .tween:
function drawFrame(idx) {
var data = _.where(dataset, {
index: idx
});
d3.select('#chart svg').datum(data)
.transition().duration(100).call(window.chart.update);
}
function tweener() {
var idx = d3.interpolateNumber(0, 9);
return function (t) {
drawFrame(Math.round(idx(t)));
};
}
//should this work?
d3.select('#chart svg')
.transition().duration(10000).ease("linear").tween(".nv-clip-points", tweener);
It might be a general d3 question, but am I missing something?
I'm new to crossfilter.js. Whenever I used the range selector I get the strange blocky highlighted area on my bar chart outside of my filter range. I can't figure out what I'm doing wrong.
How I can prevent this blocky section from highlighting outside my range?
d3.csv("mydata.csv", function(data) {
data.forEach(function(d) {
d.conf = d3.round(+d.Conf,1);
console.log(d.conf);
});
var facts = crossfilter(data); // Put data into crossfilter
var confDim = facts.dimension(function (d) { return d.conf;}); // conf # filter
var confTotal = confDim.group().reduceCount(function (d) { return d.conf;});
var confChart = dc.barChart("#conf-chart");
confChart
.width(500).height(200)
.dimension(confDim)
.group(confTotal,"Confidence Number")
.x(d3.scale.linear().domain([0,5]).range([10,400]))
.yAxisLabel("Number of data points")
.brushOn(true);
dc.renderAll();
});
This line that you've commented as being a filter is actually just setting up the dimension in its entirety:
var confDim = facts.dimension(function (d) { return d.conf;}); // conf # filter
You need to define a filter using one of the filter functions. See the documentation here.
Be careful to read the documentation for dimension.group carefully as it contains a gotcha where the filter on the dimension being grouped won't be applied. To work around this you may need to actually define 2 dimensions that are identical and use one for filtering and the other for grouping.