17

I would like to fill until a specific Y value in PlotlyJS. This is as far as I got from the PlotlyJS docs: Fiddle

{
  "x": [
    "2016-01-31T00:03:57.000Z",
    "2016-02-12T04:35:26.000Z"
  ],
  "y": [
    100,
    100
  ],
  "fill": "tonexty",
  "fillcolor": "#8adcb3"
}

In the documentation, there seems to be two options:

  1. tonexty - Fills as below. The problem is 'tonexty' is a bit limiting - The use case is 'filling until the line', so shading ONLY above 110. Example:

enter image description here

  1. tozeroy - Fills till zero:

enter image description here

Also, do you need to introduce a new trace in order to create a fill?

This means that if I have a chart as follows (with only one trace but a threshold line as a shape): I need to introduce another trace just to create a fill. Maybe there's something I missed in the docs, or this is the wrong approach altogether. enter image description here

So, how do you fill an area in a trace above a specific Y value in PlotlyJS?

David
  • 15,652
  • 26
  • 115
  • 156

2 Answers2

6

A solution is to use multiple traces.
Split all your traces between ones which are above 0 and ones which are not.
When you are done you can fill them (or not) with the 'tozeroy' value.

The following jsfiddle shows a working example.

The code is as following :

HTML:

<div id="myDiv" style="width:600px;height:250px;"></div>

JS:

var data = [
  {
    x: ['A', 'B', 'C', 'D'],
    y: [1, 3, 6, 0],
    fill: 'tozeroy',
    fillcolor: '#8adcb3'
  },
  {
    x: ['D', 'F', 'G', 'I'],
    y: [0, -3, -2, 0],
    fill: 'toself'
  },
   {
    x: ['I', 'J', 'K'],
    y: [0, 5, 7],
    fill: 'tozeroy',
    fillcolor: '#0adcb3'
  }
];

Plotly.newPlot('myDiv', data);

The result looks as following :
Result plot

John-Philip
  • 3,392
  • 2
  • 23
  • 52
  • 1
    [Don't cross the streams](http://quotegeek.com/quotes-from-movies/ghostbusters/206/) More seriously, actually because I was just playing with the plotly lib I can't give you a better solution. I'm curious though how this could be achieved with a more elegant solution. – John-Philip Aug 03 '17 at 19:40
  • It's a pretty good solution. I didn't come up with some more elegant either. You could use legendgroup to make the legend prettier but that's it. – Maximilian Peters Aug 03 '17 at 19:52
  • But more critical: The question was 'above a specific Y-value'. I don't think this will work with your solution or am I missing something? – Maximilian Peters Aug 03 '17 at 22:35
4

Here is another solution exploiting Plotly's fill: "toself". The idea is to create a closed line trace which encloses the area above the threshold and the markers of the main line. Works for threshold values above zero and for numerical x-values.

enter image description here The helper traces have their legend hidden and are grouped with the main trace, thereby preventing ugly artifacts when toggling the legend.

The function checks for each x-y-pair if the y-value is above the threshold, if yes

  • check if there is already a segment above the threshold and use this one OR create a new sgement
  • the segement starts from the y-value of the threshold and the intermediate x-value from the point above the threshold and the one before.
  • each segment is terminated with an y-value which is equal to the threshol and the x-value which the mean of the last point in the segment and the next one

The function itself can be surely written in a nicer way but it's just a proof-of-concept.

function dataToTraces(data, threshold) {

  var fillers = [];

  var emptyFiller = {
    x: [],
    y: [],
    fill: "toself",
    mode: "lines",
    line: {
      width: 0
    },
    opacity: 0.5,
    fillcolor: "#8adcb3",
    showlegend: false,
    legendgroup: "main"
  }
  
  fillers.push(emptyFiller);

  for (var i = 0; i < data.y.length; i += 1) {
    if (data.y[i] >= threshold) {
      if (i !== 0 && data.y[i - 1] < threshold) {
        fillers[fillers.length - 1].x.push(data.x[i - 1] + (threshold - data.y[i - 1]) / (data.y[i] - data.y[i - 1]));
        fillers[fillers.length - 1].y.push(threshold);
      }
      fillers[fillers.length - 1].x.push(data.x[i]);
      fillers[fillers.length - 1].y.push(data.y[i]);
    } else if (fillers[fillers.length - 1].x.length > 0) {
      if (i !== 0 && data.y[i - 1] !== threshold) {
        fillers[fillers.length - 1].x.push(data.x[i - 1] + (threshold - data.y[i - 1]) / (data.y[i] - data.y[i - 1]));
        fillers[fillers.length - 1].y.push(threshold);
      }
      fillers.push(emptyFiller);
    }
  }
  return fillers;
}

var data = [{
  x: [0, 1, 2, 3, 4, 5, 6, 7, 8],
  y: [1, 3, 6, 2, -1, 5, 1, 3, 0],
  name: "main",
  legendgroup: "main"
}];
var fillers = dataToTraces(data[0], 2);

Plotly.newPlot("myDiv", data.concat(fillers));
<div id="myDiv"></div>
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
Maximilian Peters
  • 30,348
  • 12
  • 86
  • 99