# Energy Stacked Area Chart Library

A modern TypeScript library for creating interactive energy data visualizations. Purpose-built for energy consumption
analysis with built-in support for multiple energy sources, time series data, and various representation modes.

## Why Energy Data Visualization Matters

Energy consumption patterns are complex, involving multiple sources (solar, nuclear, wind, coal, etc.) changing over
decades of time. This library makes it simple to create beautiful, interactive charts that help researchers,
policymakers, and educators understand energy trends and make data-driven decisions.

## Key Features

### **Energy-Focused Design**

- **9 Energy Sources**: Solar, Nuclear, Hydro, Wind, Geothermal, Natural Gas, Coal, Biomass, Petroleum
- **Smart Color Coding**: Industry-standard colors for each energy source
- **Multiple Views**: Fuel-based vs Sector-based energy analysis
- **Data Representations**: Fractional, Per Capita, and Total consumption modes

### **Modern Developer Experience**

- **Simple Data API**: Provide data directly, no HTTP dependencies
- **TypeScript First**: Complete type safety with strict mode
- **Zero Dependencies**: Embedded D3 v7 for standalone distribution
- **Responsive Design**: Automatic resizing and mobile-friendly
- **Instant Switching**: Change modes/representations without re-fetching data

### **Production Ready**

- **High Performance**: Handles 200+ years × 9+ energy sources smoothly
- **Comprehensive Testing**: 90%+ code coverage with unit and integration tests
- **Professional Styling**: Matches industry visualization standards
- **Robust Error Handling**: Graceful fallbacks and clear error messages

## Quick Start

### Installation

```bash
npm install energy-visualization-stacked-area-chart
```

### Basic Usage

```html
<!DOCTYPE html>
<html>
<head>
    <title>Energy Visualization</title>
</head>
<body>
<div id="energy-chart"></div>

<!-- Include the library -->
<script src="https://cdn.jsdelivr.net/npm/energy-visualization-stacked-area-chart@2.0.0/dist/stacked-area-chart.umd.js"></script>

<script>
    // Fetch your energy data
    fetch('./energy-data.json')
            .then(response => response.json())
            .then(energyData => {
                // Create chart with data
                new StackedAreaChart.default('energy-chart', {
                    mode: 'fuel',
                    representation: 'perCapita',
                    data: energyData
                });
                // Chart auto-renders when data is provided
            });
</script>
</body>
</html>
```

### ES6 Module Usage

```typescript
// Named import (recommended)
import {StackedAreaChart} from 'energy-visualization-stacked-area-chart';
import 'energy-visualization-stacked-area-chart/style.css';

// Fetch data (you control the data loading)
const energyData = await fetch('./energy-data.json').then(r => r.json());

// Option 1: Provide data in constructor
const chart = new StackedAreaChart('energy-chart', {
    mode: 'fuel',
    representation: 'perCapita',
    data: energyData
});
// Auto-renders when data provided

// Option 2: Provide data via fluent API
const chart2 = new StackedAreaChart('energy-chart-2', {
    mode: 'fuel',
    representation: 'perCapita'
});
chart2.data(energyData).render();

// Instant switching between modes (no re-fetch needed)
chart.switchDataSource('sector', 'total');
```

## Modern Framework Integration

The library is designed to work seamlessly with modern JavaScript frameworks and build tools. All exports are
tree-shakeable and fully typed.

### Importing

```typescript
// Named import (recommended for frameworks)
import {StackedAreaChart} from 'energy-visualization-stacked-area-chart';

// Default import
import StackedAreaChart from 'energy-visualization-stacked-area-chart';

// Type imports
import type {ChartConfig, MultiDatasetFormat} from 'energy-visualization-stacked-area-chart';

// CSS import
import 'energy-visualization-stacked-area-chart/style.css';
```

### Nuxt 3

Create a composable for easy chart management:

```typescript
// composables/useEnergyChart.ts
import {ref, onMounted, onUnmounted, type Ref} from 'vue';
import {StackedAreaChart, type PartialChartConfig} from 'energy-visualization-stacked-area-chart';
import 'energy-visualization-stacked-area-chart/style.css';

export function useEnergyChart(
    containerId: string,
    config?: PartialChartConfig
): Ref<StackedAreaChart | null> {
    const chart = ref<StackedAreaChart | null>(null);

    onMounted(() => {
        chart.value = new StackedAreaChart(containerId, config || {});
    });

    onUnmounted(() => {
        chart.value?.destroy();
    });

    return chart;
}
```

Use in a component:

```
<template>
  <div>
    <div id="energy-chart" style="width: 100%; height: 400px;"></div>
  </div>
</template>

<script setup lang="ts">
import { onMounted } from 'vue';
import { useEnergyChart } from '~/composables/useEnergyChart';

const chart = useEnergyChart('energy-chart', {
  mode: 'fuel',
  representation: 'perCapita'
});

onMounted(async () => {
  const data = await $fetch('/api/energy-data');
  chart.value?.data(data).render();
});
</script>
```

### Next.js (App Router)

```
'use client';

import { useEffect, useRef, useState } from 'react';
import { StackedAreaChart } from 'energy-visualization-stacked-area-chart';
import type { MultiDatasetFormat } from 'energy-visualization-stacked-area-chart';
import 'energy-visualization-stacked-area-chart/style.css';

export function EnergyChart({ data }: { data: MultiDatasetFormat }) {
  const chartRef = useRef<StackedAreaChart | null>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (!containerRef.current) return;
    
    // Create chart instance
    chartRef.current = new StackedAreaChart('energy-chart', {
      mode: 'fuel',
      representation: 'perCapita',
      data: data
    });
    
    // Cleanup on unmount
    return () => {
      chartRef.current?.destroy();
    };
  }, [data]);
  
  return (
    <div 
      id="energy-chart" 
      ref={containerRef} 
      style={{ width: '100%', height: '400px' }}
    />
  );
}
```

### Vue 3

```
<template>
  <div id="energy-chart" style="width: 100%; height: 400px;"></div>
</template>

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue';
import { StackedAreaChart } from 'energy-visualization-stacked-area-chart';
import type { PartialChartConfig, MultiDatasetFormat } from 'energy-visualization-stacked-area-chart';
import 'energy-visualization-stacked-area-chart/style.css';

interface Props {
  data: MultiDatasetFormat;
  config?: PartialChartConfig;
}

const props = defineProps<Props>();
const chart = ref<StackedAreaChart | null>(null);

onMounted(() => {
  chart.value = new StackedAreaChart('energy-chart', {
    ...props.config,
    data: props.data
  });
});

onUnmounted(() => {
  chart.value?.destroy();
});
</script>
```

### React (Create React App / Vite)

```
import { useEffect, useRef } from 'react';
import { StackedAreaChart } from 'energy-visualization-stacked-area-chart';
import type { MultiDatasetFormat, PartialChartConfig } from 'energy-visualization-stacked-area-chart';
import 'energy-visualization-stacked-area-chart/style.css';

interface EnergyChartProps {
  data: MultiDatasetFormat;
  config?: PartialChartConfig;
}

export function EnergyChartComponent({ data, config }: EnergyChartProps) {
  const chartRef = useRef<StackedAreaChart | null>(null);
  const containerRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    if (!containerRef.current) return;
    
    chartRef.current = new StackedAreaChart('energy-chart-container', {
      ...config,
      data: data
    });
    
    return () => {
      chartRef.current?.destroy();
    };
  }, [data, config]);
  
  return (
    <div 
      id="energy-chart-container" 
      ref={containerRef}
      style={{ width: '100%', height: '400px' }}
    />
  );
}
```

### TypeScript Support

All types are fully exported and documented:

```typescript
import type {
    // Main class
    StackedAreaChart,

    // Configuration types
    ChartConfig,
    PartialChartConfig,
    ChartDimensions,
    TooltipConfig,
    AnimationConfig,

    // Data types
    MultiDatasetFormat,
    RawChartData,
    EnergySource,
    ProcessedDataPoint,

    // Enums and constants
    ChartMode,           // 'fuel' | 'sector'
    DataRepresentation,  // 'fractional' | 'perCapita' | 'total'
    EnergySourceName,

    // Utility types
    ChartBounds,
    DatasetKey
} from 'energy-visualization-stacked-area-chart';

// Default configuration is also exported
import {DEFAULT_CONFIG} from 'energy-visualization-stacked-area-chart';
```

## Energy Data Features

### Energy Sources & Colors

The library includes built-in support for standard energy sources with industry-recognized colors:

```typescript
const energySources = {
    'Solar': '#fed530',        // Bright yellow
    'Nuclear': '#ca0813',      // Red  
    'Water': '#0b24fb',        // Blue (Hydro)
    'Wind': '#901d8f',         // Purple
    'Geothermal': '#905a1c',   // Brown
    'Natural Gas': '#4cabf2',  // Light blue
    'Coal': '#000000',         // Black
    'Biomass': '#46be48',      // Green
    'Petroleum': '#095f0b'     // Dark green
};
```

### Data Representations

**Fractional View**: Shows energy sources as proportions (0-1) of total consumption

```typescript
chart.representation('fractional'); // Perfect for seeing energy mix changes
```

**Per Capita View**: Shows energy consumption per person

```typescript
chart.representation('perCapita');  // Great for population-adjusted analysis
```

**Total View**: Shows absolute energy consumption values

```typescript
chart.representation('total');      // Best for seeing overall consumption growth
```

### Energy Unit Formatting

Automatic formatting with appropriate energy units:

```typescript
formatEnergyValue(1500);       // "1.5 kW"
formatEnergyValue(2500000);    // "2.5 MW" 
formatEnergyValue(1200000000); // "1.2 GW"
```

## Data Format

The library uses a **Multi-Dataset Format** that contains all 6 data variants in a single object:

```
{
  "fuel-fractional": [...],
  "fuel-perCapita": [...],
  "fuel-total": [...],
  "sectoral-fractional": [...],
  "sectoral-perCapita": [...],
  "sectoral-total": [...]
}
```

Each array follows the energy source structure:

```
{
  "fuel-fractional": [
    {
      "year": [1800, 1801, 1802, ..., 2021]
    },
    {
      "name": "Solar",
      "color": "#fed530",
      "data": [0, 0, 0, ..., 131.23],
      "opacity": 1
    },
    {
      "name": "Nuclear",
      "color": "#ca0813",
      "data": [0, 0, 0, ..., 843.45],
      "opacity": 1
    }
    // ... more energy sources
  ],
  "fuel-perCapita": [
    // Same structure with per-capita values
  ],
  // ... other datasets
}
```

**Key Points:**

- First object in each array contains the years
- Subsequent objects represent energy sources with their data
- All 6 datasets loaded once, enabling instant mode/representation switching

## API Reference

### Constructor

```
new StackedAreaChart(containerId: string, config ?: PartialChartConfig)
```

**Parameters:**

- `containerId`: CSS selector or element ID for the chart container
- `config`: Optional configuration object

### Configuration Options

```typescript
interface ChartConfig {
    // Chart dimensions
    dimensions: {
        width: number;          // Chart width (auto-calculated if not set)
        height: number;         // Chart height (auto-calculated if not set) 
        margin: {               // Chart margins
            top: number; right: number; bottom: number; left: number;
        };
    };

    // Energy data settings
    mode: 'fuel' | 'sector';                           // Analysis mode
    representation: 'fractional' | 'perCapita' | 'total'; // Data representation
    data?: MultiDatasetFormat;                         // Multi-dataset object (optional)

    // Styling and interaction
    interactive: boolean;       // Enable hover and tooltip interactions
    colors: Record<string, string>; // Custom energy source colors

    // Tooltip configuration  
    tooltip: {
        background: string;     // Tooltip background color
        color: string;          // Tooltip text color
        borderColor: string;    // Tooltip border color
        format: string;         // Number format pattern
    };

    // Animation settings
    animations: {
        duration: number;       // Animation duration in ms
        easing: string;         // CSS easing function
        enabled: boolean;       // Enable/disable animations
    };

    // Event callbacks
    events?: ChartEventListeners; // Listeners registered at construction (see Events)
}
```

### Methods

#### Fluent API Methods (Chainable)

```typescript
// Dimension control
chart.width(800)                       // Set chart width
chart.height(400)                      // Set chart height  
chart.resize(1000, 500)                // Resize chart

// Data control  
chart.mode('fuel')                     // Set to fuel-based analysis
chart.mode('sector')                   // Set to sector-based analysis  
chart.representation('perCapita')      // Set data representation
chart.data(multiDatasetObject)         // Provide multi-dataset
chart.updateData(multiDatasetObject)   // Update with new multi-dataset

// Data source switching (instant - no HTTP)
chart.switchDataSource('fuel', 'perCapita')  // Switch mode and representation

// Configuration
chart.configure({                      // Update configuration
    interactive: true,
    animations: {duration: 500}
});
```

#### Rendering Methods

```typescript
chart.render()          // Render the chart
chart.update()          // Update with current data
chart.destroy()         // Clean up and remove chart
```

#### Utility Methods

```typescript
chart.getConfig()       // Get current configuration
chart.getData()         // Get processed data points
chart.getEnergySources() // Get current energy sources
chart.getSVG()          // Get SVG element
chart.hasData()         // Check if chart has data
```

#### Event Methods

```typescript
chart.on('view.changed', handler)    // Subscribe, returns an unsubscribe function
chart.once('view.changed', handler)  // Subscribe for a single emission
chart.off('view.changed', handler)   // Unsubscribe one listener
chart.off('view.changed')            // Unsubscribe all listeners for the event
```

### Events

Listeners are dispatched synchronously, so a handler observes fully committed
state and can safely call `getConfig()` or `getData()`. A throwing listener is
isolated: it is logged and cannot break the chart or stop other listeners.

| Event | Payload |
|---|---|
| `view.changed` | `{mode, representation, previousMode, previousRepresentation, trigger}` |
| `chart.error` | `{error, phase}` |

```typescript
const stop = chart.on('view.changed', (event) => {
    if (event.trigger !== 'user') {
        return; // ignore programmatic changes
    }
    console.log(`${event.previousMode} -> ${event.mode}`);
});

// Later
stop();
```

#### The `trigger` discriminator

`trigger` is `'user'` when a person operated the embedded radio controls and
`'api'` when the host called `switchDataSource()`. Filtering on it is what keeps
programmatic changes out of usage analytics.

#### Which changes emit

`view.changed` reports transitions of the **rendered view**, not writes to
configuration. The config-only setters `mode()`, `representation()` and
`configure()` update state without reloading data or re-rendering, so they emit
nothing. Use `switchDataSource(mode, representation)` for an observable
programmatic transition.

```typescript
chart.mode('sector');                        // no event, no re-render
chart.switchDataSource('sector', 'total');   // view.changed, trigger: 'api'
```

#### Error reporting

`chart.error` covers the two paths where the host has no stack frame to catch:
the embedded controls' change handler (`phase: 'view-change'`) and the resize
observer callback (`phase: 'resize'`). Public methods still throw exactly as
before, so this supplements exceptions rather than replacing them.

If nothing is subscribed to `chart.error`, those errors are rethrown as they were
previously. Subscribing is what opts you into handling them:

```typescript
chart.on('chart.error', ({error, phase}) => {
    reportToErrorTracker(error, {phase});
});
```

#### Listeners at construction

```typescript
const chart = new StackedAreaChart('energy-chart', {
    data: multiDataset,
    events: {
        'view.changed': (event) => trackViewChange(event),
        'chart.error': ({error}) => reportToErrorTracker(error)
    }
});
```

Listeners passed this way are not stored on the config, so `getConfig()` never
returns handler functions. `destroy()` removes all listeners, disconnects the
resize observer and detaches the control listeners.

## Advanced Usage

### Custom Styling

```typescript
const chart = new StackedAreaChart('chart', {
    dimensions: {
        width: 1200,
        height: 500,
        margin: {top: 20, right: 30, bottom: 40, left: 50}
    },
    colors: {
        'Solar': '#ffeb3b',      // Custom solar color
        'Wind': '#9c27b0'        // Custom wind color
    },
    tooltip: {
        background: '#ffffff',
        borderColor: '#333333',
        color: '#333333'
    }
});
```

### Programmatic Control

```typescript
// Fetch data once
const energyData = await fetch('./energy-data.json').then(r => r.json());

const chart = new StackedAreaChart('chart', {
    data: energyData,
    mode: 'fuel',
    representation: 'perCapita'
});

// Instant switching (no re-fetch needed)
document.getElementById('fuel-btn').addEventListener('click', () => {
    chart.switchDataSource('fuel', 'perCapita');
});

document.getElementById('sector-btn').addEventListener('click', () => {
    chart.switchDataSource('sector', 'total');
});

// Responsive behavior
window.addEventListener('resize', () => {
    const container = document.getElementById('chart-container');
    chart.resize(container.clientWidth, 400);
});
```

### Custom Data Loading

```typescript
// Load and provide your own energy data
async function loadCustomData() {
    const response = await fetch('/api/energy-data');
    const multiDataset = await response.json();

    // Ensure data matches MultiDatasetFormat structure
    const chart = new StackedAreaChart('chart', {
        mode: 'fuel',
        representation: 'perCapita'
    });

    chart.data(multiDataset).render();
}
```

## Performance

### Optimized for Large Datasets

- **Time Range**: Efficiently handles 200+ years of historical data
- **Energy Sources**: Supports 9+ concurrent energy sources
- **Data Points**: Processes thousands of data points smoothly
- **Rendering**: <100ms render time for typical energy datasets
- **Memory**: Efficient D3 data joins minimize memory usage

### Bundle Size

- **UMD Bundle**: 67KB (includes embedded D3)
- **ESM Bundle**: 67KB (tree-shakeable)
- **Compressed**: ~18KB gzipped
- **Dependencies**: Zero runtime dependencies

## Development

### Setup Development Environment

```bash
# Clone and install
git clone <repository-url>
cd stacked-area-1
npm install

# Development with hot reload
npm run dev

# Run tests
npm test
npm run test:watch
npm run test:coverage

# Build for production  
npm run build
```

### Project Structure

```
src/
├── core/           # Main chart engine
│   ├── StackedAreaChart.ts    # Primary chart class
│   ├── DataProcessor.ts       # Data transformation
│   ├── EventEmitter.ts        # Typed event dispatch
│   └── Renderer.ts            # D3 rendering engine
├── components/     # UI components
│   └── Tooltip.ts             # Interactive tooltips
├── utils/          # Utility functions
│   ├── formatters.ts          # Energy value formatting
│   ├── colorMaps.ts           # Energy source colors
│   └── constants.ts           # Configuration constants
├── types/          # TypeScript definitions
└── styles/         # CSS styling
```

### Testing Strategy

```bash
# Unit tests - Core functionality
npm run test:unit

# Integration tests - Full workflows
npm run test:integration  

# Performance tests - Large datasets
npm run test:performance

# All tests with coverage
npm run test:coverage
```

## Browser Support

- **Modern Browsers**: Chrome 63+, Firefox 60+, Safari 12+, Edge 79+
- **JavaScript**: ES2018+ support required
- **Features Used**: ResizeObserver, Fetch API, ES6 Modules
- **Fallbacks**: Graceful degradation for older browsers

### Build Tools & Bundlers

The library is fully compatible with modern build tools:

- **Vite**: Works out of the box with ESM imports
- **Webpack 5**: Full support with tree-shaking
- **Rollup**: Native compatibility
- **esbuild**: Full ESM support
- **Parcel**: Zero-config support
- **Turbopack**: Next.js 13+ compatible

### Module Formats

- **ESM** (`dist/stacked-area-chart.esm.js`): For modern bundlers and browsers
- **UMD** (`dist/stacked-area-chart.umd.js`): For CDN usage and legacy environments
- **TypeScript** (`dist/index.d.ts`): Full type definitions with source maps

### Tree-Shaking

The library is marked with `sideEffects: ["*.css"]` in package.json, enabling optimal tree-shaking in modern bundlers.
Only import what you use:

```typescript
// Only imports the chart class and types used
import {StackedAreaChart, type ChartConfig} from 'energy-visualization-stacked-area-chart';
```

## Contributing

We welcome contributions! Here's how to get started:

1. **Fork the repository**
2. **Clone your fork**
3. **Create a feature branch**: `git checkout -b feature/energy-analysis`
4. **Add tests**: Ensure new features have test coverage
5. **Run the test suite**: `npm test`
6. **Submit a pull request**: Include description of changes

### Development Guidelines

- **TypeScript**: All code must be strictly typed
- **Testing**: Maintain 90%+ test coverage
- **Energy Focus**: Features should enhance energy data visualization
- **Performance**: Consider impact on large datasets
- **Documentation**: Update README for new features

## License

MIT License. See [LICENSE](LICENSE) file for details.

---

**Developed by the Research Computing Center (RCC), University of Chicago**