Using a year-field on the x-axis of a lineChart in dc.js - dc.js

I am having troubles to implement a data parser using d3.
I have a field in my csvdataset that looks like this:
.., year, ..
.., 1987, ..
.., 1988, ..
.., 1989, ..
.., 1990, ..
I would like to use this year dimension on a x-axis of a dc.lineChart.
The following is my implementation using crossfilter.js
var ndx = crossfilter(data);
var yearDim = ndx.dimension(function(d) {return d.year});
At the moment I am using the following solution, which as you can see does not achieve the wanted result:
data.forEach(function(d) {
d.year = parseInt(d.year);
});
...
...
yearChart
.width(870)
.height(500)
.dimension(yearDim)
.group(numberPerYear)
.x(d3.scaleLinear().domain([1987,2015]))
.elasticY(true)
.controlsUseVisibility(true)
.yAxis().ticks(5);
However, this results in having integer on the x-axis. I tried also to create a parser for the date, but I do not know how to pass the parameter after and also I am not sure whether this is the right solution, since the function I found online are deprecated (v.3).
var dateFormatSpecifier = '/%Y';
var dateFormat = d3.timeFormat(dateFormatSpecifier);
var dateFormatParser = d3.timeParse(dateFormatSpecifier);

You should be using d3.scaleTime() instead of d3.scaleLinear(). You can simply set the domain to exactly 00:00 for the start and end dates for the domain:
.x(d3.scaleTime().domain([
new Date("January 1, 1987 00:00:00"),
new Date("January 1, 2015 00:00:00")
]));
Remember to transform the d.year into a Date object:
var yearDim = ndx.dimension(function(d) { return new Date(d.year); });

Related

Why is my pie chart showing incorrect groups when filtered with stacked bar chart in dc.js & crossfilter.js?

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.)

Convert csv table into hierarchy / stratify doesnt work (want to build a treemap)

i have the following csv table saved to a file data.csv
Stock_Short, Country, LScore2, MarketCAPUSD
ZYNE,US,-4,181623392
ZYME,US,-4,477151488
ZUO,US,-6,2098448000
ZUMZ,US,3,685746624
...
which goes on for several countries: US, L, HK, DE, etc.
I want to build a treemap in d3 and it uses only a hierarchical list of entries.
I was able to
d3.nest
the data by country and then tried using
d3.stratify
but it complains about a missing root. what am i doing wrong?
i see on this page there is an example of stratify without using a root.
why is mine not working?
https://github.com/d3/d3-hierarchy/blob/master/README.md#stratify
here is the data as a gist:
https://gist.githubusercontent.com/a3igner/8935d1e1158898540a615900a8dcb249/raw/5c957cc489ba5049cdc81ef47c3688b7816fbe69/LeverExample
var height = 200;
var width = 500;
var margin = {left: 50,right:50,top:40,bottom:0};
//var tree = d3.tree().size([width,height]);
var svg = d3.select('body').append('svg').attr('width','100%').attr('height','100%');
var chartGroup = svg.append('g').attr('transform','translate('+margin.left+','+margin.top+')');
d3.json("keyedJSON-Lever.json").get(function(error,data){
console.log(data);
// console.log(data[0].children);
// console.log(data[0].children[0].children[1].name);
var nestedData = d3.nest().key(function(d){ return d.country; }).entries(data);
console.log(nestedData);
var treeData = d3.stratify()
.id(function(d) { return d.stock; })
.parentId(function(d) { return d.country; })
(data);
console.log(treeData);
});

DC js charts, crossfilter not filtering on click

*Updated with more relevant code.
*Updated again: removing chart groupings results in this error: "Unable to get property 'classed' of undefined or null reference dc.js (5575,9)". I am using dc 3.0.11.
I have an issue where my dc charts are not filtering each other upon clicking a selection on a chart. It does work if I call a function to explicitly do so, but I would like to avoid that.
This is my general approach:
I do have dc.css included (if that matters)
my crossfilter ndx and dimensions are correct
I am defining my charts WITHOUT .on renderlet (is this the reason?)
var masterData = [];
$(document).ready(function () { //UPDATED CODE START
var siteurl = 'site';
var ItemCount= GetItemCount(siteurl, 'list');
createRestUrl(siteurl,ItemCount,'list');
}); // UPDATED CODE END
function GetItemCount(siteurl, ListName){
var ItemCount='';
$.ajax({
url: siteurl+"/_api/web/lists/GetByTitle('"+ListName+"')/ItemCount",
method: "GET",
async: false,
headers: { "Accept": "application/json; odata=verbose" },
success: function (data) {
ItemCount = data.d.ItemCount;
},
error: function (data) {
console.log(data);
}
});
return ItemCount;
} // GetItemCount END
function createRestUrl(siteurl, ItemCount, ListName) {
var listServiceUrl = siteurl+ "/_api/web/lists/GetByTitle('" + ListName + "')/Items";
//Rest call to process each items of list
$.when(processList(listServiceUrl,ItemCount)).done(function () {
console.log("FINISHED");
console.log("--------");
console.log(masterData);
var ndx = crossfilter(masterData),
clientMgr = ndx.dimension(function(d) { return d.clientMgr}),
otherTeammates = ndx.dimension(function(d) { return d.otherTeammates}),
topic = ndx.dimension(function(d) { return d.topic}),
status = ndx.dimension(function(d) { return d.status}),
team = ndx.dimension(function(d) { return d.team}),
requester = ndx.dimension(function(d) { return d.requester}),
requesterBusiness = ndx.dimension(function(d) { return d.requesterBusiness}),
submitted = ndx.dimension(function(d) { return d.submitted}),
clientMgrGroup = clientMgr.group(),
otherTeammatesGroup = otherTeammates.group(),
topicGroup = topic.group(),
statusGroup = status.group(),
teamGroup = team.group(),
requesterGroup = requester.group(),
requesterBusinessGroup = requesterBusiness.group(),
submittedGroup = submitted.group();
var teamChart = dc.rowChart("#team_chart", "team");
var clientMgrChart = dc.pieChart("#mgr_chart", "mgr");
var statusChart = dc.pieChart("#status_chart", "status");
var requesterChart = dc.rowChart("#requester_chart", "request");
var requesterBusinessChart = dc.pieChart("#requesterBusiness_chart", "requestBusiness");
var timeSelect = dc.lineChart("#time_chart", "time");
var topicChart = dc.pieChart("#topic_chart", "topic");
var teamNum = dc.numberDisplay("#teamNum", "teamNum");
Date.prototype.addDays = function(days) {
var dat = new Date(this.valueOf());
dat.setDate(dat.getDate() + days);
return dat;
}
var thisDay = new Date();
var todayMinSix = thisDay.addDays(-30);
var todayPlusSix = thisDay.addDays(30);
teamChart
.dimension(team)
.group(teamGroup)
.width(800)
.height(200)
.transitionDuration(3000)
.ordering(function(d) { return -d.value })
.elasticX(true)
.x(d3.scaleLinear().domain([0, 100]))
.colors('#58D3F7')
//teamNum
//.valueAccessor(function(d) { return d})
//.formatNumber(d3.format())
//.group(teamGroup)
clientMgrChart
.dimension(clientMgr)
.group(clientMgrGroup)
.width(300)
.height(300)
.transitionDuration(3000)
statusChart
.dimension(status)
.group(statusGroup)
.height(200)
.width(500)
.innerRadius(95)
.transitionDuration(3000)
.colors(d3.scaleOrdinal().domain(["02 - Work-In-Progress", "01 - Pending Initial Review"])
.range(['#58D3F7', '#2E9AFE']))
requesterChart
.dimension(requester)
.group(requesterGroup)
.height(200)
.width(800)
.gap(10)
.transitionDuration(3000)
.ordering(function(d) { return -d.value })
.elasticX(true)
.colors('#F78181')
.x(d3.scaleLinear().domain([0, 100]));
requesterBusinessChart
.dimension(requesterBusiness)
.group(requesterBusinessGroup)
.height(300)
.width(300)
.innerRadius(117)
.transitionDuration(3000)
.colors('#F78181')
topicChart
.dimension(topic)
.group(topicGroup)
.height(200)
.width(500)
.innerRadius(95)
.transitionDuration(3000)
.colors(d3.scaleOrdinal().domain(["New Report / Interface", "General Support", "One-Time Data Set", "Recurring Data Set", "Modify Existing Report / Interface", "Production Issue"])
.range(['#F5A9A9', '#F78181', '#FA5858', '#F6CECE', '#F8E0E0', "#FBEFEF"]))
timeSelect
.width(1700)
.height(150)
.dimension(submitted)
.group(submittedGroup)
.transitionDuration(1000)
.elasticY(true)
.renderArea(true)
.x(d3.scaleTime().domain([todayMinSix, thisDay]))
.xUnits(d3.timeDays)
.mouseZoomable(false)
.xAxis();
teamChart.render();
statusChart.render();
requesterChart.render();
topicChart.render();
});
}
//Step 3: Rest call to process each items of list
function processList(nextUrl,ItemCount) {
var dfd = new $.Deferred();
if (nextUrl == undefined) {
dfd.resolve();
return;
}
getJSONDataFromUrl(nextUrl).done(function (listItems) {
TotalItemCount = TotalItemCount+listItems.d.results.length;
console.log("getJSON called");
var items = listItems.d.results;
var next = listItems.d.__next;
$.when(processList(next,ItemCount)).done(function (){
dfd.resolve();
});
$.each(items, function(index, obj){
items = {};
items.clientMgr = obj.ClientMgr; //Assigned To - might not be the right field
items.otherTeammates = obj.OtherTeammatesEngaged; //Assigned To - might not be the right field
items.topic = obj.Topic;
items.status = obj.Status;
items.team = obj.Team;
items.requester = obj.RequesterLOB;
items.submitted = obj.Submitted;
items.requesterBusiness = obj.RequesterBusinessGroup;
console.log("looping through each");
var convert = new Date(items.submitted);
items.submittedConvert = moment(convert).format('L');
items.submitted = convert;
var assignName = items.clientMgr;
items.clientMgr = assignName;
masterData.push(items);
console.log(items.requesterBusiness);
console.log(items.status);
}); //.each END
}); // getJSONDataFromUrl END
return dfd.promise();
} // processList END
I had this working long ago, but as the project became more complex, something broke along the way.
I'm going to guess that the error refers to the use of .classed() a few lines up from 5575. Here are the lines leading up to 5575:
if (d3.sum(_chart.data(), _chart.cappedValueAccessor)) {
pieData = pie(_chart.data());
_g.classed(_emptyCssClass, false);
} else {
// otherwise we'd be getting NaNs, so override
// note: abuse others for its ignoring the value accessor
pieData = pie([{key: _emptyTitle, value: 1, others: [_emptyTitle]}]);
_g.classed(_emptyCssClass, true);
}
if (_g) {
https://github.com/dc-js/dc.js/blob/3.0.11/dc.js#L5565-L5575
So (except for the line number being a little off) it's a good guess that _g is null.
As I noted in my comment above, this probably indicates that the chart was redrawn before it was rendered. This can happen if you create a chart but don't render it. Rendering initializes things like the scale and parent elements like the <g> that holds the chart.
When you create a chart, you either specify a chart group or the default chart group is selected for you. The chart is registered in that group, and when any chart in the group is filtered, all the charts are redrawn.
In your code above, you render specific charts (4 of them), but there are many more charts which you initialize but don't render (8). In particular, all of the pie charts are rendered except for clientMgrChart. When you later click on a chart, it's likely that chart crashes when it tries to redraw.
It would be nice if dc.js implemented a more helpful error for this case since you currently have to guess "hmm, null, guess something hasn't been set up right" and know that render must happen before redraw.
Note: it's more robust to initialize the charts and then call
dc.renderAll();
instead of rendering each one manually.
Note to others: With dc.js 3.1.2, my individual rowCharts would not animate themselves when clicked. The other charts in the crossfilter did animate.
Very confused, traced all the way through the dc.js call stack and learned a lot.
Ultimately, root cause for the failure was not having dc.css linked in my page's HTML.
Another good 'tell' that you don't have dc.css properly linked is that the rowChart vertical lines do not render.
Screenshots
rowChart With CSS - Clicking any of the bars DOES trigger animation.
rowChart Without CSS - Clicking any of the bars does not trigger animation.

dc.js pie chart is empty

I'm trying to link a pie chart to a map so that when you click a state the pie chart updates with the popular vote for that state.
Currently my piechart is displaying empty.
Csv is formatted like so:
state, party, votes
Alabama,dem,725704
Alabama,rep,1314431
Alabama,lib,44211
Alabama,gre,20276
Alabama,con,9341
Alaska,dem,116454
Alaska,rep,163387
Alaska,lib,18725
Alaska,gre,5735
Alaska,con,3866
Alaska,other,10441
My code:
d3.csv("elecVotes.csv", function (data) {
d3.json("us.json", function (json){
// set up crossfilter on the data.
var ndx = crossfilter(data);
// set up the dimensions
var stateDim = ndx.dimension(function (d) { return d.state; });
var party = partyDim.group().reduceSum(function(d) { return d.votes;});
var votesDim = ndx.dimension(function(d) { return d.votes; });
// set up the groups/values
var state = stateDim.group();
var party = partyDim.group();
var votes = votesDim.group();
// the 4 different charts - options are set below for each one.
var pie = dc.pieChart('#chart-pie');
var usmap = dc.geoChoroplethChart("#usmap");
//create pie from to show popular vote for each state
pie
.width(180)
.height(180)
.radius(80)
.dimension(partyDim)
.group(votes)
.renderLabel(true)
.innerRadius(10)
.transitionDuration(500)
.colorAccessor(function (d, i) { return d.value; });
//display US map
usmap
.width(900)
.height(500)
.dimension(stateDim)
.group(state)
.colors(["rgb(20,202,255)","rgb(144,211,035)"])
.overlayGeoJson(json.features, "name", function (d) { return d.properties.name; })
// at the end this needs to be called to actually go through and generate all the graphs on the page.
dc.renderAll();
});
});
I'm not sure what i'm doing wrong
I don't think you want a votesDim - that would group by the number of votes, so you would probably end up with a different bin for each count, since they are likely to be unique.
I'm guessing you probably want to count the number of votes for each party, so:
var partyGroup = partyDim.group().reduceSum(function(d) { return d.votes; });
Remember that a dimension specifies what you want to filter on, and a group is where data gets aggregated and read.
You also need to convert any numbers explicitly before you get started, since d3.csv will read every field as a string. So:
data.forEach(function(r) {
r.votes = +r.votes;
});
var ndx = crossfilter(data);
Not sure if this helps with your problem. Note that this really has nothing to do with the map; the pie chart should be able to draw itself independent of what the map is doing.
Edit
I bet the problem is those spaces in the column names. You could easily end up with fields named " party" and " votes" that way...

Composite Graph from Crossfilter Example

Starting from the payments example crossfilter (https://github.com/square/crossfilter/wiki/API-Reference) how may we create a Composite Chart with one Line Chart for each payment type (tab, visa, cash)?
I am assuming you want to display payment totals over time (the date dimension) for each payment type.
var payments = crossfilter([...]);
var dateDimension = payments.dimension(function(d) { return new Date(d.date); });
Create a group of payment totals for each payment type (tab, visa, cash)
var totalForType = function(type) {
return function(d) {
return d.type === type ? d.total : null;
};
};
var tabTotalsGroup = dateDimension.group().reduceSum(totalForType('tab'));
var visaTotalsGroup = dateDimension.group().reduceSum(totalForType('visa'));
var cashTotalsGroup = dateDimension.group().reduceSum(totalForType('cash'));
Define a composite chart and use the groups to define 3 line charts as part of the composite chart.
var compositeChart = dc.compositeChart('#composite-chart');
compositeChart
...
.x(d3.time.scale().domain([new Date("2011-11-14T16:15:00Z"), new Date("2011-11-14T17:45:00Z")]))
.dimension(dateDimension)
.compose([
dc.lineChart(compositeChart).group(tabTotalsGroup, 'tab').colors(['#ffaa00']),
dc.lineChart(compositeChart).group(visaTotalsGroup, 'visa').colors(['#aa00ff']),
dc.lineChart(compositeChart).group(cashTotalsGroup, 'cash').colors(['#00aaff'])
]);
dc.renderAll();
Full example: http://plnkr.co/edit/rhDURrDfeSvVqEnQR9L1?p=preview

Resources