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(tooltip): allow postfix banded area series #391

Merged
merged 6 commits into from
Oct 2, 2019
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
57 changes: 33 additions & 24 deletions src/chart_types/xy_chart/rendering/rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,20 @@ export interface GeometryId {
seriesKey: any[];
}

/**
* The accessor type
*/
export const AccessorType = Object.freeze({
Y0: 'y0' as 'y0',
Y1: 'y1' as 'y1',
});

export type AccessorType = typeof AccessorType.Y0 | typeof AccessorType.Y1;

export interface GeometryValue {
y: any;
x: any;
accessor: 'y1' | 'y0';
accessor: AccessorType;
}

/** Shared style properties for varies geometries */
Expand Down Expand Up @@ -187,19 +197,18 @@ export function renderPoints(
const isLogScale = isLogarithmicScale(yScale);
const pointGeometries = dataset.reduce(
(acc, datum) => {
const { x: xValue, y0, y1, initialY0, initialY1 } = datum;
// don't create the point if not within the xScale domain
if (!xScale.isValueInDomain(datum.x)) {
if (!xScale.isValueInDomain(xValue)) {
return acc;
}
const x = xScale.scale(datum.x);
const x = xScale.scale(xValue);
const points: PointGeometry[] = [];
const yDatums = [datum.y1];
if (hasY0Accessors) {
yDatums.unshift(datum.y0);
}
const yDatums = hasY0Accessors ? [y0, y1] : [y1];

yDatums.forEach((yDatum, index) => {
// skip rendering point if y1 is null
if (datum.y1 === null) {
if (y1 === null) {
return;
}
let y;
Expand All @@ -212,7 +221,7 @@ export function renderPoints(
} else {
y = yScale.scale(yDatum);
}
const originalY = hasY0Accessors && index === 0 ? datum.initialY0 : datum.initialY1;
const originalY = hasY0Accessors && index === 0 ? initialY0 : initialY1;
const geometryId = {
specId,
seriesKey,
Expand All @@ -224,9 +233,9 @@ export function renderPoints(
y,
color,
value: {
x: datum.x,
x: xValue,
y: originalY,
accessor: hasY0Accessors && index === 0 ? 'y0' : 'y1',
accessor: hasY0Accessors && index === 0 ? AccessorType.Y0 : AccessorType.Y1,
},
transform: {
x: shift,
Expand All @@ -235,7 +244,7 @@ export function renderPoints(
geometryId,
styleOverrides,
};
mutableIndexedGeometryMapUpsert(indexedGeometries, datum.x, pointGeometry);
mutableIndexedGeometryMapUpsert(indexedGeometries, xValue, pointGeometry);
// use the geometry only if the yDatum in contained in the current yScale domain
if (!isHidden && yScale.isValueInDomain(yDatum)) {
points.push(pointGeometry);
Expand Down Expand Up @@ -358,7 +367,7 @@ export function renderBars(
value: {
x: datum.x,
y: initialY1,
accessor: 'y1',
accessor: AccessorType.Y1,
},
geometryId,
seriesStyle,
Expand Down Expand Up @@ -395,10 +404,10 @@ export function renderLine(
const isLogScale = isLogarithmicScale(yScale);

const pathGenerator = line<DataSeriesDatum>()
.x((datum: DataSeriesDatum) => xScale.scale(datum.x) - xScaleOffset)
.y((datum: DataSeriesDatum) => yScale.scale(datum.y1))
.defined((datum: DataSeriesDatum) => {
return datum.y1 !== null && !(isLogScale && datum.y1 <= 0) && xScale.isValueInDomain(datum.x);
.x(({ x }) => xScale.scale(x) - xScaleOffset)
.y(({ y1 }) => yScale.scale(y1))
.defined(({ x, y1 }) => {
return y1 !== null && !(isLogScale && y1 <= 0) && xScale.isValueInDomain(x);
})
.curve(getCurveFactory(curve));
const y = 0;
Expand Down Expand Up @@ -458,16 +467,16 @@ export function renderArea(
const isLogScale = isLogarithmicScale(yScale);

const pathGenerator = area<DataSeriesDatum>()
.x((datum: DataSeriesDatum) => xScale.scale(datum.x) - xScaleOffset)
.y1((datum: DataSeriesDatum) => yScale.scale(datum.y1))
.y0((datum: DataSeriesDatum) => {
if (datum.y0 === null || (isLogScale && datum.y0 <= 0)) {
.x(({ x }) => xScale.scale(x) - xScaleOffset)
.y1(({ y1 }) => yScale.scale(y1))
.y0(({ y0 }) => {
if (y0 === null || (isLogScale && y0 <= 0)) {
return yScale.range[0];
}
return yScale.scale(datum.y0);
return yScale.scale(y0);
})
.defined((datum: DataSeriesDatum) => {
return datum.y1 !== null && !(isLogScale && datum.y1 <= 0) && xScale.isValueInDomain(datum.x);
.defined(({ y1, x }) => {
return y1 !== null && !(isLogScale && y1 <= 0) && xScale.isValueInDomain(x);
})
.curve(getCurveFactory(curve));

Expand Down
6 changes: 3 additions & 3 deletions src/chart_types/xy_chart/store/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { mergeYCustomDomainsByGroupId } from '../utils/axis_utils';
import { IndexedGeometry } from '../rendering/rendering';
import { IndexedGeometry, AccessorType } from '../rendering/rendering';
import { DataSeriesColorsValues, findDataSeriesByColorValues, getSeriesColorMap } from '../utils/series';
import {
AreaSeriesSpec,
Expand Down Expand Up @@ -1009,7 +1009,7 @@ describe('Chart State utils', () => {
x: 0,
y: 0,
color: '#1EA593',
value: { x: 0, y: 5, accessor: 'y1' },
value: { x: 0, y: 5, accessor: AccessorType.Y1 },
transform: { x: 0, y: 0 },
geometryId: { specId: getSpecId('line1'), seriesKey: [] },
},
Expand All @@ -1021,7 +1021,7 @@ describe('Chart State utils', () => {
x: 0,
y: 175.8,
color: '#2B70F7',
value: { x: 0, y: 2, accessor: 'y1' },
value: { x: 0, y: 2, accessor: AccessorType.Y1 },
transform: { x: 0, y: 0 },
geometryId: { specId: getSpecId('line2'), seriesKey: [] },
},
Expand Down
5 changes: 3 additions & 2 deletions src/chart_types/xy_chart/store/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
isLineSeriesSpec,
LineSeriesSpec,
Rotation,
isBandedSpec,
} from '../utils/specs';
import { ColorConfig, Theme } from '../../../utils/themes/theme';
import { identity, mergePartial } from '../../../utils/commons';
Expand Down Expand Up @@ -474,7 +475,7 @@ export function renderGeometries(
color,
(spec as LineSeriesSpec).curve || CurveType.LINEAR,
ds.specId,
Boolean(spec.y0Accessors),
isBandedSpec(spec.y0Accessors),
ds.key,
xScaleOffset,
lineSeriesStyle,
Expand All @@ -500,7 +501,7 @@ export function renderGeometries(
color,
(spec as AreaSeriesSpec).curve || CurveType.LINEAR,
ds.specId,
Boolean(spec.y0Accessors),
isBandedSpec(spec.y0Accessors),
ds.key,
xScaleOffset,
areaSeriesStyle,
Expand Down
93 changes: 93 additions & 0 deletions src/chart_types/xy_chart/tooltip/tooltip.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ describe('Tooltip formatting', () => {
yScaleType: ScaleType.Linear,
xScaleType: ScaleType.Linear,
};
const bandedSpec = {
...SPEC_1,
y0Accessors: [1],
};
const YAXIS_SPEC: AxisSpec = {
id: getAxisId('axis_1'),
groupId: SPEC_GROUP_ID_1,
Expand Down Expand Up @@ -63,6 +67,23 @@ describe('Tooltip formatting', () => {
},
seriesStyle,
};
const indexedBandedGeometry: BarGeometry = {
x: 0,
y: 0,
width: 0,
height: 0,
color: 'blue',
geometryId: {
specId: SPEC_ID_1,
seriesKey: [],
},
value: {
x: 1,
y: 10,
accessor: 'y1',
},
seriesStyle,
};

test('format simple tooltip', () => {
const tooltipValue = formatTooltip(indexedGeometry, SPEC_1, false, false, YAXIS_SPEC);
Expand All @@ -74,6 +95,78 @@ describe('Tooltip formatting', () => {
expect(tooltipValue.color).toBe('blue');
expect(tooltipValue.value).toBe('10');
});
test('format banded tooltip - upper', () => {
const tooltipValue = formatTooltip(indexedBandedGeometry, bandedSpec, false, false, YAXIS_SPEC);
expect(tooltipValue.name).toBe('bar_1 - upper');
});
test('format banded tooltip - y1AccessorFormat', () => {
const tooltipValue = formatTooltip(
indexedBandedGeometry,
{ ...bandedSpec, y1AccessorFormat: ' [max]' },
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.name).toBe('bar_1 [max]');
});
test('format banded tooltip - y1AccessorFormat as function', () => {
const tooltipValue = formatTooltip(
indexedBandedGeometry,
{ ...bandedSpec, y1AccessorFormat: (label) => `[max] ${label}` },
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.name).toBe('[max] bar_1');
});
test('format banded tooltip - lower', () => {
const tooltipValue = formatTooltip(
{
...indexedBandedGeometry,
value: {
...indexedBandedGeometry.value,
accessor: 'y0',
},
},
bandedSpec,
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.name).toBe('bar_1 - lower');
});
test('format banded tooltip - y0AccessorFormat', () => {
const tooltipValue = formatTooltip(
{
...indexedBandedGeometry,
value: {
...indexedBandedGeometry.value,
accessor: 'y0',
},
},
{ ...bandedSpec, y0AccessorFormat: ' [min]' },
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.name).toBe('bar_1 [min]');
});
test('format banded tooltip - y0AccessorFormat as function', () => {
const tooltipValue = formatTooltip(
{
...indexedBandedGeometry,
value: {
...indexedBandedGeometry.value,
accessor: 'y0',
},
},
{ ...bandedSpec, y0AccessorFormat: (label) => `[min] ${label}` },
false,
false,
YAXIS_SPEC,
);
expect(tooltipValue.name).toBe('[min] bar_1');
});
test('format tooltip with seriesKey name', () => {
const geometry: BarGeometry = {
...indexedGeometry,
Expand Down
29 changes: 15 additions & 14 deletions src/chart_types/xy_chart/tooltip/tooltip.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { TooltipValue, isFollowTooltipType, TooltipType, TooltipValueFormatter } from '../utils/interactions';
import { IndexedGeometry, isPointOnGeometry } from '../rendering/rendering';
import { IndexedGeometry, isPointOnGeometry, AccessorType } from '../rendering/rendering';
import { getColorValuesAsString } from '../utils/series';
import { AxisSpec, BasicSeriesSpec, Rotation } from '../utils/specs';
import { AxisSpec, BasicSeriesSpec, Rotation, isBandedSpec } from '../utils/specs';
import { SpecId, AxisId, GroupId } from '../../../utils/ids';
import { getAxesSpecForSpecId } from '../store/utils';
import { Scale } from '../../../utils/scales/scales';
import { Point } from '../store/chart_state';
import { getAccessorFormatLabel } from '../../../utils/accessor';

export function getSeriesTooltipValues(tooltipValues: TooltipValue[], defaultValue?: string): Map<string, any> {
// map from seriesKey to tooltipValue
Expand All @@ -25,30 +26,29 @@ export function getSeriesTooltipValues(tooltipValues: TooltipValue[], defaultVal
}

export function formatTooltip(
searchIndexValue: IndexedGeometry,
spec: BasicSeriesSpec,
{ color, value: { x, y, accessor }, geometryId: { seriesKey } }: IndexedGeometry,
{ id, name, y0AccessorFormat = ' - lower', y1AccessorFormat = ' - upper', y0Accessors }: BasicSeriesSpec,
isXValue: boolean,
isHighlighted: boolean,
axisSpec?: AxisSpec,
): TooltipValue {
const { id } = spec;
const {
color,
value: { x, y, accessor },
geometryId: { seriesKey },
} = searchIndexValue;
const seriesKeyAsString = getColorValuesAsString(seriesKey, id);
let name: string | undefined;
let displayName: string | undefined;
if (seriesKey.length > 0) {
name = seriesKey.join(' - ');
displayName = seriesKey.join(' - ');
} else {
name = spec.name || `${spec.id}`;
displayName = name || `${id}`;
}

if (isBandedSpec(y0Accessors)) {
const formatter = accessor === AccessorType.Y0 ? y0AccessorFormat : y1AccessorFormat;
displayName = getAccessorFormatLabel(formatter, displayName);
markov00 marked this conversation as resolved.
Show resolved Hide resolved
}

const value = isXValue ? x : y;
return {
seriesKey: seriesKeyAsString,
name,
name: displayName,
value: axisSpec ? axisSpec.tickFormat(value) : emptyFormatter(value),
color,
isHighlighted: isXValue ? false : isHighlighted,
Expand Down Expand Up @@ -136,6 +136,7 @@ export function getTooltipAndHighlightFromXValue(

return [...acc, formattedTooltip];
}, []);

return {
tooltipData,
highlightedGeometries,
Expand Down
18 changes: 17 additions & 1 deletion src/chart_types/xy_chart/utils/specs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
BarSeriesStyle,
PointStyle,
} from '../../../utils/themes/theme';
import { Accessor } from '../../../utils/accessor';
import { Accessor, AccessorFormat } from '../../../utils/accessor';
import { Omit, RecursivePartial } from '../../../utils/commons';
import { AnnotationId, AxisId, GroupId, SpecId } from '../../../utils/ids';
import { ScaleContinuousType, ScaleType } from '../../../utils/scales/scales';
Expand Down Expand Up @@ -108,6 +108,18 @@ export interface SeriesSpec {
/** Index per series to sort by */
sortIndex?: number;
displayValueSettings?: DisplayValueSpec;
/**
* Postfix string or accessor function for y1 accesor when using `y0Accessors`
*
* @default ' - upper'
*/
y0AccessorFormat?: AccessorFormat;
/**
* Postfix string or accessor function for y1 accesor when using `y0Accessors`
*
* @default ' - lower'
*/
y1AccessorFormat?: AccessorFormat;
}

export type CustomSeriesColorsMap = Map<DataSeriesColorsValues, string>;
Expand Down Expand Up @@ -413,3 +425,7 @@ export function isLineSeriesSpec(spec: BasicSeriesSpec): spec is LineSeriesSpec
export function isAreaSeriesSpec(spec: BasicSeriesSpec): spec is AreaSeriesSpec {
return spec.seriesType === 'area';
}

export function isBandedSpec(y0Accessors: SeriesAccessors['y0Accessors']): boolean {
return Boolean(y0Accessors && y0Accessors.length > 0);
}
Loading