feat(snippets): add charts category (6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Showing 6 changed files with +644 and −0
added
snippets/charts/chart-bar-race.html
+105 −0
@@ -0,0 +1,105 @@ | ||
| 1 | +<!-- | |
| 2 | + ============================================================ | |
| 3 | + SVGarden — https://www.svgarden.dev | |
| 4 | + Author : Simon-Pierre Boucher | |
| 5 | + Contact: contact@spboucher.ai | |
| 6 | + File : snippets/charts/chart-bar-race.html | |
| 7 | + Desc : Horizontal bars racing to their values with spring stagger | |
| 8 | + ============================================================ | |
| 9 | +--> | |
| 10 | +<!--svgarden | |
| 11 | +title: Bar race | |
| 12 | +slug: chart-bar-race | |
| 13 | +category: charts | |
| 14 | +tags: [chart, bars, stagger, spring] | |
| 15 | +difficulty: intermediate | |
| 16 | +techniques: ["--sg-i index variables", "scaleX", "cubic-bezier() overshoot", "animation-delay"] | |
| 17 | +how_it_works: > | |
| 18 | + Each bar is drawn at its final width in the markup — the data mapping is plain | |
| 19 | + linear scaling, width = value × 1.6 units — and then compressed to zero with | |
| 20 | + transform: scaleX(0), so the animation never has to know the value. The race | |
| 21 | + feel comes from two ingredients: every row carries its rank in a --sg-i custom | |
| 22 | + property, and one shared rule computes animation-delay: calc(var(--sg-i) × 18% | |
| 23 | + of the duration), so adding a sixth bar means adding one line of markup, not | |
| 24 | + one line of CSS. The overshoot is a cubic-bezier whose second control point | |
| 25 | + rises above 1, making each bar shoot slightly past its value and settle back | |
| 26 | + like a spring. Value labels reuse the same delay math plus most of the grow | |
| 27 | + time, so each number pops in just as its bar arrives. | |
| 28 | +customizable: | |
| 29 | + - { var: "--sg-color", label: "Bar color", type: color, default: "#7F77DD" } | |
| 30 | + - { var: "--sg-size", label: "Width", type: range, min: 160, max: 420, default: 260, unit: px } | |
| 31 | + - { var: "--sg-duration", label: "Speed", type: range, min: 0.4, max: 2.5, step: 0.1, default: 0.9, unit: s } | |
| 32 | +created: 2026-08-10 | |
| 33 | +--> | |
| 34 | +<div class="sg-chart-bar-race" style="--sg-color:#7F77DD; --sg-size:260px; --sg-duration:0.9s;"> | |
| 35 | + <svg viewBox="0 0 240 118" role="img" aria-label="Bar chart: Python 92, Rust 78, Go 64, TypeScript 55, C 38"> | |
| 36 | + <title>Language popularity bar chart</title> | |
| 37 | + <g class="sg-chart-bar-race-row" style="--sg-i:0"> | |
| 38 | + <text class="sg-chart-bar-race-name" x="48" y="20">Python</text> | |
| 39 | + <rect class="sg-chart-bar-race-bar" x="54" y="10" width="147" height="12" rx="3" /> | |
| 40 | + <text class="sg-chart-bar-race-val" x="206" y="20">92</text> | |
| 41 | + </g> | |
| 42 | + <g class="sg-chart-bar-race-row" style="--sg-i:1"> | |
| 43 | + <text class="sg-chart-bar-race-name" x="48" y="42">Rust</text> | |
| 44 | + <rect class="sg-chart-bar-race-bar" x="54" y="32" width="125" height="12" rx="3" /> | |
| 45 | + <text class="sg-chart-bar-race-val" x="184" y="42">78</text> | |
| 46 | + </g> | |
| 47 | + <g class="sg-chart-bar-race-row" style="--sg-i:2"> | |
| 48 | + <text class="sg-chart-bar-race-name" x="48" y="64">Go</text> | |
| 49 | + <rect class="sg-chart-bar-race-bar" x="54" y="54" width="102" height="12" rx="3" /> | |
| 50 | + <text class="sg-chart-bar-race-val" x="161" y="64">64</text> | |
| 51 | + </g> | |
| 52 | + <g class="sg-chart-bar-race-row" style="--sg-i:3"> | |
| 53 | + <text class="sg-chart-bar-race-name" x="48" y="86">TypeScript</text> | |
| 54 | + <rect class="sg-chart-bar-race-bar" x="54" y="76" width="88" height="12" rx="3" /> | |
| 55 | + <text class="sg-chart-bar-race-val" x="147" y="86">55</text> | |
| 56 | + </g> | |
| 57 | + <g class="sg-chart-bar-race-row" style="--sg-i:4"> | |
| 58 | + <text class="sg-chart-bar-race-name" x="48" y="108">C</text> | |
| 59 | + <rect class="sg-chart-bar-race-bar" x="54" y="98" width="61" height="12" rx="3" /> | |
| 60 | + <text class="sg-chart-bar-race-val" x="120" y="108">38</text> | |
| 61 | + </g> | |
| 62 | + </svg> | |
| 63 | +</div> | |
| 64 | +<style> | |
| 65 | + .sg-chart-bar-race { | |
| 66 | + width: var(--sg-size); | |
| 67 | + } | |
| 68 | + .sg-chart-bar-race svg { | |
| 69 | + display: block; | |
| 70 | + width: 100%; | |
| 71 | + font-family: inherit; | |
| 72 | + } | |
| 73 | + .sg-chart-bar-race-name { | |
| 74 | + font-size: 10px; | |
| 75 | + text-anchor: end; | |
| 76 | + fill: currentColor; | |
| 77 | + opacity: 0.75; | |
| 78 | + } | |
| 79 | + .sg-chart-bar-race-bar { | |
| 80 | + fill: var(--sg-color); | |
| 81 | + transform-box: fill-box; | |
| 82 | + transform-origin: left center; | |
| 83 | + animation: sg-chart-bar-race-grow var(--sg-duration) cubic-bezier(0.3, 1.35, 0.5, 1) both; | |
| 84 | + animation-delay: calc(var(--sg-i) * var(--sg-duration) * 0.18); | |
| 85 | + } | |
| 86 | + .sg-chart-bar-race-row:nth-of-type(2) .sg-chart-bar-race-bar { opacity: 0.85; } | |
| 87 | + .sg-chart-bar-race-row:nth-of-type(3) .sg-chart-bar-race-bar { opacity: 0.7; } | |
| 88 | + .sg-chart-bar-race-row:nth-of-type(4) .sg-chart-bar-race-bar { opacity: 0.55; } | |
| 89 | + .sg-chart-bar-race-row:nth-of-type(5) .sg-chart-bar-race-bar { opacity: 0.4; } | |
| 90 | + .sg-chart-bar-race-val { | |
| 91 | + font-size: 10px; | |
| 92 | + font-weight: 600; | |
| 93 | + fill: currentColor; | |
| 94 | + animation: sg-chart-bar-race-fade 0.3s ease both; | |
| 95 | + animation-delay: calc(var(--sg-i) * var(--sg-duration) * 0.18 + var(--sg-duration) * 0.75); | |
| 96 | + } | |
| 97 | + @keyframes sg-chart-bar-race-grow { | |
| 98 | + from { transform: scaleX(0); } | |
| 99 | + to { transform: scaleX(1); } | |
| 100 | + } | |
| 101 | + @keyframes sg-chart-bar-race-fade { | |
| 102 | + from { opacity: 0; } | |
| 103 | + to { opacity: 1; } | |
| 104 | + } | |
| 105 | +</style> | |
added
snippets/charts/chart-donut-conic.html
+98 −0
@@ -0,0 +1,98 @@ | ||
| 1 | +<!-- | |
| 2 | + ============================================================ | |
| 3 | + SVGarden — https://www.svgarden.dev | |
| 4 | + Author : Simon-Pierre Boucher | |
| 5 | + Contact: contact@spboucher.ai | |
| 6 | + File : snippets/charts/chart-donut-conic.html | |
| 7 | + Desc : Donut chart from conic-gradient with @property sweep-open | |
| 8 | + ============================================================ | |
| 9 | +--> | |
| 10 | +<!--svgarden | |
| 11 | +title: Conic donut | |
| 12 | +slug: chart-donut-conic | |
| 13 | +category: charts | |
| 14 | +tags: [chart, donut, conic-gradient, property] | |
| 15 | +difficulty: advanced | |
| 16 | +techniques: ["@property", "conic-gradient()", "calc()", "css mask"] | |
| 17 | +how_it_works: > | |
| 18 | + The whole donut is one conic-gradient: each segment is a pair of hard color | |
| 19 | + stops, and every stop position is a fraction of a single angle variable — | |
| 20 | + 38% of the data is simply 0.38 × --sg-chart-donut-sweep. Registering that | |
| 21 | + variable with @property and syntax '<angle>' is the key move: it tells the | |
| 22 | + browser the value is a real angle, not an opaque string, so a keyframe from | |
| 23 | + 0deg to 360deg interpolates smoothly and every segment sweeps open in | |
| 24 | + proportion at once. The hole is a radial-gradient mask punched through the | |
| 25 | + middle, which keeps the center transparent on any page background. Browsers | |
| 26 | + without @property skip the interpolation and simply show the finished donut. | |
| 27 | +support: > | |
| 28 | + Sweep-open animation needs @property (Chrome 85+, Safari 16.4+, Firefox 128+) | |
| 29 | + and segment tints use color-mix(); older browsers show the donut fully drawn. | |
| 30 | +customizable: | |
| 31 | + - { var: "--sg-color", label: "Base color", type: color, default: "#7F77DD" } | |
| 32 | + - { var: "--sg-size", label: "Size", type: range, min: 90, max: 240, default: 150, unit: px } | |
| 33 | +created: 2026-08-10 | |
| 34 | +--> | |
| 35 | +<div class="sg-chart-donut-conic" style="--sg-color:#7F77DD; --sg-size:150px;"> | |
| 36 | + <div class="sg-chart-donut-conic-ring" role="img" aria-label="Donut chart of browser share: Chrome 38%, Safari 28%, Edge 20%, Firefox 14%"></div> | |
| 37 | + <ul class="sg-chart-donut-conic-legend"> | |
| 38 | + <li>Chrome · 38%</li> | |
| 39 | + <li>Safari · 28%</li> | |
| 40 | + <li>Edge · 20%</li> | |
| 41 | + <li>Firefox · 14%</li> | |
| 42 | + </ul> | |
| 43 | +</div> | |
| 44 | +<style> | |
| 45 | + @property --sg-chart-donut-sweep { | |
| 46 | + syntax: '<angle>'; | |
| 47 | + inherits: false; | |
| 48 | + initial-value: 360deg; | |
| 49 | + } | |
| 50 | + .sg-chart-donut-conic { | |
| 51 | + display: flex; | |
| 52 | + align-items: center; | |
| 53 | + gap: 1.1em; | |
| 54 | + font-family: inherit; | |
| 55 | + } | |
| 56 | + .sg-chart-donut-conic-ring { | |
| 57 | + width: var(--sg-size); | |
| 58 | + aspect-ratio: 1; | |
| 59 | + border-radius: 50%; | |
| 60 | + --sg-chart-donut-sweep: 360deg; | |
| 61 | + background: conic-gradient( | |
| 62 | + color-mix(in srgb, var(--sg-color) 100%, transparent) 0 calc(var(--sg-chart-donut-sweep) * 0.38), | |
| 63 | + color-mix(in srgb, var(--sg-color) 65%, transparent) 0 calc(var(--sg-chart-donut-sweep) * 0.66), | |
| 64 | + color-mix(in srgb, var(--sg-color) 40%, transparent) 0 calc(var(--sg-chart-donut-sweep) * 0.86), | |
| 65 | + color-mix(in srgb, var(--sg-color) 22%, transparent) 0 var(--sg-chart-donut-sweep), | |
| 66 | + transparent var(--sg-chart-donut-sweep) 360deg | |
| 67 | + ); | |
| 68 | + -webkit-mask: radial-gradient(closest-side, transparent 61%, #000 62%); | |
| 69 | + mask: radial-gradient(closest-side, transparent 61%, #000 62%); | |
| 70 | + animation: sg-chart-donut-conic-open 1.1s cubic-bezier(0.25, 0.8, 0.3, 1); | |
| 71 | + } | |
| 72 | + @keyframes sg-chart-donut-conic-open { | |
| 73 | + from { --sg-chart-donut-sweep: 0deg; } | |
| 74 | + } | |
| 75 | + .sg-chart-donut-conic-legend { | |
| 76 | + list-style: none; | |
| 77 | + margin: 0; | |
| 78 | + padding: 0; | |
| 79 | + font-size: 13px; | |
| 80 | + display: grid; | |
| 81 | + gap: 0.35em; | |
| 82 | + } | |
| 83 | + .sg-chart-donut-conic-legend li { | |
| 84 | + display: flex; | |
| 85 | + align-items: center; | |
| 86 | + gap: 0.5em; | |
| 87 | + } | |
| 88 | + .sg-chart-donut-conic-legend li::before { | |
| 89 | + content: ''; | |
| 90 | + width: 10px; | |
| 91 | + height: 10px; | |
| 92 | + border-radius: 3px; | |
| 93 | + background: color-mix(in srgb, var(--sg-color) 100%, transparent); | |
| 94 | + } | |
| 95 | + .sg-chart-donut-conic-legend li:nth-child(2)::before { background: color-mix(in srgb, var(--sg-color) 65%, transparent); } | |
| 96 | + .sg-chart-donut-conic-legend li:nth-child(3)::before { background: color-mix(in srgb, var(--sg-color) 40%, transparent); } | |
| 97 | + .sg-chart-donut-conic-legend li:nth-child(4)::before { background: color-mix(in srgb, var(--sg-color) 22%, transparent); } | |
| 98 | +</style> | |
added
snippets/charts/chart-heat-calendar.html
+95 −0
@@ -0,0 +1,95 @@ | ||
| 1 | +<!-- | |
| 2 | + ============================================================ | |
| 3 | + SVGarden — https://www.svgarden.dev | |
| 4 | + Author : Simon-Pierre Boucher | |
| 5 | + Contact: contact@spboucher.ai | |
| 6 | + File : snippets/charts/chart-heat-calendar.html | |
| 7 | + Desc : GitHub-style heat calendar cascading in diagonally | |
| 8 | + ============================================================ | |
| 9 | +--> | |
| 10 | +<!--svgarden | |
| 11 | +title: Heat calendar | |
| 12 | +slug: chart-heat-calendar | |
| 13 | +category: charts | |
| 14 | +tags: [chart, heatmap, calendar, cascade] | |
| 15 | +difficulty: intermediate | |
| 16 | +techniques: ["--sg-i index variables", "nth-of-type indexing", "fill-opacity levels", "css rect geometry"] | |
| 17 | +how_it_works: > | |
| 18 | + Intensity is encoded the cheap way: every cell shares one fill (the accent | |
| 19 | + color) and only fill-opacity changes across four level classes, which keeps | |
| 20 | + the palette coherent on light and dark pages with zero color math. The | |
| 21 | + diagonal cascade is coordinate arithmetic in CSS: each row group carries | |
| 22 | + --sg-r, fourteen nth-of-type rules hand each column its --sg-c, and one rule | |
| 23 | + computes animation-delay: calc((var(--sg-r) + var(--sg-c)) × step) — cells on | |
| 24 | + the same anti-diagonal share r + c, so they pop together and the wave travels | |
| 25 | + corner to corner. Cell geometry (width, height, rx) lives in CSS too, which | |
| 26 | + is why each rect only needs x, y and a level class. | |
| 27 | +customizable: | |
| 28 | + - { var: "--sg-color", label: "Heat color", type: color, default: "#7F77DD" } | |
| 29 | + - { var: "--sg-size", label: "Width", type: range, min: 160, max: 420, default: 260, unit: px } | |
| 30 | + - { var: "--sg-duration", label: "Cascade", type: range, min: 0.5, max: 4, step: 0.1, default: 1.4, unit: s } | |
| 31 | +created: 2026-08-10 | |
| 32 | +--> | |
| 33 | +<div class="sg-chart-heat-calendar" style="--sg-color:#7F77DD; --sg-size:260px; --sg-duration:1.4s;"> | |
| 34 | + <svg viewBox="0 0 210 96" role="img" aria-label="Activity heat calendar, 14 weeks by 7 days, busiest midweek"> | |
| 35 | + <title>Contribution heat calendar</title> | |
| 36 | + <text class="sg-chart-heat-calendar-day" x="18" y="23">M</text> | |
| 37 | + <text class="sg-chart-heat-calendar-day" x="18" y="49">W</text> | |
| 38 | + <text class="sg-chart-heat-calendar-day" x="18" y="75">F</text> | |
| 39 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:0"><rect x="22" y="2" class="sg-chart-heat-calendar-l0"/><rect x="35" y="2" class="sg-chart-heat-calendar-l0"/><rect x="48" y="2" class="sg-chart-heat-calendar-l1"/><rect x="61" y="2" class="sg-chart-heat-calendar-l0"/><rect x="74" y="2" class="sg-chart-heat-calendar-l0"/><rect x="87" y="2" class="sg-chart-heat-calendar-l1"/><rect x="100" y="2" class="sg-chart-heat-calendar-l0"/><rect x="113" y="2" class="sg-chart-heat-calendar-l0"/><rect x="126" y="2" class="sg-chart-heat-calendar-l0"/><rect x="139" y="2" class="sg-chart-heat-calendar-l1"/><rect x="152" y="2" class="sg-chart-heat-calendar-l0"/><rect x="165" y="2" class="sg-chart-heat-calendar-l0"/><rect x="178" y="2" class="sg-chart-heat-calendar-l1"/><rect x="191" y="2" class="sg-chart-heat-calendar-l0"/></g> | |
| 40 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:1"><rect x="22" y="15" class="sg-chart-heat-calendar-l1"/><rect x="35" y="15" class="sg-chart-heat-calendar-l2"/><rect x="48" y="15" class="sg-chart-heat-calendar-l1"/><rect x="61" y="15" class="sg-chart-heat-calendar-l2"/><rect x="74" y="15" class="sg-chart-heat-calendar-l3"/><rect x="87" y="15" class="sg-chart-heat-calendar-l1"/><rect x="100" y="15" class="sg-chart-heat-calendar-l2"/><rect x="113" y="15" class="sg-chart-heat-calendar-l1"/><rect x="126" y="15" class="sg-chart-heat-calendar-l3"/><rect x="139" y="15" class="sg-chart-heat-calendar-l2"/><rect x="152" y="15" class="sg-chart-heat-calendar-l1"/><rect x="165" y="15" class="sg-chart-heat-calendar-l2"/><rect x="178" y="15" class="sg-chart-heat-calendar-l2"/><rect x="191" y="15" class="sg-chart-heat-calendar-l1"/></g> | |
| 41 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:2"><rect x="22" y="28" class="sg-chart-heat-calendar-l2"/><rect x="35" y="28" class="sg-chart-heat-calendar-l1"/><rect x="48" y="28" class="sg-chart-heat-calendar-l3"/><rect x="61" y="28" class="sg-chart-heat-calendar-l2"/><rect x="74" y="28" class="sg-chart-heat-calendar-l1"/><rect x="87" y="28" class="sg-chart-heat-calendar-l2"/><rect x="100" y="28" class="sg-chart-heat-calendar-l3"/><rect x="113" y="28" class="sg-chart-heat-calendar-l2"/><rect x="126" y="28" class="sg-chart-heat-calendar-l1"/><rect x="139" y="28" class="sg-chart-heat-calendar-l3"/><rect x="152" y="28" class="sg-chart-heat-calendar-l2"/><rect x="165" y="28" class="sg-chart-heat-calendar-l1"/><rect x="178" y="28" class="sg-chart-heat-calendar-l3"/><rect x="191" y="28" class="sg-chart-heat-calendar-l2"/></g> | |
| 42 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:3"><rect x="22" y="41" class="sg-chart-heat-calendar-l1"/><rect x="35" y="41" class="sg-chart-heat-calendar-l3"/><rect x="48" y="41" class="sg-chart-heat-calendar-l2"/><rect x="61" y="41" class="sg-chart-heat-calendar-l3"/><rect x="74" y="41" class="sg-chart-heat-calendar-l2"/><rect x="87" y="41" class="sg-chart-heat-calendar-l3"/><rect x="100" y="41" class="sg-chart-heat-calendar-l1"/><rect x="113" y="41" class="sg-chart-heat-calendar-l3"/><rect x="126" y="41" class="sg-chart-heat-calendar-l2"/><rect x="139" y="41" class="sg-chart-heat-calendar-l2"/><rect x="152" y="41" class="sg-chart-heat-calendar-l3"/><rect x="165" y="41" class="sg-chart-heat-calendar-l3"/><rect x="178" y="41" class="sg-chart-heat-calendar-l1"/><rect x="191" y="41" class="sg-chart-heat-calendar-l3"/></g> | |
| 43 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:4"><rect x="22" y="54" class="sg-chart-heat-calendar-l2"/><rect x="35" y="54" class="sg-chart-heat-calendar-l2"/><rect x="48" y="54" class="sg-chart-heat-calendar-l1"/><rect x="61" y="54" class="sg-chart-heat-calendar-l1"/><rect x="74" y="54" class="sg-chart-heat-calendar-l3"/><rect x="87" y="54" class="sg-chart-heat-calendar-l2"/><rect x="100" y="54" class="sg-chart-heat-calendar-l2"/><rect x="113" y="54" class="sg-chart-heat-calendar-l3"/><rect x="126" y="54" class="sg-chart-heat-calendar-l1"/><rect x="139" y="54" class="sg-chart-heat-calendar-l2"/><rect x="152" y="54" class="sg-chart-heat-calendar-l3"/><rect x="165" y="54" class="sg-chart-heat-calendar-l1"/><rect x="178" y="54" class="sg-chart-heat-calendar-l2"/><rect x="191" y="54" class="sg-chart-heat-calendar-l3"/></g> | |
| 44 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:5"><rect x="22" y="67" class="sg-chart-heat-calendar-l3"/><rect x="35" y="67" class="sg-chart-heat-calendar-l1"/><rect x="48" y="67" class="sg-chart-heat-calendar-l2"/><rect x="61" y="67" class="sg-chart-heat-calendar-l2"/><rect x="74" y="67" class="sg-chart-heat-calendar-l2"/><rect x="87" y="67" class="sg-chart-heat-calendar-l1"/><rect x="100" y="67" class="sg-chart-heat-calendar-l3"/><rect x="113" y="67" class="sg-chart-heat-calendar-l1"/><rect x="126" y="67" class="sg-chart-heat-calendar-l2"/><rect x="139" y="67" class="sg-chart-heat-calendar-l1"/><rect x="152" y="67" class="sg-chart-heat-calendar-l1"/><rect x="165" y="67" class="sg-chart-heat-calendar-l2"/><rect x="178" y="67" class="sg-chart-heat-calendar-l3"/><rect x="191" y="67" class="sg-chart-heat-calendar-l1"/></g> | |
| 45 | + <g class="sg-chart-heat-calendar-row" style="--sg-r:6"><rect x="22" y="80" class="sg-chart-heat-calendar-l0"/><rect x="35" y="80" class="sg-chart-heat-calendar-l1"/><rect x="48" y="80" class="sg-chart-heat-calendar-l0"/><rect x="61" y="80" class="sg-chart-heat-calendar-l0"/><rect x="74" y="80" class="sg-chart-heat-calendar-l1"/><rect x="87" y="80" class="sg-chart-heat-calendar-l0"/><rect x="100" y="80" class="sg-chart-heat-calendar-l0"/><rect x="113" y="80" class="sg-chart-heat-calendar-l1"/><rect x="126" y="80" class="sg-chart-heat-calendar-l0"/><rect x="139" y="80" class="sg-chart-heat-calendar-l0"/><rect x="152" y="80" class="sg-chart-heat-calendar-l2"/><rect x="165" y="80" class="sg-chart-heat-calendar-l0"/><rect x="178" y="80" class="sg-chart-heat-calendar-l0"/><rect x="191" y="80" class="sg-chart-heat-calendar-l1"/></g> | |
| 46 | + </svg> | |
| 47 | +</div> | |
| 48 | +<style> | |
| 49 | + .sg-chart-heat-calendar { | |
| 50 | + width: var(--sg-size); | |
| 51 | + } | |
| 52 | + .sg-chart-heat-calendar svg { | |
| 53 | + display: block; | |
| 54 | + width: 100%; | |
| 55 | + font-family: inherit; | |
| 56 | + } | |
| 57 | + .sg-chart-heat-calendar-day { | |
| 58 | + font-size: 7px; | |
| 59 | + fill: currentColor; | |
| 60 | + opacity: 0.55; | |
| 61 | + text-anchor: end; | |
| 62 | + } | |
| 63 | + .sg-chart-heat-calendar rect { | |
| 64 | + width: 10px; | |
| 65 | + height: 10px; | |
| 66 | + rx: 2px; | |
| 67 | + fill: var(--sg-color); | |
| 68 | + transform-box: fill-box; | |
| 69 | + transform-origin: center; | |
| 70 | + animation: sg-chart-heat-calendar-pop 0.35s ease-out both; | |
| 71 | + animation-delay: calc((var(--sg-r) + var(--sg-c)) * var(--sg-duration) / 26); | |
| 72 | + } | |
| 73 | + .sg-chart-heat-calendar rect:nth-of-type(1) { --sg-c: 0; } | |
| 74 | + .sg-chart-heat-calendar rect:nth-of-type(2) { --sg-c: 1; } | |
| 75 | + .sg-chart-heat-calendar rect:nth-of-type(3) { --sg-c: 2; } | |
| 76 | + .sg-chart-heat-calendar rect:nth-of-type(4) { --sg-c: 3; } | |
| 77 | + .sg-chart-heat-calendar rect:nth-of-type(5) { --sg-c: 4; } | |
| 78 | + .sg-chart-heat-calendar rect:nth-of-type(6) { --sg-c: 5; } | |
| 79 | + .sg-chart-heat-calendar rect:nth-of-type(7) { --sg-c: 6; } | |
| 80 | + .sg-chart-heat-calendar rect:nth-of-type(8) { --sg-c: 7; } | |
| 81 | + .sg-chart-heat-calendar rect:nth-of-type(9) { --sg-c: 8; } | |
| 82 | + .sg-chart-heat-calendar rect:nth-of-type(10) { --sg-c: 9; } | |
| 83 | + .sg-chart-heat-calendar rect:nth-of-type(11) { --sg-c: 10; } | |
| 84 | + .sg-chart-heat-calendar rect:nth-of-type(12) { --sg-c: 11; } | |
| 85 | + .sg-chart-heat-calendar rect:nth-of-type(13) { --sg-c: 12; } | |
| 86 | + .sg-chart-heat-calendar rect:nth-of-type(14) { --sg-c: 13; } | |
| 87 | + .sg-chart-heat-calendar-l0 { fill-opacity: 0.12; } | |
| 88 | + .sg-chart-heat-calendar-l1 { fill-opacity: 0.35; } | |
| 89 | + .sg-chart-heat-calendar-l2 { fill-opacity: 0.6; } | |
| 90 | + .sg-chart-heat-calendar-l3 { fill-opacity: 0.9; } | |
| 91 | + @keyframes sg-chart-heat-calendar-pop { | |
| 92 | + from { opacity: 0; transform: scale(0.4); } | |
| 93 | + to { opacity: 1; transform: scale(1); } | |
| 94 | + } | |
| 95 | +</style> | |
added
snippets/charts/chart-line-draw-tooltip.html
+120 −0
@@ -0,0 +1,120 @@ | ||
| 1 | +<!-- | |
| 2 | + ============================================================ | |
| 3 | + SVGarden — https://www.svgarden.dev | |
| 4 | + Author : Simon-Pierre Boucher | |
| 5 | + Contact: contact@spboucher.ai | |
| 6 | + File : snippets/charts/chart-line-draw-tooltip.html | |
| 7 | + Desc : Line chart that draws in, with a snapping crosshair tooltip | |
| 8 | + ============================================================ | |
| 9 | +--> | |
| 10 | +<!--svgarden | |
| 11 | +title: Line chart + crosshair | |
| 12 | +slug: chart-line-draw-tooltip | |
| 13 | +category: charts | |
| 14 | +tags: [chart, line, tooltip, crosshair, js] | |
| 15 | +difficulty: advanced | |
| 16 | +techniques: [pathLength, pointer-events, getBoundingClientRect, "svg coordinate mapping"] | |
| 17 | +how_it_works: > | |
| 18 | + The polyline's points are the data, pre-mapped into viewBox units (x = 20 + | |
| 19 | + index × 22, y = 104 − value × 0.9), and pathLength="100" lets the usual dash | |
| 20 | + trick draw it in without measuring. The interesting part is the tooltip math: | |
| 21 | + pointer events give you pixel coordinates, but the chart lives in viewBox | |
| 22 | + units, so the script converts with x = (clientX − rect.left) × 240 ÷ | |
| 23 | + rect.width — the one line that makes the crosshair correct at any rendered | |
| 24 | + size. It then snaps to the nearest hardcoded data point instead of | |
| 25 | + interpolating, which is both honest (you can only inspect real samples) and | |
| 26 | + cheap: a linear scan over ten points per move. | |
| 27 | +customizable: | |
| 28 | + - { var: "--sg-color", label: "Line color", type: color, default: "#7F77DD" } | |
| 29 | + - { var: "--sg-size", label: "Width", type: range, min: 180, max: 460, default: 280, unit: px } | |
| 30 | +created: 2026-08-10 | |
| 31 | +--> | |
| 32 | +<div class="sg-chart-line-draw-tooltip" style="--sg-color:#7F77DD; --sg-size:280px;"> | |
| 33 | + <svg viewBox="0 0 240 120" role="img" aria-label="Line chart of monthly values rising from 24 to 82"> | |
| 34 | + <title>Monthly values line chart</title> | |
| 35 | + <g class="sg-chart-line-draw-tooltip-grid"> | |
| 36 | + <line x1="20" y1="30" x2="218" y2="30" /> | |
| 37 | + <line x1="20" y1="56" x2="218" y2="56" /> | |
| 38 | + <line x1="20" y1="82" x2="218" y2="82" /> | |
| 39 | + <line x1="20" y1="104" x2="218" y2="104" /> | |
| 40 | + </g> | |
| 41 | + <polyline class="sg-chart-line-draw-tooltip-line" pathLength="100" | |
| 42 | + points="20,82 42,72 64,77 86,61 108,66 130,52 152,45 174,50 196,37 218,30" /> | |
| 43 | + <g class="sg-chart-line-draw-tooltip-hud"> | |
| 44 | + <line class="sg-chart-line-draw-tooltip-cross" x1="20" y1="14" x2="20" y2="104" /> | |
| 45 | + <circle class="sg-chart-line-draw-tooltip-dot" cx="20" cy="82" r="3.5" /> | |
| 46 | + <text class="sg-chart-line-draw-tooltip-lbl" x="20" y="73">24</text> | |
| 47 | + </g> | |
| 48 | + </svg> | |
| 49 | +</div> | |
| 50 | +<script> | |
| 51 | + document.querySelectorAll('.sg-chart-line-draw-tooltip:not([data-sg-init])').forEach(function (root) { | |
| 52 | + root.setAttribute('data-sg-init', ''); | |
| 53 | + var svg = root.querySelector('svg'); | |
| 54 | + var hud = root.querySelector('.sg-chart-line-draw-tooltip-hud'); | |
| 55 | + var cross = root.querySelector('.sg-chart-line-draw-tooltip-cross'); | |
| 56 | + var dot = root.querySelector('.sg-chart-line-draw-tooltip-dot'); | |
| 57 | + var lbl = root.querySelector('.sg-chart-line-draw-tooltip-lbl'); | |
| 58 | + var pts = [[20,82,24],[42,72,36],[64,77,30],[86,61,48],[108,66,42],[130,52,58],[152,45,66],[174,50,60],[196,37,74],[218,30,82]]; | |
| 59 | + svg.addEventListener('pointermove', function (e) { | |
| 60 | + var r = svg.getBoundingClientRect(); | |
| 61 | + var x = (e.clientX - r.left) * 240 / r.width; | |
| 62 | + var best = pts[0]; | |
| 63 | + for (var i = 1; i < pts.length; i++) if (Math.abs(pts[i][0] - x) < Math.abs(best[0] - x)) best = pts[i]; | |
| 64 | + cross.setAttribute('x1', best[0]); cross.setAttribute('x2', best[0]); | |
| 65 | + dot.setAttribute('cx', best[0]); dot.setAttribute('cy', best[1]); | |
| 66 | + lbl.setAttribute('x', Math.max(28, Math.min(210, best[0]))); | |
| 67 | + lbl.setAttribute('y', best[1] - 9); | |
| 68 | + lbl.textContent = best[2]; | |
| 69 | + hud.style.opacity = 1; | |
| 70 | + }); | |
| 71 | + svg.addEventListener('pointerleave', function () { hud.style.opacity = 0; }); | |
| 72 | + }); | |
| 73 | +</script> | |
| 74 | +<style> | |
| 75 | + .sg-chart-line-draw-tooltip { | |
| 76 | + width: var(--sg-size); | |
| 77 | + } | |
| 78 | + .sg-chart-line-draw-tooltip svg { | |
| 79 | + display: block; | |
| 80 | + width: 100%; | |
| 81 | + font-family: inherit; | |
| 82 | + touch-action: none; | |
| 83 | + } | |
| 84 | + .sg-chart-line-draw-tooltip-grid line { | |
| 85 | + stroke: rgba(128, 128, 128, 0.3); | |
| 86 | + stroke-width: 1; | |
| 87 | + stroke-dasharray: 2 4; | |
| 88 | + } | |
| 89 | + .sg-chart-line-draw-tooltip-line { | |
| 90 | + fill: none; | |
| 91 | + stroke: var(--sg-color); | |
| 92 | + stroke-width: 2.5; | |
| 93 | + stroke-linecap: round; | |
| 94 | + stroke-linejoin: round; | |
| 95 | + stroke-dasharray: 100; | |
| 96 | + stroke-dashoffset: 100; | |
| 97 | + animation: sg-chart-line-draw-tooltip-in 1.2s ease-out forwards; | |
| 98 | + } | |
| 99 | + @keyframes sg-chart-line-draw-tooltip-in { | |
| 100 | + to { stroke-dashoffset: 0; } | |
| 101 | + } | |
| 102 | + .sg-chart-line-draw-tooltip-hud { | |
| 103 | + opacity: 0; | |
| 104 | + transition: opacity 0.15s ease; | |
| 105 | + pointer-events: none; | |
| 106 | + } | |
| 107 | + .sg-chart-line-draw-tooltip-cross { | |
| 108 | + stroke: rgba(128, 128, 128, 0.5); | |
| 109 | + stroke-width: 1; | |
| 110 | + } | |
| 111 | + .sg-chart-line-draw-tooltip-dot { | |
| 112 | + fill: var(--sg-color); | |
| 113 | + } | |
| 114 | + .sg-chart-line-draw-tooltip-lbl { | |
| 115 | + font-size: 10px; | |
| 116 | + font-weight: 600; | |
| 117 | + fill: currentColor; | |
| 118 | + text-anchor: middle; | |
| 119 | + } | |
| 120 | +</style> | |
added
snippets/charts/chart-radar-pulse.html
+114 −0
@@ -0,0 +1,114 @@ | ||
| 1 | +<!-- | |
| 2 | + ============================================================ | |
| 3 | + SVGarden — https://www.svgarden.dev | |
| 4 | + Author : Simon-Pierre Boucher | |
| 5 | + Contact: contact@spboucher.ai | |
| 6 | + File : snippets/charts/chart-radar-pulse.html | |
| 7 | + Desc : Radar chart whose data area breathes and vertices ping | |
| 8 | + ============================================================ | |
| 9 | +--> | |
| 10 | +<!--svgarden | |
| 11 | +title: Radar pulse | |
| 12 | +slug: chart-radar-pulse | |
| 13 | +category: charts | |
| 14 | +tags: [chart, radar, pulse, polygon] | |
| 15 | +difficulty: intermediate | |
| 16 | +techniques: [transform-box, "polar → cartesian mapping", "animation-delay", scale] | |
| 17 | +how_it_works: > | |
| 18 | + A radar chart is pure polar-to-cartesian mapping: each axis sits at 72° | |
| 19 | + intervals (five axes), and a data point at strength v becomes the vertex | |
| 20 | + (cx + v·R·cos θ, cy + v·R·sin θ) — every polygon here, rings included, was | |
| 21 | + computed with that one formula. The rings are just the same pentagon at four | |
| 22 | + radii, so the "grid" costs four polygons instead of trigonometry at runtime. | |
| 23 | + The living feel comes from transform-box: fill-box on the data polygon, which | |
| 24 | + makes a tiny scale oscillation breathe around the shape's own centroid rather | |
| 25 | + than the SVG corner, while the vertex dots reuse one ping animation with | |
| 26 | + staggered delays so attention sweeps around the chart. | |
| 27 | +customizable: | |
| 28 | + - { var: "--sg-color", label: "Area color", type: color, default: "#7F77DD" } | |
| 29 | + - { var: "--sg-size", label: "Size", type: range, min: 110, max: 300, default: 170, unit: px } | |
| 30 | + - { var: "--sg-duration", label: "Speed", type: range, min: 1, max: 6, step: 0.5, default: 2.5, unit: s } | |
| 31 | +created: 2026-08-10 | |
| 32 | +--> | |
| 33 | +<div class="sg-chart-radar-pulse" style="--sg-color:#7F77DD; --sg-size:170px; --sg-duration:2.5s;"> | |
| 34 | + <svg viewBox="0 0 140 140" role="img" aria-label="Radar chart of five skills: speed 90, power 65, defense 80, stamina 55, accuracy 70 out of 100"> | |
| 35 | + <title>Skill radar chart</title> | |
| 36 | + <g class="sg-chart-radar-pulse-rings"> | |
| 37 | + <polygon points="70,18 119.5,53.9 100.6,112.1 39.4,112.1 20.5,53.9" /> | |
| 38 | + <polygon points="70,31 107.1,58 92.9,101.6 47.1,101.6 32.9,58" /> | |
| 39 | + <polygon points="70,44 94.7,62 85.3,91 54.7,91 45.3,62" /> | |
| 40 | + <polygon points="70,57 82.4,66 77.6,80.5 62.4,80.5 57.6,66" /> | |
| 41 | + <line x1="70" y1="70" x2="70" y2="18" /> | |
| 42 | + <line x1="70" y1="70" x2="119.5" y2="53.9" /> | |
| 43 | + <line x1="70" y1="70" x2="100.6" y2="112.1" /> | |
| 44 | + <line x1="70" y1="70" x2="39.4" y2="112.1" /> | |
| 45 | + <line x1="70" y1="70" x2="20.5" y2="53.9" /> | |
| 46 | + </g> | |
| 47 | + <polygon class="sg-chart-radar-pulse-area" points="70,23.2 102.1,59.6 94.5,103.7 53.2,93.1 35.4,58.8" /> | |
| 48 | + <g class="sg-chart-radar-pulse-dots"> | |
| 49 | + <circle cx="70" cy="23.2" r="3" /> | |
| 50 | + <circle cx="102.1" cy="59.6" r="3" /> | |
| 51 | + <circle cx="94.5" cy="103.7" r="3" /> | |
| 52 | + <circle cx="53.2" cy="93.1" r="3" /> | |
| 53 | + <circle cx="35.4" cy="58.8" r="3" /> | |
| 54 | + </g> | |
| 55 | + <g class="sg-chart-radar-pulse-labels"> | |
| 56 | + <text x="70" y="12">spd</text> | |
| 57 | + <text x="128" y="52">pwr</text> | |
| 58 | + <text x="105" y="122">def</text> | |
| 59 | + <text x="35" y="122">sta</text> | |
| 60 | + <text x="12" y="52">acc</text> | |
| 61 | + </g> | |
| 62 | + </svg> | |
| 63 | +</div> | |
| 64 | +<style> | |
| 65 | + .sg-chart-radar-pulse { | |
| 66 | + width: var(--sg-size); | |
| 67 | + } | |
| 68 | + .sg-chart-radar-pulse svg { | |
| 69 | + display: block; | |
| 70 | + width: 100%; | |
| 71 | + font-family: inherit; | |
| 72 | + } | |
| 73 | + .sg-chart-radar-pulse-rings polygon, | |
| 74 | + .sg-chart-radar-pulse-rings line { | |
| 75 | + fill: none; | |
| 76 | + stroke: rgba(128, 128, 128, 0.3); | |
| 77 | + stroke-width: 1; | |
| 78 | + } | |
| 79 | + .sg-chart-radar-pulse-area { | |
| 80 | + fill: var(--sg-color); | |
| 81 | + fill-opacity: 0.35; | |
| 82 | + stroke: var(--sg-color); | |
| 83 | + stroke-width: 2; | |
| 84 | + stroke-linejoin: round; | |
| 85 | + transform-box: fill-box; | |
| 86 | + transform-origin: center; | |
| 87 | + animation: sg-chart-radar-pulse-breathe var(--sg-duration) ease-in-out infinite; | |
| 88 | + } | |
| 89 | + .sg-chart-radar-pulse-dots circle { | |
| 90 | + fill: var(--sg-color); | |
| 91 | + transform-box: fill-box; | |
| 92 | + transform-origin: center; | |
| 93 | + animation: sg-chart-radar-pulse-ping var(--sg-duration) ease-in-out infinite; | |
| 94 | + } | |
| 95 | + .sg-chart-radar-pulse-dots circle:nth-of-type(2) { animation-delay: calc(var(--sg-duration) * 0.2); } | |
| 96 | + .sg-chart-radar-pulse-dots circle:nth-of-type(3) { animation-delay: calc(var(--sg-duration) * 0.4); } | |
| 97 | + .sg-chart-radar-pulse-dots circle:nth-of-type(4) { animation-delay: calc(var(--sg-duration) * 0.6); } | |
| 98 | + .sg-chart-radar-pulse-dots circle:nth-of-type(5) { animation-delay: calc(var(--sg-duration) * 0.8); } | |
| 99 | + .sg-chart-radar-pulse-labels text { | |
| 100 | + font-size: 9px; | |
| 101 | + fill: currentColor; | |
| 102 | + opacity: 0.65; | |
| 103 | + text-anchor: middle; | |
| 104 | + } | |
| 105 | + @keyframes sg-chart-radar-pulse-breathe { | |
| 106 | + 0%, 100% { transform: scale(1); fill-opacity: 0.3; } | |
| 107 | + 50% { transform: scale(1.045); fill-opacity: 0.5; } | |
| 108 | + } | |
| 109 | + @keyframes sg-chart-radar-pulse-ping { | |
| 110 | + 0%, 55%, 100% { transform: scale(1); opacity: 1; } | |
| 111 | + 70% { transform: scale(1.9); opacity: 0.4; } | |
| 112 | + 85% { transform: scale(1); opacity: 1; } | |
| 113 | + } | |
| 114 | +</style> | |
added
snippets/charts/chart-sparkline-live.html
+112 −0
@@ -0,0 +1,112 @@ | ||
| 1 | +<!-- | |
| 2 | + ============================================================ | |
| 3 | + SVGarden — https://www.svgarden.dev | |
| 4 | + Author : Simon-Pierre Boucher | |
| 5 | + Contact: contact@spboucher.ai | |
| 6 | + File : snippets/charts/chart-sparkline-live.html | |
| 7 | + Desc : Live sparkline — new points stream in, old ones slide out | |
| 8 | + ============================================================ | |
| 9 | +--> | |
| 10 | +<!--svgarden | |
| 11 | +title: Live sparkline | |
| 12 | +slug: chart-sparkline-live | |
| 13 | +category: charts | |
| 14 | +tags: [chart, sparkline, live, streaming, js] | |
| 15 | +difficulty: advanced | |
| 16 | +techniques: ["fixed-window data buffer", "points attribute rewrite", "transition retrigger", prefers-reduced-motion] | |
| 17 | +how_it_works: > | |
| 18 | + The script keeps a fixed-length array as a sliding window: every tick pushes a | |
| 19 | + new random-walk value, shifts the oldest out, and rewrites the polyline's | |
| 20 | + points attribute from scratch — with 22 points that is far cheaper than any | |
| 21 | + clever incremental DOM surgery. The slide illusion is a transition retrigger | |
| 22 | + trick: after each rewrite the group is jumped 10 units right with transitions | |
| 23 | + disabled (the new geometry then overlaps the old frame exactly), and one | |
| 24 | + requestAnimationFrame later the transition is re-enabled and the group eases | |
| 25 | + back to zero. The tick delay is re-read from --sg-duration on every cycle via | |
| 26 | + a setTimeout chain, so the speed slider takes effect on the very next point, | |
| 27 | + and prefers-reduced-motion stops the stream after the first paint. | |
| 28 | +customizable: | |
| 29 | + - { var: "--sg-color", label: "Line color", type: color, default: "#7F77DD" } | |
| 30 | + - { var: "--sg-size", label: "Width", type: range, min: 140, max: 380, default: 220, unit: px } | |
| 31 | + - { var: "--sg-duration", label: "Tick every", type: range, min: 0.3, max: 2, step: 0.1, default: 0.7, unit: s } | |
| 32 | +created: 2026-08-10 | |
| 33 | +--> | |
| 34 | +<div class="sg-chart-sparkline-live" style="--sg-color:#7F77DD; --sg-size:220px; --sg-duration:0.7s;"> | |
| 35 | + <svg viewBox="0 0 220 60" role="img" aria-label="Live-updating sparkline of streaming values"> | |
| 36 | + <title>Live sparkline</title> | |
| 37 | + <line class="sg-chart-sparkline-live-base" x1="5" y1="58" x2="215" y2="58" /> | |
| 38 | + <g class="sg-chart-sparkline-live-win"> | |
| 39 | + <polygon class="sg-chart-sparkline-live-area" points="" /> | |
| 40 | + <polyline class="sg-chart-sparkline-live-line" points="" /> | |
| 41 | + </g> | |
| 42 | + <circle class="sg-chart-sparkline-live-dot" cx="215" cy="30" r="3" /> | |
| 43 | + </svg> | |
| 44 | +</div> | |
| 45 | +<script> | |
| 46 | + document.querySelectorAll('.sg-chart-sparkline-live:not([data-sg-init])').forEach(function (root) { | |
| 47 | + root.setAttribute('data-sg-init', ''); | |
| 48 | + var line = root.querySelector('.sg-chart-sparkline-live-line'); | |
| 49 | + var area = root.querySelector('.sg-chart-sparkline-live-area'); | |
| 50 | + var win = root.querySelector('.sg-chart-sparkline-live-win'); | |
| 51 | + var dot = root.querySelector('.sg-chart-sparkline-live-dot'); | |
| 52 | + var N = 22, data = [30]; | |
| 53 | + for (var i = 1; i < N; i++) data.push(Math.max(8, Math.min(52, data[i - 1] + Math.random() * 14 - 7))); | |
| 54 | + function render() { | |
| 55 | + var pts = data.map(function (v, i) { return (5 + i * 10) + ',' + (58 - v).toFixed(1); }).join(' '); | |
| 56 | + line.setAttribute('points', pts); | |
| 57 | + area.setAttribute('points', pts + ' 215,58 5,58'); | |
| 58 | + dot.setAttribute('cy', (58 - data[N - 1]).toFixed(1)); | |
| 59 | + } | |
| 60 | + render(); | |
| 61 | + if (matchMedia('(prefers-reduced-motion: reduce)').matches) return; | |
| 62 | + function tick() { | |
| 63 | + data.push(Math.max(8, Math.min(52, data[N - 1] + Math.random() * 14 - 7))); | |
| 64 | + data.shift(); | |
| 65 | + win.style.transition = 'none'; | |
| 66 | + render(); | |
| 67 | + win.style.transform = 'translateX(10px)'; | |
| 68 | + requestAnimationFrame(function () { | |
| 69 | + var s = parseFloat(getComputedStyle(root).getPropertyValue('--sg-duration')) || 0.7; | |
| 70 | + win.style.transition = 'transform ' + s + 's linear'; | |
| 71 | + win.style.transform = 'translateX(0)'; | |
| 72 | + setTimeout(tick, s * 1000); | |
| 73 | + }); | |
| 74 | + } | |
| 75 | + tick(); | |
| 76 | + }); | |
| 77 | +</script> | |
| 78 | +<style> | |
| 79 | + .sg-chart-sparkline-live { | |
| 80 | + width: var(--sg-size); | |
| 81 | + } | |
| 82 | + .sg-chart-sparkline-live svg { | |
| 83 | + display: block; | |
| 84 | + width: 100%; | |
| 85 | + overflow: hidden; | |
| 86 | + } | |
| 87 | + .sg-chart-sparkline-live-base { | |
| 88 | + stroke: rgba(128, 128, 128, 0.3); | |
| 89 | + stroke-width: 1; | |
| 90 | + } | |
| 91 | + .sg-chart-sparkline-live-line { | |
| 92 | + fill: none; | |
| 93 | + stroke: var(--sg-color); | |
| 94 | + stroke-width: 2; | |
| 95 | + stroke-linecap: round; | |
| 96 | + stroke-linejoin: round; | |
| 97 | + } | |
| 98 | + .sg-chart-sparkline-live-area { | |
| 99 | + fill: var(--sg-color); | |
| 100 | + opacity: 0.12; | |
| 101 | + } | |
| 102 | + .sg-chart-sparkline-live-dot { | |
| 103 | + fill: var(--sg-color); | |
| 104 | + transform-box: fill-box; | |
| 105 | + transform-origin: center; | |
| 106 | + animation: sg-chart-sparkline-live-pulse 1.4s ease-in-out infinite; | |
| 107 | + } | |
| 108 | + @keyframes sg-chart-sparkline-live-pulse { | |
| 109 | + 0%, 100% { transform: scale(1); opacity: 1; } | |
| 110 | + 50% { transform: scale(1.7); opacity: 0.55; } | |
| 111 | + } | |
| 112 | +</style> | |
| 113 | ||