Files
tabler/shared/ui/Chart.astro
T

101 lines
3.6 KiB
Plaintext

---
import charts from '@data/charts.json';
import { chartStyle, chartConfig, type ChartData } from '@shared/lib/chart-script';
import CaptureScript from '../components/CaptureScript.astro';
interface Props {
chartId: string;
id?: string;
size?: 'sm' | 'lg';
class?: string;
height?: number;
label: string;
}
const { chartId, id = chartId, size, class: classProp, height: heightProp, label } = Astro.props;
const chartsData = charts as Record<string, ChartData>;
let data = chartsData[chartId];
let className = classProp;
if (data?.extend) {
data = { ...chartsData[data.extend], ...chartsData[chartId] };
}
let height = heightProp ?? data?.height ?? 10;
if (size === 'sm') {
className = `${classProp ?? ''} chart-sm`.trim();
height = 2.5;
} else if (size === 'lg') {
className = `${classProp ?? ''} chart-lg`.trim();
height = 15;
}
// The :root custom-property block stays a plain string rendered via set:html — it's
// inert CSS text (no injection surface), not executable code.
const style = data ? chartStyle({ id, data }) : '';
const { config, xFormatterExpr } = data ? chartConfig({ id, data, height }) : { config: undefined, xFormatterExpr: undefined };
const chartKey = `chart-${id}`;
const elementId = `chart-${id}`;
---
{
data && (
<Fragment>
<div id={elementId} class:list={['position-relative', className]} role="img" aria-label={label} />
<Fragment set:html={style} />
<CaptureScript>
<!-- BEGIN CHART -->
<script is:inline define:vars={{ chartKey, elementId, config, xFormatterExpr }}>
window.tabler_chart ??= {};
// Heatmap shades each cell by computing off the literal base color in JS
// (light -> full color-mix by value), unlike other chart types where the
// SVG `fill` attribute just holds `var(--chart-...)` and the browser paints
// it directly. A raw var()/color-mix() string can't be parsed by that color
// math, so it falls back to gray — and ApexCharts' heatmap parser only
// accepts hex, not rgb()/rgba() either. Probing a real element's computed
// `color` resolves var()/color-mix() (Chromium serializes it as a CSS Color 4
// `color(srgb ...)` string), then a 1x1 canvas round-trip converts that to hex.
function resolveCssVar(value) {
if (typeof value !== 'string' || !value.includes('var(')) return value;
const probe = document.createElement('span');
probe.style.cssText = 'position:absolute;visibility:hidden';
probe.style.color = value;
document.body.appendChild(probe);
const resolved = getComputedStyle(probe).color;
probe.remove();
if (!resolved) return value;
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 1;
const ctx = canvas.getContext('2d');
ctx.fillStyle = resolved;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b] = ctx.getImageData(0, 0, 1, 1).data;
return `#${[r, g, b].map((c) => c.toString(16).padStart(2, '0')).join('')}`;
}
function initChart() {
if (xFormatterExpr && config.xaxis?.labels) {
config.xaxis.labels.formatter = new Function('val', `return (${xFormatterExpr})`);
}
if (config.chart?.type === 'heatmap' && Array.isArray(config.colors)) {
config.colors = config.colors.map(resolveCssVar);
}
window.ApexCharts &&
(window.tabler_chart[chartKey] = new ApexCharts(document.getElementById(elementId), config)).render();
}
document.readyState !== 'loading' ? initChart() : document.addEventListener('DOMContentLoaded', initChart, { once: true });
</script>
<!-- END CHART -->
</CaptureScript>
</Fragment>
)
}