blob: c1143bf5a5cf8e4363531821ed70a98d05a5801b [file] [log] [blame]
<script lang="ts">
import type { ChartType, LegendItem, Point, TooltipItem } from "chart.js";
import { Chart } from "chart.js/auto";
import { createEventDispatcher, onMount } from "svelte";
import { writable, type Writable } from "svelte/store";
import { saveToClipboard as save } from "../clipboard.js";
import { LegendPlugin } from "../plugins.js";
import type { Data } from "../types/chart.js";
import type { Controls, ControlsEvent } from "../types/events.js";
import Legend from "./Legend.svelte";
import { isSampled } from "../transforms/standard-mappers.js";
export let data: Data;
export let chartType: ChartType = "line";
export let isExperimental: boolean = false;
export let showHistogramControls: boolean = false;
// State
let controlsDispatcher = createEventDispatcher<ControlsEvent>();
let element: HTMLCanvasElement;
let buckets: Writable<number> = writable(100);
let chart: Writable<Chart | null> = writable(null);
let items: Writable<LegendItem[] | null> = writable(null);
$: {
if ($chart) {
$chart.data = data;
$chart.update();
}
}
// Effects
onMount(() => {
const onUpdate = (updated: Chart) => {
$chart = updated;
// Bad typings.
const legend = updated.options.plugins?.legend as any;
$items = legend.labels.generateLabels($chart);
};
const plugins = {
tooltip: {
callbacks: {
label: (context: TooltipItem<typeof chartType>): string | void | string[] => {
// TODO: Configure Tooltips
// https://www.chartjs.org/docs/latest/configuration/tooltip.html
const label = context.dataset.label;
const rp = context.raw as Point;
const frequency = context.parsed.y;
if (isSampled(label)) {
const fx = rp.x.toFixed(2);
return `${label}: ${fx} F(${frequency})`;
} else {
// Fallback to default behavior
return;
}
},
},
},
legend: {
display: false,
},
benchmark: {
onUpdate: onUpdate,
},
};
$chart = new Chart(element, {
data: data,
type: chartType,
plugins: [LegendPlugin],
options: {
plugins: plugins,
},
});
});
// Copy to clip board
async function copy(event: Event) {
if ($chart) {
await save($chart);
}
}
function onHistogramChanged(event: Event) {
const element = event.target as EventTarget & HTMLInputElement;
const oldValue = $buckets;
$buckets = parseInt(element.value, 10);
if (oldValue != $buckets) {
let controls: Controls = {
buckets: $buckets,
};
controlsDispatcher("controls", controls);
}
}
</script>
<article>
<div class="toolbar">
<button
class="btn outline"
data-tooltip="Copy chart to clipboard."
on:click={copy}
>
</button>
</div>
<canvas class="chart" bind:this={element}> </canvas>
{#if showHistogramControls}
<div class="controls">
<label for="buckets">
Histogram
<input
type="range"
data-tooltip={$buckets}
data-placement="right"
min="10"
max="250"
value={$buckets}
id="buckets"
name="buckets"
on:change={onHistogramChanged}
/>
</label>
</div>
{/if}
{#if isExperimental}
<footer class="slim">
<section class="experimental">
<kbd>Experimental</kbd>
</section>
</footer>
{/if}
</article>
{#if $chart && $items}
<Legend chart={$chart} items={$items} />
{/if}
<style>
.chart {
width: 100%;
}
.toolbar {
padding: 0;
display: flex;
flex-direction: row;
justify-content: flex-end;
}
.toolbar .btn {
width: auto;
height: auto;
border: none;
padding: 5px;
}
.controls {
margin-top: 20px;
width: 100%;
}
.slim {
margin-bottom: 0px;
padding: 0;
}
.experimental {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
margin-bottom: 0px;
}
</style>