SPB Git

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%
4.8 KB · 121 lines html
Raw Blame History
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