Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support native chart title and description #2002

Merged
merged 35 commits into from
Jun 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3cc9aad
feat: chart title and description
markov00 Mar 20, 2023
6b60244
docs: add feat examples
markov00 Mar 20, 2023
6de4fc2
Merge branch 'main' into 2023_03_06-viz_titles
markov00 Mar 20, 2023
a81b758
style: align to metric title/subtitle
markov00 Apr 3, 2023
2c5090b
Merge branch 'main' into 2023_03_06-viz_titles
nickofthyme May 18, 2023
0801b5f
chore(docs): add toggles and better story type support
nickofthyme May 26, 2023
08cd3b2
chore: add markdown to Story.parameter types
nickofthyme May 26, 2023
0b56828
docs: add better types to stories, add title and description
nickofthyme May 26, 2023
792531a
chore: adapt e2e_server to support toggles
nickofthyme May 30, 2023
2f4e0c0
Merge branch 'main' into 2023_03_06-viz_titles
nickofthyme May 31, 2023
3a98cfd
fix: legend flex styles
nickofthyme May 31, 2023
a1bfca6
test: add chart sizing tests, update legend story
nickofthyme May 31, 2023
6083f79
test: fix missed legend positioning url
nickofthyme May 31, 2023
1290812
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] May 31, 2023
91a0018
fix: partition height issue
nickofthyme Jun 1, 2023
652b030
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jun 1, 2023
0d09293
test: tweak multiChartCursorSync story
nickofthyme Jun 1, 2023
7a4ba81
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jun 1, 2023
6b115cc
feat: add option to disable titles for each chart type
nickofthyme Jun 1, 2023
4b72b1f
test: update snapshots from template changes
nickofthyme Jun 1, 2023
a7d5c9a
fix: linting error
nickofthyme Jun 1, 2023
8f2431e
docs: limit metric resize extents
nickofthyme Jun 1, 2023
c36e2b5
Merge branch 'main' into 2023_03_06-viz_titles
markov00 Jun 5, 2023
119832c
Merge branch 'main' into 2023_03_06-viz_titles
nickofthyme Jun 8, 2023
dbbb4f2
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jun 8, 2023
698e093
chore: add right margin for kibana top-right menu
nickofthyme Jun 8, 2023
5853fbc
style: add top border to grid metric with titles
nickofthyme Jun 8, 2023
f1812ae
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jun 8, 2023
11dac7f
test: update snapshot
nickofthyme Jun 8, 2023
aae5cf9
chore: update api
nickofthyme Jun 8, 2023
88a66c4
Merge remote-tracking branch 'marco/2023_03_06-viz_titles' into 2023_…
nickofthyme Jun 8, 2023
fe3727d
fix: prettier formatting
nickofthyme Jun 8, 2023
19d7c29
fix: initialize chart with with titles
nickofthyme Jun 9, 2023
ec75ce4
fix: show titles on single row grids
nickofthyme Jun 9, 2023
695f575
test(vrt): update screenshots [skip ci]
elastic-datavis[bot] Jun 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions e2e/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ const groupsToSkip: Set<string> = new Set(['Components/Tooltip']);
const storiesToSkip: Map<string, string[]> = new Map(
Object.entries({
'Test Cases': ['noSeries'],
Interactions: ['multiChartCursorSync'],
nickofthyme marked this conversation as resolved.
Show resolved Hide resolved
}),
);

Expand Down
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
60 changes: 60 additions & 0 deletions e2e/tests/chart.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* 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 { test } from '@playwright/test';

import { Position } from '../constants';
import { pwEach } from '../helpers';
import { common } from '../page_objects/common';

test.describe('Chart', () => {
test.describe('Sizing', () => {
test('should accommodate chart title only', async ({ page }) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
'http://localhost:9002/?path=/story/legend--positioning&knob-position=bottom&globals=toggles.showChartTitle:true',
);
});

test('should accommodate chart description only', async ({ page }) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
'http://localhost:9002/?path=/story/legend--positioning&knob-position=bottom&globals=toggles.showChartDescription:true',
);
});

test('should render multiple charts with titles', async ({ page }) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/interactions--multi-chart-cursor-sync',
{ screenshotSelector: '#story-root' },
);
});

pwEach.test<Position>(['bottom', 'left', 'right', 'top'])(
(position) => `should accommodate chart title and description - legend ${position}`,
async (page, position) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
`http://localhost:9002/?path=/story/legend--positioning&globals=toggles.showChartTitle:true;toggles.showChartDescription:true&knob-position=${position}`,
);
},
);

pwEach.test<[type: string, url: string]>([
['Cartesian', 'http://localhost:9001/?path=/story/mixed-charts--areas-and-bars'],
['Partition', 'http://localhost:9001/?path=/story/sunburst--sunburst-with-three-layers'],
['Heatmap', 'http://localhost:9001/?path=/story/heatmap-alpha--basic'],
['WordCloud', 'http://localhost:9001/?path=/story/wordcloud-alpha--simple-wordcloud'],
['Metric SM', 'http://localhost:9001/?path=/story/metric-alpha--grid'],
])(
([type]) => `should accommodate chart title and description - ${type}`,
async (page, [, url]) => {
await common.expectChartAtUrlToMatchScreenshot(page)(
`${url}&globals=toggles.showChartTitle:true;toggles.showChartDescription:true`,
);
},
);
});
});
32 changes: 19 additions & 13 deletions e2e/tests/legend_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,22 +101,28 @@ test.describe('Legend stories', () => {

test.describe('Tooltip placement with legend', () => {
test('should render tooltip with left legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)('http://localhost:9001/?path=/story/legend--left', {
bottom: 190,
left: 310,
});
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--positioning&knob-position=left',
{
bottom: 190,
left: 310,
},
);
});

test('should render tooltip with top legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)('http://localhost:9001/?path=/story/legend--top', {
top: 150,
left: 320,
});
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--positioning&knob-position=top',
{
top: 150,
left: 320,
},
);
});

test('should render tooltip with right legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--right',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=right',
{
bottom: 180,
left: 330,
Expand All @@ -126,7 +132,7 @@ test.describe('Legend stories', () => {

test('should render tooltip with bottom legend', async ({ page }) => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--bottom',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=bottom',
{
top: 150,
left: 320,
Expand All @@ -140,7 +146,7 @@ test.describe('Legend stories', () => {
// puts mouse to the bottom left
await common.moveMouse(page)(0, 0);
await common.expectChartWithKeyboardEventsAtUrlToMatchScreenshot(page)(
'http://localhost:9001/?path=/story/legend--right',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=right',
[
{
key: 'tab',
Expand All @@ -155,7 +161,7 @@ test.describe('Legend stories', () => {
});
test('should change aria label to hidden when clicked', async ({ page }) => {
await common.loadElementFromURL(page)(
'http://localhost:9001/?path=/story/legend--right',
'http://localhost:9001/?path=/story/legend--positioning&knob-position=right',
'.echLegendItem__label',
);
await common.clickMouseRelativeToDOMElement(page)(
Expand Down Expand Up @@ -262,7 +268,7 @@ test.describe('Legend stories', () => {
test.describe('Custom width', () => {
// eslint-disable-next-line unicorn/consistent-function-scoping
const getUrl = (position: string, size: number) =>
`http://localhost:9001/?path=/story/legend--${position}&knob-enable legend size=true&knob-legend size=${size}`;
`http://localhost:9001/?path=/story/legend--positioning&knob-position=${position}&knob-enable legend size=true&knob-legend size=${size}`;

pwEach.describe(['top', 'right', 'bottom', 'left'])(
(p) => `position ${p}`,
Expand Down
1 change: 1 addition & 0 deletions e2e_server/server/generate/extract_examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ function extractExamples(exampleRelativePath = 'storybook/stories') {
name,
filename,
url,
groupTitle,
filePath: path.join(path.relative(process.cwd(), path.dirname(groupFile)), filename),
};
});
Expand Down
11 changes: 8 additions & 3 deletions e2e_server/server/generate/import_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
* Side Public License, v 1.
*/

module.exports = function lazyImportTemplate(index, path) {
const { capitalCase } = require('change-case');

module.exports = function lazyImportTemplate({ filePath, groupTitle, name }, index) {
return `
const Component${index} = React.lazy(() => {
return import('../../${path}').then((module) => {
return import('../../${filePath}').then((module) => {
setParams(urlParams, (module.Example as any).parameters);
return { default: module.Example };
const Component = module.Example.bind(module.Example, {}, getStoryContext('${groupTitle}', '${capitalCase(
name,
)}'))
return { default: Component };
});
});`;
};
2 changes: 1 addition & 1 deletion e2e_server/server/generate/route_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@
* Side Public License, v 1.
*/

module.exports = function routeComponentTemplate(index, url) {
module.exports = function routeComponentTemplate({ url }, index) {
return `{path === '${url}' && <Component${index} />}`;
};
24 changes: 18 additions & 6 deletions e2e_server/server/generate/vrt_page_template.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,26 @@ import React, { Suspense } from 'react';
import { EuiProvider } from '@elastic/eui';
import { ThemeIdProvider, BackgroundIdProvider } from '../../storybook/use_base_theme';
import { useGlobalsParameters } from '../server/mocks/use_global_parameters';
import { StoryContext } from '../../storybook/types';

export function VRTPage() {
const {
themeId,
backgroundId,
toggles,
setParams,
} = useGlobalsParameters();
const urlParams = new URL(window.location.toString()).searchParams;
const colorMode = themeId.includes('light') ? 'light' : 'dark';
const colorMode = (themeId ?? '').includes('light') ? 'light' : 'dark';
const getStoryContext = (title, name): StoryContext => ({
globals: {
theme: themeId,
background: backgroundId,
toggles,
},
title: toggles.showChartTitle ? title : undefined,
description: toggles.showChartDescription ? name : undefined,
});
${imports.join('\n ')}

const path = urlParams.get('path');
Expand All @@ -71,12 +82,13 @@ export function VRTPage() {
</ul>
</>);
}

return (
<EuiProvider colorMode={colorMode}>
<ThemeIdProvider value={themeId as any}>
<BackgroundIdProvider value={backgroundId}>
<Suspense fallback={<div>Loading...</div>}>
${routes.join('\n ')}
${routes.join('\n ')}
</Suspense>
</BackgroundIdProvider>
</ThemeIdProvider>
Expand All @@ -93,10 +105,10 @@ function compileVRTPage(examples) {
return acc;
}, []);
const { imports, routes, urls } = flatExamples.reduce(
(acc, { filePath, url }, index) => {
acc.imports.push(compileImportTemplate(index, filePath));
acc.routes.push(compileRouteTemplate(index, url));
acc.urls.push(url);
(acc, example, index) => {
acc.imports.push(compileImportTemplate(example, index));
acc.routes.push(compileRouteTemplate(example, index));
acc.urls.push(example.url);
return acc;
},
{ imports: [], routes: [], urls: [] },
Expand Down
46 changes: 35 additions & 11 deletions e2e_server/server/mocks/use_global_parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,14 @@
* Side Public License, v 1.
*/

import { useState } from 'react';
import { useMemo, useState } from 'react';

import type { StoryGlobals } from './../../../storybook/types';
import { BackgroundParameter } from '../../../storybook/node_modules/storybook-addon-background-toggle';
import { ThemeParameter } from '../../../storybook/node_modules/storybook-addon-theme-toggle';
import { storybookParameters as globalParams } from '../../../storybook/parameters';
import { ThemeId } from '../../../storybook/use_base_theme';

interface Globals {
theme?: string;
background?: string;
}

type Parameters = BackgroundParameter & ThemeParameter;

const themeParams = globalParams.theme!;
Expand Down Expand Up @@ -48,30 +44,58 @@ function applyThemeCSS(themeId: string) {
}
}

export function useGlobalsParameters() {
interface GlobalParameters {
themeId: StoryGlobals['theme'];
backgroundId: StoryGlobals['background'];
toggles: StoryGlobals['toggles'];
setParams(params: URLSearchParams, parameters?: Parameters): void;
}

export function useGlobalsParameters(): GlobalParameters {
const [themeId, setThemeId] = useState<string>(ThemeId.Light);
const [backgroundId, setBackgroundId] = useState<string | undefined>('white');
const [togglesJSON, setTogglesJSON] = useState<string>('{}');

/**
* Handles setting global context values. Stub for theme and background addons
*/
function setParams(params: URLSearchParams, parameters?: Parameters) {
const globals = getGlobalParams(params) as Globals;
const globals = getGlobalParams(params);
const backgroundIdFromParams = globals.background ?? parameters?.background?.default ?? backgroundParams.default;
setBackgroundId(backgroundIdFromParams);
const themeIdFromParams = globals.theme ?? parameters?.theme?.default ?? themeParams.default ?? ThemeId.Light;
setThemeId(themeIdFromParams);
setTogglesJSON(JSON.stringify(globals.toggles ?? '{}'));
applyThemeCSS(themeIdFromParams);
}

// using toggles object creates an infinite update loop, thus using JSON state.
const toggles = useMemo<StoryGlobals['toggles']>(() => JSON.parse(togglesJSON), [togglesJSON]);

return {
themeId,
backgroundId,
toggles,
setParams,
};
}

function getGlobalParams(params: URLSearchParams) {
const globals = params.get('globals') ?? '';
return Object.fromEntries(globals.split(';').map((pair: string) => pair.split(':')));
function getGlobalParams(params: URLSearchParams): StoryGlobals {
const rawGlobals = params.get('globals') ?? '';
const globalsArr = rawGlobals.split(';').map((pair: string) => pair.split(':'));
return globalsArr.reduce((acc, [key, value]) => {
const [k1, k2] = key?.split('.') ?? [];

if (k1 && k2) {
if (!acc[k1]) acc[k1] = {};

// capture nested object globals (i.e. toggles.showHeader:true)
try {
acc[k1][k2] = value && JSON.parse(value);
} catch {
acc[k1][k2] = value;
}
} else if (k1) acc[k1] = value;
return acc;
}, {} as any);
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"lint:it": "NODE_ENV=production eslint --quiet --ext .tsx,.ts,.js",
"lint:fix:it": "yarn lint:it --fix",
"prettier:check": "prettier --check \"**/*.{json,html,css,scss}\"",
"prettier:fix": "prettier --w \"**/*.{json,html,css,scss}\"",
"playground": "cd playground && RNG_SEED='elastic-charts' webpack serve",
"pq": "pretty-quick",
"semantic-release": "semantic-release --debug",
Expand Down Expand Up @@ -107,6 +108,7 @@
"autoprefixer": "^9.0.0",
"backport": "^5.6.6",
"commitizen": "^4.2.3",
"change-case": "^4.1.2",
"cross-env": "^7.0.2",
"cz-conventional-changelog": "^3.3.0",
"enzyme": "^3.11.0",
Expand Down
2 changes: 2 additions & 0 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,8 @@ export class Chart extends React_2.Component<ChartProps, ChartState> {
// (undocumented)
componentDidMount(): void;
// (undocumented)
componentDidUpdate({ title, description }: Readonly<ChartProps>): void;
// (undocumented)
componentWillUnmount(): void;
// (undocumented)
static defaultProps: ChartProps;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class FlameState implements InternalChartState {
isBrushAvailable = () => false;
isBrushing = () => false;
isChartEmpty = () => false;
canDisplayChartTitles = () => false;
getLegendItemsLabels = () => [];
getLegendItems = () => [];
getLegendExtraValues = () => new Map<SeriesKey, LegendItemExtraValues>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,4 +148,7 @@ export class GoalState implements InternalChartState {
smVDomain: [],
};
}

// TODO enable for small multiples
canDisplayChartTitles = () => false;
}
2 changes: 2 additions & 0 deletions packages/charts/src/chart_types/heatmap/state/chart_state.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -141,4 +141,6 @@ export class HeatmapState implements InternalChartState {
this.onBrushEndCaller(globalState);
this.onPointerUpdate(globalState);
}

canDisplayChartTitles = () => true;
}
4 changes: 4 additions & 0 deletions packages/charts/src/chart_types/metric/renderer/_index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
border-right: 1px solid #343741;
}

&--topBorder {
border-top: 1px solid #343741;
}

&--bottomBorder {
border-bottom: 1px solid #343741;
}
Expand Down
Loading