I am trying to render a map of Australian data with an Australian map using dc.js geoChoroplethChart. Unfortunately the map is not showing at all.
var nationalMap = dc.geoChoroplethChart("#national-map")
.projection(projection)
.width(900)
.height(400)
.transitionDuration(1000)
.dimension(regionDimension)
.group(regionGrouping)
.filterHandler(function(dimension, filter) {
dimension.filter(function(d) {
return nationalMap.filter() != null ? d.indexOf(nationalMap.filter()) >= 0 : true;
}); // perform filtering
return filter; // return the actual filter value
})
.overlayGeoJson(aus, "regions", function(d) {
return d.properties.STATE_ABBR + "1";
})
Fiddle here:
https://jsfiddle.net/trickidicki/mokmjhuo/
I think I fixed the problem - I hadn't specified the JSON selector on the GeoJSON file. Changing it to aus["features"] works.
Related
When I click on a dc.js stacked bar chart, my pie chart elsewhere on the same page doesn't show the correct groups.
I'm new to dc.js, so I've created a simple dataset to demo features I need: Alice and Bob write articles about fruit, and tag each article with a single tag. I've charted this data as follows:
Line chart showing number of articles per day
Pie chart showing total number of each tag used
Stacked bar chart showing number of tags used by author
The data set is as follows:
rawData = [
{"ID":"00000001","User":"Alice","Date":"20/02/2019","Tag":"apple"},
{"ID":"00000002","User":"Bob","Date":"17/02/2019","Tag":"dragonfruit"},
{"ID":"00000003","User":"Alice","Date":"21/02/2019","Tag":"banana"},
{"ID":"00000004","User":"Alice","Date":"22/02/2019","Tag":"cherry"},
{"ID":"00000005","User":"Bob","Date":"23/02/2019","Tag":"cherry"},
];
Illustrative JSFiddle here: https://jsfiddle.net/hv8sw6km/ and code snippet below:
/* Prepare data */
rawData = [
{"ID":"00000001","User":"Alice","Date":"20/02/2019","Tag":"apple"},
{"ID":"00000002","User":"Bob","Date":"17/02/2019","Tag":"dragonfruit"},
{"ID":"00000003","User":"Alice","Date":"21/02/2019","Tag":"banana"},
{"ID":"00000004","User":"Alice","Date":"22/02/2019","Tag":"cherry"},
{"ID":"00000005","User":"Bob","Date":"23/02/2019","Tag":"cherry"},
];
var data = [];
var parseDate = d3.timeParse("%d/%m/%Y");
rawData.forEach(function(d) {
d.Date = parseDate(d.Date);
data.push(d);
});
var ndx = crossfilter(data);
/* Set up dimensions, groups etc. */
var dateDim = ndx.dimension(function(d) {return d.Date;});
var dateGrp = dateDim.group();
var tagsDim = ndx.dimension(function(d) {return d.Tag;});
var tagsGrp = tagsDim.group();
var authorDim = ndx.dimension(function(d) { return d.User; });
/* Following stacked bar chart example at
https://dc-js.github.io/dc.js/examples/stacked-bar.html
adapted for context. */
var authorGrp = authorDim.group().reduce(
function reduceAdd(p,v) {
p[v.Tag] = (p[v.Tag] || 0) + 1;
p.total += 1;
return p;
},
function reduceRemove(p,v) {
p[v.Tag] = (p[v.Tag] || 0) - 1;
p.total -= 1;
return p;
},
function reduceInit() { return { total: 0 } }
);
var minDate = dateDim.bottom(1)[0].Date;
var maxDate = dateDim.top(1)[0].Date;
var fruitColors = d3
.scaleOrdinal()
.range(["#00CC00","#FFFF33","#CC0000","#CC00CC"])
.domain(["apple","banana","cherry","dragonfruit"]);
/* Create charts */
var articlesByDay = dc.lineChart("#chart-articlesperday");
articlesByDay
.width(500).height(200)
.dimension(dateDim)
.group(dateGrp)
.x(d3.scaleTime().domain([minDate,maxDate]));
var tagsPie = dc.pieChart("#chart-article-tags");
tagsPie
.width(150).height(150)
.dimension(tagsDim)
.group(tagsGrp)
.colors(fruitColors)
.ordering(function (d) { return d.key });
var reviewerOrdering = authorGrp
.all()
// .sort(function (a, b) { return a.value.total - b.value.total })
.map(function (d) { return d.key });
var tagsByAuthor = dc.barChart("#chart-tags-by-reviewer");
tagsByAuthor
.width(600).height(400)
.x(d3.scaleBand().domain(reviewerOrdering))
.xUnits(dc.units.ordinal)
.dimension(authorDim)
.colors(fruitColors)
.elasticY(true)
.title(function (d) { return d.key + ": " + this.layer + ": " + d.value[this.layer] });
function sel_stack(i) {
return function(d) {
return d.value[i];
};
}
var tags = tagsGrp
.all()
.sort(function(a,b) { return b.value - a.value})
.map(function (d) { return d.key });
tagsByAuthor.group(authorGrp, tags[0]);
tagsByAuthor.valueAccessor(sel_stack(tags[0]));
tags.shift(); // drop the first, as already added as .group()
tags.forEach(function (tag) {
tagsByAuthor.stack(authorGrp, tag, sel_stack(tag));
});
dc.renderAll();
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/crossfilter2/1.4.7/crossfilter.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dc/3.1.1/dc.min.js"></script>
<div id="chart-articlesperday"></div>
<div id="chart-article-tags"></div>
<div id="chart-tags-by-reviewer"></div>
As you can see, Alice has made three articles, each tagged with "apple", "banana" and "cherry" respectively, and her stacked bar chart shows this.
However whenever her column of the bar chart is clicked, the pie chart instead shows her as having 1 "apple" and 2 "cherry".
It took me a very long time even to get to this point, so it may be that there's something fundamental I'm not getting about crossfilter groupings, so any insights, tips or comments are very welcome.
Indeed, this is very weird behavior, and I wouldn't know what to think except that I have faced it a few times before.
If you look at the documentation for group.all(), it warns:
This method is faster than top(Infinity) because the entire group array is returned as-is rather than selecting into a new array and sorting. Do not modify the returned array!
I guess otherwise it might start modifying the wrong bins when aggregating. (Just a guess, I haven't traced through the code.)
You have:
var tags = tagsGrp
.all()
.sort(function(a,b) { return b.value - a.value})
.map(function (d) { return d.key });
Adding .slice(), to copy the array, fixes it:
var tags = tagsGrp
.all().slice()
.sort(function(a,b) { return b.value - a.value})
.map(function (d) { return d.key });
working fork of your fiddle
We actually have an open bug where the library does this itself. Ugh! (Easy enough to fix, but a little work to produce a test case.)
My data is as follows in csv format.
Id,Title,Year,Runtime,Country,imdbRating,imdbVotes,Budget,Gross,WinsNoms,IsGoodRating
13,Alone in the Dark,2005,96,"Canada, Germany, USA",2.3,37613,20000000,8178569,9,0
38,Boogeyman,2005,89,"USA, New Zealand, Germany",4.1,25931,20000000,67192859,0,0
52,Constantine,2005,121,"USA, Germany",6.9,236091,75000000,221594911,11,1
62,Diary of a Mad Black Woman,2005,116,USA,5.6,10462,5500000,50458356,26,0
83,Fever Pitch,2005,104,"USA, Germany",6.2,36198,40000000,50071069,9,1
Im trying to filter out the data as below but none of the filtering works.
d3.csv("movies.csv", function(error, data) {
// change string (from CSV) into number format
data.forEach(function(d) {
d.imdbRating = +d.imdbRating;
d["WinsNoms"] = +d["WinsNoms"];
d["IsGoodRating"] = +d["IsGoodRating"]
});
var rating0 = data.filter(function(d){ return d["IsGoodRating"] = 0});
rating0.forEach(function(d) { console.log(d); });
//the above line does not give me anything on the console
var rating1 = data.filter(function(d){ return d["IsGoodRating"] = 1});
rating1.forEach(function(d) { console.log(d); });
//the above line gives me an output of all the records with both IsGoodRating which are 0 and 1 but the output shows as 1 which is not what the data has.
Any help will be appreciated. Im new to d3.js so I might be making a basic mistake.
doing the same as below works as expected but the array filter does not.
var rating0 = data.filter(function(d)
{
if (d["IsGoodRating"] == 0)
{
return d;
}
})
// var rating0 = data.filter(function(d){ return d.IsGoodRating = 0}); This array filter is not working for some reason
rating0.forEach(function(d) { console.log(d.IsGoodRating); });
var rating1 = data.filter(function(d)
{
if (d["IsGoodRating"] == 1)
{
return d;
}
})
// var rating1 = data.filter(function(d){ return d["IsGoodRating"] != 0});This array filter is not working for some reason
rating1.forEach(function(d) { console.log(d.IsGoodRating); });
I am making a stacked line chart for a dashboard:
var json = [...]
var timeFormat = d3.time.format.iso;
json = json.map(function(c){
c.date = timeFormat.parse(c.date);
return c;
});
var data = crossfilter(json);
var days = data.dimension(function (d) {
return d.date;
});
var minDate = days.bottom(1)[0].date;
var maxDate = days.top(1)[0].date;
var lineValues = days.group().reduce(function (acc, cur) {
acc[cur.line] = (acc[cur.line] || 0) + 1
return acc;
}, function (acc, cur) {
acc[cur.line] = (acc[cur.line] || 0) - 1
return acc;
}, function () {
return {};
});
var personChart = dc.lineChart("#graph");
personChart
.turnOnControls(true)
.width(600).height(350)
.dimension(days)
.group(lineValues, "completed")
.valueAccessor(function (d) {
return d.value.completed || 0;
})
.stack(lineValues, "assigned", function (d) {
return d.value.assigned || 0;
})
.stack(lineValues, "inactive", function (d) {
return d.value.inactive || 0;
})
.stack(lineValues, "active", function (d) {
return d.value.active || 0;
})
.stack(lineValues, "new", function (d) {
return d.value.new || 0;
})
.stack(lineValues, "temp", function (d) {
return d.value.temp || 0;
})
.elasticY(true)
.renderArea(true)
.x(d3.time.scale().domain([minDate, maxDate]))
.ordinalColors(colorScale)
.legend(dc.legend().x(50).y(10).itemHeight(13).gap(5).horizontal(true));
dc.renderAll();
Fiddle here
It is working fine so far, but I reached an obstacle. I need to implement an option to filter the chart by individual stacks. Is this possible in dc.js? I can modify and rewrite the entire code if necessary as well as ask my client to remodel the data differently, if needed. There are other fields in the data that I filter on for other charts so preserving that functionality is important.
By design, dc.js has a lot of "leaky abstractions", so there is usually a way to get at the data you want, and customize the behavior by dropping down to d3, even if it's functionality that wasn't anticipated by the library.
Your workaround of using a pie chart is pretty reasonable, but I agree that clicking on the legend would be better.
Here's one way to do that:
var categories = data.dimension(function (d) {
return d.line;
});
personChart
.on('renderlet', function(chart) {
chart.selectAll('.dc-legend-item')
.on('click', function(d) {
categories.filter(d.name);
dc.redrawAll();
})
});
Basically, once the chart is done drawing, we select the legend items and replace the click behavior which our own, which filters on another dimension we've created for the purpose.
This does rely on the text of the legend matching the value you want to filter on. You might have to customize the undocumented interface .legendables() between the legend and its chart, if this doesn't match your actual use case, but it works here.
This fork of your fiddle demonstrates the functionality: https://jsfiddle.net/gordonwoodhull/gqj00v27/8/
I've also added a pie chart just to illustrate what is going on. You can have the legend filter via the pie chart by doing
catPie.filter(d.name);
instead of
categories.filter(d.name);
This way you can see the resulting filter in the slices of the pie. You also can get the toggle behavior of being able to click a second time to go back to the null selection, and clicking on multiple categories. Leave a comment if the toggle behavior is desired and I try to come up with a way to add that without using the pie chart.
Sometimes it seems like the legend should be its own independent chart type...
I'm having some trouble with the exit().remove() function in a stacked area chart I am creating.
JSFiddle here: Link
I have functionality where the user can enable/disable the data in the chart by clicking on the legend rectangle/color. I know that the data is being updated based on console messages and the Y axis changing scale, but the data in the chart does not change. For instance if the user deselects the Failed category the orange layer should disappear and the Failed and Passed layers should re-adjust.
The issue appears to be in lines 214 to 234 in the fiddle, specifically where I am calling exit().remove():
// filter the data
var updatedData = dataSeries.filter(function(d) {
if(d.vis === "1"){
return d;
}
else {
return null;
}
});
stack.values(function(d) { return d.values; });
layer = stack(updatedData);
main_layer.selectAll(".layer")
.data(layer);
main_layer
.attr("d", function(d) { return main_area(d.values); });
main_layer.exit().remove();
The error I am getting is Object [object Array] has no method exit I have tried changing the selectAll to just a select, but that also produces the same error. Thanks in advance.
I finally got this working. The code below updates the layers correctly:
// filter the data
var updatedData = dataSeries.filter(function(d) {
if(d.vis === "1"){
return d;
}
else {
return null;
}
});
stack(updatedData);
var sel = main_layer.select(".layer");
sel
.attr("class", function(d) { return d.key + " layer"; })
.style("fill", function(d, i) {
if(d.vis === "1") {
return z(i);
}
else return null;
})
.attr("d", function(d) {
if(d.vis === "1") {
return main_area(d.values);
}
else return null;
});
I want to show value in datatable for that i have put line of code
// Bar chart For pricing
var collectionRateValue = ndx.dimension(function (d) {
return d3.time.month(d.dd);
});
var collectionRateValueGroup_adj = collectionRateValue.group().reduceSum(function(d) {
return d.pay_amt;
});
var collectionRateValueGroup_payment = collectionRateValue.group().reduceSum(function(d) {
return d.ar_balance;
});
var collectionRateValueGroup_ar_bal = collectionRateValue.group().reduceSum(function(d) {
return d.charge_amt;
});
var collectionRateValueGroup_payment_line = collectionRateValue.group().reduceSum(function(d) {
return d.pay_amt;
});
This is code for showing data
dc.dataTable("#dc-data-table")
.dimension(collectionService)
.group(function (d) {
return d3.time.format(d.month_year_dos);
})
.size(10)
.columns([
function (d){
return d.month_year_dos;
},
function (d){
return d.ar_balance;
},
function (d){
return d.pay_amt;
},
function (d){
return "d.date";
},
])
This is how I want to show my data table.
Table header will come dynamically
I have find a lot but i didn't get how data will shown in row format?
It seems like you want to flip the rows of your table for columns. You will probably want to do that directly with D3.
The example at http://www.d3noob.org/2013/02/add-html-table-to-your-d3js-graph.html should show to use D3 to render a table. you will want to adapt this to render in a different order however...
Start simple and try to render one of the rows in your table - perhaps the adjustments row - by appending a td for each value in the crossfilter group.