Как построить простую диаграмму Ганта с помощью CSS и JavaScript

Как построить простую диаграмму Ганта с помощью CSS и JavaScript

От автора: до сих пор в данной серии учебных руководств по CSS диаграммам мы узнали, как создавать различные типы диаграмм, включая гистограммы, диаграммы термометров и круговые диаграммы. Сегодня мы продолжим этот путь, построив и представив данные в диаграмме Ганта.

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

Примечание: имейте в виду, что с помощью CSS Grid Layout совершенно возможно построить диаграмму Ганта на чистом CSS. Посмотрите, как это делается в этом руководстве!

Диаграмма Ганта, которую мы создаем

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

Что такое диаграмма Ганта?

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

«Диаграмма Ганта — это тип гистограммы, которая иллюстрирует график проекта. В этой таблице перечислены задачи, которые необходимо выполнить на вертикальной оси, и временные интервалы на горизонтальной оси. Ширина горизонтальных полос на графике показывает продолжительность каждого действия». — Википедия.

Визуализация диаграммы Ганта

Диаграммы Ганта дают менеджерам возможность управлять проектами, планировать задачи, следить за ходом работ, распределять обязанности и ресурсы.

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

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

Решения Ганта

Большинство платформ управления проектами используют диаграммы Ганта в качестве основной части своего предложения. ClickUp, Zapier и Monday.com — это названия, которые вы услышите, когда будут обсуждаться приложения для управления проектами (хотя основатели Monday.com подчеркивают, что их диаграмма «ориентирована на людей, а не на задачи или проекты»).

Шаблоны диаграмм Ганта на Envato

Keynote, PowerPoint и другие приложения для презентаций способны создавать диаграммы Ганта для демонстрационных целей. Посмотрите на некоторые шаблоны диаграмм Ганта, доступные прямо сейчас в Envato Elements.

Ключевой шаблон Ганта

Статус проекта для Keynote

Существует также ряд бесплатных и проприетарных решений, доступных для построения ваших собственных диаграмм Ганта. Вы можете создать их с помощью Microsoft Excel, Google Sheets, веб-приложения, такого как TeamGantt, библиотеки JavaScript, такой как Highcharts, или даже написав свой собственный код.

В этом руководстве мы выбираем последний подход; давайте создадим собственную простую диаграмму Ганта, используя CSS и JavaScript!

Укажите разметку страницы

Начнем с определения элемента-оболочки, который содержит два списка:

Первый список определяет диапазон диаграммы (данные по оси X). В нашем случае он будет содержать дни недели. Каждый день будет представлять собой нормальное рабочее время.

Второй список устанавливает данные диаграммы (данные оси Y). В нашем случае данные будут включать задачи, которые необходимо выполнить в течение недели. Каждый элемент списка, который описывает задачу, имеет два пользовательских атрибута: data-duration и data-color. Первый атрибут определяет длительность задачи, а второй — цвет фона. Значение атрибута data-duration должно быть в формате [startDay]-[endDay]. При этом мы будем использовать data-duration=»tue-wed» для задачи, которая должна начаться во вторник и закончится в среду. Кроме того, задача также может начинаться и заканчиваться в середине дня. В таком случае значение атрибута data-duration должно быть в формате [startDay]½-[endDay]½. Так, например, мы будем использовать data-duration=»tue-wed½» для задачи, которая должна начаться во вторник и закончиться в середине среды. Аналогично, в data-duration=»tue½-tue» будет описана задача, которая должна начаться в середине вторника и завершиться в тот же день.

Вот необходимая разметка:

<div class="chart-wrapper"> <ul class="chart-values"> <li>sun</li> <li>mon</li> <li>tue</li> <li>wed</li> <li>thu</li> <li>fri</li> <li>sat</li> </ul> <ul class="chart-bars"> <li data-duration="tue-wed" data-color="#b03532">Task</li> <li data-duration="wed-sat" data-color="#33a8a5">Task</li> ... </ul>
</div>

Стиль диаграммы

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

.chart-wrapper { max-width: 1150px; padding: 0 10px; margin: 0 auto;
}

Ось х

Список .chart-values будет flex-контейнером. Его flex-элементы (дни) будут равномерно распределены по главной оси и будут иметь минимальную ширину 80 пикселей. В результате этой минимальной ширины на маленьких экранах диаграмма не будет уменьшаться, и появится горизонтальная полоса прокрутки. Вы можете удалить это, если вам не нравится такое поведение.

Чтобы лучше визуализировать левую и правую границы каждого элемента, мы будем использовать их псевдо-элемент ::before. Мы зададим ему большую жестко заданную высоту (510 пикселей), которая обеспечит его расширение для всех задач. Вместо жесткого кодирования этого значения всегда есть возможность динамически рассчитать его с помощью JavaScript. Но давайте пока пропустим это решение, поскольку оно имеет второстепенное значение.

Соответствующие стили:

:root { --divider: lightgrey;
} .chart-wrapper .chart-values { position: relative; display: flex; margin-bottom: 20px; font-weight: bold; font-size: 1.2rem;
} .chart-wrapper .chart-values li { flex: 1; min-width: 80px; text-align: center;
} .chart-wrapper .chart-values li:not(:last-child) { position: relative;
} .chart-wrapper .chart-values li:not(:last-child)::before { content: ''; position: absolute; right: 0; height: 510px; border-right: 1px solid var(--divider);
}

Ось у

Элементы (столбцы) второго списка будут изначально скрыты. Конкретно они будут иметь width: 0 и opacity: 0. Плюс мы их зададим position: relative. Позже мы будем динамически устанавливать для них положение left в соответствии со значением их атрибута data-duration. Вот соответствующие стили:

:root { --white: #fff;
} .chart-wrapper .chart-bars li { position: relative; color: var(--white); margin-bottom: 15px; font-size: 16px; border-radius: 20px; padding: 10px 20px; width: 0; opacity: 0; transition: all 0.65s linear 0.2s;
} @media screen and (max-width: 600px) { .chart-wrapper .chart-bars li { padding: 10px; }
}

Добавление JavaScript

Когда страница загрузится или окно браузера createChart изменится, будет выполнена функция:

window.addEventListener("load", createChart);
window.addEventListener("resize", createChart);

Примечание. Как я уже упоминал в других руководствах, существуют разные способы ограничения генерируемых событий resize. Например, одним из эффективных решений является использование функции Лодаша _.debounce. Хотя это выходит за рамки данного руководства.

Внутри этой функции мы сначала делаем следующие вещи:

Берем элементы из двух списков.

Преобразуем daysNodeList в реальный массив с помощью оператора распространения. В качестве альтернативы, мы могли бы использовать метод Array.from(). Это преобразование позволит нам использовать метод filter(), который доступен в массивах, для фильтрации дней.

Перебираем задачи через цикл.

function createChart(e) { // 1 const days = document.querySelectorAll(".chart-values li"); const tasks = document.querySelectorAll(".chart-bars li"); // 2 const daysArray = [...days]; // 3 tasks.forEach(el => { ... });
}

Цикл задач

Далее для каждой задачи:

Мы берем значение ее атрибута data-duration (например, tue-wed). Кроме того, мы разделяем это значение, используя «-» в качестве разделителя.

Первая строка возвращаемого массива представляет начальный день задачи (например, tue), а вторая — день окончания (например, wed).

Зная начальный день, мы фильтруем daysArray, чтобы получить элемент списка (день), который соответствует этому дню. Во время этого теста мы игнорируем возможное присутствие символа «½». Затем мы делаем несколько вычислений, чтобы определить требуемое значение left свойства связанной задачи.

Зная день ее окончания, мы фильтруем, daysArray чтобы получить элемент списка (день), который соответствует этому дню. Во время этого теста мы игнорируем возможное присутствие символа «½». Затем мы делаем несколько вычислений, чтобы определить требуемое значение свойства width связанной задачи.

tasks.forEach(el => { // 1 const duration = el.dataset.duration.split("-"); // 2 const startDay = duration[0]; const endDay = duration[1]; let left = 0, width = 0; // 3 if (startDay.endsWith("½")) { const filteredArray = daysArray.filter(day => day.textContent == startDay.slice(0, -1)); left = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth / 2; } else { const filteredArray = daysArray.filter(day => day.textContent == startDay); left = filteredArray[0].offsetLeft; } // 4 if (endDay.endsWith("½")) { const filteredArray = daysArray.filter(day => day.textContent == endDay.slice(0, -1)); width = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth / 2 - left; } else { const filteredArray = daysArray.filter(day => day.textContent == endDay); width = filteredArray[0].offsetLeft + filteredArray[0].offsetWidth - left; } ... });

Примечание. В приведенном выше коде мы используем свойство Element.offsetLeft для получения левой позиции элемента относительно его родителя. Кроме того, мы используем свойство Element.offsetWidth, чтобы найти ширину элемента. В качестве другого варианта захвата его ширины мы могли бы в равной степени использовать более точный метод Element.getBoundingClientRect().

Установка стилей

Вычислив значения left и width каждой задачи, последним шагом является выполнение следующих действий:

Применить соответствующие стили.

Захватить значение атрибута data-color и установить его в качестве цвета фона задачи.

Отобразить задачу. Помните, что все задачи изначально скрыты.

Действия 2 и 3 должны выполняться только при загрузке страницы, поскольку их значения не будут меняться при каждом изменении размера окна браузера.

tasks.forEach(el => { ... // 1 el.style.left = `${left}px`; el.style.width = `${width}px`; // 4 if (e.type == "load") { // 2 el.style.backgroundColor = el.dataset.color; // 3 el.style.opacity = 1; } });

Заключение

Вот и все! Нам удалось построить полностью функциональную диаграмму Ганта. Спасибо за то, что следите за этим руководством, надеюсь, это послужило хорошим упражнением для обновления ваших навыков JavaScript. Вот еще один взгляд на финальную демонстрацию:

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

Автор: George Martsoukos

Источник: https://webdesign.tutsplus.com

Редакция: Команда webformyself.