Skip to content

Commit

Permalink
fix(scale): return ticks in millis for time scales for line/area charts
Browse files Browse the repository at this point in the history
 The current implementation return Date objects as ticks for a time scale. Since the scale range is provided as millis, it's more consistent to provide it back as millis.

BREAKING CHANGE: The  props callback is called with millis instead of Date for axis on line or area only charts.
  • Loading branch information
markov00 committed Mar 6, 2019
1 parent c03a585 commit 8b46283
Show file tree
Hide file tree
Showing 8 changed files with 2,705 additions and 147 deletions.
1,406 changes: 1,406 additions & 0 deletions src/lib/series/utils/test_dataset_kibana.ts

Large diffs are not rendered by default.

908 changes: 908 additions & 0 deletions src/lib/series/utils/test_dataset_random.ts

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion src/lib/utils/scales/scale_continuous.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ export class ScaleContinuous implements Scale {
readonly domain: any[];
readonly range: number[];
readonly isInverted: boolean;
readonly tickValues: number[];
private readonly d3Scale: any;

constructor(
Expand Down Expand Up @@ -95,6 +96,13 @@ export class ScaleContinuous implements Scale {
this.range = range;
this.minInterval = minInterval || 0;
this.isInverted = this.domain[0] > this.domain[1];
if (type === ScaleType.Time) {
this.tickValues = this.d3Scale.ticks().map((d: Date) => {
return d.getTime();
});
} else {
this.tickValues = this.d3Scale.ticks();
}
}

scale(value: any) {
Expand All @@ -108,7 +116,7 @@ export class ScaleContinuous implements Scale {
return this.domain[0] + i * this.minInterval;
});
}
return this.d3Scale.ticks();
return this.tickValues;
}
invert(value: number) {
if (this.type === ScaleType.Time) {
Expand Down
23 changes: 23 additions & 0 deletions src/lib/utils/scales/scales.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { DateTime } from 'luxon';
import { limitLogScaleDomain } from './scale_continuous';
import { createContinuousScale, createOrdinalScale, ScaleType } from './scales';

Expand Down Expand Up @@ -37,6 +38,28 @@ describe('Scale Test', () => {
const scaledValue4 = linearScale.scale(10);
expect(scaledValue4).toBe(100);
});
test('Create an time scale', () => {
const date1 = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' }).toMillis();
const date2 = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' })
.plus({ days: 90 })
.toMillis();
const date3 = DateTime.fromISO('2019-01-01T00:00:00.000', { zone: 'utc' })
.plus({ days: 180 })
.toMillis();
const data = [date1, date3];
const minRange = 0;
const maxRange = 100;
const timeScale = createContinuousScale(ScaleType.Time, data, minRange, maxRange);
const { domain, range } = timeScale;
expect(domain).toEqual([date1, date3]);
expect(range).toEqual([minRange, maxRange]);
const scaledValue1 = timeScale.scale(date1);
expect(scaledValue1).toBe(0);
const scaledValue2 = timeScale.scale(date2);
expect(scaledValue2).toBe(50);
const scaledValue3 = timeScale.scale(date3);
expect(scaledValue3).toBe(100);
});
test('Create an log scale', () => {
const data = [1, 10];
const minRange = 0;
Expand Down
14 changes: 9 additions & 5 deletions src/utils/data/formatters.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { DateTime, Interval } from 'luxon';

type Formatter = (value: any) => string;
type TimeFormatter = (value: number) => string;

export function timeFormatter(format: string): Formatter {
return (value: any): string => {
export function timeFormatter(format: string): TimeFormatter {
return (value: number): string => {
return DateTime.fromMillis(value).toFormat(format);
};
}

export function niceTimeFormatter(domain: [number, number]): Formatter {
export function niceTimeFormatter(domain: [number, number]): TimeFormatter {
const minDate = DateTime.fromMillis(domain[0]);
const maxDate = DateTime.fromMillis(domain[1]);
const diff = Interval.fromDateTimes(minDate, maxDate);
const format = niceTimeFormat(diff);
return timeFormatter(format);
}

function niceTimeFormat(interval: Interval) {
export function niceTimeFormat(interval: Interval) {
const days = interval.count('days');
return niceTimeFormatByDay(days);
}

export function niceTimeFormatByDay(days: number) {
if (days > 30) {
return 'yyyy-MM-DD';
}
Expand Down
155 changes: 105 additions & 50 deletions stories/area_chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,21 @@ import {
ScaleType,
Settings,
} from '../src';
import { KIBANA_METRICS } from '../src/lib/series/utils/test_dataset_kibana';
import { timeFormatter } from '../src/utils/data/formatters';
const dateFormatter = timeFormatter('HH:mm');

storiesOf('Area Chart', module)
.add('basic', () => {
return (
<Chart renderer="canvas" className={'story-chart'}>
<AreaSeries
id={getSpecId('lines')}
xScaleType={ScaleType.Linear}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[{ x: 0, y: 2 }, { x: 1, y: 7 }, { x: 2, y: 3 }, { x: 3, y: 6 }]}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data}
yScaleToDataExtent={false}
/>
</Chart>
Expand All @@ -32,24 +35,25 @@ storiesOf('Area Chart', module)
<Chart renderer="canvas" className={'story-chart'}>
<Axis
id={getAxisId('bottom')}
title={'timestamp per 1 minute'}
position={Position.Bottom}
title={'Bottom axis'}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
id={getAxisId('left2')}
title={'Left axis'}
id={getAxisId('left')}
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
position={Position.Left}
tickFormat={(d) => Number(d).toFixed(2)}
/>

<AreaSeries
id={getSpecId('lines')}
xScaleType={ScaleType.Linear}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[{ x: 0, y: 2 }, { x: 1, y: 7 }, { x: 2, y: 3 }, { x: 3, y: 6 }]}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data}
yScaleToDataExtent={false}
/>
</Chart>
Expand All @@ -58,38 +62,40 @@ storiesOf('Area Chart', module)
.add('with 4 axes', () => {
return (
<Chart renderer="canvas" className={'story-chart'}>
<Settings debug={false} />
<Axis
id={getAxisId('bottom')}
title={'timestamp per 1 minute'}
position={Position.Bottom}
title={'bottom'}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
id={getAxisId('left')}
title={'left'}
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
position={Position.Left}
tickFormat={(d) => Number(d).toFixed(2)}
tickFormat={(d) => `${Number(d).toFixed(0)}%`}
/>
<Axis
id={getAxisId('top')}
position={Position.Top}
title={'top'}
showOverlappingTicks={true}
tickFormat={timeFormatter('HH:mm:ss')}
/>
<Axis
id={getAxisId('right')}
title={'right'}
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
position={Position.Right}
tickFormat={(d) => Number(d).toFixed(2)}
tickFormat={(d) => `${Number(d).toFixed(0)} %`}
/>

<AreaSeries
id={getSpecId('lines')}
xScaleType={ScaleType.Linear}
id={getSpecId(KIBANA_METRICS.metrics.kibana_os_load[0].metric.label)}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[{ x: 0, y: 2 }, { x: 1, y: 7 }, { x: 2, y: 3 }, { x: 3, y: 6 }]}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data}
yScaleToDataExtent={false}
/>
</Chart>
Expand All @@ -101,64 +107,113 @@ storiesOf('Area Chart', module)
<Settings showLegend={true} legendPosition={Position.Right} />
<Axis
id={getAxisId('bottom')}
title={'timestamp per 1 minute'}
position={Position.Bottom}
title={'Bottom axis'}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
id={getAxisId('left2')}
title={'Left axis'}
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
position={Position.Left}
tickFormat={(d) => Number(d).toFixed(2)}
/>

<AreaSeries
id={getSpecId('lines')}
xScaleType={ScaleType.Linear}
id={getSpecId(KIBANA_METRICS.metrics.kibana_os_load[0].metric.label)}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[{ x: 0, y: 2 }, { x: 1, y: 7 }, { x: 2, y: 3 }, { x: 3, y: 6 }]}
xAccessor={0}
yAccessors={[1]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data}
yScaleToDataExtent={false}
/>
</Chart>
);
})
.add('stacked w axis and legend', () => {
const data1 = KIBANA_METRICS.metrics.kibana_os_load[0].data.map((d) => {
return [...d, KIBANA_METRICS.metrics.kibana_os_load[0].metric.label];
});
const data2 = KIBANA_METRICS.metrics.kibana_os_load[1].data.map((d) => {
return [...d, KIBANA_METRICS.metrics.kibana_os_load[1].metric.label];
});
const data3 = KIBANA_METRICS.metrics.kibana_os_load[2].data.map((d) => {
return [...d, KIBANA_METRICS.metrics.kibana_os_load[2].metric.label];
});
const allMetrics = [...data3, ...data2, ...data1];
return (
<Chart renderer="canvas" className={'story-chart'}>
<Settings showLegend={true} legendPosition={Position.Right} />
<Axis
id={getAxisId('bottom')}
position={Position.Bottom}
title={'Bottom axis'}
title={'timestamp per 1 minute'}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
id={getAxisId('left2')}
title={'Left axis'}
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
position={Position.Left}
tickFormat={(d) => Number(d).toFixed(2)}
/>

<AreaSeries
id={getSpecId('lines')}
xScaleType={ScaleType.Linear}
id={getSpecId(KIBANA_METRICS.metrics.kibana_os_load[0].metric.label)}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
splitSeriesAccessors={[2]}
data={allMetrics}
yScaleToDataExtent={false}
/>
</Chart>
);
})
.add('stacked with separated specs', () => {
return (
<Chart renderer="canvas" className={'story-chart'}>
<Settings showLegend={true} legendPosition={Position.Right} />
<Axis
id={getAxisId('bottom')}
position={Position.Bottom}
title={'timestamp per 1 minute'}
showOverlappingTicks={true}
tickFormat={dateFormatter}
/>
<Axis
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
position={Position.Left}
tickFormat={(d) => Number(d).toFixed(2)}
/>
<AreaSeries
id={getSpecId(KIBANA_METRICS.metrics.kibana_os_load[2].metric.label)}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
data={KIBANA_METRICS.metrics.kibana_os_load[2].data}
yScaleToDataExtent={false}
/>
<AreaSeries
id={getSpecId(KIBANA_METRICS.metrics.kibana_os_load[1].metric.label)}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
data={KIBANA_METRICS.metrics.kibana_os_load[1].data}
yScaleToDataExtent={false}
/>
<AreaSeries
id={getSpecId(KIBANA_METRICS.metrics.kibana_os_load[0].metric.label)}
xScaleType={ScaleType.Time}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
stackAccessors={['x']}
splitSeriesAccessors={['g']}
data={[
{ x: 0, y: 2, g: 'a' },
{ x: 1, y: 7, g: 'a' },
{ x: 2, y: 3, g: 'a' },
{ x: 3, y: 6, g: 'a' },
{ x: 0, y: 4, g: 'b' },
{ x: 1, y: 5, g: 'b' },
{ x: 2, y: 8, g: 'b' },
{ x: 3, y: 2, g: 'b' },
]}
xAccessor={0}
yAccessors={[1]}
stackAccessors={[0]}
data={KIBANA_METRICS.metrics.kibana_os_load[0].data}
yScaleToDataExtent={false}
/>
</Chart>
Expand Down
Loading

0 comments on commit 8b46283

Please sign in to comment.