> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/llms.txt
> Use this file to discover all available pages before exploring further.

# Data visualization

> Create animated charts and graphs using Chart.js and D3.js with frame-perfect control

This example demonstrates how to create animated data visualizations using popular charting libraries while maintaining full control over animation timing through Helios.

## Overview

The data visualization examples show:

* Disabling library animations for manual control
* Frame-based data interpolation
* Synchronized multi-series animations
* Dynamic chart updates without flickering

## Chart.js implementation

### Complete example

```html composition.html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Chart.js Animation</title>
    <style>
        body {
            margin: 0;
            padding: 0;
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            background: #ffffff;
            overflow: hidden;
        }
        #chart-container {
            width: 100vw;
            height: 100vh;
            position: relative;
        }
    </style>
</head>
<body>
    <div id="chart-container">
        <canvas id="chart"></canvas>
    </div>
    <script type="module" src="./src/main.ts"></script>
</body>
</html>
```

```typescript main.ts theme={null}
import { Helios } from '@helios-project/core';
import Chart from 'chart.js/auto';

// Init Helios
const helios = new Helios({ fps: 30, duration: 5 });
helios.bindToDocumentTimeline();

// Expose for debugging
(window as any).helios = helios;

// Init Chart
const canvas = document.getElementById('chart') as HTMLCanvasElement;
const ctx = canvas.getContext('2d');

if (!ctx) throw new Error("Could not get canvas context");

const chart = new Chart(ctx, {
    type: 'bar',
    data: {
        labels: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'],
        datasets: [{
            label: 'Sales',
            data: [10, 20, 30, 40, 50, 60],
            backgroundColor: 'rgba(75, 192, 192, 0.6)',
            borderColor: 'rgba(75, 192, 192, 1)',
            borderWidth: 1
        }]
    },
    options: {
        animation: false, // Vital: Disable internal animation
        responsive: true,
        maintainAspectRatio: false,
        scales: {
            y: {
                beginAtZero: true,
                max: 100
            }
        }
    }
});

// Subscribe
helios.subscribe((state) => {
    const t = state.currentTime;

    // Animate data based on time
    const newData = chart.data.labels!.map((_, i) => {
        // Base value + oscillation
        // Offset phase by index i to create a wave
        return 50 + 40 * Math.sin(t * 2 + i * 0.5);
    });

    chart.data.datasets[0].data = newData;
    chart.update('none'); // Render immediately
});
```

### Key patterns for Chart.js

#### Disable library animations

Always disable Chart.js internal animations to prevent conflicts:

```typescript theme={null}
options: {
    animation: false, // Critical for Helios control
    responsive: true,
    maintainAspectRatio: false
}
```

#### Update without animation

Use `update('none')` to render immediately without transitions:

```typescript theme={null}
chart.data.datasets[0].data = newData;
chart.update('none'); // No internal animation
```

#### Data interpolation

Create smooth transitions by calculating data values based on time:

```typescript theme={null}
const newData = chart.data.labels!.map((_, i) => {
    return 50 + 40 * Math.sin(t * 2 + i * 0.5);
});
```

## D3.js implementation

### Complete example

```html composition.html theme={null}
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>D3 Animation</title>
  <style>
    body {
      margin: 0;
      overflow: hidden;
      background-color: #f4f4f4;
      font-family: sans-serif;
    }
    #chart {
      width: 100vw;
      height: 100vh;
      display: flex;
      justify-content: center;
      align-items: center;
    }
  </style>
</head>
<body>
  <div id="chart"></div>
  <script type="module" src="./src/index.js"></script>
</body>
</html>
```

```javascript data.js theme={null}
// Deterministic data series for the animation
export const DATA_SERIES = [
  // Year 2000
  [
    { name: "Alpha", value: 100, color: "#1f77b4" },
    { name: "Beta", value: 80, color: "#ff7f0e" },
    { name: "Gamma", value: 60, color: "#2ca02c" },
    { name: "Delta", value: 40, color: "#d62728" },
    { name: "Epsilon", value: 20, color: "#9467bd" }
  ],
  // Year 2001
  [
    { name: "Alpha", value: 120, color: "#1f77b4" },
    { name: "Beta", value: 90, color: "#ff7f0e" },
    { name: "Gamma", value: 110, color: "#2ca02c" },
    { name: "Delta", value: 50, color: "#d62728" },
    { name: "Epsilon", value: 30, color: "#9467bd" }
  ],
  // Additional years...
];
```

```javascript index.js theme={null}
import { Helios } from '@helios-project/core';
import * as d3 from 'd3';
import { DATA_SERIES } from './data.js';

const duration = 5;
const helios = new Helios({ duration, fps: 30 });

// Expose for debugging/player
window.helios = helios;
helios.bindToDocumentTimeline();

// Setup SVG
const width = 800;
const height = 600;
const margin = { top: 30, right: 30, bottom: 30, left: 60 };

const svg = d3.select("#chart")
    .append("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", [0, 0, width, height]);

// Scales
const x = d3.scaleBand()
    .range([margin.left, width - margin.right])
    .padding(0.1);

const y = d3.scaleLinear()
    .range([height - margin.bottom, margin.top]);

// Axes
const xAxis = g => g
    .attr("transform", `translate(0,${height - margin.bottom})`)
    .call(d3.axisBottom(x));

const yAxis = g => g
    .attr("transform", `translate(${margin.left},0)`)
    .call(d3.axisLeft(y));

// Initial render
const names = DATA_SERIES[0].map(d => d.name);
x.domain(names);
y.domain([0, 200]);

svg.append("g").call(xAxis);
const yAxisGroup = svg.append("g").call(yAxis);
const barGroup = svg.append("g");

// Update function
function update(time) {
    const totalIntervals = DATA_SERIES.length - 1;
    const intervalDuration = duration / totalIntervals;
    const clampedTime = Math.min(Math.max(time, 0), duration);

    // Find current interval
    let currentIntervalIndex = Math.floor(clampedTime / intervalDuration);
    if (currentIntervalIndex >= totalIntervals) {
        currentIntervalIndex = totalIntervals - 1;
    }

    const startData = DATA_SERIES[currentIntervalIndex];
    const endData = DATA_SERIES[currentIntervalIndex + 1];

    // Interpolation factor (0 to 1 within interval)
    let t = (clampedTime - (currentIntervalIndex * intervalDuration)) / intervalDuration;
    t = Math.max(0, Math.min(1, t));

    // Interpolate data
    const interpolatedData = startData.map(d => {
        const endD = endData ? endData.find(ed => ed.name === d.name) : d;
        return {
            name: d.name,
            value: d.value + (endD.value - d.value) * t,
            color: d.color
        };
    });

    // Update bars
    const bars = barGroup.selectAll("rect")
        .data(interpolatedData, d => d.name);

    bars.enter()
        .append("rect")
        .attr("fill", d => d.color)
        .attr("x", d => x(d.name))
        .attr("width", x.bandwidth())
        .merge(bars)
        .attr("y", d => y(d.value))
        .attr("height", d => y(0) - y(d.value));

    bars.exit().remove();
}

helios.subscribe(({ currentFrame, fps }) => {
    const time = currentFrame / fps;
    update(time);
});
```

### Key patterns for D3.js

#### Interval-based interpolation

Interpolate between data snapshots using time-based calculations:

```javascript theme={null}
const totalIntervals = DATA_SERIES.length - 1;
const intervalDuration = duration / totalIntervals;
const currentIntervalIndex = Math.floor(clampedTime / intervalDuration);

const startData = DATA_SERIES[currentIntervalIndex];
const endData = DATA_SERIES[currentIntervalIndex + 1];

const t = (clampedTime - (currentIntervalIndex * intervalDuration)) / intervalDuration;
```

#### Value interpolation

Smooth transitions between data points:

```javascript theme={null}
const interpolatedData = startData.map(d => {
    const endD = endData.find(ed => ed.name === d.name);
    return {
        name: d.name,
        value: d.value + (endD.value - d.value) * t,
        color: d.color
    };
});
```

#### Immediate updates without transitions

Use the enter-merge-exit pattern without D3 transitions:

```javascript theme={null}
const bars = barGroup.selectAll("rect")
    .data(interpolatedData, d => d.name);

bars.enter()
    .append("rect")
    .merge(bars)
    .attr("y", d => y(d.value))
    .attr("height", d => y(0) - y(d.value));
```

## Performance tips

### Minimize DOM updates

Only update changed attributes:

```javascript theme={null}
// Good - only update changing values
bars.attr("y", d => y(d.value))
    .attr("height", d => y(0) - y(d.value));

// Avoid - updates all attributes every frame
bars.attr("x", d => x(d.name))
    .attr("y", d => y(d.value))
    .attr("width", x.bandwidth())
    .attr("height", d => y(0) - y(d.value));
```

### Use fixed scales

Prevent scale recalculation by using fixed domains:

```javascript theme={null}
// Fixed domain (better performance)
y.domain([0, 200]);

// Dynamic domain (avoid if possible)
y.domain([0, d3.max(data, d => d.value)]).nice();
```

### Cache D3 selections

Store selections to avoid repeated queries:

```javascript theme={null}
const barGroup = svg.append("g");
const bars = barGroup.selectAll("rect"); // Cache this

// Reuse in update function
function update(data) {
    bars.data(data).attr("height", d => y(d.value));
}
```

### Batch data updates

Update all data at once instead of incrementally:

```javascript theme={null}
// Good - single update
chart.data.datasets[0].data = newData;
chart.update('none');

// Avoid - multiple updates
newData.forEach((value, index) => {
    chart.data.datasets[0].data[index] = value;
});
chart.update('none');
```

## Advanced techniques

### Multi-dataset animations

Animate multiple datasets with different timing:

```typescript theme={null}
helios.subscribe((state) => {
    const t = state.currentTime;
    
    // Dataset 1: Fast oscillation
    const data1 = labels.map((_, i) => 50 + 40 * Math.sin(t * 4 + i));
    
    // Dataset 2: Slow oscillation with offset
    const data2 = labels.map((_, i) => 30 + 20 * Math.sin(t * 1 + i + Math.PI));
    
    chart.data.datasets[0].data = data1;
    chart.data.datasets[1].data = data2;
    chart.update('none');
});
```

### Custom easing functions

Implement custom interpolation for unique effects:

```javascript theme={null}
function easeInOutCubic(t) {
    return t < 0.5
        ? 4 * t * t * t
        : 1 - Math.pow(-2 * t + 2, 3) / 2;
}

function interpolateWithEasing(start, end, t) {
    const easedT = easeInOutCubic(t);
    return start + (end - start) * easedT;
}
```

### Race bar charts

Create racing bar chart animations with D3:

```javascript theme={null}
function update(time) {
    // Sort data by value
    const sortedData = [...interpolatedData].sort((a, b) => b.value - a.value);
    
    // Update scale domain with sorted names
    x.domain(sortedData.map(d => d.name));
    
    // Animate positions
    bars.data(sortedData, d => d.name)
        .attr("x", d => x(d.name))
        .attr("y", d => y(d.value));
}
```
