Custom Charts or Canvas Rendering
This example shows how to render a line chart with D3.js and sync it with the timeline.
The same approach can be used to render a canvas-based component to display large amounts of data without performance issues, as the timeline items are rendered as individual DOM elements (see Performance).
INFO
The basic principle is to render a custom component in a group (using the items-GROUPID
slot) and pass the viewport range to the custom component. That component is then responsible for rendering the data points within this range.
Position the custom component over a timeline row with CSS.
Code
vue
<script setup lang="ts">
import { ref } from 'vue';
import LineChart from './components/LineChart.vue';
const items = ref([
{ id: 1, group: 1, type: 'range', cssVariables: { '--item-background': 'var(--color-2)' }, start: 1000000, end: 4500000 },
{ id: 3, group: 1, type: 'range', start: 6000000, end: 8000000 },
]);
const linechartData = [{ group: 'linechart', value: 1, type: 'point', start: 1000000 },
{ group: 'linechart', value: 1, type: 'point', start: 1500000 },
{ group: 'linechart', value: 0.7, type: 'point', start: 2000000 },
{ group: 'linechart', value: 0, type: 'point', start: 2500000 },
{ group: 'linechart', value: 1, type: 'point', start: 3000000 },
{ group: 'linechart', value: 0, type: 'point', start: 3500000 },
{ group: 'linechart', value: 0, type: 'point', start: 4000000 },
{ group: 'linechart', value: 0, type: 'point', start: 4500000 },
{ group: 'linechart', value: 1, type: 'point', start: 5000000 },
{ group: 'linechart', value: 0.5, type: 'point', start: 5500000 },
{ group: 'linechart', value: 1, type: 'point', start: 6000000 },
{ group: 'linechart', value: 1, type: 'point', start: 6500000 },
{ group: 'linechart', value: 0.6, type: 'point', start: 7000000 },
];
</script>
<template>
<Timeline
:items="items"
:groups="[{id: 1},{id: 'linechart'}]"
:viewportMin="0"
:viewportMax="8000000"
>
<template #items-linechart="{ viewportStart, viewportEnd, group }">
<LineChart
:viewportStart="viewportStart"
:viewportEnd="viewportEnd"
:data="linechartData"
/>
</template>
</Timeline>
</template>
LineChart.vue (custom component utilizing D3.js)
vue
<template>
<div id="chart" class="root"></div>
</template>
<script lang="ts" setup>
import { onMounted, watch } from 'vue';
// import * as d3 from 'd3'; (disabled due to vitepress)
const props = defineProps<{
viewportStart: number;
viewportEnd: number;
data: { start: number; value: number; }[];
}>();
function initChart(start: number, end: number, data: typeof props.data) {
if (!window.d3) {
// ensure d3 is loaded (due to import within vitepress)
setTimeout(() => initChart(start, end, data), 100);
return;
};
const chart = document.getElementById('chart');
if (!chart) return;
d3.select('#chart').selectAll('svg').remove();
const margin = { top: 2, right: 0, bottom: 2, left: 0 };
const width = chart.clientWidth - margin.left - margin.right;
const height = 32 - margin.top - margin.bottom;
const svg = d3.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', `translate(${margin.left},${margin.top})`);
const x = d3.scaleLinear().domain([start, end]).range([0, width]);
const y = d3.scaleLinear().domain([0, 1]).range([height, 0]);
const line = d3.line()
.x(d => x(d.start))
.y(d => y(d.value));
svg.append('path')
.datum(data)
.attr('class', 'line')
.attr('d', line);
}
// Re-render chart when viewport changes:
watch(() => [props.viewportStart, props.viewportEnd],
() => initChart(props.viewportStart, props.viewportEnd, props.data)
);
onMounted(() => {
initChart(props.viewportStart, props.viewportEnd, props.data);
});
</script>
<style lang="scss" scoped>
.root {
width: 100%;
height: 100%;
position: absolute;
inset: 0;
}
:deep(.line) {
fill: none;
stroke: var(--color-1);
stroke-width: 2px;
}
</style>