Чтобы это работало, я изменил две вещи. Во-первых, сделать так, чтобы градиент охватывал всю область «под» линией диаграммы. Он простирается между минимальным и максимальным значениями y
.
.attr("gradientUnits", "userSpaceOnUse")
.attr("x1", 0).attr("y1", y(d3.min(data, (d) => d[1])))
.attr("x2", 0).attr("y2", y(d3.max(data, (d) => d[1])))
Следующее, что нужно сделать, это изменить остановки для градиента. Это один градиент от наименьшего значения y
до максимального значения y
. Стопы устанавливаются таким образом, что:
- «минимальный цвет» находится по оси x,
- самое высокое значение y получает «максимальный цвет»
- максимальное значение на другой стороне оси x, чем 2. получает пропорциональный цвет
Поэтому я устанавливаю остановки другим методом
.selectAll("stop")
.data(this.getGradientData())
... и метод написан так
getGradientData(): Array<T> {
const dataMaxValue = Math.max(...this.data.map(element => element[1]));
const dataMinValue = Math.min(...this.data.map(element => element[1]));
const valuesSpan = dataMaxValue - dataMinValue;
if (Math.abs(dataMaxValue) > Math.abs(dataMinValue)) {
const shorterPartToLongerPartRatio = Math.abs(dataMinValue) / Math.abs(dataMaxValue);
const shorterPartToWholeRatio = Math.abs(dataMinValue) / valuesSpan;
return [
{offset: "0%", color: `rgba(0, 0, 0, ${shorterPartToLongerPartRatio})`},
{offset: `${shorterPartToWholeRatio * 100}%`, color: 'lightblue'},
{offset: "100%", color: "black"}
];
}
const shorterPartToLongerPartRatio = Math.abs(dataMaxValue) / Math.abs(dataMinValue);
const shorterPartToWholeRatio = Math.abs(dataMaxValue) / valuesSpan;
return [
{offset: "0%", color: 'black'},
{offset: `${100 - (shorterPartToWholeRatio * 100)}%`, color: 'lightblue'},
{offset: "100%", color: `rgba(0, 0, 0, ${shorterPartToLongerPartRatio})`}
];
}
Обратите внимание, как я изменил darkblue
на black
. Это простой способ найти пропорциональное значение цвета (с непрозрачностью), когда фон белый. Это можно улучшить, рассчитав желаемый цвет.
Решение должно работать, даже если отрицательные значения больше положительных. Вот модифицированный пример: https://stackblitz.com/edit/angular-deviation-chart-gradient-zrghr2
@ Edit: Что касается вопроса об избежании градиентного искажения при масштабировании. К сожалению, вы не можете этого предотвратить. Это проблема старая, как компьютерная графика, и причина в том, что цветов просто недостаточно :)
@ Edit2: Подход к поиску промежуточного цвета довольно прост:
getIntermediateColor(colorOneRGB, colorTwoRGB, ratio) {
const newR = colorOneRGB[0] + (colorTwoRGB[0] - colorOneRGB[0]) * ratio;
const newG = colorOneRGB[1] + (colorTwoRGB[1] - colorOneRGB[1]) * ratio;
const newB = colorOneRGB[2] + (colorTwoRGB[2] - colorOneRGB[2]) * ratio;
return `rgb(${newR}, ${newG}, ${newB})`;
}