Skip to content

Commit

Permalink
fix: position tooltip within chart with single value xScale (opensear…
Browse files Browse the repository at this point in the history
  • Loading branch information
emmacunningham authored Jul 15, 2019
1 parent 0caf667 commit 6c6c2d7
Show file tree
Hide file tree
Showing 11 changed files with 190 additions and 1 deletion.
3 changes: 3 additions & 0 deletions packages/osd-charts/src/lib/utils/interactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ export function isCrosshairTooltipType(type: TooltipType) {
export function isFollowTooltipType(type: TooltipType) {
return type === TooltipType.Follow;
}
export function isNoneTooltipType(type: TooltipType) {
return type === TooltipType.None;
}

export function areIndexedGeometryArraysEquals(arr1: IndexedGeometry[], arr2: IndexedGeometry[]) {
if (arr1.length !== arr2.length) {
Expand Down
10 changes: 10 additions & 0 deletions packages/osd-charts/src/lib/utils/scales/scale_band.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,14 @@ describe('Scale Band', () => {
expect(scale.invert(99.99999)).toBe('d');
expect(scale.invert(100)).toBe('d');
});
describe('isSingleValue', () => {
it('should return true for single value scale', () => {
const scale = new ScaleBand(['a'], [0, 100]);
expect(scale.isSingleValue()).toBe(true);
});
it('should return false for multi value scale', () => {
const scale = new ScaleBand(['a', 'b'], [0, 100]);
expect(scale.isSingleValue()).toBe(false);
});
});
});
3 changes: 3 additions & 0 deletions packages/osd-charts/src/lib/utils/scales/scale_band.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export class ScaleBand implements Scale {
invertWithStep(value: any) {
return this.invertedScale(value);
}
isSingleValue() {
return this.domain.length < 2;
}
}

export function isOrdinalScale(scale: Scale): scale is ScaleBand {
Expand Down
15 changes: 15 additions & 0 deletions packages/osd-charts/src/lib/utils/scales/scale_continuous.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,21 @@ describe('Scale Continuous', () => {
expect(scaleLinear.invertWithStep(90, data)).toBe(90);
});

describe('isSingleValue', () => {
test('should return true for domain with fewer than 2 values', () => {
const scale = new ScaleContinuous(ScaleType.Linear, [], [0, 100]);
expect(scale.isSingleValue()).toBe(true);
});
test('should return true for domain with equal min and max values', () => {
const scale = new ScaleContinuous(ScaleType.Linear, [1, 1], [0, 100]);
expect(scale.isSingleValue()).toBe(true);
});
test('should return false for domain with differing min and max values', () => {
const scale = new ScaleContinuous(ScaleType.Linear, [1, 2], [0, 100]);
expect(scale.isSingleValue()).toBe(false);
});
});

describe('time ticks', () => {
const timezonesToTest = ['Asia/Tokyo', 'Europe/Berlin', 'UTC', 'America/New_York', 'America/Los_Angeles'];

Expand Down
9 changes: 9 additions & 0 deletions packages/osd-charts/src/lib/utils/scales/scale_continuous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,15 @@ export class ScaleContinuous implements Scale {
}
return prevValue;
}
isSingleValue() {
if (this.domain.length < 2) {
return true;
}

const min = this.domain[0];
const max = this.domain[this.domain.length - 1];
return max === min;
}
}

export function isContinuousScale(scale: Scale): scale is ScaleContinuous {
Expand Down
1 change: 1 addition & 0 deletions packages/osd-charts/src/lib/utils/scales/scales.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface Scale {
scale: (value: any) => number;
invert: (value: number) => any;
invertWithStep: (value: number, data: any[]) => any;
isSingleValue: () => boolean;
bandwidth: number;
minInterval: number;
type: ScaleType;
Expand Down
18 changes: 18 additions & 0 deletions packages/osd-charts/src/state/chart_state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -885,4 +885,22 @@ describe('Chart Store', () => {
expect(store.isCrosshairCursorVisible.get()).toBe(false);
});
});
test('should set tooltip type to follow when single value x scale', () => {
const singleValueSpec: BarSeriesSpec = {
id: SPEC_ID,
groupId: GROUP_ID,
seriesType: 'bar',
yScaleToDataExtent: false,
data: [{ x: 1, y: 1, g: 0 }],
xAccessor: 'x',
yAccessors: ['y'],
xScaleType: ScaleType.Linear,
yScaleType: ScaleType.Linear,
hideInLegend: false,
};

store.addSeriesSpec(singleValueSpec);
store.computeChart();
expect(store.tooltipType.get()).toBe(TooltipType.Follow);
});
});
10 changes: 10 additions & 0 deletions packages/osd-charts/src/state/chart_state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ import {
getValidYPosition,
isCrosshairTooltipType,
isFollowTooltipType,
isNoneTooltipType,
TooltipType,
TooltipValue,
TooltipValueFormatter,
Expand Down Expand Up @@ -301,11 +302,14 @@ export class ChartStore {
const updatedCursorLine = getCursorLinePosition(this.chartRotation, this.chartDimensions, this.cursorPosition);
Object.assign(this.cursorLinePosition, updatedCursorLine);

const isSingleValueXScale = this.xScale.isSingleValue();

this.tooltipPosition.transform = getTooltipPosition(
this.chartDimensions,
this.chartRotation,
this.cursorBandPosition,
this.cursorPosition,
isSingleValueXScale,
);

// get the elements on at this cursor position
Expand Down Expand Up @@ -873,6 +877,12 @@ export class ChartStore {
// console.log({ seriesGeometries });
this.geometries = seriesGeometries.geometries;
this.xScale = seriesGeometries.scales.xScale;

const isSingleValueXScale = this.xScale.isSingleValue();
if (isSingleValueXScale && !isNoneTooltipType(this.tooltipType.get())) {
this.tooltipType.set(TooltipType.Follow);
}

this.yScales = seriesGeometries.scales.yScales;
this.geometriesIndex = seriesGeometries.geometriesIndex;
this.geometriesIndexKeys = [...this.geometriesIndex.keys()].sort(compareByValueAsc);
Expand Down
18 changes: 18 additions & 0 deletions packages/osd-charts/src/state/crosshair_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,19 +122,22 @@ export function getTooltipPosition(
chartRotation: Rotation,
cursorBandPosition: Dimensions,
cursorPosition: { x: number; y: number },
isSingleValueXScale: boolean,
): string {
const isHorizontalRotated = isHorizontalRotation(chartRotation);
const hPosition = getHorizontalTooltipPosition(
cursorPosition.x,
cursorBandPosition,
chartDimensions,
isHorizontalRotated,
isSingleValueXScale,
);
const vPosition = getVerticalTooltipPosition(
cursorPosition.y,
cursorBandPosition,
chartDimensions,
isHorizontalRotated,
isSingleValueXScale,
);
const xTranslation = `translateX(${hPosition.position}px) translateX(-${hPosition.offset}%)`;
const yTranslation = `translateY(${vPosition.position}px) translateY(-${vPosition.offset}%)`;
Expand All @@ -146,9 +149,17 @@ export function getHorizontalTooltipPosition(
cursorBandPosition: Dimensions,
chartDimensions: Dimensions,
isHorizontalRotated: boolean,
isSingleValueXScale: boolean,
padding: number = 20,
): { offset: number; position: number } {
if (isHorizontalRotated) {
if (isSingleValueXScale) {
return {
offset: 0,
position: cursorBandPosition.left,
};
}

if (cursorXPosition <= chartDimensions.width / 2) {
return {
offset: 0,
Expand Down Expand Up @@ -180,6 +191,7 @@ export function getVerticalTooltipPosition(
cursorBandPosition: Dimensions,
chartDimensions: Dimensions,
isHorizontalRotated: boolean,
isSingleValueXScale: boolean,
padding: number = 20,
): {
offset: number;
Expand All @@ -198,6 +210,12 @@ export function getVerticalTooltipPosition(
};
}
} else {
if (isSingleValueXScale) {
return {
offset: 0,
position: cursorBandPosition.top,
};
}
if (cursorYPosition <= chartDimensions.height / 2) {
return {
offset: 0,
Expand Down
23 changes: 23 additions & 0 deletions packages/osd-charts/src/state/test/interactions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { ScaleContinuous } from '../../lib/utils/scales/scale_continuous';
import { ScaleType } from '../../lib/utils/scales/scales';
import { ChartStore } from '../chart_state';
import { computeSeriesDomains } from '../utils';
import { ScaleBand } from '../../lib/utils/scales/scale_band';

const SPEC_ID = getSpecId('spec_1');
const GROUP_ID = getGroupId('group_1');
Expand Down Expand Up @@ -369,4 +370,26 @@ function mouseOverTestSuite(scaleType: ScaleType) {
expect(onOverListener.mock.calls[0][0]).toEqual([indexedGeom2Blue.value]);
expect(onOutListener).toBeCalledTimes(0);
});

describe('can position tooltip within chart when xScale is a single value scale', () => {
beforeEach(() => {
const singleValueScale =
store.xScale!.type === ScaleType.Ordinal
? new ScaleBand(['a'], [0, 0])
: new ScaleContinuous(ScaleType.Linear, [1, 1], [0, 0]);
store.xScale = singleValueScale;
});
test('horizontal chart rotation', () => {
store.setCursorPosition(chartLeft + 99, chartTop + 99);
const expectedTransform = `translateX(${chartLeft}px) translateX(-0%) translateY(109px) translateY(-100%)`;
expect(store.tooltipPosition.transform).toBe(expectedTransform);
});

test('vertical chart rotation', () => {
store.chartRotation = 90;
store.setCursorPosition(chartLeft + 99, chartTop + 99);
const expectedTransform = `translateX(109px) translateX(-100%) translateY(${chartTop}px) translateY(-0%)`;
expect(store.tooltipPosition.transform).toBe(expectedTransform);
});
});
}
81 changes: 80 additions & 1 deletion packages/osd-charts/stories/bar_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -886,9 +886,39 @@ storiesOf('Bar Chart', module)
</Chart>
);
})
.add('single data chart', () => {
.add('single data chart [linear]', () => {
const hasCustomDomain = boolean('has custom domain', false);
const xDomain = hasCustomDomain
? {
min: 0,
}
: undefined;

const chartRotation = select<Rotation>(
'chartRotation',
{
'0 deg': 0,
'90 deg': 90,
'-90 deg': -90,
'180 deg': 180,
},
0,
);

const theme = {
scales: {
barsPadding: number('bars padding', 0.25, {
range: true,
min: 0,
max: 1,
step: 0.1,
}),
},
};

return (
<Chart className={'story-chart'}>
<Settings xDomain={xDomain} rotation={chartRotation} theme={theme} />
<Axis id={getAxisId('bottom')} position={Position.Bottom} title={'Bottom axis'} />
<Axis
id={getAxisId('left2')}
Expand All @@ -903,11 +933,60 @@ storiesOf('Bar Chart', module)
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
splitSeriesAccessors={['g']}
data={[{ x: 1, y: 10 }]}
/>
</Chart>
);
})
.add('single data chart [ordinal]', () => {
const hasCustomDomain = boolean('has custom domain', false);
const xDomain = hasCustomDomain ? ['a', 'b'] : undefined;

const chartRotation = select<Rotation>(
'chartRotation',
{
'0 deg': 0,
'90 deg': 90,
'-90 deg': -90,
'180 deg': 180,
},
0,
);

const theme = {
scales: {
barsPadding: number('bars padding', 0.25, {
range: true,
min: 0,
max: 1,
step: 0.1,
}),
},
};

return (
<Chart className={'story-chart'}>
<Settings xDomain={xDomain} rotation={chartRotation} theme={theme} />
<Axis id={getAxisId('bottom')} position={Position.Bottom} title={'Bottom axis'} />
<Axis
id={getAxisId('left2')}
title={'Left axis'}
position={Position.Left}
tickFormat={(d) => Number(d).toFixed(2)}
/>

<BarSeries
id={getSpecId('bars')}
xScaleType={ScaleType.Ordinal}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[{ x: 'a', y: 10, g: 1 }]}
/>
</Chart>
);
})
.add('single data clusterd chart', () => {
return (
<Chart className={'story-chart'}>
Expand Down

0 comments on commit 6c6c2d7

Please sign in to comment.