trail: линии переменной толщины

Тип графического примитива trail предназначен для построения линий с переменной толщиной вдоль траектории. В отличие от обычной линии (line), где толщина остаётся постоянной, trail позволяет кодировать дополнительную количественную переменную через ширину сегментов.

Наиболее распространённые сценарии применения:

  • визуализация интенсивности потока;
  • отображение скорости изменения параметра;
  • временные ряды с акцентом на величину значения;
  • маршруты движения;
  • погодные и географические данные;
  • графики нагрузки;
  • финансовые временные ряды.

Основное отличие:

Тип Толщина
line фиксированная
trail изменяется вдоль линии

Базовый синтаксис trail в Vega-Lite

Минимальная структура:

{
  "mark": "trail",
  "encoding": {
    "x": {"field": "x"},
    "y": {"field": "y"},
    "size": {"field": "value"}
  }
}

Канал size управляет толщиной линии.


Простейший пример

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "data": {
    "values": [
      {"x": 0, "y": 10, "v": 2},
      {"x": 1, "y": 20, "v": 5},
      {"x": 2, "y": 15, "v": 8},
      {"x": 3, "y": 25, "v": 3},
      {"x": 4, "y": 18, "v": 6}
    ]
  },

  "mark": "trail",

  "encoding": {
    "x": {"field": "x", "type": "quantitative"},
    "y": {"field": "y", "type": "quantitative"},

    "size": {
      "field": "v",
      "type": "quantitative"
    }
  }
});

В этом примере:

  • координаты линии задаются через x и y;
  • ширина линии изменяется согласно полю v.

Как работает изменение толщины

Vega-Lite интерполирует толщину между соседними точками.

Например:

Точка size
A 2
B 10

Сегмент между A и B будет плавно расширяться.

Это создаёт эффект:

  • потока;
  • органической траектории;
  • динамической интенсивности.

Настройка диапазона толщины

По умолчанию Vega-Lite автоматически вычисляет диапазон размеров. Для контроля используется scale.

"size": {
  "field": "v",
  "type": "quantitative",
  "scale": {
    "range": [1, 20]
  }
}

Теперь:

  • минимальная толщина — 1;
  • максимальная — 20.

Цвет и толщина одновременно

trail поддерживает одновременное кодирование нескольких измерений.

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "data": {
    "values": [
      {"t": 0, "temp": 10, "speed": 2},
      {"t": 1, "temp": 15, "speed": 4},
      {"t": 2, "temp": 22, "speed": 8},
      {"t": 3, "temp": 18, "speed": 5},
      {"t": 4, "temp": 25, "speed": 10}
    ]
  },

  "mark": "trail",

  "encoding": {
    "x": {"field": "t", "type": "quantitative"},

    "y": {
      "field": "temp",
      "type": "quantitative"
    },

    "size": {
      "field": "speed",
      "type": "quantitative"
    },

    "color": {
      "field": "temp",
      "type": "quantitative",
      "scale": {
        "scheme": "viridis"
      }
    }
  }
});

Здесь:

  • толщина показывает скорость;
  • цвет показывает температуру.

Управление интерполяцией линии

Поддерживаются различные типы сглаживания.

linear

"mark": {
  "type": "trail",
  "interpolate": "linear"
}

Стандартная ломаная линия.


monotone

"mark": {
  "type": "trail",
  "interpolate": "monotone"
}

Плавная кривая без резких перегибов.


basis

"mark": {
  "type": "trail",
  "interpolate": "basis"
}

Максимально сглаженная кривая.


cardinal

"mark": {
  "type": "trail",
  "interpolate": "cardinal"
}

Сглаживание с контролем формы изгиба.


Полный пример со сглаживанием

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "width": 600,
  "height": 300,

  "data": {
    "values": [
      {"x": 0, "y": 10, "s": 1},
      {"x": 1, "y": 25, "s": 5},
      {"x": 2, "y": 12, "s": 10},
      {"x": 3, "y": 30, "s": 3},
      {"x": 4, "y": 20, "s": 8}
    ]
  },

  "mark": {
    "type": "trail",
    "interpolate": "monotone"
  },

  "encoding": {
    "x": {"field": "x", "type": "quantitative"},

    "y": {"field": "y", "type": "quantitative"},

    "size": {
      "field": "s",
      "type": "quantitative",
      "scale": {
        "range": [2, 25]
      }
    },

    "color": {
      "value": "#4682b4"
    }
  }
});

Прозрачность trail

Толстые линии могут перекрывать данные. Для этого используется opacity.

"mark": {
  "type": "trail",
  "opacity": 0.6
}

Полезно при:

  • наложении нескольких графиков;
  • визуализации потоков;
  • отображении плотных временных рядов.

Группировка нескольких trail

Для нескольких независимых траекторий используется поле категории.

"detail": {
  "field": "group"
}

Пример:

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "data": {
    "values": [
      {"g":"A","x":0,"y":5,"s":2},
      {"g":"A","x":1,"y":8,"s":5},
      {"g":"A","x":2,"y":6,"s":3},

      {"g":"B","x":0,"y":10,"s":4},
      {"g":"B","x":1,"y":12,"s":8},
      {"g":"B","x":2,"y":9,"s":2}
    ]
  },

  "mark": "trail",

  "encoding": {
    "x": {"field":"x","type":"quantitative"},
    "y": {"field":"y","type":"quantitative"},

    "size": {
      "field":"s",
      "type":"quantitative"
    },

    "color": {
      "field":"g",
      "type":"nominal"
    },

    "detail": {
      "field":"g"
    }
  }
});

Сортировка точек

Trail чувствителен к порядку точек.

Если данные идут не по порядку, линия будет строиться хаотично.

Используется:

"order": {
  "field": "time"
}

Пример:

"encoding": {
  "order": {
    "field": "timestamp"
  }
}

Работа с временными рядами

Trail особенно эффективен для временных данных.

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "data": {
    "values": [
      {"date":"2025-01-01","value":20,"load":1},
      {"date":"2025-01-02","value":25,"load":3},
      {"date":"2025-01-03","value":15,"load":8},
      {"date":"2025-01-04","value":30,"load":5}
    ]
  },

  "mark": "trail",

  "encoding": {
    "x": {
      "field":"date",
      "type":"temporal"
    },

    "y": {
      "field":"value",
      "type":"quantitative"
    },

    "size": {
      "field":"load",
      "type":"quantitative"
    }
  }
});

Trail в чистом Vega

В Vega trail описывается более низкоуровнево.

{
  "type": "trail",
  "from": {"data": "table"},

  "encode": {
    "enter": {
      "x": {"scale": "x", "field": "x"},
      "y": {"scale": "y", "field": "y"},
      "stroke": {"value": "steelblue"}
    },

    "update": {
      "strokeWidth": {
        "field": "size"
      }
    }
  }
}

В Vega:

  • больше контроля;
  • сложнее описание;
  • больше возможностей кастомизации.

Отличия Vega и Vega-Lite для trail

Возможность Vega-Lite Vega
Простота высокая средняя
Автоматические scale есть нет
Низкоуровневый контроль ограничен полный
Быстрая разработка отличная хуже
Кастомные вычисления ограничены мощные

Географические маршруты

Trail часто используется для отображения маршрутов.

"longitude": {"field": "lon"},
"latitude": {"field": "lat"},
"size": {"field": "traffic"}

Толщина может показывать:

  • интенсивность движения;
  • объём трафика;
  • скорость;
  • плотность потока.

Анимационные сценарии

Vega напрямую не содержит полноценной системы анимаций уровня D3, однако trail хорошо сочетается с:

  • signal;
  • параметрами;
  • интерактивными фильтрами;
  • изменением dataset.

Можно динамически изменять:

  • толщину;
  • цвет;
  • прозрачность;
  • интерполяцию.

Использование transform

Trail часто комбинируется с преобразованиями.

calculate

"transform": [
  {
    "calculate": "datum.value * 2",
    "as": "width"
  }
]

filter

{
  "filter": "datum.value > 10"
}

window

{
  "window": [
    {
      "op": "mean",
      "field": "value",
      "as": "avg"
    }
  ]
}

Интерактивный trail

Пример с hover.

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "data": {
    "values": [
      {"x":0,"y":10,"s":2},
      {"x":1,"y":20,"s":8},
      {"x":2,"y":15,"s":5}
    ]
  },

  "params": [
    {
      "name": "hover",
      "select": {
        "type": "point",
        "nearest": true,
        "on": "mouseover"
      }
    }
  ],

  "mark": "trail",

  "encoding": {
    "x": {"field":"x","type":"quantitative"},
    "y": {"field":"y","type":"quantitative"},

    "size": {"field":"s"},

    "opacity": {
      "condition": {
        "param": "hover",
        "value": 1
      },
      "value": 0.3
    }
  }
});

Производительность

Trail более ресурсоёмок, чем обычный line.

Причины:

  • вычисление переменной толщины;
  • сложная геометрия сегментов;
  • интерполяция;
  • сглаживание.

Особенно заметно при:

  • десятках тысяч точек;
  • сложных кривых;
  • большом количестве групп.

Оптимизация

Уменьшение количества точек

"transform": [
  {
    "sample": 1000
  }
]

Ограничение диапазона толщины

Слишком толстые линии:

  • ухудшают рендеринг;
  • увеличивают перекрытия;
  • снижают читаемость.

Практически безопасный диапазон:

"range": [1, 20]

Отключение сложной интерполяции

Наиболее быстрый вариант:

"interpolate": "linear"

Частые ошибки

Отсутствие size

"mark": "trail"

Без size trail визуально почти не отличается от обычной линии.


Неправильный порядок точек

"order": {
  "field": "date"
}

Без сортировки возникают самопересечения.


Слишком большие значения толщины

"range": [1, 100]

График становится нечитаемым.


Использование категориального size

"size": {
  "field": "category",
  "type": "nominal"
}

Trail лучше работает с количественными величинами.


Сравнение trail и area

Свойство trail area
Переменная толщина да нет
Заполнение области нет да
Подходит для потоков отлично средне
Подходит для диапазонов плохо отлично

Сравнение trail и line

Свойство line trail
Простота высокая средняя
Производительность выше ниже
Визуальная выразительность ниже выше
Кодирование дополнительной метрики нет да

Рекомендации по визуальному дизайну

Умеренная толщина

Оптимальный диапазон:

"range": [2, 15]

Контрастный цвет

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

Подходящие схемы:

  • viridis;
  • magma;
  • plasma;
  • blues.

Полупрозрачность

"opacity": 0.7

Снижает визуальную перегруженность.


Сглаживание только при необходимости

Иногда linear выглядит лучше, чем чрезмерно сглаженные кривые.


Практический пример: нагрузка сервера

vegaEmbed('#view', {
  "$schema": "https://vega.github.io/schema/vega-lite/v5.json",

  "width": 700,
  "height": 300,

  "data": {
    "values": [
      {"time":0,"cpu":20,"requests":100},
      {"time":1,"cpu":40,"requests":250},
      {"time":2,"cpu":35,"requests":500},
      {"time":3,"cpu":60,"requests":700},
      {"time":4,"cpu":30,"requests":200}
    ]
  },

  "mark": {
    "type": "trail",
    "interpolate": "monotone",
    "opacity": 0.8
  },

  "encoding": {
    "x": {
      "field":"time",
      "type":"quantitative"
    },

    "y": {
      "field":"cpu",
      "type":"quantitative",
      "title":"CPU %"
    },

    "size": {
      "field":"requests",
      "type":"quantitative",
      "scale": {
        "range":[2,25]
      }
    },

    "color": {
      "field":"cpu",
      "type":"quantitative",
      "scale":{
        "scheme":"plasma"
      }
    }
  }
});

Здесь одновременно отображаются:

  • загрузка CPU;
  • интенсивность запросов;
  • изменение состояния системы во времени.

Trail позволяет кодировать несколько измерений в одном визуальном объекте без необходимости строить дополнительные графики.