# react-smoothie

React wrapper for [Smoothie Charts](http://smoothiecharts.org/).

## Install

With Yarn:

```bash
yarn add react-smoothie
```

With NPM:

```bash
npm install react-smoothie --save
```

### Install from GitHub

```bash
npm i cinderblock/react-smoothie
```

## Usage

There are 2 main ways to populate data.

- Original `ref` based `addTimeSeries()`
- New _(added in `0.4.0`)_ props based with reference to TimeSeries

### Import / Require

Both import or require work

```javascript
const { default: SmoothieComponent, TimeSeries } = require('react-smoothie');
import SmoothieComponent, { TimeSeries } from 'react-smoothie';
```

As of `1.0.0` the package ships native ESM alongside CommonJS; bundlers and Node pick the right build automatically.

### New prop based API

```tsx
const ts1 = new TimeSeries({});
const ts2 = new TimeSeries({
  resetBounds: true,
  resetBoundsInterval: 3000,
});

setInterval(() => {
  var time = new Date().getTime();

  ts1.append(time, Math.random());
  ts2.append(time, Math.random());
}, 500);

var TestComponent = React.createClass({
  render() {
    return (
      <SmoothieComponent
        responsive
        height={300}
        series={[
          {
            data: ts1,
            strokeStyle: { g: 255 },
            fillStyle: { g: 255 },
            lineWidth: 4,
          },
          {
            data: ts2,
            strokeStyle: { r: 255 },
            fillStyle: { r: 255 },
            lineWidth: 4,
          },
        ]}
      />
    );
  },
});
```

### Old reference based API

```tsx
var TestComponent = React.createClass({
  render() {
    return <SmoothieComponent ref="chart" responsive height={300} />;
  },

  componentDidMount() {
    // Initialize TimeSeries yourself
    var ts1 = new TimeSeries({});

    this.refs.chart.addTimeSeries(ts1, {
      strokeStyle: 'rgba(0, 255, 0, 1)',
      fillStyle: 'rgba(0, 255, 0, 0.2)',
      lineWidth: 4,
    });

    // Or let addTimeSeries create a new instance of TimeSeries for us
    var ts2 = this.refs.chart.addTimeSeries(
      {
        resetBounds: true,
        resetBoundsInterval: 3000,
      },
      {
        strokeStyle: { r: 255 },
        fillStyle: { r: 255 },
        lineWidth: 4,
      }
    );

    this.dataGenerator = setInterval(() => {
      var time = new Date().getTime();

      ts1.append(time, Math.random());
      ts2.append(time, Math.random());
    }, 500);
  },

  componentWillUnmount() {
    clearInterval(this.dataGenerator);
  },
});
```

### More Examples

See [`example.tsx`](example.tsx) for a relatively standalone and complete example.

## Props

`SmoothieComponent`'s props are all passed as the options object to the _Smoothie Charts_ constructor.

```tsx
<SmoothieComponent ref="chart" width={1000} height={300} interpolation="step" />
```

### Extra props

There are some extra props that control other behaviors.

#### `tooltip`

Generate a tooltip on mouseover

- `false` does not enable tooltip
- `true` enables a default tooltip (generated by `react-smoothie`)
- `function` that returns a stateless React component

_default: `false`_

#### `responsive`

Enabling responsive mode automatically sets the width to `100%`.

_default: `false`_

#### `width`

Control the width of the `<canvas>` used.

_default: `800`_

#### `height`

Control the height of the `<canvas>` used.

_default: `200`_

#### `streamDelay`

_default: `0` (ms)_

Delay the displayed chart. This value is passed after the component mounts as the second argument to `SmoothieChart.streamTo`.

#### `paused`

_default: `false`_

Freeze this chart (skip its frames) while `true`.
The chart stays mounted and registered and resumes cleanly when unpaused.
To pause a whole group of charts at once, use [`<SmoothieProvider paused>`](#synchronized-rendering) instead.

### Responsive charts

Experimental support for responsive charts was added in 0.3.0.
Simply set the `responsive` prop to `true` and canvas will use the full width of the parent container.
Height is still a controlled prop.

### TimeSeries

`TimeSeries` is the main class that _Smoothie Charts_ uses internally for each series of data.
There are two ways to access and use these objects, corresponding to the two API versions.

#### New API

`TimeSeries` is available as an import.

```tsx
const ts1 = new TimeSeries();
ts1.append(time, Math.random());
```

#### Old API

`TimeSeries` is exposed via the `addTimeSeries()` function.

The optional first argument of `addTimeSeries()` gets passed as the options to the `TimeSeries` constructor.
The last argument of `addTimeSeries()` gets passed as the options argument of `SmoothieChart.addTimeSeries()`.

As of `0.4.0`, an instance of `TimeSeries` can be passed as an argument to `addTimeSeries()`.

```tsx
var ts = this.refs.chart.addTimeSeries(
  {
    /* Optional TimeSeries opts */
  },
  {
    /* Chart.addTimeSeries opts */
  }
);

ts.append(new Date().getTime(), Math.random());
```

## Synchronized rendering

Smoothie Charts runs one `requestAnimationFrame` loop per chart, so ten charts means ten
independent loops redrawing at the display refresh rate.
Since `0.14.0`, react-smoothie instead drives **all charts from a single shared animation
loop by default** — no configuration or provider needed, and no visual difference.
With many charts on one page this is a significant reduction in scheduling and rendering
overhead.

Rendering also stops entirely while the browser tab is hidden and resumes (without a
visual jump) when it becomes visible again.

### `<SmoothieProvider>`

Wrap a subtree in `<SmoothieProvider>` to control its rendering as a group.
Charts inside it share their own loop, separate from the default global one.
The nearest provider wins.

```tsx
import SmoothieComponent, { SmoothieProvider } from 'react-smoothie';

<SmoothieProvider fps={30} paused={paused}>
  <SmoothieComponent series={...} />
  <SmoothieComponent series={...} />
</SmoothieProvider>;
```

#### `fps`

_default: `0` (uncapped)_

Maximum frame rate for the subtree's charts, in frames per second.
`0` renders at the display refresh rate.
Charts' own `limitFPS` options still apply on top of this.

#### `paused`

_default: `false`_

Freeze all charts in the subtree while `true`. Unpausing resumes cleanly.

#### `coordinate`

_default: `true`_

Set to `false` to opt the subtree out of synchronized rendering and restore the old
behavior where every chart runs its own Smoothie-driven animation loop.

### Without a provider

Charts outside any provider register with a global coordinator, exported as
`globalCoordinator`, which can be used to cap or pause everything without touching JSX:

```ts
import { globalCoordinator } from 'react-smoothie';

globalCoordinator.setFps(30);
globalCoordinator.setPaused(true);
```

## Test / Example

Run `yarn dev` or `npm run dev` to start the Webpack Dev Server and open the page on your browser.
Don't forget to run `yarn` or `npm install` first to install dependencies.

## Change Log

### v1.0.0

- Dual CommonJS + ESM builds in `dist/` with an `exports` map and TypeScript types for both
- Publish via npm Trusted Publishing (OIDC) with provenance — no npm tokens
- GitHub Releases created automatically on each version tag
- `sideEffects: false` for better tree-shaking
- Remove GitHub Packages publishing (its npm registry requires scoped package names)

### v0.14.0

- **Synchronized rendering, on by default**: all charts share a single `requestAnimationFrame`
  loop instead of one loop per chart. Visually identical; opt out with
  `<SmoothieProvider coordinate={false}>`
- New `<SmoothieProvider>` component: per-subtree `fps` cap, `paused`, and `coordinate` opt-out
- New per-chart `paused` prop
- Rendering stops while the browser tab is hidden and resumes cleanly
- `react` is now a peer dependency (`>=16.8`) instead of a direct dependency,
  fixing possible duplicate-React installs in consumers
- Improve Types for Canvas drawing (setting gradients)
- Rewrite of options processing
- New modern React (with hooks) example
- Switch to Npm
- Update all dependencies (React 19, TypeScript 5.9, webpack-dev-server 5)
- Add test suite (vitest + Testing Library)
- Fix crash in `componentDidUpdate` when the `series` prop is not used
- Fix `CanvasGradient` reference error in environments without canvas (SSR)

### v0.13.0

- Remove Yarn restrictions

### v0.12.x

- Update dep to latest
- Publish to GitHub Packages

### v0.11.0

- Use [`prepare`](https://docs.npmjs.com/misc/scripts#prepublish-and-prepare) script to allow installing from GitHub

### v0.10.0

- Export prop type

### v0.9.0

- TypeScript

### v0.8.0

- Fix tooltip positioning relative
- Allow setting class via `classNameCanvas`

### v0.7.0

- Allow setting canvas css class

### v0.6.0

- Tooltip support

### v0.5.0

- Single option to set both style colors to be the same

### v0.4.0

- `TimeSeries` can be passed as an argument to `addTimeSeries()`
- Use object to set style colors

### v0.3.0

- Export as Module

### v0.2.0

- Allow setting `streamDelay` option

### v0.1.0

- Fix passing args to Smoothie Charts
