-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feature/charts package #1648
base: main
Are you sure you want to change the base?
Feature/charts package #1648
Changes from all commits
2a42d22
3bd8c56
f0ed931
30150af
414b773
4546750
8b38b74
4218d1b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
# Shoreline Charts | ||
|
||
`shoreline-components` and `echarts` are peer dependencies of `shoreline-charts` | ||
|
||
```sh | ||
pnpm add @vtex/shoreline echarts @vtex/shoreline-charts | ||
``` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
{ | ||
"name": "@vtex/shoreline-charts", | ||
"description": "Shoreline datavis library", | ||
"version": "0.0.0", | ||
"main": "./dist/index.js", | ||
"module": "./dist/index.mjs", | ||
"types": "./dist/index.d.ts", | ||
"publishConfig": { | ||
"access": "public", | ||
"registry": "https://registry.npmjs.org" | ||
}, | ||
"files": [ | ||
"dist" | ||
], | ||
"exports": { | ||
".": { | ||
"require": "./dist/index.js", | ||
"import": "./dist/index.mjs", | ||
"types": "./dist/index.d.ts" | ||
} | ||
}, | ||
"engines": { | ||
"node": ">=16" | ||
}, | ||
"scripts": { | ||
"prebuild": "rm -rf dist", | ||
"dev": "tsup --watch", | ||
"build": "tsup" | ||
}, | ||
"repository": { | ||
"directory": "packages/charts", | ||
"type": "git", | ||
"url": "git+https://github.com/vtex/shoreline.git" | ||
}, | ||
"bugs": { | ||
"url": "https://github.com/vtex/shoreline/issues" | ||
}, | ||
"peerDependencies": { | ||
"@vtex/shoreline": "1.x", | ||
"echarts": "5.x", | ||
"react": "18.x", | ||
"react-dom": "18.x" | ||
}, | ||
"devDependencies": { | ||
"@types/lodash": "^4.17.4", | ||
"@vtex/shoreline": "workspace:*", | ||
"echarts": "5.5.0" | ||
}, | ||
"dependencies": { | ||
"@vtex/shoreline-utils": "workspace:*", | ||
"echarts-for-react": "^3.0.2", | ||
"vitest-canvas-mock": "^0.3.3" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
import { | ||
useRef, | ||
useEffect, | ||
useMemo, | ||
forwardRef, | ||
useImperativeHandle, | ||
type ComponentPropsWithRef, | ||
useCallback, | ||
} from 'react' | ||
import type { EChartsOption, SetOptionOpts } from 'echarts' | ||
import ReactECharts from 'echarts-for-react' | ||
import type * as echarts from 'echarts' | ||
|
||
import { defaultTheme } from './theme/themes' | ||
import type { ChartConfig } from './types/chart' | ||
import { getChartOptions } from './utils/chart' | ||
import { canUseDOM } from '@vtex/shoreline-utils' | ||
|
||
/** | ||
* Render a Shoreline Chart with echarts | ||
* @see https://echarts.apache.org/en/index.html | ||
*/ | ||
export const Chart = forwardRef<echarts.EChartsType | undefined, ChartProps>( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was facing a type error with ref, on the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does this echarts.ECartsType return? Some HTMLDivElement? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It returns the Chart instance, the same as in |
||
function Charts(props, ref) { | ||
const { | ||
option, | ||
settings, | ||
loading = false, | ||
chartConfig, | ||
style, | ||
...otherProps | ||
} = props | ||
|
||
const chartRef = useRef<ReactECharts>(null) | ||
|
||
useImperativeHandle(ref, () => { | ||
if (chartRef.current) { | ||
return chartRef.current.getEchartsInstance() | ||
} | ||
return undefined | ||
}) | ||
|
||
const chartOptions: EChartsOption = useMemo(() => { | ||
const { type, variant } = chartConfig | ||
return getChartOptions(option, type, variant) || option | ||
}, [option, chartConfig]) | ||
|
||
const handleResize = useCallback(() => { | ||
if (chartRef.current) { | ||
chartRef.current.getEchartsInstance().resize() | ||
} | ||
}, []) | ||
|
||
useEffect(() => { | ||
if (!canUseDOM) return | ||
|
||
window.addEventListener('resize', handleResize) | ||
return () => { | ||
window.removeEventListener('resize', handleResize) | ||
} | ||
}, [handleResize, canUseDOM]) | ||
|
||
if (loading) return <div>loading...</div> | ||
|
||
return ( | ||
<div data-sl-chart {...otherProps}> | ||
<ReactECharts | ||
ref={chartRef} | ||
theme={defaultTheme} | ||
option={chartOptions} | ||
style={style} | ||
opts={{ | ||
renderer: 'svg', | ||
}} | ||
/> | ||
</div> | ||
) | ||
} | ||
) | ||
|
||
export interface ChartsOptions { | ||
/** | ||
* Echarts options | ||
*/ | ||
option: EChartsOption | ||
/** | ||
* Echarts settings | ||
*/ | ||
settings?: SetOptionOpts | ||
/** | ||
* Wether is loading | ||
* @default false | ||
*/ | ||
loading?: boolean | ||
/** | ||
* Configs containing type of chart and its variants, each variant is a pre-defined chart style for each type | ||
* @default default | ||
*/ | ||
chartConfig: ChartConfig | ||
} | ||
|
||
export type ChartProps = ChartsOptions & ComponentPropsWithRef<'div'> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export { Chart } from './chart' | ||
export type { ChartProps } from './chart' |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { Chart } from '../index' | ||
|
||
export default { | ||
title: 'Charts/bar', | ||
} | ||
|
||
export function Basic() { | ||
return ( | ||
<Chart | ||
option={{ | ||
xAxis: { | ||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | ||
}, | ||
series: { data: [1, 2, 3, 4, 5, 6, 7] }, | ||
}} | ||
chartConfig={{ type: 'bar' }} | ||
style={{ height: 550 }} | ||
/> | ||
) | ||
} | ||
|
||
export function Horizontal() { | ||
return ( | ||
<Chart | ||
option={{ | ||
xAxis: { | ||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | ||
}, | ||
series: [ | ||
{ data: [1, 2, 3, 4, 5, 6, 7] }, | ||
{ data: [1, 4, 2, 1, 4, 3, 5] }, | ||
], | ||
}} | ||
chartConfig={{ type: 'bar', variant: 'horizontal' }} | ||
style={{ height: 550 }} | ||
/> | ||
) | ||
} | ||
|
||
export function MultiType() { | ||
return ( | ||
<Chart | ||
option={{ | ||
xAxis: { | ||
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | ||
}, | ||
series: [ | ||
{ data: [1, 2, 3, 4, 5, 6, 7] }, | ||
{ data: [1, 4, 2, 1, 4, 3, 5], type: 'line' }, | ||
], | ||
}} | ||
chartConfig={{ type: 'bar', variant: 'default' }} | ||
style={{ height: 550 }} | ||
/> | ||
) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const BAR_CHART_DATA = { | ||
xAxis: { | ||
weekdays: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], | ||
}, | ||
series: { | ||
dayNumbers: [1, 2, 3, 4, 5, 6, 7], | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { | ||
describe, | ||
expect, | ||
test, | ||
render, | ||
waitFor, | ||
screen, | ||
} from '@vtex/shoreline-test-utils' | ||
import { Chart } from '../chart' | ||
import { BAR_CHART_DATA } from './__fixtures__/chartData' | ||
|
||
describe('@vtex.shoreline-charts bar chart tests', () => { | ||
test('renders the bar chart with correct data', async () => { | ||
const { container } = render( | ||
<Chart | ||
option={{ | ||
xAxis: { | ||
data: BAR_CHART_DATA.xAxis.weekdays, | ||
}, | ||
series: { data: BAR_CHART_DATA.series.dayNumbers }, | ||
}} | ||
chartConfig={{ type: 'bar' }} | ||
style={{ width: '100%', height: '400px' }} | ||
/> | ||
) | ||
|
||
const divChartContainer = container.querySelector('[data-sl-chart]') | ||
await waitFor(() => expect(divChartContainer).toBeInTheDocument()) | ||
|
||
BAR_CHART_DATA.xAxis.weekdays.forEach((value) => | ||
waitFor(() => expect(screen.queryByText(value)).toBeInTheDocument()) | ||
) | ||
|
||
BAR_CHART_DATA.series.dayNumbers.forEach((value) => | ||
waitFor(() => expect(screen.queryByText(value)).toBeInTheDocument()) | ||
) | ||
}) | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { vi } from 'vitest' | ||
import 'vitest-canvas-mock' | ||
|
||
vi.mock('echarts', async () => { | ||
const echarts = await vi.importActual<typeof import('echarts')>('echarts') | ||
return { | ||
...echarts, | ||
init: vi.fn(() => { | ||
return { | ||
setOption: vi.fn(), | ||
resize: vi.fn(), | ||
getOption: vi.fn(), | ||
dispose: vi.fn(), | ||
clear: vi.fn(), | ||
on: vi.fn(), | ||
off: vi.fn(), | ||
} | ||
}), | ||
} | ||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
export const CHART_STYLES: any = { | ||
bar: { | ||
default: { | ||
xAxis: { | ||
type: 'category', | ||
}, | ||
yAxis: { | ||
type: 'value', | ||
}, | ||
series: { | ||
type: 'bar', | ||
}, | ||
}, | ||
horizontal: { | ||
xAxis: { | ||
type: 'value', | ||
}, | ||
yAxis: { | ||
type: 'category', | ||
}, | ||
series: { | ||
type: 'bar', | ||
itemStyle: { | ||
borderRadius: [0, 4, 4, 0], | ||
}, | ||
}, | ||
}, | ||
}, | ||
line: { | ||
default: {}, | ||
}, | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
export const CATEGORICAL = { | ||
primary: '#014592', | ||
secondary: '#9C56F3', | ||
tertiary: '#0D504D', | ||
quaternary: '#CA226A', | ||
quinary: '#F95D47', | ||
senary: '#5C12B6', | ||
septenary: '#08A822', | ||
octonary: '#EF5997', | ||
nonary: '#157BF4', | ||
denary: '#B18D01', | ||
undenary: '#013A5E', | ||
duodenary: '#01A29B;', | ||
ternary: '#B24D01', | ||
fourteen: '#720000', | ||
} | ||
Comment on lines
+1
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks to me that this naming could be simpler. For example: 100, 200, 300, 400, 500... OR 1, 2, 3, 4, 5... You would write less and semantic actually gets lost after quaternary, for example. What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe we can achieve a better way to name it, for now i'm was following the token names defined in Figma There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you think about this naming, @beatrizmilhomem, @davicostalf ? |
||
|
||
export const BASE = { | ||
lineColor: '#ADADAD', | ||
textSoft: '#707070', | ||
bgLineColor: '#EBEBEB', | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.