Как отразить этот линейный градиент по оси x и настроить его при масштабировании - PullRequest
2 голосов
/ 09 мая 2020

Я работал над диаграммой с областями для временных рядов, которые находятся близко к нулю и ниже него. Мне нужны возможности масштабирования и фокусировки / всплывающей подсказки, которые я уже реализовал. Вы можете перетащить и выделить область диаграммы, и она увеличится. Двойной щелчок уменьшит масштаб.

Я хочу сделать акцент на значениях, наиболее удаленных от нуля, используя линейный градиент от голубого до темно-синего. Мне удалось добавить его для значений выше нуля (от 0 до yMax).

Как я мог симметрично «отразить» этот эффект градиента для отрицательных значений? Градиент должен быть семантически логичным, например yMin, изображенный здесь как -0,5, должен иметь примерно уровень цвета +0,5, а не yMax.

Как я могу применить градиент к обводке линии?

Как избежать искажения градиента при масштабировании?

https://stackblitz.com/edit/angular-deviation-chart-gradient enter image description here

Ответы [ 3 ]

6 голосов
/ 11 мая 2020

Чтобы это работало, я изменил две вещи. Во-первых, сделать так, чтобы градиент охватывал всю область «под» линией диаграммы. Он простирается между минимальным и максимальным значениями 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. Стопы устанавливаются таким образом, что:

  1. «минимальный цвет» находится по оси x,
  2. самое высокое значение y получает «максимальный цвет»
  3. максимальное значение на другой стороне оси 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})`;
  }
3 голосов
/ 11 мая 2020

Для первого вопроса, вот stackblitz , также обратите внимание, чтобы градиент работал, вы должны умножить y1 на 2 вместе с градиентом offset:

svg.append("linearGradient")                
    .attr("id", "area-gradient")            
    .attr("gradientUnits", "userSpaceOnUse")    
    .attr("x1", 0).attr("y1", y(0) * 2)         
    .attr("x2", 0).attr("y2", y(yExtent[1]))        
  .selectAll("stop")                        
    .data([                             
      {offset: "0%", color: "darkblue"},
      {offset: "50%", color: "lightblue"},  
      {offset: "100%", color: "darkblue"},
    ])      

Вопрос 2, если вы добавите градиент, который сделает его невидимым и устранит всю цель его присутствия, поскольку он имеет тот же цвет, что и область заливки.

Вопрос 3, «Как избежать искажение градиента при масштабировании? » У меня нет проблем с масштабированием, какой браузер вы используете?

2 голосов
/ 13 мая 2020

Я не разбираюсь в d3, поэтому не могу сильно помочь вам с кодом.

Однако позвольте мне объяснить вам способ решения вашей проблемы

Рассчитайте минимальное и максимальное значения y в ваших данных

Эти строки кода, взятые из ответа wiktus239, похоже, делают это

const dataMaxValue = Math.max(...this.data.map(element => element[1]));
const dataMinValue = Math.min(...this.data.map(element => element[1]));

Теперь возьмите наибольшее из их абсолютных значений.

const dataLimit = Math.max (Math.abs(dataMaxValue), Math.abs(dataMinValue));

Градиент должен быть нарисован между -dataLimit и + dataLimit.

Теперь сопоставьте эти значения данных со значениями y графика. Я считаю, что это делается с помощью функции y, но если я ошибаюсь, замените соответствующий метод

const yMax = y(dataLimit);
const yMin = y(-dataLimit);

Это данные, которые вам нужно установить для градиента:

.attr("gradientUnits", "userSpaceOnUse")    
.attr("x1", 0).attr("y1", yMax)
.attr("x2", 0).attr("y2", yMin)

смещение остается прежним

  {offset: "0%", color: "darkblue"},
  {offset: "50%", color: "lightblue"},  
  {offset: "100%", color: "darkblue"},

Это решение должно исправить ваши вопросы 1 и 3.

Что касается второго, вы можете установить stroke-width: 0, чтобы обводка линии просто не будет видно

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...