Реализация тепловой карты фронтенд-алгоритма, которого вы не знаете

внешний интерфейс алгоритм JavaScript внешний фреймворк Canvas
Реализация тепловой карты фронтенд-алгоритма, которого вы не знаете

Автор статьи: Ли Фэнлу, инженер по визуализации TalkingData.

Монтажер: Аресн

Добро пожаловать в группу QQ для участия в технических дискуссиях: 618308202

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

Адрес гитхаба:GitHub.com/говорящие данные…(Нажмите на звездочку, чтобы поддержать автора!)

Название «тепловая карта» звучит очень высокопарно, но на самом деле оно эквивалентно карте плотности, о которой мы часто говорим.

image

Как показано на рисунке, красная область указывает на высокую плотность элементов анализа, а синяя область указывает на низкую плотность элементов анализа. Пока точки плотные, формируются сгруппированные области. Увидев такой ослепительный эффект, вы тоже хотите его реализовать? Далее мы реализуем жару вручную (притворяясь, что заставляем вас летать, ха-ха), торжественно заявляем: следующие фрагменты кода все изinMap.

Подготовить данные

inMap получает данные широты и долготы, которые необходимо сопоставить с пиксельными координатами холста.При этом используется преобразование Меркатора.Алгоритм Меркатора очень сложен.В будущем у нас будет отдельная статья,чтобы рассказать о его принципе. После преобразования данные, которые вы получите, должны выглядеть так:

[
  {
    "lng": "116.395645",
    "lat": 39.929986,
    "count": 6,
    "pixel": { //像素坐标
      "x": 689,
      "y": 294
    }
  },
  {
    "lng": "121.487899",
    "lat": 31.249162,
    "count": 10,
    "pixel": { //像素坐标
      "x": 759,
      "y": 439
    }
  },
  ...
]

Что ж, мы получаем преобразованные данные координат пикселей (x, y) и можем сделать следующее.

Создание градиентной заливки холста

Создайте круг с градиентом от черного к белому

let gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
gradient.addColorStop(0, 'rgba(0,0,0,1)');
gradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradient;
ctx.arc(x, y, radius, 0, Math.PI * 2, true);
  • createRadialGradient() создает объект линейного градиента
  • addColorStop() определяет полосу градиентного цвета

Эффект показан на рисунке:

image
Тогда возникает вопрос, если каждый счетчик значения веса данных отличается, как мы его представляем?

установить globalAlpha

Установите разные альфа-каналы в соответствии с разными значениями счетчика.Предполагая, что альфа-канал самого большого счетчика равен 1, а альфа-канал наименьшего счетчика равен 0, я вычисляю альфа-канал на основе этого счетчика.

let alpha = (count - minValue) / (maxValue - minValue);

Затем мы кодируем следующим образом:

drawPoint(x, y, radius, alpha) {
    let ctx = this.ctx;
    ctx.globalAlpha = alpha; //设置 Alpha 透明度
    ctx.beginPath();
    let gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
    gradient.addColorStop(0, 'rgba(0,0,0,1)');
    gradient.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = gradient;
    ctx.arc(x, y, radius, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fill();
}

Эффект сильно отличается от предыдущего скриншота, можете сравнить изменения прозрачности.

image
(Такая темная масса, большой разрыв с жарой)

image

сбросить цвет холста холста

  • getImageData() копирует пиксельные данные указанного прямоугольника на холсте.
  • putImageData() помещает данные изображения обратно на холст:

Формат данных, возвращаемый функцией getImageData(), следующий:

{
  "data": {
    "0": 0,   //R
    "1": 128, //G
    "2": 0,   //B
    "3": 255, //Aplah
    "4": 0, //R
    "5": 128, //G
    "6": 0,  //B
    "7": 255, //Aplah
    "8": 0,
    "9": 128,
    "10": 0,
    "11": 255,
    "12": 0,
    "13": 128,
    "14": 0,
    "15": 255,
    "16": 0,
    "17": 128,
    "18": 0,
    "19": 255,
    "20": 0,
    "21": 128,
    "22": 0
    ...

Возвращаемые данные представляют собой одномерный массив, в котором каждые четыре элемента представляют значение пикселя (rgba).

Реализуйте тепловой принцип: считывайте альфа-значение (прозрачность) каждого пикселя и создавайте карту цветов.

код показывает, как показано ниже:

let palette = this.getColorPaint(); //取色面板
let img = ctx.getImageData(0, 0, container.width, container.height);
    let imgData = img.data;
    let max_opacity = normal.maxOpacity * 255;
    let min_opacity = normal.minOpacity * 255;
    //权重区间
    let max_scope = (normal.maxScope > 1 ? 1 : normal.maxScope) * 255;
    let min_scope = (normal.minScope < 0 ? 0 : normal.minScope) * 255;
    let len = imgData.length;
    for (let i = 3; i < len; i += 4) {
        let alpha = imgData[i]; 
        let offset = alpha * 4;
        if (!offset) {
            continue;
        }
        //映射颜色
        imgData[i - 3] = palette[offset];
        imgData[i - 2] = palette[offset + 1];
        imgData[i - 1] = palette[offset + 2];

        // 范围区间
        if (imgData[i] > max_scope) {
            imgData[i] = 0;
        }
        if (imgData[i] < min_scope) {
            imgData[i] = 0;
        }

        // 透明度
        if (imgData[i] > max_opacity) {
            imgData[i] = max_opacity;
        }
        if (imgData[i] < min_opacity) {
            imgData[i] = min_opacity;
        }
    }
    //将设置后的像素数据放回画布
ctx.putImageData(img, 0, 0, 0, 0, container.width, container.height);

Создайте палитру, хорошая палитра определяет конечный эффект. inMap создает палитру длиной 256 пикселей:

let paletteCanvas = document.createElement('canvas');
let paletteCtx = paletteCanvas.getContext('2d');
paletteCanvas.width = 256;
paletteCanvas.height = 1;
let gradient = paletteCtx.createLinearGradient(0, 0, 256, 1);

Цвета inMap по умолчанию следующие:

this.gradient = {
    0.25: 'rgb(0,0,255)',
    0.55: 'rgb(0,255,0)',
    0.85: 'yellow',
    1.0: 'rgb(255,0,0)'
};

Установите цвет градиента для объекта палитры

for (let key in gradient) {
    gradient.addColorStop(key, gradientConfig[key]);
}

Вернуть пиксельные данные палитры:

return paletteCtx.getImageData(0, 0, 256, 1).data;

Результирующая визуализация палитры выглядит следующим образом: (выглядит как полоса градиентного цвета)

image

Окончательная тепловая карта, которую мы получили, выглядит следующим образом:

image

уведомление о следующем разделе

В следующем разделе мы сосредоточимся на реализации алгоритма предотвращения литерала inMap.