d3js error on decrementing datapoints - how to resolve? [duplicate] - javascript

This question already has an answer here:
D3 update pie data with zero values
(1 answer)
Closed 8 years ago.
So, I have a series of datapoints with incrementing/decrementing counters which are graphed:
{id: 1, count: 1, abnormal: 0, visualisation_colour: #FFFFFF};
{id: 2, count: 2, abnormal: 0, visualisation_colour: #FFFFFF};
{id: 3, count: 3, abnormal: 0, visualisation_colour: #FFFFFF};
If I decrement the counters (count, and abnormal) to zero, then start incrementing them again, I get:
Error: Invalid value for <path> attribute
d="M0,-100A100,100 0 1,1 NaN,NaNLNaN,NaNA50,50 0 1,0 0,-50Z"
Which repeats for all the zero'd datapoints. How do I prevent this from occurring, as also once they have been zeroed out, incrementing them again fails too with the same error. I'm using the (now working) code referenced in this question

So, the answer basically encompasses #Lars Kotthoff's answer from here.
By bringing in the filter he uses:
if(data.filter(function(d) { return d.totalCrimes > 0; }).length > 0) {
Into the renderSlices function:
function renderSlices(pie, arc) {
var slices;
if (pie(_data).filter(function(d) {return d.value > 0;}).length > 0 ) {
slices = _pieG.selectAll("path.arc")
.data(pie(_data));
slices.enter()
.append("path")
.attr("class", "arc")
.attr("fill", function(d) {
return d.data.visualisation_colour;
});
slices.transition()
.attrTween("d", function(d) {
var currentArc = this.__current__;
if (!currentArc) {
currentArc = {startAngle: 0, endAngle: 0};
}
var interpolate = d3.interpolate(currentArc, d);
this.__current__ = interpolate(1);
return function(t) {
return arc(interpolate(t));
};
});
} else {
slices = _pieG.selectAll("path.arc")
.data(pie(_data));
slices.remove();
}
You can remove all the zero'd arcs, and clean up the graph.

Related

dc.js - Remove empty bins from stacked bar chart upon filtering

Here is the fiddle for a stacked bar chart. This chart filters another line chart.
To remove empty bins, I tried dc.js FAQ, this example and this.
I saw this and this for a stacked bar chart scenario, but my grouping is different.
I've tried different things but I'm not able to get it to work.
Pretty sure I'm missing something simple.
Kindly review my code. Am I doing something wrong? How do i get the remove_empty_bins() working?
var stack = dc.barChart('#stack');
var XDimension = ndx.dimension(function (d) {return d.no;});
var YDimension_before = XDimension.group().reduce(
function(p, d) {
p[d.sub_no] = (p[d.sub_no]|| 0) + +d.avg;
return p;
},
function(p, d) {
p[d.sub_no] = (p[d.sub_no]|| 0) - +d.avg;
return p;
},
function() {
return {};});
var YDimension = remove_empty_bins(YDimension_before);
stack.width(550)
.height(400)
.dimension(XDimension)
.group(YDimension, '1', sel_stack(1))
.transitionDuration(500)
.xUnits(dc.units.ordinal)
.x(d3.scaleBand())
.margins({left: 80, top: 20, right: 80, bottom: 80})
.brushOn(false)
.clipPadding(20)
.elasticX(true)
.elasticY(true)
.title(function(d) {
return [ d.key + '[' + this.layer + '] ',
d.value[this.layer]].join('\n')
});
stack.stack(YDimension, '2', sel_stack(2))
.stack(YDimension, '3', sel_stack(3))
function remove_empty_bins(source_group) {
return {
all:function () {
return source_group.all().filter(function(d) {
return d.value != 0;
});
}
};
}
I think the problem is that you are reducing to an object, so d.value never equals zero.
You could use Object.values and Array.some to check if any stack is non zero for each bin:
function remove_competely_empty_bins(source_group) {
return {
all:function () {
return source_group.all().filter(function(d) {
return Object.values(d.value).some(v => v!=0);
});
}
};
}
Warning: dc.js isn't happy if the different stacks don't have the same x values. So that's why you wouldn't want to remove just the empty stacks. Only remove the bin if all the stacks are zero.

D3 - Add event to bars

I'm trying to add an event to the bars in my graph. I tried this function bellow, but with this function it doesn't matter what bar I click, it will always return the last key in the array.
My guess would be this has to do with asynchronization, because it returns the last key value.
for (var key in data) {
bar = bars.append('rect')
.attr('class', 'bar')
.attr('x', (dimensions.width / data.length) * currentId + 41)
.attr('y', 100 - data[key] + 10).attr('height', data[key])
.attr('width', dimensions.width / data.length)
.attr('fill', '#4682B4')
.on('click', (bar = key) => {
console.log(key) //Always returns the same key
});
currentId++;
}
I have also tried to copy one of the keys the array contains and make a if-statement like this:
console.log(key === 1 ? true : false);
This will return true and false, exactly as it should. Another reason why I think this has to do with async.
My essential question is;
How can I make a click-event on this bar which will return the correct key
Before anything else: this is not the idiomatic way of adding events in D3. As a general rule, when you write a D3 code, you normally don't need any kind of loop. Of course, we use loops sometimes, but in very specific situations and to solve very specific problems. Thus, 98.57% of the loops found in D3 codes are unnecessary (source: FakeData Inc.), be it a for...in, a for...of or a simple for loop.
That being said, let's see what's happening here.
Your real problem has nothing to do with D3 or with async code. Actually, your problem can be explained by this excellent answer: JavaScript closure inside loops – simple practical example (I'll avoid closing this as a duplicate, though).
After reading the answer in the link above, let's see two demos.
The first one, using var. Please click the circles:
var data = [{
name: "foo",
value: 1
}, {
name: "bar",
value: 2
}, {
name: "baz",
value: 3
}];
var svg = d3.select("svg");
for (var key in data) {
var foo = key;//look at the var here
circle = svg.append("circle")
.attr("cy", 50)
.attr("fill", "teal")
.attr("cx", d=> 20 + key*50)
.attr("r", 15)
.on('click', () => {
console.log(foo)
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>
Now another one, using let, please click the circles and compare the results:
var data = [{
name: "foo",
value: 1
}, {
name: "bar",
value: 2
}, {
name: "baz",
value: 3
}];
var svg = d3.select("svg");
for (var key in data) {
let foo = key;//look at the let here
circle = svg.append("circle")
.attr("cy", 50)
.attr("fill", "teal")
.attr("cx", d=> 20 + key*50)
.attr("r", 15)
.on('click', () => {
console.log(foo)
});
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg></svg>

D3.js create a dynamic color() function

I am working on this pie-chart in D3.js.
This is the data:
DATA.JSON
[
{
"key":"amministrazione",
"categoria":"funzioni",
"val2015":404571081,
"val2013":374545999
},
{
"key":"sociale",
"categoria":"funzioni",
"val2015":235251679,
"val2013":258973653
},
{
"key":"territorio e ambiente",
"categoria":"funzioni",
"val2015":286164667,
"val2013":274949400
},
{
"key":"viabilità e trasporti",
"categoria":"funzioni",
"val2015":144185664,
"val2013":140619534
},
{
"key":"istruzione",
"categoria":"funzioni",
"val2015":168774925,
"val2013":170016208
},
{
"key":"cultura",
"categoria":"funzioni",
"val2015":55868045,
"val2013":55735535
},
{
"key":"sport",
"categoria":"funzioni",
"val2015":27219432,
"val2013":31244800
},
{
"key":"turismo",
"categoria":"funzioni",
"val2015":9544845,
"val2013":7674419
},
{
"key":"sviluppo economico",
"categoria":"funzioni",
"val2015":14790363,
"val2013":16635868
},
{
"key":"servizi produttivi",
"categoria":"funzioni",
"val2015":4334,
"val2013":4440
},
{
"key":"polizia locale",
"categoria":"funzioni",
"val2015":99007202,
"val2013":102065987
},
{
"key":"giustizia",
"categoria":"funzioni",
"val2015":12147068,
"val2013":12880138
},
{
"key":"anticipazioni di cassa",
"categoria":"rimborso prestiti",
"val2015":304323808,
"val2013":304323808
},
{
"key":"finanziamenti a breve termine",
"categoria":"rimborso prestiti",
"val2015":0,
"val2013":0
},
{
"key":"prestiti obbligazionari",
"categoria":"rimborso prestiti",
"val2015":38842996,
"val2013":36652213
},
{
"key":"quota capitale di debiti pluriennali",
"categoria":"rimborso prestiti",
"val2015":0,
"val2013":47152
},
{
"key":"quota capitale di mutui e prestiti",
"categoria":"rimborso prestiti",
"val2015":128508755,
"val2013":329885961
},
{
"key":"spese per conto terzi",
"categoria":"altro",
"val2015":232661261,
"val2013":236921438
},
{
"key":"disavanzo di amministrazione",
"categoria":"altro",
"val2015":0,
"val2013":0
}
]
It shows how the governmental budget is allocated to different functions (i.e. "key"). A value is given for each year (e.g. "val2015", "val2013") and each function is part of a macro-category (i.e. "funzioni", "rimborso prestiti", or "altro").
I am trying to create a color() function that dynamically changes its domain and range depending on:
the colorRange arbitrarily assigned as domain: greenRange for "funzioni", redRange for "rimborso prestiti" and blueRange for "altro"
the number of functions ("key") in each category that have a positive value, thus ignoring functions for which no resources were allocate during a given year. Done through the count() function (which works)
Then creates X number of shades for each ranging depending on the count() function of point 2
And assigns the appropriate color to each of the pie's wedges
This is my starting point:
var greenRange = ["rgb(199,233,192)", "rgb(0,68,27)"]; //range for the first 12 wedges of the pie (assuming they are all >0)
var redRange = ["rgb(252,187,161)", "rgb(103,0,13)"]; //range for the following 5 wedges of the pie (same assumption)
var blueRange = ["rgb(198,219,239)", "rgb(8,48,107)"]; //range for the last 3 wedges of the pie (same assumption)
I tried two options but neither works.
OPTION 1
function draw () {
//(1) count the number of data points with value > 0 in each category - This works well!
var countFunzioni=0;
dataset.forEach (function (d) {if (d.categoria=="funzioni" && d.val2015>0) { countFunzioni += 1;}})
var countRimborso=0;
dataset.forEach (function (d) {if (d.categoria=="rimborso prestiti" && d.val2015>0) { countRimborso += 1;}})
var countAltro=0;
dataset.forEach (function (d) {if (d.categoria=="altro" && d.val2015>0) { countAltro += 1;}})
//(2) create a color method for each category based on a the count calculated above and the range I determined
var colorFunzioni = d3.scale.linear()
.domain([0, countFunzioni])
.range(redRange);
var colorRimborso = d3.scale.linear()
.domain([0, countRimborso])
.range(redRange);
var colorAltro = d3.scale.linear()
.domain([0, countAltro])
.range(blueRange);
//draw the chart
chart = d3.select("#visualizationInner")
.append("svg")
.attr("id", "visualization")
.attr("width", w)
.attr("height", h)
.append("g")
.attr("transform", "translate(" + w / 2 + "," + h / 2 + ")");
//draw and color the paths
var path = chart.datum(dataset).selectAll("path")
.data(pie)
.enter()
.append("path")
//(3) return the appropriate color method depending on the datum's category
.attr("fill", function(d, i) {
if (d.data.categoria=="funzioni") {return colorFunzioni(i);}
else if (d.data.categoria=="rimborso prestiti") {return colorRimborso(i);}
else if (d.data.categoria=="altro") {return colorAltro(i);}
})
.style("fill-opacity", 0.75)
.attr("d", arc);
}
Which returns this result:
This goes close, however it assigns a range of red colors to the first 12 wedges (which should get the greenRange instead) and no color to the wedges pertaining to the other categoreis
OPTION 2
function draw () {
//(1) same as above
//(2) create a color method that adapts to each category's count and range
var color = d3.scale.linear()
.domain([0, function (d) {
if (d.data.categoria=="funzioni") {return countFunzioni;}
else if (d.data.categoria=="rimborso prestiti") {return countRimborso;}
else if (d.data.categoria=="altro") {return countAltro;}
}])
.range(function (d) {
if (d.cdata.ategoria=="funzioni") {return greenRange;}
else if (d.data.categoria=="rimborso prestiti") {return redRange;}
else if (d.data.categoria=="altro") {return blueRange;}
});
////(3) return the appropriate color method depending on the datum's category
.attr("fill", function(d, i) {return color(i);}
}
This does not get any coloring done.
Any idea how to solve this?
Option1 Remarks:
.attr("fill", function(d, i) {
if (d.data.categoria=="funzioni") {return colorFunzioni(i);}
else if (d.data.categoria=="rimborso prestiti") {return colorRimborso(i);}
else if (d.data.categoria=="altro") {return colorAltro(i);}
})
The trouble is the above is written as if 'i' will maintain separate tallies for the three categories. It doesn't though, it keeps an index for all elements in your selection, and as soon as the first 12 items in the selections are done, the next items are going to be out of range of any of the scales you described and return "#000000" - this is why the first 12 are coloured (and the first 12 may be red because you assign the red range to two scales, and the green range isn't used) and the rest aren't.
As a quick fix, keep a tally in the data itself of where it occurs in each category like this:
dataset.forEach (function (d) {if (d.categoria=="altro" && d.val2015>0) { countAltro += 1; d.catIndex = countAltro; }})
do this for each category
and then in the fill attr function do:
else if (d.data.categoria=="altro") {return colorAltro(d.data.catIndex);}
and again that needs done for each category.
As a separate thing, you can get rid of those else-if's by assigning the colors like this:
var colorMap = {
funzioni: colorFunzioni,
altro: colorAltro,
"rimborso prestiti": colorRimborso
}
and then later doing
.attr("fill", function(d, i) {
var scale = colorMap[d.data.categoria];
if (scale) return scale(d.data.catIndex)
})

dc.js: Reducing rows in data table

So I have a data.table object that is being outputed like this:
gender hair-color pets group1.totals group2.totals group3.totals
F black Y 10 0 0
F black Y 0 7 0
F black Y 0 0 8
How do I collapse it so that it will be like this?
gender hair-color pets group1.totals group2.totals group3.totals
F black Y 10 7 8
I have tried reducing the dimensions but it doesn't seem to work. My code is below:
ndx = crossfilter(data);
dataTable = dc.dataTable('#data-table');
var tableDim = ndx.dimension(function(d) {
return d.gender + "/" + d.hair-color + "/" + d.pets;
});
dataTable
.width(400)
.height(800)
.dimension(tableDim)
.group(function(d){
return "Data Counts";
}),
.columns([
function(d) {
return d.gender;
},
function(d) {
return d.hair-color;
},
function(d) {
return d.pets;
}
function(d) {
if (d.group == 1) return d.totals;
else return 0;
},
function(d) {
if (d.group == 2) return d.totals;
else return 0;
},
function(d) {
if (d.group == 3) return d.totals;
else return 0;
Essentially I know that I have to reduce and group my data but I can't find specifically what I have to do in order to achieve. Any help would be great, thanks!
Use the following code;
var ndx=crossfilter(data);
var dimension=ndx.dimension(function(d){return d.hair-color});
var dataByHairColor=dimension.group().reduceCount();
I hope it'll solve the problem. If you want other filtering option use that. I used hair color. Let me know if you are still facing issues

Transforming a dataset with crossfilter

I'm trying to create a dashboard using dc.js and I want to customize how a data table is visualized. So my data looks something like this
agegroup gender group scores total
18-24 M 1 0.04 1
45-54 F 2 2.23 13
25-34 M 1 0.74 6
25-34 M 2 1.47 8
18-24 F 1 2.88 7
35-44 F 2 3.98 14
When I initialize a data table, it'll look the same as my original csv. However what if I want
agegroup gender group1.scores group1.total group2.scores group2.total
18-24 M 0.04 1 0.0 0
18-24 F 2.88 1 0.0 0
25-34 M 0.74 8 1.47 8
25-34 F 0.0 0 0.0 0
Here is how I initalize and set up my data table
dataTable = dc.dataTable('#data-table');
var tableDim = ndx.dimension(function(d) {
return d.gender;
});
dataTable
.width(400)
.height(800)
.dimension(tableDim)
.group(function(d){
return "Counts"
})
.size(20)
.columns([
function(d){
return d.gender;},
function(d){
return d.agegroup;
},
function(d){
return d.group;
},
function(d){
return d.scores;
},
function(d){
return d.total;
},
])
.order(d3.ascending)
.sortBy(function(d){
return d.gender;
});
I know that crossfilter allows you to filter and subset data quickly but I'm not sure how it'll function transforming datasets. Thanks!
So far, I was able to do this for now.
var tableDim = ndx.dimension(function (d) {
return d.agegroup;
});
var dataTable = dc.dataTable("#someTable");
dataTable.width(300).height(800)
.dimension(tableDim)
.group(function (d) {
return "Counts";
})
.columns([
function (d) {
return d.agegroup;
},
function (d) {
return d.gender;
},
function (d) {
if (d.group == 1) return d.scores;
else return 0;
},
function (d) {
if (d.group == 1) return d.total;
else return 0;
},
function (d) {
if (d.group == 2) return d.scores;
else return 0;
},
function (d) {
if (d.group == 2) return d.total;
else return 0;
}]);
dc.renderAll();
Here is the JSFiddle working with the above code. Use this or make a new one next time when you are asking for such solutions on SO.
Remember, using dc.dataTable you may not be able to reduce the number of rows in the data set. If you really want to reduce the number of rows you may try group().reduce() methods and create new fields for group1.total, group1.scores etc..

Categories