diff --git a/jest.config.ts b/jest.config.ts index fb0926a5ee7..7263343f2c9 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -19,9 +19,9 @@ const config: Config = { '^.+\\.m?[jt]sx?$': 'babel-jest', '^.+\\.svg$': 'jest-transform-stub' }, - setupFilesAfterEnv: ['/packages/testSetup.ts'], + setupFilesAfterEnv: ['/packages/testSetup.ts', 'jest-canvas-mock'], testPathIgnorePatterns: ['/packages/react-integration/'], - transformIgnorePatterns: ['node_modules/victory-*/', '/node_modules/(?!(case-anything)/)'], + transformIgnorePatterns: ['/node_modules/victory-*/', '/node_modules/(?!(case-anything|echarts|zrender)/)'], coveragePathIgnorePatterns: ['/dist/'], moduleNameMapper: { '\\.(css|less)$': '/packages/react-styles/__mocks__/styleMock.js' diff --git a/packages/react-charts/package.json b/packages/react-charts/package.json index cffb96341bf..6d4ed0cba0d 100644 --- a/packages/react-charts/package.json +++ b/packages/react-charts/package.json @@ -7,6 +7,9 @@ "types": "dist/esm/index.d.ts", "typesVersions": { "*": { + "echarts": [ + "dist/esm/echarts/index.d.ts" + ], "victory": [ "dist/esm/victory/index.d.ts" ] @@ -42,7 +45,13 @@ "lodash": "^4.17.21", "tslib": "^2.8.1" }, + "devDependencies": { + "@types/lodash": "^4.17.16", + "fs-extra": "^11.3.0", + "jest-canvas-mock": "^2.5.2" + }, "peerDependencies": { + "echarts": "^5.6.0", "react": "^17 || ^18", "react-dom": "^17 || ^18", "victory-area": "^37.3.6", @@ -64,6 +73,9 @@ "victory-zoom-container": "^37.3.6" }, "peerDependenciesMeta": { + "echarts": { + "optional": true + }, "victory-area": { "optional": true }, @@ -120,9 +132,5 @@ "clean": "rimraf dist echarts victory", "build:single:packages": "node ../../scripts/build-single-packages.mjs --config single-packages.config.json", "subpaths": "node ../../scripts/exportSubpaths.mjs --config subpaths.config.json" - }, - "devDependencies": { - "@types/lodash": "^4.17.16", - "fs-extra": "^11.3.0" } } diff --git a/packages/react-charts/single-packages.config.json b/packages/react-charts/single-packages.config.json index 4a9651bb17d..274428abad0 100644 --- a/packages/react-charts/single-packages.config.json +++ b/packages/react-charts/single-packages.config.json @@ -1,4 +1,4 @@ { "packageName": "@patternfly/react-charts", - "exclude": ["dist/esm/deprecated/index.js", "dist/esm/next/index.js"] + "exclude": ["dist/esm/echarts/deprecated/index.js", "dist/esm/echarts/next/index.js", "dist/esm/victory/deprecated/index.js", "dist/esm/victory/next/index.js"] } diff --git a/packages/react-charts/src/echarts/__mocks__/echarts.ts b/packages/react-charts/src/echarts/__mocks__/echarts.ts new file mode 100644 index 00000000000..52c5c5c2c22 --- /dev/null +++ b/packages/react-charts/src/echarts/__mocks__/echarts.ts @@ -0,0 +1,6 @@ +const echarts: any = jest.createMockFromModule('echarts'); +echarts.init = jest.fn(() => ({ + setOption: jest.fn(), + dispose: jest.fn() +})); +module.exports = echarts; diff --git a/packages/react-charts/src/echarts/components/Charts/Charts.tsx b/packages/react-charts/src/echarts/components/Charts/Charts.tsx new file mode 100644 index 00000000000..4a4b13eae78 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Charts/Charts.tsx @@ -0,0 +1,252 @@ +import { FunctionComponent, useEffect } from 'react'; +import { useCallback, useReducer, useRef } from 'react'; +import cloneDeep from 'lodash/cloneDeep'; +import defaultsDeep from 'lodash/defaultsDeep'; + +import * as echarts from 'echarts/core'; +import { EChartsInitOpts } from 'echarts/types/dist/echarts'; +import { EChartsOption } from 'echarts/types/dist/option'; +import { TooltipOption } from 'echarts/types/dist/shared'; + +import { getLineSeries } from '../Line'; +import { getSankeySeries } from '../Sankey'; + +import { ThemeDefinition } from '../themes/Theme'; +import { getMutationObserver } from '../utils/observe'; +import { getClassName } from '../utils/styles'; +import { getTheme } from '../utils/theme'; +import { getLegendTooltip, getSankeyTooltip } from '../utils/tooltip'; +import { ThemeColor } from '../themes/ThemeColor'; + +/** + * See https://echarts.apache.org/en/option.html#tooltip + * + * @public + * @beta + */ +export interface TooltipOptionProps extends TooltipOption { + /** + * The destination label shown in the tooltip -- for Sankey only + */ + destinationLabel?: string; + /** + * The source label shown in the tooltip -- for Sankey only + */ + sourceLabel?: string; +} + +/** + * See https://echarts.apache.org/en/option.html + * + * @public + * @beta + */ +export interface ChartsOptionProps extends EChartsOption { + /** + * Tooltip component -- see https://echarts.apache.org/en/option.html#tooltip + */ + tooltip?: TooltipOptionProps | TooltipOptionProps[]; +} + +/** + * This component is based on the Apache ECharts chart library. It provides additional functionality, custom + * components, and theming for PatternFly. This provides a collection of React based components you can use to build + * PatternFly patterns with consistent markup, styling, and behavior. + * + * See https://echarts.apache.org/en/api.html#echarts + * + * @public + * @beta + */ +export interface ChartsProps { + /** + * The className prop specifies a class name that will be applied to outermost element + */ + className?: string; + /** + * Specify height explicitly, in pixels + */ + height?: number; + /** + * The id prop specifies an ID that will be applied to outermost element. + */ + id?: string; + /** + * Flag indicating to use the legend tooltip (default). This may be overridden by the `option.tooltip` property. + */ + isLegendTooltip?: boolean; + /** + * Flag indicating to use the SVG renderer (default). This may be overridden by the `opts.renderer` property. + */ + isSvgRenderer?: boolean; + /** + * This creates a Mutation Observer to watch the given DOM selector. + * + * When the pf-v6-theme-dark selector is added or removed, this component will be notified to update its computed + * theme styles. However, if the dark theme is not updated dynamically (e.g., via a toggle), there is no need to add + * this Mutation Observer. + * + * Note: Don't provide ".pf-v6-theme-dark" as the node selector as it won't exist in the page for light theme. + * The underlying querySelectorAll() function needs to find the element the dark theme selector will be added to. + * + * See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Locating_DOM_elements_using_selectors + * + * @propType string + * @example + * @example + * @example + */ + nodeSelector?: string; + /** + * ECharts uses this object to configure its properties; for example, series, title, and tooltip + * + * See https://echarts.apache.org/en/option.html + */ + option?: ChartsOptionProps; + /** + * Optional chart configuration + * + * See https://echarts.apache.org/en/api.html#echarts.init + */ + opts?: EChartsInitOpts; + /** + * The theme prop specifies a theme to use for determining styles and layout properties for a component. Any styles or + * props defined in theme may be overwritten by props specified on the component instance. + * + * See https://echarts.apache.org/handbook/en/concepts/style/#theme + */ + theme?: ThemeDefinition; + /** + * Specifies the theme color. Valid values are 'blue', 'green', 'multi', etc. + * + * Note: Not compatible with theme prop + * + * @example themeColor={ChartThemeColor.blue} + */ + themeColor?: string; + /** + * Specify width explicitly, in pixels + */ + width?: number; +} +export const Charts: FunctionComponent = ({ + className, + height, + id, + isLegendTooltip = true, + isSvgRenderer = true, + nodeSelector, + option, + opts, + theme, + themeColor, + width, + ...rest +}: ChartsProps) => { + const containerRef = useRef(); + const echart = useRef(); + const [update, forceUpdate] = useReducer((x) => x + 1, 0); + + const getSize = () => ({ + ...(height && { height: `${height}px` }), + ...(width && { width: `${width}px` }) + }); + + const getTooltip = useCallback( + (series: any[], tooltipType: string, isSkeleton: boolean, echart) => { + // Skeleton should not have any interactions + if (isSkeleton) { + return undefined; + } else if (tooltipType === 'sankey') { + return getSankeyTooltip(series, option); + } else if (tooltipType === 'legend') { + return getLegendTooltip(series, option, echart); + } + return option.tooltip; + }, + [option] + ); + + const getSeries = useCallback( + (chartTheme: ThemeDefinition, isSkeleton: boolean) => { + let tooltipType; + const series: any = cloneDeep(option?.series); + const newSeries = []; + + series.map((serie: any) => { + switch (serie.type) { + case 'sankey': + tooltipType = 'sankey'; // Overrides legend tooltip + newSeries.push(getSankeySeries(serie, chartTheme, isSkeleton)); + break; + case 'line': + if (!tooltipType) { + tooltipType = 'legend'; + } + newSeries.push(getLineSeries(serie, chartTheme, isSkeleton)); + break; + default: + newSeries.push(serie); + break; + } + }); + return { series, tooltipType }; + }, + [option?.series] + ); + + useEffect(() => { + const isSkeleton = themeColor === ThemeColor.skeleton; + const chartTheme = theme ? theme : getTheme(themeColor); + const renderer = isSvgRenderer ? 'svg' : 'canvas'; + + echart.current = echarts.init( + containerRef.current, + chartTheme, + defaultsDeep(opts, { height, renderer, width }) // height and width are necessary here for unit tests + ); + + const { series, tooltipType } = getSeries(chartTheme, isSkeleton); + echart.current?.setOption({ + ...option, + ...(isLegendTooltip && { tooltip: getTooltip(series, tooltipType, isSkeleton, echart.current) }), + series + }); + + return () => { + echart.current?.dispose(); + }; + }, [ + containerRef, + getSeries, + getTooltip, + height, + isLegendTooltip, + isSvgRenderer, + option, + opts, + theme, + themeColor, + update, + width + ]); + + // Resize observer + useEffect(() => { + echart.current?.resize(); + }, [height, width]); + + // Dark theme observer + useEffect(() => { + let observer = () => {}; + observer = getMutationObserver(nodeSelector, () => { + forceUpdate(); + }); + return () => { + observer(); + }; + }, [nodeSelector]); + + return
; +}; +Charts.displayName = 'Charts'; diff --git a/packages/react-charts/src/echarts/components/Charts/docs/Charts.tsx b/packages/react-charts/src/echarts/components/Charts/docs/Charts.tsx new file mode 100644 index 00000000000..dbd01f55105 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Charts/docs/Charts.tsx @@ -0,0 +1,411 @@ +import { EChartsInitOpts } from 'echarts/types/dist/echarts'; +import { EChartsOption } from 'echarts/types/dist/option'; +import { + AngleAxisOption, + AriaOption, + AxisPointerOption, + BrushOption, + CalendarOption, + DatasetOption, + DataZoomComponentOption, + GeoOption, + GraphicComponentLooseOption, + GridOption, + LegendComponentOption, + ParallelCoordinateSystemOption, + PolarOption, + RadarOption, + RadiusAxisOption, + SingleAxisOption, + TimelineOption, + TitleOption, + ToolboxComponentOption, + VisualMapComponentOption, + XAXisOption, + YAXisOption +} from 'echarts/types/dist/shared'; + +// The properties below exist to document properties in the example docs. Some EChart types are not exported and too +// complex to recreate here. Attempting to duplicate each EChart type would be error-prone. For documentation purposes, +// we shall define simple types here, which avoids having to duplicate EChart's complex object types. + +/** + * See https://echarts.apache.org/en/option.html#tooltip + * + * @private Not intended as public API and subject to change + */ +export interface TooltipOptionProps { + /** + * Whether to show tooltip content all the time -- see https://echarts.apache.org/en/option.html#tooltip.alwaysShowContent + */ + alwaysShowContent?: boolean; + /** + * Which DOM element to append the tooltip to -- see https://echarts.apache.org/en/option.html#tooltip.appendTo + */ + appendTo?: string | HTMLElement | Function; + /** + * Configuration item for axisPointer -- see https://echarts.apache.org/en/option.html#tooltip.axisPointer + */ + axisPointer?: Object; + /** + * The background color of tooltip's floating layer -- see https://echarts.apache.org/en/option.html#tooltip.backgroundColor + */ + backgroundColor?: string; + /** + * The border color of tooltip's floating layer -- see https://echarts.apache.org/en/option.html#tooltip.borderColor + */ + borderColor?: string; + /** + * The border width of tooltip's floating layer -- see https://echarts.apache.org/en/option.html#tooltip.borderWidth + */ + borderWidth?: number; + /** + * Specify the classes for the tooltip root DOM -- see https://echarts.apache.org/en/option.html#tooltip.className + */ + className?: string; + /** + * Whether confine tooltip content in the view rect of chart instance -- see https://echarts.apache.org/en/option.html#tooltip.confine + */ + confine?: boolean; + /** + * The destination label shown in the tooltip -- for Sankey only + */ + destinationLabel?: string; + /** + * Whether mouse is allowed to enter the floating layer of tooltip -- see https://echarts.apache.org/en/option.html#tooltip.enterable + */ + enterable?: boolean; + /** + * Extra CSS style for floating layer -- see https://echarts.apache.org/en/option.html#tooltip.extraCssText + */ + extraCssText?: string; + /** + * The content formatter of tooltip's floating layer -- see https://echarts.apache.org/en/option.html#tooltip.formatter + */ + formatter?: string | Function; + /** + * Delay time for hiding tooltip -- see https://echarts.apache.org/en/option.html#tooltip.hideDelay + */ + hideDelay?: number; + /** + * Tooltip order for multiple series -- see https://echarts.apache.org/en/option.html#tooltip.order + */ + order?: string; + /** + * The floating layer of tooltip space around content -- see https://echarts.apache.org/en/option.html#tooltip.padding + */ + padding?: number; + /** + * The position of the tooltip's floating layer -- see https://echarts.apache.org/en/option.html#tooltip.position + */ + position?: string[]; + /** + * Render mode for tooltip -- see https://echarts.apache.org/en/option.html#tooltip.renderMode + */ + renderMode?: string; + /** + * Whether to show the tooltip component -- see https://echarts.apache.org/en/option.html#tooltip.show + */ + show?: boolean; + /** + * Whether to show the tooltip floating layer -- see https://echarts.apache.org/en/option.html#tooltip.showContent + */ + showContent?: boolean; + /** + * Delay time for showing tooltip -- see https://echarts.apache.org/en/option.html#tooltip.showDelay + */ + showDelay?: number; + /** + * The source label shown in the tooltip -- for Sankey only + */ + sourceLabel?: string; + /** + * The text style of tooltip's floating layer -- see https://echarts.apache.org/en/option.html#tooltip.textStyle + */ + textStyle?: Object; + /** + * The transition duration of tooltip's animation, in seconds -- see https://echarts.apache.org/en/option.html#tooltip.transitionDuration + */ + transitionDuration?: number; + /** + * Type of triggering -- see https://echarts.apache.org/en/option.html#tooltip.trigger + */ + trigger?: string; + /** + * Conditions to trigger tooltip -- see https://echarts.apache.org/en/option.html#tooltip.triggerOn + */ + triggerOn?: 'mousemove' | 'click' | 'mousemove|click' | 'none'; + /** + * Callback function for formatting the value section in tooltip -- see https://echarts.apache.org/en/option.html#tooltip.valueFormatter + */ + valueFormatter?: string; +} + +/** + * See https://echarts.apache.org/en/option.html + * + * @private Not intended as public API and subject to change + */ +export interface ChartsOptionProps { + /** + * The angle axis in Polar Coordinate -- see https://echarts.apache.org/en/option.html#angleAxis + */ + angleAxis?: AngleAxisOption | AngleAxisOption[]; + /** + * Whether to enable animation -- see https://echarts.apache.org/en/option.html#animation + */ + animation?: boolean; + /** + * Delay before updating the first animation -- see https://echarts.apache.org/en/option.html#animationDelay + */ + animationDelay?: number; + /** + * Delay before updating animation -- see https://echarts.apache.org/en/option.html#animationDelayUpdate + */ + animationDelayUpdate?: number; + /** + * Duration of the first animation -- see https://echarts.apache.org/en/option.html#animationDuration + */ + animationDuration?: number; + /** + * Time for animation to complete -- see https://echarts.apache.org/en/option.html#animationDurationUpdate + */ + animationDurationUpdate?: number; + /** + * Easing method used for the first animation -- see https://echarts.apache.org/en/option.html#animationEasing + */ + animationEasing?: string; + /** + * Easing method used for animation -- see https://echarts.apache.org/en/option.html#animationEasingUpdate + */ + animationEasingUpdate?: string; + /** + * Whether to set graphic number threshold to animation -- see https://echarts.apache.org/en/option.html#animationThreshold + */ + animationThreshold?: number; + /** + * The W3C has developed the WAI-ARIA, the Accessible Rich Internet Applications Suite, which is dedicated to making web content and web applications accessible -- see https://echarts.apache.org/en/option.html#aria + */ + aria?: AriaOption; + /** + * This is the global configurations of axisPointer -- see https://echarts.apache.org/en/option.html#axisPointer + */ + axisPointer?: AxisPointerOption | AxisPointerOption[]; + /** + * Background color - see https://echarts.apache.org/en/option.html#backgroundColor + */ + backgroundColor?: string; + /** + * Sets the type of compositing operation to apply when drawing a new shape -- see https://echarts.apache.org/en/option.html#blendMode + */ + blendMode?: string; + /** + * Brush is an area-selecting component -- see https://echarts.apache.org/en/option.html#brush + */ + brush?: BrushOption | BrushOption[]; + /** + * Calendar coordinates -- see https://echarts.apache.org/en/option.html#calendar + */ + calendar?: CalendarOption | CalendarOption[]; + /** + * The color list of palette -- see https://echarts.apache.org/en/option.html#color + */ + color?: string[]; + /** + * Dataset brings convenience in data management separated with styles and enables data reuse by different series -- see https://echarts.apache.org/en/option.html#dataset + */ + dataset?: DatasetOption | DatasetOption[]; + /** + * Data zoom component is used for zooming a specific area -- see https://echarts.apache.org/en/option.html#dataZoom + */ + dataZoom?: DataZoomComponentOption | DataZoomComponentOption[]; + /** + * ECharts will automatically detect it via backgroundColor by default and adjust the text color accordingly - see https://echarts.apache.org/en/option.html#darkMode + */ + darkMode?: boolean | 'auto'; + /** + * ECharts will automatically detect it via backgroundColor by default and adjust the text color accordingly -- see https://echarts.apache.org/en/option.html#darkMode + */ + /** + * Geographic coordinate system component -- see https://echarts.apache.org/en/option.html#geo + */ + geo?: GeoOption | GeoOption[]; + /** + * Graphic component enables creating graphic elements in ECharts -- see https://echarts.apache.org/en/option.html#graphic + */ + graphic?: GraphicComponentLooseOption | GraphicComponentLooseOption[]; + /** + * Drawing grid in rectangular coordinate -- see https://echarts.apache.org/en/option.html#grid + */ + grid?: GridOption | GridOption[]; + /** + * When the number of element of the whole chart is larger than hoverLayerThreshold, a separate hover layer is used to render hovered elements -- see https://echarts.apache.org/en/option.html#hoverLayerThreshold + */ + hoverLayerThreshold?: number; + /** + * Legend component -- see https://echarts.apache.org/en/option.html#legend + */ + legend?: LegendComponentOption | LegendComponentOption[]; + /** + * See Responsive Mobile-End for details -- see https://echarts.apache.org/en/option.html#media + */ + media?: Object; + /** + * Option array used in timeline -- see https://echarts.apache.org/en/option.html#options + */ + options?: EChartsOption[]; + /** + * Parallel Coordinates is a common way of visualizing high-dimensional geometry and analyzing multivariate data -- see https://echarts.apache.org/en/option.html#parallel + */ + parallel?: ParallelCoordinateSystemOption | ParallelCoordinateSystemOption[]; + /** + * This component is the coordinate axis for parallel coordinate -- see https://echarts.apache.org/en/option.html#parallelAxis + * + * @type ParallelAxisOption | ParallelAxisOption[] + */ + parallelAxis?: Object; + /** + * Polar coordinate can be used in scatter and line chart -- see https://echarts.apache.org/en/option.html#polar + */ + polar?: PolarOption | PolarOption[]; + /** + * Coordinate for radar charts -- see https://echarts.apache.org/en/option.html#radar + */ + radar?: RadarOption | RadarOption[]; + /** + * Radial axis of polar coordinate -- see https://echarts.apache.org/en/option.html#radiusAxis + */ + radiusAxis?: RadiusAxisOption | RadiusAxisOption[]; + /** + * Properties for various chart types -- see https://echarts.apache.org/en/option.html#series + * + * @type SeriesOption | SeriesOption[] + */ + series?: Object; + /** + * An axis with a single dimension -- see https://echarts.apache.org/en/option.html#singleAxis + */ + singleAxis?: SingleAxisOption | SingleAxisOption[]; + /** + * Animation configurations of state switchment -- see https://echarts.apache.org/en/option.html#stateAnimation + */ + stateAnimation?: Object; + /** + * Timeline component -- see https://echarts.apache.org/en/option.html#timeline + * + * @type TimelineOption | SliderTimelineOption + */ + timeline?: TimelineOption | Object; + /** + * Title component, including main title and subtitle -- see https://echarts.apache.org/en/option.html#title + */ + title?: TitleOption | TitleOption[]; + /** + * A group of utility tools -- see https://echarts.apache.org/en/option.html#toolbox + */ + toolbox?: ToolboxComponentOption | ToolboxComponentOption[]; + /** + * Tooltip component -- see https://echarts.apache.org/en/option.html#tooltip + */ + tooltip?: TooltipOptionProps | TooltipOptionProps[]; + /** + * Whether to use UTC in display -- see https://echarts.apache.org/en/option.html#useUTC + */ + useUTC?: boolean; + /** + * Visual map is a type of component for visual encoding -- see https://echarts.apache.org/en/option.html#visualMap + */ + visualMap?: VisualMapComponentOption | VisualMapComponentOption[]; + /** + * The x-axis in cartesian(rectangular) coordinate -- see https://echarts.apache.org/en/option.html#xAxis + */ + xAxis?: XAXisOption | XAXisOption[]; + /** + * The y-axis in cartesian(rectangular) coordinate -- see https://echarts.apache.org/en/option.html#yAxis + */ + yAxis?: YAXisOption | YAXisOption[]; +} + +/** + * This component is based on the Apache ECharts chart library. It provides additional functionality, custom + * components, and theming for PatternFly. This provides a collection of React based components you can use to build + * PatternFly patterns with consistent markup, styling, and behavior. + * + * See https://echarts.apache.org/en/api.html#echarts + * + * @private Not intended as public API and subject to change + * @beta + */ +export interface Charts { + /** + * The className prop specifies a class name that will be applied to outermost element + */ + className?: string; + /** + * Specify height explicitly, in pixels + */ + height?: number; + /** + * The id prop specifies an ID that will be applied to outermost element. + */ + id?: string; + /** + * Flag indicating to use the legend tooltip (default). This may be overridden by the `option.tooltip` property. + */ + isLegendTooltip?: boolean; + /** + * Flag indicating to use the SVG renderer (default). This may be overridden by the `opts.renderer` property. + */ + isSvgRenderer?: boolean; + /** + * This creates a Mutation Observer to watch the given DOM selector. + * + * When the pf-v6-theme-dark selector is added or removed, this component will be notified to update its computed + * theme styles. However, if the dark theme is not updated dynamically (e.g., via a toggle), there is no need to add + * this Mutation Observer. + * + * Note: Don't provide ".pf-v6-theme-dark" as the node selector as it won't exist in the page for light theme. + * The underlying querySelectorAll() function needs to find the element the dark theme selector will be added to. + * + * See https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Locating_DOM_elements_using_selectors + * + * @propType string + * @example + * @example + * @example + */ + nodeSelector?: string; + /** + * ECharts uses this object to configure its properties; for example, series, title, and tooltip + * + * See https://echarts.apache.org/en/option.html + */ + option?: ChartsOptionProps; + /** + * Optional chart configuration + * + * See https://echarts.apache.org/en/api.html#echarts.init + */ + opts?: EChartsInitOpts; + /** + * The theme prop specifies a theme to use for determining styles and layout properties for a component. Any styles or + * props defined in theme may be overwritten by props specified on the component instance. + * + * See https://echarts.apache.org/handbook/en/concepts/style/#theme + * + * @type ThemeDefinition + */ + theme?: any; + /** + * Specifies the theme color. Valid values are 'blue', 'green', 'multi', etc. + * + * Note: Not compatible with theme prop + * + * @example themeColor={ChartThemeColor.blue} + */ + themeColor?: string; + /** + * Specify width explicitly, in pixels + */ + width?: number; +} diff --git a/packages/react-charts/src/echarts/components/Charts/index.ts b/packages/react-charts/src/echarts/components/Charts/index.ts new file mode 100644 index 00000000000..36eda29c1da --- /dev/null +++ b/packages/react-charts/src/echarts/components/Charts/index.ts @@ -0,0 +1 @@ +export * from './Charts'; diff --git a/packages/react-charts/src/echarts/components/Line/Line.tsx b/packages/react-charts/src/echarts/components/Line/Line.tsx new file mode 100644 index 00000000000..8010ed87caf --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/Line.tsx @@ -0,0 +1,22 @@ +import defaultsDeep from 'lodash/defaultsDeep'; + +import { ThemeDefinition } from '../themes/Theme'; + +/** + * Returns series properties for Line chart + * + * @param serie + * @param theme + * @param isSkeleton + * @private Not intended as public API and subject to change + */ +export const getLineSeries = (serie: any, theme: ThemeDefinition, isSkeleton: boolean) => { + const defaults = { + emphasis: { + ...(isSkeleton ? { disabled: true } : { focus: 'adjacency' }) + }, + showSymbol: false, + type: 'line' + }; + return defaultsDeep(serie, defaults); +}; diff --git a/packages/react-charts/src/echarts/components/Line/__tests__/Line.test.tsx b/packages/react-charts/src/echarts/components/Line/__tests__/Line.test.tsx new file mode 100644 index 00000000000..a089e491d09 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/__tests__/Line.test.tsx @@ -0,0 +1,90 @@ +import { setupJestCanvasMock } from 'jest-canvas-mock'; +import { render, screen } from '@testing-library/react'; +import { Charts } from '../../Charts'; + +import * as echarts from 'echarts/core'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([GridComponent, LineChart, SVGRenderer, TitleComponent, TooltipComponent]); + +beforeEach(() => { + jest.resetAllMocks(); + jest.mock('echarts'); + setupJestCanvasMock(); +}); + +const props: any = { + height: 400, + id: 'line-chart', + option: { + xAxis: { + type: 'category', + data: ['2015', '2016', '2017', '2018'] + }, + yAxis: { + axisLabel: { + formatter: (value) => (value !== 0 ? `${value}` : '') + }, + splitNumber: 3, + type: 'value' + }, + series: [ + { + data: [1, 2, 5, 3], + name: 'Cats', + type: 'line' + }, + { + data: [2, 1, 7, 4], + name: 'Dogs', + lineStyle: { + type: 'dashed' + }, + type: 'line' + }, + { + data: [3, 4, 9, 5], + name: 'Birds', + type: 'line' + }, + { + data: [3, 3, 8, 7], + name: 'Mice', + type: 'line' + } + ], + title: { + text: 'This is a Line chart' + } + }, + width: 800 +}; + +// Remove dynamic _echarts_instance_ ID +const removeInstanceID = (fragment) => { + fragment.getElementById('line-chart').removeAttribute('_echarts_instance_'); + return fragment; +}; + +test('renders component', () => { + const { asFragment } = render(); + expect(removeInstanceID(asFragment())).toMatchSnapshot(); +}); + +test('renders title', async () => { + render(); + + const title = await screen.findByText(props.option.title.text); + expect(title).toMatchSnapshot(); +}); + +test('renders height and width', async () => { + const { asFragment } = render(); + + const svg = asFragment().querySelector('svg'); + expect(svg).toHaveAttribute('height', `${props.height}`); + expect(svg).toHaveAttribute('width', `${props.width}`); +}); diff --git a/packages/react-charts/src/echarts/components/Line/__tests__/__snapshots__/Line.test.tsx.snap b/packages/react-charts/src/echarts/components/Line/__tests__/__snapshots__/Line.test.tsx.snap new file mode 100644 index 00000000000..ccc5369f37d --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/__tests__/__snapshots__/Line.test.tsx.snap @@ -0,0 +1,268 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders component 1`] = ` + +
+
+ + + + + + + + + + + + + + + + + + + + + 2015 + + + 2016 + + + 2017 + + + 2018 + + + + + + + + + + + + + + + + This is a Line chart + + + + + + + + + + + + + + + + + +
+
+
+ +`; + +exports[`renders title 1`] = ` + + This is a Line chart + +`; diff --git a/packages/react-charts/src/echarts/components/Line/examples/Basic.tsx b/packages/react-charts/src/echarts/components/Line/examples/Basic.tsx new file mode 100644 index 00000000000..22c5eef7997 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/examples/Basic.tsx @@ -0,0 +1,69 @@ +import { FunctionComponent } from 'react'; +import { Charts } from '@patternfly/react-charts/echarts'; + +import * as echarts from 'echarts/core'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([GridComponent, LineChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Basic: FunctionComponent = () => ( + (value !== 0 ? `${value}` : '') + }, + splitNumber: 3, + type: 'value' + }, + series: [ + { + data: [1, 2, 5, 3], + name: 'Cats', + type: 'line' + }, + { + data: [2, 1, 7, 4], + name: 'Dogs', + lineStyle: { + type: 'dashed' + }, + type: 'line' + }, + { + data: [3, 4, 9, 5], + name: 'Birds', + type: 'line' + }, + { + data: [3, 3, 8, 7], + name: 'Mice', + type: 'line' + } + ], + title: { + left: 'center', + text: 'This is a Line chart' + } + }} + width={825} + /> +); diff --git a/packages/react-charts/src/echarts/components/Line/examples/Responsive.tsx b/packages/react-charts/src/echarts/components/Line/examples/Responsive.tsx new file mode 100644 index 00000000000..7905aa65a0c --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/examples/Responsive.tsx @@ -0,0 +1,88 @@ +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import { Charts, ThemeColor } from '@patternfly/react-charts/echarts'; +import { getResizeObserver } from '@patternfly/react-core'; + +import * as echarts from 'echarts/core'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([GridComponent, LineChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Responsive: FunctionComponent = () => { + const containerRef = useRef(); + const [width, setWidth] = useState(0); + + useEffect(() => { + const handleResize = () => { + if (containerRef.current && containerRef.current.clientWidth) { + setWidth(containerRef.current.clientWidth); + } + }; + let observer = () => {}; + observer = getResizeObserver(containerRef.current, handleResize); + + return () => { + observer(); + }; + }, [containerRef, width]); + + return ( +
+ (value !== 0 ? `${value}` : '') + }, + splitNumber: 3, + type: 'value' + }, + series: [ + { + data: [1, 2, 5, 3], + name: 'Cats', + type: 'line' + }, + { + data: [2, 1, 7, 4], + name: 'Dogs', + lineStyle: { + type: 'dashed' + }, + type: 'line' + }, + { + data: [3, 4, 9, 5], + name: 'Birds', + type: 'line' + }, + { + data: [3, 3, 8, 7], + name: 'Mice', + type: 'line' + } + ], + title: { + left: 'center', + text: 'This is a Line chart', + type: 'line' + } + }} + themeColor={ThemeColor.green} + width={width} + /> +
+ ); +}; diff --git a/packages/react-charts/src/echarts/components/Line/examples/Skeleton.tsx b/packages/react-charts/src/echarts/components/Line/examples/Skeleton.tsx new file mode 100644 index 00000000000..f41a82d6ae7 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/examples/Skeleton.tsx @@ -0,0 +1,75 @@ +import { FormEvent, FunctionComponent, useState } from 'react'; +import { Charts, ThemeColor } from '@patternfly/react-charts/echarts'; +import { Switch } from '@patternfly/react-core'; + +import * as echarts from 'echarts/core'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([GridComponent, LineChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Skeleton: FunctionComponent = () => { + const [isChecked, setIsChecked] = useState(true); + + const handleChange = (_event: FormEvent, checked: boolean) => { + setIsChecked(checked); + }; + + return ( + <> + + + + ); +}; diff --git a/packages/react-charts/src/echarts/components/Line/examples/Theme.tsx b/packages/react-charts/src/echarts/components/Line/examples/Theme.tsx new file mode 100644 index 00000000000..3d33aaf3498 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/examples/Theme.tsx @@ -0,0 +1,93 @@ +/* eslint-disable camelcase */ +import { FunctionComponent } from 'react'; +import { getComputedStyleValue, getCustomTheme, Charts, ThemeColor } from '@patternfly/react-charts/echarts'; + +import * as echarts from 'echarts/core'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +import chart_color_blue_300 from '@patternfly/react-tokens/dist/esm/chart_color_blue_300'; +import chart_color_green_300 from '@patternfly/react-tokens/dist/esm/chart_color_green_300'; +import chart_color_purple_300 from '@patternfly/react-tokens/dist/esm/chart_color_purple_300'; +import chart_color_teal_300 from '@patternfly/react-tokens/dist/esm/chart_color_teal_300'; +import chart_color_yellow_300 from '@patternfly/react-tokens/dist/esm/chart_color_yellow_300'; + +// Register required components +echarts.use([GridComponent, LineChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Theme: FunctionComponent = () => { + const myCustomTheme = getCustomTheme(ThemeColor.default, { + color: [ + getComputedStyleValue(chart_color_purple_300), + getComputedStyleValue(chart_color_blue_300), + getComputedStyleValue(chart_color_green_300), + getComputedStyleValue(chart_color_teal_300), + getComputedStyleValue(chart_color_yellow_300) + ] + }); + + return ( + (value !== 0 ? `${value}` : '') + }, + splitNumber: 3, + type: 'value' + }, + series: [ + { + data: [1, 2, 5, 3], + name: 'Cats', + symbol: 'rect', + type: 'line' + }, + { + data: [2, 1, 7, 4], + name: 'Dogs', + lineStyle: { + type: 'dashed' + }, + symbol: 'arrow', + type: 'line' + }, + { + data: [3, 4, 9, 5], + name: 'Birds', + symbol: 'circle', + type: 'line' + }, + { + data: [3, 3, 8, 7], + name: 'Mice', + symbol: 'emptyCircle', + type: 'line' + } + ], + title: { + left: 'center', + text: 'This is a Line chart' + } + }} + theme={myCustomTheme} + width={825} + /> + ); +}; diff --git a/packages/react-charts/src/echarts/components/Line/examples/index.md b/packages/react-charts/src/echarts/components/Line/examples/index.md new file mode 100644 index 00000000000..bbbdb5abdd9 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/examples/index.md @@ -0,0 +1,61 @@ +--- +id: Line chart +section: charts +propComponents: [ + { + component: 'Charts', + source: 'ECharts-docs' + }, + { + component: 'ChartsOptionProps', + source: 'ECharts-docs' + }, + { + component: 'TooltipOptionProps', + source: 'ECharts-docs' + } +] +beta: true +--- +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import { LineChart } from 'echarts/charts'; +import { GridComponent, TitleComponent, ToolboxComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; +import { getComputedStyleValue, getCustomTheme, Charts, ThemeColor } from '@patternfly/react-charts/echarts'; + +import chart_color_blue_300 from '@patternfly/react-tokens/dist/esm/chart_color_blue_300'; +import chart_color_green_300 from '@patternfly/react-tokens/dist/esm/chart_color_green_300'; +import chart_color_purple_300 from '@patternfly/react-tokens/dist/esm/chart_color_purple_300'; +import chart_color_teal_300 from '@patternfly/react-tokens/dist/esm/chart_color_teal_300'; +import chart_color_yellow_300 from '@patternfly/react-tokens/dist/esm/chart_color_yellow_300'; + +## Introduction +Note: PatternFly React charts live in its own package at [@patternfly/react-charts](https://www.npmjs.com/package/@patternfly/react-charts)! + +The examples below are based on the [Apache ECharts](https://echarts.apache.org/) chart library, along with additional functionality, custom components, and theming for PatternFly. This provides a collection of React based components you can use to build PatternFly patterns with consistent markup, styling, and behavior. + +## Examples +### Basic with right aligned legend + +```ts file="./Basic.tsx" + +``` + +### Green with responsive container and bottom aligned legend +```ts file="./Responsive.tsx" + +``` + +### Custom theme +This demonstrates how to create a color scale via a custom theme, which may be applied to multiple charts. + +```ts file="./Theme.tsx" + +``` + +### Skeleton + +```ts file="./Skeleton.tsx" + +``` diff --git a/packages/react-charts/src/echarts/components/Line/index.ts b/packages/react-charts/src/echarts/components/Line/index.ts new file mode 100644 index 00000000000..34a969a3d62 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Line/index.ts @@ -0,0 +1 @@ +export * from './Line'; diff --git a/packages/react-charts/src/echarts/components/Sankey/Sankey.tsx b/packages/react-charts/src/echarts/components/Sankey/Sankey.tsx new file mode 100644 index 00000000000..5fd03095354 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/Sankey.tsx @@ -0,0 +1,32 @@ +import defaultsDeep from 'lodash/defaultsDeep'; + +import { ThemeDefinition } from '../themes/Theme'; + +/** + * Returns series properties for Sankey chart + * + * @param serie + * @param theme + * @param isSkeleton + * @private Not intended as public API and subject to change + */ +export const getSankeySeries = (serie: any, theme: ThemeDefinition, isSkeleton: boolean) => { + const defaults = { + data: serie.data.map((datum: any, index: number) => ({ + itemStyle: { + color: theme?.color[index % theme?.color.length] + } + })), + ...(isSkeleton ? { draggable: false } : {}), + emphasis: { + ...(isSkeleton ? { disabled: true } : { focus: 'adjacency' }) + }, + layout: 'none', + lineStyle: { + color: 'source', + opacity: 0.6 + }, + type: 'sankey' + }; + return defaultsDeep(serie, defaults); +}; diff --git a/packages/react-charts/src/echarts/components/Sankey/__tests__/Sankey.test.tsx b/packages/react-charts/src/echarts/components/Sankey/__tests__/Sankey.test.tsx new file mode 100644 index 00000000000..a0910906a2f --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/__tests__/Sankey.test.tsx @@ -0,0 +1,109 @@ +import { setupJestCanvasMock } from 'jest-canvas-mock'; +import { render, screen } from '@testing-library/react'; +import { Charts } from '../../Charts'; + +import * as echarts from 'echarts/core'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([SankeyChart, SVGRenderer, TitleComponent, TooltipComponent]); + +beforeEach(() => { + jest.resetAllMocks(); + jest.mock('echarts'); + setupJestCanvasMock(); +}); + +const data = [ + { + name: 'a' + }, + { + name: 'b' + }, + { + name: 'a1' + }, + { + name: 'a2' + }, + { + name: 'b1' + }, + { + name: 'c' + } +]; + +const links = [ + { + source: 'a', + target: 'a1', + value: 5 + }, + { + source: 'a', + target: 'a2', + value: 3 + }, + { + source: 'b', + target: 'b1', + value: 8 + }, + { + source: 'a', + target: 'b1', + value: 3 + }, + { + source: 'b1', + target: 'a1', + value: 1 + }, + { + source: 'b1', + target: 'c', + value: 2 + } +]; + +const props: any = { + height: 400, + id: 'sankey-chart', + option: { + series: [{ data, links, type: 'sankey' }], + title: { + text: 'This is a Sankey chart' + } + }, + width: 800 +}; + +// Remove dynamic _echarts_instance_ ID +const removeInstanceID = (fragment) => { + fragment.getElementById('sankey-chart').removeAttribute('_echarts_instance_'); + return fragment; +}; + +test('renders component', () => { + const { asFragment } = render(); + expect(removeInstanceID(asFragment())).toMatchSnapshot(); +}); + +test('renders title', async () => { + render(); + + const title = await screen.findByText(props.option.title.text); + expect(title).toMatchSnapshot(); +}); + +test('renders height and width', async () => { + const { asFragment } = render(); + + const svg = asFragment().querySelector('svg'); + expect(svg).toHaveAttribute('height', `${props.height}`); + expect(svg).toHaveAttribute('width', `${props.width}`); +}); diff --git a/packages/react-charts/src/echarts/components/Sankey/__tests__/__snapshots__/Sankey.test.tsx.snap b/packages/react-charts/src/echarts/components/Sankey/__tests__/__snapshots__/Sankey.test.tsx.snap new file mode 100644 index 00000000000..66802d6a2b7 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/__tests__/__snapshots__/Sankey.test.tsx.snap @@ -0,0 +1,218 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders component 1`] = ` + +
+
+ + + + + + + + + + + + + + + + + + a + + + b + + + a1 + + + a2 + + + b1 + + + c + + + + + This is a Sankey chart + + + + + + + + +
+
+
+ +`; + +exports[`renders title 1`] = ` + + This is a Sankey chart + +`; diff --git a/packages/react-charts/src/echarts/components/Sankey/examples/Basic.tsx b/packages/react-charts/src/echarts/components/Sankey/examples/Basic.tsx new file mode 100644 index 00000000000..f970a3b137f --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/examples/Basic.tsx @@ -0,0 +1,87 @@ +import { FunctionComponent } from 'react'; +import { Charts } from '@patternfly/react-charts/echarts'; + +import * as echarts from 'echarts/core'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([SankeyChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Basic: FunctionComponent = () => { + const data = [ + { + name: 'a' + }, + { + name: 'b' + }, + { + name: 'a1' + }, + { + name: 'a2' + }, + { + name: 'b1' + }, + { + name: 'c' + } + ]; + + const links = [ + { + source: 'a', + target: 'a1', + value: 5 + }, + { + source: 'a', + target: 'a2', + value: 3 + }, + { + source: 'b', + target: 'b1', + value: 8 + }, + { + source: 'a', + target: 'b1', + value: 3 + }, + { + source: 'b1', + target: 'a1', + value: 1 + }, + { + source: 'b1', + target: 'c', + value: 2 + } + ]; + + return ( + `${value} GiB` + } + }} + width={825} + /> + ); +}; diff --git a/packages/react-charts/src/echarts/components/Sankey/examples/Responsive.tsx b/packages/react-charts/src/echarts/components/Sankey/examples/Responsive.tsx new file mode 100644 index 00000000000..778cc25cca1 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/examples/Responsive.tsx @@ -0,0 +1,108 @@ +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import { Charts, ThemeColor } from '@patternfly/react-charts/echarts'; +import { getResizeObserver } from '@patternfly/react-core'; + +import * as echarts from 'echarts/core'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([SankeyChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Responsive: FunctionComponent = () => { + const data = [ + { + name: 'a' + }, + { + name: 'b' + }, + { + name: 'a1' + }, + { + name: 'a2' + }, + { + name: 'b1' + }, + { + name: 'c' + } + ]; + + const links = [ + { + source: 'a', + target: 'a1', + value: 5 + }, + { + source: 'a', + target: 'a2', + value: 3 + }, + { + source: 'b', + target: 'b1', + value: 8 + }, + { + source: 'a', + target: 'b1', + value: 3 + }, + { + source: 'b1', + target: 'a1', + value: 1 + }, + { + source: 'b1', + target: 'c', + value: 2 + } + ]; + + const containerRef = useRef(); + const [width, setWidth] = useState(0); + + useEffect(() => { + const handleResize = () => { + if (containerRef.current && containerRef.current.clientWidth) { + setWidth(containerRef.current.clientWidth); + } + }; + let observer = () => {}; + observer = getResizeObserver(containerRef.current, handleResize); + + return () => { + observer(); + }; + }, [containerRef, width]); + + return ( +
+ `${value} GiB` + } + }} + themeColor={ThemeColor.multiUnordered} + width={width} + /> +
+ ); +}; diff --git a/packages/react-charts/src/echarts/components/Sankey/examples/Skeleton.tsx b/packages/react-charts/src/echarts/components/Sankey/examples/Skeleton.tsx new file mode 100644 index 00000000000..ef0b126c05f --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/examples/Skeleton.tsx @@ -0,0 +1,98 @@ +import { FormEvent, FunctionComponent, useState } from 'react'; +import { Charts, ThemeColor } from '@patternfly/react-charts/echarts'; +import { Switch } from '@patternfly/react-core'; + +import * as echarts from 'echarts/core'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([SankeyChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Skeleton: FunctionComponent = () => { + const [isChecked, setIsChecked] = useState(true); + + const handleChange = (_event: FormEvent, checked: boolean) => { + setIsChecked(checked); + }; + + const data = [ + { + name: 'a' + }, + { + name: 'b' + }, + { + name: 'a1' + }, + { + name: 'a2' + }, + { + name: 'b1' + }, + { + name: 'c' + } + ]; + + const links = [ + { + source: 'a', + target: 'a1', + value: 5 + }, + { + source: 'a', + target: 'a2', + value: 3 + }, + { + source: 'b', + target: 'b1', + value: 8 + }, + { + source: 'a', + target: 'b1', + value: 3 + }, + { + source: 'b1', + target: 'a1', + value: 1 + }, + { + source: 'b1', + target: 'c', + value: 2 + } + ]; + + return ( + <> + + `${value} GiB` + } + }} + themeColor={isChecked ? ThemeColor.skeleton : ThemeColor.green} + width={825} + /> + + ); +}; diff --git a/packages/react-charts/src/echarts/components/Sankey/examples/Theme.tsx b/packages/react-charts/src/echarts/components/Sankey/examples/Theme.tsx new file mode 100644 index 00000000000..cc8ed0b4ed7 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/examples/Theme.tsx @@ -0,0 +1,105 @@ +/* eslint-disable camelcase */ +import { FunctionComponent } from 'react'; +import { getComputedStyleValue, getCustomTheme, Charts, ThemeColor } from '@patternfly/react-charts/echarts'; + +import * as echarts from 'echarts/core'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +import chart_color_blue_300 from '@patternfly/react-tokens/dist/esm/chart_color_blue_300'; +import chart_color_green_300 from '@patternfly/react-tokens/dist/esm/chart_color_green_300'; +import chart_color_purple_300 from '@patternfly/react-tokens/dist/esm/chart_color_purple_300'; +import chart_color_teal_300 from '@patternfly/react-tokens/dist/esm/chart_color_teal_300'; +import chart_color_yellow_300 from '@patternfly/react-tokens/dist/esm/chart_color_yellow_300'; + +// Register required components +echarts.use([SankeyChart, SVGRenderer, TitleComponent, TooltipComponent]); + +export const Theme: FunctionComponent = () => { + const data = [ + { + name: 'a' + }, + { + name: 'b' + }, + { + name: 'a1' + }, + { + name: 'a2' + }, + { + name: 'b1' + }, + { + name: 'c' + } + ]; + + const links = [ + { + source: 'a', + target: 'a1', + value: 5 + }, + { + source: 'a', + target: 'a2', + value: 3 + }, + { + source: 'b', + target: 'b1', + value: 8 + }, + { + source: 'a', + target: 'b1', + value: 3 + }, + { + source: 'b1', + target: 'a1', + value: 1 + }, + { + source: 'b1', + target: 'c', + value: 2 + } + ]; + + const myCustomTheme = getCustomTheme(ThemeColor.default, { + color: [ + getComputedStyleValue(chart_color_purple_300), + getComputedStyleValue(chart_color_blue_300), + getComputedStyleValue(chart_color_green_300), + getComputedStyleValue(chart_color_teal_300), + getComputedStyleValue(chart_color_yellow_300) + ] + }); + + return ( + `${value} GiB` + } + }} + theme={myCustomTheme} + width={825} + /> + ); +}; diff --git a/packages/react-charts/src/echarts/components/Sankey/examples/Toolbox.tsx b/packages/react-charts/src/echarts/components/Sankey/examples/Toolbox.tsx new file mode 100644 index 00000000000..73453276ba6 --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/examples/Toolbox.tsx @@ -0,0 +1,117 @@ +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import { Charts, ThemeColor } from '@patternfly/react-charts/echarts'; +import { getResizeObserver } from '@patternfly/react-core'; + +import * as echarts from 'echarts/core'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, ToolboxComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; + +// Register required components +echarts.use([SankeyChart, SVGRenderer, TitleComponent, ToolboxComponent, TooltipComponent]); + +export const Toolbox: FunctionComponent = () => { + const data = [ + { + name: 'a' + }, + { + name: 'b' + }, + { + name: 'a1' + }, + { + name: 'a2' + }, + { + name: 'b1' + }, + { + name: 'c' + } + ]; + + const links = [ + { + source: 'a', + target: 'a1', + value: 5 + }, + { + source: 'a', + target: 'a2', + value: 3 + }, + { + source: 'b', + target: 'b1', + value: 8 + }, + { + source: 'a', + target: 'b1', + value: 3 + }, + { + source: 'b1', + target: 'a1', + value: 1 + }, + { + source: 'b1', + target: 'c', + value: 2 + } + ]; + + // let observer = () => {}; + const containerRef = useRef(); + const [width, setWidth] = useState(0); + + useEffect(() => { + const handleResize = () => { + if (containerRef.current && containerRef.current.clientWidth) { + setWidth(containerRef.current.clientWidth); + } + }; + let observer = () => {}; + observer = getResizeObserver(containerRef.current, handleResize); + + return () => { + observer(); + }; + }, [containerRef, width]); + + return ( +
+ `${value} GiB` + } + }} + themeColor={ThemeColor.teal} + width={width} + /> +
+ ); +}; diff --git a/packages/react-charts/src/echarts/components/Sankey/examples/index.md b/packages/react-charts/src/echarts/components/Sankey/examples/index.md new file mode 100644 index 00000000000..ea47a3892eb --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/examples/index.md @@ -0,0 +1,71 @@ +--- +id: Sankey chart +section: charts +propComponents: [ + { + component: 'Charts', + source: 'ECharts-docs' + }, + { + component: 'ChartsOptionProps', + source: 'ECharts-docs' + }, + { + component: 'TooltipOptionProps', + source: 'ECharts-docs' + } +] +beta: true +--- + +import { FunctionComponent, useEffect, useRef, useState } from 'react'; +import * as echarts from 'echarts'; +import { SankeyChart } from 'echarts/charts'; +import { TitleComponent, ToolboxComponent, TooltipComponent } from 'echarts/components'; +import { SVGRenderer } from 'echarts/renderers'; +import { getComputedStyleValue, getCustomTheme, Charts, ThemeColor } from '@patternfly/react-charts/echarts'; + +import chart_color_blue_300 from '@patternfly/react-tokens/dist/esm/chart_color_blue_300'; +import chart_color_green_300 from '@patternfly/react-tokens/dist/esm/chart_color_green_300'; +import chart_color_purple_300 from '@patternfly/react-tokens/dist/esm/chart_color_purple_300'; +import chart_color_teal_300 from '@patternfly/react-tokens/dist/esm/chart_color_teal_300'; +import chart_color_yellow_300 from '@patternfly/react-tokens/dist/esm/chart_color_yellow_300'; + +## Introduction +Note: PatternFly React charts live in its own package at [@patternfly/react-charts](https://www.npmjs.com/package/@patternfly/react-charts)! + +The examples below are based on the [Apache ECharts](https://echarts.apache.org/) chart library, along with additional functionality, custom components, and theming for PatternFly. This provides a collection of React based components you can use to build PatternFly patterns with consistent markup, styling, and behavior. + +## Examples +### Basic + +```ts file="./Basic.tsx" + +``` + +### Multi-color (unordered) with responsive container + +```ts file="./Responsive.tsx" + +``` + +### Teal color with responsive container and toolbox + +This demonstrates how to import `ToolboxComponent` for use with ECharts + +```ts file="./Toolbox.tsx" + +``` + +### Custom theme +This demonstrates how to create a color scale via a custom theme, which may be applied to multiple charts. + +```ts file="./Theme.tsx" + +``` + +### Skeleton + +```ts file="./Skeleton.tsx" + +``` diff --git a/packages/react-charts/src/echarts/components/Sankey/index.ts b/packages/react-charts/src/echarts/components/Sankey/index.ts new file mode 100644 index 00000000000..4bb44aaee6a --- /dev/null +++ b/packages/react-charts/src/echarts/components/Sankey/index.ts @@ -0,0 +1 @@ +export * from './Sankey'; diff --git a/packages/react-charts/src/echarts/components/index.ts b/packages/react-charts/src/echarts/components/index.ts new file mode 100644 index 00000000000..45429a5ccd4 --- /dev/null +++ b/packages/react-charts/src/echarts/components/index.ts @@ -0,0 +1,4 @@ +export * from './Charts'; +export * from './themes/ThemeColor'; +export { getCustomTheme, getTheme } from './utils/theme'; +export { getComputedStyleValue } from './utils/styles'; diff --git a/packages/react-charts/src/echarts/components/themes/Theme.ts b/packages/react-charts/src/echarts/components/themes/Theme.ts new file mode 100644 index 00000000000..07e6787b6c4 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/Theme.ts @@ -0,0 +1,15 @@ +import { ThemeOption } from 'echarts/types/src/util/types'; + +/** + * Theme definition interface + * + * @public + */ +export interface ThemeDefinitionInterface extends ThemeOption {} + +/** + * Theme definition type + * + * @public + */ +export type ThemeDefinition = ThemeDefinitionInterface; diff --git a/packages/react-charts/src/echarts/components/themes/ThemeColor.ts b/packages/react-charts/src/echarts/components/themes/ThemeColor.ts new file mode 100644 index 00000000000..15cb8830f2b --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/ThemeColor.ts @@ -0,0 +1,55 @@ +interface ThemeColorInterface { + blue: string; + teal: string; + default: string; + yellow: string; + gray: string; + green: string; + multi: string; + multiOrdered: string; + multiUnordered: string; + orange: string; + purple: string; + skeleton: string; +} + +/** + * The color family to be applied to a theme. For example, 'blue' represents an ordered list of colors + * (i.e., a color scale) composed of the blue color family defined by PatternFly core. + * + * For example, the 'blue' color scale looks like: + * + * chart_color_blue_100 + * chart_color_blue_200 + * chart_color_blue_300 + * chart_color_blue_400 + * chart_color_blue_500 + * + * In this case, the chart_color_blue_100 value would be applied to the first data point in a chart. + * The chart_color_blue_200 value would be applied to the second data point in a chart. And so on... + * + * If legend data is provided to a chart, those colors would be synced with the legend as well. + * + * The 'multiOrdered' color family is intended for ordered charts; donut, pie, bar, & stack + * The 'multiUnordered' color family is intended for unordered charts; area & line + * The 'multi' defaults to the 'multiOrdered' color family + * + * Note: These values are not intended to be applied directly as a component's fill style. For example, "multi" would + * not be a valid fill color. Please use chart variables from PatternFly core (e.g., via the react-charts package) + * + * @public + */ +export const ThemeColor: ThemeColorInterface = { + blue: 'blue', + teal: 'teal', + default: 'blue', + yellow: 'yellow', + gray: 'gray', + green: 'green', + multi: 'multi', + multiOrdered: 'multi-ordered', + multiUnordered: 'multi-unordered', + orange: 'orange', + purple: 'purple', + skeleton: 'skeleton' +}; diff --git a/packages/react-charts/src/echarts/components/themes/base-theme.ts b/packages/react-charts/src/echarts/components/themes/base-theme.ts new file mode 100644 index 00000000000..7ff7fa3a533 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/base-theme.ts @@ -0,0 +1,253 @@ +/* eslint-disable camelcase */ +import chart_echarts_bar_item_style_BarBorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_bar_item_style_BarBorderColor'; +import chart_echarts_boxplot_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_boxplot_item_style_BorderWidth'; +import chart_echarts_candlestick_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_candlestick_item_style_BorderWidth'; +import chart_echarts_datazoom_HandleSize from '@patternfly/react-tokens/dist/esm/chart_echarts_datazoom_HandleSize'; +import chart_echarts_funnel_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_funnel_item_style_BorderWidth'; +import chart_echarts_gauge_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_gauge_item_style_BorderWidth'; +import chart_echarts_geo_emphasis_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_gauge_item_style_BorderWidth'; +import chart_echarts_geo_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_gauge_item_style_BorderWidth'; +import chart_echarts_global_axis_BoundaryGap from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_BoundaryGap'; +import chart_echarts_global_axis_axis_label_Show from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_label_Show'; +import chart_echarts_global_axis_axis_line_Show from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_line_Show'; +import chart_echarts_global_axis_axis_tick_Show from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_tick_Show'; +import chart_echarts_global_axis_split_area_Show from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_split_area_Show'; +import chart_echarts_global_axis_split_line_Show from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_split_line_Show'; +import chart_echarts_graph_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_item_style_BorderWidth'; +import chart_echarts_graph_line_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_line_style_BorderWidth'; +import chart_echarts_graph_Smooth from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_Smooth'; +import chart_echarts_graph_Symbol from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_Symbol'; +import chart_echarts_graph_SymbolSize from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_SymbolSize'; +import chart_echarts_line_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_line_item_style_BorderWidth'; +import chart_echarts_line_line_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_line_line_style_BorderWidth'; +import chart_echarts_line_Smooth from '@patternfly/react-tokens/dist/esm/chart_echarts_line_Smooth'; +import chart_echarts_line_Symbol from '@patternfly/react-tokens/dist/esm/chart_echarts_line_Symbol'; +import chart_echarts_line_SymbolSize from '@patternfly/react-tokens/dist/esm/chart_echarts_line_SymbolSize'; +import chart_echarts_map_emphasis_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_map_emphasis_item_style_BorderWidth'; +import chart_echarts_map_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_map_item_style_BorderWidth'; +import chart_echarts_parallel_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_parallel_item_style_BorderWidth'; +import chart_echarts_pie_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_pie_item_style_BorderWidth'; +import chart_echarts_radar_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_radar_item_style_BorderWidth'; +import chart_echarts_radar_line_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_radar_line_style_BorderWidth'; +import chart_echarts_radar_Smooth from '@patternfly/react-tokens/dist/esm/chart_echarts_radar_Smooth'; +import chart_echarts_radar_Symbol from '@patternfly/react-tokens/dist/esm/chart_echarts_radar_Symbol'; +import chart_echarts_radar_SymbolSize from '@patternfly/react-tokens/dist/esm/chart_echarts_radar_SymbolSize'; +import chart_echarts_sankey_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_sankey_item_style_BorderWidth'; +import chart_echarts_scatter_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_scatter_item_style_BorderWidth'; +import chart_echarts_timeline_control_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_control_style_BorderWidth'; +import chart_echarts_timeline_emphasis_control_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_emphasis_control_style_BorderWidth'; +import chart_echarts_timeline_item_style_BorderWidth from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_item_style_BorderWidth'; +import chart_echarts_timeline_line_style_Width from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_line_style_Width'; +import chart_echarts_tooltip_axis_pointer_cross_style_Width from '@patternfly/react-tokens/dist/esm/chart_echarts_tooltip_axis_pointer_cross_style_Width'; +import chart_echarts_tooltip_axis_pointer_line_style_Width from '@patternfly/react-tokens/dist/esm/chart_echarts_tooltip_axis_pointer_line_style_Width'; +import chart_global_FontFamily from '@patternfly/react-tokens/dist/esm/chart_global_FontFamily'; +import chart_global_FontSize_sm from '@patternfly/react-tokens/dist/esm/chart_global_FontSize_sm'; + +import { ThemeDefinition } from './Theme'; +import { getComputedStyleValue } from '../utils/styles'; + +/** + * Base theme containing EChart properties only + * + * @private Not intended as public API and subject to change + */ +export const BaseTheme = (): ThemeDefinition => { + const textProps = { + fontFamily: chart_global_FontFamily.var.replace(/"/g, "'"), // Well-formed XML + fontSize: chart_global_FontSize_sm.value + }; + + const axisProps = { + boundaryGap: getComputedStyleValue(chart_echarts_global_axis_BoundaryGap), + axisLabel: { + ...textProps, + show: getComputedStyleValue(chart_echarts_global_axis_axis_label_Show) + }, + axisLine: { + lineStyle: {}, + show: getComputedStyleValue(chart_echarts_global_axis_axis_line_Show) + }, + axisTick: { + lineStyle: {}, + show: getComputedStyleValue(chart_echarts_global_axis_axis_tick_Show) + }, + splitArea: { + areaStyle: {}, + show: getComputedStyleValue(chart_echarts_global_axis_split_area_Show) + }, + splitLine: { + lineStyle: {}, + show: getComputedStyleValue(chart_echarts_global_axis_split_line_Show) // Grid + } + }; + + return { + bar: { + itemStyle: { + barBorderWidth: getComputedStyleValue(chart_echarts_bar_item_style_BarBorderWidth) + } + }, + boxplot: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_boxplot_item_style_BorderWidth) + } + }, + candlestick: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_candlestick_item_style_BorderWidth) + } + }, + categoryAxis: { ...axisProps }, + dataZoom: { + handleSize: getComputedStyleValue(chart_echarts_datazoom_HandleSize), + textStyle: { ...textProps } + }, + funnel: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_funnel_item_style_BorderWidth) + } + }, + gauge: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_gauge_item_style_BorderWidth) + } + }, + geo: { + emphasis: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_geo_emphasis_item_style_BorderWidth) + }, + label: {} + }, + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_geo_item_style_BorderWidth) + }, + label: {} + }, + graph: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_graph_item_style_BorderWidth) + }, + label: {}, + lineStyle: { + width: getComputedStyleValue(chart_echarts_graph_line_style_BorderWidth) + }, + smooth: getComputedStyleValue(chart_echarts_graph_Smooth), + symbolSize: getComputedStyleValue(chart_echarts_graph_SymbolSize), + symbol: getComputedStyleValue(chart_echarts_graph_Symbol) + }, + label: { + ...textProps + }, + legend: { + textStyle: { ...textProps } + }, + line: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_line_item_style_BorderWidth) + }, + lineStyle: { + width: getComputedStyleValue(chart_echarts_line_line_style_BorderWidth) + }, + smooth: getComputedStyleValue(chart_echarts_line_Smooth), + symbolSize: getComputedStyleValue(chart_echarts_line_SymbolSize), + symbol: getComputedStyleValue(chart_echarts_line_Symbol) + }, + logAxis: { ...axisProps }, + map: { + emphasis: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_map_emphasis_item_style_BorderWidth) + }, + label: {} + }, + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_map_item_style_BorderWidth) + }, + label: { + label: {} + } + }, + markPoint: { + emphasis: { + label: {} + }, + label: {} + }, + parallel: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_parallel_item_style_BorderWidth) + } + }, + pie: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_pie_item_style_BorderWidth) + } + }, + radar: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_radar_item_style_BorderWidth) + }, + lineStyle: { + width: getComputedStyleValue(chart_echarts_radar_line_style_BorderWidth) + }, + smooth: getComputedStyleValue(chart_echarts_radar_Smooth), + symbolSize: getComputedStyleValue(chart_echarts_radar_SymbolSize), + symbol: getComputedStyleValue(chart_echarts_radar_Symbol) + }, + sankey: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_sankey_item_style_BorderWidth) + } + }, + scatter: { + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_scatter_item_style_BorderWidth) + } + }, + textStyle: { ...textProps }, + timeAxis: { ...axisProps }, + timeline: { + emphasis: { + controlStyle: { + borderWidth: getComputedStyleValue(chart_echarts_timeline_emphasis_control_style_BorderWidth) + }, + itemStyle: {}, + label: {} + }, + checkpointStyle: {}, + controlStyle: { + borderWidth: getComputedStyleValue(chart_echarts_timeline_control_style_BorderWidth) + }, + itemStyle: { + borderWidth: getComputedStyleValue(chart_echarts_timeline_item_style_BorderWidth) + }, + label: {}, + lineStyle: { + width: getComputedStyleValue(chart_echarts_timeline_line_style_Width) + } + }, + title: { + subtextStyle: { ...textProps }, + textStyle: { ...textProps } + }, + toolbox: { + emphasis: { + iconStyle: {} + }, + iconStyle: {} + }, + tooltip: { + axisPointer: { + crossStyle: { + width: getComputedStyleValue(chart_echarts_tooltip_axis_pointer_cross_style_Width) + }, + lineStyle: { + width: getComputedStyleValue(chart_echarts_tooltip_axis_pointer_line_style_Width) + } + } + }, + valueAxis: { ...axisProps }, + visualMap: {} + }; +}; diff --git a/packages/react-charts/src/echarts/components/themes/color-theme.ts b/packages/react-charts/src/echarts/components/themes/color-theme.ts new file mode 100644 index 00000000000..beffa6d54f5 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/color-theme.ts @@ -0,0 +1,253 @@ +/* eslint-disable camelcase */ +import chart_echarts_BackgroundColor from '@patternfly/react-tokens/dist/esm/chart_echarts_BackgroundColor'; +import chart_echarts_bar_item_style_BarBorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_bar_item_style_BarBorderColor'; +import chart_echarts_boxplot_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_boxplot_item_style_BorderColor'; +import chart_echarts_candlestick_item_style_positive_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_candlestick_item_style_positive_BorderColor'; +import chart_echarts_candlestick_item_style_positive_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_candlestick_item_style_positive_Color'; +import chart_echarts_candlestick_item_style_negative_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_candlestick_item_style_negative_Color'; +import chart_echarts_candlestick_item_style_negative_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_candlestick_item_style_negative_BorderColor'; +import chart_echarts_funnel_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_funnel_item_style_BorderColor'; +import chart_echarts_gauge_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_gauge_item_style_BorderColor'; +import chart_echarts_geo_emphasis_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_geo_emphasis_item_style_BorderColor'; +import chart_echarts_geo_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_geo_item_style_BorderColor'; +import chart_echarts_global_axis_axis_line_item_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_line_item_style_Color'; +import chart_echarts_global_axis_axis_tick_item_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_tick_item_style_Color'; +import chart_echarts_global_axis_split_area_area_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_split_area_area_style_Color'; +import chart_echarts_global_label_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_global_label_Color'; +import chart_echarts_graph_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_item_style_BorderColor'; +import chart_echarts_graph_line_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_graph_line_style_Color'; +import chart_echarts_map_emphasis_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_map_emphasis_item_style_BorderColor'; +import chart_echarts_map_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_map_item_style_BorderColor'; +import chart_echarts_parallel_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_parallel_item_style_BorderColor'; +import chart_echarts_pie_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_pie_item_style_BorderColor'; +import chart_echarts_sankey_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_sankey_item_style_BorderColor'; +import chart_echarts_scatter_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_scatter_item_style_BorderColor'; +import chart_echarts_tooltip_axis_pointer_cross_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_tooltip_axis_pointer_cross_style_Color'; +import chart_echarts_tooltip_axis_pointer_line_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_tooltip_axis_pointer_line_style_Color'; +import chart_echarts_toolbox_emphasis_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_toolbox_emphasis_item_style_BorderColor'; +import chart_echarts_toolbox_item_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_toolbox_item_style_BorderColor'; +import chart_echarts_timeline_emphasis_control_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_emphasis_control_style_Color'; +import chart_echarts_timeline_emphasis_item_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_emphasis_item_style_Color'; +import chart_echarts_timeline_emphasis_control_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_emphasis_control_style_BorderColor'; +import chart_echarts_timeline_checkpoint_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_checkpoint_style_Color'; +import chart_echarts_timeline_checkpoint_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_checkpoint_style_BorderColor'; +import chart_echarts_timeline_control_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_control_style_Color'; +import chart_echarts_timeline_control_style_BorderColor from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_control_style_BorderColor'; +import chart_echarts_timeline_item_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_item_style_Color'; +import chart_echarts_timeline_line_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_timeline_line_style_Color'; +import chart_echarts_tooltip_backgroundColor from '@patternfly/react-tokens/dist/esm/chart_echarts_tooltip_backgroundColor'; +import chart_echarts_tooltip_text_Style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_tooltip_text_Style_Color'; + +import { ThemeDefinition } from './Theme'; +import { getComputedStyleValue } from '../utils/styles'; + +interface ColorThemeInterface { + COLOR_SCALE: string[]; +} + +/** + * ECharts color theme + * + * @private Not intended as public API and subject to change + * @beta + */ +export const ColorTheme = (props: ColorThemeInterface): ThemeDefinition => { + const { COLOR_SCALE } = props; + + const labelProps = { + color: getComputedStyleValue(chart_echarts_global_label_Color) + }; + + const axisProps = { + axisLabel: { ...labelProps }, + axisLine: { + lineStyle: { + color: getComputedStyleValue(chart_echarts_global_axis_axis_line_item_style_Color) + } + }, + axisTick: { + lineStyle: { + color: getComputedStyleValue(chart_echarts_global_axis_axis_tick_item_style_Color) + } + }, + splitArea: { + areaStyle: { + color: getComputedStyleValue(chart_echarts_global_axis_split_area_area_style_Color) + } + }, + splitLine: { + lineStyle: { + color: getComputedStyleValue(chart_echarts_global_axis_axis_tick_item_style_Color) // Grid + } + } + }; + + return { + color: COLOR_SCALE, // See https://echarts.apache.org/en/option.html#color + backgroundColor: getComputedStyleValue(chart_echarts_BackgroundColor), // See https://echarts.apache.org/en/option.html#backgroundColor + bar: { + itemStyle: { + barBorderColor: getComputedStyleValue(chart_echarts_bar_item_style_BarBorderColor) + } + }, + boxplot: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_boxplot_item_style_BorderColor) + } + }, + candlestick: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_candlestick_item_style_negative_BorderColor), + borderColor0: getComputedStyleValue(chart_echarts_candlestick_item_style_positive_BorderColor), + color: getComputedStyleValue(chart_echarts_candlestick_item_style_negative_Color), + color0: getComputedStyleValue(chart_echarts_candlestick_item_style_positive_Color) + } + }, + categoryAxis: { ...axisProps }, + dataZoom: {}, + funnel: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_funnel_item_style_BorderColor) + } + }, + gauge: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_gauge_item_style_BorderColor) + } + }, + geo: { + emphasis: { + itemStyle: { + areaColor: COLOR_SCALE[1], + borderColor: getComputedStyleValue(chart_echarts_geo_emphasis_item_style_BorderColor) + }, + label: { ...labelProps } + }, + itemStyle: { + areaColor: COLOR_SCALE[0], + borderColor: getComputedStyleValue(chart_echarts_geo_item_style_BorderColor) + }, + label: { ...labelProps } + }, + graph: { + color: COLOR_SCALE, + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_graph_item_style_BorderColor) + }, + label: { ...labelProps }, + lineStyle: { + color: getComputedStyleValue(chart_echarts_graph_line_style_Color) + } + }, + label: { ...labelProps }, + legend: { + textStyle: { + color: getComputedStyleValue(chart_echarts_global_label_Color) + } + }, + line: {}, + logAxis: { ...axisProps }, + map: { + emphasis: { + itemStyle: { + areaColor: COLOR_SCALE[1], + borderColor: getComputedStyleValue(chart_echarts_map_emphasis_item_style_BorderColor) + }, + label: { ...labelProps } + }, + itemStyle: { + areaColor: COLOR_SCALE[0], + borderColor: getComputedStyleValue(chart_echarts_map_item_style_BorderColor) + }, + label: { ...labelProps } + }, + markPoint: { + emphasis: { + label: { ...labelProps } + }, + label: { ...labelProps } + }, + parallel: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_parallel_item_style_BorderColor) + } + }, + pie: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_pie_item_style_BorderColor) + } + }, + radar: {}, + sankey: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_sankey_item_style_BorderColor) + } + }, + scatter: { + itemStyle: { + borderColor: getComputedStyleValue(chart_echarts_scatter_item_style_BorderColor) + } + }, + textStyle: {}, + timeAxis: { ...axisProps }, + timeline: { + emphasis: { + controlStyle: { + color: getComputedStyleValue(chart_echarts_timeline_emphasis_control_style_Color), + borderColor: getComputedStyleValue(chart_echarts_timeline_emphasis_control_style_BorderColor) + }, + itemStyle: { + color: getComputedStyleValue(chart_echarts_timeline_emphasis_item_style_Color) + }, + label: { ...labelProps } + }, + checkpointStyle: { + color: getComputedStyleValue(chart_echarts_timeline_checkpoint_style_Color), + borderColor: getComputedStyleValue(chart_echarts_timeline_checkpoint_style_BorderColor) + }, + controlStyle: { + color: getComputedStyleValue(chart_echarts_timeline_control_style_Color), + borderColor: getComputedStyleValue(chart_echarts_timeline_control_style_BorderColor) + }, + itemStyle: { + color: getComputedStyleValue(chart_echarts_timeline_item_style_Color) + }, + label: { ...labelProps }, + lineStyle: { + color: getComputedStyleValue(chart_echarts_timeline_line_style_Color) + } + }, + title: { + subtextStyle: { ...labelProps }, + textStyle: { ...labelProps } + }, + toolbox: { + emphasis: { + iconStyle: { + borderColor: getComputedStyleValue(chart_echarts_toolbox_emphasis_item_style_BorderColor) + } + }, + iconStyle: { + borderColor: getComputedStyleValue(chart_echarts_toolbox_item_style_BorderColor) + } + }, + tooltip: { + backgroundColor: getComputedStyleValue(chart_echarts_tooltip_backgroundColor), + axisPointer: { + crossStyle: { + color: getComputedStyleValue(chart_echarts_tooltip_axis_pointer_cross_style_Color) + }, + lineStyle: { + color: getComputedStyleValue(chart_echarts_tooltip_axis_pointer_line_style_Color) + } + }, + textStyle: { + color: getComputedStyleValue(chart_echarts_tooltip_text_Style_Color) + } + }, + valueAxis: { ...axisProps }, + visualMap: { + color: COLOR_SCALE + } + }; +}; diff --git a/packages/react-charts/src/echarts/components/themes/colors/blue-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/blue-theme.ts new file mode 100644 index 00000000000..de69ebf60e0 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/blue-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_blue_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_blue_ColorScale_100'; +import chart_theme_blue_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_blue_ColorScale_200'; +import chart_theme_blue_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_blue_ColorScale_300'; +import chart_theme_blue_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_blue_ColorScale_400'; +import chart_theme_blue_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_blue_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Blue color theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getBlueColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_blue_ColorScale_100), + getComputedStyleValue(chart_theme_blue_ColorScale_200), + getComputedStyleValue(chart_theme_blue_ColorScale_300), + getComputedStyleValue(chart_theme_blue_ColorScale_400), + getComputedStyleValue(chart_theme_blue_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/gray-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/gray-theme.ts new file mode 100644 index 00000000000..076e0b599d6 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/gray-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_gray_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_gray_ColorScale_100'; +import chart_theme_gray_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_gray_ColorScale_200'; +import chart_theme_gray_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_gray_ColorScale_300'; +import chart_theme_gray_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_gray_ColorScale_400'; +import chart_theme_gray_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_gray_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Gray color theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getGrayColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_gray_ColorScale_100), + getComputedStyleValue(chart_theme_gray_ColorScale_200), + getComputedStyleValue(chart_theme_gray_ColorScale_300), + getComputedStyleValue(chart_theme_gray_ColorScale_400), + getComputedStyleValue(chart_theme_gray_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/green-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/green-theme.ts new file mode 100644 index 00000000000..7c578344fe1 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/green-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_green_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_green_ColorScale_100'; +import chart_theme_green_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_green_ColorScale_200'; +import chart_theme_green_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_green_ColorScale_300'; +import chart_theme_green_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_green_ColorScale_400'; +import chart_theme_green_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_green_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Green color theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getGreenColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_green_ColorScale_100), + getComputedStyleValue(chart_theme_green_ColorScale_200), + getComputedStyleValue(chart_theme_green_ColorScale_300), + getComputedStyleValue(chart_theme_green_ColorScale_400), + getComputedStyleValue(chart_theme_green_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/multi-ordered-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/multi-ordered-theme.ts new file mode 100644 index 00000000000..bc22c38e48e --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/multi-ordered-theme.ts @@ -0,0 +1,65 @@ +/* eslint-disable camelcase */ +import chart_theme_multi_color_ordered_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_100'; +import chart_theme_multi_color_ordered_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_200'; +import chart_theme_multi_color_ordered_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_300'; +import chart_theme_multi_color_ordered_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_400'; +import chart_theme_multi_color_ordered_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_500'; +import chart_theme_multi_color_ordered_ColorScale_600 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_600'; +import chart_theme_multi_color_ordered_ColorScale_700 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_700'; +import chart_theme_multi_color_ordered_ColorScale_800 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_800'; +import chart_theme_multi_color_ordered_ColorScale_900 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_900'; +import chart_theme_multi_color_ordered_ColorScale_1000 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1000'; +import chart_theme_multi_color_ordered_ColorScale_1100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1100'; +import chart_theme_multi_color_ordered_ColorScale_1200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1200'; +import chart_theme_multi_color_ordered_ColorScale_1300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1300'; +import chart_theme_multi_color_ordered_ColorScale_1400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1400'; +import chart_theme_multi_color_ordered_ColorScale_1500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1500'; +import chart_theme_multi_color_ordered_ColorScale_1600 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1600'; +import chart_theme_multi_color_ordered_ColorScale_1700 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1700'; +import chart_theme_multi_color_ordered_ColorScale_1800 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1800'; +import chart_theme_multi_color_ordered_ColorScale_1900 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_1900'; +import chart_theme_multi_color_ordered_ColorScale_2000 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_2000'; +import chart_theme_multi_color_ordered_ColorScale_2100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_2100'; +import chart_theme_multi_color_ordered_ColorScale_2200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_2200'; +import chart_theme_multi_color_ordered_ColorScale_2300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_2300'; +import chart_theme_multi_color_ordered_ColorScale_2400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_2400'; +import chart_theme_multi_color_ordered_ColorScale_2500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_ordered_ColorScale_2500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Multi-color ordered theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getMultiColorOrderedTheme = () => + ColorTheme({ + // The color order below (minus the purple color family) improves the color contrast in ordered charts; donut, pie, bar, & stack + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_100), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_200), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_300), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_400), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_500), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_600), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_700), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_800), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_900), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1000), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1100), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1200), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1300), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1400), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1500), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1600), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1700), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1800), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_1900), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_2000), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_2100), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_2200), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_2300), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_2400), + getComputedStyleValue(chart_theme_multi_color_ordered_ColorScale_2500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/multi-unordered-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/multi-unordered-theme.ts new file mode 100644 index 00000000000..a1dcc75d59c --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/multi-unordered-theme.ts @@ -0,0 +1,85 @@ +/* eslint-disable camelcase */ +import chart_theme_multi_color_unordered_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_100'; +import chart_theme_multi_color_unordered_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_200'; +import chart_theme_multi_color_unordered_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_300'; +import chart_theme_multi_color_unordered_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_400'; +import chart_theme_multi_color_unordered_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_500'; +import chart_theme_multi_color_unordered_ColorScale_600 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_600'; +import chart_theme_multi_color_unordered_ColorScale_700 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_700'; +import chart_theme_multi_color_unordered_ColorScale_800 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_800'; +import chart_theme_multi_color_unordered_ColorScale_900 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_900'; +import chart_theme_multi_color_unordered_ColorScale_1000 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1000'; +import chart_theme_multi_color_unordered_ColorScale_1100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1100'; +import chart_theme_multi_color_unordered_ColorScale_1200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1200'; +import chart_theme_multi_color_unordered_ColorScale_1300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1300'; +import chart_theme_multi_color_unordered_ColorScale_1400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1400'; +import chart_theme_multi_color_unordered_ColorScale_1500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1500'; +import chart_theme_multi_color_unordered_ColorScale_1600 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1600'; +import chart_theme_multi_color_unordered_ColorScale_1700 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1700'; +import chart_theme_multi_color_unordered_ColorScale_1800 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1800'; +import chart_theme_multi_color_unordered_ColorScale_1900 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_1900'; +import chart_theme_multi_color_unordered_ColorScale_2000 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2000'; +import chart_theme_multi_color_unordered_ColorScale_2100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2100'; +import chart_theme_multi_color_unordered_ColorScale_2200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2200'; +import chart_theme_multi_color_unordered_ColorScale_2300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2300'; +import chart_theme_multi_color_unordered_ColorScale_2400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2400'; +import chart_theme_multi_color_unordered_ColorScale_2500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2500'; +import chart_theme_multi_color_unordered_ColorScale_2600 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2600'; +import chart_theme_multi_color_unordered_ColorScale_2700 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2700'; +import chart_theme_multi_color_unordered_ColorScale_2800 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2800'; +import chart_theme_multi_color_unordered_ColorScale_2900 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_2900'; +import chart_theme_multi_color_unordered_ColorScale_3000 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_3000'; +import chart_theme_multi_color_unordered_ColorScale_3100 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_3100'; +import chart_theme_multi_color_unordered_ColorScale_3200 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_3200'; +import chart_theme_multi_color_unordered_ColorScale_3300 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_3300'; +import chart_theme_multi_color_unordered_ColorScale_3400 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_3400'; +import chart_theme_multi_color_unordered_ColorScale_3500 from '@patternfly/react-tokens/dist/esm/chart_theme_multi_color_unordered_ColorScale_3500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Multi-color unordered theme -- see https://github.com/patternfly/patternfly-next/issues/1551 + * + * @private Not intended as public API and subject to change + */ +export const getMultiColorUnorderedTheme = () => + ColorTheme({ + // The color order below improves the color contrast in unordered charts; area & line + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_100), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_200), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_300), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_400), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_500), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_600), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_700), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_800), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_900), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1000), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1100), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1200), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1300), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1400), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1500), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1600), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1700), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1800), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_1900), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2000), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2100), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2200), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2300), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2400), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2500), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2600), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2700), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2800), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_2900), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_3000), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_3100), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_3200), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_3300), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_3400), + getComputedStyleValue(chart_theme_multi_color_unordered_ColorScale_3500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/orange-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/orange-theme.ts new file mode 100644 index 00000000000..000776a98bc --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/orange-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_orange_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_orange_ColorScale_100'; +import chart_theme_orange_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_orange_ColorScale_200'; +import chart_theme_orange_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_orange_ColorScale_300'; +import chart_theme_orange_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_orange_ColorScale_400'; +import chart_theme_orange_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_orange_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Orange color theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getOrangeColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_orange_ColorScale_100), + getComputedStyleValue(chart_theme_orange_ColorScale_200), + getComputedStyleValue(chart_theme_orange_ColorScale_300), + getComputedStyleValue(chart_theme_orange_ColorScale_400), + getComputedStyleValue(chart_theme_orange_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/purple-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/purple-theme.ts new file mode 100644 index 00000000000..cd1aa460468 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/purple-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_purple_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_purple_ColorScale_100'; +import chart_theme_purple_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_purple_ColorScale_200'; +import chart_theme_purple_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_purple_ColorScale_300'; +import chart_theme_purple_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_purple_ColorScale_400'; +import chart_theme_purple_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_purple_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Purple ordered theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getPurpleColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_purple_ColorScale_100), + getComputedStyleValue(chart_theme_purple_ColorScale_200), + getComputedStyleValue(chart_theme_purple_ColorScale_300), + getComputedStyleValue(chart_theme_purple_ColorScale_400), + getComputedStyleValue(chart_theme_purple_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/skeleton-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/skeleton-theme.ts new file mode 100644 index 00000000000..895c20bd742 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/skeleton-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_bullet_qualitative_range_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_bullet_qualitative_range_ColorScale_100'; +import chart_bullet_qualitative_range_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_bullet_qualitative_range_ColorScale_200'; +import chart_bullet_qualitative_range_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_bullet_qualitative_range_ColorScale_300'; +import chart_bullet_qualitative_range_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_bullet_qualitative_range_ColorScale_400'; +import chart_bullet_qualitative_range_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_bullet_qualitative_range_ColorScale_500'; +import { ColorTheme } from '../skeleton-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Skeleton color theme + * + * @private Not intended as public API and subject to change + */ +export const getSkeletonColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_bullet_qualitative_range_ColorScale_100), + getComputedStyleValue(chart_bullet_qualitative_range_ColorScale_200), + getComputedStyleValue(chart_bullet_qualitative_range_ColorScale_300), + getComputedStyleValue(chart_bullet_qualitative_range_ColorScale_400), + getComputedStyleValue(chart_bullet_qualitative_range_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/teal-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/teal-theme.ts new file mode 100644 index 00000000000..9d36e4d05cb --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/teal-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_teal_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_teal_ColorScale_100'; +import chart_theme_teal_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_teal_ColorScale_200'; +import chart_theme_teal_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_teal_ColorScale_300'; +import chart_theme_teal_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_teal_ColorScale_400'; +import chart_theme_teal_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_teal_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Teal color theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getTealColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_teal_ColorScale_100), + getComputedStyleValue(chart_theme_teal_ColorScale_200), + getComputedStyleValue(chart_theme_teal_ColorScale_300), + getComputedStyleValue(chart_theme_teal_ColorScale_400), + getComputedStyleValue(chart_theme_teal_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/colors/yellow-theme.ts b/packages/react-charts/src/echarts/components/themes/colors/yellow-theme.ts new file mode 100644 index 00000000000..a50e7756589 --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/colors/yellow-theme.ts @@ -0,0 +1,24 @@ +/* eslint-disable camelcase */ +import chart_theme_yellow_ColorScale_100 from '@patternfly/react-tokens/dist/esm/chart_theme_yellow_ColorScale_100'; +import chart_theme_yellow_ColorScale_200 from '@patternfly/react-tokens/dist/esm/chart_theme_yellow_ColorScale_200'; +import chart_theme_yellow_ColorScale_300 from '@patternfly/react-tokens/dist/esm/chart_theme_yellow_ColorScale_300'; +import chart_theme_yellow_ColorScale_400 from '@patternfly/react-tokens/dist/esm/chart_theme_yellow_ColorScale_400'; +import chart_theme_yellow_ColorScale_500 from '@patternfly/react-tokens/dist/esm/chart_theme_yellow_ColorScale_500'; +import { ColorTheme } from '../color-theme'; +import { getComputedStyleValue } from '../../utils/styles'; + +/** + * Yellow color theme -- see https://docs.google.com/document/d/1cw10pJFXWruB1SA8TQwituxn5Ss6KpxYPCOYGrH8qAY/edit + * + * @private Not intended as public API and subject to change + */ +export const getYellowColorTheme = () => + ColorTheme({ + COLOR_SCALE: [ + getComputedStyleValue(chart_theme_yellow_ColorScale_100), + getComputedStyleValue(chart_theme_yellow_ColorScale_200), + getComputedStyleValue(chart_theme_yellow_ColorScale_300), + getComputedStyleValue(chart_theme_yellow_ColorScale_400), + getComputedStyleValue(chart_theme_yellow_ColorScale_500) + ] + }); diff --git a/packages/react-charts/src/echarts/components/themes/skeleton-theme.ts b/packages/react-charts/src/echarts/components/themes/skeleton-theme.ts new file mode 100644 index 00000000000..e5f4777d16d --- /dev/null +++ b/packages/react-charts/src/echarts/components/themes/skeleton-theme.ts @@ -0,0 +1,213 @@ +/* eslint-disable camelcase */ +import chart_echarts_BackgroundColor from '@patternfly/react-tokens/dist/esm/chart_echarts_BackgroundColor'; +import chart_echarts_global_axis_axis_line_item_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_line_item_style_Color'; +import chart_echarts_global_axis_axis_tick_item_style_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_global_axis_axis_tick_item_style_Color'; +import chart_echarts_skeleton_label_Color from '@patternfly/react-tokens/dist/esm/chart_echarts_skeleton_label_Color'; + +import { ThemeDefinition } from '../themes/Theme'; +import { getComputedStyleValue } from '../utils/styles'; + +interface ColorThemeInterface { + COLOR_SCALE: string[]; +} + +/** + * ECharts skeleton color theme + * + * @private Not intended as public API and subject to change + */ +export const ColorTheme = (props: ColorThemeInterface): ThemeDefinition => { + const { COLOR_SCALE } = props; + + const labelProps = { + backgroundColor: COLOR_SCALE[0], + color: getComputedStyleValue(chart_echarts_skeleton_label_Color) + }; + + const axisProps = { + axisLabel: { ...labelProps }, + axisLine: { + lineStyle: { + color: getComputedStyleValue(chart_echarts_global_axis_axis_line_item_style_Color) + } + }, + axisTick: { + lineStyle: { + color: getComputedStyleValue(chart_echarts_global_axis_axis_tick_item_style_Color) + } + }, + splitArea: { + areaStyle: { + color: COLOR_SCALE + } + }, + splitLine: { + lineStyle: { + color: [COLOR_SCALE[0]] + } + } + }; + + return { + color: COLOR_SCALE, + backgroundColor: getComputedStyleValue(chart_echarts_BackgroundColor), // See https://echarts.apache.org/en/option.html#backgroundColor + bar: { + itemStyle: { + barBorderColor: COLOR_SCALE[0] + } + }, + boxplot: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + candlestick: { + itemStyle: { + borderColor: COLOR_SCALE[0], + borderColor0: COLOR_SCALE[1], + color: COLOR_SCALE[0], + color0: COLOR_SCALE[1] + } + }, + categoryAxis: { ...axisProps }, + dataZoom: {}, + funnel: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + gauge: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + geo: { + emphasis: { + itemStyle: { + areaColor: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + label: { ...labelProps } + }, + itemStyle: { + areaColor: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + label: { ...labelProps } + }, + graph: { + color: COLOR_SCALE, + itemStyle: { + borderColor: COLOR_SCALE[0] + }, + label: { ...labelProps }, + lineStyle: { + color: COLOR_SCALE[0] + } + }, + label: { ...labelProps }, + legend: { + textStyle: { ...labelProps } + }, + line: {}, + logAxis: { ...axisProps }, + map: { + emphasis: { + itemStyle: { + areaColor: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + label: { ...labelProps } + }, + itemStyle: { + areaColor: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + label: { ...labelProps } + }, + markPoint: { + emphasis: { + label: { ...labelProps } + }, + label: { ...labelProps } + }, + parallel: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + pie: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + radar: {}, + sankey: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + scatter: { + itemStyle: { + borderColor: COLOR_SCALE[0] + } + }, + textStyle: {}, + timeAxis: { ...axisProps }, + timeline: { + emphasis: { + controlStyle: { + color: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + itemStyle: { + color: COLOR_SCALE[0] + }, + label: { ...labelProps } + }, + checkpointStyle: { + color: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + controlStyle: { + color: COLOR_SCALE[0], + borderColor: COLOR_SCALE[0] + }, + itemStyle: { + color: COLOR_SCALE[0] + }, + label: { ...labelProps }, + lineStyle: { + color: COLOR_SCALE[0] + } + }, + title: { + subtextStyle: { ...labelProps }, + textStyle: { ...labelProps } + }, + toolbox: { + emphasis: { + iconStyle: { + borderColor: COLOR_SCALE[0] + } + }, + iconStyle: { + borderColor: COLOR_SCALE[0] + } + }, + tooltip: { + axisPointer: { + crossStyle: { + color: COLOR_SCALE[0] + }, + lineStyle: { + color: COLOR_SCALE[0] + } + } + }, + valueAxis: { ...axisProps }, + visualMap: { + color: COLOR_SCALE + } + }; +}; diff --git a/packages/react-charts/src/echarts/components/utils/observe.ts b/packages/react-charts/src/echarts/components/utils/observe.ts new file mode 100644 index 00000000000..293776f91a8 --- /dev/null +++ b/packages/react-charts/src/echarts/components/utils/observe.ts @@ -0,0 +1,37 @@ +/** + * Mutation Observer Helper function -- see developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe + * + * @param {string} selector The DOM selector to watch + * @param {object} opt MutationObserver options + * @param {function} cb Pass Mutation object to a callback function + * @private Not intended as public API and subject to change + */ +export const observe = (selector: any, opt: any, cb: any) => { + let unobserve: any; + + if (selector) { + const Obs = new MutationObserver((m) => [...m].forEach(cb)); + document.querySelectorAll(selector).forEach((el) => Obs.observe(el, opt)); + unobserve = () => Obs.disconnect(); + } + return () => { + if (unobserve) { + unobserve(); + } + }; +}; + +// See https://stackoverflow.com/questions/17134823/detect-element-style-changes-with-javascript +export const getMutationObserver = (nodeSelector: string, cb: any) => + observe( + nodeSelector, + { + attributesList: ['style'], // Only the "style" attribute + attributeOldValue: true // Report also the oldValue + }, + (m: any) => { + if (cb) { + cb(m); + } + } + ); diff --git a/packages/react-charts/src/echarts/components/utils/styles.ts b/packages/react-charts/src/echarts/components/utils/styles.ts new file mode 100644 index 00000000000..862be8950dd --- /dev/null +++ b/packages/react-charts/src/echarts/components/utils/styles.ts @@ -0,0 +1,50 @@ +/** + * Copied from exenv + * + * @private Not intended as public API and subject to change + */ +export const canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement); + +/** + * Returns the class name that will be applied to the outermost div rendered by the chart's container + * + * @private Not intended as public API and subject to change + */ +export const getClassName = (className: string) => { + let cleanClassName; + + // Cleanup class name + if (className) { + cleanClassName = className + .replace(/pf-v6-c-chart/g, '') + .replace(/pf-c-chart/g, '') + .replace(/\s+/g, ' ') + .trim(); + } + return cleanClassName?.length ? `pf-v6-c-chart ${cleanClassName}` : 'pf-v6-c-chart'; +}; + +/** + * Get computed style value -- see https://github.com/apache/echarts/issues/19743 + * + * Note that Victory uses the style property to apply CSS variables. However, ECharts' LineChart uses `fill` and + * `stroke` attributes, which doesn't work with CSS variables. In addition, using CSS variables with Sankey + * causes sever flashing during mouse hover. Therefore, we will obtain the CSS computed value from the DOM. + * + * @public + * @beta + */ +export const getComputedStyleValue = (token: { name: string; value: string | number; var: string }) => { + let result: any; + + if (canUseDOM) { + result = window.getComputedStyle(document.body)?.getPropertyValue(token.name); + + if (result === '') { + result = undefined; + } else if (!isNaN(result) || result === 'true' || result === 'false') { + result = JSON.parse(result); + } + } + return result !== undefined ? result : token.value; +}; diff --git a/packages/react-charts/src/echarts/components/utils/symbol.tsx b/packages/react-charts/src/echarts/components/utils/symbol.tsx new file mode 100644 index 00000000000..ee4c4a6f4aa --- /dev/null +++ b/packages/react-charts/src/echarts/components/utils/symbol.tsx @@ -0,0 +1,166 @@ +import { getComputedStyleValue } from './styles'; + +// eslint-disable-next-line camelcase +import chart_echarts_BackgroundColor from '@patternfly/react-tokens/dist/esm/chart_echarts_BackgroundColor'; + +const enum SymbolType { + arrow = 'arrow', + circle = 'circle', + diamond = 'diamond', + emptyArrow = 'emptyArrow', + emptyCircle = 'emptyCircle', + emptyDiamond = 'emptyDiamond', + emptyPin = 'emptyPin', + emptyRect = 'emptyRect', + emptyRoundRect = 'emptyRoundRect', + emptySquare = 'emptySquare', + emptyTriangle = 'emptyTriangle', + pin = 'pin', + rect = 'rect', + roundRect = 'roundRect', + square = 'square', + triangle = 'triangle' +} + +// ECharts icon types -- see https://svg-path.com/ +const symbols = { + arrow: 'M12.5 7L19.9667 18.2L12.5 15.4L5.0333 18.2L12.5 7Z', + circle: 'M18.1 7A5.6 5.6 0 1 1 18.1 6.9994Z', + diamond: 'M12.5 1.4L18.1 7L12.5 12.6L6.9 7Z', + emptyArrow: 'M12.5 7L19.9667 18.2L12.5 15.4L5.0333 18.2L12.5 7Z', + emptyCircle: 'M18.1 7A5.6 5.6 0 1 1 18.1 6.9994Z', + emptyDiamond: 'M12.5 1.4L18.1 7L12.5 12.6L6.9 7Z', + emptyPin: + 'M9.4642 2.04A3.36 3.36 0 1 1 15.5358 2.04C14.6718 3.8615 12.5 4.648 12.5 7C12.5 4.648 10.3282 3.8615 9.4642 2.04Z', + emptyRect: 'M6.9,1.4 l14.2,0 l0,11.2 l-14.2,0 Z', + emptyRoundRect: + 'M9.7,1.4 L18.3,1.4 A2.8,2.8,0,0,1,21.1,4.2 L21.1,9.8 A2.8,2.8,0,0,1,18.3,12.6 L9.7,12.6 A2.8,2.8,0,0,1,6.9,9.8 L6.9,4.2 A2.8,2.8,0,0,1,9.7,1.4 Z', + emptySquare: 'M6.9 1.4l11.2 0l0 11.2l-11.2 0Z', + emptyTriangle: 'M12.5 1.4L18.1 12.5L12.5 12.5L6.9 12.5Z', + pin: 'M9.4642 2.04A3.36 3.36 0 1 1 15.5358 2.04C14.6718 3.8615 12.5 4.648 12.5 7C12.5 4.648 10.3282 3.8615 9.4642 2.04Z', + rect: 'M6.9,1.4 l18.2,0 l0,11.2 l-18.2,0 Z', + roundRect: + 'M9.7,1.4 L22.3,1.4 A2.8,2.8,0,0,1,25.1,4.2 L25.1,9.8 A2.8,2.8,0,0,1,22.3,12.6 L9.7,12.6 A2.8,2.8,0,0,1,6.9,9.8 L6.9,4.2 A2.8,2.8,0,0,1,9.7,1.4 Z', + square: 'M6.9 1.4l11.2 0l0 11.2l-11.2 0Z', + triangle: 'M12.5 1.4L18.1 12.5L12.5 12.5L6.9 12.5Z' +}; + +/** + * Returns marker -- see https://github.com/apache/echarts/issues/19826 + * + * @param serie + * @param symbol + * @param color + * @private Not intended as public API and subject to change + */ +export const getMarker = (serie: any, symbol: string, color: string = '') => { + const size = 18; + let path; + let pathStyle = `fill:${color};`; + const svgStyle = 'vertical-align: middle;'; + + let transform; + + // Set marker type + switch (symbol) { + case SymbolType.arrow: + case SymbolType.circle: + case SymbolType.diamond: + case SymbolType.emptyArrow: + case SymbolType.emptyCircle: + case SymbolType.emptyDiamond: + case SymbolType.emptyPin: + case SymbolType.emptyRect: + case SymbolType.emptyRoundRect: + case SymbolType.emptySquare: + case SymbolType.emptyTriangle: + case SymbolType.pin: + case SymbolType.rect: + case SymbolType.roundRect: + case SymbolType.square: + case SymbolType.triangle: + path = symbols[symbol]; + break; + default: + path = symbols.square; + break; + } + + // Set path style for EChart symbols + switch (symbol) { + case SymbolType.emptyArrow: + case SymbolType.emptyCircle: + case SymbolType.emptyDiamond: + case SymbolType.emptyPin: + case SymbolType.emptyRect: + case SymbolType.emptyRoundRect: + case SymbolType.emptySquare: + case SymbolType.emptyTriangle: + pathStyle = `fill:${getComputedStyleValue(chart_echarts_BackgroundColor)}; stroke:${color}; stroke-width:2`; + break; + default: + pathStyle = `fill:${color}; margin-left:-50px;`; + } + + // Set SVG style for EChart symbols + switch (symbol) { + case SymbolType.arrow: + case SymbolType.emptyArrow: + transform = `translate(-5 -5)`; + break; + case SymbolType.emptyDiamond: + transform = `translate(-5 5)`; + break; + case SymbolType.emptyRect: + case SymbolType.emptyRoundRect: + transform = `translate(-5 0)`; + break; + case SymbolType.pin: + transform = `translate(-5 5)`; + break; + case SymbolType.rect: + transform = `translate(-7 0)`; + break; + case SymbolType.roundRect: + transform = `translate(-7 0)`; + break; + default: + transform = `translate(-5 0)`; + break; + } + + const marker = ``; + const svg = `${marker}`; + + return svg; +}; + +/** + * Returns default symbol + * + * @param series + * @param seriesIndex + * @param echart + * @private Not intended as public API and subject to change + */ +export const getSymbol = (series: any, seriesIndex: number, echart: any) => { + const ase = echart?.getModel().findComponents({ mainType: 'series' }); + const data = ase[seriesIndex].getData(); + const symbol = data?.getVisual('symbol')?.replace(/"/g, ''); + + if (!symbol) { + const serie = series[seriesIndex]; + if (serie?.symbol) { + return serie?.symbol; + } + switch (serie?.type) { + case 'bar': + return SymbolType.rect; + case 'line': + return SymbolType.square; + default: + break; + } + } + return symbol; +}; diff --git a/packages/react-charts/src/echarts/components/utils/theme.ts b/packages/react-charts/src/echarts/components/utils/theme.ts new file mode 100644 index 00000000000..f4f9c996c6a --- /dev/null +++ b/packages/react-charts/src/echarts/components/utils/theme.ts @@ -0,0 +1,68 @@ +import cloneDeep from 'lodash/cloneDeep'; +import merge from 'lodash/merge'; +import { ThemeColor } from '../themes/ThemeColor'; +import { ThemeDefinition } from '../themes/Theme'; +import { BaseTheme } from '../themes/base-theme'; +import { getBlueColorTheme } from '../themes/colors/blue-theme'; +import { getTealColorTheme } from '../themes/colors/teal-theme'; +import { getYellowColorTheme } from '../themes/colors/yellow-theme'; +import { getGrayColorTheme } from '../themes/colors/gray-theme'; +import { getGreenColorTheme } from '../themes/colors/green-theme'; +import { getSkeletonColorTheme } from '../themes/colors/skeleton-theme'; +import { getMultiColorOrderedTheme } from '../themes/colors/multi-ordered-theme'; +import { getMultiColorUnorderedTheme } from '../themes/colors/multi-unordered-theme'; +import { getOrangeColorTheme } from '../themes/colors/orange-theme'; +import { getPurpleColorTheme } from '../themes/colors/purple-theme'; + +/** + * Apply custom properties to base and color themes + * + * @param themeColor The theme color to merge with custom theme + * @param customTheme The custom theme to merge + * @public + * @beta + */ +export const getCustomTheme = (themeColor: string, customTheme: ThemeDefinition): ThemeDefinition => + merge(getTheme(themeColor), customTheme); + +/** + * Returns base theme for given color + * @public + * @beta + */ +export const getTheme = (themeColor: string): ThemeDefinition => { + const baseTheme = cloneDeep(BaseTheme()); + return merge(baseTheme, getThemeColors(themeColor)); +}; + +/** + * Returns computed theme colors + * @private Not intended as public API and subject to change + */ +const getThemeColors = (themeColor: string) => { + switch (themeColor) { + case ThemeColor.blue: + return getBlueColorTheme(); + case ThemeColor.teal: + return getTealColorTheme(); + case ThemeColor.yellow: + return getYellowColorTheme(); + case ThemeColor.gray: + return getGrayColorTheme(); + case ThemeColor.green: + return getGreenColorTheme(); + case ThemeColor.multi: + case ThemeColor.multiOrdered: + return getMultiColorOrderedTheme(); + case ThemeColor.multiUnordered: + return getMultiColorUnorderedTheme(); + case ThemeColor.orange: + return getOrangeColorTheme(); + case ThemeColor.purple: + return getPurpleColorTheme(); + case ThemeColor.skeleton: + return getSkeletonColorTheme(); + default: + return getBlueColorTheme(); + } +}; diff --git a/packages/react-charts/src/echarts/components/utils/tooltip.tsx b/packages/react-charts/src/echarts/components/utils/tooltip.tsx new file mode 100644 index 00000000000..f15941b6c09 --- /dev/null +++ b/packages/react-charts/src/echarts/components/utils/tooltip.tsx @@ -0,0 +1,100 @@ +import defaultsDeep from 'lodash/defaultsDeep'; + +import { ChartsOptionProps } from '../Charts'; +import { getMarker, getSymbol } from './symbol'; + +/** + * Returns a custom legend tooltip for Line charts + * + * @param series + * @param option + * @param echart + * @private Not intended as public API and subject to change + */ +export const getLegendTooltip = (series: any[], option: ChartsOptionProps, echart: any) => { + const tooltip = option?.tooltip as any; + const valueFormatter = tooltip?.valueFormatter ? tooltip.valueFormatter : (value: number | string) => value; + + const defaults = { + confine: true, + formatter: (params: any) => { + let result = ''; + params?.map((param, index) => { + if (index === 0) { + result += `

${param.name}

`; + } + const symbol = getSymbol(series, param.seriesIndex, echart); + const marker = getMarker(series[param.seriesIndex], symbol, param.color); + const formattedValue = valueFormatter(param.value, param.dataIndex); + const seriesName = `${param.seriesName}`; + const value = `${formattedValue}`; + + result += `

${marker}${seriesName}${value}

`; + }); + return result; + }, + trigger: 'axis' + }; + return defaultsDeep(tooltip, defaults); +}; + +/** + * Returns source and target colors from given series + * + * @param series + * @param formatterParams + * @private Not intended as public API and subject to change + */ +const getItemColor = (series: any[], formatterParams: any) => { + const serie = series[formatterParams.seriesIndex]; + const sourceData = serie?.data.find((datum: any) => datum.name === formatterParams.data?.source); + const targetData = serie?.data.find((datum: any) => datum.name === formatterParams.data?.target); + const sourceColor = sourceData?.itemStyle?.color; + const targetColor = targetData?.itemStyle?.color; + return { sourceColor, targetColor }; +}; + +/** + * Returns a custom legend tooltip for Sankey chart + * + * @param series + * @param option + * @private Not intended as public API and subject to change + */ +export const getSankeyTooltip = (series: any[], option: ChartsOptionProps) => { + const symbolSize = '10px'; + const tooltip = option?.tooltip as any; + const sourceLabel = tooltip?.sourceLabel ? tooltip.sourceLabel : ''; + const destinationLabel = tooltip?.destinationLabel ? tooltip.destinationLabel : ''; + const valueFormatter = tooltip?.valueFormatter ? tooltip.valueFormatter : (value: number | string) => value; + + const defaults = { + confine: true, + formatter: (params: any) => { + const result = ` +
+ ${params.name} ${params.value} + `; + if (params?.data?.source && params?.data?.target) { + const { sourceColor, targetColor } = getItemColor(series, params); + return ` +

${sourceLabel}

+
+ ${params.data.source} +

${destinationLabel}

+

+

+ ${params.data.target} + + ${valueFormatter(params.value, params.dataIndex)} + +

+ `; + } + return result.replace(/\s\s+/g, ' '); + }, + trigger: 'item', + triggerOn: 'mousemove' + }; + return defaultsDeep(tooltip, defaults); +}; diff --git a/packages/react-charts/src/echarts/index.ts b/packages/react-charts/src/echarts/index.ts new file mode 100644 index 00000000000..07635cbbc8e --- /dev/null +++ b/packages/react-charts/src/echarts/index.ts @@ -0,0 +1 @@ +export * from './components'; diff --git a/packages/react-charts/src/echarts/typings/echarts.d.ts b/packages/react-charts/src/echarts/typings/echarts.d.ts new file mode 100644 index 00000000000..497834cab88 --- /dev/null +++ b/packages/react-charts/src/echarts/typings/echarts.d.ts @@ -0,0 +1,7 @@ +import * as echarts from 'echarts/types/dist/core'; + +declare module 'echarts' { + export const registerTheme: echarts.registerTheme; + export const init: echarts.init; + export type ECharts = any; +} diff --git a/packages/react-charts/src/victory/components/Chart/__snapshots__/Chart.test.tsx.snap b/packages/react-charts/src/victory/components/Chart/__snapshots__/Chart.test.tsx.snap index 399b97d88d5..c8a9e294587 100644 --- a/packages/react-charts/src/victory/components/Chart/__snapshots__/Chart.test.tsx.snap +++ b/packages/react-charts/src/victory/components/Chart/__snapshots__/Chart.test.tsx.snap @@ -49,7 +49,7 @@ exports[`Chart 1`] = ` @@ -80,7 +80,7 @@ exports[`Chart 1`] = ` @@ -111,7 +111,7 @@ exports[`Chart 1`] = ` @@ -142,7 +142,7 @@ exports[`Chart 1`] = ` @@ -173,7 +173,7 @@ exports[`Chart 1`] = ` @@ -218,7 +218,7 @@ exports[`Chart 1`] = ` @@ -249,7 +249,7 @@ exports[`Chart 1`] = ` @@ -280,7 +280,7 @@ exports[`Chart 1`] = ` @@ -311,7 +311,7 @@ exports[`Chart 1`] = ` @@ -342,7 +342,7 @@ exports[`Chart 1`] = ` @@ -415,7 +415,7 @@ exports[`Chart 2`] = ` @@ -446,7 +446,7 @@ exports[`Chart 2`] = ` @@ -477,7 +477,7 @@ exports[`Chart 2`] = ` @@ -508,7 +508,7 @@ exports[`Chart 2`] = ` @@ -539,7 +539,7 @@ exports[`Chart 2`] = ` @@ -584,7 +584,7 @@ exports[`Chart 2`] = ` @@ -615,7 +615,7 @@ exports[`Chart 2`] = ` @@ -646,7 +646,7 @@ exports[`Chart 2`] = ` @@ -677,7 +677,7 @@ exports[`Chart 2`] = ` @@ -708,7 +708,7 @@ exports[`Chart 2`] = ` @@ -879,7 +879,7 @@ exports[`renders axis and component children 1`] = ` @@ -910,7 +910,7 @@ exports[`renders axis and component children 1`] = ` @@ -941,7 +941,7 @@ exports[`renders axis and component children 1`] = ` @@ -972,7 +972,7 @@ exports[`renders axis and component children 1`] = ` @@ -1003,7 +1003,7 @@ exports[`renders axis and component children 1`] = ` @@ -1034,7 +1034,7 @@ exports[`renders axis and component children 1`] = ` @@ -1065,7 +1065,7 @@ exports[`renders axis and component children 1`] = ` @@ -1110,7 +1110,7 @@ exports[`renders axis and component children 1`] = ` @@ -1141,7 +1141,7 @@ exports[`renders axis and component children 1`] = ` @@ -1172,7 +1172,7 @@ exports[`renders axis and component children 1`] = ` @@ -1203,7 +1203,7 @@ exports[`renders axis and component children 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartAxis/__snapshots__/ChartAxis.test.tsx.snap b/packages/react-charts/src/victory/components/ChartAxis/__snapshots__/ChartAxis.test.tsx.snap index 1b9c5e4545a..d46c34dd079 100644 --- a/packages/react-charts/src/victory/components/ChartAxis/__snapshots__/ChartAxis.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartAxis/__snapshots__/ChartAxis.test.tsx.snap @@ -46,7 +46,7 @@ exports[`ChartAxis 1`] = ` @@ -77,7 +77,7 @@ exports[`ChartAxis 1`] = ` @@ -108,7 +108,7 @@ exports[`ChartAxis 1`] = ` @@ -139,7 +139,7 @@ exports[`ChartAxis 1`] = ` @@ -170,7 +170,7 @@ exports[`ChartAxis 1`] = ` @@ -201,7 +201,7 @@ exports[`ChartAxis 1`] = ` @@ -270,7 +270,7 @@ exports[`ChartAxis 2`] = ` @@ -301,7 +301,7 @@ exports[`ChartAxis 2`] = ` @@ -332,7 +332,7 @@ exports[`ChartAxis 2`] = ` @@ -363,7 +363,7 @@ exports[`ChartAxis 2`] = ` @@ -394,7 +394,7 @@ exports[`ChartAxis 2`] = ` @@ -425,7 +425,7 @@ exports[`ChartAxis 2`] = ` @@ -595,7 +595,7 @@ exports[`renders component data 1`] = ` @@ -626,7 +626,7 @@ exports[`renders component data 1`] = ` @@ -657,7 +657,7 @@ exports[`renders component data 1`] = ` @@ -702,7 +702,7 @@ exports[`renders component data 1`] = ` @@ -733,7 +733,7 @@ exports[`renders component data 1`] = ` @@ -764,7 +764,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartBar/__snapshots__/ChartBar.test.tsx.snap b/packages/react-charts/src/victory/components/ChartBar/__snapshots__/ChartBar.test.tsx.snap index 2be439c1caa..ccd666c4a78 100644 --- a/packages/react-charts/src/victory/components/ChartBar/__snapshots__/ChartBar.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartBar/__snapshots__/ChartBar.test.tsx.snap @@ -491,7 +491,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -522,7 +522,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -553,7 +553,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -584,7 +584,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -629,7 +629,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -660,7 +660,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -691,7 +691,7 @@ A 0 0 0 0 1, 246.5, 150 @@ -722,7 +722,7 @@ A 0 0 0 0 1, 246.5, 150 diff --git a/packages/react-charts/src/victory/components/ChartBoxPlot/__snapshots__/ChartBoxPlot.test.tsx.snap b/packages/react-charts/src/victory/components/ChartBoxPlot/__snapshots__/ChartBoxPlot.test.tsx.snap index c35a6de7347..803a1f557da 100644 --- a/packages/react-charts/src/victory/components/ChartBoxPlot/__snapshots__/ChartBoxPlot.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartBoxPlot/__snapshots__/ChartBoxPlot.test.tsx.snap @@ -709,7 +709,7 @@ exports[`renders component data 1`] = ` @@ -740,7 +740,7 @@ exports[`renders component data 1`] = ` @@ -771,7 +771,7 @@ exports[`renders component data 1`] = ` @@ -802,7 +802,7 @@ exports[`renders component data 1`] = ` @@ -847,7 +847,7 @@ exports[`renders component data 1`] = ` @@ -878,7 +878,7 @@ exports[`renders component data 1`] = ` @@ -909,7 +909,7 @@ exports[`renders component data 1`] = ` @@ -940,7 +940,7 @@ exports[`renders component data 1`] = ` @@ -971,7 +971,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBullet.test.tsx.snap b/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBullet.test.tsx.snap index 9bd4b9b3e52..53345c07ea7 100644 --- a/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBullet.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBullet.test.tsx.snap @@ -49,7 +49,7 @@ exports[`ChartBulletQualitativeRange 1`] = ` @@ -134,7 +134,7 @@ exports[`ChartBulletQualitativeRange 2`] = ` @@ -231,7 +231,7 @@ exports[`renders component data 1`] = ` @@ -262,7 +262,7 @@ exports[`renders component data 1`] = ` @@ -293,7 +293,7 @@ exports[`renders component data 1`] = ` @@ -324,7 +324,7 @@ exports[`renders component data 1`] = ` @@ -355,7 +355,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletGroupTitle.test.tsx.snap b/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletGroupTitle.test.tsx.snap index 01c4dc97c33..945620d4575 100644 --- a/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletGroupTitle.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletGroupTitle.test.tsx.snap @@ -77,7 +77,7 @@ exports[`renders component data 1`] = ` @@ -86,7 +86,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletTitle.test.tsx.snap b/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletTitle.test.tsx.snap index c4decf7d44e..0775de19682 100644 --- a/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletTitle.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartBullet/__snapshots__/ChartBulletTitle.test.tsx.snap @@ -77,7 +77,7 @@ exports[`renders component data 1`] = ` @@ -86,7 +86,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartContainer/__snapshots__/ChartContainer.test.tsx.snap b/packages/react-charts/src/victory/components/ChartContainer/__snapshots__/ChartContainer.test.tsx.snap index aa3d2e741aa..dc0e41034f1 100644 --- a/packages/react-charts/src/victory/components/ChartContainer/__snapshots__/ChartContainer.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartContainer/__snapshots__/ChartContainer.test.tsx.snap @@ -98,7 +98,7 @@ exports[`renders container via ChartLegend 1`] = ` @@ -115,7 +115,7 @@ exports[`renders container via ChartLegend 1`] = ` @@ -132,7 +132,7 @@ exports[`renders container via ChartLegend 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartLabel/__snapshots__/ChartLabel.test.tsx.snap b/packages/react-charts/src/victory/components/ChartLabel/__snapshots__/ChartLabel.test.tsx.snap index 0e7841ee941..3703c9eebb0 100644 --- a/packages/react-charts/src/victory/components/ChartLabel/__snapshots__/ChartLabel.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartLabel/__snapshots__/ChartLabel.test.tsx.snap @@ -16,7 +16,7 @@ exports[`renders component text 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartLegend/__snapshots__/ChartLegend.test.tsx.snap b/packages/react-charts/src/victory/components/ChartLegend/__snapshots__/ChartLegend.test.tsx.snap index b63520fdaf6..d75ef211769 100644 --- a/packages/react-charts/src/victory/components/ChartLegend/__snapshots__/ChartLegend.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartLegend/__snapshots__/ChartLegend.test.tsx.snap @@ -53,7 +53,7 @@ exports[`ChartLegend 1`] = ` @@ -70,7 +70,7 @@ exports[`ChartLegend 1`] = ` @@ -145,7 +145,7 @@ exports[`ChartLegend 2`] = ` @@ -162,7 +162,7 @@ exports[`ChartLegend 2`] = ` @@ -237,7 +237,7 @@ exports[`renders component data 1`] = ` @@ -254,7 +254,7 @@ exports[`renders component data 1`] = ` @@ -271,7 +271,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltip.test.tsx.snap b/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltip.test.tsx.snap index 1a7a1fe9973..485cb16e8d0 100644 --- a/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltip.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltip.test.tsx.snap @@ -59,7 +59,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -90,7 +90,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -121,7 +121,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -152,7 +152,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -207,7 +207,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -248,7 +248,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -289,7 +289,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -467,7 +467,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -484,7 +484,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -501,7 +501,7 @@ exports[`allows tooltip via container component 1`] = ` @@ -518,7 +518,7 @@ exports[`allows tooltip via container component 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltipLabel.test.tsx.snap b/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltipLabel.test.tsx.snap index d74aac8b820..b6832ff72af 100644 --- a/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltipLabel.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartLegendTooltip/__snapshots__/ChartLegendTooltipLabel.test.tsx.snap @@ -16,7 +16,7 @@ exports[`renders component text 1`] = ` @@ -33,7 +33,7 @@ exports[`renders component text 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartLine/__snapshots__/ChartLine.test.tsx.snap b/packages/react-charts/src/victory/components/ChartLine/__snapshots__/ChartLine.test.tsx.snap index e85594e6893..761b0a05ddd 100644 --- a/packages/react-charts/src/victory/components/ChartLine/__snapshots__/ChartLine.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartLine/__snapshots__/ChartLine.test.tsx.snap @@ -251,7 +251,7 @@ exports[`renders component data 1`] = ` @@ -282,7 +282,7 @@ exports[`renders component data 1`] = ` @@ -313,7 +313,7 @@ exports[`renders component data 1`] = ` @@ -344,7 +344,7 @@ exports[`renders component data 1`] = ` @@ -375,7 +375,7 @@ exports[`renders component data 1`] = ` @@ -406,7 +406,7 @@ exports[`renders component data 1`] = ` @@ -437,7 +437,7 @@ exports[`renders component data 1`] = ` @@ -482,7 +482,7 @@ exports[`renders component data 1`] = ` @@ -513,7 +513,7 @@ exports[`renders component data 1`] = ` @@ -544,7 +544,7 @@ exports[`renders component data 1`] = ` @@ -575,7 +575,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartPoint/__snapshots__/ChartPoint.test.tsx.snap b/packages/react-charts/src/victory/components/ChartPoint/__snapshots__/ChartPoint.test.tsx.snap index 11bf469294c..8c3301896f9 100644 --- a/packages/react-charts/src/victory/components/ChartPoint/__snapshots__/ChartPoint.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartPoint/__snapshots__/ChartPoint.test.tsx.snap @@ -53,7 +53,7 @@ exports[`ChartPoint 1`] = ` @@ -70,7 +70,7 @@ exports[`ChartPoint 1`] = ` @@ -145,7 +145,7 @@ exports[`ChartPoint 2`] = ` @@ -162,7 +162,7 @@ exports[`ChartPoint 2`] = ` @@ -247,7 +247,7 @@ exports[`renders component data 1`] = ` @@ -264,7 +264,7 @@ exports[`renders component data 1`] = ` @@ -281,7 +281,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartScatter/__snapshots__/ChartScatter.test.tsx.snap b/packages/react-charts/src/victory/components/ChartScatter/__snapshots__/ChartScatter.test.tsx.snap index 97c857766e8..79737756f72 100644 --- a/packages/react-charts/src/victory/components/ChartScatter/__snapshots__/ChartScatter.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartScatter/__snapshots__/ChartScatter.test.tsx.snap @@ -1181,7 +1181,7 @@ exports[`renders component data 1`] = ` @@ -1212,7 +1212,7 @@ exports[`renders component data 1`] = ` @@ -1243,7 +1243,7 @@ exports[`renders component data 1`] = ` @@ -1274,7 +1274,7 @@ exports[`renders component data 1`] = ` @@ -1305,7 +1305,7 @@ exports[`renders component data 1`] = ` @@ -1336,7 +1336,7 @@ exports[`renders component data 1`] = ` @@ -1367,7 +1367,7 @@ exports[`renders component data 1`] = ` @@ -1412,7 +1412,7 @@ exports[`renders component data 1`] = ` @@ -1443,7 +1443,7 @@ exports[`renders component data 1`] = ` @@ -1474,7 +1474,7 @@ exports[`renders component data 1`] = ` @@ -1505,7 +1505,7 @@ exports[`renders component data 1`] = ` @@ -1536,7 +1536,7 @@ exports[`renders component data 1`] = ` @@ -1567,7 +1567,7 @@ exports[`renders component data 1`] = ` @@ -1598,7 +1598,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/src/victory/components/ChartStack/__snapshots__/ChartStack.test.tsx.snap b/packages/react-charts/src/victory/components/ChartStack/__snapshots__/ChartStack.test.tsx.snap index e78ea341416..2344203306d 100644 --- a/packages/react-charts/src/victory/components/ChartStack/__snapshots__/ChartStack.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartStack/__snapshots__/ChartStack.test.tsx.snap @@ -361,7 +361,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -392,7 +392,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -423,7 +423,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -454,7 +454,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -499,7 +499,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -530,7 +530,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -561,7 +561,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -592,7 +592,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 @@ -623,7 +623,7 @@ A 0 0 0 0 1, 229.99999999999997, 150 diff --git a/packages/react-charts/src/victory/components/ChartTheme/styles/common-styles.ts b/packages/react-charts/src/victory/components/ChartTheme/styles/common-styles.ts index 7a49d4b763c..5ce740f3262 100644 --- a/packages/react-charts/src/victory/components/ChartTheme/styles/common-styles.ts +++ b/packages/react-charts/src/victory/components/ChartTheme/styles/common-styles.ts @@ -8,7 +8,7 @@ import chart_legend_Margin from '@patternfly/react-tokens/dist/esm/chart_legend_ import chart_legend_position from '@patternfly/react-tokens/dist/esm/chart_legend_position'; // Typography -const TYPOGRAPHY_FONT_FAMILY = chart_global_FontFamily.var; +const TYPOGRAPHY_FONT_FAMILY = chart_global_FontFamily.var.replace(/"/g, "'"); // Well-formed XML const TYPOGRAPHY_LETTER_SPACING = chart_global_letter_spacing.var; const TYPOGRAPHY_FONT_SIZE = chart_global_FontSize_sm.value; diff --git a/packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts b/packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts index 0190f902453..dd537ec0fb2 100644 --- a/packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts +++ b/packages/react-charts/src/victory/components/ChartTheme/themes/base-theme.ts @@ -126,7 +126,7 @@ import { ChartComponentThemeDefinition, ChartThemeDefinition } from '../ChartThe // // Note: Victory's approximateTextSize function uses specific character widths and does not work with font variables // See https://github.com/patternfly/patternfly-react/issues/5300 and https://github.com/patternfly/patternfly-react/pull/5301 -const TYPOGRAPHY_FONT_FAMILY = chart_global_FontFamily.value; +const TYPOGRAPHY_FONT_FAMILY = chart_global_FontFamily.var.replace(/"/g, "'"); // Well-formed XML const TYPOGRAPHY_LETTER_SPACING = chart_global_letter_spacing.value; const TYPOGRAPHY_FONT_SIZE = chart_global_FontSize_sm.value; diff --git a/packages/react-charts/src/victory/components/ChartThreshold/__snapshots__/ChartThreshold.test.tsx.snap b/packages/react-charts/src/victory/components/ChartThreshold/__snapshots__/ChartThreshold.test.tsx.snap index 9d4b72ac769..2959fcc6d2b 100644 --- a/packages/react-charts/src/victory/components/ChartThreshold/__snapshots__/ChartThreshold.test.tsx.snap +++ b/packages/react-charts/src/victory/components/ChartThreshold/__snapshots__/ChartThreshold.test.tsx.snap @@ -179,7 +179,7 @@ exports[`renders component data 1`] = ` @@ -210,7 +210,7 @@ exports[`renders component data 1`] = ` @@ -241,7 +241,7 @@ exports[`renders component data 1`] = ` @@ -272,7 +272,7 @@ exports[`renders component data 1`] = ` @@ -303,7 +303,7 @@ exports[`renders component data 1`] = ` @@ -348,7 +348,7 @@ exports[`renders component data 1`] = ` @@ -379,7 +379,7 @@ exports[`renders component data 1`] = ` @@ -410,7 +410,7 @@ exports[`renders component data 1`] = ` @@ -441,7 +441,7 @@ exports[`renders component data 1`] = ` @@ -472,7 +472,7 @@ exports[`renders component data 1`] = ` @@ -503,7 +503,7 @@ exports[`renders component data 1`] = ` diff --git a/packages/react-charts/subpaths.config.json b/packages/react-charts/subpaths.config.json index 5c5e5d1baff..706c0581100 100644 --- a/packages/react-charts/subpaths.config.json +++ b/packages/react-charts/subpaths.config.json @@ -1,4 +1,4 @@ { "packageName": "@patternfly/react-charts", - "paths": ["victory"] + "paths": ["echarts", "victory"] } diff --git a/packages/react-charts/tsconfig.json b/packages/react-charts/tsconfig.json index 2b83373e153..b163d5d03bf 100644 --- a/packages/react-charts/tsconfig.json +++ b/packages/react-charts/tsconfig.json @@ -5,6 +5,10 @@ "outDir": "./dist/esm", "tsBuildInfoFile": "dist/esm.tsbuildinfo", "noImplicitAny": false, + "baseUrl": ".", + "paths": { + "./next": ["./src/echarts/next"] + } }, "include": [ "./src/*", diff --git a/packages/react-docs/package.json b/packages/react-docs/package.json index e7c2180edb2..200e28c3afe 100644 --- a/packages/react-docs/package.json +++ b/packages/react-docs/package.json @@ -33,6 +33,7 @@ "@patternfly/react-table": "workspace:^", "@patternfly/react-templates": "workspace:^", "@patternfly/react-tokens": "workspace:^", + "echarts": "^5.6.0", "victory": "^37.3.6" }, "devDependencies": { diff --git a/packages/react-docs/patternfly-a11y.config.js b/packages/react-docs/patternfly-a11y.config.js index 99b89a7cd10..dbe597c577d 100644 --- a/packages/react-docs/patternfly-a11y.config.js +++ b/packages/react-docs/patternfly-a11y.config.js @@ -38,6 +38,12 @@ const urls = Object.keys(fullscreenRoutes) if (path.includes('/tile/') || path.includes('/chip/')) { return path.replace(/\/react-deprecated$/, ''); } + } else if (path.match(/\/charts\/.*\/ECharts$/g)) { + if (path.includes('/sankey-chart/')) { + return path.replace(/\/ECharts$/, ''); + } + } else if (path.match(/\/charts\/.*\/-Victory$/g)) { + return path.replace(/\/-Victory/, ''); } // all other pages in the components section have a trailing 'react' string in their default tab return path.replace(/\/react$/, ''); diff --git a/packages/react-docs/patternfly-docs/patternfly-docs.source.js b/packages/react-docs/patternfly-docs/patternfly-docs.source.js index 4d5914b5a5f..63c2d811165 100644 --- a/packages/react-docs/patternfly-docs/patternfly-docs.source.js +++ b/packages/react-docs/patternfly-docs/patternfly-docs.source.js @@ -40,10 +40,11 @@ module.exports = (baseSourceMD, sourceProps) => { sourceMD(path.join(reactTablePath, '/**/demos/*.md'), 'react-demos'); // Charts MD - sourceMD(path.join(reactChartsPath, '/echarts/components/**/examples/*.md'), 'react'); - sourceMD(path.join(reactChartsPath, '/next/echarts/components/**/examples/*.md'), 'react-next'); - sourceMD(path.join(reactChartsPath, '/next/victory/components/**/examples/*.md'), 'react-next'); - sourceMD(path.join(reactChartsPath, '/victory/components/**/examples/*.md'), 'react'); + sourceMD(path.join(reactChartsPath, '/echarts/components/**/docs/*.md'), 'ECharts-docs'); // Alternate docs for examples + sourceMD(path.join(reactChartsPath, '/echarts/components/**/examples/*.md'), 'ECharts'); + sourceMD(path.join(reactChartsPath, '/echarts/next/components/**/examples/*.md'), 'ECharts-next'); + sourceMD(path.join(reactChartsPath, '/victory/components/**/examples/*.md'), '-Victory'); // Default tab + sourceMD(path.join(reactChartsPath, '/victory/next/components/**/examples/*.md'), '-Victory-next'); // Code Editor MD sourceMD(path.join(reactCodeEditorPath, '/**/examples/*.md'), 'react'); diff --git a/packages/testSetup.ts b/packages/testSetup.ts index 52b79e9723a..0c5fc63a84e 100644 --- a/packages/testSetup.ts +++ b/packages/testSetup.ts @@ -1,2 +1,3 @@ // Add custom jest matchers from jest-dom import '@testing-library/jest-dom'; +import 'jest-canvas-mock'; diff --git a/yarn.lock b/yarn.lock index 7ab65303ad8..e65852e5151 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3648,9 +3648,11 @@ __metadata: "@types/lodash": "npm:^4.17.16" fs-extra: "npm:^11.3.0" hoist-non-react-statics: "npm:^3.3.2" + jest-canvas-mock: "npm:^2.5.2" lodash: "npm:^4.17.21" tslib: "npm:^2.8.1" peerDependencies: + echarts: ^5.6.0 react: ^17 || ^18 react-dom: ^17 || ^18 victory-area: ^37.3.6 @@ -3671,6 +3673,8 @@ __metadata: victory-voronoi-container: ^37.3.6 victory-zoom-container: ^37.3.6 peerDependenciesMeta: + echarts: + optional: true victory-area: optional: true victory-axis: @@ -3761,6 +3765,7 @@ __metadata: "@patternfly/react-table": "workspace:^" "@patternfly/react-templates": "workspace:^" "@patternfly/react-tokens": "workspace:^" + echarts: "npm:^5.6.0" victory: "npm:^37.3.6" languageName: unknown linkType: soft @@ -7823,7 +7828,7 @@ __metadata: languageName: node linkType: hard -"color-name@npm:^1.0.0, color-name@npm:~1.1.4": +"color-name@npm:^1.0.0, color-name@npm:^1.1.4, color-name@npm:~1.1.4": version: 1.1.4 resolution: "color-name@npm:1.1.4" checksum: 10c0/a1a3f914156960902f46f7f56bc62effc6c94e84b2cae157a526b1c1f74b677a47ec602bf68a61abfa2b42d15b7c5651c6dbe72a43af720bc588dff885b10f95 @@ -8488,6 +8493,13 @@ __metadata: languageName: node linkType: hard +"cssfontparser@npm:^1.2.1": + version: 1.2.1 + resolution: "cssfontparser@npm:1.2.1" + checksum: 10c0/ceb9b2976d503dbff3ac2aff0229b263affb4fb221a6947b357682cd8a952f6995253646ca5f820020d2fe05b5e29b56dbdd2343388c32203e8dd0ed15bdc1ca + languageName: node + linkType: hard + "cssom@npm:^0.5.0": version: 0.5.0 resolution: "cssom@npm:0.5.0" @@ -9422,6 +9434,16 @@ __metadata: languageName: node linkType: hard +"echarts@npm:^5.6.0": + version: 5.6.0 + resolution: "echarts@npm:5.6.0" + dependencies: + tslib: "npm:2.3.0" + zrender: "npm:5.6.1" + checksum: 10c0/6d6a2ee88534d1ff0433e935c542237b9896de1c94959f47ebc7e0e9da26f59bf11c91ed6fc135b62ad2786c779ee12bc536fa481e60532dad5b6a2f5167e9ea + languageName: node + linkType: hard + "editions@npm:^2.2.0": version: 2.3.1 resolution: "editions@npm:2.3.1" @@ -13865,6 +13887,16 @@ __metadata: languageName: node linkType: hard +"jest-canvas-mock@npm:^2.5.2": + version: 2.5.2 + resolution: "jest-canvas-mock@npm:2.5.2" + dependencies: + cssfontparser: "npm:^1.2.1" + moo-color: "npm:^1.0.2" + checksum: 10c0/6a4190354b1e9aedcb3045273f13f6f1d2d1efb00cfe6458707fae538a8f91f6afdf72b9e201b653666863054edc783428bdc0c1a2c71d66d9ac364b4893f6d6 + languageName: node + linkType: hard + "jest-changed-files@npm:^29.7.0": version: 29.7.0 resolution: "jest-changed-files@npm:29.7.0" @@ -15943,6 +15975,15 @@ __metadata: languageName: node linkType: hard +"moo-color@npm:^1.0.2": + version: 1.0.3 + resolution: "moo-color@npm:1.0.3" + dependencies: + color-name: "npm:^1.1.4" + checksum: 10c0/778c82f67f638c03a1d0fa78dcd6ea376a9f17b5e78e349c7e34a290b496dbdb43fd0b1c38070e2062d5e784bcf08e57f499015fcbcf52b3a1887d7825ebb80d + languageName: node + linkType: hard + "move-concurrently@npm:^1.0.1": version: 1.0.1 resolution: "move-concurrently@npm:1.0.1" @@ -21365,6 +21406,13 @@ __metadata: languageName: node linkType: hard +"tslib@npm:2.3.0": + version: 2.3.0 + resolution: "tslib@npm:2.3.0" + checksum: 10c0/a845aed84e7e7dbb4c774582da60d7030ea39d67307250442d35c4c5dd77e4b44007098c37dd079e100029c76055f2a362734b8442ba828f8cc934f15ed9be61 + languageName: node + linkType: hard + "tslib@npm:^1.9.0": version: 1.14.1 resolution: "tslib@npm:1.14.1" @@ -23597,3 +23645,12 @@ __metadata: checksum: 10c0/8f14c87d6b1b53c944c25ce7a28616896319d95bc46a9660fe441adc0ed0a81253b02b5abdaeffedbeb23bdd25a0bf1c29d2c12dd919aef6447652dd295e3e69 languageName: node linkType: hard + +"zrender@npm:5.6.1": + version: 5.6.1 + resolution: "zrender@npm:5.6.1" + dependencies: + tslib: "npm:2.3.0" + checksum: 10c0/dc1cc570054640cbd8fbb7b92e6252f225319522bfe3e8dc8bf02cc02d414e00a4c8d0a6f89bfc9d96e5e9511fdca94dd3d06bf53690df2b2f12b0fc560ac307 + languageName: node + linkType: hard