1

I use dc.js for showing the results of multiple classification algorithms. More specifically, I want to show a precision recall chart (each point corresponds to a result of a classification system). I already used a dc.js scatter chart for this which works fine. Additionally I would like to have a d3 contour in the background of the chart which shows the F-measure. This is already implemented. The only issue is that the contour part is in the foreground and not in the background of the chart. Please have a look at the jsfiddle for a full example.

Two questions are still open for me because I'm not a dc.js or d3 expert:

  1. Is there a way to put the contour in the background or the symbols(cycles) of the scatter chart in the foreground (I already tried it with the help of this stackoverflow question but with no success)
  2. I used the 'g.brush' selector to get the area of the inner chart. This works fine as long as the brushing is turned on. Is the selector a good way to go or are there better alternatives (which may also work if brushing is switched off).

In my example I put the contour part in the upper left corner to see if it works but I also provide the code (currently uncommented) to increase the width and height of the contour to the correct size.

chart
  .on('renderlet', function (chart) {
    var innerChart = chart.select('g.brush');

    var width = 300, height=300;
    //getting the correct width, height
    //var innerChartBoundingRect = innerChart.node().getBoundingClientRect();
    //var width = innerChartBoundingRect.width, height=innerChartBoundingRect.height;

    [contours, color] = generateFmeasureContours(width,height, 1);

    innerChart
      .selectAll("path")
      .data(contours)
      .enter()
      .append("path")
      .attr("d", d3.geoPath())
      .attr("fill",  d => color(d.value));

    var symbols = chart.chartBodyG().selectAll('path.symbol');
        symbols.moveToFront();
  });

jsfiddle

A. Nadjar
  • 2,440
  • 2
  • 19
  • 20
sven
  • 47
  • 3
  • it is all based on the [dc.js example 'bar chart with extra line'](https://dc-js.github.io/dc.js/examples/bar-extra-line.html) but it renders on top of the chart and not in the background – sven Sep 01 '19 at 17:25
  • Maybe a composed chart with geoChoroplethChart and scatter works, but I think the geoChoroplethChart needs to have a geojson and not only a d3.geoPath – sven Sep 01 '19 at 17:54
  • Cool!! I look forward to answering this (if someone doesn't beat me to it). Would love to add an example like this.. – Gordon Sep 03 '19 at 09:57

1 Answers1

0

Putting something in the background is a general purpose SVG skill.

SVG renders everything in the order it is declared, from back to front, so the key is to put your content syntactically before everything else in the chart.

I recommend encapsulating it in an svg <g> element, and to get the order right you can use d3-selection's insert method and the :first-child CSS selector instead of append:

  .on('pretransition', function (chart) {
    // add contour layer to back (beginning of svg) only if it doesn't exist
    var contourLayer = chart.g().selectAll('g.contour-layer').data([0]);
    contourLayer = contourLayer
        .enter().insert('g', ':first-child')
        .attr('class', 'contour-layer')
        .attr('transform', 'translate(' + [chart.margins().left,chart.margins().top].join(',') + ')')
        .merge(contourLayer);

A few more points on this implementation:

  • use dc's pretransition event because it happens immediately after rendering and redrawing (whereas renderlet waits for transitions to complete)
  • the pattern .data([0]).enter() adds the element only if it doesn't exist. (It binds a 1-element array; it doesn't matter what that element is.) This matters because the event handler will get called on every redraw and we don't want to keep adding layers.
  • we give our layer the distinct class name contour-layer so that we can identify it, and so the add-once pattern works
  • contourLayer = contourLayer.enter().insert(...)...merge(contourLayer) is another common D3 pattern to insert stuff and merge it back into the selection so that we treat insertion and modification the same later on. This would probably be simpler with the newer selection.join method but tbh I haven't tried that yet.
  • (I think there may also have been some improvements in ordering that might be easier than insert, but again, I'm going with what I know works.)
  • finally, we fetch the upper-left offset from the margin mixin

Next, we can retrieve the width and height of the actual chart body using (sigh, undocumented) methods from dc.marginMixin:

var width = chart.effectiveWidth(), height = chart.effectiveHeight();

And we don't need to move dots to front or any of that; the rest of your code is as before except we use this new layer instead of drawing to the brushing layer:

contourLayer
  .selectAll("path")
  .data(contours)
  .enter()
  .append("path")
  .attr("d", d3.geoPath())
  .attr("fill",  d => color(d.value));

properly layered and positioned

Fork of your fiddle.

Again, if you'd like to collaborate on getting a contour example into dc.js, that would be awesome!

Community
  • 1
  • 1
Gordon
  • 19,811
  • 4
  • 36
  • 74