Initial commit

This commit is contained in:
Oleh Omelchenko
2026-08-27 14:28:09 +03:00
committed by GitHub
commit 54c3647c96
18 changed files with 4129 additions and 0 deletions
+331
View File
@@ -0,0 +1,331 @@
# Портфоліо · шаблон
Сайт для ваших робіт із курсу «Візуалізація даних». Хто відкриє
посилання, одразу бачить самі роботи — без проміжних сторінок.
Вбудовує графіки й дашборди з **Tableau Public** і специфікації
**Vega-Lite**.
Одна робота — це **один файл** у теці `_works/`. Пишете звичайним
текстом. HTML не потрібен ніде.
Установлювати нічого не треба. Усе робиться в браузері.
> **Сайт публічний.** Його бачить будь-хто в інтернеті, і ваше ім'я
> стоїть на ньому. Беріть навчальні або відкриті дані. Якщо графік
> зроблено на робочих даних, заберіть назви клієнтів і справжні суми
> або візьміть інший графік. У Tableau Public вимкніть **Allow
> Access**: інакше книгу з даними можна завантажити.
---
## Крок 1. Зробіть із шаблону свій репозиторій
1. Угорі натисніть зелену кнопку **Use this template**.
2. Виберіть **Create a new repository**.
3. У полі **Repository name** напишіть `dataviz-portfolio`.
4. Виберіть **Public**. Приватний репозиторій не публікується.
5. Натисніть **Create repository**.
Далі ви працюєте у своєму репозиторії, не в цьому.
## Крок 2. Увімкніть публікацію
1. У своєму репозиторії відкрийте вкладку **Settings**.
2. Зліва виберіть **Pages**.
3. У полі **Source** виберіть `Deploy from a branch`.
4. У полі **Branch** виберіть `main` і теку `/ (root)`.
5. Натисніть **Save**.
6. Зачекайте одну-дві хвилини й оновіть **Settings → Pages**.
Угорі з'явиться адреса вашого сайту:
```
https://ВАШ-ЛОГІН.github.io/dataviz-portfolio/
```
Відкрийте її. Сайт уже працює: дві роботи-приклади і список робіт
у бічній панелі.
## Крок 3. Підпишіть сайт своїм іменем
Ім'я стоїть в одному місці — у файлі `_config.yml`.
1. Відкрийте `_config.yml`.
2. Замініть `title`, `role` і `tagline`.
3. Натисніть **Commit changes**.
```yaml
title: "Олена Ковальчук"
role: "Аналітик даних"
tagline: "Роботи з курсу «Візуалізація даних» KSE, 2026."
```
Через хвилину сайт оновиться сам.
Лапки навколо тексту обов'язкові. Якщо в тексті потрібні лапки,
беріть «такі».
---
## Як додати роботу
Одна робота — один файл у теці `_works/`.
1. Відкрийте теку `_works`.
2. Натисніть **Add file → Create new file**.
3. Напишіть ім'я файлу латиницею, з `.md` на кінці: `03-facets.md`.
4. Вставте текст нижче і замініть його своїм.
5. Натисніть **Commit changes**.
Робота з **Tableau Public**:
```markdown
---
title: "Назва роботи"
card: "Один рядок для картки на головній."
tableau: "https://public.tableau.com/app/profile/ваш.профіль/viz/Книга/Аркуш"
---
**Питання:** на яке питання відповідає графік.
**Рішення:** що ви вирішили і чому.
**Що видно:** що з цього побачив читач.
```
Робота з **Vega-Lite**:
```markdown
---
title: "Назва роботи"
card: "Один рядок для картки на головній."
spec: "specs/my-chart.json"
---
**Питання:** на яке питання відповідає графік.
```
Файл специфікації покладіть у теку `specs`: **Add file → Upload
files** → перетягніть файл → **Commit changes**. Ім'я файлу —
латиницею, без пробілів.
Роботи стоять на сторінці за іменем файлу, за абеткою. Тому ставте
число попереду: `01-`, `02-`, `03-`. Пункт у бічній панелі
з'являється сам.
Найсильнішу роботу ставте першою. Її побачать усі, решту —
не обов'язково.
### Що можна написати в шапці
| Поле | Навіщо |
|---|---|
| `title` | заголовок роботи. Обов'язкове |
| `card` | короткий опис. Знадобиться, якщо додасте окрему головну |
| `tableau` | адреса графіка з адресного рядка Tableau Public |
| `spec` | ім'я файлу специфікації в теці `specs` |
| `before` | картинка «було» над графіком. Для розборів «до/після» |
| `steps` | кілька картинок-спроб перед графіком. Для розборів у кілька кроків |
| `wide: true` | графік на всю ширину сторінки. Для дашбордів |
| `height: 1150` | висота рамки в пікселях. Для дашбордів |
Кожна робота на сторінці згортається: читач може закрити зайве й
лишити тільки те, що хоче роздивитись. За замовчуванням усі роботи
розгорнуті. У бічній панелі є кнопка «Згорнути всі».
Текст під шапкою — звичайний Markdown. `**жирний**`, списки,
посилання працюють.
### Перед тим як вбудовувати графік Tableau
1. Відкрийте свій графік на Tableau Public.
2. Натисніть шестерню у верхньому правому куті.
3. Вимкніть **Show Sheets**.
Інакше поруч із графіком видно всі аркуші книги, зокрема чернетки.
У тому самому меню вимкніть **Allow Access**, якщо графік зроблено на
робочих даних.
### Дашборд ширший за графік
Дашборд вищий і ширший за окремий графік, тому в текстовій колонці
його обріже. Найпростіший шлях — виправити розмір у Tableau:
1. Відкрийте дашборд у Tableau.
2. У полі **Size** виберіть **Automatic**.
Якщо розмір змінити не можна, допишіть у шапку роботи два рядки:
```yaml
wide: true
height: 1150
```
Висоту підберіть на око. Якщо низ дашборда обрізаний, збільшуйте
число, поки дашборд не вміститься.
### Розбір «до / стало»
Найсильніша робота в портфоліо — не картинка, а хід думки. Покладіть
скріншот вихідного графіка в теку `specs` (або в будь-яку іншу) і
допишіть у шапку:
```yaml
before: "specs/vacancies-before.png"
```
Над графіком з'явиться картинка з підписом «Було», під нею — ваш
графік із підписом «Стало».
Якщо спроб було кілька, покажіть їх усі — так видно хід думки, а не
тільки результат:
```yaml
steps:
- img: "specs/step1.png"
label: "Спроба 1"
note: "два ряди окремо"
- img: "specs/step2.png"
label: "Спроба 2"
note: "подвійна вісь"
final_label: "Що вийшло"
```
Кожна спроба має пояснити, **чому** вона не підійшла. Тупик без
діагнозу — просто чернетка; тупик із діагнозом — доказ, що ви
розуміли, що робите.
У шаблоні є два готові розбори: «Скільки кандидатів на одну вакансію»
(`_works/01-candidates-per-vacancy.md`) і «Чи заробляємо ми на цьому
обороті» (`_works/02-margin-case.md`). Подивіться, як він
побудований, і напишіть свій за тією ж рамкою:
- **Питання** — на що читач хотів відповіді;
- **Що не працювало** — чому попередній графік не відповідав;
- **Рішення** — що ви зробили і чому саме так;
- **Що я відкинув** — варіант, який ви розглянули й не взяли. Один
такий абзац доводить, що рішення було свідоме;
- **Що стало видно** — конкретна знахідка, з числом;
- **Що я б ще змінив** — те, що лишилось слабким. Робота, яка
вдає завершеність, читається як навчальна вправа.
Один такий розбір у портфоліо коштує більше, ніж п'ять акуратних
графіків без пояснень.
---
## Як додати ще одну сторінку
Наприклад, `about.md` — про себе.
1. Натисніть **Add file → Create new file**. Ім'я — `about.md`.
2. Вставте текст:
```markdown
---
heading: "Про мене"
---
Аналітик у ритейлі. Тут коротко про досвід і контакти.
```
3. Натисніть **Commit changes**.
Щоб вставити графік на такій сторінці, напишіть один рядок:
```liquid
{% include viz.html tableau="https://public.tableau.com/app/profile/…/viz/Книга/Аркуш" %}
```
або `{% include viz.html spec="specs/my-chart.json" %}`.
4. Відкрийте `_config.yml` і допишіть сторінку в список `nav`:
```yaml
nav:
- name: "Про мене"
file: "about.html"
```
5. Натисніть **Commit changes**.
У полі `file` пишіть ім'я з `.html`, навіть якщо файл має `.md`.
Поки список `nav` порожній, у бічній панелі немає розділу сторінок.
Щойно ви додасте першу сторінку, поруч з'явиться і посилання
«Роботи» на головну.
---
## Якщо щось не показується
Спершу подивіться на вкладку **Actions** у своєму репозиторії.
Зелена галочка — сайт зібрано. Червоний хрестик — у якомусь файлі
помилка, і сайт лишився таким, яким був до неї.
| Що бачите | Що робити |
|---|---|
| Червоний хрестик в **Actions** | Відкрийте останній запис і прочитайте рядок з `Error:`. Найчастіше — лапки або відступ у шапці роботи |
| «Не вдалося прочитати адресу Tableau» | Відкрийте графік на Tableau Public і скопіюйте адресу з адресного рядка заново |
| У рамці англійське «Where's the viz?» | Адресу прочитано, графіка за нею немає. Ви перейменували, приховали або видалили книгу чи аркуш |
| «Файл … не знайдено» | Перевірте, що файл лежить у теці `specs`, а ім'я в полі `spec:` збігається |
| «Специфікація не читається як JSON» | Вставте специфікацію у Vega Editor. Він покаже, де зайва або пропущена кома |
| Дашборд обрізаний | Виберіть **Size → Automatic** у Tableau або допишіть `wide: true` і `height:` |
| Робота не з'явилась | Перевірте, що файл лежить у теці `_works` і має `.md` на кінці |
| Роботи стоять не в тому порядку | Вони йдуть за іменем файлу. Перейменуйте: `01-`, `02-`, `03-` |
| Сайт не оновився | Зачекайте хвилину й оновіть сторінку через `Ctrl`/`Cmd` + `Shift` + `R` |
### Якщо не виходить — спитайте ШІ
Скопіюйте текст нижче в ChatGPT, Claude або Gemini. Він описує, як
влаштований сайт, тому модель відповідатиме по суті, а не навмання.
Свою проблему допишіть у кінці.
```text
Допоможи розібратися з моїм сайтом-портфоліо. Відповідай українською,
покроково, і пам'ятай: я працюю тільки через сайт github.com у
браузері, без командного рядка і без програм на комп'ютері.
Як влаштований сайт:
- Це GitHub Pages з Jekyll. Репозиторій публічний, гілка main, тека
публікації — корінь.
- Налаштування сайту в _config.yml: title, role, tagline, footer і
список сторінок nav (у кожної name і file).
- Головна сторінка — index.md, вона показує всі роботи одразу.
Розмітка одна: _layouts/default.html.
- Кожна робота портфоліо — окремий файл у теці _works/. У його шапці
між --- стоять поля: title, card, а також tableau (адреса графіка
Tableau Public) або spec (ім'я файлу в теці specs/). Необов'язкові
поля: wide: true і height (число).
- Текст роботи під шапкою — звичайний Markdown.
- Кожна робота на сторінці — це <details class="work">, тобто її можна
згорнути. Розгорнуті за замовчуванням.
- На вільній сторінці графік вставляється через {% include viz.html %}.
- Файли специфікацій Vega-Lite лежать у теці specs/.
- Оформлення в assets/style.css, вбудовування графіків — у
assets/embed.js. Ці два файли я не змінюю.
- Сторінка сама збирає список робіт і бічну панель.
Що мені треба:
[опишіть, що ви робили, що очікували побачити і що побачили насправді]
Текст помилки з вкладки Actions, якщо він є:
[вставте сюди]
```
Якщо модель радить встановити Ruby, Jekyll, Node або запустити щось
у терміналі — скажіть їй, що ви працюєте тільки в браузері на
github.com, і попросіть інший шлях.
---
## Що в теці
| Файл або тека | Навіщо |
|---|---|
| `_config.yml` | ім'я, підпис, список сторінок |
| `_works/` | ваші роботи, по одному файлу на кожну |
| `specs/` | файли специфікацій Vega-Lite |
| `index.md` | сторінка з роботами. Її бачать першою |
| `_layouts/default.html` | спільна розмітка сторінок. Правити не треба |
| `assets/style.css` | оформлення. Кольори — у змінних на початку файлу |
| `assets/embed.js` | вбудовує графіки. Правити не треба |
+28
View File
@@ -0,0 +1,28 @@
# ╔══════════════════════════════════════════════════════════════════╗
# ║ НАЛАШТУВАННЯ САЙТУ ║
# ║ Ім'я стоїть тільки тут. Замінили — змінилося на всіх сторінках. ║
# ╚══════════════════════════════════════════════════════════════════╝
title: "Ім'я Прізвище"
role: "Аналітик даних"
tagline: "Аналітик даних. Тут зібрані мої роботи з курсу «Візуалізація даних» KSE, 2026: від перших графіків у Tableau до інтерактивних специфікацій."
footer: "Зроблено на курсі «Візуалізація даних» · Kyiv School of Economics"
# Додаткові сторінки. Поки список порожній, у панелі їх немає.
# Додали сторінку — допишіть її сюди, і посилання з'являться.
nav: []
# Нижче — службове. Правити не треба.
collections:
works:
output: false
defaults:
- scope:
path: ""
values:
layout: "default"
exclude:
- README.md
- ЯК-ПИТАТИ-ШІ.md
+11
View File
@@ -0,0 +1,11 @@
{%- comment -%}
Вбудовує графік на будь-якій сторінці, не тільки в роботі з _works/.
Приклади:
{% include viz.html tableau="https://public.tableau.com/app/profile/…/viz/Книга/Аркуш" %}
{% include viz.html spec="specs/my-chart.json" %}
{% include viz.html tableau="…" wide=true height=1150 %}
{%- endcomment -%}
<div class="viz{% if include.wide %} wide{% endif %}"
{%- if include.height %} data-height="{{ include.height }}"{% endif %}
{%- if include.tableau %} data-tableau="{{ include.tableau }}"
{%- elsif include.spec %} data-spec="{{ include.spec }}"{% endif %}></div>
+62
View File
@@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="uk">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{% if page.heading %}{{ page.heading }} · {% endif %}{{ site.title }}</title>
{% if page.charts %}
<script src="https://cdn.jsdelivr.net/npm/vega@5"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-lite@5"></script>
<script src="https://cdn.jsdelivr.net/npm/vega-embed@6"></script>
{% endif %}
<link rel="stylesheet" href="assets/style.css">
</head>
<body>
<div class="layout">
<aside class="side">
<a class="side-name" href="index.html">{{ site.title }}</a>
<p class="side-role">{{ site.role }}</p>
{%- if site.nav.size > 0 %}
<nav class="side-nav">
<a href="index.html"{% if page.url == "/index.html" %} class="here"{% endif %}>Роботи</a>
{%- for item in site.nav %}
<a href="{{ item.file }}"{% if page.url contains item.file %} class="here"{% endif %}>{{ item.name }}</a>
{%- endfor %}
</nav>
{%- endif %}
{%- if page.charts and site.works.size > 0 %}
<nav class="side-works">
{%- for work in site.works %}
<a href="#work-{{ work.slug }}">{{ work.title }}</a>
{%- endfor %}
</nav>
{%- endif %}
</aside>
<div class="content">
<header class="page-head">
<h1>{{ page.heading }}</h1>
{%- if page.show_tagline %}
<p class="tagline">{{ site.tagline }}</p>
{%- endif %}
</header>
<main>
{{ content }}
</main>
<footer class="page-foot">
<p>{{ site.footer }}</p>
</footer>
</div><!-- .content -->
</div><!-- .layout -->
{% if page.charts %}<script src="assets/embed.js"></script>{% endif %}
</body>
</html>
+80
View File
@@ -0,0 +1,80 @@
---
title: "Скільки кандидатів на одну вакансію"
card: "Чотири підходи до одних даних. Два перші відповідали не на те питання, третій читався неправильно — і тільки четвертий показав, що паритет зламався за пʼять місяців до вторгнення."
steps:
- img: "specs/cand-step1.png"
label: "Спроба 1"
note: "два ряди окремо"
- img: "specs/cand-step2.png"
label: "Спроба 2"
note: "подвійна вісь"
- img: "specs/candidates-before.png"
label: "Спроба 3"
note: "співвідношення, але стовпчики читаються як складені"
final_label: "Що вийшло"
spec: "specs/candidates-per-vacancy.vl.json"
wide: true
---
**Питання.** Як змінилося співвідношення «кандидати на одну вакансію»
на ринку праці — і що саме дало цю зміну: приплив кандидатів чи обвал
вакансій. Дві тижневі серії, березень 2020 — липень 2026.
**Спроба 1: два ряди окремо.** Найочевидніше рішення — показати обидві
серії, кожну своїм графіком. Обидва графіки правильні, і кожен окремо
читається легко. Але питання було про **співвідношення**, а щоб його
дістати, читач мусить ділити один ряд на інший, тиждень за тижнем.
Людина цього не робить. Графіки відповідають на «скільки кандидатів»
і «скільки вакансій» — не на те, що питали.
**Спроба 2: подвійна вісь.** Класичний наступний крок: обидві серії в
одному кадрі, кожна на своїй осі. Тепер лінії видно разом — і вони
перетинаються на початку 2022-го, точно біля вертикалі вторгнення.
Виглядає як знахідка.
Це неправда. Точка перетину тут — **артефакт масштабу**: ліва вісь
іде до 100 тисяч, права до 30 тисяч, і саме це співвідношення осей
ставить перетин туди, де він стоїть. Розтягніть праву вісь удвічі, і
«момент перелому» переїде на рік. Подвійна вісь дала мені готовий
хибний висновок і зробила його переконливим на вигляд.
**Спроба 3: обчислити співвідношення.** Якщо питання про зв'язок двох
рядів — треба обчислити зв'язок і зробити його головним графіком.
Лінія «кандидатів на вакансію», лінія паритету на 1, вертикаль
вторгнення, а сирі величини — прозорими стовпчиками на тлі, щоб не
загубити масштаб.
Тут з'явилася нова вада, вже не логічна, а перцептивна: обидва
стовпчики однакової ширини від нуля читаються як **складений**
стовпчик. Перекриття дає темнішу «нижню секцію», а вища серія виглядає
сегментом зверху. Графік технічно правильний, а читається неправильно.
Жодна автоматична перевірка цього не ловить — це видно тільки оком.
**Що вийшло.** Дві ширини замість однієї: вузький помаранчевий
стовпчик перед широким блідо-синім, як у bullet-графіка. Різниця ширин
робить «перед» однозначним. Стовпчики лишаються на **прихованій**
шкалі — другої осі немає взагалі, тож графік не запрошує читати лінію
по шкалі стовпчиків. Точні числа живуть у підказці й у двох підписах
на кінці.
**Що стало видно.** Кандидати обійшли вакансії назавжди у вересні
2021 — за пʼять місяців **до** вторгнення. Вторгнення не перевернуло
знак, воно рознесло вже перевернуте співвідношення: 1,4 напередодні
проти 12,5 зараз. У логарифмі обвал вакансій (÷3,7) пояснює близько
60 % зсуву, приплив кандидатів (×2,4) — решту 40 %.
Порівняйте з другою спробою: там «перелом» стояв у лютому 2022-го.
Різниця між двома висновками — не в даних, а у виборі кодування.
**Що я б ще змінив.** На лінійній осі провал 2021-го під паритет
стиснутий у нижні 6 % полотна: підпис на нього вказує, але вся драма
візуально post-2022. Для аналітичної аудиторії логарифмічна вісь
чесніша — там однакові відстані вгору і вниз означають однакові
кратності. Для загальної потрібне окреме речення пояснення, тому тут
лишилася лінійна.
**Як це зроблено.** Чотири спроби — це чотири раунди діалогу з
агентом: він давав форми і компроміси, я вибирав і ламав. Найцінніше
повідомлення в усій розмові було моїм: один скріншот і рядок «сині
завжди зверху помаранчевих». Рендер-перевірка агента ловить накладання
підписів, але не те, що кодування **читається** не так.
+35
View File
@@ -0,0 +1,35 @@
---
title: "Чи заробляємо ми на цьому обороті"
card: "Розбір: два ряди окремо не відповідали на питання про зв'язок. Обчислена маржа показала два збиткові місяці."
before: "specs/margin-before.png"
spec: "specs/margin.vl.json"
example: true
---
**Питання.** Керівник продажів питав щотижня одне й те саме: «обіг
росте, але чи ми на ньому заробляємо». На дашборді стояли два графіки
один під одним — продажі й прибуток за місяцями.
**Що не працювало.** Обидва графіки правильні, і прибуток навіть
двічі провалюється нижче нуля. Але відповіді на питання там немає:
щоб її дістати, читач мусить подумки ділити один ряд на інший,
місяць за місяцем. Людина цього не робить. На тлі піків у
вісімнадцять тисяч ті два провали виглядають дрібними зарубками.
**Рішення.** Питання було про **зв'язок** двох рядів, тому я обчислив
зв'язок і зробив його головним графіком: маржа у відсотках від
продажів, нульова лінія, збиткові місяці позначені точками.
**Що я відкинув.** Спершу зробив подвійну вісь — продажі зліва,
прибуток справа. Виглядало компактно, але масштаб правої осі можна
підібрати так, щоб та сама пара рядів «доводила» будь-що. Коли
питання про співвідношення, подвійна вісь дає привід для суперечки
замість відповіді.
**Що стало видно.** Січень 2015-го — мінус вісімнадцять відсотків,
найгірший місяць за чотири роки. На парі сирих графіків він губився
серед звичайних коливань.
**Що я б ще змінив.** Маржа нічого не каже про причину. Наступний
крок — розбити той місяць за підкатегоріями і знижками: гіпотеза, що
провал дали великі знижки на меблі, але вона поки не перевірена.
+14
View File
@@ -0,0 +1,14 @@
---
title: "Що виросло, а що впало"
card: "Замість двох графіків поруч — одна обчислена різниця з нульовою віссю. Втрату в Machines стало видно одразу."
tableau: "https://public.tableau.com/app/profile/oleh.omelchenko7227/viz/DatavizWeek3-Makeover/Sheet10"
example: true
---
**Питання:** які підкатегорії втратили в продажах за рік.
**Рішення:** одна обчислена різниця з нульовою віссю, відсортована за
величиною, замість двох стовпчикових графіків поруч.
**Що видно:** Machines — єдина помітна втрата. На парі графіків вона
губилася між великими числами.
+14
View File
@@ -0,0 +1,14 @@
---
title: "Сезонність за категоріями"
card: "Величину несе яскравість, тому четверті квартали видно одразу. Найсильніший пік 2017-го дала техніка."
spec: "specs/example.vl.json"
example: true
---
**Питання:** чи однакова сезонність у трьох категорій товару.
**Рішення:** теплокарта. Величину несе яскравість, а позиція витрачена
на категорію і квартал.
**Що видно:** четверті квартали світліші в усіх трьох. Найсильніший
пік 2017-го дала техніка.
+182
View File
@@ -0,0 +1,182 @@
/* Вбудовує графіки у сторінку робіт.
*
* Цей файл правити не треба. Роботи ви описуєте у файлах теки _works/,
* а звідки брати графік, каже їхня «шапка»:
* tableau: "адреса з адресного рядка Tableau Public"
* spec: "specs/файл.json"
*
* Блок із помилкою показує підказку і не ламає решту сторінки.
*/
(function () {
"use strict";
function box(el, kind, title, hint) {
var d = document.createElement("div");
d.className = "viz-msg viz-msg--" + kind;
d.innerHTML = "<b></b><span></span>";
d.querySelector("b").textContent = title;
d.querySelector("span").textContent = hint || "";
el.replaceChildren(d);
}
/* Адреса графіка Tableau Public → адреса, яку можна вбудувати.
Приймає і те, що видно в адресному рядку, і готову адресу /views/. */
function tableauEmbedUrl(raw) {
var url = String(raw).trim().split("?")[0];
var m = url.match(/[#!/]*\/(?:viz|views|vizhome)\/([^/]+)\/([^/?#]+)/);
if (!m) return null;
/* tabs=no обов'язковий: без нього рамка показує всі аркуші книги,
разом із чернетками, і будь-хто може в них клацнути. */
return "https://public.tableau.com/views/" + m[1] + "/" + m[2] +
"?:showVizHome=no&:embed=y&:tabs=no&:display_count=no&:toolbar=no";
}
function mountTableau(el) {
var raw = el.getAttribute("data-tableau");
var src = tableauEmbedUrl(raw);
if (!src) {
box(el, "warn", "Не вдалося прочитати адресу Tableau",
"У полі tableau: має бути адреса вигляду " +
"public.tableau.com/app/profile/…/viz/Книга/Аркуш. " +
"Відкрийте графік на Tableau Public і скопіюйте адресу з адресного рядка.");
return;
}
var frame = document.createElement("iframe");
frame.className = "viz-frame";
frame.src = src;
frame.loading = "lazy";
frame.title = "Графік Tableau Public";
frame.setAttribute("allowfullscreen", "");
frame.style.height = (el.getAttribute("data-height") || 620) + "px";
/* Пряме посилання під рамкою. Усередині рамки Tableau малює власну
сторінку помилки, якої ми звідси не бачимо. Посилання дає читачеві
шлях до графіка, а автору показує, чи жива адреса. */
var link = document.createElement("p");
link.className = "viz-link";
var a = document.createElement("a");
a.href = String(raw).trim();
a.target = "_blank";
a.rel = "noopener";
a.textContent = "Відкрити на Tableau Public";
link.appendChild(a);
el.replaceChildren(frame, link);
}
function mountSpec(el) {
var path = el.getAttribute("data-spec");
if (typeof vegaEmbed !== "function") {
box(el, "warn", "Бібліотека Vega-Lite не завантажилась",
"Перевірте інтернет і оновіть сторінку.");
return;
}
vegaEmbed(el, path, { actions: false, renderer: "svg" })
.catch(function (err) {
var msg = String(err && err.message || err);
var hint = /fetch|load|404|Not Found/i.test(msg)
? "Файл " + path + " не знайдено. Перевірте, що він лежить у теці specs/ " +
"і що ім'я в полі spec: збігається. Великі й малі літери мають значення."
: "Специфікація не читається як JSON. Найчастіша причина — зайва або " +
"пропущена кома. Вставте специфікацію у Vega Editor: він покаже, де саме.";
box(el, "warn", "Графік не вдалося показати", hint);
});
}
/* Підсвічує в бічній панелі ту роботу, яка зараз на екрані. */
function followScroll() {
var links = document.querySelectorAll(".side-works a");
if (!links.length || !("IntersectionObserver" in window)) return;
var pairs = [];
links.forEach(function (a) {
var el = document.getElementById(a.getAttribute("href").slice(1));
if (el) pairs.push({ a: a, el: el });
});
var seen = new Set();
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (e) {
if (e.isIntersecting) seen.add(e.target); else seen.delete(e.target);
});
pairs.forEach(function (p) { p.a.classList.remove("here"); });
for (var i = 0; i < pairs.length; i++) {
if (seen.has(pairs[i].el)) { pairs[i].a.classList.add("here"); break; }
}
}, { rootMargin: "-20% 0px -70% 0px" });
pairs.forEach(function (p) { io.observe(p.el); });
}
/* Клік у бічній панелі або адреса з #якорем мають розгортати роботу,
навіть якщо читач її згорнув. */
function openOnJump() {
function openHash() {
var id = location.hash.slice(1);
if (!id) return;
var el = document.getElementById(id);
if (el && el.tagName === "DETAILS") el.open = true;
}
openHash();
window.addEventListener("hashchange", openHash);
document.querySelectorAll('.side-works a').forEach(function (a) {
a.addEventListener("click", function () {
var el = document.getElementById(a.getAttribute("href").slice(1));
if (el && el.tagName === "DETAILS") el.open = true;
});
});
}
/* Одна кнопка: згорнути всі роботи або розгорнути назад. */
function foldAll() {
var works = document.querySelectorAll("details.work");
var side = document.querySelector(".side");
if (!works.length || !side) return;
var btn = document.createElement("button");
btn.className = "side-fold";
btn.type = "button";
side.appendChild(btn);
function label() {
var open = document.querySelectorAll("details.work[open]").length;
btn.textContent = open ? "Згорнути всі" : "Розгорнути всі";
}
btn.addEventListener("click", function () {
var open = document.querySelectorAll("details.work[open]").length;
works.forEach(function (w) { w.open = open === 0; });
label();
});
works.forEach(function (w) { w.addEventListener("toggle", label); });
label();
}
function mountAll() {
document.querySelectorAll(".viz").forEach(function (el) {
try {
if (el.hasAttribute("data-tableau")) mountTableau(el);
else if (el.hasAttribute("data-spec")) mountSpec(el);
else box(el, "todo", "Тут буде графік",
"У шапці роботи напишіть tableau: з адресою або spec: з іменем файлу.");
} catch (e) {
box(el, "warn", "Цей блок не вдалося показати", String(e.message || e));
}
});
followScroll();
openOnJump();
foldAll();
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", mountAll);
} else {
mountAll();
}
})();
+412
View File
@@ -0,0 +1,412 @@
/* Оформлення сторінки портфоліо. Правити не обов'язково —
але якщо хочете свої кольори, міняйте змінні в :root нижче. */
:root {
--ink: #1f2a33;
--ink-soft: #56646f;
--accent: #003964;
--line: #e0e6ea;
--bg: #ffffff;
--bg-soft: #fbfcfd;
--warn: #f15b43;
--page: 46rem;
--bleed: 0rem;
}
/* Пороги рахуються так: розкладка впирається в 66rem, тож блок
виступає за неї безпечно, поки ширина вікна ≥ 1008px + 2 × виступ. */
@media (min-width: 1200px) { :root { --bleed: 5rem; } }
@media (min-width: 1400px) { :root { --bleed: 9rem; } }
* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
margin: 0;
background: var(--bg);
color: var(--ink);
font: 17px/1.6 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
"Helvetica Neue", Arial, sans-serif;
}
/* ── розкладка: бічна панель + вміст ───────────────────────────── */
.layout {
display: grid;
grid-template-columns: 15rem minmax(0, 1fr);
gap: 2rem;
max-width: 66rem;
margin: 0 auto;
padding: 0 1.5rem;
}
.content { min-width: 0; }
.side {
min-width: 0;
position: sticky;
top: 0;
align-self: start;
max-height: 100vh;
overflow-y: auto;
padding: 3.5rem 0 2rem;
}
.side-name {
display: block;
font-size: 1.15rem;
font-weight: 650;
line-height: 1.25;
color: var(--accent);
text-decoration: none;
}
.side-role {
margin: 0.3rem 0 1.6rem;
font-size: 0.85rem;
color: var(--ink-soft);
}
.side-nav a {
display: block;
padding: 0.4rem 0;
font-size: 0.95rem;
color: var(--ink-soft);
text-decoration: none;
border-left: 2px solid transparent;
padding-left: 0.8rem;
margin-left: -0.8rem;
}
.side-nav a:hover { color: var(--accent); }
.side-nav a.here {
color: var(--accent);
font-weight: 600;
border-left-color: var(--accent);
}
.side-works { margin-top: 1.2rem; }
/* Рамка-роздільник потрібна тільки тоді, коли вище є список сторінок. */
.side-nav + .side-works {
padding-top: 1.2rem;
border-top: 1px solid var(--line);
}
.side-works a {
display: block;
padding: 0.35rem 0 0.35rem 0.8rem;
margin-left: -0.8rem;
font-size: 0.85rem;
line-height: 1.35;
color: var(--ink-soft);
text-decoration: none;
border-left: 2px solid transparent;
}
.side-works a:hover { color: var(--accent); }
.side-works a.here { color: var(--accent); border-left-color: var(--accent); }
/* ── шапка ─────────────────────────────────────────────────────── */
.page-head {
padding: 3.5rem 0 2.5rem;
border-bottom: 1px solid var(--line);
}
.page-head h1 {
margin: 0;
font-size: 2.4rem;
line-height: 1.15;
letter-spacing: -0.02em;
color: var(--accent);
}
.tagline {
margin: 0.6rem 0 0;
color: var(--ink-soft);
font-size: 1.05rem;
}
/* ── головна: заголовок розділу і картки ───────────────────────── */
.back { margin: 0 0 1.2rem; font-size: 0.9rem; }
.back a { color: var(--ink-soft); }
.back a:hover { color: var(--accent); }
.section-title {
margin: 2.5rem 0 1.2rem;
font-size: 1.1rem;
font-weight: 600;
letter-spacing: 0.04em;
text-transform: uppercase;
color: var(--ink-soft);
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
gap: 1rem;
padding-bottom: 1rem;
}
.card {
display: block;
padding: 1.4rem 1.5rem 1.2rem;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--bg-soft);
text-decoration: none;
color: inherit;
}
.card:hover { border-color: var(--accent); }
.card h3 {
margin: 0 0 0.4rem;
font-size: 1.05rem;
line-height: 1.3;
color: var(--accent);
}
.card p {
margin: 0 0 1rem;
font-size: 0.9rem;
color: var(--ink-soft);
}
.card-go { font-size: 0.85rem; color: var(--ink-soft); }
.card:hover .card-go { color: var(--accent); }
/* ── роботи ────────────────────────────────────────────────────── */
main { padding: 0; }
.work {
scroll-margin-top: 1.5rem;
padding: 2rem 0;
border-bottom: 1px solid var(--line);
}
.work:last-of-type { border-bottom: 0; }
/* Заголовок роботи — він же кнопка «згорнути». */
.work > summary {
list-style: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.3rem 0;
user-select: none;
}
.work > summary::-webkit-details-marker { display: none; }
.work > summary::before {
content: "";
flex: 0 0 auto;
width: 0.5rem;
height: 0.5rem;
border-right: 2px solid var(--ink-soft);
border-bottom: 2px solid var(--ink-soft);
transform: rotate(-45deg);
transition: transform 0.15s;
}
.work[open] > summary::before { transform: rotate(45deg); }
.work > summary:hover::before { border-color: var(--accent); }
.work-title {
font-size: 1.4rem;
font-weight: 650;
line-height: 1.25;
color: var(--accent);
}
.work-body { padding-top: 0.6rem; }
/* Пара «було / стало» для розбору однієї роботи. */
.ba { margin: 0 0 1.4rem; }
.ba img {
display: block;
width: 100%;
border: 1px solid var(--line);
border-radius: 8px;
}
.ba figcaption, .ba-label {
margin: 0.5rem 0 0;
font-size: 0.8rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-soft);
}
/* Пояснення після назви кроку — звичайним текстом, не капсом. */
.ba figcaption span {
font-weight: 400;
letter-spacing: 0;
text-transform: none;
}
.ba-label { margin: 0 0 0.5rem; }
/* Кнопка «згорнути всі» в бічній панелі. */
.side-fold {
margin-top: 1.2rem;
padding: 0;
border: 0;
background: none;
font: inherit;
font-size: 0.8rem;
color: var(--ink-soft);
cursor: pointer;
text-align: left;
}
.side-fold:hover { color: var(--accent); }
.note {
margin: 0 0 1.4rem;
color: var(--ink-soft);
max-width: 34rem;
}
.note b, .note strong { color: var(--ink); font-weight: 600; }
.note p { margin: 0 0 0.7rem; }
.note p:last-child { margin-bottom: 0; }
.note + .viz, .note + .note { margin-top: 1.2rem; }
/* заголовок розділу, який Jekyll робить із «## Роботи» */
h2.section-title { margin: 2.5rem 0 1.2rem; }
/* Рядок «це приклад» — видимий, але не на місці опису роботи. */
.note.swap {
margin-top: -0.9rem;
padding-left: 0.8rem;
border-left: 2px solid var(--line);
font-size: 0.85rem;
}
/* Позначка прикладу. Приберете приклад — приберете і її. */
.tag {
display: inline-block;
padding: 0.15em 0.5em;
border: 1px solid var(--line);
border-radius: 999px;
background: var(--bg);
font-size: 0.6em;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-soft);
vertical-align: middle;
}
.card .tag { margin-bottom: 0.5rem; }
/* Контейнер графіка. Широкий графік прокручується всередині себе,
а не розтягує сторінку. */
.viz {
max-width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* Робота на всю ширину: <section class="work wide">.
Дашборди Tableau ширші за текстову колонку, і в ній їх обрізає. */
/* Ширший блок для дашбордів: вилазить за колонку тексту рівно на
--bleed з кожного боку. Класичний трюк «на всю ширину вікна» тут
не працює: колонка стоїть праворуч від панелі, тобто не по центру
вікна, і центрування зсуває блок за правий край. */
/* Кроки розбору виступають так само, як фінальний графік: інакше
«до» і «після» різного розміру, і порівняння нечесне. */
.work.wide .ba,
.work.wide .ba-label,
.work.wide .viz,
.viz.wide {
/* Виступ тільки вправо. Ліворуч стоїть липка бічна панель, і
симетричний виступ наїжджав би на неї. */
width: calc(100% + var(--bleed));
max-width: none; /* інакше .viz{max-width:100%} тримає в колонці */
}
@media (max-width: 720px) {
.work.wide .ba,
.work.wide .ba-label,
.work.wide .viz,
.viz.wide { width: 100%; }
}
/* vega-embed робить свій контейнер inline-block, і той обгортається
під ширину графіка, вилазячи з колонки. Повертаємо блок. */
.viz.vega-embed { display: block; }
.viz-frame {
display: block;
width: 100%;
min-height: 400px;
border: 1px solid var(--line);
border-radius: 8px;
background: var(--bg-soft);
}
.viz-link {
margin: 0.6rem 0 0;
font-size: 0.85rem;
}
.viz-link a { color: var(--ink-soft); }
.viz-link a:hover { color: var(--accent); }
/* ── підказки замість графіка ──────────────────────────────────── */
.viz-msg {
padding: 2rem 1.5rem;
border-radius: 8px;
background: var(--bg-soft);
font-size: 0.95rem;
}
.viz-msg b { display: block; color: var(--accent); margin-bottom: 0.35rem; }
.viz-msg span { color: var(--ink-soft); }
.viz-msg--todo { border: 2px dashed var(--line); }
.viz-msg--warn { border: 1px solid var(--warn); border-left-width: 4px; }
.viz-msg--warn b { color: var(--warn); }
/* ── підвал ────────────────────────────────────────────────────── */
.page-foot {
padding: 2.5rem 0 4rem;
border-top: 1px solid var(--line);
color: var(--ink-soft);
font-size: 0.9rem;
}
.page-foot p { margin: 0; }
@media (max-width: 720px) {
/* minmax(0,…), не 1fr: інакше колонка розтягується під найширший
вміст — вбудований графік — і сторінка їде вбік. */
.layout { grid-template-columns: minmax(0, 1fr); gap: 0; }
.side {
position: static;
max-height: none;
padding: 2rem 0 1rem;
border-bottom: 1px solid var(--line);
}
.side-role { margin-bottom: 1rem; }
.side-nav { display: flex; gap: 1.2rem; }
.side-nav a { border-left: 0; padding-left: 0; margin-left: 0; }
.side-nav a.here { border-bottom: 2px solid var(--accent); }
.side-works { display: none; }
.page-head { padding-top: 2rem; }
.page-head h1 { font-size: 1.9rem; }
.work { padding: 2rem 0; }
}
+42
View File
@@ -0,0 +1,42 @@
---
heading: "Ім'я Прізвище"
show_tagline: true
charts: true
---
{%- for work in site.works %}
<details class="work{% if work.wide %} wide{% endif %}" id="work-{{ work.slug }}" open>
<summary>
<span class="work-title">{{ work.title }}</span>
{%- if work.example %} <span class="tag">приклад</span>{% endif %}
</summary>
<div class="work-body">
<div class="note">{{ work.content | markdownify }}</div>
{%- if work.example %}
<p class="note swap">Це приклад із шаблону. Замініть його своєю роботою або
видаліть файл <code>_works/{{ work.slug }}.md</code>.</p>
{%- endif %}
{%- if work.steps %}
{%- for step in work.steps %}
<figure class="ba">
<img src="{{ step.img }}" alt="{{ step.label }}" loading="lazy">
<figcaption>{{ step.label }}{% if step.note %} — <span>{{ step.note }}</span>{% endif %}</figcaption>
</figure>
{%- endfor %}
<p class="ba-label">{{ work.final_label | default: "Стало" }}</p>
{%- elsif work.before %}
<figure class="ba">
<img src="{{ work.before }}" alt="Було: {{ work.title }}" loading="lazy">
<figcaption>Було</figcaption>
</figure>
<p class="ba-label">Стало</p>
{%- endif %}
<div class="viz"{% if work.height %} data-height="{{ work.height }}"{% endif %}
{%- if work.tableau %} data-tableau="{{ work.tableau }}"
{%- elsif work.spec %} data-spec="{{ work.spec }}"{% endif %}></div>
</div>
</details>
{%- endfor %}
Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 KiB

File diff suppressed because it is too large Load Diff
+307
View File
@@ -0,0 +1,307 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"description": "Приклад роботи в шаблоні портфоліо: величина закодована яскравістю, не веселкою.",
"title": {
"text": "Продажі за кварталами",
"subtitle": "Sample Superstore, 2014–2017",
"anchor": "start",
"fontSize": 16,
"subtitleFontSize": 12,
"subtitleColor": "#7a8894"
},
"width": 620,
"height": 150,
"data": {
"values": [
{
"cat": "Канцелярія",
"quarter": "2014 Q1",
"sales": 14529
},
{
"cat": "Канцелярія",
"quarter": "2014 Q2",
"sales": 31244
},
{
"cat": "Канцелярія",
"quarter": "2014 Q3",
"sales": 53924
},
{
"cat": "Канцелярія",
"quarter": "2014 Q4",
"sales": 52080
},
{
"cat": "Канцелярія",
"quarter": "2015 Q1",
"sales": 23059
},
{
"cat": "Канцелярія",
"quarter": "2015 Q2",
"sales": 32320
},
{
"cat": "Канцелярія",
"quarter": "2015 Q3",
"sales": 35761
},
{
"cat": "Канцелярія",
"quarter": "2015 Q4",
"sales": 46093
},
{
"cat": "Канцелярія",
"quarter": "2016 Q1",
"sales": 29441
},
{
"cat": "Канцелярія",
"quarter": "2016 Q2",
"sales": 34584
},
{
"cat": "Канцелярія",
"quarter": "2016 Q3",
"sales": 45148
},
{
"cat": "Канцелярія",
"quarter": "2016 Q4",
"sales": 74767
},
{
"cat": "Канцелярія",
"quarter": "2017 Q1",
"sales": 43232
},
{
"cat": "Канцелярія",
"quarter": "2017 Q2",
"sales": 45721
},
{
"cat": "Канцелярія",
"quarter": "2017 Q3",
"sales": 72197
},
{
"cat": "Канцелярія",
"quarter": "2017 Q4",
"sales": 84946
},
{
"cat": "Меблі",
"quarter": "2014 Q1",
"sales": 22656
},
{
"cat": "Меблі",
"quarter": "2014 Q2",
"sales": 28064
},
{
"cat": "Меблі",
"quarter": "2014 Q3",
"sales": 41958
},
{
"cat": "Меблі",
"quarter": "2014 Q4",
"sales": 64515
},
{
"cat": "Меблі",
"quarter": "2015 Q1",
"sales": 27374
},
{
"cat": "Меблі",
"quarter": "2015 Q2",
"sales": 27565
},
{
"cat": "Меблі",
"quarter": "2015 Q3",
"sales": 49586
},
{
"cat": "Меблі",
"quarter": "2015 Q4",
"sales": 65993
},
{
"cat": "Меблі",
"quarter": "2016 Q1",
"sales": 24349
},
{
"cat": "Меблі",
"quarter": "2016 Q2",
"sales": 41402
},
{
"cat": "Меблі",
"quarter": "2016 Q3",
"sales": 52815
},
{
"cat": "Меблі",
"quarter": "2016 Q4",
"sales": 80335
},
{
"cat": "Меблі",
"quarter": "2017 Q1",
"sales": 23724
},
{
"cat": "Меблі",
"quarter": "2017 Q2",
"sales": 45032
},
{
"cat": "Меблі",
"quarter": "2017 Q3",
"sales": 56283
},
{
"cat": "Меблі",
"quarter": "2017 Q4",
"sales": 90348
},
{
"cat": "Техніка",
"quarter": "2014 Q1",
"sales": 37263
},
{
"cat": "Техніка",
"quarter": "2014 Q2",
"sales": 27231
},
{
"cat": "Техніка",
"quarter": "2014 Q3",
"sales": 47751
},
{
"cat": "Техніка",
"quarter": "2014 Q4",
"sales": 63033
},
{
"cat": "Техніка",
"quarter": "2015 Q1",
"sales": 18418
},
{
"cat": "Техніка",
"quarter": "2015 Q2",
"sales": 29239
},
{
"cat": "Техніка",
"quarter": "2015 Q3",
"sales": 44913
},
{
"cat": "Техніка",
"quarter": "2015 Q4",
"sales": 70211
},
{
"cat": "Техніка",
"quarter": "2016 Q1",
"sales": 39447
},
{
"cat": "Техніка",
"quarter": "2016 Q2",
"sales": 60095
},
{
"cat": "Техніка",
"quarter": "2016 Q3",
"sales": 45825
},
{
"cat": "Техніка",
"quarter": "2016 Q4",
"sales": 80997
},
{
"cat": "Техніка",
"quarter": "2017 Q1",
"sales": 56189
},
{
"cat": "Техніка",
"quarter": "2017 Q2",
"sales": 43011
},
{
"cat": "Техніка",
"quarter": "2017 Q3",
"sales": 67772
},
{
"cat": "Техніка",
"quarter": "2017 Q4",
"sales": 104759
}
]
},
"mark": {
"type": "rect",
"stroke": "#ffffff",
"strokeWidth": 1
},
"encoding": {
"x": {
"field": "quarter",
"type": "ordinal",
"title": null,
"axis": {
"labelAngle": -45,
"labelFontSize": 10
}
},
"y": {
"field": "cat",
"type": "nominal",
"title": null,
"sort": [
"Меблі",
"Канцелярія",
"Техніка"
]
},
"color": {
"field": "sales",
"type": "quantitative",
"title": "продажі, $",
"scale": {
"scheme": "viridis"
}
},
"tooltip": [
{
"field": "cat",
"title": "категорія"
},
{
"field": "quarter",
"title": "квартал"
},
{
"field": "sales",
"title": "продажі, $",
"format": ","
}
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

+287
View File
@@ -0,0 +1,287 @@
{
"$schema": "https://vega.github.io/schema/vega-lite/v5.json",
"description": "Стало: обчислена маржа з нульовою віссю.",
"title": {
"text": "Маржа, % від продажів",
"subtitle": "Sample Superstore, 2014–2017",
"anchor": "start",
"fontSize": 16,
"subtitleFontSize": 12,
"subtitleColor": "#7a8894"
},
"width": 600,
"height": 300,
"data": {
"values": [
{
"month": "2014-01-01",
"margin": 17.21
},
{
"month": "2014-02-01",
"margin": 19.08
},
{
"month": "2014-03-01",
"margin": 0.9
},
{
"month": "2014-04-01",
"margin": 12.33
},
{
"month": "2014-05-01",
"margin": 11.58
},
{
"month": "2014-06-01",
"margin": 14.38
},
{
"month": "2014-07-01",
"margin": -2.48
},
{
"month": "2014-08-01",
"margin": 19.05
},
{
"month": "2014-09-01",
"margin": 10.18
},
{
"month": "2014-10-01",
"margin": 10.96
},
{
"month": "2014-11-01",
"margin": 11.82
},
{
"month": "2014-12-01",
"margin": 12.92
},
{
"month": "2015-01-01",
"margin": -18.05
},
{
"month": "2015-02-01",
"margin": 23.54
},
{
"month": "2015-03-01",
"margin": 25.13
},
{
"month": "2015-04-01",
"margin": 12.25
},
{
"month": "2015-05-01",
"margin": 15.49
},
{
"month": "2015-06-01",
"margin": 13.45
},
{
"month": "2015-07-01",
"margin": 11.43
},
{
"month": "2015-08-01",
"margin": 14.52
},
{
"month": "2015-09-01",
"margin": 12.71
},
{
"month": "2015-10-01",
"margin": 8.97
},
{
"month": "2015-11-01",
"margin": 16.42
},
{
"month": "2015-12-01",
"margin": 10.7
},
{
"month": "2016-01-01",
"margin": 15.23
},
{
"month": "2016-02-01",
"margin": 21.78
},
{
"month": "2016-03-01",
"margin": 6.98
},
{
"month": "2016-04-01",
"margin": 7.68
},
{
"month": "2016-05-01",
"margin": 15.2
},
{
"month": "2016-06-01",
"margin": 11.77
},
{
"month": "2016-07-01",
"margin": 11.29
},
{
"month": "2016-08-01",
"margin": 6.63
},
{
"month": "2016-09-01",
"margin": 12.71
},
{
"month": "2016-10-01",
"margin": 27.21
},
{
"month": "2016-11-01",
"margin": 5.05
},
{
"month": "2016-12-01",
"margin": 18.44
},
{
"month": "2017-01-01",
"margin": 16.24
},
{
"month": "2017-02-01",
"margin": 7.95
},
{
"month": "2017-03-01",
"margin": 25.06
},
{
"month": "2017-04-01",
"margin": 2.56
},
{
"month": "2017-05-01",
"margin": 14.33
},
{
"month": "2017-06-01",
"margin": 15.52
},
{
"month": "2017-07-01",
"margin": 15.36
},
{
"month": "2017-08-01",
"margin": 14.32
},
{
"month": "2017-09-01",
"margin": 12.51
},
{
"month": "2017-10-01",
"margin": 11.93
},
{
"month": "2017-11-01",
"margin": 8.18
},
{
"month": "2017-12-01",
"margin": 10.12
}
]
},
"layer": [
{
"mark": {
"type": "area",
"line": {
"color": "#003964",
"strokeWidth": 2
},
"color": "#cfe4ea",
"opacity": 0.9
},
"encoding": {
"x": {
"field": "month",
"type": "temporal",
"title": null,
"axis": {
"format": "%Y",
"tickCount": 4
}
},
"y": {
"field": "margin",
"type": "quantitative",
"title": null
}
}
},
{
"transform": [
{
"filter": "datum.margin < 0"
}
],
"mark": {
"type": "point",
"filled": true,
"size": 70,
"color": "#f15b43"
},
"encoding": {
"x": {
"field": "month",
"type": "temporal"
},
"y": {
"field": "margin",
"type": "quantitative"
},
"tooltip": [
{
"field": "month",
"type": "temporal",
"title": "місяць",
"format": "%b %Y"
},
{
"field": "margin",
"type": "quantitative",
"title": "маржа, %"
}
]
}
},
{
"mark": {
"type": "rule",
"color": "#8a97a3",
"strokeWidth": 1
},
"encoding": {
"y": {
"datum": 0
}
}
}
]
}