# @nebula.js/sn-line-chart

The line chart is a common, widely used visualization. It is often used to show trends over time.

## Requirements

Requires `@nebula.js/stardust` version `1.7.0` or later.

## Installing

If you use npm: `npm install @nebula.js/sn-line-chart`. You can also load through the script tag directly from [https://unpkg.com](https://unpkg.com/@nebula.js/sn-line-chart).

## Usage

```js
import { embed } from '@nebula.js/stardust';
import line from '@nebula.js/sn-line-chart';

// 'app' is an enigma app model
const nuked = embed(app, {
  types: [
    {
      // register line chart
      name: 'line-chart',
      load: () => Promise.resolve(line),
    },
  ],
});

nuked.render({
  element: document.querySelector('.lines'),
  type: 'line-chart',
  fields: ['Date.autoCalendar.YearMonth', '=Avg(Gold)', '=Avg(Bitcoin)'],

  // Overrides default properties
  properties: {
    title: 'Price of Gold and Bitcoin',
    dataPoint: {
      show: true,
      showLabels: true,
    },
    gridLine: {
      auto: false,
    },
    dimensionAxis: {
      show: 'all',
      dock: 'near',
    },
    measureAxis: {
      spacing: 0.5,
      dock: 'near',
      show: 'all',
      logarithmic: true,
    },
  },
});
```

## More examples

### One dimension, two measures, area styling

```js
nuked.render({
  element: document.querySelector('.lines'),
  type: 'line-chart',
  fields: ['Date.autoCalendar.YearMonth', '=Avg(Gold)', '=Avg(Bitcoin)'],

  // Overrides default properties
  properties: {
    lineType: 'area',
  },
});
```

### One dimension, two measures, vertical orientation

```js
nuked.render({
  element: document.querySelector('.lines'),
  type: 'line-chart',
  fields: ['Date.autoCalendar.Quarter', 'Date.autoCalendar.Year', '=Avg(Bitcoin)'],

  // Overrides default properties
  properties: {
    orientation: 'vertical',
    dimensionAxis: {
      continuousAuto: false,
      dock: 'near',
    },
    dataPoint: {
      show: true,
      showLabels: true,
    },
    preferContinuousAxis: false,
  },
});
```

### Two dimensions, one measure

```js
nuked.render({
  element: document.querySelector('.lines'),
  type: 'line-chart',
  // Two dimensions, one measure
  fields: ['Date.autoCalendar.Quarter', 'Date.autoCalendar.Year', '=Avg(Bitcoin)'],
  properties: {
    measureAxis: {
      dock: 'near',
      show: 'all',
      logarithmic: true,
    },
    dataPoint: {
      show: true,
      showLabels: true,
    },
  },
});
```

### One dimension, two measures, two reference lines

```js
nuked.render({
      element: document.querySelector('.lines'),
      type: 'line-chart',
      fields: ['Date.autoCalendar.YearMonth', '=Avg(Gold)', '=Avg(Bitcoin)'],

      // Overrides default properties
      properties: {
        refLine: {
          refLines: [
            {
              label: '',
              paletteColor: {
                color: 'green',
              },
              refLineExpr: {
                value: 52500,
              },
              show: true,
            },
            {
              label: '',
              paletteColor: {
                color: 'red',
              },
              refLineExpr: {
                value: 3570,
              },
              show: true,
            },
          ],
        },
        measureAxis: {
          dock: 'near',
          show: 'all',
          logarithmic: true,
        },
        dataPoint: {
          show: true,
        },
      },
    });
  });
```

## Line chart plugins

A plugin can be passed into a line chart to add or modify its capability
or visual appearance.
A plugin needs to be defined before it can be rendered together with the chart.

```js
// Step 1: define the plugin

// Modifying the look of the minor axis title
const minorAxisTitlePlugin = {
  info: {
    name: 'minor-axis-title-plugin',
    type: 'component-definition',
  },
  fn: ({ keys, layout }) => {
    const componentDefinition = {
      type: 'data-title',

      // Provide the same name as the exisiting component to override it
      key: keys.COMPONENT.MINOR_AXIS_TITLE,
      style: {
        fontFamily: 'Tahoma, san-serif',
        fontSize: '15px',
      },
    };
    return componentDefinition;
  },
};

// Step 2: passing the plugin definition into the render function

// Rendering a line chart with plugins
nuked.render({
  element: document.querySelector('#object'),
  type: 'sn-line-chart',
  plugins: [majorAxisTitlePlugin],
  fields: ['Date.autoCalendar.YearMonth', '=Avg(Gold)', '=Avg(Bitcoin)'],
  // eslint-disable-next-line no-undef
  properties: {
    title: 'History Price of Gold vesus Bitcoin (USD)',
    measureAxis: { show: 'all', logarithmic: true },
  },
});
```

The plugin definition is an object, with two properties `info` and `fn`.
The `fn` returns a `picasso.js` component. To build this component,
some important chart internals are passed into the argument object of `fn`.

```js
// Structure of the argument object of fn
const pluginArgs = {
  layout,
  keys: {
    SCALE: {
      MAIN: {
        MINOR,
        MAJOR,
      },
    },
    COMPONENT: {
      LINE,
      MAJOR_AXIS,
      MAJOR_AXIS_TIME_INNER,
      MAJOR_AXIS_TITLE,
      MINOR_AXIS,
      MINOR_AXIS_TITLE,
    },
    COLLECTION: {
      MAIN,
    },
  },
};
```

With plugins, you can either add new components or modify existing components
of the line chart.

### Add new components

The new component can be a standard Picasso component
or a custom Picasso component. Here we demo a custom component
which add labels to the min/max positions of the line.

```js
// Implement a custom min/max labels plugin, so that we can use it later
// to show the min/max of Bitcoin price since 2018
const minMaxLabelsPluginImplementation = {
  info: {
    componentName: 'custom-labels-plugin',
    name: 'custom-labels-plugin',
    type: 'custom-component',
  },
  fn: ({ keys }) => {
    const implementation = {
      require: ['chart', 'renderer'],
      render() {
        // We are only interested in data of Bitcoin after year 2018
        const items = this.chart
          .component(keys.COMPONENT.LINE)
          .data.items.filter((item) => item.line.value === 1 && item.label >= '2018');
        const scale = this.chart.scales();
        const timeScale = scale[keys.SCALE.MAIN.MAJOR];
        const lineScale = scale[keys.SCALE.MAIN.MINOR];
        const { width, height } = this.rect;
        const min = Math.min(...items.map((item) => item.end.value));
        const max = Math.max(...items.map((item) => item.end.value));
        const labels = [];
        items.forEach((item) => {
          if (item.end.value === min) {
            labels.push({
              type: 'text',
              text: `min: ${item.end.label}`,
              x: timeScale(item.major.value) * width,
              y: lineScale(item.end.value) * height + 15,
              anchor: 'middle',
              fontFamily: 'Tahoma, san-serif',
              fontSize: '15px',
              fill: 'darkred',
            });
          } else if (item.end.value === max) {
            labels.push({
              type: 'text',
              text: `max: ${item.end.label}`,
              x: timeScale(item.major.value) * width,
              y: lineScale(item.end.value) * height - 15,
              anchor: 'middle',
              fontFamily: 'Tahoma, san-serif',
              fontSize: '15px',
              fill: 'darkgreen',
            });
          }
        });
        return labels;
      },
    };
    return implementation;
  },
};

// Using the min/max labels plugin, defined above
const minMaxLabelsPlugin = {
  info: {
    name: 'labels',
    type: 'component-definition',
  },
  fn: ({ keys }) => {
    const componentDefinition = {
      // The type has to match with the componentName of the labels plugin definition above
      type: 'custom-labels-plugin',
      key: 'my-labels',
    };
    return componentDefinition;
  },
};
```

### Modify existing components

As an example, the appearance of the line can be
modified by plugin.

To overide an existing component, `fn` should returns a `picasso.js` component
that has the same `key` as the existing component (`keys.COMPONENT.LINE` in
this example)

```js
// Modifying the look of the existing line component
const linePlugin = {
  info: {
    name: 'line-plugin',
    type: 'component-definition',
  },
  fn: ({ layout, keys }) => {
    const componentDefinition = {
      type: 'line',

      // Provide the same name as the exisiting line component to override it
      key: keys.COMPONENT.LINE,
      settings: {
        layers: { curve: 'monotone', line: { strokeWidth: 3 } },
      },
    };
    return componentDefinition;
  },
};
```

### Plugins disclaimer

- The plugins API is still experimental.
- We can not guarantee our charts to be compatible with all different settings, especially when modifying existing components.
