Skip to content

Commit

Permalink
LG-4574: Add base chart components (#2510)
Browse files Browse the repository at this point in the history
* Build TimeSeriesLineChart

* Core README

* Update READMEs

* Use seeded data

* Match test utils format

* Add ARIA

* Add darkMode colors

* Lint

* Add LG provider to storybook

* Convert TimeSeries* to just LineChart

* Lint

* Add tests

* Lint

* Revert past CHANGELOG diff

* Changeset

* Remove unused deps

* Add lib dep

* Try awaiting throw test

* Working state

* Fix makeData types

* Lint

* Complete hook tests

* Fix test

* Lint

* Update comment

* Allow external styling

* Fix README nesting

* Fix build

* Delete line-chart

* Fix storybook

* Storybook update

* Update README

* Lint

* Add deps

* Fix faker dep

* Upgrade lib

* LG-4618: Add Grid component (#2516)

* Add Grid component

* Update README

* Add changeset

* Fix changelog

* Add Grid to live example

* Fix storybook

* CR changes

* Lint

* Lint

* CR changes

* Remove logging

* Fix README

* Fix tests

* Fix linting error
  • Loading branch information
tsck authored Oct 29, 2024
1 parent aaa8756 commit 1be6f67
Show file tree
Hide file tree
Showing 46 changed files with 1,169 additions and 744 deletions.
9 changes: 9 additions & 0 deletions .changeset/nervous-mangos-exercise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
---
'@lg-charts/core': minor
---

Adds core chart components package

- Adds `Chart` component for overarching chart configuration.
- Adds `Line` component for adding individual series data to a chart.
- Adds `Grid` component for configuring grid line on a chart.
85 changes: 85 additions & 0 deletions charts/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Core Chart Components

Library of composable charting components that provides a way to create interactive charts rendered on canvas.

![npm (scoped)](https://img.shields.io/npm/v/@lg-charts/core.svg)

## Installation

### Yarn

```shell
yarn add @lg-charts/core
```

### NPM

```shell
npm install @lg-charts/core
```

## Basic Example

```js
import { Chart, Line, Grid } from '@lg-charts/core';

<Chart>
<Grid vertical={false}>
<Line
name="Series 1"
data={[
[new Date(2020, 01, 01), 0],
[new Date(2020, 01, 02), 1],
[new Date(2020, 01, 03), 2],
[new Date(2020, 01, 04), 3],
[new Date(2020, 01, 05), 4],
]}
/>
<Line
name="Series 2"
data={[
[new Date(2020, 01, 01), 4],
[new Date(2020, 01, 02), 3],
[new Date(2020, 01, 03), 2],
[new Date(2020, 01, 04), 1],
[new Date(2020, 01, 05), 0],
]}
/>
</Chart>;
```

## Parent Components

### `Chart`

Chart container component.

#### Props

| Name | Description | Type | Default |
| -------------- | ------------------------------------------------------- | ---------- | ------- |
| `onChartReady` | Callback to be called when chart is finished rendering. | () => void | |

## Child Components

### `Line`

Component that takes in data points and renders a single line on the chart.

#### Props

| Name | Description | Type | Default |
| ------ | ---------------------------------------------------------------------- | ----------------------------------------------------------- | ------- |
| `name` | Name used to identify the series. | string | |
| `data` | Data array of tuples that represent x and y coordinates in the series. | Array<[string \| number \| Date, string \| number \| Date]> | |

### `Grid`

Component that displays grid lines on the chart.

#### Props

| Name | Description | Type | Default |
| ------------ | --------------------------- | ------- | ------- |
| `horizontal` | Show horizontal grid lines. | boolean | true |
| `vertical` | Show vertical grid lines. | boolean | true |
20 changes: 14 additions & 6 deletions charts/line-chart/package.json → charts/core/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@lg-charts/line-chart",
"name": "@lg-charts/core",
"version": "0.1.0",
"description": "lg-charts Line Chart",
"description": "lg-charts Core Chart Components",
"main": "./dist/index.js",
"module": "./dist/esm/index.js",
"types": "./dist/index.d.ts",
Expand All @@ -16,13 +16,21 @@
},
"dependencies": {
"@leafygreen-ui/emotion": "^4.0.7",
"@leafygreen-ui/lib": "^12.0.0",
"@leafygreen-ui/palette": "^4.1.1",
"@leafygreen-ui/lib": "13.7.0",
"@leafygreen-ui/tokens": "^2.11.0",
"@lg-tools/storybook-utils": "^0.1.1",
"echarts": "^5.5.1"
"echarts": "^5.5.1",
"lodash.debounce": "^4.0.8",
"lodash.merge": "^4.6.2"
},
"peerDependencies": {
"@leafygreen-ui/leafygreen-provider": "^3.1.12"
},
"devDependencies": {
"@faker-js/faker": "8.0.2",
"@types/lodash.debounce": "^4.0.9",
"@types/lodash.merge": "^4.6.9"
},
"homepage": "https://github.com/mongodb/leafygreen-ui/tree/main/packages/line-chart",
"repository": {
"type": "git",
"url": "https://github.com/mongodb/leafygreen-ui"
Expand Down
50 changes: 50 additions & 0 deletions charts/core/src/Chart/Chart.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import React from 'react';
import { render, screen } from '@testing-library/react';

import '@testing-library/jest-dom/extend-expect';

import { ChartProvider } from '../ChartContext';

import { Chart } from './Chart';

jest.mock('../ChartContext', () => ({
ChartProvider: jest.fn(({ children }) => <div>{children}</div>),
}));

jest.mock('./hooks', () => ({
useChart: jest.fn(() => ({
chartOptions: {},
updateChartOptions: jest.fn(),
addChartSeries: jest.fn(),
removeChartSeries: jest.fn(),
})),
}));

jest.mock('@leafygreen-ui/leafygreen-provider', () => ({
useDarkMode: jest.fn(() => ({ theme: 'light' })),
}));

/**
* Tests Echarts wrapper component is rendered with the correct props. Visual changes
* occur on the canvas element, so we can't test those with Jest. Will instead rely on
* Chromatic tests for rendering logic.
*/
describe('lg-charts/core/Chart', () => {
it('renders the echart container', () => {
render(<Chart />);
expect(screen.getByTestId('echart')).toBeInTheDocument();
});

it('passes the correct props to ChartProvider', () => {
render(<Chart />);
expect(ChartProvider).toHaveBeenCalledWith(
expect.objectContaining({
chartOptions: {},
updateChartOptions: expect.any(Function),
addChartSeries: expect.any(Function),
removeChartSeries: expect.any(Function),
}),
expect.anything(),
);
});
});
22 changes: 22 additions & 0 deletions charts/core/src/Chart/Chart.styles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { css } from '@leafygreen-ui/emotion';
import { Theme } from '@leafygreen-ui/lib';
import {
borderRadius,
color,
InteractionState,
Variant,
} from '@leafygreen-ui/tokens';

export const chartStyles = css`
grid-row: 2;
`;

export const getWrapperStyles = (theme: Theme) => css`
height: 280px;
width: 100%;
border: 1px solid
${color[theme].border[Variant.Secondary][InteractionState.Default]};
border-radius: ${borderRadius[200]}px;
display: grid;
grid-template-rows: auto 1fr;
`;
60 changes: 60 additions & 0 deletions charts/core/src/Chart/Chart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* React wrapper for Apache Echarts.
* https://echarts.apache.org/en/option.html#title
*
* Wraps the Echarts library and provides a React-friendly API. It adds default options
* and styling according to our design system's specs.
*/
import React from 'react';

import { cx } from '@leafygreen-ui/emotion';
import { useDarkMode } from '@leafygreen-ui/leafygreen-provider';
import LeafyGreenProvider from '@leafygreen-ui/leafygreen-provider/src/LeafyGreenContext';

import { ChartProvider } from '../ChartContext';

import { chartStyles, getWrapperStyles } from './Chart.styles';
import { ChartProps } from './Chart.types';
import { useChart } from './hooks';

export function Chart({
children,
darkMode: darkModeProp,
onChartReady,
className,
...rest
}: ChartProps) {
const { theme } = useDarkMode(darkModeProp);
const {
chartOptions,
updateChartOptions,
addChartSeries,
removeChartSeries,
chartRef,
} = useChart({
theme,
onChartReady,
});

return (
<LeafyGreenProvider darkMode={darkModeProp}>
<ChartProvider
chartOptions={chartOptions}
updateChartOptions={updateChartOptions}
addChartSeries={addChartSeries}
removeChartSeries={removeChartSeries}
>
<div className={cx(getWrapperStyles(theme), className)} {...rest}>
{children}
<div
ref={chartRef}
className={`echart ${chartStyles}`}
data-testid="echart"
/>
</div>
</ChartProvider>
</LeafyGreenProvider>
);
}

Chart.displayName = 'Chart';
45 changes: 45 additions & 0 deletions charts/core/src/Chart/Chart.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { XAXisComponentOption, YAXisComponentOption } from 'echarts';
import type { LineSeriesOption } from 'echarts/charts';
import type {
DatasetComponentOption,
GridComponentOption,
LegendComponentOption,
TitleComponentOption,
ToolboxComponentOption,
TooltipComponentOption,
} from 'echarts/components';
import type { ComposeOption } from 'echarts/core';

import { DarkModeProps, type HTMLElementProps } from '@leafygreen-ui/lib';

type RequiredSeriesProps = 'type' | 'name' | 'data';
export type SeriesOption = Pick<LineSeriesOption, RequiredSeriesProps> &
Partial<Omit<LineSeriesOption, RequiredSeriesProps>>;

/**
* TODO: This might need to be improved. `ComposeOption` appears to make most base option
* keys "Arrayable". This is making it difficult to properly test partial options on
* methods like updateUtils > updateOptions(), since something like `options.grid` could be
* an array even if an object.
*/
export type ChartOptions = ComposeOption<
| TooltipComponentOption
| GridComponentOption
| DatasetComponentOption
| TitleComponentOption
| LegendComponentOption
| ToolboxComponentOption
| XAXisComponentOption
| YAXisComponentOption
> & { series?: Array<SeriesOption> };

export interface ChartProps extends HTMLElementProps<'div'>, DarkModeProps {
children?: React.ReactNode;
onChartReady?: () => void;
}

export const ChartActionType = {
addChartSeries: 'addChartSeries',
removeChartSeries: 'removeChartSeries',
updateOptions: 'updateOptions',
} as const;
38 changes: 38 additions & 0 deletions charts/core/src/Chart/chartSeriesColors/chartSeriesColors.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { Theme } from '@leafygreen-ui/lib';

export const chartSeriesColors = {
[Theme.Dark]: [
'#0498EC',
'#00ED64',
'#FFC010',
'#FF6960',
'#B45AF2',
'#C3E7FE',
'#71F6BA',
'#FFEC9E',
'#FFCDC7',
'#F1D4FD',
'#E1F7FF',
'#C0FAE6',
'#FEF7DB',
'#FFEAE5',
'#F9EBFF',
],
[Theme.Light]: [
'#016BF8',
'#00A35C',
'#FFC010',
'#DB3030',
'#5E0C9E',
'#1254B7',
'#00684A',
'#944F01',
'#970606',
'#2D0B59',
'#0498EC',
'#00ED64',
'#FFEC9E',
'#FF6960',
'#B45AF2',
],
};
1 change: 1 addition & 0 deletions charts/core/src/Chart/chartSeriesColors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { chartSeriesColors } from './chartSeriesColors';
Loading

0 comments on commit 1be6f67

Please sign in to comment.