DC - Can geoChoroplethChart be combined with bubbleOverlay? - dc.js

This example (http://dc-js.github.io/dc.js/crime/index.html) looks great but the path elements and SVG for the map of Canada have been defined manually. I'd like to draw a world map with geoChoroplethChart and then add a bubbleOverlay.
I've tried them separately and recently tried a compositeChart approach:
compositeChart.width(1440)
.height(500)
.margins({top: 10, right: 50, bottom: 30, left: 60})
.compose([
dc.geoChoroplethChart("#us-chart")
.dimension(countries)
.projection(projection)
.group(countryValue)
.colors(d3.scale.category10())
.colorDomain([0, 3])
.colorCalculator(function (d) { return d ? geoChart.colors()(d) : '#ccc'; })
.overlayGeoJson(statesJson.features, "state", function (d) {
return d.properties.name;
}),
dc.bubbleOverlay("#us-chart")
.svg(d3.select("#us-chart svg"))
.dimension(countries)
.group(countryValue)
.radiusValueAccessor(function(p) {
//console.log(p);
return p.val;
})
.r(d3.scale.linear().domain([0, 4]))
.point("Angola",200,200)
])
dc.renderAll();
I have a sneaky feeling compositeChart will only work with charts based on a grid system, like line and bar charts. Is this right? In which case, is there a way to add a bubbleOverlay to a choropleth in DC, without doing it manually?

Related

display tip when mouse is anywhere over line chart

I have a composite line chart.
How can I display the respective values (date and value) when i hover on any place of the chart like below:
var composechart = dc.compositeChart("#test_composed");
composechart
.width(990)
.height(450)
.margins({ top: 50, right: 40, left: 50, bottom: 50 })
.x(d3.scaleTime().domain([new Date(2017, 0, 1), new Date(2019, 10, 30)]))
.rangeChart(xChart)
.elasticY(true)
.xUnits(d3.timeMonths)
.legend(dc.legend().x(80).y(20).itemHeight(13).gap(5))
.renderHorizontalGridLines(true)
.brushOn(false)
.compose([
dc.lineChart(composechart)
.dimension(salesgrafikDim)
.group(salesgrafikGroup,"Sales"),
dc.lineChart(composechart)
.dimension(satisgrafikDim)
.colors('red')
.group(quantitygrafikGroup,"Quantity")
])
xChart
.width(990)
.height(40)
.margins({top: 0, right: 50, bottom: 20, left: 50})
.dimension(salesgrafikDim)
.group(salesgrafikGroup)
.x(d3.scaleTime().domain([new Date(2017, 0, 1), new Date(2019, 10, 30)]))
.xUnits(d3.timeMonths);
xChart.yAxis().ticks(0);
Basically want to display a tooltip like in the screenshot below, where the mouse should not need to be over a dot for it to display.
I can do it with straight D3, with svg and append and so on. But i have dimension with other charts and dataTable, so I want to use this with dc.js.
It would be much more user friendly and easy to understand if you don't have to hover over the dots to see the tip.
My chart is below :
Since dc.js doesn't support this directly, you're going to end up dropping down into D3 in order to implement this.
First, we need to disable the existing titles:
.title(() => '')
and hover events:
composite.on('pretransition', chart => {
chart.children().forEach(
child => child.selectAll('circle.dot')
.on('mousemove', null)
.on('mouseout', null)
);
})
Then we can add a handler to put mouse handlers on the SVG itself
composite.on('postRender', chart => {
chart.svg().on('mousemove', () => { // 2
// find closest data point
const x = chart.x().invert(d3.mouse(chart.svg().node())[0] - chart.margins().left),
xs = chart.children()[0].group().all().map(kv => kv.key),
right = d3.bisectLeft(xs, x);
let closest = right;
if(right >= xs.length)
closest = right - 1;
else if(right > 0) {
// see if point to the left is closer
if(x - xs[right-1] < xs[right] - x)
closest = right - 1;
}
//console.log('closest', new Date(x), closest, xs[closest])
chart.children().forEach(child => { // 3
child.g().selectAll('circle.dot').each(function(d) {
if(d.x === xs[closest]) {
child._showDot(d3.select(this));
child.g().select('text.data-tip')
.attr('visibility', 'visible')
.attr('x', child.x()(d.x))
.attr('y', child.y()(d.y))
.text(tooltip_text(d.data))
} else
child._hideDot(d3.select(this));
});
})
})
chart.svg().on('mouseout', () => { // 4
chart.children().forEach(child => {
child.selectAll('circle.dot').each(function(d) {
child._hideDot(d3.select(this));
});
})
})
chart.children().forEach(child => child.g() // 1
.append('text')
.attr('class', 'data-tip')
.attr('fill', 'black')
.attr('alignment-baseline', 'top')
.attr('text-anchor', 'begin')
.attr('visibility', 'hidden')
)
});
I'm not going to get into the details, but this
adds a text element to each child chart, which will display the tip
listens for mousemove and finds the nearest point
shows the dots and text for the selected point in each child
hides all dots and text when the mouse leaves the chart
I ran out of time to think about this, so I didn't try to get the text positioned correctly or make it look nice.
linear scale
Linear scale fiddle.
time scale
Time scale fiddle.
As I mentioned in the comments, you're going to run into trouble with this design, if the lines get too close together. You can see this problem with the first point in this example.

DC.js - Custom ordering of ordinal scale line chart

Please forgive my english.
I am trying to create a line chart with ordinal scale and brushon function. I successfully set brushing on ordinal thanks to this method :
https://dc-js.github.io/dc.js/examples/brush-ordinal.html
And now I need to set the x-axis values order.
My data is like this :
[{"id_commune":4,"datation":"-30/-20","effectif":0.09,"commune":"Frejus","lieu_dit":"Les Aiguieres","departement":"83","pays":"FR","longitude":6.73703399,"latitude":43.433152,"quantite":1,"id_datation":1},
{"id_commune":4,"datation":"-20/-10","effectif":0.09,"commune":"Frejus","lieu_dit":"Les Aiguieres","departement":"83","pays":"FR","longitude":6.73703399,"latitude":43.433152,"quantite":1,"id_datation":2},
{"id_commune":4,"datation":"-10/1","effectif":0.09,"commune":"Frejus","lieu_dit":"Les Aiguieres","departement":"83","pays":"FR","longitude":6.73703399,"latitude":43.433152,"quantite":1,"id_datation":3},
{"id_commune":7,"datation":"20/30","effectif":0.33,"commune":"Nimes","lieu_dit":"Solignac","departement":"30","pays":"FR","longitude":4.36005399,"latitude":43.836699,"quantite":1,"id_datation":6},{"id_commune":6,"datation":"20\/30","effectif":0.6,"commune":"Muralto","lieu_dit":"Liverpool b","departement":"TI","pays":"CH","longitude":8.80560809,"latitude":46.1729618,"quantite":1,"id_datation":6},
{"id_commune":4,"datation":"20/30","effectif":0.09,"commune":"Frejus","lieu_dit":"Les Aiguieres","departement":"83","pays":"FR","longitude":6.73703399,"latitude":43.433152,"quantite":1,"id_datation":6},{"id_commune":1,"datation":"20/30","effectif":0.14,"commune":"Aislingen","lieu_dit":"NP","departement":"Lkr. Dillingen an der Donau BY","pays":"DE","longitude":10.4559987,"latitude":48.5065603,"quantite":1,"id_datation":6},]
My crossfilter dimension and group look like this :
var ndx = crossfilter(records)
var graphDim = ndx.dimension(function(d) {return d.datation});
var graphGroup = graphDim.group().reduceSum(function(d) {return d.effectif;});
And the chart :
lineChart
.width(950)
.height(350)
.margins({top: 10, right: 50, bottom: 35, left: 30})
.dimension(graphDim)
.keyAccessor(function(kv) { return graphGroup.ord2int(kv.key); })
.group(graphGroup)
.x(d3.scaleLinear().domain(linear_domain))
.xAxisLabel("Chronologie")
.yAxisLabel("Effectif")
.brushOn(true)
.renderArea(true)
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true)
.elasticY(true)
.yAxis().ticks(4);
Right now, I get this :
The result I am trying to accomplish is the same chart but with x-axis ticks values ordered like this :
"-30/-20
-20/-10
-10/1
..."
Any help appreciated. Thank you!
Welcome to Stack Overflow. For future reference, it helps if you can include a reproducible example, for example a jsFiddle.
I understood what you were doing because I wrote the ordinal brushing example, but you didn't include all the code, so it wouldn't be clear to others.
Here is a fiddle with complete code, and here is the relevant code:
var graphDim = ndx.dimension(function(d) {return d.datation});
var graphGroup = graphDim.group().reduceSum(function(d) {return d.effectif;});
graphGroup = ordinal_to_linear_group(sort_group(graphGroup, function(a, b) {
return d3.descending(a.value, b.value);
}));
line = dc.lineChart('#line');
var linear_domain = [-0.5, data.length - 0.5];
line
.width(950)
.height(350)
.margins({top: 10, right: 50, bottom: 35, left: 30})
.dimension(graphDim)
.keyAccessor(function(kv) { return graphGroup.ord2int(kv.key); })
.group(graphGroup)
.x(d3.scaleLinear().domain(linear_domain))
.xAxisLabel("Chronologie")
.yAxisLabel("Effectif")
.brushOn(true)
.renderArea(true)
.renderHorizontalGridLines(true)
.renderVerticalGridLines(true)
.elasticY(true)
line.yAxis().ticks(4);
line.xAxis()
.tickValues(d3.range(data.length))
.tickFormat(function(d) { return graphGroup.int2ord(d); });
Here is the (bad) result:
As its name implies, sort_group() creates a fake group sorted by the ordering function. Right now it's sorting by value, from highest to lowest:
sort_group(graphGroup, function(a, b) {
return d3.descending(a.value, b.value);
})
It looks like you want to sort by the numeric value of the first part of each key. You can change the sort accordingly:
sort_group(graphGroup, function(a, b) {
return d3.ascending(+a.key.split('/')[0], +b.key.split('/')[0]);
})
This splits each key by /, then takes the first item ([0]), then converts it to a number (+), before using d3.ascending to specify sorting from low to high.
The result:
And a working version of the fiddle.

Why are black areas being renderd both above and below the line graph?

I have three charts rendering using dc.js and all of them render a black area below or above the line. I looked through the dc.css file for any fill options that were relevant, but I couldn't find any that had a positive effect.
I', using Angular 7 as the framework to develop my application.
Json Data is in this form and has a few different device types:
{
device_ID: "DHT11-3fe381"
​​​​
device_type: "thermometer"
​​​​
time: "2019-02-26T00:56:54.000Z"
​​​​
value: 25.9
}
I use crossfilter to create a timeDim dimension and sort each value according to device_type:
const timeDim = ndx.dimension((d) => d.time);
var Thermo = timeDim.group().reduceSum((d) => {
if (d.device_type === 'thermometer' && d.value <= 40) {
return d.value;
} else {
return 0;
}
});
Then, I create my graphs:
const DTchart = dc.compositeChart('#graphT');
DTchart
.width(800)
.height(300)
.margins({top: 10, right: 50, bottom: 100, left: 60})
.dimension(timeDim)
.group(device, 'value')
.xUnits(d3.timeMinutes)
.x(d3.scaleTime().domain([bottomDate, topDate]))
.yAxisLabel('Thermometer reading')
.xAxisLabel('Time')
.brushOn(false)
.elasticX(true)
.elasticY(true)
.controlsUseVisibility(true)
.renderHorizontalGridLines(false)
.compose([
dc.lineChart(DTchart)
.dimension(timeDim)
.renderArea(false) // This didn't contribute anything
.colors('RED')
.group(Thermo)
.valueAccessor((d) => d.value)]
);
The default for an SVG path element, which is used for line charts, is to display filled in black,
If you include dc.css it will set fill: none. The axis font will fit better and the axis lines will be narrower too.
You didn't ask, but I think the vertical red lines are due to zeros in your data. If data is missing you could consider using lineChart.defined to put gaps in your lines instead of dropping to zero.

mouseZoomable does not work with elasticX on line chart

I have code of lineChart
as below,
var ndx = crossfilter(data),
dim = ndx.dimension(function(d) {return d.index;}),
profitGrp = dim.group().reduceSum(function(d) { return d.profit});
var profitChart = dc.lineChart("#profit-chart")
.width($("#profit-chart").parent().width())
.height(400)
.dimension(dim)
.mouseZoomable(true)
.x(d3.scale.linear()).xAxisPadding(0.25).elasticX(true)
.group(profitGrp, "Profit")
.renderHorizontalGridLines(true)
.elasticY(true)
.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
.margins({left: 100, right: 40, top: 40, bottom: 40})
.brushOn(false);
But I can't mouse zoom in/out.
What should I do? I think it is because of elasticX(true)
Right, zoom is contradictory to elasticX(true)
One trick, if you want "elastic once" behavior, is:
chart.on('postRender', function(chart) {
chart.elasticX(false);
});
http://dc-js.github.io/dc.js/docs/html/dc.baseMixin.html#on
In my case Gordon answer didn´t work.
So there is another aproach, try to use "preRedraw" event:
chart.on('preRedraw', function(chart) {
chart.elasticX(false);
});

Problems with .x() in dc.js barCharts

I am doing my first interactive visualization using dc.js. I have come to a hard stop over a dc.barChart() attribute that I cannot seem with the answers I find here og on google. I will start the code from the crossfilter()function. I am able to create dc.rowCharts() so I highly doubt that it is a malformed .json problem
var ndx = crossfilter(salgsTransaksjonene);
//Define Dimensions
var kundeDim = ndx.dimension(function(d) { return d["CustomerName"]; });
//Calculate metrics
var OmsetningKunder = kundeDim.group().reduceSum(function(fact) { return fact.TotalAmount;});
//Charts
var toppChart = dc.barChart("#topp-20-bar-chart");
toppChart
.width(950)
.height(240)
.margins({top: 10, right: 50, bottom: 30, left: 50})
.dimension(kundeDim)
.group(topp20OmsetningKunder)
.transitionDuration(500)
.elasticY(true)
.yAxis().ticks(4)
When it comes to the .x() attribute I have tried the following
// .isOrdinal(true)
// .xUnitCount(20)
.xUnits(dc.units.ordinal.domain(salgsTransaksjonene.map(function (d) {return d.CustomerName; })))
.x(d3.scale.ordinal());
// .isOrdinal(true)
// .xUnitCount(20)
.xUnits(dc.units.ordinal)
.x(d3.scale.ordinal());
The errors I get is mostly the same; Uncaught TypeError: undefined is not a function
and various combinations of the above attributes, but to no avail. Any Ideas about what could be wrong is greatly appreciated!
Try to add a .domain with your ordinal values like this.
Also I've seen the order of the declarations to be of importance.
.x(d3.scale.ordinal().domain(['cat1','cat2','cat3']))
.xUnits(dc.units.ordinal)
here is a full working barchart for your inspiration.
chart11
.width(xxx)
.height(xxx)
.margins(xxx)
.dimension(xxx)
.group(xxx)
.x(d3.scale.ordinal().domain([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]))
.xUnits(dc.units.ordinal)
.centerBar(false)
.transitionDuration(transitionDuration)
.elasticY(true)
.gap(1)
.renderHorizontalGridLines(true)
.yAxis().ticks(2)
;

Resources