Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fill / Shade a chart above a specific Y value in PlotlyJS

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?

like image 444
David Avatar asked Jul 24 '17 13:07

David


2 Answers

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

like image 187
John-Philip Avatar answered Oct 31 '22 18:10

John-Philip


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>
like image 45
Maximilian Peters Avatar answered Oct 31 '22 19:10

Maximilian Peters