Кольцевые столбчатые диаграммы в ассортименте
- вторник, 20 февраля 2018 г. в 03:16:40
Эволюционный подход в решении задач как нельзя кстати подходит для визуализации данных. Дивжение от простого к сложному, от одномерных данных к многомерным итерация за итерацией. В этой статье рассмотрим различные варианты круговых диаграмм, от самой простой одномерной до нестандартной самодельной многомерной. В качестве инструмента будем использовать D3.js. Всех заинтересованных прошу под кат.
Примечание: рядом с русскоязычными терминами в скобочках будет даваться англоязычный вариант.
Кольцевая, круговоая, или радиальная столбчатая диаграмма (radial column/bar chart) является вариацией на тему классической столбчатой диаграммы (bar chart).
Окружность в качестве оси абсцисс (X) и концентрические окружности в роли координатной решетки, вот собственно отличительные особенности это вида графика от обычной столбчатой диаграммы. Выглядит это всё следующим образом.
Тут есть один нюанс, а именно какую шкалу (scale) использовать? Дело в том, что чем дальше от центра, тем больше будет длина внешней дуги (C = 2πR) и площадь нашего сектора (S ~ πR2). Поэтому нам нужно решить что для нас важнее: компенсировать различный визуальный вес наших столбцов или сохранить регулярность сетки.
Для первого варианта можно использовать масштаб, предложенный Майком Бостоком:
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("d3-scale")) :
typeof define === "function" && define.amd ? define(["exports", "d3-scale"], factory) :
(factory(global.d3 = global.d3 || {}, global.d3));
}(this, function(exports, d3Scale) {
'use strict';
function square(x) {
return x * x;
}
function radial() {
var linear = d3Scale.scaleLinear();
function scale(x) {
return Math.sqrt(linear(x));
}
scale.domain = function(_) {
return arguments.length ? (linear.domain(_), scale) : linear.domain();
};
scale.nice = function(count) {
return (linear.nice(count), scale);
};
scale.range = function(_) {
return arguments.length ? (linear.range(_.map(square)), scale) : linear.range().map(Math.sqrt);
};
scale.ticks = linear.ticks;
scale.tickFormat = linear.tickFormat;
return scale;
}
exports.scaleRadial = radial;
Object.defineProperty(exports, '__esModule', {value: true});
}));
Для регулярной сетки используйте стандартный линейный масштаб (d3.scaleLinear()
). Я в своих примерах использовал именно его.
Некоторое время назад мне довелось пообщаться с представителями одного из подразделений Департамента транспорта Москвы, они то мне и предложили подумать над задачей визуализации данных по ДТП на МКАД. Предполагалось, что есть данные о месте и времени происшествия и скоростном режиме на участке дороги. Нужно было проверить есть ли связь между скоростным режимом и количеством аварий. Найти пространственные и временные паттерны (если они существуют). Первая задача решается с помощью диаграммы рассеяния (scatterplot) и построения регрессии. Вторая куда более креативная, ей то мы здесь и будем заниматься.
Да, никаких данных мне так и не предоставили, так что дальше пары эскизов и обсуждений дело не пошло.
Здесь и далее будем отображать «гипотетическое» (рандомное) количество аварий на МКАД за отчётный период. Ось абсцисс разбита на километровые отсечки и соответствующим образом повёрнута. Основная цель визуализации найти потенциальные пространственные и временные аномалии или паттерны. Да, нужно сохранить «топологичность» дороги, то есть закольцованность. Это красиво, и в данном случае вполне логично.
Поскольку движение на МКАД у нас двустороннее, то и статистику нам нужно отображать сразу по двум направлениям. Сделать это можно разными способами, рассмотрим их подробнее. Все графики кликабельны и ведут к исходникам на bl.ocks.org. Каждый вариант диграммы сопровождается комментарием о её читабельности*. Некоторые недостатки графиков можно нивелировать с помощью интерактива: различные ховер эффекты, фокусировки и др… Но мы здесь будем рассматривать возможности графиков только как статичных картинок.
* читабельность графика — совокупность свойств графика, определяющих скорость и полноту восприятия информации, отображённой на графике.
Пожалуй первое, что приходит в голову, это сгруппировать показатели.
Читабельность:
Оценить картину в целом довольно сложно, из-за близости столбцов их легко сравнивать, но сложно оценить два тренда разом.
Попробуем разнести наши дороги и данные в пространстве.
Читабельность:
Здесь просто два графика. Тренды легко отслеживаются. Попарное сравнение тоже работает, но хуже. Так бывает, одно лечим, другое калечим.
Снова попробуем объединить показтели, теперь в виде накопительной диаграммы.
Вариант накопительной диаграммы вполне возможен. Но накопительные диаграммы не зря так называются, они хороши когда нам важно одновременно видеть показатель целиком и его составные части. У нас же немного другая ситуация. Так же такой тип графика не работает на большом количестве столбцов, а у нас их много. Получаем просто картинку с красивым «забором».
Читабельность:
Попарное сравнение есть, суммарное значение есть (хоть и не нужно), а вот отследить тренд для второго показателя весьма сложно из-за разного начального уровня.
Вариант накопительный диаграммы с отрицательными значениями. У нас их нет, но нам важно, что база у столбцов здесь общая. Меняем знак у одной из колонок, затем создаём накопительную диаграмму со смещением: stack.offset([offset]). В качестве функции смещения передаём d3.stackOffsetDiverging.
Читабельность:
Попарное сравнение есть. Внешний тренд просматривается, внутренний тоже, но хуже. Картина в целом теперь есть, хотя просматривается ещё не чётко.
Название авторское, как собственно и график (возможно я не первопроходец, а просто плохо искал). Здесь я решил как бы инвертировать предыдущий вариант графика. Получилось вот что.
Данный график основан на принципах гештальта.
На принципе замыкания: наш глаз пытается замкнуть «открытые» фигуры.
На принципе фон-фигура.
С одной стороны меньшая из фигур воспринимается как главный график, с другой выпуклый график превалирует над вогнутым. Это создаёт нестабильность, но за счёт цвета мы можем ей управлять.
С точки зрения кода оснонвое отличие в собственной функции сдвига, в данном случае она выглядит так:
function stackOffsetOpposing(series, order) {
// Check if no staks (amount of series < 1)
if (!((n = series.length) > 1)) return;
// find max sum
var stackSums = [];
var stackMaxes = [];
for (var i = 0, n = series.length; i < n; i++) {
var stackMax = d3.max(series[i], function(d) { return Math.abs(d[1] - d[0])});
stackMaxes.push(stackMax);
}
var max = d3.sum(stackMaxes);
// Redifining baselines
for (var i, j = 0, d, dy, yp, yn, n, m = series[order[0]].length; j < m; ++j) {
for (yp = 0, yn = max, i = 0; i < n; ++i) {
if ((dy = (d = series[order[i]][j])[1] - d[0]) >= 0) {
//d[0] -bottom; d[1] -top
d[0] = yp, d[1] = yp += dy;
} else if (dy < 0) {
d[1] = yn;
yn += dy;
d[0] = yn;
} else {
d[0] = yp;
}
}
}
}
Читабельность:
Попарное сравнение есть. Оба тренда просматриваются. Есть картина в целом.
Да, график остаётся накопительным, так что можно добавлять подкатегории.
Преимущества:
По аналогии с годовыми кольцами деревьев можно добавлять кольца с данными за разные отчётные периоды. Главное не переусердствовать и правильно подобрать ширину колец. Выглядеть это может так.
Неплохой вариант для печати в большом формате, на экране, по-моему, смотрится довольно-таки тяжеловесно.
Мы рассмотрели различные варианты круговых диаграмм, их плюсы и минусы. Несмотря на то, что этот класс диаграмм далеко не самый лучший в плане читаемости, он хорош в плане визуальной привлекательности. Так что если привлечь внимание «смотрящего» для вас важнее, чем скорость выуживания полезной информации, то такой вариант может вполне подойти. Надеюсь, рассмотренные примеры будут вам полезны в реальной работе. Да, JS код не соответствует современным стандартам, но он должен быть понятен. В любом случае это лишь примеры и заготовки для реальных визуализаций.
Если бы были реальные данные, то можно было бы добавить интерактив, прикрутить фильтрацию и т.д… А поскольку их нет, да и статья обучающая, остановимся на этом. Но если вдруг кто-нибудь (ЦОДД, Яндекс?) предоставит сэмпл с реальными данными, то можно было бы продолжить изыскания.
А какой тип график предложили бы вы? Собственные варианты пишите в комментариях =)