Skip to content

Commit

Permalink
fix(scales): bisect correctly on continuous scales (#346)
Browse files Browse the repository at this point in the history
This commit fix the wrong behaviour of the current invertWithStep implementation. It also fix the behaviour of cursor sync between charts with only lines and charts with only bars and empty intervals.
It also removes the annotation dependency on ticks

fix #227, fix #221
  • Loading branch information
markov00 authored Aug 26, 2019
1 parent 02a313b commit 5112208
Show file tree
Hide file tree
Showing 21 changed files with 535 additions and 525 deletions.
18 changes: 10 additions & 8 deletions .playground/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,24 @@
<style>
html,
body {
background: darkgrey !important;
background: blanchedalmond !important;
margin: 0;
padding: 0;
}
#root {
background: white;

position: absolute;
top: 100px;
left: 100px;
bottom: 100px;
right: 100px;
top: 10px;
left: 10px;
bottom: 10px;
right: 10px;
}
.chart {
background: white;
position: relative;
width: 100%;
height: 100%;
width: 800px;
height: 150px;
margin: 10px;
}
</style>
</head>
Expand Down
97 changes: 17 additions & 80 deletions .playground/playgroud.tsx
Original file line number Diff line number Diff line change
@@ -1,88 +1,25 @@
import React from 'react';

import {
Axis,
Chart,
getAxisId,
getSpecId,
niceTimeFormatter,
Position,
ScaleType,
Settings,
AreaSeries,
getGroupId,
} from '../src';
import { Axis, Chart, getAxisId, getSpecId, Position, ScaleType, Settings, BarSeries } from '../src';
import { KIBANA_METRICS } from '../src/utils/data_samples/test_dataset_kibana';
export class Playground extends React.Component {
ref1 = React.createRef<Chart>();
ref2 = React.createRef<Chart>();
ref3 = React.createRef<Chart>();

export class Playground extends React.Component {
render() {
return (
<Chart>
<Settings
tooltip={{ type: 'vertical' }}
debug={false}
legendPosition={Position.Right}
showLegend={true}
rotation={0}
/>
<Axis
id={getAxisId('timestamp')}
title="timestamp"
position={Position.Bottom}
tickFormat={niceTimeFormatter([1555819200000, 1555905600000])}
/>
<Axis id={getAxisId('A axis')} title="A" position={Position.Left} tickFormat={(d) => `GA: ${d.toFixed(2)}`} />
<Axis
id={getAxisId('B axis')}
groupId={getGroupId('aaa')}
title="B"
hide={true}
position={Position.Left}
tickFormat={(d) => `GB: ${d.toFixed(2)}`}
/>
<AreaSeries
id={getSpecId('dataset A1')}
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 50)}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
/>
<AreaSeries
id={getSpecId('dataset A2')}
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data.slice(0, 50)}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
/>
<AreaSeries
id={getSpecId('dataset B1')}
groupId={getGroupId('aaa')}
useDefaultGroupDomain={true}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 50).map((d) => [d[0], -d[1]])}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
/>
<AreaSeries
id={getSpecId('dataset B2')}
groupId={getGroupId('aaa')}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 50).map((d) => [d[0], -d[1]])}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
/>
</Chart>
<div className="chart">
<Chart>
<Settings showLegend={true} />
<Axis id={getAxisId('y')} position={Position.Left} />
<Axis id={getAxisId('x')} position={Position.Bottom} />
<BarSeries
id={getSpecId('bar')}
yScaleType={ScaleType.Linear}
xScaleType={ScaleType.Time}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 15)}
/>
</Chart>
</div>
);
}
}
52 changes: 26 additions & 26 deletions src/chart_types/xy_chart/annotations/annotation_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import {
getAnnotationLineTooltipTransform,
getAnnotationLineTooltipXOffset,
getAnnotationLineTooltipYOffset,
getNearestTick,
getRotatedCursor,
isBottomRectTooltip,
isRightRectTooltip,
Expand All @@ -56,7 +55,7 @@ describe('annotation utils', () => {
domain: continuousData,
range: [minRange, maxRange],
},
{ bandwidth: 0, minInterval: 1 },
{ bandwidth: 10, minInterval: 1 },
);

const ordinalData = ['a', 'b', 'c', 'd', 'a', 'b', 'c'];
Expand Down Expand Up @@ -291,6 +290,7 @@ describe('annotation utils', () => {
position: [0, 20, 20 + DEFAULT_LINE_OVERFLOW, 20],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [20, 0, 20, 20],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -386,9 +386,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [20, -DEFAULT_LINE_OVERFLOW, 20, 20],
position: [25, -DEFAULT_LINE_OVERFLOW, 25, 20],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [20, 0, 20, 20],
tooltipLinePosition: [25, 0, 25, 20],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -421,9 +422,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [20, 0, 20, 20],
position: [25, 0, 25, 20],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [20, 0, 20, 20],
tooltipLinePosition: [25, 0, 25, 20],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -456,9 +458,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [105, 0, 105, 20],
position: [110, 0, 110, 20],
details: { detailsText: 'foo', headerText: '10.5' },
tooltipLinePosition: [105, 0, 105, 20],
tooltipLinePosition: [110, 0, 110, 20],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -495,6 +498,7 @@ describe('annotation utils', () => {
position: [12.5, -DEFAULT_LINE_OVERFLOW, 12.5, 10],
details: { detailsText: 'foo', headerText: 'a' },
tooltipLinePosition: [0, 12.5, 10, 12.5],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -528,9 +532,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [20, -DEFAULT_LINE_OVERFLOW, 20, 10],
position: [25, -DEFAULT_LINE_OVERFLOW, 25, 10],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [0, 20, 10, 20],
tooltipLinePosition: [0, 25, 10, 25],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -564,9 +569,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [20, -DEFAULT_LINE_OVERFLOW, 20, 10],
position: [25, -DEFAULT_LINE_OVERFLOW, 25, 10],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [0, 0, 10, 0],
tooltipLinePosition: [0, -5, 10, -5],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -600,9 +606,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [20, -DEFAULT_LINE_OVERFLOW, 20, 20],
position: [25, -DEFAULT_LINE_OVERFLOW, 25, 20],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [20, 0, 20, 20],
tooltipLinePosition: [25, 0, 25, 20],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -635,9 +642,10 @@ describe('annotation utils', () => {
);
const expectedDimensions = [
{
position: [20, DEFAULT_LINE_OVERFLOW, 20, 20],
position: [25, DEFAULT_LINE_OVERFLOW, 25, 20],
details: { detailsText: 'foo', headerText: '2' },
tooltipLinePosition: [20, DEFAULT_LINE_OVERFLOW, 20, 20],
tooltipLinePosition: [25, DEFAULT_LINE_OVERFLOW, 25, 20],
marker: undefined,
},
];
expect(dimensions).toEqual(expectedDimensions);
Expand Down Expand Up @@ -1482,7 +1490,7 @@ describe('annotation utils', () => {
expect(scaleAndValidateDatum(0, continuousScale, false)).toBe(0);

// aligned with tick
expect(scaleAndValidateDatum(1.25, continuousScale, true)).toBe(10);
expect(scaleAndValidateDatum(1.25, continuousScale, true)).toBe(12.5);
});
test('should determine if a point is within a rectangle annotation', () => {
const cursorPosition = { x: 3, y: 4 };
Expand Down Expand Up @@ -1681,15 +1689,7 @@ describe('annotation utils', () => {
expect(getRotatedCursor(rawCursorPosition, chartDimensions, -90)).toEqual({ x: 18, y: 9 });
expect(getRotatedCursor(rawCursorPosition, chartDimensions, 180)).toEqual({ x: 9, y: 18 });
});
test('should get nearest tick', () => {
const ticks = [0, 1, 2];
expect(getNearestTick(0.25, [], 1)).toBeUndefined();
expect(getNearestTick(0.25, [100], 1)).toBeUndefined();
expect(getNearestTick(0.25, ticks, 1)).toBe(0);
expect(getNearestTick(0.75, ticks, 1)).toBe(1);
expect(getNearestTick(0.5, ticks, 1)).toBe(1);
expect(getNearestTick(1.75, ticks, 1)).toBe(2);
});

test('should compute cluster offset', () => {
const singleBarCluster = 1;
const multiBarCluster = 2;
Expand Down
38 changes: 1 addition & 37 deletions src/chart_types/xy_chart/annotations/annotation_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -323,44 +323,9 @@ export function computeLineAnnotationDimensions(
);
}

/**
* Used when we need to snap values to the nearest tick edge, this performs a binary search for the nearest tick
* @param dataValue - dataValue defined as an annotation cooordinate
* @param ticks - ticks from the scale
* @param minInterval - minInterva from the scale
*/
export function getNearestTick(dataValue: number, ticks: number[], minInterval: number): number | undefined {
if (ticks.length === 0) {
return;
}

if (ticks.length === 1) {
if (Math.abs(dataValue - ticks[0]) <= minInterval / 2) {
return ticks[0];
}
return;
}

const numTicks = ticks.length - 1;
const midIdx = Math.ceil(numTicks / 2);
const midPoint = ticks[midIdx];

if (Math.abs(dataValue - midPoint) <= minInterval / 2) {
return midPoint;
}

if (dataValue > midPoint) {
return getNearestTick(dataValue, ticks.slice(midIdx, ticks.length), minInterval);
}

return getNearestTick(dataValue, ticks.slice(0, midIdx), minInterval);
}

export function scaleAndValidateDatum(dataValue: any, scale: Scale, alignWithTick: boolean): any | null {
const isContinuous = scale.type !== ScaleType.Ordinal;
const value = isContinuous && alignWithTick ? getNearestTick(dataValue, scale.ticks(), scale.minInterval) : dataValue;
const scaledValue = scale.scale(value);

const scaledValue = scale.scale(dataValue);
// d3.scale will return 0 for '', rendering the line incorrectly at 0
if (isNaN(scaledValue) || (isContinuous && dataValue === '')) {
return null;
Expand Down Expand Up @@ -951,7 +916,6 @@ export function computeRectAnnotationTooltipState(
const endY = startY + rect.height;

const isWithinBounds = isWithinRectBounds(cursorPosition, { startX, endX, startY, endY });

if (isWithinBounds) {
annotationTooltipState.isVisible = true;
annotationTooltipState.details = details;
Expand Down
Loading

0 comments on commit 5112208

Please sign in to comment.