How to position NVD3 line graph above all other area / bar graphs?
Asked Answered
K

3

3

The Plunker example

In the example above you can see that the line is rendered under the orange area graph:

enter image description here

Was reading this trend here, then I tried this d3.select('svg#chart .lines1Wrap').moveToFront();, but got the error moveToFront is not a function.

Chart code

var data = [{
  "key": "Price",
  "type": "line",
  "yAxis": 2,
  "values": [
    [1443621600000, 71.89],
    [1443619800000, 75.51],
    [1443618000000, 12.49],
    [1443616200000, 20.72],
    [1443612600000, 70.39],
    [1443610800000, 59.77],
  ]
}, {
  "key": "Quantity1",
  "type": "area",
  "yAxis": 1,
  "values": [
    [1136005200000, 1],
    [1138683600000, 5],
    [1141102800000, 10],
    [1143781200000, 0],
    [1146369600000, 1],
    [1149048000000, 0],
  ]
}];

data = data.map(function(series) {
  series.values = series.values.map(function(d) {
    return {
      x: d[0],
      y: d[1]
    }
  });
  return series;
});

nv.addGraph(function() {
  var chart = nv.models.multiChart()
    .margin({
      top: 20,
      right: 40,
      bottom: 50,
      left: 40
    })
    .yDomain1([0, 10])
    .yDomain2([0, 100]) // hard-coded :<
    .interpolate("linear") // don't smooth out the lines
    .color(d3.scale.category10().range());

  chart.xAxis.tickFormat(function(d) {
    return d3.time.format('%I:%M')(new Date(d));
  });
  chart.yAxis1.tickFormat(d3.format(',f'));
  chart.yAxis2.tickFormat(function(d) {
    return '$' + d3.format(',f')(d)
  });

  d3.select('svg#chart')
    .datum(data)
    .transition().duration(500).call(chart);

  d3.selection.prototype.moveToFront = function() {
      return this.each(function() {
          this.parentNode.appendChild(this);
      });
  };

  d3.select('svg#chart .lines1Wrap').moveToFront();

  chart.update();
  nv.utils.windowResize(chart.update);
  return chart;
});

UPDATE Found this answer here, tried to use the solution:

d3.selection.prototype.moveToFront = function() {
    return this.each(function() {
        this.parentNode.appendChild(this);
    });
};

d3.selection.prototype.moveToBack = function() { 
    return this.each(function() { 
        var firstChild = this.parentNode.firstChild; 
        if (firstChild) { 
            this.parentNode.insertBefore(this, firstChild); 
        } 
    }); 
};

d3.select('svg#chart .lines1Wrap').moveToFront();
d3.select('svg#chart .nv-areaWrap').moveToBack();

No more error, however the blue line graph still is not moved in front of all the others.

Kletter answered 7/10, 2015 at 19:22 Comment(0)
D
2

Add this to move your line(line chart) DOM element after the line(Area Chart) DOM element.

d3.select('.lines2Wrap').node().parentNode.insertBefore(d3.select('.stack1Wrap').node(), d3.select('.lines2Wrap').node());

Full working code here

Hoe this helps! :)

Doorstep answered 8/10, 2015 at 9:37 Comment(0)
V
1

As I struggled with a similar problem for two days I will post my solution here in case it helps someone.

In my chart settings I have dispatch function which will append a rectangle to the chart and then lower it to the bottom:

dispatch: {
  renderEnd: () => {
     drawThresholds();
     lowerThresholdAreas();
  }
}

In the drawThresholds I draw the actual rectangle:

const drawThresholds = () => {
  try {
    let svgContainer = d3.select(`svg g`);

     svgContainer.append('rect')
        .attr('x', 0)
        .attr('y', 2)
        .attr('width', 200)
        .attr('height', 200)
        .attr('class', 'threshold_area')
        .attr('fill', 'yellow');
    }
    catch (err) {
    // Selector throws an error instead of returning empty list
    // when element is not found.
    // Ignore exceptions, they occur because page is not loaded yet.
    }
  };

And finally after rectangles are appended, I need to place them to the bottom by calling lowerFunction. use d3 selectAll to get all .threshold_area classes and then insert them before the line.

const lowerThresholdAreas = () => {
  d3.selectAll('.threshold_area').each(function () {
    this.parentNode.insertBefore(this, this.parentNode.firstChild);
  });
};

And what is happening, is that on SVG canvas there is no z-index. It means the order of appends defines the layers, meaning if you append the rectangle as last item, it will be on top. So the order of elements needs to be changed.

Also a step where I did mistake was that I first used ES6 function declaration and it doesn't work with "this" as I supposed it would.

Read more about selection: https://github.com/d3/d3-selection/blob/master/README.md#select

Read more about lowering or raising items: https://github.com/d3/d3-selection/blob/master/README.md#selection_lower

Here is a working plunker for seeing it in action: https://plnkr.co/edit/QI2HcxcJYRAv0FzXWihd?p=preview

Vespucci answered 8/5, 2018 at 12:54 Comment(0)
F
0

I think it's just that on your particular graph, the line series is under lines2wrap, not lines1wrap. IIRC, this has to do with which series is 'first'. In your plunker, I changed the 1 to a 2 and it worked.

It's a hacky workaround (that's mine from Github, thanks for notifying me), and probably requires some tinkering to be more generally useful.

Fetus answered 7/10, 2015 at 20:9 Comment(3)
Hmm, could you post your forked version? I tried d3.select('svg#chart .lines2wrap').moveToFront(); still same results however...Kletter
What sucks is that I have to render the line data first in my app, in order to obtain the min and max x values... to send into another API request to get the data for the other graphs.Kletter
Just wait to render everything until you get the other data?Disruptive

© 2022 - 2024 — McMap. All rights reserved.