@opendata-ai/openchart-vanilla 6.28.6 → 7.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- .oc-root,.oc-chart-root,.oc-table-wrapper,.oc-table-root,.oc-graph-wrapper,.oc-graph-root,.oc-sankey-root,.oc-tilemap-root,.oc-barlist-root{--oc-font-family:Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--oc-font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", monospace;--oc-ease-smooth:linear(0, .157, .438, .64, .766, .85, .906, .941, .964, .978, .988, .994, .998, 1);--oc-ease-snappy:linear(0, .012, .048, .108, .194, .302, .426, .559, .69, .808, .905, .973, 1.013, 1.028, 1.023, 1.006, .984, .966, .957, .957, .964, .975, .986, .995, 1, 1.003, 1.002, 1, .998, .998, .999, 1);--oc-animation-duration:.5s;--oc-animation-stagger:80ms;--oc-annotation-delay:.2s;--oc-title-size:22px;--oc-title-weight:700;--oc-title-tracking:-.02em;--oc-subtitle-size:14px;--oc-subtitle-weight:400;--oc-source-size:12px;--oc-source-weight:400;--oc-body-size:13px;--oc-bg:#fff;--oc-text:#1d1d1d;--oc-text-secondary:#5c5c5c;--oc-text-muted:#999;--oc-gridline:#e8e8e8;--oc-axis:#888;--oc-border:#e2e2e2;--oc-border-radius:4px;--oc-focus:#3b82f6;--oc-hover-bg:rgba(0,0,0,.024);--oc-tooltip-bg:rgba(255,255,255,.88);--oc-tooltip-border:rgba(0,0,0,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.08), 0 0 1px rgba(0,0,0,.12);--oc-tooltip-text:#1d1d1d;--oc-legend-text:#555}.oc-dark{--oc-bg:#1a1a2e;--oc-text:#e0e0e0;--oc-text-secondary:#b0b0b0;--oc-text-muted:gray;--oc-gridline:#335;--oc-axis:#999;--oc-border:#446;--oc-focus:#60a5fa;--oc-hover-bg:rgba(255,255,255,.05);--oc-tooltip-bg:rgba(30,30,50,.85);--oc-tooltip-border:rgba(255,255,255,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.3), 0 0 1px rgba(0,0,0,.4);--oc-tooltip-text:#e0e0e0;--oc-legend-text:#b0b0b0}.oc-chart-root{width:100%}.oc-table-root,.oc-graph-root,.oc-sankey-root,.oc-tilemap-root,.oc-barlist-root{width:100%;height:100%}.oc-table-root{overflow:auto}.oc-chart{font-family:var(--oc-font-family);width:100%;display:block}.oc-barlist{width:100%;display:block}.oc-sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.oc-editable-hover{outline-offset:2px;border-radius:2px;outline:1.5px solid rgba(79,70,229,.35)}.oc-chrome{font-family:var(--oc-font-family)}.oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);fill:var(--oc-text)}.oc-subtitle{font-size:var(--oc-subtitle-size);font-weight:var(--oc-subtitle-weight);fill:var(--oc-text-secondary)}.oc-source,.oc-byline,.oc-footer{font-size:var(--oc-source-size);font-weight:var(--oc-source-weight);fill:var(--oc-text-muted)}.oc-chrome-footer{padding-top:16px}.oc-tooltip{pointer-events:none;z-index:1000;background:var(--oc-tooltip-bg);-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px);border:1px solid var(--oc-tooltip-border);box-shadow:var(--oc-tooltip-shadow);color:var(--oc-tooltip-text);font-family:var(--oc-font-family);border-radius:8px;min-width:140px;max-width:260px;padding:0;font-size:12px;line-height:1.4;animation:.12s ease-out oc-tooltip-in;display:none;position:absolute}.oc-tooltip .oc-tooltip-header{align-items:center;gap:6px;padding:8px 12px 6px;display:flex}.oc-tooltip .oc-tooltip-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px}.oc-tooltip .oc-tooltip-title{letter-spacing:-.01em;color:var(--oc-tooltip-text);white-space:nowrap;text-overflow:ellipsis;font-size:12px;font-weight:600;overflow:hidden}.oc-tooltip .oc-tooltip-body{border-top:1px solid var(--oc-tooltip-border);padding:4px 12px 8px}.oc-tooltip .oc-tooltip-header+.oc-tooltip-body{padding-top:6px}.oc-tooltip .oc-tooltip-body:first-child{border-top:none;padding-top:8px}.oc-tooltip .oc-tooltip-row{justify-content:space-between;align-items:baseline;gap:12px;padding:1px 0;display:flex}.oc-tooltip .oc-tooltip-label{color:var(--oc-text-muted);white-space:nowrap;flex-shrink:0;font-size:11px}.oc-tooltip .oc-tooltip-value{font-variant-numeric:tabular-nums;text-align:right;text-overflow:ellipsis;white-space:nowrap;font-size:11px;font-weight:500;overflow:hidden}.oc-legend{font-family:var(--oc-font-family);font-size:var(--oc-body-size)}.oc-legend-entry{cursor:default}.oc-legend text{fill:var(--oc-legend-text)}.oc-table-wrapper{font-family:var(--oc-font-family);color:var(--oc-text);background:var(--oc-bg)}.oc-table-wrapper>.oc-chrome{margin-bottom:16px;padding-left:16px;padding-right:16px}.oc-table-wrapper>.oc-chrome:first-child{padding-top:4px}.oc-table-wrapper table{border-collapse:collapse;width:100%}.oc-table-wrapper th{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper td{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper th{text-transform:uppercase;letter-spacing:.05em;color:var(--oc-text-secondary);white-space:nowrap;font-size:12px;font-weight:600}.oc-table-wrapper thead{z-index:2;background:var(--oc-bg);position:sticky;top:0}.oc-table-wrapper thead th{border-bottom-width:2px}.oc-table-wrapper td{font-variant-numeric:tabular-nums;font-size:14px}.oc-table-wrapper th:focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-wrapper tbody:focus{outline:none}.oc-table-title{font-size:var(--oc-title-computed-size,var(--oc-title-size));font-weight:var(--oc-title-computed-weight,var(--oc-title-weight));color:var(--oc-title-computed-color,var(--oc-text));margin-bottom:4px}.oc-table-subtitle{font-size:var(--oc-subtitle-computed-size,var(--oc-subtitle-size));font-weight:var(--oc-subtitle-computed-weight,var(--oc-subtitle-weight));color:var(--oc-subtitle-computed-color,var(--oc-text-secondary));margin-bottom:8px}.oc-table-source{font-size:var(--oc-source-computed-size,var(--oc-source-size));color:var(--oc-source-computed-color,var(--oc-text-muted))}.oc-table-footer-text{font-size:var(--oc-footer-computed-size,var(--oc-source-size));color:var(--oc-footer-computed-color,var(--oc-text-muted))}.oc-table-scroll{overflow-x:auto}.oc-table--sticky th:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table--sticky td:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table-sort-btn{cursor:pointer;vertical-align:middle;background:0 0;border:none;flex-direction:column;align-items:center;gap:2px;margin-left:6px;padding:2px;display:inline-flex}.oc-table-sort-btn:before{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:after{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:before{border-bottom:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:after{border-top:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:hover:before{opacity:.75}.oc-table-sort-btn:hover:after{opacity:.75}th[aria-sort=ascending] .oc-table-sort-btn:before{opacity:1;border-bottom-color:var(--oc-text)}th[aria-sort=ascending] .oc-table-sort-btn:after{opacity:.15}th[aria-sort=descending] .oc-table-sort-btn:after{opacity:1;border-top-color:var(--oc-text)}th[aria-sort=descending] .oc-table-sort-btn:before{opacity:.15}.oc-table-search{padding:8px 0}.oc-table-search input{border:1px solid var(--oc-border);background:var(--oc-bg);width:100%;color:var(--oc-text);box-sizing:border-box;border-radius:6px;padding:8px 12px;font-family:inherit;font-size:13px;transition:border-color .15s}.oc-table-search input::-ms-input-placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input::placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input:focus{border-color:var(--oc-focus);outline:none;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.oc-table-pagination{color:var(--oc-text-secondary);justify-content:space-between;align-items:center;padding:12px 0 4px;font-size:13px;display:flex}.oc-table-pagination button{border:1px solid var(--oc-border);background:var(--oc-bg);color:var(--oc-text);cursor:pointer;border-radius:6px;padding:6px 14px;font-family:inherit;font-size:13px;transition:background .15s,border-color .15s}.oc-table-pagination button:disabled{opacity:.35;cursor:not-allowed}.oc-table-pagination button:hover:not(:disabled){background:var(--oc-hover-bg);border-color:var(--oc-axis)}.oc-table-pagination button:focus-visible{outline:2px solid var(--oc-focus);outline-offset:1px}.oc-table-pagination-info{font-variant-numeric:tabular-nums}.oc-table-pagination-btns{gap:8px;display:flex}.oc-table-bar{position:relative}.oc-table-bar-fill{opacity:.15;pointer-events:none;border-radius:2px;position:absolute;top:6px;bottom:6px;left:0}.oc-table-bar-value{z-index:1;position:relative}.oc-table-sparkline{width:100%;display:block;position:relative}.oc-table-sparkline svg{width:100%;display:block;overflow:visible}.oc-table-sparkline-dot{border-radius:50%;width:5px;height:5px;position:absolute}.oc-table-sparkline-labels{justify-content:space-between;font-size:11px;line-height:1;display:flex}.oc-table-image{vertical-align:middle;display:inline-block}.oc-table-image img{object-fit:cover}.oc-table-image-rounded img{border-radius:50%}.oc-table-flag{font-size:1.2em}.oc-table--compact th{padding:4px 8px;font-size:13px}.oc-table--compact td{padding:4px 8px;font-size:13px}.oc-table--compact th{font-size:11px}.oc-table--clickable tbody tr{cursor:pointer}.oc-table--clickable tbody tr:hover{background:var(--oc-hover-bg)}.oc-table-cell-focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-empty{text-align:center;color:var(--oc-text-secondary);padding:32px 16px;font-size:14px;font-style:italic}.oc-table-wrapper.oc-animate>.oc-chrome{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate thead{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate tbody tr{animation:oc-table-enter-row var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate tbody td{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate td.oc-table-heatmap{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate td.oc-table-category{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-bar-fill{animation:oc-table-enter-bar-fill calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-sparkline>svg{animation:oc-enter-line calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .4)}.oc-table-wrapper.oc-animate .oc-table-sparkline-dot{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-sparkline-labels{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-search{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate .oc-table-pagination{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-graph-wrapper{background:var(--oc-bg);font-family:var(--oc-font-family);width:100%;height:100%;position:relative;overflow:hidden}.oc-graph-canvas{cursor:grab;width:100%;height:100%;display:block}.oc-graph-canvas--dragging{cursor:grabbing}.oc-graph-chrome{z-index:2;pointer-events:none;padding:16px 16px 8px;position:absolute;top:0;left:0;right:0}.oc-graph-chrome .oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);color:var(--oc-text);--_stroke:color-mix(in srgb, var(--oc-bg) 80%, transparent);text-shadow:-2px -2px 0 var(--_stroke), 2px -2px 0 var(--_stroke), -2px 2px 0 var(--_stroke), 2px 2px 0 var(--_stroke), 0 -2px 0 var(--_stroke), 0 2px 0 var(--_stroke), -2px 0 0 var(--_stroke), 2px 0 0 var(--_stroke);margin:0 0 4px}.oc-graph-chrome .oc-subtitle{font-size:var(--oc-subtitle-size);color:var(--oc-text-secondary);--_stroke:color-mix(in srgb, var(--oc-bg) 80%, transparent);text-shadow:-1px -1px 0 var(--_stroke), 1px -1px 0 var(--_stroke), -1px 1px 0 var(--_stroke), 1px 1px 0 var(--_stroke);margin:0}.oc-graph-legend{background:var(--oc-bg);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);color:var(--oc-text-secondary);max-height:200px;padding:8px 12px;font-size:12px;position:absolute;top:8px;right:8px;overflow-y:auto}.oc-graph-legend-item{align-items:center;gap:6px;padding:2px 0;display:flex}.oc-graph-legend-swatch{border-radius:50%;flex-shrink:0;width:10px;height:10px}.oc-graph-search{position:absolute;top:8px;left:8px}.oc-graph-search input{font-family:var(--oc-font-family);font-size:var(--oc-body-size);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);background:var(--oc-bg);color:var(--oc-text);outline:none;padding:6px 10px}.oc-graph-search input:focus{border-color:var(--oc-focus);box-shadow:0 0 0 2px rgba(59,130,246,.25)}.oc-dark .oc-graph-wrapper,.oc-graph-wrapper.oc-dark{--oc-bg:#0d1117}.oc-dark .oc-graph-legend,.oc-dark.oc-graph-wrapper .oc-graph-legend,.oc-dark .oc-graph-search input{background:rgba(13,17,23,.85);border-color:rgba(255,255,255,.1)}.oc-chart[data-display=sparkline]{margin:0;padding:0;display:block}@keyframes oc-enter-bar{0%{clip-path:inset(100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-bar-h{0%{clip-path:inset(0 100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-line{0%{clip-path:inset(0 100% 0 0);opacity:0}15%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-point{0%{opacity:0;transform:scale(.3)}to{opacity:1;transform:scale(1)}}@keyframes oc-enter-fade-only{0%{opacity:0}to{opacity:1}}@keyframes oc-enter-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes oc-table-enter-row{0%{transform:translateY(6px)}to{transform:translateY(0)}}@keyframes oc-table-enter-bar-fill{0%{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0)}}@keyframes oc-tooltip-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}.oc-animate .oc-mark-rect rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-bar rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rect[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-bar[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-rect[data-stack-pos] rect{animation-duration:var(--oc-stack-segment-duration,.15s);animation-timing-function:linear;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-stack-pos,0) * var(--oc-stack-segment-duration,.15s))}.oc-animate .oc-mark-line{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-arc{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-point{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-circle{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-line~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-text text{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rule line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-tick line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-label{animation:oc-enter-fade .3s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-animation-duration) * .7)}.oc-animate .oc-annotation{animation:oc-enter-fade .4s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-duration) + var(--oc-annotation-delay,.2s))}.oc-animate .oc-tilemap-tile{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .35) ease-in both;animation-delay:var(--oc-tile-delay,0s)}.oc-animate .oc-sankey-node rect{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-sankey-link path{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-duration) * .3 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-barlist-row{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:var(--oc-row-delay,0s)}.oc-animate .oc-barlist-bar{animation:oc-enter-bar-h var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:var(--oc-row-delay,0s)}.oc-animate[data-display=sparkline] .oc-mark-line,.oc-animate[data-display=sparkline] .oc-mark-area{animation-timing-function:cubic-bezier(.16,1,.3,1)}@media (prefers-reduced-motion:reduce){.oc-table-sort-btn:before,.oc-table-sort-btn:after,.oc-table-search input,.oc-table-pagination button{transition:none}.oc-animate .oc-mark-rect rect,.oc-animate .oc-mark-bar rect,.oc-animate .oc-mark-arc,.oc-animate .oc-mark-line,.oc-animate .oc-mark-area,.oc-animate circle.oc-mark-point,.oc-animate circle.oc-mark-circle,.oc-animate .oc-mark-text text,.oc-animate .oc-mark-rule line,.oc-animate .oc-mark-tick line,.oc-animate .oc-mark-label,.oc-animate .oc-annotation,.oc-animate .oc-sankey-node rect,.oc-animate .oc-sankey-link path,.oc-table-wrapper.oc-animate>.oc-chrome,.oc-table-wrapper.oc-animate thead,.oc-table-wrapper.oc-animate tbody tr,.oc-table-wrapper.oc-animate tbody td,.oc-table-wrapper.oc-animate td.oc-table-heatmap,.oc-table-wrapper.oc-animate td.oc-table-category,.oc-table-wrapper.oc-animate .oc-table-bar-fill,.oc-table-wrapper.oc-animate .oc-table-sparkline>svg,.oc-table-wrapper.oc-animate .oc-table-sparkline-dot,.oc-table-wrapper.oc-animate .oc-table-sparkline-labels,.oc-table-wrapper.oc-animate .oc-table-search,.oc-table-wrapper.oc-animate .oc-table-pagination{animation:none}}
1
+ .oc-root,.oc-chart-root,.oc-table-wrapper,.oc-table-root,.oc-graph-wrapper,.oc-graph-root,.oc-sankey-root,.oc-tilemap-root,.oc-barlist-root{--oc-font-family:"Inter Variable", Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;--oc-font-mono:"JetBrains Mono", "Fira Code", "Cascadia Code", monospace;--oc-ease-smooth:linear(0, .157, .438, .64, .766, .85, .906, .941, .964, .978, .988, .994, .998, 1);--oc-ease-snappy:linear(0, .012, .048, .108, .194, .302, .426, .559, .69, .808, .905, .973, 1.013, 1.028, 1.023, 1.006, .984, .966, .957, .957, .964, .975, .986, .995, 1, 1.003, 1.002, 1, .998, .998, .999, 1);--oc-animation-duration:.5s;--oc-animation-stagger:80ms;--oc-annotation-delay:.2s;--oc-title-size:26px;--oc-title-weight:590;--oc-title-tracking:-.022em;--oc-subtitle-size:14px;--oc-subtitle-weight:400;--oc-source-size:11px;--oc-source-weight:400;--oc-body-size:13px;--oc-eyebrow-size:11px;--oc-eyebrow-weight:510;--oc-eyebrow-tracking:.08em;--oc-bg:#fff;--oc-card:#fff;--oc-secondary:#f4f4f5;--oc-text:#09090b;--oc-text-secondary:#3f3f46;--oc-text-muted:#71717a;--oc-text-subtle:#a1a1aa;--oc-text-faint:#d4d4d8;--oc-gridline:rgba(0,0,0,.06);--oc-axis:rgba(0,0,0,.1);--oc-border:rgba(0,0,0,.08);--oc-border-radius:2px;--oc-accent:#06b6d4;--oc-accent-strong:#0891b2;--oc-positive:#10b981;--oc-negative:#e11d48;--oc-focus:#3b82f6;--oc-space-1:4px;--oc-space-2:8px;--oc-space-3:12px;--oc-space-4:16px;--oc-space-6:24px;--oc-space-8:32px;--oc-hover-bg:rgba(0,0,0,.024);--oc-tooltip-bg:rgba(255,255,255,.88);--oc-tooltip-border:rgba(0,0,0,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.08), 0 0 1px rgba(0,0,0,.12);--oc-tooltip-text:#09090b;--oc-legend-text:#3f3f46}.oc-dark{--oc-bg:#09090b;--oc-card:#111113;--oc-secondary:#27272a;--oc-text:#f7f8f8;--oc-text-secondary:#d0d6e0;--oc-text-muted:#a1a1aa;--oc-text-subtle:#71717a;--oc-text-faint:#52525b;--oc-gridline:rgba(255,255,255,.05);--oc-axis:rgba(255,255,255,.1);--oc-border:rgba(255,255,255,.1);--oc-accent:#06b6d4;--oc-accent-strong:#06b6d4;--oc-positive:#34d399;--oc-negative:#fb7185;--oc-focus:#60a5fa;--oc-hover-bg:rgba(255,255,255,.05);--oc-tooltip-bg:rgba(17,17,19,.92);--oc-tooltip-border:rgba(255,255,255,.08);--oc-tooltip-shadow:0 2px 8px rgba(0,0,0,.3), 0 0 1px rgba(0,0,0,.4);--oc-tooltip-text:#f7f8f8;--oc-legend-text:#d0d6e0}.oc-chart-root,.oc-table-root,.oc-graph-root,.oc-sankey-root,.oc-barlist-root{width:100%;height:100%}.oc-tilemap-root{width:100%}.oc-table-root{overflow:auto}.oc-chart{font-family:var(--oc-font-family);width:100%;display:block}.oc-tilemap{width:100%;height:auto;display:block}.oc-barlist{width:100%;display:block}.oc-sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.oc-editable-hover{outline-offset:2px;border-radius:2px;outline:1.5px solid rgba(79,70,229,.35)}.oc-chrome{font-family:var(--oc-font-family)}.oc-eyebrow{font-size:var(--oc-eyebrow-size);font-weight:var(--oc-eyebrow-weight);letter-spacing:var(--oc-eyebrow-tracking);text-transform:uppercase;fill:var(--oc-accent)}.oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);fill:var(--oc-text)}.oc-subtitle{font-size:var(--oc-subtitle-size);font-weight:var(--oc-subtitle-weight);fill:var(--oc-text-muted)}.oc-source,.oc-byline,.oc-footer{font-size:var(--oc-source-size);font-weight:var(--oc-source-weight);fill:var(--oc-text-muted)}.oc-brand{letter-spacing:.02em;fill:var(--oc-text-faint);font-size:11px;font-weight:510}.oc-brand-dot,.oc-eyebrow-dot{fill:var(--oc-accent)}.oc-metrics{font-family:var(--oc-font-family)}.oc-metric-label{letter-spacing:.08em;text-transform:uppercase;fill:var(--oc-text-muted);font-size:10px;font-weight:510}.oc-metric-value{letter-spacing:-.01em;fill:var(--oc-text);font-variant-numeric:tabular-nums;font-size:22px;font-weight:510}.oc-metric-delta-up{fill:var(--oc-positive);font-size:12px;font-weight:510}.oc-metric-delta-down{fill:var(--oc-negative);font-size:12px;font-weight:510}.oc-metric-secondary{fill:var(--oc-positive);font-size:12px;font-weight:400}.oc-axis-tick-inline{fill:var(--oc-text-muted);font-size:11px;font-weight:400}.oc-endpoint-labels{font-family:var(--oc-font-family)}.oc-endpoint-label{fill:var(--oc-endpoint-label-color,var(--oc-text))}.oc-endpoint-value{fill:var(--oc-endpoint-value-color,var(--oc-text-muted))}.oc-endpoint-leader{stroke:var(--oc-endpoint-leader-color,currentColor)}.oc-annotation-subtitle{fill:var(--oc-annotation-subtitle-color,var(--oc-text-muted))}.oc-chrome-footer{padding-top:16px}.oc-tooltip{pointer-events:none;z-index:1000;background:var(--oc-tooltip-bg);-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px);border:1px solid var(--oc-tooltip-border);border-radius:var(--oc-border-radius,4px);box-shadow:var(--oc-tooltip-shadow);color:var(--oc-tooltip-text);font-family:var(--oc-font-family);min-width:160px;max-width:280px;padding:0;font-size:12px;line-height:1.4;transition:left .1s ease-in-out,top .1s ease-in-out;animation:.12s ease-out oc-tooltip-in;display:none;position:absolute}.oc-tooltip .oc-tooltip-header{align-items:center;gap:8px;padding:8px 12px 6px;display:flex}.oc-tooltip .oc-tooltip-dot{border-radius:50%;flex-shrink:0;width:8px;height:8px}.oc-tooltip .oc-tooltip-title{letter-spacing:.04em;text-transform:uppercase;color:var(--oc-text-muted);white-space:nowrap;text-overflow:ellipsis;font-size:11px;font-weight:590;overflow:hidden}.oc-tooltip .oc-tooltip-body{border-top:1px solid var(--oc-tooltip-border);padding:6px 12px 10px}.oc-tooltip .oc-tooltip-body:first-child{border-top:none;padding-top:10px}.oc-tooltip .oc-tooltip-row{justify-content:space-between;align-items:baseline;gap:16px;padding:2px 0;display:flex}.oc-tooltip .oc-tooltip-row-swatch{border-radius:50%;flex-shrink:0;width:8px;height:8px;margin-right:6px;display:inline-block;transform:translateY(-1px)}.oc-tooltip .oc-tooltip-label{color:var(--oc-text-secondary);white-space:nowrap;flex-shrink:0;align-items:center;font-size:12px;font-weight:400;display:inline-flex}.oc-tooltip .oc-tooltip-value{font-variant-numeric:tabular-nums;color:var(--oc-tooltip-text);text-align:right;text-overflow:ellipsis;white-space:nowrap;font-size:12px;font-weight:510;overflow:hidden}.oc-crosshair{pointer-events:none;transition:x1 50ms ease-in-out,x2 50ms ease-in-out}.oc-snap-dots circle{pointer-events:none;transition:cx 50ms ease-in-out,cy 50ms ease-in-out}@media (prefers-reduced-motion:reduce){.oc-tooltip,.oc-crosshair,.oc-snap-dots circle{transition:none}}.oc-legend{font-family:var(--oc-font-family);font-size:var(--oc-body-size)}.oc-legend-entry{cursor:default}.oc-legend text{fill:var(--oc-legend-text)}.oc-table-wrapper{font-family:var(--oc-font-family);color:var(--oc-text);background:var(--oc-bg)}.oc-table-wrapper>.oc-chrome{margin-bottom:16px;padding-left:16px;padding-right:16px}.oc-table-wrapper>.oc-chrome:first-child{padding-top:4px}.oc-table-wrapper table{border-collapse:collapse;width:100%}.oc-table-wrapper th{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper td{text-align:left;border-bottom:1px solid var(--oc-border);padding:10px 16px}.oc-table-wrapper th{text-transform:uppercase;letter-spacing:.05em;color:var(--oc-text-secondary);white-space:nowrap;font-size:12px;font-weight:600}.oc-table-wrapper thead{z-index:2;background:var(--oc-bg);position:sticky;top:0}.oc-table-wrapper thead th{border-bottom-width:2px}.oc-table-wrapper td{font-variant-numeric:tabular-nums;font-size:14px}.oc-table-wrapper th:focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-wrapper tbody:focus{outline:none}.oc-table-title{font-size:var(--oc-title-computed-size,var(--oc-title-size));font-weight:var(--oc-title-computed-weight,var(--oc-title-weight));color:var(--oc-title-computed-color,var(--oc-text));margin-bottom:4px}.oc-table-subtitle{font-size:var(--oc-subtitle-computed-size,var(--oc-subtitle-size));font-weight:var(--oc-subtitle-computed-weight,var(--oc-subtitle-weight));color:var(--oc-subtitle-computed-color,var(--oc-text-secondary));margin-bottom:8px}.oc-table-source{font-size:var(--oc-source-computed-size,var(--oc-source-size));color:var(--oc-source-computed-color,var(--oc-text-muted))}.oc-table-footer-text{font-size:var(--oc-footer-computed-size,var(--oc-source-size));color:var(--oc-footer-computed-color,var(--oc-text-muted))}.oc-table-scroll{overflow-x:auto}.oc-table--sticky th:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table--sticky td:first-child{z-index:1;background:var(--oc-bg);position:sticky;left:0}.oc-table-sort-btn{cursor:pointer;vertical-align:middle;background:0 0;border:none;flex-direction:column;align-items:center;gap:2px;margin-left:6px;padding:2px;display:inline-flex}.oc-table-sort-btn:before{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:after{content:"";border-left:5px solid transparent;border-right:5px solid transparent;width:0;height:0;transition:opacity .15s,border-color .15s;display:block}.oc-table-sort-btn:before{border-bottom:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:after{border-top:4.5px solid var(--oc-text-secondary);opacity:.45}.oc-table-sort-btn:hover:before{opacity:.75}.oc-table-sort-btn:hover:after{opacity:.75}th[aria-sort=ascending] .oc-table-sort-btn:before{opacity:1;border-bottom-color:var(--oc-text)}th[aria-sort=ascending] .oc-table-sort-btn:after{opacity:.15}th[aria-sort=descending] .oc-table-sort-btn:after{opacity:1;border-top-color:var(--oc-text)}th[aria-sort=descending] .oc-table-sort-btn:before{opacity:.15}.oc-table-search{padding:8px 0}.oc-table-search input{border:1px solid var(--oc-border);background:var(--oc-bg);width:100%;color:var(--oc-text);box-sizing:border-box;border-radius:6px;padding:8px 12px;font-family:inherit;font-size:13px;transition:border-color .15s}.oc-table-search input::-ms-input-placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input::placeholder{color:var(--oc-text-muted);font-size:13px}.oc-table-search input:focus{border-color:var(--oc-focus);outline:none;box-shadow:0 0 0 3px rgba(59,130,246,.1)}.oc-table-pagination{color:var(--oc-text-secondary);justify-content:space-between;align-items:center;padding:12px 0 4px;font-size:13px;display:flex}.oc-table-pagination button{border:1px solid var(--oc-border);background:var(--oc-bg);color:var(--oc-text);cursor:pointer;border-radius:6px;padding:6px 14px;font-family:inherit;font-size:13px;transition:background .15s,border-color .15s}.oc-table-pagination button:disabled{opacity:.35;cursor:not-allowed}.oc-table-pagination button:hover:not(:disabled){background:var(--oc-hover-bg);border-color:var(--oc-axis)}.oc-table-pagination button:focus-visible{outline:2px solid var(--oc-focus);outline-offset:1px}.oc-table-pagination-info{font-variant-numeric:tabular-nums}.oc-table-pagination-btns{gap:8px;display:flex}.oc-table-bar{position:relative}.oc-table-bar-fill{opacity:.15;pointer-events:none;border-radius:2px;position:absolute;top:6px;bottom:6px;left:0}.oc-table-bar-value{z-index:1;position:relative}.oc-table-sparkline{width:100%;display:block;position:relative}.oc-table-sparkline svg{width:100%;display:block;overflow:visible}.oc-table-sparkline-dot{border-radius:50%;width:5px;height:5px;position:absolute}.oc-table-sparkline-labels{justify-content:space-between;font-size:11px;line-height:1;display:flex}.oc-table-image{vertical-align:middle;display:inline-block}.oc-table-image img{object-fit:cover}.oc-table-image-rounded img{border-radius:50%}.oc-table-flag{font-size:1.2em}.oc-table--compact th{padding:4px 8px;font-size:13px}.oc-table--compact td{padding:4px 8px;font-size:13px}.oc-table--compact th{font-size:11px}.oc-table--clickable tbody tr{cursor:pointer}.oc-table--clickable tbody tr:hover{background:var(--oc-hover-bg)}.oc-table-cell-focus{outline:2px solid var(--oc-focus);outline-offset:-2px}.oc-table-empty{text-align:center;color:var(--oc-text-secondary);padding:32px 16px;font-size:14px;font-style:italic}.oc-table-wrapper.oc-animate>.oc-chrome{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate thead{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate tbody tr{animation:oc-table-enter-row var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate tbody td{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0))}.oc-table-wrapper.oc-animate td.oc-table-heatmap{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate td.oc-table-category{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .7) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-bar-fill{animation:oc-table-enter-bar-fill calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .3)}.oc-table-wrapper.oc-animate .oc-table-sparkline>svg{animation:oc-enter-line calc(var(--oc-animation-duration) * .8) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .4)}.oc-table-wrapper.oc-animate .oc-table-sparkline-dot{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-sparkline-labels{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .3) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-row-index,0) + var(--oc-animation-duration) * .8)}.oc-table-wrapper.oc-animate .oc-table-search{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-table-wrapper.oc-animate .oc-table-pagination{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both}.oc-graph-wrapper{background:var(--oc-bg);font-family:var(--oc-font-family);width:100%;height:100%;position:relative;overflow:hidden}.oc-graph-canvas{cursor:grab;width:100%;height:100%;display:block}.oc-graph-canvas--dragging{cursor:grabbing}.oc-graph-chrome{z-index:2;pointer-events:none;padding:16px 16px 8px;position:absolute;top:0;left:0;right:0}.oc-graph-chrome .oc-title{font-size:var(--oc-title-size);font-weight:var(--oc-title-weight);letter-spacing:var(--oc-title-tracking);color:var(--oc-text);--_stroke:color-mix(in srgb, var(--oc-bg) 80%, transparent);text-shadow:-2px -2px 0 var(--_stroke), 2px -2px 0 var(--_stroke), -2px 2px 0 var(--_stroke), 2px 2px 0 var(--_stroke), 0 -2px 0 var(--_stroke), 0 2px 0 var(--_stroke), -2px 0 0 var(--_stroke), 2px 0 0 var(--_stroke);margin:0 0 4px}.oc-graph-chrome .oc-subtitle{font-size:var(--oc-subtitle-size);color:var(--oc-text-secondary);--_stroke:color-mix(in srgb, var(--oc-bg) 80%, transparent);text-shadow:-1px -1px 0 var(--_stroke), 1px -1px 0 var(--_stroke), -1px 1px 0 var(--_stroke), 1px 1px 0 var(--_stroke);margin:0}.oc-graph-legend{background:var(--oc-bg);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);color:var(--oc-text-secondary);max-height:200px;padding:8px 12px;font-size:12px;position:absolute;top:8px;right:8px;overflow-y:auto}.oc-graph-legend-item{align-items:center;gap:6px;padding:2px 0;display:flex}.oc-graph-legend-swatch{border-radius:50%;flex-shrink:0;width:10px;height:10px}.oc-graph-search{position:absolute;top:8px;left:8px}.oc-graph-search input{font-family:var(--oc-font-family);font-size:var(--oc-body-size);border:1px solid var(--oc-border);border-radius:var(--oc-border-radius);background:var(--oc-bg);color:var(--oc-text);outline:none;padding:6px 10px}.oc-graph-search input:focus{border-color:var(--oc-focus);box-shadow:0 0 0 2px rgba(59,130,246,.25)}.oc-dark .oc-graph-wrapper,.oc-graph-wrapper.oc-dark{--oc-bg:#0d1117}.oc-dark .oc-graph-legend,.oc-dark.oc-graph-wrapper .oc-graph-legend,.oc-dark .oc-graph-search input{background:rgba(13,17,23,.85);border-color:rgba(255,255,255,.1)}.oc-chart[data-display=sparkline]{margin:0;padding:0;display:block}@keyframes oc-enter-bar{0%{clip-path:inset(100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-bar-h{0%{clip-path:inset(0 100% 0 0);opacity:0}75%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-line{0%{clip-path:inset(0 100% 0 0);opacity:0}15%{opacity:1}to{clip-path:inset(0);opacity:1}}@keyframes oc-enter-point{0%{opacity:0;transform:scale(.3)}to{opacity:1;transform:scale(1)}}@keyframes oc-enter-fade-only{0%{opacity:0}to{opacity:1}}@keyframes oc-enter-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}@keyframes oc-table-enter-row{0%{transform:translateY(6px)}to{transform:translateY(0)}}@keyframes oc-table-enter-bar-fill{0%{clip-path:inset(0 100% 0 0)}to{clip-path:inset(0)}}@keyframes oc-tooltip-in{0%{opacity:0;transform:translateY(2px)}to{opacity:1;transform:translateY(0)}}.oc-animate .oc-mark-rect rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-bar rect{animation:oc-enter-bar var(--oc-animation-duration) var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rect[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-bar[data-orient=horizontal] rect{animation-name:oc-enter-bar-h}.oc-animate .oc-mark-rect[data-stack-pos] rect{animation-duration:var(--oc-stack-segment-duration,.15s);animation-timing-function:linear;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-stack-pos,0) * var(--oc-stack-segment-duration,.15s))}.oc-animate .oc-mark-line{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area{animation:oc-enter-line var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-arc{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-point{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate circle.oc-mark-circle{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .4) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-line~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-area~circle.oc-mark-point{animation-delay:calc(var(--oc-animation-duration) * .35 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-text text{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-rule line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-tick line{animation:oc-enter-fade calc(var(--oc-animation-duration) * .5) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-mark-label{animation:oc-enter-fade .3s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0) + var(--oc-animation-duration) * .7)}.oc-animate .oc-annotation{animation:oc-enter-fade .4s var(--oc-ease-smooth) both;animation-delay:calc(var(--oc-animation-duration) + var(--oc-annotation-delay,.2s))}.oc-animate .oc-tilemap-tile{animation:oc-enter-fade-only calc(var(--oc-animation-duration) * .35) ease-in both;animation-delay:var(--oc-tile-delay,0s)}.oc-animate .oc-sankey-node rect{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-sankey-link path{animation:oc-enter-fade-only var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:calc(var(--oc-animation-duration) * .3 + var(--oc-animation-stagger) * var(--oc-mark-index,0))}.oc-animate .oc-barlist-row{animation:oc-enter-fade calc(var(--oc-animation-duration) * .6) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:var(--oc-row-delay,0s)}.oc-animate .oc-barlist-bar{animation:oc-enter-bar-h var(--oc-animation-duration) var(--oc-animation-ease,var(--oc-ease-smooth)) both;animation-delay:var(--oc-row-delay,0s)}.oc-animate[data-display=sparkline] .oc-mark-line,.oc-animate[data-display=sparkline] .oc-mark-area{animation-timing-function:cubic-bezier(.16,1,.3,1)}@media (prefers-reduced-motion:reduce){.oc-table-sort-btn:before,.oc-table-sort-btn:after,.oc-table-search input,.oc-table-pagination button{transition:none}.oc-animate .oc-mark-rect rect,.oc-animate .oc-mark-bar rect,.oc-animate .oc-mark-arc,.oc-animate .oc-mark-line,.oc-animate .oc-mark-area,.oc-animate circle.oc-mark-point,.oc-animate circle.oc-mark-circle,.oc-animate .oc-mark-text text,.oc-animate .oc-mark-rule line,.oc-animate .oc-mark-tick line,.oc-animate .oc-mark-label,.oc-animate .oc-annotation,.oc-animate .oc-sankey-node rect,.oc-animate .oc-sankey-link path,.oc-table-wrapper.oc-animate>.oc-chrome,.oc-table-wrapper.oc-animate thead,.oc-table-wrapper.oc-animate tbody tr,.oc-table-wrapper.oc-animate tbody td,.oc-table-wrapper.oc-animate td.oc-table-heatmap,.oc-table-wrapper.oc-animate td.oc-table-category,.oc-table-wrapper.oc-animate .oc-table-bar-fill,.oc-table-wrapper.oc-animate .oc-table-sparkline>svg,.oc-table-wrapper.oc-animate .oc-table-sparkline-dot,.oc-table-wrapper.oc-animate .oc-table-sparkline-labels,.oc-table-wrapper.oc-animate .oc-table-search,.oc-table-wrapper.oc-animate .oc-table-pagination{animation:none}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opendata-ai/openchart-vanilla",
3
- "version": "6.28.6",
3
+ "version": "7.0.2",
4
4
  "description": "Vanilla JS renderer for openchart: SVG charts, HTML tables, force-directed graphs",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Riley Hilliard",
@@ -50,8 +50,8 @@
50
50
  },
51
51
  "dependencies": {
52
52
  "@floating-ui/dom": "^1.7.6",
53
- "@opendata-ai/openchart-core": "6.28.6",
54
- "@opendata-ai/openchart-engine": "6.28.6",
53
+ "@opendata-ai/openchart-core": "7.0.2",
54
+ "@opendata-ai/openchart-engine": "7.0.2",
55
55
  "d3-force": "^3.0.0",
56
56
  "d3-quadtree": "^3.0.1"
57
57
  },
@@ -75,9 +75,18 @@ describe('crosshair', () => {
75
75
  chart.destroy();
76
76
  });
77
77
 
78
- it('does not create crosshair when crosshair is false or omitted', () => {
78
+ it('crosshair defaults to on for line charts (omitted spec)', () => {
79
79
  const chart = createChart(container, lineSpecNoCrosshair);
80
80
 
81
+ const crosshair = container.querySelector('[data-crosshair]');
82
+ expect(crosshair).not.toBeNull();
83
+
84
+ chart.destroy();
85
+ });
86
+
87
+ it('does not create crosshair when explicitly disabled', () => {
88
+ const chart = createChart(container, { ...lineSpecNoCrosshair, crosshair: false });
89
+
81
90
  const crosshair = container.querySelector('[data-crosshair]');
82
91
  expect(crosshair).toBeNull();
83
92
 
@@ -102,7 +111,7 @@ describe('crosshair', () => {
102
111
 
103
112
  const crosshair = container.querySelector('[data-crosshair]') as SVGLineElement;
104
113
  expect(crosshair).not.toBeNull();
105
- expect(crosshair.getAttribute('stroke-dasharray')).toBe('4,3');
114
+ expect(crosshair.getAttribute('stroke-dasharray')).toBe('3,3');
106
115
  expect(crosshair.getAttribute('stroke-width')).toBe('1');
107
116
  expect(crosshair.getAttribute('pointer-events')).toBe('none');
108
117
 
@@ -112,7 +112,7 @@ describe('chart event handlers', () => {
112
112
  });
113
113
 
114
114
  describe('onLegendToggle', () => {
115
- it('fires when a legend entry is clicked', () => {
115
+ it('fires when a legend entry is clicked, and toggling back restores the same series', () => {
116
116
  const onLegendToggle = vi.fn();
117
117
  const chart = createChart(
118
118
  container,
@@ -122,19 +122,64 @@ describe('chart event handlers', () => {
122
122
  },
123
123
  );
124
124
 
125
- const legendEntry = container.querySelector('[data-legend-index]');
126
- expect(legendEntry).not.toBeNull();
125
+ const firstEntry = container.querySelector('[data-legend-index]') as HTMLElement | null;
126
+ expect(firstEntry).not.toBeNull();
127
+ const targetLabel = firstEntry!.getAttribute('data-legend-label')!;
128
+ expect(targetLabel).toBeTypeOf('string');
127
129
 
128
- legendEntry.dispatchEvent(new MouseEvent('click', { bubbles: true }));
130
+ firstEntry!.dispatchEvent(new MouseEvent('click', { bubbles: true }));
129
131
 
130
132
  expect(onLegendToggle).toHaveBeenCalledTimes(1);
131
- const [series, visible] = onLegendToggle.mock.calls[0];
132
- expect(series).toBeTypeOf('string');
133
- // First click hides the series
134
- expect(visible).toBe(false);
133
+ const [hiddenSeries, visibleAfterFirst] = onLegendToggle.mock.calls[0];
134
+ expect(hiddenSeries).toBe(targetLabel);
135
+ expect(visibleAfterFirst).toBe(false);
136
+
137
+ // Toggling triggers a recompile (so the y-scale rebalances and any
138
+ // endpoint labels for the hidden series clear), which replaces the
139
+ // legend DOM. Find the same entry by label and click it again.
140
+ const sameEntry = container.querySelector(
141
+ `[data-legend-label="${targetLabel}"]`,
142
+ ) as HTMLElement | null;
143
+ expect(sameEntry).not.toBeNull();
144
+ sameEntry!.dispatchEvent(new MouseEvent('click', { bubbles: true }));
135
145
 
136
- // Click again to toggle back
137
- legendEntry.dispatchEvent(new MouseEvent('click', { bubbles: true }));
146
+ expect(onLegendToggle).toHaveBeenCalledTimes(2);
147
+ const [shownSeries, visibleAfterSecond] = onLegendToggle.mock.calls[1];
148
+ expect(shownSeries).toBe(targetLabel); // same series, not the other one
149
+ expect(visibleAfterSecond).toBe(true);
150
+
151
+ chart.destroy();
152
+ });
153
+
154
+ it('refuses to hide the last visible series', () => {
155
+ // Two-series chart: hide one, then attempt to hide the other.
156
+ // The second click should be a no-op (no toggle callback for the hide).
157
+ const onLegendToggle = vi.fn();
158
+ const chart = createChart(
159
+ container,
160
+ { ...lineSpec, legend: { show: true } },
161
+ { onLegendToggle },
162
+ );
163
+
164
+ const entries = container.querySelectorAll('[data-legend-label]');
165
+ expect(entries.length).toBeGreaterThanOrEqual(2);
166
+ const firstLabel = (entries[0] as HTMLElement).getAttribute('data-legend-label')!;
167
+
168
+ // Hide the first one.
169
+ (entries[0] as HTMLElement).dispatchEvent(new MouseEvent('click', { bubbles: true }));
170
+ expect(onLegendToggle).toHaveBeenCalledTimes(1);
171
+
172
+ // Click the *other* (still-visible) series. The guard should refuse.
173
+ const remaining = Array.from(container.querySelectorAll('[data-legend-label]')).find(
174
+ (el) => (el as HTMLElement).getAttribute('data-legend-label') !== firstLabel,
175
+ ) as HTMLElement | undefined;
176
+ expect(remaining).toBeTruthy();
177
+ remaining!.dispatchEvent(new MouseEvent('click', { bubbles: true }));
178
+
179
+ // The handler still fires (the click bubbled and the wired listener ran),
180
+ // but the visible flag should report that nothing was actually hidden.
181
+ // Implementation detail: we expect the second call to report visible=true
182
+ // (the series is still visible because the toggle was refused).
138
183
  expect(onLegendToggle).toHaveBeenCalledTimes(2);
139
184
  const [, visibleAfter] = onLegendToggle.mock.calls[1];
140
185
  expect(visibleAfter).toBe(true);
@@ -111,7 +111,21 @@ export function createBarList(
111
111
  measureText,
112
112
  };
113
113
 
114
- return compileBarList(currentSpec, compileOpts);
114
+ const layout = compileBarList(currentSpec, compileOpts);
115
+
116
+ // Auto-size height to content so few-row lists don't leave empty space.
117
+ if (layout.rows.length > 0) {
118
+ const lastRow = layout.rows[layout.rows.length - 1];
119
+ const contentBottom =
120
+ lastRow.y + lastRow.height + layout.chrome.bottomHeight + layout.theme.spacing.padding;
121
+ if (contentBottom < layout.height) {
122
+ layout.height = contentBottom;
123
+ layout.area.height =
124
+ contentBottom - layout.area.y - layout.chrome.bottomHeight - layout.theme.spacing.padding;
125
+ }
126
+ }
127
+
128
+ return layout;
115
129
  }
116
130
 
117
131
  function wireTooltipAndInteraction(svg: SVGSVGElement, layout: BarListLayout): () => void {
@@ -98,6 +98,7 @@ function makeTheme(isDark = false): ResolvedTheme {
98
98
  },
99
99
  borderRadius: 4,
100
100
  chrome: {
101
+ eyebrow: { fontSize: 11, fontWeight: 510, color: '#06b6d4', lineHeight: 1.4 },
101
102
  title: { fontSize: 18, fontWeight: 600, color: '#1a1a2e', lineHeight: 1.2 },
102
103
  subtitle: { fontSize: 14, fontWeight: 400, color: '#666', lineHeight: 1.3 },
103
104
  source: { fontSize: 10, fontWeight: 400, color: '#999', lineHeight: 1.2 },
@@ -0,0 +1,139 @@
1
+ import type { Annotation, ChartEventHandlers, ChartLayout } from '@opendata-ai/openchart-core';
2
+
3
+ /**
4
+ * Build a map from data-mark-id to { datum, series } so event handlers
5
+ * can look up the data row associated with a clicked/hovered mark element.
6
+ */
7
+ function buildMarkDataMap(
8
+ layout: ChartLayout,
9
+ ): Map<string, { datum: Record<string, unknown>; series?: string }> {
10
+ const map = new Map<string, { datum: Record<string, unknown>; series?: string }>();
11
+
12
+ for (let i = 0; i < layout.marks.length; i++) {
13
+ const mark = layout.marks[i];
14
+ switch (mark.type) {
15
+ case 'line':
16
+ map.set(`line-${mark.seriesKey ?? i}`, {
17
+ datum: mark.data[0] ?? {},
18
+ series: mark.seriesKey,
19
+ });
20
+ break;
21
+ case 'area':
22
+ map.set(`area-${mark.seriesKey ?? i}`, {
23
+ datum: mark.data[0] ?? {},
24
+ series: mark.seriesKey,
25
+ });
26
+ break;
27
+ case 'rect':
28
+ map.set(`rect-${i}`, { datum: mark.data });
29
+ break;
30
+ case 'arc':
31
+ map.set(`arc-${i}`, { datum: mark.data });
32
+ break;
33
+ case 'point':
34
+ map.set(`point-${i}`, { datum: mark.data });
35
+ break;
36
+ }
37
+ }
38
+
39
+ return map;
40
+ }
41
+
42
+ /**
43
+ * Wire chart event handlers (onMarkClick, onMarkHover, onMarkLeave) to mark
44
+ * elements, onLegendToggle to legend entries, and onAnnotationClick to annotation
45
+ * elements inside an SVG.
46
+ *
47
+ * Returns a cleanup function to remove all listeners.
48
+ */
49
+ export function wireChartEvents(
50
+ svg: SVGElement,
51
+ layout: ChartLayout,
52
+ specAnnotations: Annotation[],
53
+ handlers: ChartEventHandlers,
54
+ ): () => void {
55
+ const cleanups: Array<() => void> = [];
56
+ const markDataMap = buildMarkDataMap(layout);
57
+
58
+ if (handlers.onMarkClick || handlers.onMarkHover || handlers.onMarkLeave) {
59
+ const markElements = svg.querySelectorAll('[data-mark-id]');
60
+
61
+ for (const el of markElements) {
62
+ const markId = el.getAttribute('data-mark-id');
63
+ if (!markId) continue;
64
+
65
+ const markInfo = markDataMap.get(markId);
66
+ if (!markInfo) continue;
67
+
68
+ const series = markInfo.series ?? el.getAttribute('data-series') ?? undefined;
69
+
70
+ if (handlers.onMarkClick) {
71
+ const handleClick = (e: Event) => {
72
+ const mouseEvent = e as MouseEvent;
73
+ const svgRect = svg.getBoundingClientRect();
74
+ handlers.onMarkClick!({
75
+ datum: markInfo.datum,
76
+ series,
77
+ position: {
78
+ x: mouseEvent.clientX - svgRect.left,
79
+ y: mouseEvent.clientY - svgRect.top,
80
+ },
81
+ event: mouseEvent,
82
+ });
83
+ };
84
+ el.addEventListener('click', handleClick);
85
+ cleanups.push(() => el.removeEventListener('click', handleClick));
86
+ }
87
+
88
+ if (handlers.onMarkHover) {
89
+ const handleEnter = (e: Event) => {
90
+ const mouseEvent = e as MouseEvent;
91
+ const svgRect = svg.getBoundingClientRect();
92
+ handlers.onMarkHover!({
93
+ datum: markInfo.datum,
94
+ series,
95
+ position: {
96
+ x: mouseEvent.clientX - svgRect.left,
97
+ y: mouseEvent.clientY - svgRect.top,
98
+ },
99
+ event: mouseEvent,
100
+ });
101
+ };
102
+ el.addEventListener('mouseenter', handleEnter);
103
+ cleanups.push(() => el.removeEventListener('mouseenter', handleEnter));
104
+ }
105
+
106
+ if (handlers.onMarkLeave) {
107
+ const handleLeave = () => {
108
+ handlers.onMarkLeave!();
109
+ };
110
+ el.addEventListener('mouseleave', handleLeave);
111
+ cleanups.push(() => el.removeEventListener('mouseleave', handleLeave));
112
+ }
113
+ }
114
+ }
115
+
116
+ if (handlers.onAnnotationClick) {
117
+ const annotationElements = svg.querySelectorAll('.oc-annotation');
118
+
119
+ for (let i = 0; i < annotationElements.length; i++) {
120
+ const el = annotationElements[i];
121
+ const specAnnotation = specAnnotations[i];
122
+ if (!specAnnotation) continue;
123
+
124
+ const handleClick = (e: Event) => {
125
+ const mouseEvent = e as MouseEvent;
126
+ handlers.onAnnotationClick!(specAnnotation, mouseEvent);
127
+ };
128
+
129
+ el.addEventListener('click', handleClick);
130
+ cleanups.push(() => el.removeEventListener('click', handleClick));
131
+ }
132
+ }
133
+
134
+ return () => {
135
+ for (const cleanup of cleanups) {
136
+ cleanup();
137
+ }
138
+ };
139
+ }
@@ -0,0 +1,233 @@
1
+ import type { ChartLayout, TooltipContent } from '@opendata-ai/openchart-core';
2
+ import { getRepresentativeColor } from '@opendata-ai/openchart-core';
3
+ import type { TooltipManager } from '../tooltip';
4
+
5
+ interface SeriesPoint {
6
+ x: number;
7
+ y: number;
8
+ datum: Record<string, unknown>;
9
+ tooltip?: TooltipContent;
10
+ }
11
+
12
+ interface SeriesGroup {
13
+ seriesKey: string;
14
+ color: string;
15
+ pointsByX: Map<number, SeriesPoint>;
16
+ }
17
+
18
+ function snapKey(x: number): number {
19
+ return Math.round(x);
20
+ }
21
+
22
+ function collectSeriesGroups(layout: ChartLayout): SeriesGroup[] {
23
+ // Dedupe by seriesKey: area charts emit BOTH an AreaMark and a derived
24
+ // LineMark per series. Without dedupe, single-series area charts show
25
+ // "line-0" / "area-1" in the tooltip instead of the actual data fields.
26
+ // Prefer the line mark (same logic as endpoint-labels).
27
+ const byKey = new Map<string, SeriesGroup>();
28
+ const markTypeByKey = new Map<string, 'line' | 'area'>();
29
+ for (let i = 0; i < layout.marks.length; i++) {
30
+ const mark = layout.marks[i];
31
+ if ((mark.type === 'line' || mark.type === 'area') && mark.dataPoints?.length) {
32
+ const key = mark.seriesKey ?? '__default__';
33
+ const existingType = markTypeByKey.get(key);
34
+ if (existingType === 'line' && mark.type === 'area') continue;
35
+ const color = mark.type === 'line' ? mark.stroke : getRepresentativeColor(mark.fill);
36
+ const pointsByX = new Map<number, SeriesPoint>();
37
+ for (const dp of mark.dataPoints) {
38
+ pointsByX.set(snapKey(dp.x), { ...dp });
39
+ }
40
+ byKey.set(key, { seriesKey: key, color, pointsByX });
41
+ markTypeByKey.set(key, mark.type);
42
+ }
43
+ }
44
+ return Array.from(byKey.values());
45
+ }
46
+
47
+ function collectSnapXs(groups: SeriesGroup[]): number[] {
48
+ const seen = new Set<number>();
49
+ for (const g of groups) {
50
+ for (const k of g.pointsByX.keys()) seen.add(k);
51
+ }
52
+ return Array.from(seen).sort((a, b) => a - b);
53
+ }
54
+
55
+ function findNearestX(sortedXs: number[], x: number): number | null {
56
+ if (sortedXs.length === 0) return null;
57
+ let lo = 0;
58
+ let hi = sortedXs.length - 1;
59
+ while (lo < hi) {
60
+ const mid = (lo + hi) >> 1;
61
+ if (sortedXs[mid] < x) lo = mid + 1;
62
+ else hi = mid;
63
+ }
64
+ const candidate = sortedXs[lo];
65
+ if (lo > 0) {
66
+ const prev = sortedXs[lo - 1];
67
+ if (Math.abs(prev - x) < Math.abs(candidate - x)) return prev;
68
+ }
69
+ return candidate;
70
+ }
71
+
72
+ function buildSliceTooltip(
73
+ hits: Array<{ group: SeriesGroup; point: SeriesPoint }>,
74
+ ): TooltipContent | null {
75
+ if (hits.length === 0) return null;
76
+
77
+ const title = hits[0].point.tooltip?.title;
78
+ const fields: Array<{ label: string; value: string; color?: string }> = [];
79
+
80
+ const isMulti = hits.length > 1;
81
+ for (const { group, point } of hits) {
82
+ const tip = point.tooltip;
83
+ if (!tip) continue;
84
+ if (isMulti) {
85
+ const yField =
86
+ tip.fields.find((f) => !f.color && f.label !== title) ??
87
+ tip.fields[tip.fields.length - 1] ??
88
+ null;
89
+ if (!yField) continue;
90
+ fields.push({
91
+ label: group.seriesKey,
92
+ value: yField.value,
93
+ color: group.color,
94
+ });
95
+ } else {
96
+ for (const f of tip.fields) {
97
+ fields.push({ ...f, color: f.color ?? group.color });
98
+ }
99
+ }
100
+ }
101
+
102
+ if (fields.length === 0) return null;
103
+ return { title, fields };
104
+ }
105
+
106
+ /**
107
+ * Wire snap-to-x multi-series tooltip events for line/area charts.
108
+ * On mousemove over the chart area we find the nearest x in the union of all
109
+ * series, render one snap dot per series at that x, and show one merged
110
+ * tooltip listing every series' value.
111
+ */
112
+ export function wireVoronoiTooltipEvents(
113
+ svg: SVGElement,
114
+ layout: ChartLayout,
115
+ tooltipManager: TooltipManager,
116
+ ): () => void {
117
+ const overlay = svg.querySelector('[data-voronoi-overlay]');
118
+ if (!overlay) return () => {};
119
+
120
+ const groups = collectSeriesGroups(layout);
121
+ if (groups.length === 0) return () => {};
122
+
123
+ const snapXs = collectSnapXs(groups);
124
+ if (snapXs.length === 0) return () => {};
125
+
126
+ const crosshair = svg.querySelector('[data-crosshair]') as SVGLineElement | null;
127
+ const dotsLayer = svg.querySelector('[data-snap-dots]') as SVGGElement | null;
128
+
129
+ const dots: SVGCircleElement[] = [];
130
+ if (dotsLayer) {
131
+ while (dotsLayer.firstChild) dotsLayer.removeChild(dotsLayer.firstChild);
132
+ for (const group of groups) {
133
+ const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
134
+ circle.setAttribute('r', '4');
135
+ circle.setAttribute('fill', layout.theme.colors.background);
136
+ circle.setAttribute('stroke', group.color);
137
+ circle.setAttribute('stroke-width', '2');
138
+ circle.setAttribute('pointer-events', 'none');
139
+ circle.style.display = 'none';
140
+ dotsLayer.appendChild(circle);
141
+ dots.push(circle);
142
+ }
143
+ }
144
+
145
+ const positionAt = (svgX: number, _svgY: number, viewBoxToContainer: number): boolean => {
146
+ const snappedX = findNearestX(snapXs, svgX);
147
+ if (snappedX === null) return false;
148
+
149
+ const hits: Array<{ group: SeriesGroup; point: SeriesPoint }> = [];
150
+ let anchorY = 0;
151
+ let anchorCount = 0;
152
+ for (let i = 0; i < groups.length; i++) {
153
+ const group = groups[i];
154
+ const point = group.pointsByX.get(snappedX);
155
+ const dot = dots[i];
156
+ if (point) {
157
+ hits.push({ group, point });
158
+ anchorY += point.y;
159
+ anchorCount += 1;
160
+ if (dot) {
161
+ dot.setAttribute('cx', String(point.x));
162
+ dot.setAttribute('cy', String(point.y));
163
+ dot.style.display = '';
164
+ }
165
+ } else if (dot) {
166
+ dot.style.display = 'none';
167
+ }
168
+ }
169
+
170
+ if (crosshair) {
171
+ crosshair.setAttribute('x1', String(snappedX));
172
+ crosshair.setAttribute('x2', String(snappedX));
173
+ crosshair.style.display = '';
174
+ }
175
+
176
+ const tooltip = buildSliceTooltip(hits);
177
+ if (!tooltip) return false;
178
+
179
+ const containerAnchorX = snappedX * viewBoxToContainer;
180
+ const containerAnchorY = anchorCount > 0 ? (anchorY / anchorCount) * viewBoxToContainer : 0;
181
+ tooltipManager.show(tooltip, containerAnchorX, containerAnchorY, {
182
+ placement: 'right',
183
+ });
184
+ return true;
185
+ };
186
+
187
+ const toSvgCoords = (clientX: number, clientY: number) => {
188
+ const svgEl = svg as unknown as SVGSVGElement;
189
+ const svgRect = svgEl.getBoundingClientRect();
190
+ const viewBox = svgEl.viewBox?.baseVal;
191
+ const scaleX = viewBox?.width && svgRect.width ? viewBox.width / svgRect.width : 1;
192
+ const scaleY = viewBox?.height && svgRect.height ? viewBox.height / svgRect.height : 1;
193
+ const svgX = (clientX - svgRect.left) * scaleX;
194
+ const svgY = (clientY - svgRect.top) * scaleY;
195
+ const viewBoxToContainer = scaleX > 0 ? 1 / scaleX : 1;
196
+ return { svgX, svgY, viewBoxToContainer };
197
+ };
198
+
199
+ const handleMouseMove = (e: Event) => {
200
+ const me = e as MouseEvent;
201
+ const { svgX, svgY, viewBoxToContainer } = toSvgCoords(me.clientX, me.clientY);
202
+ positionAt(svgX, svgY, viewBoxToContainer);
203
+ };
204
+
205
+ const hideAll = () => {
206
+ if (crosshair) crosshair.style.display = 'none';
207
+ for (const dot of dots) dot.style.display = 'none';
208
+ tooltipManager.hide();
209
+ };
210
+
211
+ const handleTouch = (e: Event) => {
212
+ const te = e as TouchEvent;
213
+ if (te.touches.length === 0) return;
214
+ const t = te.touches[0];
215
+ const { svgX, svgY, viewBoxToContainer } = toSvgCoords(t.clientX, t.clientY);
216
+ if (te.cancelable) te.preventDefault();
217
+ positionAt(svgX, svgY, viewBoxToContainer);
218
+ };
219
+
220
+ overlay.addEventListener('mousemove', handleMouseMove);
221
+ overlay.addEventListener('mouseleave', hideAll);
222
+ overlay.addEventListener('touchstart', handleTouch, { passive: false });
223
+ overlay.addEventListener('touchmove', handleTouch, { passive: false });
224
+ overlay.addEventListener('touchend', hideAll);
225
+
226
+ return () => {
227
+ overlay.removeEventListener('mousemove', handleMouseMove);
228
+ overlay.removeEventListener('mouseleave', hideAll);
229
+ overlay.removeEventListener('touchstart', handleTouch);
230
+ overlay.removeEventListener('touchmove', handleTouch);
231
+ overlay.removeEventListener('touchend', hideAll);
232
+ };
233
+ }