@opendata-ai/openchart-core 6.28.5 → 7.0.0
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/index.d.ts +671 -111
- package/dist/index.js +163 -66
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
- package/src/colors/__tests__/contrast.test.ts +2 -2
- package/src/colors/__tests__/palettes.test.ts +22 -2
- package/src/colors/index.ts +1 -0
- package/src/colors/palettes.ts +52 -20
- package/src/helpers/spec-builders.ts +3 -1
- package/src/layout/chrome.ts +91 -10
- package/src/styles/base.css +11 -1
- package/src/styles/chrome.css +127 -2
- package/src/styles/dark.css +27 -10
- package/src/styles/tokens.css +57 -14
- package/src/styles/tooltip.css +66 -22
- package/src/theme/__tests__/dark-mode.test.ts +53 -8
- package/src/theme/__tests__/defaults.test.ts +43 -17
- package/src/theme/dark-mode.ts +76 -16
- package/src/theme/defaults.ts +44 -30
- package/src/theme/index.ts +1 -1
- package/src/theme/resolve.ts +2 -0
- package/src/types/__tests__/spec.test.ts +85 -18
- package/src/types/index.ts +16 -0
- package/src/types/layout.ts +151 -5
- package/src/types/spec.ts +517 -85
- package/src/types/theme.ts +5 -0
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
|
@@ -58,8 +58,8 @@ describe('findAccessibleColor', () => {
|
|
|
58
58
|
});
|
|
59
59
|
|
|
60
60
|
it('lightens a dark color to meet contrast on dark background', () => {
|
|
61
|
-
const result = findAccessibleColor('#333333', '#
|
|
62
|
-
const ratio = contrastRatio(result, '#
|
|
61
|
+
const result = findAccessibleColor('#333333', '#09090b');
|
|
62
|
+
const ratio = contrastRatio(result, '#09090b');
|
|
63
63
|
expect(ratio).toBeGreaterThanOrEqual(4.5);
|
|
64
64
|
});
|
|
65
65
|
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { adaptColorForDarkMode } from '../../theme/dark-mode';
|
|
2
3
|
import {
|
|
4
|
+
ACHROMATIC_RAMP,
|
|
3
5
|
CATEGORICAL_PALETTE,
|
|
4
6
|
DIVERGING_PALETTES,
|
|
5
7
|
DIVERGING_RED_BLUE,
|
|
@@ -8,8 +10,12 @@ import {
|
|
|
8
10
|
} from '../palettes';
|
|
9
11
|
|
|
10
12
|
describe('palettes', () => {
|
|
11
|
-
it('categorical palette has
|
|
12
|
-
expect(CATEGORICAL_PALETTE).toHaveLength(
|
|
13
|
+
it('categorical palette has 9 colors (cyan-led OKLCH ramp)', () => {
|
|
14
|
+
expect(CATEGORICAL_PALETTE).toHaveLength(9);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('categorical palette leads with cyan #06b6d4', () => {
|
|
18
|
+
expect(CATEGORICAL_PALETTE[0]).toBe('#06b6d4');
|
|
13
19
|
});
|
|
14
20
|
|
|
15
21
|
it('categorical palette colors are valid hex', () => {
|
|
@@ -18,6 +24,20 @@ describe('palettes', () => {
|
|
|
18
24
|
}
|
|
19
25
|
});
|
|
20
26
|
|
|
27
|
+
it('every categorical color round-trips cleanly through the dark-mode adapter', () => {
|
|
28
|
+
// Guard against hex strings that fail to parse (e.g. accidental oklch).
|
|
29
|
+
for (const color of CATEGORICAL_PALETTE) {
|
|
30
|
+
const adapted = adaptColorForDarkMode(color, '#ffffff', ACHROMATIC_RAMP.bg);
|
|
31
|
+
expect(adapted).toMatch(/^#[0-9a-f]{6}$/i);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('exports an achromatic ramp keyed by token name', () => {
|
|
36
|
+
expect(ACHROMATIC_RAMP.bg).toBe('#09090b');
|
|
37
|
+
expect(ACHROMATIC_RAMP.fg).toBe('#f7f8f8');
|
|
38
|
+
expect(ACHROMATIC_RAMP.fgMuted).toBe('#a1a1aa');
|
|
39
|
+
});
|
|
40
|
+
|
|
21
41
|
it('sequential palettes have expected keys', () => {
|
|
22
42
|
expect(Object.keys(SEQUENTIAL_PALETTES)).toEqual(
|
|
23
43
|
expect.arrayContaining(['blue', 'green', 'orange', 'purple']),
|
package/src/colors/index.ts
CHANGED
package/src/colors/palettes.ts
CHANGED
|
@@ -1,38 +1,70 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Color palettes for @opendata-ai.
|
|
3
3
|
*
|
|
4
|
-
* Categorical palette
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Categorical palette leads with cyan and uses an OKLCH-balanced multi-hue
|
|
5
|
+
* ramp tuned to L≈0.70, C≈0.13–0.15. Hex values are precomputed sRGB
|
|
6
|
+
* (rather than raw `oklch()` strings) because raw OKLCH strings parse
|
|
7
|
+
* unreliably through d3-color and the dark-mode adapter's hsl-based
|
|
8
|
+
* binary search. Source OKLCH values are documented inline so the ramp
|
|
9
|
+
* can be regenerated if needed.
|
|
10
|
+
*
|
|
11
|
+
* Convention for low-cardinality charts (1 / 2-4 / 5+ series) is enforced
|
|
12
|
+
* by chart-type renderer logic, not the palette itself. The palette stays
|
|
13
|
+
* a flat 9-color ramp consumed by index. The convention is:
|
|
14
|
+
* - 1 series: cyan only
|
|
15
|
+
* - 2-4 series: cyan + zinc-400/500/600 (achromatic)
|
|
16
|
+
* - 5+ series: full multi-hue ramp below
|
|
9
17
|
*
|
|
10
18
|
* Sequential palettes: 5-7 stops from light to dark.
|
|
11
19
|
* Diverging palettes: 7 stops with a neutral midpoint.
|
|
12
20
|
*/
|
|
13
21
|
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Achromatic ramp (zinc-based, dark-mode canonical)
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Achromatic surface and supporting-series ramp. Values are the canonical
|
|
28
|
+
* dark-mode tokens; light-mode equivalents are derived per token in
|
|
29
|
+
* `theme/dark-mode.ts` and the CSS partials, not mirrored here.
|
|
30
|
+
*/
|
|
31
|
+
export const ACHROMATIC_RAMP = {
|
|
32
|
+
fg: '#f7f8f8', // primary text
|
|
33
|
+
fg2: '#d0d6e0', // body text
|
|
34
|
+
fgMuted: '#a1a1aa', // secondary series (zinc-400)
|
|
35
|
+
fgSubtle: '#71717a', // tertiary series (zinc-500)
|
|
36
|
+
fgFaint: '#52525b', // quaternary series (zinc-600)
|
|
37
|
+
secondary: '#27272a', // hover / raised surface
|
|
38
|
+
card: '#111113', // card surface
|
|
39
|
+
bg: '#09090b', // canvas
|
|
40
|
+
} as const;
|
|
41
|
+
|
|
14
42
|
// ---------------------------------------------------------------------------
|
|
15
43
|
// Categorical
|
|
16
44
|
// ---------------------------------------------------------------------------
|
|
17
45
|
|
|
18
46
|
/**
|
|
19
|
-
* Default categorical palette.
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
47
|
+
* Default categorical palette. Cyan-led OKLCH-balanced multi-hue ramp.
|
|
48
|
+
*
|
|
49
|
+
* Hex values precomputed from OKLCH via the standard OKLab -> linear sRGB
|
|
50
|
+
* -> sRGB pipeline. Documented OKLCH source values are the contract; if
|
|
51
|
+
* the conversion math changes, regenerate from the source rather than
|
|
52
|
+
* editing hex literals directly.
|
|
24
53
|
*/
|
|
54
|
+
// Order interleaves hues so adjacent slots sit at least ~70° apart on the
|
|
55
|
+
// OKLCH wheel. Cyan and teal differ by only ~10° and read as the same color
|
|
56
|
+
// when placed side-by-side (e.g. on a pie slice or stacked area), so the
|
|
57
|
+
// teal slot is pushed deep into the ramp where it won't neighbor cyan.
|
|
25
58
|
export const CATEGORICAL_PALETTE = [
|
|
26
|
-
'#
|
|
27
|
-
'#
|
|
28
|
-
'#
|
|
29
|
-
'#
|
|
30
|
-
'#
|
|
31
|
-
'#
|
|
32
|
-
'#
|
|
33
|
-
'#
|
|
34
|
-
'#
|
|
35
|
-
'#858078', // warm gray
|
|
59
|
+
'#06b6d4', // cyan, primary accent (sRGB literal, ~205°)
|
|
60
|
+
'#eb7289', // rose — oklch(70% 0.15 10)
|
|
61
|
+
'#3bb974', // emerald — oklch(70% 0.15 155)
|
|
62
|
+
'#ad87ed', // violet — oklch(70% 0.15 300)
|
|
63
|
+
'#e69c3a', // amber — oklch(75% 0.14 70)
|
|
64
|
+
'#4ba3f7', // sky — oklch(70% 0.15 250)
|
|
65
|
+
'#eb8656', // orange — oklch(72% 0.14 45)
|
|
66
|
+
'#8494fa', // indigo — oklch(70% 0.15 275)
|
|
67
|
+
'#00b9c3', // teal — oklch(70% 0.15 200)
|
|
36
68
|
] as const;
|
|
37
69
|
|
|
38
70
|
export type CategoricalPalette = typeof CATEGORICAL_PALETTE;
|
|
@@ -167,7 +167,9 @@ function buildChartSpec(
|
|
|
167
167
|
encoding: Encoding,
|
|
168
168
|
options?: ChartBuilderOptions,
|
|
169
169
|
): ChartSpec {
|
|
170
|
-
|
|
170
|
+
// Cast needed because the generic discriminated union can't be constructed
|
|
171
|
+
// from a runtime MarkType variable — the caller always pairs mark+encoding correctly.
|
|
172
|
+
const spec = { mark, data, encoding } as ChartSpec;
|
|
171
173
|
if (options?.chrome) spec.chrome = options.chrome;
|
|
172
174
|
if (options?.annotations) spec.annotations = options.annotations;
|
|
173
175
|
if (options?.responsive !== undefined) spec.responsive = options.responsive;
|
package/src/layout/chrome.ts
CHANGED
|
@@ -164,6 +164,12 @@ function estimateLineCount(
|
|
|
164
164
|
* @param measureText - Optional real text measurement function from the adapter.
|
|
165
165
|
* @param chromeMode - Chrome display mode: full, compact (title only), or hidden.
|
|
166
166
|
* @param padding - Override padding (for scaled padding from dimensions).
|
|
167
|
+
* @param watermark - Whether the brand watermark renders (affects bottom space).
|
|
168
|
+
* @param bottomLegendHeight - Reserved height for a bottom-positioned legend
|
|
169
|
+
* (legend bounds height + gap). When > 0, source/byline/footer y positions
|
|
170
|
+
* are shifted down by this amount so the chrome stacks BELOW the legend
|
|
171
|
+
* rather than colliding with it. The returned `bottomHeight` includes this
|
|
172
|
+
* reservation, so callers should not double-reserve it in margin math.
|
|
167
173
|
*/
|
|
168
174
|
export function computeChrome(
|
|
169
175
|
chrome: Chrome | undefined,
|
|
@@ -173,6 +179,7 @@ export function computeChrome(
|
|
|
173
179
|
chromeMode: ChromeMode = 'full',
|
|
174
180
|
padding?: number,
|
|
175
181
|
watermark: boolean = true,
|
|
182
|
+
bottomLegendHeight: number = 0,
|
|
176
183
|
): ResolvedChrome {
|
|
177
184
|
if (!chrome || chromeMode === 'hidden') {
|
|
178
185
|
// Brand watermark is also skipped at cramped sizes (height < 200px triggers
|
|
@@ -191,7 +198,30 @@ export function computeChrome(
|
|
|
191
198
|
|
|
192
199
|
// Track vertical cursor for top elements
|
|
193
200
|
let topY = pad;
|
|
194
|
-
const topElements: Partial<Pick<ResolvedChrome, 'title' | 'subtitle'>> = {};
|
|
201
|
+
const topElements: Partial<Pick<ResolvedChrome, 'eyebrow' | 'title' | 'subtitle'>> = {};
|
|
202
|
+
|
|
203
|
+
// Eyebrow (hidden in compact mode — same rule as subtitle, keeps the
|
|
204
|
+
// title alone at narrow viewports).
|
|
205
|
+
const eyebrowNorm = chromeMode === 'compact' ? null : normalizeChromeText(chrome.eyebrow);
|
|
206
|
+
if (eyebrowNorm) {
|
|
207
|
+
const style = buildTextStyle(
|
|
208
|
+
theme.chrome.eyebrow,
|
|
209
|
+
fontFamily,
|
|
210
|
+
theme.chrome.eyebrow.color,
|
|
211
|
+
width,
|
|
212
|
+
eyebrowNorm.style,
|
|
213
|
+
);
|
|
214
|
+
const lineCount = estimateLineCount(eyebrowNorm.text, style, maxWidth, measureText);
|
|
215
|
+
const element: ResolvedChromeElement = {
|
|
216
|
+
text: eyebrowNorm.text,
|
|
217
|
+
x: pad + (eyebrowNorm.offset?.dx ?? 0),
|
|
218
|
+
y: topY + (eyebrowNorm.offset?.dy ?? 0),
|
|
219
|
+
maxWidth,
|
|
220
|
+
style,
|
|
221
|
+
};
|
|
222
|
+
topElements.eyebrow = element;
|
|
223
|
+
topY += estimateTextHeight(style.fontSize, lineCount, style.lineHeight) + chromeGap;
|
|
224
|
+
}
|
|
195
225
|
|
|
196
226
|
// Title
|
|
197
227
|
const titleNorm = normalizeChromeText(chrome.title);
|
|
@@ -239,7 +269,7 @@ export function computeChrome(
|
|
|
239
269
|
|
|
240
270
|
// Add chromeToChart gap if there are any top elements. Tighten on narrow
|
|
241
271
|
// viewports so the subtitle doesn't float far above a legend or chart area.
|
|
242
|
-
const hasTopChrome = titleNorm || subtitleNorm;
|
|
272
|
+
const hasTopChrome = eyebrowNorm || titleNorm || subtitleNorm;
|
|
243
273
|
const chromeToChart =
|
|
244
274
|
width < COMPACT_WIDTH ? Math.min(theme.spacing.chromeToChart, 2) : theme.spacing.chromeToChart;
|
|
245
275
|
const topHeight = hasTopChrome ? topY - pad + chromeToChart - chromeGap : 0;
|
|
@@ -250,7 +280,12 @@ export function computeChrome(
|
|
|
250
280
|
let compactBottom = 0;
|
|
251
281
|
if (watermark && width >= BRAND_MIN_WIDTH) {
|
|
252
282
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
253
|
-
compactBottom = theme.spacing.chartToFooter + brandHeight + pad;
|
|
283
|
+
compactBottom = theme.spacing.chartToFooter + brandHeight + pad + bottomLegendHeight;
|
|
284
|
+
} else if (bottomLegendHeight > 0) {
|
|
285
|
+
// No bottom chrome content but a bottom legend was reserved upstream.
|
|
286
|
+
// Surface the reservation through bottomHeight so margin math stays
|
|
287
|
+
// additive and consistent with full mode.
|
|
288
|
+
compactBottom = bottomLegendHeight;
|
|
254
289
|
}
|
|
255
290
|
return {
|
|
256
291
|
topHeight,
|
|
@@ -259,9 +294,14 @@ export function computeChrome(
|
|
|
259
294
|
};
|
|
260
295
|
}
|
|
261
296
|
|
|
262
|
-
// Bottom elements: source, byline, footer
|
|
263
|
-
//
|
|
264
|
-
|
|
297
|
+
// Bottom elements: source, byline, footer, and an optional custom brand
|
|
298
|
+
// (`chrome.brand`). When a custom brand is supplied it suppresses the default
|
|
299
|
+
// tryOpenData.ai watermark and renders right-anchored on the same baseline.
|
|
300
|
+
const brandNorm = normalizeChromeText(chrome.brand);
|
|
301
|
+
const showWatermark = watermark && !brandNorm;
|
|
302
|
+
// Reserve space on the right for the brand watermark or custom brand so the
|
|
303
|
+
// left-anchored bottom chrome items don't overlap.
|
|
304
|
+
const shouldReserveBrandWidth = (showWatermark || !!brandNorm) && width >= BRAND_MIN_WIDTH;
|
|
265
305
|
const bottomMaxWidth = maxWidth - (shouldReserveBrandWidth ? BRAND_RESERVE_WIDTH : 0);
|
|
266
306
|
const bottomElements: Partial<Pick<ResolvedChrome, 'source' | 'byline' | 'footer'>> = {};
|
|
267
307
|
let bottomHeight = 0;
|
|
@@ -301,6 +341,9 @@ export function computeChrome(
|
|
|
301
341
|
|
|
302
342
|
if (bottomItems.length > 0) {
|
|
303
343
|
bottomHeight += theme.spacing.chartToFooter;
|
|
344
|
+
// Push bottom chrome below the bottom legend (if reserved). Stored y values
|
|
345
|
+
// include this offset so renderers don't need to know about the legend band.
|
|
346
|
+
bottomHeight += bottomLegendHeight;
|
|
304
347
|
|
|
305
348
|
for (const item of bottomItems) {
|
|
306
349
|
const style = buildTextStyle(
|
|
@@ -332,7 +375,7 @@ export function computeChrome(
|
|
|
332
375
|
// Ensure bottom height accommodates the brand watermark, which renders
|
|
333
376
|
// at the same Y as the first bottom chrome item but is taller (20px font
|
|
334
377
|
// vs 12px source). Without this, the brand overflows the SVG viewBox.
|
|
335
|
-
if (
|
|
378
|
+
if (showWatermark && width >= BRAND_MIN_WIDTH) {
|
|
336
379
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
337
380
|
// firstItemY is chartToFooter (the Y offset of the first bottom item).
|
|
338
381
|
// The brand needs brandHeight below that point; bottom chrome content
|
|
@@ -345,11 +388,48 @@ export function computeChrome(
|
|
|
345
388
|
|
|
346
389
|
// Add bottom padding
|
|
347
390
|
bottomHeight += pad;
|
|
348
|
-
} else if (
|
|
391
|
+
} else if (showWatermark && width >= BRAND_MIN_WIDTH) {
|
|
349
392
|
// No bottom chrome items, but brand watermark still renders.
|
|
350
|
-
// Reserve space: chartToFooter gap + brand text height + padding
|
|
393
|
+
// Reserve space: chartToFooter gap + brand text height + padding,
|
|
394
|
+
// plus the bottom-legend reservation so the watermark sits below it.
|
|
351
395
|
const brandHeight = estimateTextHeight(BRAND_FONT_SIZE, 1);
|
|
352
|
-
bottomHeight = theme.spacing.chartToFooter + brandHeight + pad;
|
|
396
|
+
bottomHeight = theme.spacing.chartToFooter + brandHeight + pad + bottomLegendHeight;
|
|
397
|
+
} else if (bottomLegendHeight > 0) {
|
|
398
|
+
// No bottom chrome content and no watermark, but a bottom legend was
|
|
399
|
+
// reserved upstream. Surface the reservation so callers don't need to
|
|
400
|
+
// re-add it on top of bottomHeight.
|
|
401
|
+
bottomHeight = bottomLegendHeight;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Custom brand is right-anchored on the same row as the first bottom chrome
|
|
405
|
+
// item. Compute its style/position once we know whether bottom chrome exists.
|
|
406
|
+
let brandElement: ResolvedChromeElement | undefined;
|
|
407
|
+
if (brandNorm && width >= BRAND_MIN_WIDTH) {
|
|
408
|
+
const brandStyle = buildTextStyle(
|
|
409
|
+
theme.chrome.footer,
|
|
410
|
+
fontFamily,
|
|
411
|
+
theme.chrome.footer.color,
|
|
412
|
+
width,
|
|
413
|
+
brandNorm.style,
|
|
414
|
+
);
|
|
415
|
+
brandStyle.textAnchor = 'end';
|
|
416
|
+
// Y matches the first bottom item (chartToFooter offset). When there are
|
|
417
|
+
// no bottom items, the brand becomes the sole footer element and we need
|
|
418
|
+
// to allocate the same baseline.
|
|
419
|
+
const brandY =
|
|
420
|
+
bottomItems.length > 0 ? theme.spacing.chartToFooter : theme.spacing.chartToFooter;
|
|
421
|
+
brandElement = {
|
|
422
|
+
text: brandNorm.text,
|
|
423
|
+
x: width - pad + (brandNorm.offset?.dx ?? 0),
|
|
424
|
+
y: brandY + (brandNorm.offset?.dy ?? 0),
|
|
425
|
+
maxWidth: BRAND_RESERVE_WIDTH,
|
|
426
|
+
style: brandStyle,
|
|
427
|
+
};
|
|
428
|
+
// Ensure bottomHeight reserves space when brand is the only footer item.
|
|
429
|
+
if (bottomItems.length === 0) {
|
|
430
|
+
const brandHeight = estimateTextHeight(brandStyle.fontSize, 1, brandStyle.lineHeight);
|
|
431
|
+
bottomHeight = theme.spacing.chartToFooter + brandHeight + pad;
|
|
432
|
+
}
|
|
353
433
|
}
|
|
354
434
|
|
|
355
435
|
return {
|
|
@@ -357,5 +437,6 @@ export function computeChrome(
|
|
|
357
437
|
bottomHeight,
|
|
358
438
|
...topElements,
|
|
359
439
|
...bottomElements,
|
|
440
|
+
...(brandElement ? { brand: brandElement } : {}),
|
|
360
441
|
};
|
|
361
442
|
}
|
package/src/styles/base.css
CHANGED
|
@@ -4,17 +4,21 @@
|
|
|
4
4
|
|
|
5
5
|
.oc-chart-root {
|
|
6
6
|
width: 100%;
|
|
7
|
+
height: 100%;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
.oc-table-root,
|
|
10
11
|
.oc-graph-root,
|
|
11
12
|
.oc-sankey-root,
|
|
12
|
-
.oc-tilemap-root,
|
|
13
13
|
.oc-barlist-root {
|
|
14
14
|
width: 100%;
|
|
15
15
|
height: 100%;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
.oc-tilemap-root {
|
|
19
|
+
width: 100%;
|
|
20
|
+
}
|
|
21
|
+
|
|
18
22
|
.oc-table-root {
|
|
19
23
|
overflow: auto;
|
|
20
24
|
}
|
|
@@ -33,6 +37,12 @@
|
|
|
33
37
|
* BarList SVG container
|
|
34
38
|
* --------------------------------------------------------------------------- */
|
|
35
39
|
|
|
40
|
+
.oc-tilemap {
|
|
41
|
+
display: block;
|
|
42
|
+
width: 100%;
|
|
43
|
+
height: auto;
|
|
44
|
+
}
|
|
45
|
+
|
|
36
46
|
.oc-barlist {
|
|
37
47
|
display: block;
|
|
38
48
|
width: 100%;
|
package/src/styles/chrome.css
CHANGED
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/* ---------------------------------------------------------------------------
|
|
2
|
-
* Chrome (title, subtitle, source, footer)
|
|
2
|
+
* Chrome (eyebrow, title, subtitle, source, byline, footer, brand)
|
|
3
3
|
* --------------------------------------------------------------------------- */
|
|
4
4
|
|
|
5
5
|
.oc-chrome {
|
|
6
6
|
font-family: var(--oc-font-family);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
.oc-eyebrow {
|
|
10
|
+
font-size: var(--oc-eyebrow-size);
|
|
11
|
+
font-weight: var(--oc-eyebrow-weight);
|
|
12
|
+
letter-spacing: var(--oc-eyebrow-tracking);
|
|
13
|
+
text-transform: uppercase;
|
|
14
|
+
fill: var(--oc-accent);
|
|
15
|
+
}
|
|
16
|
+
|
|
9
17
|
.oc-title {
|
|
10
18
|
font-size: var(--oc-title-size);
|
|
11
19
|
font-weight: var(--oc-title-weight);
|
|
@@ -16,7 +24,7 @@
|
|
|
16
24
|
.oc-subtitle {
|
|
17
25
|
font-size: var(--oc-subtitle-size);
|
|
18
26
|
font-weight: var(--oc-subtitle-weight);
|
|
19
|
-
fill: var(--oc-text-
|
|
27
|
+
fill: var(--oc-text-muted);
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
.oc-source,
|
|
@@ -27,6 +35,123 @@
|
|
|
27
35
|
fill: var(--oc-text-muted);
|
|
28
36
|
}
|
|
29
37
|
|
|
38
|
+
.oc-brand {
|
|
39
|
+
font-size: 11px;
|
|
40
|
+
font-weight: 510;
|
|
41
|
+
letter-spacing: 0.02em;
|
|
42
|
+
fill: var(--oc-text-faint);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.oc-brand-dot {
|
|
46
|
+
fill: var(--oc-accent);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.oc-eyebrow-dot {
|
|
50
|
+
fill: var(--oc-accent);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* ---------------------------------------------------------------------------
|
|
54
|
+
* Metric bar (.oc-metrics) — optional row of label/value cells above chart
|
|
55
|
+
* --------------------------------------------------------------------------- */
|
|
56
|
+
|
|
57
|
+
.oc-metrics {
|
|
58
|
+
font-family: var(--oc-font-family);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.oc-metric-label {
|
|
62
|
+
font-size: 10px;
|
|
63
|
+
font-weight: 510;
|
|
64
|
+
letter-spacing: 0.08em;
|
|
65
|
+
text-transform: uppercase;
|
|
66
|
+
fill: var(--oc-text-muted);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.oc-metric-value {
|
|
70
|
+
font-size: 22px;
|
|
71
|
+
font-weight: 510;
|
|
72
|
+
letter-spacing: -0.01em;
|
|
73
|
+
fill: var(--oc-text);
|
|
74
|
+
font-variant-numeric: tabular-nums;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.oc-metric-delta-up {
|
|
78
|
+
fill: var(--oc-positive);
|
|
79
|
+
font-size: 12px;
|
|
80
|
+
font-weight: 510;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.oc-metric-delta-down {
|
|
84
|
+
fill: var(--oc-negative);
|
|
85
|
+
font-size: 12px;
|
|
86
|
+
font-weight: 510;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
.oc-metric-secondary {
|
|
90
|
+
fill: var(--oc-positive);
|
|
91
|
+
font-size: 12px;
|
|
92
|
+
font-weight: 400;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ---------------------------------------------------------------------------
|
|
96
|
+
* Axis tick labels — inline variant sits above each gridline at the
|
|
97
|
+
* chart-area left edge (no left gutter). Used by line/area y-axes.
|
|
98
|
+
* --------------------------------------------------------------------------- */
|
|
99
|
+
|
|
100
|
+
.oc-axis-tick-inline {
|
|
101
|
+
font-size: 11px;
|
|
102
|
+
font-weight: 400;
|
|
103
|
+
fill: var(--oc-text-muted);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/* ---------------------------------------------------------------------------
|
|
107
|
+
* Endpoint labels (right-side per-series column for line/area charts)
|
|
108
|
+
*
|
|
109
|
+
* Swatch idiom: a thicker colored stroke segment with an optional thinner
|
|
110
|
+
* lighter strip below. Mirrors the refreshed traditional legend swatch so a
|
|
111
|
+
* single chart never shows two swatch idioms (mock-2 alignment). Per-series
|
|
112
|
+
* color is stamped inline by the renderer; CSS tokens here cover the muted
|
|
113
|
+
* value text and the leader line so theme overrides flow through naturally.
|
|
114
|
+
* --------------------------------------------------------------------------- */
|
|
115
|
+
|
|
116
|
+
.oc-endpoint-labels {
|
|
117
|
+
font-family: var(--oc-font-family);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.oc-endpoint-label {
|
|
121
|
+
/* Color set inline per series; this fallback only kicks in if the renderer
|
|
122
|
+
ever omits the inline fill. */
|
|
123
|
+
fill: var(--oc-endpoint-label-color, var(--oc-text));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.oc-endpoint-value {
|
|
127
|
+
fill: var(--oc-endpoint-value-color, var(--oc-text-muted));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.oc-endpoint-leader {
|
|
131
|
+
stroke: var(--oc-endpoint-leader-color, currentColor);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.oc-endpoint-marker {
|
|
135
|
+
/* Fill/stroke set inline (open-ring style: bg fill, series stroke). */
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.oc-endpoint-swatch {
|
|
139
|
+
/* Color set inline per series. */
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ---------------------------------------------------------------------------
|
|
143
|
+
* Annotation dot + subtitle (text-annotation extensions)
|
|
144
|
+
* --------------------------------------------------------------------------- */
|
|
145
|
+
|
|
146
|
+
.oc-annotation-dot {
|
|
147
|
+
/* Fill/stroke resolved by the engine. Open-ring default = bg fill, series
|
|
148
|
+
stroke. */
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.oc-annotation-subtitle {
|
|
152
|
+
fill: var(--oc-annotation-subtitle-color, var(--oc-text-muted));
|
|
153
|
+
}
|
|
154
|
+
|
|
30
155
|
/* ---------------------------------------------------------------------------
|
|
31
156
|
* Table footer chrome
|
|
32
157
|
* --------------------------------------------------------------------------- */
|