dc js choropleth map appears invisible - d3.js

I am trying to draw two choropleths ( US states and county) in a page, using dc.js . The us county map is somehow becoming invisible. The dom elements are present, properly with path and fill values, when inspected (as shown in the figure). I'm able to click on those inivisible counties and able to affect the filters as expected. The fill colors are accurate as per the cross_filtered data. Why is the color fill not happening properly ? What is it missing here ?
d3.json(load_geojson_states){
<code for states json>
d3.json(load_geojson_county){
<define dimensions>
<define group on dimension>
usChart_cnt
.width(1000)
.height(500)
.dimension(cnt_dim)
.group(change_p_sqft_cnt_group)
.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.colorDomain([0, 30])
.colorAccessor(function (d) { /*console.log(d);*/ return d? d.chng_pct:0; })
.overlayGeoJson(countyJson.features, "county", function (d) { return d.properties.FIPS; })
.valueAccessor(function(kv) { //console.log(kv);
return kv.value;
})
.title(function (d) {
return "County: " + d.key + "\nChange in Price per SqFt: " + numberFormat(d.value ? d.value.chng_pct : 0) + "%";
})
dc.renderAll();
}
}

This issue is resolved by referring to this post: DC.js choropleth map chart CSS conflicting with colouring, no map showing. How can I turn off the fill:none?
The whole issue is because the 'county' class gets over-rided with css from dc.js . Upon changing the class name as follows ,i'm seeing the values getting filled up properly :
.overlayGeoJson(countyJson.features, "cnty", function (d) { return d.properties.FIPS; })

Related

dc.js geoChoroplethChart doesn't display legend

I'm trying to plot a dc choropleth , but somehow the legend is not showing up.
Here is the sample fiddle :
http://jsfiddle.net/susram/9VJHe/56/
usChart
.width(1200)
.height(500)
.dimension(state_dim)
.group(latest_mean_sqft_per_state)
//.colors(d3.scale.quantize().range(["#E2F2FF", "#C4E4FF", "#9ED2FF", "#81C5FF", "#6BBAFF", "#51AEFF", "#36A2FF", "#1E96FF", "#0089FF", "#0061B5"]))
.colors(d3.scale.quantize().range(["#fff7fb","#ece2f0","#d0d1e6","#a6bddb","#67a9cf","#3690c0","#02818a","#016c59","#014636"]))
//.colors(d3.scale.quantize().range(d3.schemeBlues()(9)))
.colorDomain([0, 500])
//.colorAccessor(function (d) { /*console.log(d);*/ return d? usChart.colors(d.avg_psft) : '#ccc'; })
.colorAccessor(function (d) { /*console.log(d);*/ return d.avg_psft; })
.overlayGeoJson(statesJson.features, "state", function (d) {
return d.properties.name;
})
.valueAccessor(function(kv) {
console.log(kv);
return kv.value;
})
.title(function (d) {
return "State: " + d.key + "\nAverage Price per SqFt: " + numberFormat(d.value.avg_psft ? d.value.avg_psft : 0) + "M";
})
.legend(dc.legend().x(1300).y(80).itemHeight(13).gap(5));
Why is the legend showing up as 0x0 ?
I've been trying to get the legend to work with geoChoroplethCharts as well and unfortunately legend support appears to not have been implemented yet in dc. There are a few functions (legendables, legendHighlight, legendReset, legendToggle, ect...) that were defined in the dc base-mixin and would need to be extended before legend support would work.
For an example take a look at the source for pieChart:
https://github.com/dc-js/dc.js/blob/develop/src/pie-chart.js
Versus the soruce for geoChoroplethChart:
https://github.com/dc-js/dc.js/blob/develop/src/geo-choropleth-chart.js
You'll notice at the bottom of the pieChart source that the related legend functions were extended. I belive something similar would need to be done for the geoChoroplethChart source code.
EDIT:
I worked off your jsfiddle and was able to get a bare bones label to display on the geoChoroplethChart: http://jsfiddle.net/Lx3x929v/2/
usChart.legendables = function () {
return usChart.group().all().map(function (d, i) {
var legendable = {name: d.key, data: d.value, others: d.others,
chart: usChart};
legendable.color = usChart.colorCalculator()(d.value);
return legendable;
});
};
Here is my modification —for a continuous map— from #MarcTifrea 's solution and comment.
chart.legendables = function () {
var domain = chart.colorDomain();
return domain.map(function (d, i) {
var legendable = {name: parseFloat((Math.round(domain[i] * 100000) /100000).toPrecision(2)) , chart: chart};
if (i==1) legendable.name += ' unit'; // add the unit only in second(last) legend item
legendable.color = chart.colorCalculator()(domain[i]);
return legendable;
});
};
chart.legend(
dc.legend()
.x(width/4)
.y(height*4/5)
.itemHeight(height/30)
// .itemWidth(width/25)
.gap(5)
// .horizontal(1)
// .autoItemWidth(1)
);

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

How do I filter a stacked line chart by stack in dc.js?

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

Update domain of color scale only once in d3 reusable charts

I am building a reusable chart following this tutorial: https://bost.ocks.org/mike/chart/. The full code is at the end of the question. I have the following problem:
As you can see the 'click' event on a specific component triggers a query that updates the whole chart retrieving new data. I am referring to this line:
selection.datum(relatedConcepts).call(chart); // Update this vis
Now this update works great, but of course given that in the function "chart" I also have
color.domain(data.map(function(d){ return d[0]}));
the domain of the color scale will be also updated and I don't want that.
So the question is: how do I set the color scale domain ONLY the first time the chart gets created?
d3.custom = d3.custom || {};
d3.custom.conceptsVis = function () {
var color = d3.scale.category20();
// To get events out of the module we use d3.dispatch, declaring a "conceptClicked" event
var dispatch = d3.dispatch('conceptClicked');
function chart(selection) {
selection.each(function (data) {
//TODO: This should be executed only the first time
color.domain(data.map(function(d){ return d[0]}));
// Data binding
var concepts = selection.selectAll(".progress").data(data, function (d) {return d[0]});
// Enter
concepts.enter()
.append("div")
.classed("progress", true)
.append("div")
.classed("progress-bar", true)
.classed("progress-bar-success", true)
.style("background-color", function (d) {
return color(d[0])
})
.attr("role", "progressbar")
.attr("aria-valuenow", "40")
.attr("aria-valuemin", "0")
.attr("aria-valuemax", "100")
.append("span") // (http://stackoverflow.com/questions/12937470/twitter-bootstrap-center-text-on-progress-bar)
.text(function (d) {
return d[0]
})
.on("click", function (d) {
// Update the concepts vis
d3.json("api/concepts" + "?concept=" + d[0], function (error, relatedConcepts) {
if (error) throw error;
selection.datum(relatedConcepts).call(chart); // Update this vis
dispatch.conceptClicked(relatedConcepts, color); // Push the event outside
});
});
// Enter + Update
concepts.select(".progress-bar").transition().duration(500)
.style("width", function (d) {
return (d[1] * 100) + "%"
});
// Exit
concepts.exit().select(".progress-bar").transition().duration(500)
.style("width", "0%");
});
}
d3.rebind(chart, dispatch, "on");
return chart;
};
ANSWER
I ended up doing what meetamit suggested and I added this:
// Getter/setter
chart.colorDomain = function(_) {
if (!arguments.length) return color.domain();
color.domain(_);
return chart;
};
to my conceptsVis function, so that from the outside I can do:
.... = d3.custom.conceptsVis().colorDomain(concepts);
Of course I deleted the line:
color.domain(data.map(function(d){ return d[0]}));
You can check if the domain is an empty array and only populate it if it is:
if(color.domain().length == 0) {
color.domain(data.map(function(d){ return d[0]}));
}
That being said, this behavior seems fundamentally wrong, or at least bug-prone. It means that the populating of the domain is a side-effect of the first render. But what is it about that first render that makes it different than subsequent calls and therefore worthy of setting the domain? What happens if later, as your app evolves, you decide to render a different dataset first and afterwards render what is currently the first dataset? Then you might end up with a different domain. It seems more sane to compute the domain explicitly, outside of the chart's code, and then pass the domain into the chart via a setter. Something like:
chart.colorDomain(someArrayOfValuesThatYouPreComputeOrHardCode)

D3 sort() with CSV data

I am trying all kinds of ways to make .sort() work on my csv dataset. No luck.
I'd just like to sort my data by a "value" column.
This is the function I'm running inside my d3.csv api call and before I select the dom and append my divs:
dataset = dataset.sort(function (a,b) {return d3.ascending(a.value, b.value); });
Before I get to the .sort, I clean the data:
dataset.forEach(function(d) {
d.funded_month = parseDate(d.funded_month);
d.value = +d.value;
});
};
Everything seems in order. When I console.log(d3.ascending(a.value, b.value)), I get the right outputs:
-1 d32.html:138
1 d32.html:138
-1 d32.html:138
1 d32.html:138
etc..
Yet the bars data doesn't sort.
It is not clear from the provided code but I will hazard a guess you are not handling async nature of d3.csv.
This plunkr shows your sort code working fine. Note where the data object is declared, populated, and used.
here is a partial listing. I have added buttons that re-order data. To achieve this we need to put the ordering logic inside render rather than inside the d3.csv callback.
<script type="text/javascript">
var data = [];
d3.csv("data.csv",
function(error, rows) {
rows.forEach(function(r) {
data.push({
expense: +r.expense,
category: r.category
})
});
render();
});
function render(d3Comparator) {
if(d3Comparator) data = data.sort(function(a, b) {
return d3[d3Comparator](a.expense, b.expense);
});
d3.select("body").selectAll("div.h-bar") // <-B
.data(data)
.enter().append("div")
.attr("class", "h-bar")
.append("span");
d3.select("body").selectAll("div.h-bar") // <-C
.data(data)
.exit().remove();
d3.select("body").selectAll("div.h-bar") // <-D
.style("width", function(d) {
return (d.expense * 5) + "px";
})
.select("span")
.text(function(d) {
return d.category;
});
}
</script>
<button onclick="render('ascending')">Sort ascending!</button>
<button onclick="render('descending')">Sort descending!</button>

Resources