From 0b3265e33a90ab08e51cb88f69b018f39b8b9cd5 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Wed, 20 Apr 2022 18:01:55 +0200 Subject: [PATCH 01/30] Basic chart template --- packages/charts/src/chart_types/index.ts | 1 + packages/charts/src/chart_types/specs.ts | 2 + .../template/renderer/canvas/viz.tsx | 167 ++++++++++++++++++ .../src/chart_types/template/specs/index.ts | 53 ++++++ .../template/state/chart_state.tsx | 121 +++++++++++++ .../template/state/selectors/chart_size.ts | 21 +++ .../template/state/selectors/data.ts | 27 +++ packages/charts/src/state/chart_state.ts | 2 + .../stories/new_chart_type/1_basic.story.tsx | 21 +++ .../new_chart_type/new_chart_type.stories.tsx | 13 ++ 10 files changed, 428 insertions(+) create mode 100644 packages/charts/src/chart_types/template/renderer/canvas/viz.tsx create mode 100644 packages/charts/src/chart_types/template/specs/index.ts create mode 100644 packages/charts/src/chart_types/template/state/chart_state.tsx create mode 100644 packages/charts/src/chart_types/template/state/selectors/chart_size.ts create mode 100644 packages/charts/src/chart_types/template/state/selectors/data.ts create mode 100644 storybook/stories/new_chart_type/1_basic.story.tsx create mode 100644 storybook/stories/new_chart_type/new_chart_type.stories.tsx diff --git a/packages/charts/src/chart_types/index.ts b/packages/charts/src/chart_types/index.ts index 85dd457ec3..81dd656573 100644 --- a/packages/charts/src/chart_types/index.ts +++ b/packages/charts/src/chart_types/index.ts @@ -19,6 +19,7 @@ export const ChartType = Object.freeze({ XYAxis: 'xy_axis' as const, Heatmap: 'heatmap' as const, Wordcloud: 'wordcloud' as const, + NewViz: 'newViz' as const, }); /** @public */ export type ChartType = $Values; diff --git a/packages/charts/src/chart_types/specs.ts b/packages/charts/src/chart_types/specs.ts index ff029ddb4b..29a8fb6b3a 100644 --- a/packages/charts/src/chart_types/specs.ts +++ b/packages/charts/src/chart_types/specs.ts @@ -30,3 +30,5 @@ export * from './xy_chart/utils/specs'; export { Partition } from './partition_chart/specs'; export { Heatmap, HeatmapSpec, RasterTimeScale, TimeScale, LinearScale, OrdinalScale } from './heatmap/specs'; + +export { NewVizSpec, NewViz } from './template/specs'; diff --git a/packages/charts/src/chart_types/template/renderer/canvas/viz.tsx b/packages/charts/src/chart_types/template/renderer/canvas/viz.tsx new file mode 100644 index 0000000000..5e1644d41d --- /dev/null +++ b/packages/charts/src/chart_types/template/renderer/canvas/viz.tsx @@ -0,0 +1,167 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { RefObject } from 'react'; +import { connect } from 'react-redux'; +import { bindActionCreators, Dispatch } from 'redux'; + +import { ScreenReaderSummary } from '../../../../components/accessibility'; +import { clearCanvas, withContext } from '../../../../renderers/canvas'; +import { onChartRendered } from '../../../../state/actions/chart'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { + A11ySettings, + DEFAULT_A11Y_SETTINGS, + getA11ySettingsSelector, +} from '../../../../state/selectors/get_accessibility_config'; +import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized'; +import { chartSize } from '../../state/selectors/chart_size'; +import { data } from '../../state/selectors/data'; + +interface ReactiveChartStateProps { + initialized: boolean; + size: { + width: number; + height: number; + }; + data: number[]; + a11y: A11ySettings; +} + +interface ReactiveChartDispatchProps { + onChartRendered: typeof onChartRendered; +} + +interface ReactiveChartOwnProps { + forwardStageRef: RefObject; +} + +type Props = ReactiveChartStateProps & ReactiveChartDispatchProps & ReactiveChartOwnProps; +class Component extends React.Component { + static displayName = 'TODO_SPECIFY_YOUR_CHART_NAME'; + + private ctx: CanvasRenderingContext2D | null; + private readonly devicePixelRatio: number; + + constructor(props: Readonly) { + super(props); + this.ctx = null; + this.devicePixelRatio = window.devicePixelRatio; + } + + componentDidMount() { + this.tryCanvasContext(); + if (this.props.initialized) { + this.drawCanvas(); + this.props.onChartRendered(); + } + } + + componentDidUpdate() { + if (!this.ctx) { + this.tryCanvasContext(); + } + if (this.props.initialized) { + this.drawCanvas(); + this.props.onChartRendered(); + } + } + + private tryCanvasContext() { + const canvas = this.props.forwardStageRef.current; + this.ctx = canvas && canvas.getContext('2d'); + } + + private drawCanvas() { + if (this.ctx) { + // TODO render your chart here + const { + data, + size: { width, height }, + } = this.props; + clearCanvas(this.ctx, 'white'); + withContext(this.ctx, (ctx) => { + ctx.scale(this.devicePixelRatio, this.devicePixelRatio); + ctx.textBaseline = 'middle'; + ctx.textAlign = 'center'; + data.forEach((d, i) => { + const step = width / data.length; + const h = height * d; + const x = step * i; + const y = height - h; + ctx.fillStyle = 'black'; + ctx.fillRect(x, y, step, h); + ctx.fillStyle = 'white'; + ctx.fillText(`${d * 100}%`, x + step / 2, y + h / 2); + }); + }); + } + } + + render() { + const { + initialized, + size: { width, height }, + a11y, + forwardStageRef, + } = this.props; + if (!initialized || width === 0 || height === 0) { + return null; + } + return ( +
+ + + +
+ ); + } +} + +const mapDispatchToProps = (dispatch: Dispatch): ReactiveChartDispatchProps => + bindActionCreators( + { + onChartRendered, + }, + dispatch, + ); + +const DEFAULT_PROPS: ReactiveChartStateProps = { + initialized: false, + data: [], + size: { + width: 0, + height: 0, + }, + a11y: DEFAULT_A11Y_SETTINGS, +}; +const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => { + if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) { + return DEFAULT_PROPS; + } + return { + initialized: true, + data: data(state), + size: chartSize(state), + a11y: getA11ySettingsSelector(state), + }; +}; + +/** @internal */ +export const Viz = connect(mapStateToProps, mapDispatchToProps)(Component); diff --git a/packages/charts/src/chart_types/template/specs/index.ts b/packages/charts/src/chart_types/template/specs/index.ts new file mode 100644 index 0000000000..05bcef186a --- /dev/null +++ b/packages/charts/src/chart_types/template/specs/index.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ComponentProps } from 'react'; + +import { ChartType } from '../..'; +import { Spec, SpecType } from '../../../specs'; +import { buildSFProps, SFProps, useSpecFactory } from '../../../state/spec_factory'; +import { stripUndefined } from '../../../utils/common'; + +/** @internal */ +export interface NewVizSpec extends Spec { + specType: typeof SpecType.Series; + // TODO add a new ChartType to ../../../index.ts + chartType: typeof ChartType.NewViz; + data: number; +} + +const buildProps = buildSFProps()( + { + chartType: ChartType.NewViz, + specType: SpecType.Series, + }, + { + data: 100, + }, +); + +/** + * Adds bar series to chart specs + * @public + */ +export const NewViz = function ( + props: SFProps< + NewVizSpec, + keyof typeof buildProps['overrides'], + keyof typeof buildProps['defaults'], + keyof typeof buildProps['optionals'], + keyof typeof buildProps['requires'] + >, +) { + const { defaults, overrides } = buildProps; + useSpecFactory({ ...defaults, ...stripUndefined(props), ...overrides }); + return null; +}; + +/** @public */ +export type NewVizSpecProps = ComponentProps; diff --git a/packages/charts/src/chart_types/template/state/chart_state.tsx b/packages/charts/src/chart_types/template/state/chart_state.tsx new file mode 100644 index 0000000000..daedc18821 --- /dev/null +++ b/packages/charts/src/chart_types/template/state/chart_state.tsx @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React, { RefObject } from 'react'; + +import { ChartType } from '../..'; +import { DEFAULT_CSS_CURSOR } from '../../../common/constants'; +import { LegendItem } from '../../../common/legend'; +import { SpecType } from '../../../specs'; +import { InternalChartState, GlobalChartState, BackwardRef } from '../../../state/chart_state'; +import { InitStatus } from '../../../state/selectors/get_internal_is_intialized'; +import { LegendItemLabel } from '../../../state/selectors/get_legend_items_labels'; +import { DebugState } from '../../../state/types'; +import { getSpecsFromStore } from '../../../state/utils'; +import { Dimensions } from '../../../utils/dimensions'; +import { Viz } from '../renderer/canvas/viz'; +import { NewVizSpec } from '../specs'; + +const EMPTY_MAP = new Map(); +const EMPTY_LEGEND_LIST: LegendItem[] = []; +const EMPTY_LEGEND_ITEM_LIST: LegendItemLabel[] = []; + +/** @internal */ +export class NewVizState implements InternalChartState { + chartType = ChartType.Goal; + + // eslint-disable-next-line @typescript-eslint/no-useless-constructor + constructor() { + // TODO add selectors that needs initialization + } + + isInitialized(globalState: GlobalChartState) { + return getSpecsFromStore(globalState.specs, ChartType.NewViz, SpecType.Series).length > 0 + ? InitStatus.Initialized + : InitStatus.ChartNotInitialized; + } + + isBrushAvailable() { + return false; + } + + isBrushing() { + return false; + } + + isChartEmpty() { + return false; + } + + getLegendItems() { + return EMPTY_LEGEND_LIST; + } + + getLegendItemsLabels() { + return EMPTY_LEGEND_ITEM_LIST; + } + + getLegendExtraValues() { + return EMPTY_MAP; + } + + chartRenderer(containerRef: BackwardRef, forwardStageRef: RefObject) { + return ( + <> + + + ); + } + + getPointerCursor() { + return DEFAULT_CSS_CURSOR; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + isTooltipVisible(globalState: GlobalChartState) { + return { visible: false, isExternal: false }; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getTooltipInfo(globalState: GlobalChartState) { + return undefined; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getTooltipAnchor(globalState: GlobalChartState) { + return null; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + eventCallbacks(globalState: GlobalChartState) {} + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getChartTypeDescription(globalState: GlobalChartState) { + return 'TODO new chart type'; + } + + // TODO + getProjectionContainerArea(): Dimensions { + return { width: 0, height: 0, top: 0, left: 0 }; + } + + // TODO + getMainProjectionArea(): Dimensions { + return { width: 0, height: 0, top: 0, left: 0 }; + } + + // TODO + getBrushArea(): Dimensions | null { + return null; + } + + // TODO + getDebugState(): DebugState { + return {}; + } +} diff --git a/packages/charts/src/chart_types/template/state/selectors/chart_size.ts b/packages/charts/src/chart_types/template/state/selectors/chart_size.ts new file mode 100644 index 0000000000..6446193f78 --- /dev/null +++ b/packages/charts/src/chart_types/template/state/selectors/chart_size.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { GlobalChartState } from '../../../../state/chart_state'; +import { createCustomCachedSelector } from '../../../../state/create_selector'; +import { Dimensions } from '../../../../utils/dimensions'; + +const getParentDimension = (state: GlobalChartState) => state.parentDimensions; + +/** @internal */ +export const chartSize = createCustomCachedSelector( + [getParentDimension], + (container): Dimensions => { + return { ...container }; + }, +); diff --git a/packages/charts/src/chart_types/template/state/selectors/data.ts b/packages/charts/src/chart_types/template/state/selectors/data.ts new file mode 100644 index 0000000000..e16938b5e7 --- /dev/null +++ b/packages/charts/src/chart_types/template/state/selectors/data.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import { ChartType } from '../../..'; +import { SpecType } from '../../../../specs'; +import { GlobalChartState } from '../../../../state/chart_state'; +import { createCustomCachedSelector } from '../../../../state/create_selector'; +import { getSpecsFromStore } from '../../../../state/utils'; +import { NewVizSpec } from '../../specs'; + +/** @internal */ +export const getSpecs = createCustomCachedSelector( + [(global: GlobalChartState) => global.specs], + (specs): NewVizSpec[] => { + return getSpecsFromStore(specs, ChartType.NewViz, SpecType.Series); + }, +); + +/** @internal */ +export const data = createCustomCachedSelector([getSpecs], (spec): number[] => { + return spec.map((d) => d.data); +}); diff --git a/packages/charts/src/state/chart_state.ts b/packages/charts/src/state/chart_state.ts index 6cf76ef6a9..81f6300e4e 100644 --- a/packages/charts/src/state/chart_state.ts +++ b/packages/charts/src/state/chart_state.ts @@ -12,6 +12,7 @@ import { ChartType } from '../chart_types'; import { GoalState } from '../chart_types/goal_chart/state/chart_state'; import { HeatmapState } from '../chart_types/heatmap/state/chart_state'; import { PartitionState } from '../chart_types/partition_chart/state/chart_state'; +import { NewVizState } from '../chart_types/template/state/chart_state'; import { WordcloudState } from '../chart_types/wordcloud/state/chart_state'; import { XYAxisChartState } from '../chart_types/xy_chart/state/chart_state'; import { CategoryKey } from '../common/category'; @@ -429,6 +430,7 @@ const constructors: Record InternalChartState | null> = { [ChartType.Heatmap]: () => new HeatmapState(), [ChartType.Wordcloud]: () => new WordcloudState(), [ChartType.Global]: () => null, + [ChartType.NewViz]: () => new NewVizState(), }; // with no default, TS signals if a new chart type isn't added here too function newInternalState(chartType: ChartType | null): InternalChartState | null { diff --git a/storybook/stories/new_chart_type/1_basic.story.tsx b/storybook/stories/new_chart_type/1_basic.story.tsx new file mode 100644 index 0000000000..29644f4b8a --- /dev/null +++ b/storybook/stories/new_chart_type/1_basic.story.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import React from 'react'; + +import { Chart, NewViz } from '@elastic/charts'; + +export const Example = () => { + return ( + + + + + + ); +}; diff --git a/storybook/stories/new_chart_type/new_chart_type.stories.tsx b/storybook/stories/new_chart_type/new_chart_type.stories.tsx new file mode 100644 index 0000000000..86fea06874 --- /dev/null +++ b/storybook/stories/new_chart_type/new_chart_type.stories.tsx @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +export default { + title: 'New Chart Type', +}; + +export { Example as basic } from './1_basic.story'; From 0c2357a99f1710dabeff0fcfb72c81d40a6489b3 Mon Sep 17 00:00:00 2001 From: Marco Vettorello Date: Thu, 19 May 2022 16:16:41 +0200 Subject: [PATCH 02/30] feat(new_chart): metric --- .eslintrc.js | 3 +- .prettierignore | 2 + CHANGELOG.md | 35 + CONTRIBUTING.md | 42 +- e2e-server/server/webpack.config.js | 2 +- e2e/playwright.config.ts | 1 + .../cpu-profile-flame-chart-chrome-linux.png | Bin 108708 -> 107262 bytes ...pu-profile-gl-flame-chart-chrome-linux.png | Bin 0 -> 97753 bytes ...ld-focus-element-on-click-chrome-linux.png | Bin 0 -> 87490 bytes e2e/tests/flame_stories.test.ts | 32 + integration/jest.config.js | 10 +- integration/jest_env_setup.ts | 5 + integration/jest_puppeteer.config.js | 5 +- integration/page_objects/common.ts | 3 +- integration/server/webpack.config.js | 2 +- ...-styling-visually-looks-correct-1-snap.png | Bin 21233 -> 26177 bytes ...me-chart-visually-looks-correct-1-snap.png | Bin 0 -> 109156 bytes ...ha-basic-visually-looks-correct-1-snap.png | Bin 0 -> 49511 bytes ...ons-when-line-marker-is-hovered-1-snap.png | Bin 0 -> 29276 bytes ...when-rect-annotation-is-hovered-1-snap.png | Bin 0 -> 29230 bytes ...-show-tooltip-on-hover-x-domain-1-snap.png | Bin 24374 -> 24376 bytes ...-show-tooltip-on-hover-y-domain-1-snap.png | Bin 28005 -> 28036 bytes ...tiples-with-log-scale-dont-clip-1-snap.png | Bin 0 -> 74482 bytes ...bottom-should-hide-panel-titles-1-snap.png | Bin 97717 -> 97769 bytes ...on-left-should-hide-empty-title-1-snap.png | Bin 95254 -> 95288 bytes ...position-left-should-hide-title-1-snap.png | Bin 95254 -> 95288 bytes ...n-right-should-hide-empty-title-1-snap.png | Bin 94931 -> 94795 bytes ...osition-right-should-hide-title-1-snap.png | Bin 94931 -> 94795 bytes ...on-top-should-hide-panel-titles-1-snap.png | Bin 97554 -> 97607 bytes ...der-four-axes-with-no-gridlines-1-snap.png | Bin 91989 -> 92102 bytes ...es-with-titles-and-panel-titles-1-snap.png | Bin 95945 -> 95993 bytes integration/tests/annotations_stories.test.ts | 18 + integration/tests/area_stories.test.ts | 6 + package.json | 4 +- packages/charts/.gitignore | 8 +- packages/charts/api/charts.api.md | 211 ++- packages/charts/package.json | 3 +- .../src/chart_types/flame_chart/flame_api.ts | 86 + .../chart_types/flame_chart/flame_chart.tsx | 639 ++++++++ .../flame_chart/internal_chart_state.ts | 40 + .../chart_types/flame_chart/render/common.ts | 17 +- .../flame_chart/render/draw_a_frame.ts | 127 ++ .../flame_chart/render/draw_canvas.ts | 126 ++ .../flame_chart/render/draw_webgl.ts | 65 + .../flame_chart/render/ensure_webgl.ts | 75 + .../src/chart_types/flame_chart/shaders.ts | 106 ++ .../src/chart_types/flame_chart/types.ts | 46 + packages/charts/src/chart_types/index.ts | 3 +- .../chart_types/metric/renderer/_index.scss | 37 + .../metric/renderer/dom/_progress.scss | 47 + .../metric/renderer/dom/_sparkline.scss | 29 + .../metric/renderer/dom/_text.scss | 55 + .../chart_types/metric/renderer/dom/index.tsx | 201 +++ .../metric/renderer/dom/progress.tsx | 43 + .../metric/renderer/dom/sparkline.tsx | 61 + .../chart_types/metric/renderer/dom/text.tsx | 104 ++ .../src/chart_types/metric/specs/index.ts | 97 ++ .../state/chart_state.tsx | 10 +- .../state/selectors/chart_size.ts | 0 .../state/selectors/data.ts | 11 +- .../layout/types/config_types.ts | 11 +- .../layout/types/viewmodel_types.ts | 3 +- .../layout/viewmodel/picked_shapes.ts | 4 +- .../layout/viewmodel/viewmodel.ts | 2 +- .../renderer/dom/highlighter.tsx | 3 +- .../renderer/dom/highlighter_hover.tsx | 2 +- .../partition_chart/specs/index.ts | 3 +- .../state/selectors/geometries.ts | 1 - .../state/selectors/picked_shapes.test.ts | 13 +- packages/charts/src/chart_types/specs.ts | 10 +- .../template/renderer/canvas/viz.tsx | 167 -- .../src/chart_types/template/specs/index.ts | 53 - .../annotations/line/dimensions.test.ts | 1 + .../xy_chart/annotations/rect/dimensions.ts | 24 +- .../xy_chart/annotations/rect/tooltip.test.ts | 7 +- .../xy_chart/annotations/rect/tooltip.ts | 26 +- .../xy_chart/annotations/rect/types.ts | 2 + .../chart_types/xy_chart/annotations/types.ts | 4 +- .../renderer/canvas/animations/README.md | 141 ++ .../renderer/canvas/animations/animation.ts | 188 +++ .../renderer/canvas/animations/index.ts | 93 ++ .../renderer/canvas/annotations/index.ts | 40 +- .../renderer/canvas/annotations/lines.ts | 34 +- .../renderer/canvas/annotations/rect.ts | 30 +- .../xy_chart/renderer/canvas/areas.ts | 28 +- .../xy_chart/renderer/canvas/bars.ts | 7 +- .../xy_chart/renderer/canvas/bubbles.ts | 23 +- .../xy_chart/renderer/canvas/lines.ts | 24 +- .../renderer/canvas/panel_clipping.ts | 20 + .../xy_chart/renderer/canvas/points.ts | 7 +- .../xy_chart/renderer/canvas/renderers.ts | 269 ++-- .../xy_chart/renderer/canvas/xy_chart.tsx | 21 +- .../xy_chart/renderer/common/utils.ts | 35 + .../renderer/dom/annotations/annotations.tsx | 46 +- .../renderer/dom/annotations/line_marker.tsx | 39 +- .../state/chart_state.interactions.test.tsx | 42 +- .../selectors/get_annotation_tooltip_state.ts | 3 +- ...get_highlighted_annotation_ids_selector.ts | 37 + .../get_multiple_rectangle_annotations.ts | 10 +- .../src/chart_types/xy_chart/utils/specs.ts | 2 +- packages/charts/src/common/animation.ts | 23 + .../src/common/color_library_wrappers.ts | 11 + .../src/common/event_handler_selectors.ts | 18 +- packages/charts/src/common/kingly.ts | 626 ++++++++ .../__snapshots__/chart.test.tsx.snap | 6 +- .../charts/src/components/_container.scss | 1 + packages/charts/src/components/_index.scss | 1 + .../charts/src/components/chart_resizer.tsx | 8 +- .../charts/src/components/tooltip/tooltip.tsx | 57 +- packages/charts/src/geoms/path.ts | 48 + packages/charts/src/index.ts | 12 +- .../src/mocks/annotations/annotations.ts | 2 + .../cpu_profile_tree_mock_columnar.json | 1405 +++++++++++++++++ packages/charts/src/mocks/utils.ts | 2 +- packages/charts/src/specs/settings.tsx | 19 +- packages/charts/src/state/chart_state.ts | 7 +- .../charts/src/state/reducers/interactions.ts | 32 +- .../get_chart_container_dimensions.ts | 35 +- packages/charts/src/utils/logger.ts | 2 +- .../charts/src/utils/themes/dark_theme.ts | 9 + .../charts/src/utils/themes/light_theme.ts | 9 + .../charts/src/utils/themes/merge_utils.ts | 54 +- .../charts/src/utils/themes/theme.test.ts | 71 +- packages/charts/src/utils/themes/theme.ts | 39 +- .../charts/src/utils/time_functions.test.ts | 24 + packages/charts/src/utils/time_functions.ts | 81 + playground/webpack.config.js | 2 +- scripts/setup_enzyme.ts | 5 + scripts/webgl_context_mock.ts | 381 +++++ storybook/package.json | 2 +- .../annotations/rects/4_styling.story.tsx | 185 ++- .../bar/54_functional_accessors.story.tsx | 10 +- .../icicle/03_cpu_profile_flame.story.tsx | 8 +- .../icicle/04_cpu_profile_gl_flame.story.tsx | 82 + storybook/stories/icicle/icicle.stories.tsx | 1 + storybook/stories/metric/1_basic.story.tsx | 139 ++ .../metric.stories.tsx} | 2 +- .../2_vertical_areas.story.tsx | 7 +- storybook/stories/utils/knobs.ts | 19 +- .../stories/wordcloud/1_wordcloud.story.tsx | 6 +- storybook/style.scss | 2 +- storybook/webpack.config.js | 4 - yarn.lock | 29 +- 143 files changed, 6530 insertions(+), 848 deletions(-) create mode 100644 e2e/screenshots/all.test.ts-snapshots/baselines/flame-alpha/cpu-profile-gl-flame-chart-chrome-linux.png create mode 100644 e2e/screenshots/flame_stories.test.ts-snapshots/flame-stories/should-focus-element-on-click-chrome-linux.png create mode 100644 e2e/tests/flame_stories.test.ts create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-flame-alpha-cpu-profile-gl-flame-chart-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/all-test-ts-baseline-visual-tests-for-all-stories-metric-alpha-basic-visually-looks-correct-1-snap.png create mode 100644 integration/tests/__image_snapshots__/annotations-stories-test-ts-annotations-stories-hover-state-should-fade-all-other-annotations-when-line-marker-is-hovered-1-snap.png create mode 100644 integration/tests/__image_snapshots__/annotations-stories-test-ts-annotations-stories-hover-state-should-fade-all-other-annotations-when-rect-annotation-is-hovered-1-snap.png create mode 100644 integration/tests/__image_snapshots__/area-stories-test-ts-area-series-stories-small-multiples-with-log-scale-dont-clip-1-snap.png create mode 100644 packages/charts/src/chart_types/flame_chart/flame_api.ts create mode 100644 packages/charts/src/chart_types/flame_chart/flame_chart.tsx create mode 100644 packages/charts/src/chart_types/flame_chart/internal_chart_state.ts rename storybook/stories/new_chart_type/1_basic.story.tsx => packages/charts/src/chart_types/flame_chart/render/common.ts (53%) create mode 100644 packages/charts/src/chart_types/flame_chart/render/draw_a_frame.ts create mode 100644 packages/charts/src/chart_types/flame_chart/render/draw_canvas.ts create mode 100644 packages/charts/src/chart_types/flame_chart/render/draw_webgl.ts create mode 100644 packages/charts/src/chart_types/flame_chart/render/ensure_webgl.ts create mode 100644 packages/charts/src/chart_types/flame_chart/shaders.ts create mode 100644 packages/charts/src/chart_types/flame_chart/types.ts create mode 100644 packages/charts/src/chart_types/metric/renderer/_index.scss create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/_progress.scss create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/_sparkline.scss create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/_text.scss create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/index.tsx create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/progress.tsx create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/sparkline.tsx create mode 100644 packages/charts/src/chart_types/metric/renderer/dom/text.tsx create mode 100644 packages/charts/src/chart_types/metric/specs/index.ts rename packages/charts/src/chart_types/{template => metric}/state/chart_state.tsx (91%) rename packages/charts/src/chart_types/{template => metric}/state/selectors/chart_size.ts (100%) rename packages/charts/src/chart_types/{template => metric}/state/selectors/data.ts (74%) delete mode 100644 packages/charts/src/chart_types/template/renderer/canvas/viz.tsx delete mode 100644 packages/charts/src/chart_types/template/specs/index.ts create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/canvas/animations/README.md create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/canvas/animations/animation.ts create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/canvas/animations/index.ts create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/canvas/panel_clipping.ts create mode 100644 packages/charts/src/chart_types/xy_chart/renderer/common/utils.ts create mode 100644 packages/charts/src/chart_types/xy_chart/state/selectors/get_highlighted_annotation_ids_selector.ts create mode 100644 packages/charts/src/common/animation.ts create mode 100644 packages/charts/src/common/kingly.ts create mode 100644 packages/charts/src/geoms/path.ts create mode 100644 packages/charts/src/mocks/hierarchical/cpu_profile_tree_mock_columnar.json create mode 100644 packages/charts/src/utils/time_functions.test.ts create mode 100644 packages/charts/src/utils/time_functions.ts create mode 100644 scripts/webgl_context_mock.ts create mode 100644 storybook/stories/icicle/04_cpu_profile_gl_flame.story.tsx create mode 100644 storybook/stories/metric/1_basic.story.tsx rename storybook/stories/{new_chart_type/new_chart_type.stories.tsx => metric/metric.stories.tsx} (93%) diff --git a/.eslintrc.js b/.eslintrc.js index 4d398c6920..4e660253d2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -469,9 +469,10 @@ module.exports = { }, }, { - files: ['./integration/**/*.test.ts?(x)'], + files: ['integration/**/*.test.ts?(x)'], rules: { 'jest/expect-expect': 0, + 'unicorn/consistent-function-scoping': 0, }, }, ], diff --git a/.prettierignore b/.prettierignore index 481a27e4f6..90ca62a0ab 100644 --- a/.prettierignore +++ b/.prettierignore @@ -10,3 +10,5 @@ coverage/ **/tmp/ packages/charts/src/_reset.scss +packages/charts/src/mocks/hierarchical/cpu_profile_tree_mock_columnar.json +packages/charts/src/mocks/hierarchical/data.json diff --git a/CHANGELOG.md b/CHANGELOG.md index 962d6de26f..919f473e2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +# [46.2.0](https://github.com/elastic/elastic-charts/compare/v46.1.0...v46.2.0) (2022-05-18) + + +### Bug Fixes + +* runtime error with `process.env` in src ([#1672](https://github.com/elastic/elastic-charts/issues/1672)) ([47a6b0b](https://github.com/elastic/elastic-charts/commit/47a6b0b9ab88cc33229ca8c132e10deb6729cd57)) +* **deps:** update dependency @elastic/eui to v56 ([#1667](https://github.com/elastic/elastic-charts/issues/1667)) ([285ec8b](https://github.com/elastic/elastic-charts/commit/285ec8bdd05dee758f03b503cb1df311b140c778)) + + +### Features + +* flame graph with WebGL ([#1664](https://github.com/elastic/elastic-charts/issues/1664)) ([96368ea](https://github.com/elastic/elastic-charts/commit/96368ea45e093cf0e1b0b6db7356568e98be25c9)) + +# [46.1.0](https://github.com/elastic/elastic-charts/compare/v46.0.1...v46.1.0) (2022-05-05) + + +### Bug Fixes + +* **animations:** flashing when using grouped parameterized keys ([#1665](https://github.com/elastic/elastic-charts/issues/1665)) ([1323edc](https://github.com/elastic/elastic-charts/commit/1323edc76f496028ffe2119ec83af2834484bede)) +* **deps:** update dependency @elastic/eui to ^55.1.0 ([#1663](https://github.com/elastic/elastic-charts/issues/1663)) ([ef8a185](https://github.com/elastic/elastic-charts/commit/ef8a185fc621890d561432953846112e957bdb5d)) +* **deps:** update dependency @elastic/eui to v55 ([#1659](https://github.com/elastic/elastic-charts/issues/1659)) ([5fc4af3](https://github.com/elastic/elastic-charts/commit/5fc4af3c84b1ff9d2818862d1eb19de780de4208)) + + +### Features + +* **annotations:** animated focus states for hovered annotation ([#1628](https://github.com/elastic/elastic-charts/issues/1628)) ([0bbb809](https://github.com/elastic/elastic-charts/commit/0bbb809132897dc691f1a71c676c46ec91aa59df)) + +## [46.0.1](https://github.com/elastic/elastic-charts/compare/v46.0.0...v46.0.1) (2022-04-19) + + +### Bug Fixes + +* **deps:** update dependency @elastic/eui to ^54.1.0 ([#1652](https://github.com/elastic/elastic-charts/issues/1652)) ([c9c6d31](https://github.com/elastic/elastic-charts/commit/c9c6d31aca342df83271efcbaeed5a79c02a71a3)) +* **rendering:** clip at panel sizes on small multiples ([#1651](https://github.com/elastic/elastic-charts/issues/1651)) ([2850530](https://github.com/elastic/elastic-charts/commit/285053061f286d6171221cb99483dd500dfaf52a)) + # [46.0.0](https://github.com/elastic/elastic-charts/compare/v45.1.1...v46.0.0) (2022-04-14) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8d960070e7..9383fd4d52 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -118,43 +118,29 @@ This will automatically generate a pr to merge into the target branch. Once the ## Linking to Kibana -To link `@elastic/charts` to kibana we need to perform a few workarounds. Fortunately, this is done automatically by a custom built linking scripts. +We previously had a way to link a watched charts build to kibana via https://github.com/elastic/elastic-charts/pull/1164 but that was only a brief luxury as kibana completed migration to Bazel. We hope for this to return in some form at some point. -## Before linking -Before you link kibana be sure to have a kibana instance up and running locally and pointing at whatever elasticsearch cluster you want. - -> Note: ⚠️ Make sure that there is no `@elastic/charts` directory within the kibana `kbn-ui-shared-deps` directory (i.e. `/packages/kbn-ui-shared-deps/node_modules/@elastic/charts` should not exist). If this does exist please delete it and run `yarn kbn bootstrap --no-cache` then restart kibana. The issue is that this package should be hoisted to the top-level `node_modules`, if it is not at the top-level the linking will update the wrong package and not work properly. - -### Linking - -In order to create link run... +So the current way to link charts is via `.gz` build file. You’d first build and pack charts locally. ``` -yarn link:kibana +cd ./packages/charts +yarn build +npm pack ``` -This will prompt you for the following inputs: - -- Select the `Link` action -- Select the path to kibana (default: `../kibana`) -- Confirm kibana is running - -The reset is handled automatically! If any errors occur in the build method, they will be surfaced in the terminal and pause the build process until corrected. +Locate the full path to the created `.gz` file within `packages/charts` and update kibana `package.json` with this path for charts in kibana. -If you encounter and issue or close the watch mode following the linking process, you can run the script is watch mode. Do so by running... - -``` -yarn link:kibana +```jsonc +// kibana package.json +{ + "@elastic/charts": "Users/path/to/charts/file.gz" +} ``` -- Select the `Watch mode` action - -### Unlinking - -In order to remove the link and restore kibana to state prior to linking, run... +Then run `bootstrap` in kibana ``` -yarn link:kibana +yarn kbn bootstrap --no-validate ``` -- Select the `Unlink` action +The `--no-validate` flag is required when installing from a local source, otherwise it will throw an error. diff --git a/e2e-server/server/webpack.config.js b/e2e-server/server/webpack.config.js index 9735c06c1c..9df79c6353 100644 --- a/e2e-server/server/webpack.config.js +++ b/e2e-server/server/webpack.config.js @@ -128,7 +128,7 @@ module.exports = { filename: 'index.html', favicon: '../../public/favicon.ico', }), - new webpack.EnvironmentPlugin({ RNG_SEED: null }), + new webpack.EnvironmentPlugin({ RNG_SEED: null, VRT: 'true' }), new MiniCssExtractPlugin(), new SpeedMeasurePlugin(), ], diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts index 7741fd71d9..0c73339e32 100644 --- a/e2e/playwright.config.ts +++ b/e2e/playwright.config.ts @@ -18,6 +18,7 @@ const config: PlaywrightTestConfig = { video: process.env.CI ? 'off' : 'retain-on-failure', launchOptions: { ignoreDefaultArgs: ['--hide-scrollbars'], + args: ['--use-gl=egl'], }, }, reporter: process.env.CI ? 'github' : [['html', { open: 'never', outputFolder: 'report' }], ['list']], diff --git a/e2e/screenshots/all.test.ts-snapshots/baselines/flame-alpha/cpu-profile-flame-chart-chrome-linux.png b/e2e/screenshots/all.test.ts-snapshots/baselines/flame-alpha/cpu-profile-flame-chart-chrome-linux.png index ec4323f8e08ad15a2e38bfdf24badaed154d2763..405840133d4330a8a8215c7dfafdc8e402550dfb 100644 GIT binary patch literal 107262 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