Apache Superset всё чаще становится выбором для визуализации данных благодаря открытому коду. Но, увы, столкнувшись с его ограничениями и тонкостями, даже самые опытные пользователи могут столкнуться с трудностями. Есть много ограничений, которые требуют обращения за доработками к разработчикам, но с помощью шаблона Handlebars в сочетании с шаблонизацией jinja некоторые трудности можно обойти.
С его помощью можно внедрить web-верстку прямо в ваши дашборды, обходя множество подводных камней. Готовых шаблонов для handlebars (superset) мало, так как это довольно трудоемкая задача, часто выходящая за рамки работы с готовыми BI-системами.
Работая аналитиком данных и столкнувшись с ограничениями в вёрстке дашборда, пришлось разработать несколько кастомизированных шаблонов, которые можно быстро применить на практике. Если пользователь не знаком с html и css — поначалу это будет довольно сложно, но постараюсь как можно подробнее расписать значение классов и элементов шаблона для неопытных пользователей, заодно можно немного погрузиться в мир web-разработки :).
Общие правила использования Handlebars для Superset:
-
сначала создаём необходимые показатели, которые будут отображаться в карточках,
-
размещаем код HTML + jinja в первом окне, CSS во втором. Элементы jinja заключаются в двойные фигурные скобки {{ col1 }}, внутри ваш показатель, он автоматически изменяется по мере обновления данных,
Пара {{#each data}} … {{/each}}: блок шаблона Handlebars, который проходит по каждому элементу массива data и генерирует соответствующую разметку.
-
пользуясь подсказками к коду CSS, меняем цвета, размер полей и т.д. в соответствии со своими потребностями.
Первый шаблон
Набор карточек с общим показателем на 2 строки.
Иногда нужны довольно мелкие карточки для показателей и не всегда возможно много разместить на дашборде. Некоторым неудобством является, что для каждой карточки нужно прописать отдельный показатель если хочется разные сущности отобразить в одном контейнере, но это иногда и упрощает задачу по поиску нарушений в данных.
Готовый код HTML + jinja размещаемый в первом окне Handlebars Template:
<div class="f-section"> {{#each data}} <div class="f-container"> <div class="f-item left-f"> <div class="glav">ВСЕГО</div> <div class="all">{{ ppt_all }}</div> </div> <div class="right-column"> <div class="top-row"> <div class="f-item"> <h5>РЛС</h5> <div class="number">{{ rls }}</div> </div> <div class="f-item"> <h5>ЛА</h5> <div class="number">{{ la }}</div> </div> <div class="f-item"> <h5>ВО</h5> <div class="number">{{ vo }}</div> </div> <div class="f-item"> <h5>ЛВО</h5> <div class="number">{{ lvo }}</div> </div> <div class="f-item"> <h5>ТН</h5> <div class="number">{{ tn }}</div> </div> <div class="f-item"> <h5>ОХО</h5> <div class="number">{{ oho }}</div> </div> <div class="f-item"> <h5>КАК</h5> <div class="number">{{ kak }}</div> </div> <div class="f-item"> <h5>ИСА</h5> <div class="number">{{ isa }}</div> </div> <div class="f-item"> <h5>КХО</h5> <div class="number">{{ kho }}</div> </div> <div class="f-item"> <h5>КП</h5> <div class="number">{{ kp }}</div> </div> </div> <div class="bottom-row"> <div class="f-item"> <div class="header1">СТИ ВСЕГО</div> <div class = "spp">11 344 тыс. м.</div> </div> <div class="f-item"> <div class="header1">ГМС ВСЕГО</div> <div class = "spp">2 479 тыс. м.</div> </div> <div class="f-item"> <div class="header1">ЦСМ ВСЕГО</div> <div class = "spp">2 650 тыс. м.</div> </div> <div class="f-item"> <div class="header1">ГПК ВСЕГО</div> <div class = "spp">3 345 тыс. м.</div> </div> </div> </div> </div> {{/each}} </div>
Готовый код CSS для второго окна:
.f-container { display: flex; gap: 14px; } .left-f { flex: 0.4; width: 160px; } .right-column { flex: 3; display: grid; flex-direction: column; gap: 10px; } .top-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); margin-bottom: 14px; gap: 10px; } .bottom-row { display: grid; grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; } .f-item { font-weight:bolder; border: solid 1px lightgray; background: #fff; padding: 10px 8px 6px 8px; border-radius: 12px; text-align: center; box-shadow: 0 4px 4px rgba(0,0,0,0.2); transition: transform 0.3s, box-shadow 0.3s; } .f-item:hover { transform: translateY(-5px); box-shadow: 0 8px 10px rgba(0,0,0,0.2); } .f-item h5 { margin: 0 0 8px 0; font-weight:bolder; } .glav { font-weight:bolder; padding-top: 30px; font-size: 22px; color: #c90808; } .all { font-size: 26px; color: #c90808; } .number { font-weight:bolder; font-size: 20px; color: #2e75b6; } .header1 { font-weight:bolder; color: #2e75b6; } .spp { font-style: italic; }
Подробное объяснение css для тех, кто web-вёрсткой обычно не занимается, но общее представление имеет
.f-container { /*общий контейнер*/ display: flex; /*позволяет элементы в строку или столбец с гибким расположением.*/ gap: 14px; /*промежуток между элементами внутри flex-контейнера*/ } .left-f { /*большая карточка*/ /*коэффициент гибкости для элемента внутри flex-контейнера*/ /*в данном случае элемент будет занимать 0.4 доли доступного пространства*/ flex: 0.4; width: 160px; /*фиксированная ширина элемента в пикселях*/ } .right-column { /*колонка с двумя строками карточек*/ /*коэффициент гибкости для элемента, позволяя ему занимать*/ /*в 3 раза больше пространства по сравнению с элементами с меньшим значением*/ flex: 3; display: grid; /*создание сеточной компоновки*/ flex-direction: column; /*определяет направление расположения элементов внутри flex-контейнера по вертикали (в столбец)*/ gap: 10px; } .top-row { /*верхняя строка*/ display: grid; /*определяет шаблон столбцов сетки, автоматически подгоняя количество столбцов*/ /*под доступное пространство с минимальной шириной 70 пикселей*/ /*и распределением оставшегося пространства поровну (1fr).*/ grid-template-columns: repeat(auto-fit, minmax(70px, 1fr)); margin-bottom: 14px; /* внешний отступ под элементом*/ gap: 10px; /*промежуток между элементами сетки*/ } .bottom-row { /*нижняя строка*/ display: grid; /*настраивает шаблон столбцов сетки с автоматическим подбором количества столбцов,*/ /*минимальная ширина каждого столбца 120 пикселей, а оставшееся пространство*/ /*распределяется равномерно (1fr)*/ grid-template-columns: repeat(auto-fit, minmax(120px, 1fr)); gap: 10px; } .f-item { /*внешний вид всех карточек*/ font-weight:bolder; /*более жирное начертание шрифта*/ border: solid 1px lightgray; /*рамка карточек с шириной и серым цветом*/ background: #fff; /*белый фон для элемента*/ /*внутренние отступы: сверху 10px, справа 8px, снизу 6px, слева 8px*/ padding: 10px 8px 6px 8px; border-radius: 12px; /*закругляет углы элемента радиусом 12 пикселей*/ text-align: center; /*выравнивает текст внутри элемента по центру*/ /*добавляет тень к элементу с отступом по оси X: 0, по оси Y: 4px,*/ /*размытием: 4px и цветом с прозрачностью 20%*/ box-shadow: 0 4px 4px rgba(0,0,0,0.2); /*определяет переходные эффекты для свойств transform*/ /*и box-shadow с длительностью 0.3 секунды*/ transition: transform 0.3s, box-shadow 0.3s; } .f-item:hover { /*анимация карточек*/ /*при наведении курсора элемент сдвигается вверх на 5 пикселей*/ transform: translateY(-5px); /*усиливается тень элемента: вертикальный отступ 8px, размывание 10px*/ /*с тем же цветом и прозрачностью.*/ box-shadow: 0 8px 10px rgba(0,0,0,0.2); } .f-item h5 { /*внешние отступы для заголовков верхних карточек*/ margin: 0 0 8px 0; font-weight:bolder; } .glav { /*текст на большой карточке*/ font-weight:bolder; padding-top: 30px; /*внутренний отступ*/ font-size: 22px; /*размер шрифта*/ color: #c90808; /*цвет текста*/ } .all { /*показатель на большой карточке*/ font-size: 26px; color: #c90808; /*цвет текста - оттенок красного*/ } .number { /*стилизация цифр карточек в верхней строке*/ font-weight:bolder; font-size: 20px; color: #2e75b6; /*цвет текста - оттенок синего*/ } .header1 { /*заголовки карточек нижней строки*/ font-weight:bolder; color: #2e75b6; } .spp { font-style: italic; /*стиль текста - курсив*/ }
Второй шаблон
График с надписями на цветовых элементах и показателем с общей суммой со всплывающими подсказками. Цветовые шкалы меняются в процентном соотношении друг к другу.
Лучше всего выглядит для двух качественных значений, с уже накопленными показателями.
Готовый код HTML + jinja размещаемый в первом окне Handlebars Template
<div class="hb-ppt-oc"> {{#each data}} <div class="bar-container"> <div class="bars"> <div class="bar gray-bar" style="flex-grow: {{ ppt_1 }};" title="Общие: {{ ppt_1 }}"> <span class="bar-label">ОБЩИЕ: {{ ppt_1 }}</span> </div> <div class="bar red-bar" style="flex-grow: {{ ppt_2 }};" title="Сезонные: {{ ppt_2 }}"> <span class="bar-label">СЕЗОННЫЕ: {{ ppt_2 }} </span> </div> <div class="bar-summ">БЮДЖЕТ: {{ inv }}</div> </div> </div> {{/each}} </div>
Готовый код CSS для второго окна:
.hb-ppt-oc { width: 100%; margin: 0 auto; } .bar-container { width: 100%; height: 50px; } .bars { display: flex; align-items: center; height: 100%; width: calc(100% - 150px); } .bar-summ { margin-left: 20px; font-size: 18px; font-weight: bolder; flex: 0 0 150px; text-align: right; } .bar { display: flex; align-items: center; justify-content: center; height: 95%; position: relative; color: white; font-weight: bold; } .gray-bar { margin-left: 20px; background-color: #9c9c9b; padding: 0 5px; border-radius: 6px 0px 0px 6px; margin-right: 0; } .red-bar { background-color: #e06969; border-radius: 0px 6px 6px 0px; } .bar-label { position: absolute; width: 100%; text-align: center; font-size: 14px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
Подробное объяснение css для тех, кто вёрсткой обычно не занимается, но общее представление имеет
.hb-ppt-oc { /*общий контейнер по центру и занимает всю доступную ширину*/ width: 100%; /*занимает всю доступную ширину родительского контейнера*/ margin: 0 auto; /*центрирует контейнер по горизонтали*/ } /*Внутри .hb-ppt-oc, для каждого элемента данных ({{#each data}}),*/ /*создаётся .bar-container с фиксированной высотой.*/ .bar-container { width: 100%; height: 50px; /*фиксированная высота для строки баров*/ } .bars { /*Flex-контейнер внутри .bar-container*/ display: flex; /*Flexbox для горизонтального расположения*/ align-items: center; /*вертикальное выравнивание элементов по центру*/ height: 100%; /*высота равна высоте .bar-container*/ width: calc(100% - 150px); /*занимает всю ширину минус 150px для .bar-summ*/ } .bar-summ { /*контейнер для подписи БЮДЖЕТ и числового показателя суммы*/ margin-left: 20px; /*отступ слева для отделения от бара*/ font-size: 18px; /*размер шрифта*/ font-weight: bolder; /*более жирный шрифт*/ flex: 0 0 150px; /*фиксированная ширина элемента с суммой*/ text-align: right; /*выравнивание текста по правому краю*/ } .bar { display: flex; align-items: center; justify-content: center; /*центрирование по горизонтали*/ height: 95%; /*уменьшенние высоты для визуального отступа*/ position: relative; /*позиционирование дочерних элементов .bar-label*/ color: white; /*белый цвет текста на цветных барах*/ font-weight: bold; } .gray-bar { /*серый бар*/ margin-left: 20px; /*отступ слева для отделения от предыдущего элемента*/ background-color: #9c9c9b; /*серый цвет фона бара*/ padding: 0 5px; /*внутренние отступы слева и справа*/ border-radius: 6px 0px 0px 6px; /*скругление левых углов баров*/ margin-right: 0; /*без отступа справа*/ } .red-bar { /*красный бар*/ background-color: #e06969; border-radius: 0px 6px 6px 0px; } .bar-label { /*текстовые описания и значение для баров*/ position: absolute; /*абсолютное позиционирование относительно .bar*/ width: 100%; text-align: center; font-size: 14px; white-space: nowrap; /*запрет переноса текста на новую строку*/ overflow: hidden; /*скрывает содержимое, выходящее за пределы блока*/ text-overflow: ellipsis; /*добавляет троеточие при обрезке текста*/ }
В завершение, несколько подсказок:
-
нужно аккуратно относиться к форматированию кода в окнах, он может не срабатывать и выдавать ошибки если есть подсказки или пробелы в строках;
-
элементы классов и элементов css лучше для каждого графика называть индивидуально, т.к. они будут влиять на все графики на дашборде с одинаковыми именами классов, вы будете менять что-то в коде, а это не будет срабатывать или наоборот срабатывать не там :);
-
осторожнее с body, его включать в код не стоит, он будет влиять на весь дашборд;
-
можно было бы уменьшить код, и не прописывать каждый показатель отдельно в первом случае, а просто поместить колонку с названием показателей в код html, но карточки с нулевыми значениями показателя не появлялись, даже если в вычислении использовался SQL-запрос: COALESCE(COUNT(col1), 0).
Данные полностью выдуманы, никаких инсайтов можно не искать, если есть конструктивные предложения по улучшению кода, делитесь подсказками.
ссылка на оригинал статьи https://habr.com/ru/articles/851756/
Добавить комментарий