I'm working on a data visualization project using ECharts, and I need to create a confidence band that adjusts its transparency based on the proximity to the lower and upper bounds. Specifically, I want the transparency to be higher (less opaque) when the data points are near the lower or upper bounds, and lower (more opaque) when the data points are farther away from the bounds.
The below is a perfect example solution, but it lacks the dynamic transparency:
https://echarts.apache.org/examples/en/editor.html?c=confidence-band
ideally it should be as in below:
I think the line gradient can be used to do this as in below code:
option = {
xAxis: {
type: 'category'
},
yAxis: {
type: 'value'
},
series: [
{
data: [820, 932, 901, 934, 1290, 1330, 1320],
type: 'line'
},
{
color: 'rgba(255, 70, 131, 0)',
data: [300, 500],
type: 'line',
stack: 'area-1'
},
{
color: 'rgba(255, 70, 131, 0)',
stack: 'area-1',
data: [900, 1200],
type: 'line',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0.8667,
color: 'rgba(255, 70, 131, 0.8667)'
},
{
offset: 0.1333,
color: 'rgba(255, 70, 131, 0.1333)'
}
])
}
}
]
};
but the problem is that how to properly put the right offset, as you can see in the below image it's not right as in the bottom it's dark, which is wrong, it has to be dark when near the blue line
This is the best i could do. Its not exactly what you wanted because the gradient is taken between the minimal and maximal y value over all x values and not per point.
myChart.showLoading();
$.get(ROOT_PATH + '/data/asset/data/confidence-band.json', function (data) {
myChart.hideLoading();
var base = -data.reduce(function (min, val) {
return Math.floor(Math.min(min, val.l));
}, Infinity);
myChart.setOption(
(option = {
title: {
text: 'Confidence Band',
subtext: 'Example in MetricsGraphics.js',
left: 'center'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
animation: false,
label: {
backgroundColor: '#ccc',
borderColor: '#aaa',
borderWidth: 1,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 0,
color: '#222'
}
},
formatter: function (params) {
return (
params[2].name +
'<br />' +
((params[2].value - base) * 100).toFixed(1) +
'%'
);
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.map(function (item) {
return item.date;
}),
axisLabel: {
formatter: function (value, idx) {
var date = new Date(value);
return idx === 0
? value
: [date.getMonth() + 1, date.getDate()].join('-');
}
},
boundaryGap: false
},
yAxis: {
axisLabel: {
formatter: function (val) {
return (val - base) * 100 + '%';
}
},
axisPointer: {
label: {
formatter: function (params) {
return ((params.value - base) * 100).toFixed(1) + '%';
}
}
},
splitNumber: 3
},
series: [
{
name: 'L',
type: 'line',
data: data.map(function (item) {
return item.l + base;
}),
lineStyle: {opacity: 0},
symbol: 'none',
stack: 'bandLowerPart',
},
{
type: 'line',
data: data.map(function (item) {
return item.value - item.l;
}),
lineStyle: {opacity: 0},
showSymbol: false,
stack: 'bandLowerPart',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0.35, 0, 0, [
{
offset: 0.5,
color: 'rgba(112,128,144, 0.3)'
},
{
offset: 1,
color: 'rgba(112,128,144, 1)'
}
])
},
},
{
type: 'line',
data: data.map(function (item) {
return item.value + base;
}),
itemStyle: {color: '#333'},
showSymbol: false,
stack: 'bandUpperPart',
},
{
name: 'U',
type: 'line',
data: data.map(function (item) {
return item.u - item.value;
}),
lineStyle: {opacity: 0},
symbol: 'none',
stack: 'bandUpperPart',
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 0.65, [
{
offset: 0.5,
color: 'rgba(112,128,144, 0.3)'
},
{
offset: 1,
color: 'rgba(112,128,144, 1)'
}
])
},
},
]
})
);
});
I just built up on your example and put two gradients (one from upper line and one from lower line) instead of one from upper to lower.
I managed to build a really hacky solution by slicing the data into small pieces and plotting a gradient for each piece. Code is also pretty messy. Do with it what you want:
myChart.showLoading();
$.get(ROOT_PATH + '/data/asset/data/confidence-band.json', function (data) {
myChart.hideLoading();
var base = -data.reduce(function (min, val) {
return Math.floor(Math.min(min, val.l));
}, Infinity);
data.map((item) => {item.lb = item.l + base});
data.map((item) => {item.mlb = item.value - item.l});
data.map((item) => {item.mub = item.value + base});
data.map((item) => {item.ub = item.u - item.value});
let seriesList = [];
const filteredDatasets = [];
function createSeries(nrSlices) {
const sliceSize = Math.ceil(data.length / nrSlices);
for (let index = 0; index < nrSlices; index++) {
let lowIndex = index*sliceSize-1;
let highIndex = lowIndex + sliceSize+1;
if (highIndex >= data.length) {
highIndex = data.length - 1;
}
const datasetId = 'dataset_' + index;
const datasetSliced = data.slice(lowIndex, highIndex);
filteredDatasets.push({
id: datasetId,
source: datasetSliced,
});
const slice = [
{
type: 'line',
datasetId: datasetId,
encode: {x: 'date', y: 'lb'},
lineStyle: { opacity: 0 },
symbol: 'none',
stack: 'bandLowerPart' + index
},
{
type: 'line',
datasetId: datasetId,
encode: {x: 'date', y: 'mlb'},
lineStyle: { opacity: 0 },
showSymbol: false,
stack: 'bandLowerPart' + index,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 1, 0, 0, [
{
offset: 1,
color: 'rgba(112,128,144, 1)'
},
{
offset: 0.3,
color: 'rgba(112,128,144, 0.3)'
}
])
}
},
{
type: 'line',
datasetId: datasetId,
encode: {x: 'date', y: 'mub'},
itemStyle: { color: '#333' },
showSymbol: false,
stack: 'bandUpperPart' + index
},
{
type: 'line',
datasetId: datasetId,
encode: {x: 'date', y: 'ub'},
lineStyle: { opacity: 0 },
symbol: 'none',
stack: 'bandUpperPart' + index,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 0.6, [
{
offset: 0.3,
color: 'rgba(112,128,144, 0.3)'
},
{
offset: 1,
color: 'rgba(112,128,144, 1)'
}
])
}
}
];
seriesList = seriesList.concat(slice);
}
}
createSeries(100);
myChart.setOption(
(option = {
title: {
text: 'Confidence Band',
subtext: 'Example in MetricsGraphics.js',
left: 'center'
},
dataset: filteredDatasets,
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
animation: false,
label: {
backgroundColor: '#ccc',
borderColor: '#aaa',
borderWidth: 1,
shadowBlur: 0,
shadowOffsetX: 0,
shadowOffsetY: 0,
color: '#222'
}
},
formatter: function (params) {
return (
params[2].name +
'<br />' +
((params[2].value - base) * 100).toFixed(1) +
'%'
);
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: data.map(function (item) {
return item.date;
}),
axisLabel: {
formatter: function (value, idx) {
var date = new Date(value);
return idx === 0
? value
: [date.getMonth() + 1, date.getDate()].join('-');
}
},
boundaryGap: false
},
yAxis: {
axisLabel: {
formatter: function (val) {
return (val - base) * 100 + '%';
}
},
axisPointer: {
label: {
formatter: function (params) {
return ((params.value - base) * 100).toFixed(1) + '%';
}
}
},
splitNumber: 3
},
series: seriesList,
})
);
});