spb/svgarden Public
SVGarden — searchable bank of 74 self-contained SVG+CSS animation snippets (svgarden.dev)
HTML 79.2%
Astro 10.3%
JavaScript 6%
CSS 3.7%
Shell 0.8%
1<!--2 ============================================================3 SVGarden — https://www.svgarden.dev4 Author : Simon-Pierre Boucher5 Contact: contact@spboucher.ai6 File : snippets/charts/chart-line-draw-tooltip.html7 Desc : Line chart that draws in, with a snapping crosshair tooltip8 ============================================================9-->10<!--svgarden11title: Line chart + crosshair12slug: chart-line-draw-tooltip13category: charts14tags: [chart, line, tooltip, crosshair, js]15difficulty: advanced16techniques: [pathLength, pointer-events, getBoundingClientRect, "svg coordinate mapping"]17how_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 dash20 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 viewBox22 units, so the script converts with x = (clientX − rect.left) × 240 ÷23 rect.width — the one line that makes the crosshair correct at any rendered24 size. It then snaps to the nearest hardcoded data point instead of25 interpolating, which is both honest (you can only inspect real samples) and26 cheap: a linear scan over ten points per move.27customizable: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 }30created: 2026-08-1031-->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>121