Начнем сразу с итогового результата, который выглядит вот так:
Для удобства лучше открыть его в полном окне.
Для удобства лучше открыть его в полном окне.
Как читать эту диаграмму (если не очень понятно):
- по оси X - время (в данном случае - с 4.30 утра до 12.00). по оси Y - идут станции Ленинградского направления (от Москвы Ленинградской до Твери). Расстояние между названиями по оси соответствует реальному расстоянию между станциями в километрах. Поэтому некоторые станции почти "налезают" друг на друга ‑ расстояние между ними совсем небольшое (по крайней мере, по тем данным, которые у меня есть).
- каждая линия представляет собой поезд, каждая точка на линии - станцию.
- по умолчанию выбраны оба направления, но можно выбрать отдельно - "в Москву"/"в область".
- при наведении мышкой на станцию появляется маленькое окошко, которое показывает название станции и время прибытия.
- чем круче линия, тем быстрее идет данный поезд.
- Цветом обозначены типы электричек: черный - ежедневно, рыжий - по выходным, фиолетовый - выходные /кроме субботы/кроме воскресенья, красный - электричка отменилась.
- Информация по расписанию взята с tutu.ru и соответствует 13 июня 2013 года, со всеми изменениями в расписании.
Немного об истории вопроса
Такое представление расписания поездов первым представил в своей книге 1885 года (128 лет назад!) La méthode graphique французский ученый Этьен-Жюль Маре. Таким образом в книге было изображено расписание поездов между Парижем и Лионом.
Тем, кто занимается графической визуализацией, это представление во многом известно благодаря культовой книге Эдварда Тафте (Edward Tafte) - "The Visual Display of Quantiative Information". Именно диаграмма Маре вынесена на обложку книги, и Тафте по ходу несколько раз возвращается к этой диаграмме. Такое представление соответствует "принципам графического совершенства" (principles of graphical excellence) от Тафте - хорошая визуальная презентация данных является сочетанием сути вопроса, статистики и дизайна. Сложные идеи представлены ясно, точно и эффективно. Читатель получает максимальное количество идей в минимальный период времени и с минимальным количеством "чернила" на единицу пространства.
Разумеется, с помощью современных технологий подобной репрезентации можно придать интерактивность и дополнительное удобство представления.
К примеру, вот пример расписания пригородных поездов Сан-Франциско в стиле Маре, созданный дизайнером Nicholas Rougeux. Недавно я увидел аналогичный пример, созданный в d3 автором этой удивительной библиотеки (Mike Bostock) и решил сделать аналогичную визуализацию для наших данных в качестве урока по изучению d3, создания визуализации в стиле Тафте, а также потому, что мне кажется, что используемые представления расписания электричек как на самих станциях, так и в Интернете, можно значительно улучшить.
Исходные данные.
Разумеется, сначала необходимо получить исходные данные - расписание движения пригородных поездов в удобном для машинной обработки виде. Как ни удивительно, но официальный перевозчик - ОАО «Центральная пригородная пассажирская компания» - не предоставляет общественности подобной информацииии. Раздел "Он-лайн табло" на официальном сайте находится в "стадии наполнения".
Почему-то компания, имеющая почти $1 млрд ежегодной выручки и более $100 млн чистой прибыли, не нашла возможности сообщать пассажирам о расписании движения. Правительство г. Москвы также не имеет подобной информации. Широко разрекламированный "Портал открытых данных" содержит информацию о стоимости проезда, но не расписании движения пригородных поездов.
Разумеется, сначала необходимо получить исходные данные - расписание движения пригородных поездов в удобном для машинной обработки виде. Как ни удивительно, но официальный перевозчик - ОАО «Центральная пригородная пассажирская компания» - не предоставляет общественности подобной информацииии. Раздел "Он-лайн табло" на официальном сайте находится в "стадии наполнения".
Почему-то компания, имеющая почти $1 млрд ежегодной выручки и более $100 млн чистой прибыли, не нашла возможности сообщать пассажирам о расписании движения. Правительство г. Москвы также не имеет подобной информации. Широко разрекламированный "Портал открытых данных" содержит информацию о стоимости проезда, но не расписании движения пригородных поездов.
Разумеется, все кто ездил на электричках, знают альтернативные источники информации :) Это конечно, Яндекс и tutu.ru. Обращает кстати внимание, что оба сервиса используют довольно скудное текстовое представление расписания.
Я написал небольшой скрипт в R, который собирает данные с tutu.tu, хотя концептуально это не очень правильно - перевозчик, либо местные власти должны предоставлять подобную информацию в удобном виде.
В нашем случае для Ленинградского направления, но разумеется, можно собрать информацию по и другим направлениям/вокзалам. Причем информацию по расстояниями между станциями пришлось "парсить" тоже с tutu.ru, так как я не смог найти какой-либо официальной информации по этому поводу.
В нашем случае для Ленинградского направления, но разумеется, можно собрать информацию по и другим направлениям/вокзалам. Причем информацию по расстояниями между станциями пришлось "парсить" тоже с tutu.ru, так как я не смог найти какой-либо официальной информации по этому поводу.
Итоговый файл представляет собой матрицу, в котором по столбцам идут остановки, а по строкам - отдельные поезда. В названии столбцам закодированы расстояния между станциями и зоны оплаты. Выглядит это вот так:
Соответственно мы видим, что на Ленинградском направлении в день проходит 165 поездов (по обоим направлениям), которые останавливаются на 45 различных станциях. Матрица состоит из почти трех тысяч значений, поэтому необходимо специальные средства представления этой информации в графическом виде.
Построение диаграммы в стиле Маре.
Как уже говорилось, я использовал графическую библиотеку Data-Driven Documents (или d3), которая представляет собой нечто среднее между библиотеками готовых графиков и самостоятельным рисованием диаграмм в графическом редакторе вроде Inkscape.
Это мой второй опыт самостоятельного рисования в d3 (первый - карты хороплет для РФ). Поэтому я основывался на готовом примере Майка, но несколько видоизменил его для своих данных и дополнил дополнительными интерактивными элементами.
Вариант с этими "плюшечками" занимает около 300 строк кода, но вполне возможно, потому что я использовал не самые оптимальные конструкции.
Основная "рабочая" функция преобразует матрицу поезда-станции и преобразует ее в JS-объект. На основе этого объекта собственно и строится вся графическая составляющая.
Соответственно объект tooltip то появляется и наполняется информационным содержанием (название станции и время прибытия поезда на эту станцию), то исчезает.
Соответственно все объекта класса "oblast" или "moscow" показываются, либо скрываются от зрителя. Переменная cur_direction нужна для того, чтобы правильно реагировать на выбор для недели, когда направление уже выбрано.
Как уже говорилось, я использовал графическую библиотеку Data-Driven Documents (или d3), которая представляет собой нечто среднее между библиотеками готовых графиков и самостоятельным рисованием диаграмм в графическом редакторе вроде Inkscape.
Это мой второй опыт самостоятельного рисования в d3 (первый - карты хороплет для РФ). Поэтому я основывался на готовом примере Майка, но несколько видоизменил его для своих данных и дополнил дополнительными интерактивными элементами.
Вариант с этими "плюшечками" занимает около 300 строк кода, но вполне возможно, потому что я использовал не самые оптимальные конструкции.
Основная "рабочая" функция преобразует матрицу поезда-станции и преобразует ее в JS-объект. На основе этого объекта собственно и строится вся графическая составляющая.
function type(d, i) { // Extract the stations from the "stop|*" columns. if (!i) for (var k in d) { if (/^stop\|/.test(k)) { var p = k.split("|"); stations.push({ key: k, name: p[1], distance: +p[2], zone: +p[3] }); } } return { number: d.number, type: d.type, direction: d.direction, stops: stations .map(function(s) { return {station: s, time: parseTime(d[s.key])}; }) .filter(function(s) { return s.time != null; }) };
Информационное окно
В дополнении примеру Майка я добавил еще всплывающее окошко, которое появляется при наведении мышкой на любую круг - станцию. Делается это с помощью простого event-listener, реагирующего на mouseover.train.selectAll("circle") .data(function(d) { return d.stops; }) .enter().append("circle") .attr("transform", function(d) { return "translate(" + x(d.time) + "," + y(d.station.distance) + ")"; }) .attr("r", 3) .on("mouseover", function(d) { var xPosition = x(d.time)+margin.left + margin.right; var yPosition = y(d.station.distance); d3.select("#tooltip") .style("left", xPosition + "px") .style("top", yPosition + "px") .select("#stations") .text(d.station.name); d3.select("#tooltip") .select("#time") .text(formatTime(d.time)); d3.select("#tooltip").classed("hidden", false); }) .on("mouseout", function() { d3.select("#tooltip").classed("hidden", true); });
Соответственно объект tooltip то появляется и наполняется информационным содержанием (название станции и время прибытия поезда на эту станцию), то исчезает.
Выбор направления
В исходном примере Майка отображается только одно направление, я же хотел, чтобы была возможность посмотреть оба направления одновременно, а также по отдельности. Это делается с помощью отдельной формы и функции, которая реагирует на изменение этой формы.d3.selectAll("input[name=f_direction]") .on("change", function(){ cur_direction = this.id; if (this.id == 'moscow'){ d3.selectAll(".moscow").classed("hidden", false); d3.selectAll(".oblast").classed("hidden", true); } if (this.id == 'oblast'){ d3.selectAll(".moscow").classed("hidden", true); d3.selectAll(".oblast").classed("hidden", false); } })
Соответственно все объекта класса "oblast" или "moscow" показываются, либо скрываются от зрителя. Переменная cur_direction нужна для того, чтобы правильно реагировать на выбор для недели, когда направление уже выбрано.
Выбор дня недели
Исходные данные имеют для каждого поезда его тип: ежедневно, по выходным, по рабочим, кроме суббот, кроме воскресений, отменен. Но пользователя на самом деле интересует какие поезда идут в конкретный день, поэтому я решил целесообразнее предоставить выбор для недели и в зависимости от этого рисовать нужные поезда. Соответственно, поезд, который ходит в режим "кроме воскресений" не будет отображаться в при выборе воскресенья. При выборе субботы или воскресенья также не будут показываться электрички, которые ходят по рабочим дням.
Реализация этой логики сделана топорным if :) Отмененные электрички (красным цветом) должны отображаться при любом раскладе
d3.selectAll("input[name=type_train]") .on("change", function(){ if (this.id == 'working'){ d3.selectAll(".daily"+"."+cur_direction).classed("hidden", false); d3.selectAll(".working"+"."+cur_direction).classed("hidden", false); d3.selectAll(".weekend"+"."+cur_direction).classed("hidden", true); d3.selectAll(".ex_saturday"+"."+cur_direction).classed("hidden", true); d3.selectAll(".ex_sunday"+"."+cur_direction).classed("hidden", true); } if (this.id == 'saturday'){ d3.selectAll(".daily"+"."+cur_direction).classed("hidden", false); d3.selectAll(".working"+"."+cur_direction).classed("hidden", true); d3.selectAll(".weekend"+"."+cur_direction).classed("hidden", false); d3.selectAll(".ex_saturday"+"."+cur_direction).classed("hidden", true); d3.selectAll(".ex_sunday"+"."+cur_direction).classed("hidden", false); } if (this.id == 'sunday'){ d3.selectAll(".daily"+"."+cur_direction).classed("hidden", false); d3.selectAll(".working"+"."+cur_direction).classed("hidden", true); d3.selectAll(".weekend"+"."+cur_direction).classed("hidden", false); d3.selectAll(".ex_saturday"+"."+cur_direction).classed("hidden", false); d3.selectAll(".ex_sunday"+"."+cur_direction).classed("hidden", true); } })
Вот вроде бы и все. Буду рад услышать любые комментарии по оптимальном представления расписания и на сколько это вообще удобно. Как я понимаю, люди, которые ездят на электричках помнят расписание в пределах нужного часа-двух наизусть и в принципе не слишком нуждаются в графической визуализации. Я, к примеру, езжу на электричках изредка, и оно мне кажется удобным.
Комментариев нет:
Отправить комментарий