From f11141cb0c88d4bae0b1eda6b176b8ac92493452 Mon Sep 17 00:00:00 2001 From: plouc Date: Sat, 29 Apr 2023 19:26:23 +0900 Subject: [PATCH] feat(line): move stories to the dedicated storybook workspace --- packages/line/stories/LineCanvas.stories.js | 225 ---- .../stories/ResponsiveLineCanvas.stories.js | 244 ---- packages/line/stories/line.stories.js | 1088 ----------------- pnpm-lock.yaml | 3 + storybook/package.json | 1 + storybook/stories/heatmap/HeatMap.stories.tsx | 1 - storybook/stories/line/Line.stories.tsx | 1081 ++++++++++++++++ storybook/stories/line/LineCanvas.stories.tsx | 231 ++++ 8 files changed, 1316 insertions(+), 1558 deletions(-) delete mode 100644 packages/line/stories/LineCanvas.stories.js delete mode 100644 packages/line/stories/ResponsiveLineCanvas.stories.js delete mode 100644 packages/line/stories/line.stories.js create mode 100644 storybook/stories/line/Line.stories.tsx create mode 100644 storybook/stories/line/LineCanvas.stories.tsx diff --git a/packages/line/stories/LineCanvas.stories.js b/packages/line/stories/LineCanvas.stories.js deleted file mode 100644 index 695c2d0340..0000000000 --- a/packages/line/stories/LineCanvas.stories.js +++ /dev/null @@ -1,225 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { useRef } from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs, boolean, button } from '@storybook/addon-knobs' -import { generateDrinkStats } from '@nivo/generators' -import { LineCanvas } from '../src' - -const data = generateDrinkStats(32) -const commonProperties = { - width: 900, - height: 400, - margin: { top: 20, right: 20, bottom: 60, left: 80 }, - data, - pointSize: 8, - pointColor: { theme: 'background' }, - pointBorderWidth: 2, - pointBorderColor: { theme: 'background' }, -} - -const stories = storiesOf('LineCanvas', module) - -stories.addDecorator(withKnobs) - -stories.add('default', () => ) - -stories.add( - 'holes in data', - () => ( - ({ - x: `#${i}`, - y, - })), - }, - { - id: 'fake corp. B', - data: [5, 9, 8, 6, 3, 1, 2, null, 5, 8, 4].map((y, i) => ({ x: `#${i}`, y })), - }, - ]} - yScale={{ - type: 'linear', - stacked: boolean('stacked', false), - }} - curve="monotoneX" - /> - ), - { - info: { - text: `You can skip portions of the lines by setting y value to \`null\`.`, - }, - } -) - -stories.add( - 'different series lengths', - () => ( - - ), - { - info: { - text: ` - Please note that when using stacked y scale with variable length/data holes, - if one of the y value is \`null\` all subsequent values will be skipped - as we cannot properly compute the sum. - `, - }, - } -) - -stories.add('time scale', () => ( - -)) - -stories.add('custom line style', () => ( - { - lineGenerator.context(ctx) - series.forEach(serie => { - const gradient = ctx.createLinearGradient(0, 0, innerWidth, 0) - gradient.addColorStop('0', 'white') - gradient.addColorStop('0.5', serie.color) - gradient.addColorStop('1.0', 'black') - ctx.strokeStyle = gradient - ctx.lineWidth = lineWidth - ctx.beginPath() - lineGenerator(serie.data.map(d => d.position)) - ctx.stroke() - }) - }, - 'points', - 'mesh', - 'legends', - ]} - /> -)) - -stories.add('Get canvas - download the chart', () => { - const ref = useRef(undefined) - - button('Download image', () => { - const canvas = ref.current - const link = document.createElement('a') - link.download = 'chart.png' - link.href = canvas.toDataURL('image/png') - link.click() - }) - - return -}) diff --git a/packages/line/stories/ResponsiveLineCanvas.stories.js b/packages/line/stories/ResponsiveLineCanvas.stories.js deleted file mode 100644 index 8df4f1649f..0000000000 --- a/packages/line/stories/ResponsiveLineCanvas.stories.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { useRef } from 'react' -import { storiesOf } from '@storybook/react' -import { withKnobs, boolean, button } from '@storybook/addon-knobs' -import { generateDrinkStats } from '@nivo/generators' -import { ResponsiveLineCanvas } from '../src' - -const data = generateDrinkStats(32) -const commonProperties = { - margin: { top: 20, right: 20, bottom: 60, left: 80 }, - data, - pointSize: 8, - pointColor: { theme: 'background' }, - pointBorderWidth: 2, - pointBorderColor: { theme: 'background' }, -} - -const Wrapper = props =>
- -const stories = storiesOf('ResponsiveLineCanvas', module) - -stories.addDecorator(withKnobs) - -stories.add('default', () => ( - - - -)) - -stories.add( - 'holes in data', - () => ( - - ({ - x: `#${i}`, - y, - })), - }, - { - id: 'fake corp. B', - data: [5, 9, 8, 6, 3, 1, 2, null, 5, 8, 4].map((y, i) => ({ - x: `#${i}`, - y, - })), - }, - ]} - yScale={{ - type: 'linear', - stacked: boolean('stacked', false), - }} - curve="monotoneX" - /> - - ), - { - info: { - text: `You can skip portions of the lines by setting y value to \`null\`.`, - }, - } -) - -stories.add( - 'different series lengths', - () => ( - - - - ), - { - info: { - text: ` - Please note that when using stacked y scale with variable length/data holes, - if one of the y value is \`null\` all subsequent values will be skipped - as we cannot properly compute the sum. - `, - }, - } -) - -stories.add('time scale', () => ( - - - -)) - -stories.add('custom line style', () => ( - - { - lineGenerator.context(ctx) - series.forEach(serie => { - const gradient = ctx.createLinearGradient(0, 0, innerWidth, 0) - gradient.addColorStop('0', 'white') - gradient.addColorStop('0.5', serie.color) - gradient.addColorStop('1.0', 'black') - ctx.strokeStyle = gradient - ctx.lineWidth = lineWidth - ctx.beginPath() - lineGenerator(serie.data.map(d => d.position)) - ctx.stroke() - }) - }, - 'points', - 'mesh', - 'legends', - ]} - /> - -)) - -stories.add('Get canvas - download the chart', () => { - const ref = useRef(undefined) - - button('Download image', () => { - const canvas = ref.current - const link = document.createElement('a') - link.download = 'chart.png' - link.href = canvas.toDataURL('image/png') - link.click() - }) - - return ( - - - - ) -}) diff --git a/packages/line/stories/line.stories.js b/packages/line/stories/line.stories.js deleted file mode 100644 index b0104b9196..0000000000 --- a/packages/line/stories/line.stories.js +++ /dev/null @@ -1,1088 +0,0 @@ -/* - * This file is part of the nivo project. - * - * Copyright 2016-present, Raphaël Benitte. - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ -import { Component, useState, useEffect } from 'react' -import range from 'lodash/range' -import last from 'lodash/last' -import { storiesOf } from '@storybook/react' -import { withKnobs, boolean, select } from '@storybook/addon-knobs' -import { generateDrinkStats } from '@nivo/generators' -import { Defs, linearGradientDef } from '@nivo/core' -import { area, curveMonotoneX } from 'd3-shape' -import * as time from 'd3-time' -import { timeFormat } from 'd3-time-format' -import { Line } from '../src' - -const data = generateDrinkStats(18) -const commonProperties = { - width: 900, - height: 400, - margin: { top: 20, right: 20, bottom: 60, left: 80 }, - data, - animate: true, - enableSlices: 'x', -} - -const curveOptions = ['linear', 'monotoneX', 'step', 'stepBefore', 'stepAfter'] - -const CustomSymbol = ({ size, color, borderWidth, borderColor }) => ( - - - - -) - -const stories = storiesOf('Line', module) - -stories.addDecorator(withKnobs) - -stories.add( - 'stacked lines', - () => ( - - ), - { - info: { - text: ` - You can stack lines using the \`yScale.stacked\` property. - Please note that stacking is only supported for linear scales. - `, - }, - } -) - -stories.add( - 'linear scale', - () => ( - - ), - { - info: { - text: ` - By default, \`xScale\` is a point scale, but you can switch to linear using - the \`xScale.type\` property. It supports irregular intervals while \`point\` - scale doesn't. - - If you want missing datums to appear as holes instead of connecting defined ones, - you should set their y value to \`null\`. - `, - }, - } -) - -stories.add('time scale', () => ( - -)) - -stories.add('time scale milliseconds precision', () => ( - -)) - -stories.add('logarithmic scale', () => ( - -)) - -stories.add('symmetric logarithmic scale', () => ( - -)) - -class RealTimeChart extends Component { - constructor(props) { - super(props) - - const date = new Date() - date.setMinutes(0) - date.setSeconds(0) - date.setMilliseconds(0) - - this.state = { - dataA: range(100).map(i => ({ - x: time.timeMinute.offset(date, i * 30), - y: 10 + Math.round(Math.random() * 20), - })), - dataB: range(100).map(i => ({ - x: time.timeMinute.offset(date, i * 30), - y: 30 + Math.round(Math.random() * 20), - })), - dataC: range(100).map(i => ({ - x: time.timeMinute.offset(date, i * 30), - y: 60 + Math.round(Math.random() * 20), - })), - } - - this.formatTime = timeFormat('%Y %b %d') - } - - componentDidMount() { - this.timer = setInterval(this.next, 100) - } - - componentWillUnmount() { - clearInterval(this.timer) - } - - next = () => { - const dataA = this.state.dataA.slice(1) - dataA.push({ - x: time.timeMinute.offset(last(dataA).x, 30), - y: 10 + Math.round(Math.random() * 20), - }) - const dataB = this.state.dataB.slice(1) - dataB.push({ - x: time.timeMinute.offset(last(dataB).x, 30), - y: 30 + Math.round(Math.random() * 20), - }) - const dataC = this.state.dataC.slice(1) - dataC.push({ - x: time.timeMinute.offset(last(dataC).x, 30), - y: 60 + Math.round(Math.random() * 20), - }) - - this.setState({ dataA, dataB, dataC }) - } - - render() { - const { dataA, dataB, dataC } = this.state - - return ( - - ) - } -} - -stories.add('real time chart', () => ) - -const GrowingLine = () => { - const [points, setPoints] = useState([{ x: 0, y: 50 }]) - - useEffect(() => { - if (points.length === 101) return - - setTimeout(() => { - setPoints(p => { - const prev = p[p.length - 1] - - return [ - ...p, - { - x: prev.x + 1, - y: Math.max(Math.min(prev.y + Math.random() * 10 - 5, 100), 0), - }, - ] - }) - }, 300) - }, [points, setPoints]) - - return ( - - ) -} - -stories.add('growing line', () => ) - -stories.add('custom point symbol', () => ( - -)) - -stories.add('using data colors', () => ( - -)) - -stories.add('adding markers', () => ( - -)) - -stories.add( - 'holes in data', - () => ( - ({ - x: `#${i}`, - y, - })), - }, - { - id: 'fake corp. B', - data: [5, 9, 8, 6, 3, 1, 2, null, 5, 8, 4].map((y, i) => ({ x: `#${i}`, y })), - }, - ]} - yScale={{ - type: 'linear', - stacked: boolean('stacked', false), - }} - curve={select('curve', curveOptions, 'monotoneX')} - pointSize={8} - pointBorderColor="#fff" - pointBorderWidth={2} - /> - ), - { - info: { - text: `You can skip portions of the lines by setting y value to \`null\`.`, - }, - } -) - -stories.add( - 'different serie lengths', - () => ( - - ), - { - info: { - text: ` - Please note that when using stacked y scale with variable length/data holes, - if one of the y value is \`null\` all subsequent values will be skipped - as we cannot properly compute the sum. - `, - }, - } -) - -stories.add('custom min/max y', () => ( - ({ x: `#${i}`, y })), - }, - { - id: 'fake corp. B', - data: [ - 0.9, 0.5, 0.6, 0.5, 0.4, 0.3, -0.1, -0.5, -0.4, -0.4, -0.1, -0.3, -0.2, 0.1, - 0.1, 0.3, 0.4, 0.5, 0.4, 0.6, 0.5, 0.7, 0.8, 0.4, 0.3, - ].map((y, i) => ({ x: `#${i}`, y })), - }, - ]} - curve={select('curve', curveOptions, 'monotoneX')} - pointSize={8} - pointBorderColor="#fff" - pointBorderWidth={2} - yScale={{ - type: 'linear', - stacked: boolean('stacked', false), - min: -1, - max: 1, - }} - /> -)) - -stories.add('non linear values', () => ( - -)) - -stories.add( - 'highlighting negative values', - () => ( - - ), - { - info: { - text: ` - You can have two different line styles for a line if you split it into - two data set, one containing positive values and negative values filled with \`null\` - and the other having only negative values and positive ones replaced by \`null\`. - `, - }, - } -) - -stories.add('formatting axis values', () => ( - - `${Number(value).toLocaleString('ru-RU', { - minimumFractionDigits: 2, - })} ₽`, - }} - /> -)) - -stories.add('formatting values', () => ( - - `${Number(value).toLocaleString('ru-RU', { - minimumFractionDigits: 2, - })} ₽` - } - /> -)) - -stories.add('custom tooltip', () => ( - { - return ( -
-
x: {slice.id}
- {slice.points.map(point => ( -
- {point.serieId} [{point.data.yFormatted}] -
- ))} -
- ) - }} - /> -)) - -const AreaLayer = ({ series, xScale, yScale, innerHeight }) => { - const areaGenerator = area() - .x(d => xScale(d.data.x)) - .y0(d => Math.min(innerHeight, yScale(d.data.y - 40))) - .y1(d => yScale(d.data.y + 10)) - .curve(curveMonotoneX) - - return ( - <> - - - - ) -} - -stories.add( - 'custom layers', - () => ( - ['rhum', 'whisky'].includes(d.id))} - lineWidth={3} - curve="linear" - colors={['#028ee6', '#774dd7']} - enableGridX={false} - pointSize={12} - pointColor="white" - pointBorderWidth={2} - pointBorderColor={{ from: 'serieColor' }} - layers={[ - 'grid', - 'markers', - 'areas', - AreaLayer, - 'lines', - 'slices', - 'axes', - 'points', - 'legends', - ]} - theme={{ - crosshair: { - line: { - strokeWidth: 2, - stroke: '#774dd7', - strokeOpacity: 1, - }, - }, - }} - /> - ), - { - info: { - text: ` - You can use the layers property to add extra layers - to the line chart. - `, - }, - } -) - -const styleById = { - cognac: { - strokeDasharray: '12, 6', - strokeWidth: 2, - }, - vodka: { - strokeDasharray: '1, 16', - strokeWidth: 8, - strokeLinejoin: 'round', - strokeLinecap: 'round', - }, - rhum: { - strokeDasharray: '6, 6', - strokeWidth: 4, - }, - default: { - strokeWidth: 1, - }, -} - -const DashedLine = ({ series, lineGenerator, xScale, yScale }) => { - return series.map(({ id, data, color }) => ( - ({ - x: xScale(d.data.x), - y: yScale(d.data.y), - })) - )} - fill="none" - stroke={color} - style={styleById[id] || styleById.default} - /> - )) -} - -stories.add( - 'custom line style', - () => ( - - ), - { - info: { - text: `You can customize line styles using the 'layers' property and implement your own line rendering.`, - }, - } -) - -stories.add('area gradients', () => ( - -)) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45a85f026a..d6e7888182 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1526,6 +1526,9 @@ importers: '@nivo/heatmap': specifier: workspace:* version: link:../packages/heatmap + '@nivo/line': + specifier: workspace:* + version: link:../packages/line '@nivo/tooltip': specifier: workspace:* version: link:../packages/tooltip diff --git a/storybook/package.json b/storybook/package.json index 0abe35ed87..fa8aec8375 100644 --- a/storybook/package.json +++ b/storybook/package.json @@ -25,6 +25,7 @@ "@nivo/funnel": "workspace:*", "@nivo/generators": "workspace:*", "@nivo/heatmap": "workspace:*", + "@nivo/line": "workspace:*", "@nivo/tooltip": "workspace:*", "@nivo/waffle": "workspace:*", "@babel/preset-env": "^7.21.5", diff --git a/storybook/stories/heatmap/HeatMap.stories.tsx b/storybook/stories/heatmap/HeatMap.stories.tsx index c5761d67f4..cb9f6e213a 100644 --- a/storybook/stories/heatmap/HeatMap.stories.tsx +++ b/storybook/stories/heatmap/HeatMap.stories.tsx @@ -1,5 +1,4 @@ import type { Meta, StoryObj } from '@storybook/react' -// import { select } from '@storybook/addon-knobs' import { ContinuousColorScaleConfig } from '@nivo/colors' import { HeatMap, ComputedCell } from '@nivo/heatmap' import { sampleData } from './data' diff --git a/storybook/stories/line/Line.stories.tsx b/storybook/stories/line/Line.stories.tsx new file mode 100644 index 0000000000..45837a9938 --- /dev/null +++ b/storybook/stories/line/Line.stories.tsx @@ -0,0 +1,1081 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { Component, useState, useEffect } from 'react' +import range from 'lodash/range' +import last from 'lodash/last' +import { generateDrinkStats } from '@nivo/generators' +import { Defs, linearGradientDef } from '@nivo/core' +import { area, curveMonotoneX } from 'd3-shape' +import * as time from 'd3-time' +import { timeFormat } from 'd3-time-format' +import { Line } from '@nivo/line' + +const meta: Meta = { + title: 'Line', + component: Line, + tags: ['autodocs'], + argTypes: { + curve: { + control: 'select', + options: ['linear', 'monotoneX', 'step', 'stepBefore', 'stepAfter'], + }, + }, + args: { + curve: 'monotoneX', + }, +} + +export default meta +type Story = StoryObj + +const data = generateDrinkStats(18) +const commonProperties = { + width: 900, + height: 400, + margin: { top: 20, right: 20, bottom: 60, left: 80 }, + data, + animate: true, + enableSlices: 'x', +} + +const CustomSymbol = ({ size, color, borderWidth, borderColor }) => ( + + + + +) + +export const StackedLines: Story = { + // You can stack lines using the \`yScale.stacked\` property. + // Please note that stacking is only supported for linear scales. + render: args => ( + + ), +} + +export const LinearScale: Story = { + // By default, \`xScale\` is a point scale, but you can switch to linear using + // the \`xScale.type\` property. It supports irregular intervals while \`point\` + // scale doesn't. + // + // If you want missing datums to appear as holes instead of connecting defined ones, + // you should set their y value to \`null\`. + render: () => ( + + ), +} + +export const TimeScale: Story = { + render: args => ( + + ), +} + +export const TimeScaleMillisecondsPrecision: Story = { + render: args => ( + + ), +} + +export const LogarithmicScale: Story = { + render: () => ( + + ), +} + +export const SymmetricLogarithmicScale: Story = { + render: () => ( + + ), +} + +class RealTimeChartComponent extends Component { + constructor(props) { + super(props) + + const date = new Date() + date.setMinutes(0) + date.setSeconds(0) + date.setMilliseconds(0) + + this.state = { + dataA: range(100).map(i => ({ + x: time.timeMinute.offset(date, i * 30), + y: 10 + Math.round(Math.random() * 20), + })), + dataB: range(100).map(i => ({ + x: time.timeMinute.offset(date, i * 30), + y: 30 + Math.round(Math.random() * 20), + })), + dataC: range(100).map(i => ({ + x: time.timeMinute.offset(date, i * 30), + y: 60 + Math.round(Math.random() * 20), + })), + } + + this.formatTime = timeFormat('%Y %b %d') + } + + componentDidMount() { + this.timer = setInterval(this.next, 100) + } + + componentWillUnmount() { + clearInterval(this.timer) + } + + next = () => { + const dataA = this.state.dataA.slice(1) + dataA.push({ + x: time.timeMinute.offset(last(dataA).x, 30), + y: 10 + Math.round(Math.random() * 20), + }) + const dataB = this.state.dataB.slice(1) + dataB.push({ + x: time.timeMinute.offset(last(dataB).x, 30), + y: 30 + Math.round(Math.random() * 20), + }) + const dataC = this.state.dataC.slice(1) + dataC.push({ + x: time.timeMinute.offset(last(dataC).x, 30), + y: 60 + Math.round(Math.random() * 20), + }) + + this.setState({ dataA, dataB, dataC }) + } + + render() { + const { dataA, dataB, dataC } = this.state + + return ( + + ) + } +} + +export const RealTimeChart: Story = { + render: () => , +} + +const GrowingLineComponent = () => { + const [points, setPoints] = useState([{ x: 0, y: 50 }]) + + useEffect(() => { + if (points.length === 101) return + + setTimeout(() => { + setPoints(p => { + const prev = p[p.length - 1] + + return [ + ...p, + { + x: prev.x + 1, + y: Math.max(Math.min(prev.y + Math.random() * 10 - 5, 100), 0), + }, + ] + }) + }, 300) + }, [points, setPoints]) + + return ( + + ) +} + +export const GrowingLine: Story = { + render: () => , +} + +export const CustomPointSymbol: Story = { + render: args => ( + + ), +} + +export const UsingDataColors: Story = { + render: () => ( + + ), +} + +export const AddingMarkers: Story = { + args: { + curve: 'catmullRom', + }, + render: args => ( + + ), +} + +export const HolesInData: Story = { + // You can skip portions of the lines by setting y value to \`null\`. + render: args => ( + ({ + x: `#${i}`, + y, + })), + }, + { + id: 'fake corp. B', + data: [5, 9, 8, 6, 3, 1, 2, null, 5, 8, 4].map((y, i) => ({ x: `#${i}`, y })), + }, + ]} + yScale={{ + type: 'linear', + // stacked: boolean('stacked', false), + }} + curve={args.curve} + pointSize={8} + pointBorderColor="#fff" + pointBorderWidth={2} + /> + ), +} + +export const DifferentSeriesLength: Story = { + // Please note that when using stacked y scale with variable length/data holes, + // if one of the y value is \`null\` all subsequent values will be skipped + // as we cannot properly compute the sum. + render: () => ( + + ), +} + +export const CustomMinMaxY: Story = { + render: args => ( + ({ x: `#${i}`, y })), + }, + { + id: 'fake corp. B', + data: [ + 0.9, 0.5, 0.6, 0.5, 0.4, 0.3, -0.1, -0.5, -0.4, -0.4, -0.1, -0.3, -0.2, 0.1, + 0.1, 0.3, 0.4, 0.5, 0.4, 0.6, 0.5, 0.7, 0.8, 0.4, 0.3, + ].map((y, i) => ({ x: `#${i}`, y })), + }, + ]} + curve={args.curve} + pointSize={8} + pointBorderColor="#fff" + pointBorderWidth={2} + yScale={{ + type: 'linear', + //stacked: boolean('stacked', false), + min: -1, + max: 1, + }} + /> + ), +} + +export const NonLinearValues: Story = { + render: () => ( + + ), +} + +export const HighlightingNegativeValues: Story = { + // You can have two different line styles for a line if you split it into + // two data set, one containing positive values and negative values filled with \`null\` + // and the other having only negative values and positive ones replaced by \`null\`. + render: args => ( + + ), +} + +export const FormattingAxisValues: Story = { + render: args => ( + + `${Number(value).toLocaleString('ru-RU', { + minimumFractionDigits: 2, + })} ₽`, + }} + /> + ), +} + +export const FormattingValues: Story = { + render: args => ( + + `${Number(value).toLocaleString('ru-RU', { + minimumFractionDigits: 2, + })} ₽` + } + /> + ), +} + +export const CustomTooltip: Story = { + render: args => ( + { + return ( +
+
x: {slice.id}
+ {slice.points.map(point => ( +
+ {point.serieId} [{point.data.yFormatted}] +
+ ))} +
+ ) + }} + /> + ), +} + +const AreaLayer = ({ series, xScale, yScale, innerHeight }) => { + const areaGenerator = area() + .x(d => xScale(d.data.x)) + .y0(d => Math.min(innerHeight, yScale(d.data.y - 40))) + .y1(d => yScale(d.data.y + 10)) + .curve(curveMonotoneX) + + return ( + <> + + + + ) +} + +export const CustomLayers: Story = { + // You can use the layers property to add extra layers to the line chart. + args: { + curve: 'linear', + }, + render: args => ( + ['rhum', 'whisky'].includes(d.id))} + lineWidth={3} + curve={args.curve} + colors={['#028ee6', '#774dd7']} + enableGridX={false} + pointSize={12} + pointColor="white" + pointBorderWidth={2} + pointBorderColor={{ from: 'serieColor' }} + layers={[ + 'grid', + 'markers', + 'areas', + AreaLayer, + 'lines', + 'slices', + 'axes', + 'points', + 'legends', + ]} + theme={{ + crosshair: { + line: { + strokeWidth: 2, + stroke: '#774dd7', + strokeOpacity: 1, + }, + }, + }} + /> + ), +} + +const styleById = { + cognac: { + strokeDasharray: '12, 6', + strokeWidth: 2, + }, + vodka: { + strokeDasharray: '1, 16', + strokeWidth: 8, + strokeLinejoin: 'round', + strokeLinecap: 'round', + }, + rhum: { + strokeDasharray: '6, 6', + strokeWidth: 4, + }, + default: { + strokeWidth: 1, + }, +} + +const DashedLine = ({ series, lineGenerator, xScale, yScale }) => { + return series.map(({ id, data, color }) => ( + ({ + x: xScale(d.data.x), + y: yScale(d.data.y), + })) + )} + fill="none" + stroke={color} + style={styleById[id] || styleById.default} + /> + )) +} + +export const CustomLineStyle: Story = { + // You can customize line styles using the 'layers' property and implement your own line rendering. + render: () => ( + + ), +} + +export const AreaGradients: Story = { + render: args => ( + + ), +} diff --git a/storybook/stories/line/LineCanvas.stories.tsx b/storybook/stories/line/LineCanvas.stories.tsx new file mode 100644 index 0000000000..be4d6119d7 --- /dev/null +++ b/storybook/stories/line/LineCanvas.stories.tsx @@ -0,0 +1,231 @@ +import type { Meta, StoryObj } from '@storybook/react' +import { useCallback, useRef } from 'react' +import { generateDrinkStats } from '@nivo/generators' +import { LineCanvas } from '@nivo/line' + +const meta: Meta = { + title: 'LineCanvas', + component: LineCanvas, + tags: ['autodocs'], + argTypes: { + curve: { + control: 'select', + options: ['linear', 'monotoneX', 'step', 'stepBefore', 'stepAfter'], + }, + }, + args: { + curve: 'monotoneX', + }, +} + +export default meta +type Story = StoryObj + +const data = generateDrinkStats(32) +const commonProperties = { + width: 900, + height: 400, + margin: { top: 20, right: 20, bottom: 60, left: 80 }, + data, + pointSize: 8, + pointColor: { theme: 'background' }, + pointBorderWidth: 2, + pointBorderColor: { theme: 'background' }, +} + +export const Basic: Story = { + render: () => , +} + +export const HolesInData: Story = { + // You can skip portions of the lines by setting y value to `null`. + render: () => ( + ({ + x: `#${i}`, + y, + })), + }, + { + id: 'fake corp. B', + data: [5, 9, 8, 6, 3, 1, 2, null, 5, 8, 4].map((y, i) => ({ x: `#${i}`, y })), + }, + ]} + yScale={{ + type: 'linear', + // stacked: boolean('stacked', false), + }} + curve="monotoneX" + /> + ), +} + +export const DifferentSeriesLengths: Story = { + // Please note that when using stacked y scale with variable length/data holes, + // if one of the y value is `null` all subsequent values will be skipped + // as we cannot properly compute the sum. + render: () => ( + + ), +} + +export const TimeScale: Story = { + render: () => ( + + ), +} + +export const CustomLineStyle: Story = { + render: () => ( + { + lineGenerator.context(ctx) + series.forEach(serie => { + const gradient = ctx.createLinearGradient(0, 0, innerWidth, 0) + gradient.addColorStop('0', 'white') + gradient.addColorStop('0.5', serie.color) + gradient.addColorStop('1.0', 'black') + ctx.strokeStyle = gradient + ctx.lineWidth = lineWidth + ctx.beginPath() + lineGenerator(serie.data.map(d => d.position)) + ctx.stroke() + }) + }, + 'points', + 'mesh', + 'legends', + ]} + /> + ), +} + +export const DownloadTheChart: Story = { + render: () => { + const ref = useRef(undefined) + + const handleDownload = useCallback(() => { + const canvas = ref.current + const link = document.createElement('a') + link.download = 'chart.png' + link.href = canvas.toDataURL('image/png') + link.click() + }) + + return ( +
+ + +
+ ) + }, +}