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(heatmap): add axis titles #1503

Merged
merged 29 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
467a3d9
feat: add axis titles for heatmap
rshen91 Nov 23, 2021
1ce8069
chore: update chart api
rshen91 Nov 23, 2021
a04178c
refactor: add titles to spec vs config
rshen91 Nov 24, 2021
b854dd0
refactor: update story knobs
rshen91 Nov 24, 2021
a83e676
Merge remote-tracking branch 'upstream/master' into heatmap-titles
rshen91 Nov 29, 2021
e324ed4
refactor: pull from chart theme vs config for axes titles
rshen91 Nov 29, 2021
dd3a34d
test: add vrt test
rshen91 Nov 29, 2021
6ca4ed5
Merge remote-tracking branch 'upstream/master' into heatmap-titles
rshen91 Dec 1, 2021
7672988
fix: initial code review
rshen91 Dec 1, 2021
eb9c5ab
refactor: code review changes
rshen91 Dec 2, 2021
7e4384e
test: update stories w padding changes
rshen91 Dec 2, 2021
199c656
fix: centering based on axes not chart
rshen91 Dec 2, 2021
0ba6e13
refactor: axis title position computations
rshen91 Dec 6, 2021
a2d59ce
Remove unused theme
markov00 Dec 9, 2021
f5e2006
Add title view model and render title with that
markov00 Dec 10, 2021
15621e2
Move scenegraph file outside selectors
markov00 Dec 10, 2021
3d5a867
Deprecate width, height, margin Config properties
markov00 Dec 10, 2021
2851dbf
Renamed default config to DEFAULT_CONFIG and align the deprecation of…
markov00 Dec 10, 2021
df4de3e
Refactor dimensions calculation and label positioning
markov00 Dec 10, 2021
139b719
Merge branch 'master' into pr/1503
markov00 Dec 20, 2021
8cd19eb
Move axisTitle style to theme
markov00 Dec 20, 2021
2a10f44
Add correct title prop
markov00 Dec 20, 2021
578eaa6
Remove console log
markov00 Dec 20, 2021
14056ab
Reintroduce padding for x ticks
markov00 Dec 20, 2021
75e2a08
Update VRTs
markov00 Dec 20, 2021
9d82293
Rename ChartDims to ChartElementSizes
markov00 Dec 21, 2021
a97d47a
Reuse theme.axes.axisTitle
markov00 Dec 21, 2021
5e56096
Remove axisTitle style from API report
markov00 Dec 21, 2021
6b12cac
DRY padding calc
markov00 Dec 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
6 changes: 6 additions & 0 deletions integration/tests/heatmap_stories.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,10 @@ describe('Heatmap stories', () => {
`http://localhost:9001/?path=/story/heatmap-alpha--time-snap&globals=theme:light&knob-dataset=${dataset}`,
);
});

it('should show x and y axis titles', async () => {
await common.expectChartAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/heatmap-alpha--basic&knob-Show%20x%20axis%20title=true&knob-Show%20y%20axis%20title=true',
);
});
});
6 changes: 6 additions & 0 deletions packages/charts/api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,8 @@ export type HeatmapBrushEvent = {

// @public (undocumented)
export interface HeatmapConfig {
// (undocumented)
axisTitle: TextStyle & Visible;
markov00 marked this conversation as resolved.
Show resolved Hide resolved
brushArea: {
visible: boolean;
fill: Color;
Expand Down Expand Up @@ -1216,12 +1218,16 @@ export interface HeatmapSpec extends Spec {
// (undocumented)
xAccessor: Accessor | AccessorFn;
// (undocumented)
xAxisTitle: string;
// (undocumented)
xScale: RasterTimeScale | OrdinalScale | LinearScale;
// (undocumented)
xSortPredicate: Predicate;
// (undocumented)
yAccessor: Accessor | AccessorFn;
// (undocumented)
yAxisTitle: string;
// (undocumented)
ySortPredicate: Predicate;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { Colors } from '../../../../common/colors';
import { LIGHT_THEME } from '../../../../utils/themes/light_theme';
import { Config } from '../types/config_types';

/** @internal */
Expand All @@ -17,6 +18,7 @@ export const config: Config = {
maxRowHeight: 30,
maxColumnWidth: 30,
fontFamily: 'Sans-Serif',
axisTitle: LIGHT_THEME.axes.axisTitle,

brushArea: {
visible: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import { Color } from '../../../../common/colors';
import { Pixels, SizeRatio } from '../../../../common/geometry';
import { Font, FontFamily, TextAlign, TextBaseline } from '../../../../common/text_utils';
import { TextStyle, Visible } from '../../../../utils/themes/theme';

/**
* @public
Expand All @@ -22,6 +23,7 @@ export interface Config {
// general text config
fontFamily: FontFamily;
timeZone: string;
axisTitle: TextStyle & Visible;

/**
* Config of the mask over the area outside of the selected cells
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,21 @@
import { Color, Colors } from '../../../../common/colors';
import { Font } from '../../../../common/text_utils';
import { clearCanvas, renderLayers, withContext } from '../../../../renderers/canvas';
import { Theme } from '../../../../utils/themes/theme';
import { renderMultiLine } from '../../../xy_chart/renderer/canvas/primitives/line';
import { renderRect } from '../../../xy_chart/renderer/canvas/primitives/rect';
import { renderText, wrapLines } from '../../../xy_chart/renderer/canvas/primitives/text';
import { ShapeViewModel } from '../../layout/types/viewmodel_types';
import { HeatmapSpec } from '../../specs/heatmap';

/** @internal */
export function renderCanvas2d(
ctx: CanvasRenderingContext2D,
dpr: number,
{ config, heatmapViewModel }: ShapeViewModel,
background: Color,
theme: Theme,
heatmapSpec?: HeatmapSpec,
) {
// eslint-disable-next-line no-empty-pattern
const {} = config;
Expand Down Expand Up @@ -127,6 +131,61 @@ export function renderCanvas2d(
renderText(ctx, { x: xValue.x, y: xValue.y }, xValue.text, config.xAxisLabel),
),
),

() =>
// render the xAxisTitle
heatmapSpec?.xAxisTitle &&
withContext(ctx, () => {
const { xValues } = heatmapViewModel;
const { width } = config;
// half the xAxixLabel width and padding
const halfLabel = (xValues[0].x + config.xAxisLabel.padding) / 2;
renderText(
ctx,
{
x: (width + halfLabel) / 2,
y: xValues[0].y + config.xAxisLabel.fontSize,
markov00 marked this conversation as resolved.
Show resolved Hide resolved
},
heatmapSpec.xAxisTitle,
{
fontVariant: 'normal',
fontWeight: 'bold',
textColor: '#333',
fontStyle: 'normal',
baseline: 'middle',
...config.axisTitle,
align: 'center',
},
);
}),

() =>
// render the yAxisTitle
heatmapSpec?.yAxisTitle &&
withContext(ctx, () => {
const { yValues } = heatmapViewModel;
const heightOfLabels = yValues[yValues.length - 1].y - yValues[0].y;
const yAxisTitlePadding =
typeof config.yAxisLabel.padding === 'number'
? config.yAxisLabel.padding
: config.yAxisLabel.padding.left ?? config.yAxisLabel.padding.bottom;
renderText(
ctx,
// subtract two times the padding from the chart for the axis label height
{ x: config.yAxisLabel.fontSize, y: (heightOfLabels + 2 * (yAxisTitlePadding ?? 0)) / 2 },
heatmapSpec.yAxisTitle,
{
fontVariant: 'normal',
fontWeight: 'bold',
textColor: '#333',
fontStyle: 'normal',
baseline: 'middle',
...config.axisTitle,
align: 'center',
},
markov00 marked this conversation as resolved.
Show resolved Hide resolved
-90,
);
}),
]);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,13 @@ import {
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
import { getInternalIsInitializedSelector, InitStatus } from '../../../../state/selectors/get_internal_is_intialized';
import { Dimensions } from '../../../../utils/dimensions';
import { LIGHT_THEME } from '../../../../utils/themes/light_theme';
import { Theme } from '../../../../utils/themes/theme';
import { nullShapeViewModel, ShapeViewModel } from '../../layout/types/viewmodel_types';
import { HeatmapSpec } from '../../specs/heatmap';
import { geometries } from '../../state/selectors/geometries';
import { getHeatmapContainerSizeSelector } from '../../state/selectors/get_heatmap_container_size';
import { getHeatmapSpecSelector } from '../../state/selectors/get_heatmap_spec';
import { renderCanvas2d } from './canvas_renderers';

interface ReactiveChartStateProps {
Expand All @@ -33,6 +37,8 @@ interface ReactiveChartStateProps {
chartContainerDimensions: Dimensions;
a11ySettings: A11ySettings;
background: Color;
heatmapTheme: Theme;
heatmapSpec?: HeatmapSpec;
}

interface ReactiveChartDispatchProps {
Expand Down Expand Up @@ -97,6 +103,8 @@ class Component extends React.Component<Props> {
config: { ...this.props.geometries.config, width, height },
},
this.props.background,
this.props.heatmapTheme,
this.props.heatmapSpec,
);
}
}
Expand Down Expand Up @@ -152,8 +160,8 @@ const DEFAULT_PROPS: ReactiveChartStateProps = {
},
a11ySettings: DEFAULT_A11Y_SETTINGS,
background: Colors.Transparent.keyword,
heatmapTheme: LIGHT_THEME,
};

const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
if (getInternalIsInitializedSelector(state) !== InitStatus.Initialized) {
return DEFAULT_PROPS;
Expand All @@ -162,8 +170,10 @@ const mapStateToProps = (state: GlobalChartState): ReactiveChartStateProps => {
initialized: true,
geometries: geometries(state),
chartContainerDimensions: getHeatmapContainerSizeSelector(state),
heatmapSpec: getHeatmapSpecSelector(state),
a11ySettings: getA11ySettingsSelector(state),
background: getChartThemeSelector(state).background.color,
heatmapTheme: getChartThemeSelector(state),
markov00 marked this conversation as resolved.
Show resolved Hide resolved
};
};

Expand Down
6 changes: 6 additions & 0 deletions packages/charts/src/chart_types/heatmap/specs/heatmap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ const defaultProps = {
xSortPredicate: Predicate.AlphaAsc,
ySortPredicate: Predicate.AlphaAsc,
config,
xAxisTitle: '',
yAxisTitle: '',
};

/** @public */
Expand Down Expand Up @@ -93,6 +95,8 @@ export interface HeatmapSpec extends Spec {
ySortPredicate: Predicate;
xScale: RasterTimeScale | OrdinalScale | LinearScale;
config: RecursivePartial<Config>;
xAxisTitle: string;
yAxisTitle: string;
highlightedData?: { x: Array<string | number>; y: Array<string | number> };
name?: string;
}
Expand All @@ -112,5 +116,7 @@ export const Heatmap: React.FunctionComponent<
| 'valueFormatter'
| 'config'
| 'xScale'
| 'xAxisTitle'
| 'yAxisTitle'
>(defaultProps),
);
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,15 @@ import { max as d3Max } from 'd3-array';
import { Box, measureText } from '../../../../common/text_utils';
import { GlobalChartState } from '../../../../state/chart_state';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { Position } from '../../../../utils/common';
import { Dimensions } from '../../../../utils/dimensions';
import { HeatmapCellDatum } from '../../layout/viewmodel/viewmodel';
import { getGridHeightParamsSelector } from './get_grid_full_height';
import { getHeatmapConfigSelector } from './get_heatmap_config';
import { getHeatmapSpecSelector } from './get_heatmap_spec';
import { getHeatmapTableSelector } from './get_heatmap_table';
import { getXAxisRightOverflow } from './get_x_axis_right_overflow';

Expand Down Expand Up @@ -45,6 +47,8 @@ export const computeChartDimensionsSelector = createCustomCachedSelector(
getXAxisRightOverflow,
getGridHeightParamsSelector,
markov00 marked this conversation as resolved.
Show resolved Hide resolved
getSettingsSpecSelector,
getHeatmapSpecSelector,
getChartThemeSelector,
],
(
chartContainerDimensions,
Expand All @@ -54,6 +58,8 @@ export const computeChartDimensionsSelector = createCustomCachedSelector(
rightOverflow,
{ height },
{ showLegend, legendPosition },
{ yAxisTitle },
{ axes: axesStyles },
): Dimensions => {
let { width, left } = chartContainerDimensions;
const { top } = chartContainerDimensions;
Expand All @@ -76,7 +82,9 @@ export const computeChartDimensionsSelector = createCustomCachedSelector(
...config.yAxisLabel,
};
});
// account for the space needed to show the yAxisTitle in the canvas element
const measuredYValues = textMeasure(config.yAxisLabel.fontSize, boxedYValues);
const titleWidth = yAxisTitle ? axesStyles.axisTitle.fontSize : 0;

let yColumnWidth: number = d3Max(measuredYValues, ({ width }) => width) ?? 0;
if (typeof config.yAxisLabel.width === 'number') {
Expand All @@ -85,8 +93,8 @@ export const computeChartDimensionsSelector = createCustomCachedSelector(
yColumnWidth = config.yAxisLabel.width.max;
}

width -= yColumnWidth + rightOverflow + totalHorizontalPadding;
left += yColumnWidth + totalHorizontalPadding;
width -= yColumnWidth + rightOverflow + totalHorizontalPadding + titleWidth;
left += yColumnWidth + totalHorizontalPadding + titleWidth;
}
let legendWidth = 0;
if (showLegend && (legendPosition === Position.Right || legendPosition === Position.Left)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@

import { GlobalChartState } from '../../../../state/chart_state';
import { createCustomCachedSelector } from '../../../../state/create_selector';
import { getChartThemeSelector } from '../../../../state/selectors/get_chart_theme';
import { getLegendSizeSelector } from '../../../../state/selectors/get_legend_size';
import { getSettingsSpecSelector } from '../../../../state/selectors/get_settings_specs';
import { isHorizontalLegend } from '../../../../utils/legend';
import { Config } from '../../layout/types/config_types';
import { getHeatmapConfigSelector } from './get_heatmap_config';
import { getHeatmapSpecSelector } from './get_heatmap_spec';
import { getHeatmapTableSelector } from './get_heatmap_table';

/** @internal */
Expand All @@ -31,21 +33,25 @@ export const getGridHeightParamsSelector = createCustomCachedSelector(
getParentDimension,
getHeatmapConfigSelector,
getHeatmapTableSelector,
getHeatmapSpecSelector,
getChartThemeSelector,
],
(
legendSize,
{ showLegend },
{ height: containerHeight },
{ xAxisLabel: { padding, visible, fontSize }, grid, maxLegendHeight },
{ yValues },
{ xAxisTitle },
{ axes: axesStyle },
): GridHeightParams => {
const xAxisHeight = visible ? fontSize : 0;
const totalVerticalPadding = padding * 2;
const titleHeight = xAxisTitle ? axesStyle.axisTitle.fontSize : 0;
let legendHeight = 0;
if (showLegend && isHorizontalLegend(legendSize.position)) {
legendHeight = maxLegendHeight ?? legendSize.height;
}
const verticalRemainingSpace = containerHeight - xAxisHeight - totalVerticalPadding - legendHeight;
const verticalRemainingSpace = containerHeight - xAxisHeight - padding - legendHeight - titleHeight;

// compute the grid cell height
const gridCellHeight = getGridCellHeight(yValues, grid, verticalRemainingSpace);
Expand Down
2 changes: 2 additions & 0 deletions packages/charts/src/mocks/specs/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ export class MockSeriesSpec {
xSortPredicate: Predicate.AlphaAsc,
ySortPredicate: Predicate.AlphaAsc,
config: {},
xAxisTitle: '',
yAxisTitle: '',
};

static bar(partial?: Partial<BarSeriesSpec>): BarSeriesSpec {
Expand Down
5 changes: 5 additions & 0 deletions storybook/stories/heatmap/1_basic.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export const Example = () => {

const persistCellsSelection = boolean('Persist cells selection', true);
const debugState = boolean('Enable debug state', true);
const showXAxisTitle = boolean('Show x axis title', false);
const showYAxisTitle = boolean('Show y axis title', false);

const dataStateAction = action('DataState');

const handler = useCallback(() => {
Expand Down Expand Up @@ -140,6 +143,8 @@ export const Example = () => {
xScale={{ type: ScaleType.Time, interval: DATA_6.interval }}
config={config}
highlightedData={persistCellsSelection ? selection : undefined}
xAxisTitle={showXAxisTitle ? 'Bottom axis' : undefined}
yAxisTitle={showYAxisTitle ? 'Left axis' : undefined}
/>
</Chart>
);
Expand Down
5 changes: 5 additions & 0 deletions storybook/stories/heatmap/2_categorical.story.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export const Example = () => {
const minCellHeight = number('min cell height', 10, { step: 1, min: 3, max: 8, range: true }, 'grid');
const maxCellHeight = number('max cell height', 30, { step: 1, min: 8, max: 45, range: true }, 'grid');

const showXAxisTitle = boolean('Show x axis title', false);
const showYAxisTitle = boolean('Show y axis title', false);

return (
<Chart>
<Settings
Expand Down Expand Up @@ -56,6 +59,8 @@ export const Example = () => {
valueAccessor={(d) => d[3]}
valueFormatter={(value) => value.toFixed(0.2)}
xSortPredicate="alphaAsc"
xAxisTitle={showXAxisTitle ? 'Bottom axis' : undefined}
yAxisTitle={showYAxisTitle ? 'Left axis' : undefined}
config={{
grid: {
stroke: {
Expand Down