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

fix(scales): bisect correctly on continuous scales #346

Merged
merged 5 commits into from
Aug 26, 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
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