Cannot hover over points that are behind path.area - d3.js

As the title mention we are not able to hover over point thats are behind a path.area.
The situation is the following
If we hover the blue and the yellow line we are able to get the tooltip but if we hover over the red line we get the tooltip only from the first and the last point.
The code is the following:
let nix = crossfilter();
timeseriesFiltered.forEach(ts => {
ndx.add(ts.values.map(function(d) {
let temp = JSON.parse(JSON.stringify(objTemplate));
temp[ts.place] = d.value;
temp.date = new Date(d.timestamp);
return temp;
}));
});
let dimDate = ndx.dimension(dc.pluck('date'));
let lineChartGroups = [];
timeseriesFiltered.forEach((ts, index) => {
let group = dimDate.group().reduceSum(dc.pluck(ts.place));
let lineChart = dc.lineChart(composite)
.dimension(dimDate)
.renderDataPoints(true)
.renderArea(true)
.defined(d => {
return d.y != 0;
})
.colors(this.colors[index])
.group(group, this.setName(ts.place));
lineChartGroups.push(lineChart);
})
let chart = composite
.width(width)
.height(300)
.margins({top: 30, right: 60, bottom: 30, left: 60})
.x(d3.scaleTime().domain([new Date(minDate), new Date(maxDate)]))
.yAxisLabel(timeseriesFiltered[0].unit)
.elasticY(true)
.mouseZoomable(true)
.brushOn(false)
.clipPadding(10)
.legend(dc.legend().x(80).y(20).itemHeight(13).gap(5))
._rangeBandPadding(1)
.compose(lineChartGroups);
chart.render();
We have tried to raise all the circle dot by using the following statement:
d3.selectAll('circle.dot').raise();
But it didn't work. Any suggestion?

I'm not sure that it's a great idea to use renderArea in a composite chart; as you can see, the colors get muddied together into brown, and it's not really clear what it's supposed to convey.
I think renderArea works better with a stacked chart, where the area that is covered by each color means something.
That said, it's pretty easy to fix the problem you are seeing.
The reason why raising the dots doesn't work is because each child of the composite chart is in its own layer. So raising the dots only puts them at the top of that chart (where they already are).
Instead, you can disable mouse interactions for the filled areas:
path.area {
pointer-events: none;
}
Since the filled areas weren't interactive before, this shouldn't lose much, but you might want to be more conservative and restrict the rule to the particular chart with the selector #composite-chart path.area

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 disable brush resizing

I am trying to disable brush extent on a line chart and set a fixed width.
I am aware this subject has been discussed several times. I have tried without success to work on this answer: Disable brush resize (DC.js, D3.js) but I couldn't find a solution matching my situation.
If it makes any difference, I am expanding on the brushable ordinal chart discussed in this question: DC.js - Custom ordering of ordinal scale line chart
Here is the chart initialization code:
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)
.filter(dc.filters.RangedFilter(0,0.9));
line.yAxis().ticks(4);
line.xAxis()
.tickValues(d3.range(data.length))
.tickFormat(function(d) { return graphGroup.int2ord(d); });
line.filterHandler(function(dimension, filters) {
if(!filters || !filters.length) {
dimension.filter(null);
return filters;
}
console.assert(filters.length === 1);
console.assert(filters[0].filterType === 'RangedFilter');
var inside = graphGroup.all().filter(function(kv) {
var i = graphGroup.ord2int(kv.key);
return filters[0][0] <= i && i < filters[0][1];
}).map(function(kv) { return kv.key; });
dimension.filterFunction(function(d) {
return inside.indexOf(d) >= 0;
});
return filters;
})
And the fiddle: https://jsfiddle.net/bob_magnus_1/sr7hmnvf/9/`
Is there a simple way to override coordinateGridMixin.extendBrush function in such a chart?
The previous question was for DCv2. Fixed-width brushes are even easier in DCv3+ (because of improvements to d3-brush in D3v4).
It's the same technique as before: look at how extendBrush is implemented, and then replace it. Here's how it looks in DCv3:
_chart.extendBrush = function (brushSelection) {
if (brushSelection && _chart.round()) {
brushSelection[0] = _chart.round()(brushSelection[0]);
brushSelection[1] = _chart.round()(brushSelection[1]);
}
return brushSelection;
};
(source)
It takes a selection – an array of two elements – and returns a new selection.
In your case, your data is ordinal but the scale is linear in order to enable brushing. The brush should match the scale.
To keep it at 0.9 width:
line.extendBrush = function(brushSelection) {
brushSelection[1] = brushSelection[0] + 0.9;
return brushSelection;
};
Hiding the brush handles (which have weird behavior) with CSS:
.brush .custom-brush-handle {
display: none;
}
Fork of your fiddle.
Ordinal brush with snapping
I realized (looking at your 0.9 width brush) that you probably want proper ordinal brushing where it snaps to the region around the one selected point.
You can do this by setting the begin and end of the selection:
line.extendBrush = function(brushSelection) {
brushSelection[0] = Math.round(brushSelection[0]) - 0.5;
brushSelection[1] = brushSelection[0] + 1;
return brushSelection;
};
We find the the currently hovered point by rounding, and then set the begin to 0.5 before it, and the end to 0.5 after it.
Initialize the filter to surround the zero point:
line
.filter(dc.filters.RangedFilter(-0.5,0.5));
Revised fiddle.
Floating point bug fix
I noticed that if you select 20/30 above (linear value 3), brushSelection[0] will have changed from 2.5 to 2.49999999, causing the brush to jump back by one. Some numbers don't represent correctly in floating point.
It's safer to use the average of begin and end:
line.extendBrush = function(brushSelection) {
const point = Math.round((brushSelection[0] + brushSelection[1])/2);
return [
point - 0.5,
point + 0.5
];
};
Fiddle version with 20/30 selectable.

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.

dc.js composite chart with bar and line. implementing single select on bar

I'm trying to recreate the single select bar on a dc.js composite chart as shown here
https://dc-js.github.io/dc.js/examples/bar-single-select.html
I've tried adding a filter handler to the child chart but it never gets called when I click on the bar. I've also tried adding a filter handler to the Composite chart itself with no luck. Is there any way I can select a bar on a composite chart or do I have to assign it a colour and then color the other bars grey manually and redraw the graph based on what was clicked?
This is the initialization of the graph in my component.
The data goes through a formatting process where I parse the date using the formatData function. I also pass in a dimensions prop (apologies for the bad naming) which tells the component what kind of chart should correspond to the chart name and the color of the dataset.
dimensions={
{"Data1": ["line", AppStyles.color.warning],
"Data2": ["line", AppStyles.color.danger],
"Data3": ["bar", AppStyles.color.blue]
}
}
formatData = (data) => {
let formattedData = [];
for(let key in data) {
formattedData.push({
...data[key],
x: this.parseDate.parse(data[key].x)
})
}
return formattedData;
}
componentDidMount(){
let data = this.formatData(this.props.data);
this.ndx = crossfilter.crossfilter(data);
this.chart = dc.compositeChart(this.multiLineChartContainer);
this.dimension = this.ndx.dimension((d) => {
return d.x;
});
let minDate = this.dimension.bottom(1)[0].x;
let maxDate = this.dimension.top(1)[0].x;
let composeGroup = [];
Object.keys(this.props.dimensions).map((dim,i) => {
let grp = this.dimension.group().reduceSum((d) => {
return d[dim];
});
if(this.props.dimensions[dim][0] === "bar"){
composeGroup.push(dc.barChart(this.multiLineChartContainer)
.group(grp, dim)
.colors("blue")
.centerBar(true)
.addFilterHandler(function(filters, filter) {return [filter];})
)
} else {
composeGroup.push(dc.lineChart(this.multiLineChartContainer)
.group(grp, dim)
.colors(this.props.dimensions[dim][1])
.useRightYAxis(true)
);
}
});
this.chart.width(this.props.width)
.height(this.props.height)
.renderHorizontalGridLines(true)
.x(d3.time.scale().domain([minDate, maxDate]))
.elasticY(true)
.elasticX(true)
.xAxisLabel("Cohort")
.brushOn(false)
.yAxisLabel("Left")
.rightYAxisLabel("Right")
.xUnits(()=>{
return 30;
})
.legend(dc.legend().x(this.chart.width()- 130))
.compose(composeGroup)
this.chart.renderlet((chart) => {
chart.selectAll('circle, rect.bar').on("click", (event) => {
this.props.dataSelect(event);
});
});
this.chart.xAxis().ticks(5)
this.chart.render();
}
Please consider adding your code (or better, a running example) next time you ask a question on SO.
It would also help to spell out what "no luck" means - wrong click behavior? No chart displayed at all?
It's hard to guess what might be going wrong for you.
This works fine for me, although ordinal scales are a little bit tricky, and composing them in a composite chart even more so.
Is the problem that you were not using an ordinal scale? Because currently the kind of selection (brush or click) is determined by the scale/xUnits and it's hard to get around it.
composite
.width(768)
.height(480)
.x(d3.scaleOrdinal().domain(d3.range(1,21)))
.xUnits(dc.units.ordinal)
.yAxisLabel("The Y Axis")
.legend(dc.legend().x(80).y(20).itemHeight(13).gap(5))
.brushOn(true)
.renderHorizontalGridLines(true)
.compose([
dc.barChart(composite)
.dimension(dim)
.colors('blue')
.group(grp2, "Bars")
.addFilterHandler(function(filters, filter) {return [filter];})
.centerBar(true),
dc.lineChart(composite)
.dimension(dim)
.colors('red')
.group(grp1, "Dots")
.dashStyle([2,2])
])
.render();
https://jsfiddle.net/gordonwoodhull/ronqfyj0/39/

set row chart labels outside the bar like a barchart in Dc.js?

How can I have the labels outside the bar in a row chart with dc.js?
I'd like a graph like this:
however, the labels are inside the actual bars... is there any settings i need to change to have it like this?
I finally got this working and here is the trick,
As mentioned by the Gordon it can be done on renderlet.
First I have gave the margin for my row chart ,
.margins({ top: 20, right: 20, bottom: 40, left: 110 })
Then took the label outside by giving x value in negative.
.labelOffsetX(-10)
This will perfectly move our label but its still need some style changes to look fine and that need to be done on renderlet,
.on('renderlet', function (chart) {
chart.selectAll("g.row text")
.style("text-anchor", "end")
.call(function (t) {
t.each(function (d) {
var self = d3.select(this);
var text = self.text();
if (text.length > 14) {
self.text('');
text = text.substring(0, 14) + '..';
self.text(text);
}
})
});
})
You can ignore .call() function as I place this just to make sure my label length does not cross certain limit.
Hope that work for you.!

Resources