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(series): add simple mark formatter #775

Merged
merged 5 commits into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ module.exports = {
'jsx-a11y/click-events-have-key-events': 1,
'@typescript-eslint/member-ordering': 1,
eqeqeq: 1,
'unicorn/no-nested-ternary': 0,

/**
* Standard rules
Expand Down Expand Up @@ -249,7 +250,6 @@ module.exports = {
'unicorn/no-fn-reference-in-iterator': 0,
'unicorn/prefer-query-selector': 0,
'unicorn/no-for-loop': 0,
'unicorn/no-nested-ternary': 1,
'unicorn/no-reduce': 0,
'unicorn/no-useless-undefined': 0,
'unicorn/prefer-spread': 0,
Expand Down
9 changes: 6 additions & 3 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,9 @@ export interface BasePointerEvent {
export type BasicListener = () => undefined | void;

// @public (undocumented)
export type BasicSeriesSpec = SeriesSpec & SeriesAccessors & SeriesScales;
export type BasicSeriesSpec = SeriesSpec & SeriesAccessors & SeriesScales & {
markFormat?: TickFormatter<number>;
};

// Warning: (ae-missing-release-tag) "BinAgg" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
Expand Down Expand Up @@ -1516,7 +1518,7 @@ export interface Theme {
}

// @public (undocumented)
export type TickFormatter = (value: any, options?: TickFormatterOptions) => string;
export type TickFormatter<V = any> = (value: V, options?: TickFormatterOptions) => string;
monfera marked this conversation as resolved.
Show resolved Hide resolved

// @public (undocumented)
export type TickFormatterOptions = {
Expand Down Expand Up @@ -1574,11 +1576,12 @@ export type TooltipType = $Values<typeof TooltipType>;
// @public
export interface TooltipValue {
color: Color;
formattedMarkValue?: string | null;
formattedValue: string;
isHighlighted: boolean;
isVisible: boolean;
label: string;
markValue?: any;
markValue?: number | null;
monfera marked this conversation as resolved.
Show resolved Hide resolved
seriesIdentifier: SeriesIdentifier;
value: any;
valueAccessor?: Accessor;
Expand Down
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.
7 changes: 7 additions & 0 deletions integration/tests/interactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,5 +278,12 @@ describe('Interactions', () => {
{ left: 280, top: 80 },
);
});

it('should use custom mark formatters', async () => {
await common.expectChartWithMouseAtUrlToMatchScreenshot(
'http://localhost:9001/?path=/story/mixed-charts--mark-size-accessor',
{ left: 400, top: 80 },
);
});
});
});
39 changes: 39 additions & 0 deletions src/chart_types/xy_chart/tooltip/tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,4 +339,43 @@ describe('Tooltip formatting', () => {
expect(tooltipValue.value).toBe(1);
expect(tooltipValue.formattedValue).toBe('1');
});

describe('markFormat', () => {
const markFormat = jest.fn((d) => `${d} number`);
const markIndexedGeometry: BarGeometry = {
...indexedGeometry,
value: {
x: 1,
y: 10,
accessor: 'y1',
mark: 10,
},
};

it('should format mark value with markFormat', () => {
const tooltipValue = formatTooltip(
markIndexedGeometry,
{
...SPEC_1,
markFormat,
},
false,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.markValue).toBe(10);
expect(tooltipValue.formattedMarkValue).toBe('10 number');
expect(markFormat).toBeCalledWith(10, undefined);
});

it('should format mark value with defaultTickFormatter', () => {
const tooltipValue = formatTooltip(markIndexedGeometry, SPEC_1, false, false, false, YAXIS_SPEC);
expect(tooltipValue).toBeDefined();
expect(tooltipValue.markValue).toBe(10);
expect(tooltipValue.formattedMarkValue).toBe('10');
expect(markFormat).not.toBeCalled();
});
});
});
9 changes: 8 additions & 1 deletion src/chart_types/xy_chart/tooltip/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { LegendItemExtraValues } from '../../../commons/legend';
import { SeriesKey } from '../../../commons/series_id';
import { TooltipValue } from '../../../specs';
import { getAccessorFormatLabel } from '../../../utils/accessor';
import { isDefined } from '../../../utils/commons';
import { IndexedGeometry, BandedAccessorType } from '../../../utils/geometry';
import { defaultTickFormatter } from '../utils/axis_utils';
import { getSeriesName } from '../utils/series';
Expand Down Expand Up @@ -83,6 +84,7 @@ export function formatTooltip(
const isVisible = label === '' ? false : isFiltered;

const value = isHeader ? x : y;
const markValue = isHeader || mark === null ? null : mark;
const tickFormatOptions: TickFormatterOptions | undefined = spec.timeZone ? { timeZone: spec.timeZone } : undefined;
const tickFormatter =
(isHeader ? axisSpec?.tickFormat : spec.tickFormat ?? axisSpec?.tickFormat) ?? defaultTickFormatter;
Expand All @@ -93,7 +95,12 @@ export function formatTooltip(
label,
value,
formattedValue: tickFormatter(value, tickFormatOptions),
markValue: isHeader || mark === null ? null : mark,
markValue,
...(isDefined(markValue) && {
nickofthyme marked this conversation as resolved.
Show resolved Hide resolved
formattedMarkValue: spec.markFormat
? spec.markFormat(markValue, tickFormatOptions)
: defaultTickFormatter(markValue),
}),
color,
isHighlighted: isHeader ? false : isHighlighted,
isVisible,
Expand Down
19 changes: 16 additions & 3 deletions src/chart_types/xy_chart/utils/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,11 @@ export interface SeriesAccessors {
splitSeriesAccessors?: Accessor[];
/** An array of fields thats indicates the stack membership */
stackAccessors?: Accessor[];
/** Field name of mark size metric on `Datum` */
/**
* Field name of mark size metric on `Datum`
*
* Only used with line/area series
*/
markSizeAccessor?: Accessor | AccessorFn;
}

Expand Down Expand Up @@ -453,7 +457,16 @@ export interface SeriesScales {
}

/** @public */
export type BasicSeriesSpec = SeriesSpec & SeriesAccessors & SeriesScales;
export type BasicSeriesSpec = SeriesSpec &
SeriesAccessors &
SeriesScales & {
/**
* A function called to format every single mark value
*
* Only used with line/area series
*/
markFormat?: TickFormatter<number>;
};

export type SeriesSpecs<S extends BasicSeriesSpec = BasicSeriesSpec> = Array<S>;

Expand Down Expand Up @@ -661,7 +674,7 @@ export type TickFormatterOptions = {
};

/** @public */
export type TickFormatter = (value: any, options?: TickFormatterOptions) => string;
export type TickFormatter<V = any> = (value: V, options?: TickFormatterOptions) => string;

export const AnnotationTypes = Object.freeze({
Line: 'line' as const,
Expand Down
17 changes: 14 additions & 3 deletions src/components/tooltip/tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { getInternalTooltipAnchorPositionSelector } from '../../state/selectors/
import { getInternalTooltipInfoSelector } from '../../state/selectors/get_internal_tooltip_info';
import { getSettingsSpecSelector } from '../../state/selectors/get_settings_specs';
import { getTooltipHeaderFormatterSelector } from '../../state/selectors/get_tooltip_header_formatter';
import { Rotation } from '../../utils/commons';
import { Rotation, isDefined } from '../../utils/commons';
import { TooltipPortal, TooltipPortalSettings, AnchorPosition, Placement } from '../portal';
import { getTooltipSettings } from './get_tooltip_settings';
import { TooltipInfo, TooltipAnchorPosition } from './types';
Expand Down Expand Up @@ -102,12 +102,23 @@ const TooltipComponent = ({
<div className="echTooltip__list">
{values.map(
(
{ seriesIdentifier, valueAccessor, label, formattedValue, markValue, color, isHighlighted, isVisible },
{
seriesIdentifier,
valueAccessor,
label,
markValue,
formattedValue,
formattedMarkValue,
color,
isHighlighted,
isVisible,
},
index,
) => {
if (!isVisible) {
return null;
}

const classes = classNames('echTooltip__item', {
echTooltip__rowHighlighted: isHighlighted,
});
Expand All @@ -129,7 +140,7 @@ const TooltipComponent = ({
<div className="echTooltip__item--container">
<span className="echTooltip__label">{label}</span>
<span className="echTooltip__value">{formattedValue}</span>
{markValue && <span className="echTooltip__markValue">&nbsp;({markValue})</span>}
{isDefined(markValue) && <span className="echTooltip__markValue">&nbsp;({formattedMarkValue})</span>}
</div>
</div>
);
Expand Down
6 changes: 5 additions & 1 deletion src/specs/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,11 @@ export interface TooltipValue {
/**
* The mark value
*/
markValue?: any;
markValue?: number | null;
/**
* The mark value to display
*/
formattedMarkValue?: string | null;
/**
* The color of the graphic mark (by default the color of the series)
*/
Expand Down
8 changes: 6 additions & 2 deletions stories/mixed/7_marks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
*/

import { action } from '@storybook/addon-actions';
import { number, boolean } from '@storybook/addon-knobs';
import { number, boolean, text } from '@storybook/addon-knobs';
import numeral from 'numeral';
import React from 'react';

import { AreaSeries, Axis, Chart, LineSeries, Position, ScaleType, Settings } from '../../src';
Expand All @@ -33,7 +34,7 @@ const data1 = new Array(100).fill(0).map((_, x) => ({
const data2 = new Array(100).fill(0).map((_, x) => ({
x,
y: getRandomNumber(0, 100),
z: getRandomNumber(0, 50),
z: getRandomNumber(200, 500, 4),
}));

export const Example = () => {
Expand All @@ -54,6 +55,7 @@ export const Example = () => {
max: 100,
step: 10,
});
const markFormat = text('markFormat', '0.0');

return (
<Chart className="story-chart">
Expand Down Expand Up @@ -81,6 +83,7 @@ export const Example = () => {
yAccessors={['y']}
markSizeAccessor="z"
data={data1.slice(0, size)}
markFormat={(d) => `${numeral(d).format(markFormat)}%`}
/>
<LineSeries
id="line"
Expand All @@ -90,6 +93,7 @@ export const Example = () => {
yAccessors={['y']}
markSizeAccessor="z"
data={data2.slice(0, size)}
markFormat={(d) => `$${numeral(d).format(markFormat)}`}
/>
</Chart>
);
Expand Down