Skip to content

Commit af92736

Browse files
emmacunninghammarkov00
authored andcommitted
fix(ticks): fill in additional ticks for histogram (#251)
1 parent 1bfb430 commit af92736

File tree

3 files changed

+151
-33
lines changed

3 files changed

+151
-33
lines changed

src/lib/axes/axis_utils.test.ts

Lines changed: 84 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -206,36 +206,90 @@ describe('Axis computational utils', () => {
206206
expect(xScale).toBeDefined();
207207
});
208208

209-
test('should compute available ticks', () => {
210-
const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0);
211-
const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0, false);
212-
const expectedAxisPositions = [
213-
{ label: '0', position: 100, value: 0 },
214-
{ label: '0.1', position: 90, value: 0.1 },
215-
{ label: '0.2', position: 80, value: 0.2 },
216-
{ label: '0.3', position: 70, value: 0.3 },
217-
{ label: '0.4', position: 60, value: 0.4 },
218-
{ label: '0.5', position: 50, value: 0.5 },
219-
{ label: '0.6', position: 40, value: 0.6 },
220-
{ label: '0.7', position: 30, value: 0.7 },
221-
{ label: '0.8', position: 20, value: 0.8 },
222-
{ label: '0.9', position: 10, value: 0.9 },
223-
{ label: '1', position: 0, value: 1 },
224-
];
225-
expect(axisPositions).toEqual(expectedAxisPositions);
226-
227-
// histogram mode axis ticks should add an additional tick
228-
const xBandDomain: XDomain = {
229-
type: 'xDomain',
230-
scaleType: ScaleType.Linear,
231-
domain: [0, 100],
232-
isBandScale: true,
233-
minInterval: 10,
234-
};
235-
const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0);
236-
const histogramAxisPositions = getAvailableTicks(horizontalAxisSpec, xScale!, 1, true);
237-
const histogramTickLabels = histogramAxisPositions.map((tick: AxisTick) => tick.label);
238-
expect(histogramTickLabels).toEqual(['0', '10', '20', '30', '40', '50', '60', '70', '80', '90', '100', '110']);
209+
describe('getAvailableTicks', () => {
210+
test('should compute to end of domain when histogram mode not enabled', () => {
211+
const enableHistogramMode = false;
212+
const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0);
213+
const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0, enableHistogramMode);
214+
const expectedAxisPositions = [
215+
{ label: '0', position: 100, value: 0 },
216+
{ label: '0.1', position: 90, value: 0.1 },
217+
{ label: '0.2', position: 80, value: 0.2 },
218+
{ label: '0.3', position: 70, value: 0.3 },
219+
{ label: '0.4', position: 60, value: 0.4 },
220+
{ label: '0.5', position: 50, value: 0.5 },
221+
{ label: '0.6', position: 40, value: 0.6 },
222+
{ label: '0.7', position: 30, value: 0.7 },
223+
{ label: '0.8', position: 20, value: 0.8 },
224+
{ label: '0.9', position: 10, value: 0.9 },
225+
{ label: '1', position: 0, value: 1 },
226+
];
227+
expect(axisPositions).toEqual(expectedAxisPositions);
228+
});
229+
230+
test('should extend ticks to domain + minInterval in histogram mode for linear scale', () => {
231+
const enableHistogramMode = true;
232+
const xBandDomain: XDomain = {
233+
type: 'xDomain',
234+
scaleType: ScaleType.Linear,
235+
domain: [0, 100],
236+
isBandScale: true,
237+
minInterval: 10,
238+
};
239+
const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0);
240+
const histogramAxisPositions = getAvailableTicks(horizontalAxisSpec, xScale!, 1, enableHistogramMode);
241+
const histogramTickLabels = histogramAxisPositions.map(({ label }: AxisTick) => label);
242+
expect(histogramTickLabels).toEqual(['0', '10', '20', '30', '40', '50', '60', '70', '80', '90', '100', '110']);
243+
});
244+
245+
test('should extend ticks to domain + minInterval in histogram mode for time scale', () => {
246+
const enableHistogramMode = true;
247+
const xBandDomain: XDomain = {
248+
type: 'xDomain',
249+
scaleType: ScaleType.Time,
250+
domain: [1560438420000, 1560438510000],
251+
isBandScale: true,
252+
minInterval: 90000,
253+
};
254+
const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0);
255+
const histogramAxisPositions = getAvailableTicks(horizontalAxisSpec, xScale!, 1, enableHistogramMode);
256+
const histogramTickValues = histogramAxisPositions.map(({ value }: AxisTick) => value);
257+
258+
const expectedTickValues = [
259+
1560438420000,
260+
1560438435000,
261+
1560438450000,
262+
1560438465000,
263+
1560438480000,
264+
1560438495000,
265+
1560438510000,
266+
1560438525000,
267+
1560438540000,
268+
1560438555000,
269+
1560438570000,
270+
1560438585000,
271+
1560438600000,
272+
];
273+
274+
expect(histogramTickValues).toEqual(expectedTickValues);
275+
});
276+
277+
test('should extend ticks to domain + minInterval in histogram mode for a scale with single datum', () => {
278+
const enableHistogramMode = true;
279+
const xBandDomain: XDomain = {
280+
type: 'xDomain',
281+
scaleType: ScaleType.Time,
282+
domain: [1560438420000, 1560438420000], // a single datum scale will have the same value for domain start & end
283+
isBandScale: true,
284+
minInterval: 90000,
285+
};
286+
const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0);
287+
const histogramAxisPositions = getAvailableTicks(horizontalAxisSpec, xScale!, 1, enableHistogramMode);
288+
const histogramTickValues = histogramAxisPositions.map(({ value }: AxisTick) => value);
289+
const expectedTickValues = [1560438420000, 1560438510000];
290+
291+
expect(histogramTickValues).toEqual(expectedTickValues);
292+
});
239293
});
240294
test('should compute visible ticks for a vertical axis', () => {
241295
const allTicks = [

src/lib/axes/axis_utils.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -383,17 +383,47 @@ export function getAvailableTicks(
383383
enableHistogramMode: boolean,
384384
): AxisTick[] {
385385
const ticks = scale.ticks();
386+
const isSingleValueScale = scale.domain[0] - scale.domain[1] === 0;
387+
const hasAdditionalTicks = enableHistogramMode && scale.bandwidth > 0;
386388

387-
if (enableHistogramMode && scale.bandwidth > 0) {
388-
const finalTick = ticks[ticks.length - 1] + scale.minInterval;
389-
ticks.push(finalTick);
389+
if (hasAdditionalTicks) {
390+
const lastComputedTick = ticks[ticks.length - 1];
391+
392+
if (!isSingleValueScale) {
393+
const penultimateComputedTick = ticks[ticks.length - 2];
394+
const computedTickDistance = lastComputedTick - penultimateComputedTick;
395+
const numTicks = scale.minInterval / computedTickDistance;
396+
397+
for (let i = 1; i <= numTicks; i++) {
398+
ticks.push(i * computedTickDistance + lastComputedTick);
399+
}
400+
}
390401
}
391402

392403
const shift = totalBarsInCluster > 0 ? totalBarsInCluster : 1;
393404

394405
const band = scale.bandwidth / (1 - scale.barsPadding);
395406
const halfPadding = (band - scale.bandwidth) / 2;
396407
const offset = enableHistogramMode ? -halfPadding : (scale.bandwidth * shift) / 2;
408+
409+
if (isSingleValueScale && hasAdditionalTicks) {
410+
const firstTickValue = ticks[0];
411+
const firstTick = {
412+
value: firstTickValue,
413+
label: axisSpec.tickFormat(firstTickValue),
414+
position: scale.scale(firstTickValue) + offset,
415+
};
416+
417+
const lastTickValue = firstTickValue + scale.minInterval;
418+
const lastTick = {
419+
value: lastTickValue,
420+
label: axisSpec.tickFormat(lastTickValue),
421+
position: scale.bandwidth + halfPadding * 2,
422+
};
423+
424+
return [firstTick, lastTick];
425+
}
426+
397427
return ticks.map((tick) => {
398428
return {
399429
value: tick,
@@ -433,6 +463,7 @@ export function getVisibleTicks(allTicks: AxisTick[], axisSpec: AxisSpec, axisDi
433463
}
434464
}
435465
}
466+
436467
return visibleTicks;
437468
}
438469

stories/bar_chart.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1608,6 +1608,39 @@ storiesOf('Bar Chart', module)
16081608
</Chart>
16091609
);
16101610
})
1611+
.add('[test] single histogram bar chart', () => {
1612+
const formatter = timeFormatter(niceTimeFormatByDay(1));
1613+
1614+
const xDomain = {
1615+
minInterval: 60000,
1616+
};
1617+
1618+
return (
1619+
<Chart className={'story-chart'}>
1620+
<Settings xDomain={xDomain} />
1621+
<Axis
1622+
id={getAxisId('bottom')}
1623+
title={'timestamp per 1 minute'}
1624+
position={Position.Bottom}
1625+
showOverlappingTicks={true}
1626+
tickFormat={formatter}
1627+
/>
1628+
<Axis
1629+
id={getAxisId('left')}
1630+
title={KIBANA_METRICS.metrics.kibana_os_load[0].metric.title}
1631+
position={Position.Left}
1632+
/>
1633+
<HistogramBarSeries
1634+
id={getSpecId('bars')}
1635+
xScaleType={ScaleType.Linear}
1636+
yScaleType={ScaleType.Linear}
1637+
xAccessor={0}
1638+
yAccessors={[1]}
1639+
data={KIBANA_METRICS.metrics.kibana_os_load[0].data.slice(0, 1)}
1640+
/>
1641+
</Chart>
1642+
);
1643+
})
16111644
.add('stacked only grouped areas', () => {
16121645
const data1 = [[1, 2], [2, 2], [3, 3], [4, 5], [5, 5], [6, 3], [7, 8], [8, 2], [9, 1]];
16131646
const data2 = [[1, 1], [2, 2], [3, 3], [4, 4], [5, 5], [6, 4], [7, 3], [8, 2], [9, 4]];

0 commit comments

Comments
 (0)