Fill / Shade a chart above a specific Y value in PlotlyJS
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.
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>
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 :