component Chart { outputs { select(payload: { item: object; index: number; sourceEvent?: Event }) dataPointClick(payload: { item: object; index: number; sourceEvent?: Event }) legendToggle(payload: { item: object; index: number; hidden: boolean; sourceEvent?: Event }) } props { title: string = "Chart" description: string = "" items: unknown[] = [] valueKey: string = "value" labelKey: string = "label" height: number = 240 showLegend: boolean = true showValues: boolean = true size: string = "default" color: string = "primary" variant: string = "bar" class: string = "" } state hiddenIndexes = [] functions { shared function numericValue(item) { return Math.max(0, Number(item[valueKey]) || 0) } shared function maximumValue() { return Math.max(1, ...items.map(function (item) { return numericValue(item) })) } shared function isHidden(index) { return hiddenIndexes.includes(index) } shared function barWidth() { return items.length ? Math.max(4, 80 / items.length) : 80 } shared function barX(index) { return 10 + index * (80 / Math.max(1, items.length)) } shared function barHeight(item) { return numericValue(item) / maximumValue() * 72 } shared function barY(item) { return 84 - barHeight(item) } client function selectPoint(item, index, sourceEvent) { output.dataPointClick({ item: item, index: index, sourceEvent: sourceEvent }) output.select({ item: item, index: index, sourceEvent: sourceEvent }) } client function toggleLegend(item, index, sourceEvent, next) { next = hiddenIndexes.slice() if (next.includes(index)) { next = next.filter(function (value) { return value !== index }) } else { next.push(index) } hiddenIndexes = next output.legendToggle({ item: item, index: index, hidden: next.includes(index), sourceEvent: sourceEvent }) } } view {
{#if title || description}
{#if title}{title}{/if} {#if description}{description}{/if}
{/if} {#each items as item, index} {#if showValues}{numericValue(item)}{/if} {item[labelKey]} {/each} {#if showLegend}
{#each items as item, index} {/each}
{/if}
} style { .wrn-chart { display: grid; gap: 1rem; min-width: 0; margin: 0; padding: 1rem; border: 1px solid var(--wrn-color-border); border-radius: var(--wrn-radius); color: var(--wrn-color-text); background: var(--wrn-color-surface); box-shadow: var(--wrn-shadow-1); } .wrn-chart__header { display: grid; gap: 0.2rem; } .wrn-chart__header > span { color: var(--wrn-color-muted); font-size: 0.8rem; } .wrn-chart__plot { display: block; width: 100%; min-height: 12rem; overflow: visible; } .wrn-chart__axis { stroke: var(--wrn-color-border); stroke-width: 0.5; } .wrn-chart__bar { fill: var(--wrn-color-primary); cursor: pointer; transition: opacity var(--wrn-motion-fast), transform var(--wrn-motion-fast); transform-box: fill-box; transform-origin: bottom; } .wrn-chart__bar:hover { opacity: 0.82; transform: scaleY(1.02); } .wrn-chart__bar:focus-visible { outline: 1.5px solid var(--wrn-color-focus); outline-offset: 1px; } .wrn-chart__plot g[data-hidden="true"] { opacity: 0.18; pointer-events: none; } .wrn-chart__value, .wrn-chart__label { fill: var(--wrn-color-text); font-size: 3.5px; text-anchor: middle; } .wrn-chart__label { fill: var(--wrn-color-muted); } .wrn-chart__legend { display: flex; flex-wrap: wrap; gap: 0.5rem; } .wrn-chart__legend button { display: inline-flex; align-items: center; gap: 0.35rem; padding: 0.3rem 0.5rem; border: 1px solid var(--wrn-color-border); border-radius: 999px; color: var(--wrn-color-text); background: transparent; font: inherit; font-size: 0.75rem; cursor: pointer; } .wrn-chart__legend button > span { width: 0.65rem; height: 0.65rem; border-radius: 0.2rem; background: var(--wrn-color-primary); } .wrn-chart__legend button[aria-pressed="false"] { opacity: 0.5; } } }