Нажмите здесь, чтобы опубликовать эту статью в LinkedIn »

Много лет назад я прочитал очень вдохновляющую статью, написанную Полом Грэмом: Корни Лиспа. Имея лишь ограниченное количество операторов и одну единую структуру данных (список), вы можете создать язык программирования. В тот момент я был шокирован философией простоты: с одной стороны, lisp маленький, каждый оператор очень простой; с другой стороны, такие вещи, как МАКРОСЫ, функции высокого порядка, рекурсивные, могут помочь вам создать программист любого комплекса. И самое прекрасное, что сложное - это построить только из самых простых компонентов.

Недавно я работал над чем-то, связанным с визуализацией данных, и обнаружил, что та же философия может применяться и здесь. Привлекательная, красивая визуализация данных может состоять из очень небольшого числа элементов, таких как форма, длина, цвет, текстура и углы.

Фактически, автор книги Точки данных: визуализация, которая что-то значит, Натан Яу перечислил наиболее часто используемые элементы в своей книге на следующем изображении:

Интересный факт заключается в том, что, составив эти элементы вместе, вы можете достичь этого 1+1>2.

Психология и когнитивные системы

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

Психология

В жизни мы столкнемся с таким сценарием: если первоначальная цена продукта составляла 10 долларов, а цена со скидкой - 5 долларов, покупатель с большой вероятностью его купит; но если цена продукта составляет 100 долларов, а цена со скидкой - 95 долларов, тогда это уже не так привлекательно. Таким образом, абсолютное количество этих двух скидок одинаково (обе равны 5 долларам), но эффект не тот.

Это описывается как закон Вебера – Фехнера:

Закон Вебера – Фехнера относится к двум взаимосвязанным законам в области психофизики, известным как закон Вебера и закон Фехнера. Оба закона относятся к человеческому восприятию, а точнее к взаимосвязи между фактическим изменением физического стимула и воспринимаемым изменением. Сюда входят стимулы для всех органов чувств: зрения, слуха, вкуса, осязания и обоняния.

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

  • Избегайте использования площади для сравнения
  • подумайте об использовании нелинейных визуальных элементов, когда разница достаточно велика
  • сделайте разницу в цвете достаточно большой при использовании нескольких цветов в качестве визуального кодирования

Например:

На изображении выше, когда площадь увеличивается, вы можете очень трудно определить реальную разницу, просто взглянув на квадрат. Если вы внимательно посмотрите на базовые данные, вы обнаружите, что абсолютная разница составляет всего 5:

var data = [
  {width: 5, height: 5},
  {width: 10, height: 10},
  {width: 50, height: 50},
  {width: 55, height: 55},
  {width: 100, height: 100},
  {width: 105, height: 105}
];

Гештальт-психология

Гештальт-психология или гештальтизм (нем. Gestalt [ɡəˈʃtalt] «форма, форма») - это философия разума Берлинской школы экспериментальной психологии. Гештальт-психология - это попытка понять законы, лежащие в основе способности приобретать и поддерживать значимые восприятия в очевидно хаотическом мире. Центральный принцип гештальт-психологии состоит в том, что разум образует глобальное целое со склонностями к самоорганизации.

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

Самая известная далматинская собака:

Вы можете легко (ну, может быть, не так просто) идентифицировать собаку, обнюхивающую землю в тени нависающих деревьев, но собаку не узнать, сначала определив ее части.

На самом деле, принципы гештальта содержат множество субпринципов:

  • Закон близости
  • Закон подобия
  • Закон закрытия
  • Закон симметрии
  • Закон общей судьбы

Я не буду вдаваться в подробности, поскольку в Интернете есть масса примеров. Просто имейте в виду, что когда вы разрабатываете какой-нибудь продукт для визуализации, подумайте об использовании этих законов, чтобы помочь вам создать что-то, что будет проще для человека.

Основные принципы визуального дизайна

В своей книге The Non-Designer’s Design Book Робин Уильямс обсудил 4 основных принципа визуального дизайна:

  • Контрастность (использование разных цветов, типа шрифта, размера шрифта для построения иерархии информации)
  • Повторение (повторяющиеся образцы, такие как цвет, используемый для заголовка)
  • Выравнивание (выравнивание вещей по вертикали или горизонтали)
  • Близость (поставьте родственное поближе)

Взгляните на этот простой пример:

Некоторые простые способы использования

  • Исчезновение фоновой сетки линейной диаграммы (контраст)
  • Выделите выбросы (другой цвет для контраста)
  • Цвет, используемый в диаграмме и легенде (повторение)
  • Ярлыки рядом с осями (Близость)

Хорошо, хватит теории, давайте перейдем к очень конкретному примеру и посмотрим, как мы можем применить к нему эти принципы.

карта звездной ночи

Я собрал некоторые данные о повседневной активности моей дочери (в основном, она просто делает 3 типа вещей: ест, спит и немного играет).

Самый первый шаг визуализации данных - what do you want to get from the data, в данном случае я просто хочу знать, сколько часов она спит каждый день и как выглядит распределение.

Расширенная гистограмма

Как только я получу представление о том, что нужно визуализировать, следующим шагом будет выбор правильной визуальной кодировки. Таким образом, для наших человеческих глаз наиболее точной кодировкой является length. Мне нужно преобразовать sleeping hours в length, самым простым способом была бы гистограмма.

Ожидаемые данные (в формате CSV) выглядят следующим образом:

2016/11/21,768
2016/11/22,760
2016/11/23,700

Первое поле - это дата, второе - минуты сна. Я нарисовал набросок на бумаге, как показано на рисунке:

По оси Y отображается время каждый день в 24 часа, а по оси X - текущая дата. Сплошной прямоугольник означает, что она спала, а пунктирный прямоугольник означает, что она не спала.

Исходные данные

name,date,length,note
心心,2016/11/21 19:23,119,
心心,2016/11/21 22:04,211,
心心,2016/11/22 02:03,19,
心心,2016/11/22 02:23,118,
心心,2016/11/22 05:58,242,
心心,2016/11/22 10:57,128,
心心,2016/11/22 14:35,127,
心心,2016/11/22 17:15,127,
心心,2016/11/22 20:02,177,
心心,2016/11/23 01:27,197,

Здесь есть небольшая проблема: наша ось Y имеет фиксированную длину, если она спала с 23:00 и просыпалась через 3 часа, тогда полоса будет больше 24 по оси Y.

Поэтому я пишу простой скрипт для преобразования данных:

require 'csv'
require 'active_support/all'
require 'json'
csv = CSV.read('./visualization/data/sleeping_data_refined.csv', :headers => :first_row)
data = []
csv.each do |row|
    date = DateTime.parse(row['date'], "%Y/%m/%d %H:%M")
    
    mins_until_end_of_day = date.seconds_until_end_of_day / 60
    diff = mins_until_end_of_day - row['length'].to_i
    
    if (diff >= 0) then
        data << {
            :name => row['name'],
            :date => row['date'],
            :length => row['length'],
            :note => row['note']
        }
    else 
        data << {
            :name => row['name'],
            :date => date.strftime("%Y/%m/%d %H:%M"),
            :length => mins_until_end_of_day,
            :note => row['note']
        }
        data << {
            :name => row['name'],
            :date => (date.beginning_of_day + 1.day).strftime("%Y/%m/%d %H:%M"),
            :length => diff.abs,
            :note => row['note']
        }
    end
end

Когда у нас есть cleaned данные, мы можем попытаться нарисовать гистограмму. Вот некоторые моменты, которые необходимо учитывать:

  • Время должно быть выровнено с нулем.
  • Чем дольше она спала, тем глубже должна быть планка.
var dateRange = _.uniq(data, function(d) {
    var date = d.date;
    return [date.getYear(), date.getMonth(), date.getDate()].join("/");
});
xScale.domain(dateRange.map(function(d) { return d.date; }));
function getFirstInDomain(date) {
    var domain = xScale.domain();
    var index = _.findIndex(domain, function(d) {
        return date.getYear() === d.getYear() 
            && date.getMonth() === d.getMonth() 
            && date.getDate() === d.getDate();
    });
    return domain[index];
}

Funciton getFirstInDomain возвращает X по дате, поэтому date 2016/11/21 19:23 и 2016/11/21 22:04 вернут ингетер для d3.scale.

Мы можем оценить качество сна по 5 уровням по количеству минут сна, использовать d3 довольно просто:

var level = d3.scale.threshold()
  .domain([60, 120, 180, 240, 300])
  .range(["low", "fine", "medium", "good", "great", "prefect"]);

И мы можем добавить к прямоугольнику разные CSS Class:

svg.selectAll(".bar")
    .data(data)
    .enter()
    .append("rect")
    .attr("class", function(d) {
        return level(d.length)+" bar";
    })
//...

Хорошо, окончательный результат выглядит так. Мы можем сказать, что она спит в 0–6 утра, затем в 10–13 часов дня и, наконец, в 18–20 часов ночи.

карта звездной ночи

Но есть проблема, когда данные увеличиваются. Мы можем попробовать другой способ, например, радиальную ось. Поскольку мы визуализируем что-то, связанное с time, цикл реализации watch был бы просто отличным.

преобразовать угол в радиан

Здесь нам понадобится небольшая математика, сначала мы разделим круг (360 градусов) на минуты, затем количество углов в минуту будет: 360/(24*60), а затем угол преобразуется в радианы по формуле: degree * π/180:

var perAngle = (360 / (24 * 60)) * (Math.PI/180);

Для любого конкретного времени, скажем 10:20, мы сначала вычисляем минуты: 10*60+20 и умножаем на preAngle, результатом будет начальный угол дуги, а плюс несколько минут сна preAngle получим конечный угол дуги:

function startAngle(date) {
    var start = (date.getHours() * 60 + date.getMinutes()) * perAngle;
    return Math.floor(start*1000)/1000;
}
function endAngle(date, length) {
    var end = (date.getHours() * 60 + date.getMinutes() + length) * perAngle;
    return Math.floor(end*1000)/1000;
}

Результат выглядит так:

Кроме того, мы можем использовать линейную шкалу и градиент svg, чтобы нарисовать легенду для графика (большое спасибо отличному сообщению о градиенте svg от Надие Бремер.):

var colorScale = d3.scale.linear()
  .range(["#2c7bb6", "#00a6ca","#00ccbc","#90eb9d","#ffff8c","#f9d057"].reverse());
var defs = vis.append("defs");
var linearGradient = defs.append("linearGradient")
    .attr("id", "linear-gradient")
    .attr("x1", "0%")
    .attr("y1", "0%")
    .attr("x2", "100%")
    .attr("y2", "0%");
linearGradient.selectAll("stop") 
  .data( colorScale.range() )                  
  .enter().append("stop")
  .attr("offset", function(d,i) { return i/(colorScale.range().length-1); })
  .attr("stop-color", function(d) { return d; });

Когда у нас есть градиент и диапазон, мы можем использовать их для рисования легенды.

var legendWidth = 300;
var legendsvg = vis.append("g")
  .attr("class", "legendWrapper")
  .attr("transform", "translate(" + (width/2+legendWidth) + "," + (height - 40) + ")");
//Draw the Rectangle
legendsvg.append("rect")
  .attr("class", "legendRect")
  .attr("x", -legendWidth/2)
  .attr("y", 0)
  .attr("width", legendWidth)
  .attr("height", 3.5)
  .style("fill", "url(#linear-gradient)");
  
//Append title
legendsvg.append("text")
  .attr("class", "legendTitle")
  .attr("x", 0)
  .attr("y", -10)
  .style("text-anchor", "middle")
  .text("Sleeping Minutes");

Я также добавил всплывающую подсказку для каждой дуги при наведении на нее мыши.

Финальная рабочая демонстрация размещена здесь: Спящий статус Xinxin, вы можете найти весь код здесь.

После добавления фоновых изображений финальная версия будет выглядеть так:

Резюме

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