Skip to content

Commit

Permalink
feat: add histogram mode (#218)
Browse files Browse the repository at this point in the history
  • Loading branch information
emmacunningham authored Jun 11, 2019
1 parent 91dbcb6 commit b418b67
Show file tree
Hide file tree
Showing 29 changed files with 978 additions and 100 deletions.
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ export { timeFormatter, niceTimeFormatter, niceTimeFormatByDay } from './utils/d
export { DataGenerator } from './utils/data_generators/data_generator';
export { DataSeriesColorsValues } from './lib/series/series';
export {
AnnotationDomainType,
AnnotationDomainTypes,
CustomSeriesColorsMap,
HistogramModeAlignment,
HistogramModeAlignments,
LineAnnotationDatum,
LineAnnotationSpec,
RectAnnotationDatum,
Expand Down
51 changes: 35 additions & 16 deletions src/lib/axes/axis_utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ import { XDomain } from '../series/domains/x_domain';
import { YDomain } from '../series/domains/y_domain';
import { AxisSpec, DomainRange, Position } from '../series/specs';
import { LIGHT_THEME } from '../themes/light_theme';
import { getAxisId, getGroupId, GroupId } from '../utils/ids';
import { AxisId, getAxisId, getGroupId, GroupId } from '../utils/ids';
import { ScaleType } from '../utils/scales/scales';
import {
AxisTick,
AxisTicksDimensions,
centerRotationOrigin,
computeAxisGridLinePositions,
computeAxisTicksDimensions,
Expand Down Expand Up @@ -65,8 +67,6 @@ describe('Axis computational utils', () => {
left: 0,
};
const axis1Dims = {
axisScaleType: ScaleType.Linear,
axisScaleDomain: [0, 1],
tickValues: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
tickLabels: ['0', '0.1', '0.2', '0.3', '0.4', '0.5', '0.6', '0.7', '0.8', '0.9', '1'],
maxLabelBboxWidth: 10,
Expand Down Expand Up @@ -223,7 +223,7 @@ describe('Axis computational utils', () => {

test('should compute available ticks', () => {
const scale = getScaleForAxisSpec(verticalAxisSpec, xDomain, [yDomain], 0, 0, 100, 0);
const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0);
const axisPositions = getAvailableTicks(verticalAxisSpec, scale!, 0, false);
const expectedAxisPositions = [
{ label: '0', position: 100, value: 0 },
{ label: '0.1', position: 90, value: 0.1 },
Expand All @@ -238,6 +238,19 @@ describe('Axis computational utils', () => {
{ label: '1', position: 0, value: 1 },
];
expect(axisPositions).toEqual(expectedAxisPositions);

// histogram mode axis ticks should add an additional tick
const xBandDomain: XDomain = {
type: 'xDomain',
scaleType: ScaleType.Linear,
domain: [0, 100],
isBandScale: true,
minInterval: 10,
};
const xScale = getScaleForAxisSpec(horizontalAxisSpec, xBandDomain, [yDomain], 1, 0, 100, 0);
const histogramAxisPositions = getAvailableTicks(horizontalAxisSpec, xScale!, 1, true);
const histogramTickLabels = histogramAxisPositions.map((tick: AxisTick) => tick.label);
expect(histogramTickLabels).toEqual(['0', '10', '20', '30', '40', '50', '60', '70', '80', '90', '100', '110']);
});
test('should compute visible ticks for a vertical axis', () => {
const allTicks = [
Expand Down Expand Up @@ -723,7 +736,7 @@ describe('Axis computational utils', () => {
test('should compute axis ticks positions with title', () => {
const chartRotation = 0;
const showLegend = false;

// validate assumptions for test
expect(verticalAxisSpec.id).toEqual(verticalAxisSpecWTitle.id);

Expand All @@ -743,6 +756,7 @@ describe('Axis computational utils', () => {
xDomain,
[yDomain],
1,
false,
);

let left = 12 + 5 + 10 + 10; // font size + title padding + chart margin left + label width
Expand All @@ -763,6 +777,7 @@ describe('Axis computational utils', () => {
xDomain,
[yDomain],
1,
false,
);

left = 0 + 10 + 10; // no title + chart margin left + label width
Expand Down Expand Up @@ -918,10 +933,10 @@ describe('Axis computational utils', () => {
const showLegend = true;
const leftLegendPosition = Position.Left;

const axisSpecs = new Map();
const axisSpecs = new Map<AxisId, AxisSpec>();
axisSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const axisDims = new Map();
const axisDims = new Map<AxisId, AxisTicksDimensions>();
axisDims.set(getAxisId('not_a_mapped_one'), axis1Dims);

const axisTicksPosition = getAxisTicksPositions(
Expand All @@ -934,6 +949,7 @@ describe('Axis computational utils', () => {
xDomain,
[yDomain],
1,
false,
leftLegendPosition,
);
expect(axisTicksPosition.axisPositions.size).toBe(0);
Expand All @@ -948,10 +964,10 @@ describe('Axis computational utils', () => {
const leftLegendPosition = Position.Left;
const topLegendPosition = Position.Top;

const axisSpecs = new Map();
const axisSpecs = new Map<AxisId, AxisSpec>();
axisSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const axisDims = new Map();
const axisDims = new Map<AxisId, AxisTicksDimensions>();
axisDims.set(verticalAxisSpec.id, axis1Dims);

const axisTicksPosition = getAxisTicksPositions(
Expand All @@ -964,6 +980,7 @@ describe('Axis computational utils', () => {
xDomain,
[yDomain],
1,
false,
leftLegendPosition,
);

Expand Down Expand Up @@ -995,6 +1012,7 @@ describe('Axis computational utils', () => {
xDomain,
[yDomain],
1,
false,
topLegendPosition,
);

Expand All @@ -1010,7 +1028,7 @@ describe('Axis computational utils', () => {
expect(verticalAxisWithTopLegendPosition).toEqual(expectedPositionWithTopLegend);

const ungroupedAxisSpec = { ...verticalAxisSpec, groupId: getGroupId('foo') };
const invalidSpecs = new Map();
const invalidSpecs = new Map<AxisId, AxisSpec>();
invalidSpecs.set(verticalAxisSpec.id, ungroupedAxisSpec);
const computeScalelessSpec = () => {
getAxisTicksPositions(
Expand All @@ -1023,6 +1041,7 @@ describe('Axis computational utils', () => {
xDomain,
[yDomain],
1,
false,
leftLegendPosition,
);
};
Expand Down Expand Up @@ -1073,7 +1092,7 @@ describe('Axis computational utils', () => {

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
const axesSpecs = new Map<AxisId, AxisSpec>();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

// Base case
Expand Down Expand Up @@ -1129,7 +1148,7 @@ describe('Axis computational utils', () => {

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
const axesSpecs = new Map<AxisId, AxisSpec>();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const axis2 = { ...verticalAxisSpec, id: getAxisId('axis2') };
Expand Down Expand Up @@ -1157,7 +1176,7 @@ describe('Axis computational utils', () => {

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
const axesSpecs = new Map<AxisId, AxisSpec>();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const axis2 = { ...verticalAxisSpec, id: getAxisId('axis2') };
Expand Down Expand Up @@ -1188,7 +1207,7 @@ describe('Axis computational utils', () => {

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
const axesSpecs = new Map<AxisId, AxisSpec>();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const axis2 = { ...verticalAxisSpec, id: getAxisId('axis2') };
Expand Down Expand Up @@ -1224,7 +1243,7 @@ describe('Axis computational utils', () => {

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
const axesSpecs = new Map<AxisId, AxisSpec>();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const axis2 = { ...verticalAxisSpec, id: getAxisId('axis2') };
Expand Down Expand Up @@ -1252,7 +1271,7 @@ describe('Axis computational utils', () => {

verticalAxisSpec.domain = domainRange1;

const axesSpecs = new Map();
const axesSpecs = new Map<AxisId, AxisSpec>();
axesSpecs.set(verticalAxisSpec.id, verticalAxisSpec);

const attemptToMerge = () => {
Expand Down
30 changes: 20 additions & 10 deletions src/lib/axes/axis_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ import {
} from '../series/specs';
import { AxisConfig, Theme } from '../themes/theme';
import { Dimensions, Margins } from '../utils/dimensions';
import { Domain } from '../utils/domain';
import { AxisId, GroupId } from '../utils/ids';
import { Scale, ScaleType } from '../utils/scales/scales';
import { Scale } from '../utils/scales/scales';
import { BBox, BBoxCalculator } from './bbox_calculator';

export type AxisLinePosition = [number, number, number, number];
Expand All @@ -27,8 +26,6 @@ export interface AxisTick {
}

export interface AxisTicksDimensions {
axisScaleType: ScaleType;
axisScaleDomain: Domain;
tickValues: string[] | number[];
tickLabels: string[];
maxLabelBboxWidth: number;
Expand Down Expand Up @@ -90,8 +87,6 @@ export function computeAxisTicksDimensions(
);

return {
axisScaleDomain: xDomain.domain,
axisScaleType: xDomain.scaleType,
...dimensions,
};
}
Expand Down Expand Up @@ -291,7 +286,7 @@ export function getTickLabelProps(
}

return {
x: tickPosition - maxLabelBboxWidth / 2,
x: (tickPosition - maxLabelBboxWidth / 2),
y: isAxisTop ? 0 : tickSize + tickPadding,
align,
verticalAlign,
Expand Down Expand Up @@ -393,10 +388,24 @@ export function getLeftAxisMinMaxRange(chartRotation: Rotation, height: number)
}
}

export function getAvailableTicks(axisSpec: AxisSpec, scale: Scale, totalBarsInCluster: number) {
export function getAvailableTicks(
axisSpec: AxisSpec,
scale: Scale,
totalBarsInCluster: number,
enableHistogramMode: boolean,
): AxisTick[] {
const ticks = scale.ticks();

if (enableHistogramMode && scale.bandwidth > 0) {
const finalTick = ticks[ticks.length - 1] + scale.minInterval;
ticks.push(finalTick);
}

const shift = totalBarsInCluster > 0 ? totalBarsInCluster : 1;
const offset = (scale.bandwidth * shift) / 2;

const band = scale.bandwidth / (1 - scale.barsPadding);
const halfPadding = (band - scale.bandwidth) / 2;
const offset = enableHistogramMode ? -halfPadding : (scale.bandwidth * shift) / 2;
return ticks.map((tick) => {
return {
value: tick,
Expand Down Expand Up @@ -507,6 +516,7 @@ export function getAxisTicksPositions(
xDomain: XDomain,
yDomain: YDomain[],
totalGroupsCount: number,
enableHistogramMode: boolean,
legendPosition?: Position,
barsPadding?: number,
) {
Expand Down Expand Up @@ -567,7 +577,7 @@ export function getAxisTicksPositions(
throw new Error(`Cannot compute scale for axis spec ${axisSpec.id}`);
}

const allTicks = getAvailableTicks(axisSpec, scale, totalGroupsCount);
const allTicks = getAvailableTicks(axisSpec, scale, totalGroupsCount, enableHistogramMode);
const visibleTicks = getVisibleTicks(allTicks, axisSpec, axisDim);

if (axisSpec.showGridLines) {
Expand Down
Loading

0 comments on commit b418b67

Please sign in to comment.