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%
9.6 KB · 96 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-heat-calendar.html7  Desc   : GitHub-style heat calendar cascading in diagonally8  ============================================================9-->10<!--svgarden11title: Heat calendar12slug: chart-heat-calendar13category: charts14tags: [chart, heatmap, calendar, cascade]15difficulty: intermediate16techniques: ["--sg-i index variables", "nth-of-type indexing", "fill-opacity levels", "css rect geometry"]17how_it_works: >18  Intensity is encoded the cheap way: every cell shares one fill (the accent19  color) and only fill-opacity changes across four level classes, which keeps20  the palette coherent on light and dark pages with zero color math. The21  diagonal cascade is coordinate arithmetic in CSS: each row group carries22  --sg-r, fourteen nth-of-type rules hand each column its --sg-c, and one rule23  computes animation-delay: calc((var(--sg-r) + var(--sg-c)) × step) — cells on24  the same anti-diagonal share r + c, so they pop together and the wave travels25  corner to corner. Cell geometry (width, height, rx) lives in CSS too, which26  is why each rect only needs x, y and a level class.27customizable: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 }31created: 2026-08-1032-->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>96