Skip to content

Commit

Permalink
feat(brush): histogram brushing last values and rounding (#801)
Browse files Browse the repository at this point in the history
  • Loading branch information
nickofthyme authored Sep 4, 2020
1 parent 8a0fb97 commit 6d0319f
Show file tree
Hide file tree
Showing 8 changed files with 171 additions and 29 deletions.
2 changes: 2 additions & 0 deletions api/charts.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1292,6 +1292,7 @@ export const Settings: React.FunctionComponent<SettingsSpecProps>;

// @public
export interface SettingsSpec extends Spec {
allowBrushingLastHistogramBucket?: boolean;
// (undocumented)
animateData: boolean;
baseTheme?: Theme;
Expand Down Expand Up @@ -1339,6 +1340,7 @@ export interface SettingsSpec extends Spec {
resizeDebounce?: number;
// (undocumented)
rotation: Rotation;
roundHistogramBrushValues?: boolean;
// (undocumented)
showLegend: boolean;
showLegendExtra: boolean;
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 22 additions & 4 deletions src/chart_types/xy_chart/state/selectors/on_brush_end_caller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,14 @@ export function createOnBrushEndCaller(): (state: GlobalChartState) => void {
],
(
lastDrag,
{ onBrushEnd, rotation, brushAxis, minBrushDelta },
{
onBrushEnd,
rotation,
brushAxis,
minBrushDelta,
roundHistogramBrushValues,
allowBrushingLastHistogramBucket,
},
computedScales,
{ chartDimensions },
histogramMode,
Expand All @@ -111,6 +118,8 @@ export function createOnBrushEndCaller(): (state: GlobalChartState) => void {
histogramMode,
xScale,
minBrushDelta,
roundHistogramBrushValues,
allowBrushingLastHistogramBucket,
);
}
if (brushAxis === BrushAxis.Y || brushAxis === BrushAxis.Both) {
Expand Down Expand Up @@ -140,6 +149,8 @@ function getXBrushExtent(
histogramMode: boolean,
xScale: Scale,
minBrushDelta?: number,
roundHistogramBrushValues?: boolean,
allowBrushingLastHistogramBucket?: boolean,
): [number, number] | undefined {
let startPos = getLeftPoint(chartDimensions, lastDrag.start.position);
let endPos = getLeftPoint(chartDimensions, lastDrag.end.position);
Expand All @@ -163,10 +174,17 @@ function getXBrushExtent(
}

const offset = histogramMode ? 0 : -(xScale.bandwidth + xScale.bandwidthPadding) / 2;
const minPosScaled = xScale.invert(minPos + offset);
const maxPosScaled = xScale.invert(maxPos + offset);
const invertValue = roundHistogramBrushValues
? (value: number) => xScale.invertWithStep(value, xScale.domain)?.value
: (value: number) => xScale.invert(value);
const minPosScaled = invertValue(minPos + offset);
const maxPosScaled = invertValue(maxPos + offset);

const maxDomainValue = xScale.domain[1] + (allowBrushingLastHistogramBucket ? xScale.minInterval : 0);

const minValue = minValueWithLowerLimit(minPosScaled, maxPosScaled, xScale.domain[0]);
const maxValue = maxValueWithUpperLimit(minPosScaled, maxPosScaled, xScale.domain[1]);
const maxValue = maxValueWithUpperLimit(minPosScaled, maxPosScaled, maxDomainValue);

return [minValue, maxValue];
}

Expand Down
24 changes: 24 additions & 0 deletions src/specs/settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,30 @@ export interface SettingsSpec extends Spec {
* @defaultValue 2
*/
minBrushDelta?: number;
/**
* Boolean to round brushed values to nearest step bounds.
*
* e.g.
* A brush selection range of [1.23, 3.6] with a domain of [1, 2, 3, 4].
*
* - when true returns [1, 3]
* - when false returns [1.23, 3.6]
*
* @defaultValue false
*/
roundHistogramBrushValues?: boolean;
/**
* Boolean to allow brushing on last bucket even when outside domain or limit to end of domain.
*
* e.g.
* A brush selection range of [1.23, 3.6] with a domain of [1, 2, 3]
*
* - when true returns [1.23, 3.6]
* - when false returns [1.23, 3]
*
* @defaultValue false
*/
allowBrushingLastHistogramBucket?: boolean;
}

export type DefaultSettingsProps =
Expand Down
70 changes: 49 additions & 21 deletions stories/interactions/10_brush_selection_bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,27 +21,55 @@ import { action } from '@storybook/addon-actions';
import React from 'react';

import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '../../src';
import { isVerticalRotation } from '../../src/chart_types/xy_chart/state/utils/common';
import { getChartRotationKnob } from '../utils/knobs';

export const Example = () => (
<Chart className="story-chart">
<Settings onBrushEnd={action('onBrushEnd')} rotation={getChartRotationKnob()} />
<Axis id="bottom" position={Position.Bottom} title="bottom" showOverlappingTicks />
<Axis id="left" title="left" position={Position.Left} tickFormat={(d) => Number(d).toFixed(2)} />
<Axis id="top" position={Position.Top} title="top" showOverlappingTicks />
<Axis id="right" title="right" position={Position.Right} tickFormat={(d) => Number(d).toFixed(2)} />
export const Example = () => {
const rotation = getChartRotationKnob();
const isVertical = isVerticalRotation(rotation);

<BarSeries
id="lines"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 1, y: 2 },
{ x: 2, y: 7 },
{ x: 3, y: 3 },
]}
/>
</Chart>
);
return (
<Chart className="story-chart">
<Settings onBrushEnd={action('onBrushEnd')} rotation={rotation} />
<Axis
id="bottom"
position={Position.Bottom}
title="bottom"
showOverlappingTicks
tickFormat={isVertical ? (d) => Number(d).toFixed(2) : undefined}
/>
<Axis
id="left"
title="left"
position={Position.Left}
tickFormat={!isVertical ? (d) => Number(d).toFixed(2) : undefined}
/>
<Axis
id="top"
position={Position.Top}
title="top"
showOverlappingTicks
tickFormat={isVertical ? (d) => Number(d).toFixed(2) : undefined}
/>
<Axis
id="right"
title="right"
position={Position.Right}
tickFormat={!isVertical ? (d) => Number(d).toFixed(2) : undefined}
/>

<BarSeries
id="lines"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
yAccessors={['y']}
data={[
{ x: 1, y: 2 },
{ x: 2, y: 7 },
{ x: 3, y: 3 },
]}
/>
</Chart>
);
};
56 changes: 56 additions & 0 deletions stories/interactions/10a_brush_selection_bar_hist.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Licensed to Elasticsearch B.V. under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch B.V. licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

import { action } from '@storybook/addon-actions';
import { boolean } from '@storybook/addon-knobs';
import React from 'react';

import { Axis, BarSeries, Chart, Position, ScaleType, Settings } from '../../src';
import { getChartRotationKnob } from '../utils/knobs';

export const Example = () => (
<Chart className="story-chart">
<Settings
onBrushEnd={action('onBrushEnd')}
rotation={getChartRotationKnob()}
roundHistogramBrushValues={boolean('roundHistogramBrushValues', false)}
allowBrushingLastHistogramBucket={boolean('allowBrushingLastHistogramBucket', false)}
/>
<Axis id="bottom" position={Position.Bottom} title="bottom" showOverlappingTicks />
<Axis id="left" title="left" position={Position.Left} tickFormat={(d) => Number(d).toFixed(2)} />
<Axis id="top" position={Position.Top} title="top" showOverlappingTicks />
<Axis id="right" title="right" position={Position.Right} tickFormat={(d) => Number(d).toFixed(2)} />

<BarSeries
id="lines"
xScaleType={ScaleType.Linear}
yScaleType={ScaleType.Linear}
xAccessor="x"
enableHistogramMode
yAccessors={['y']}
data={[
{ x: 1, y: 2 },
{ x: 2, y: 7 },
{ x: 3, y: 8 },
{ x: 4, y: 9 },
{ x: 5, y: 3 },
]}
/>
</Chart>
);
21 changes: 17 additions & 4 deletions stories/interactions/12_brush_time_hist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,19 @@ import { DateTime } from 'luxon';
import React from 'react';

import { Axis, Chart, niceTimeFormatter, Position, ScaleType, Settings, HistogramBarSeries } from '../../src';
import { isVerticalRotation } from '../../src/chart_types/xy_chart/state/utils/common';
import { getChartRotationKnob } from '../utils/knobs';

export const Example = () => {
const rotation = getChartRotationKnob();
const isVertical = isVerticalRotation(rotation);
const now = DateTime.fromISO('2019-01-11T00:00:00.000')
.setZone('utc+1')
.toMillis();
const oneDay = 1000 * 60 * 60 * 24;
const formatter = niceTimeFormatter([now, now + oneDay * 5]);
const dateFormatter = niceTimeFormatter([now, now + oneDay * 5]);
const numberFormatter = (d: any) => Number(d).toFixed(2);

return (
<Chart className="story-chart">
<Settings
Expand All @@ -39,13 +44,21 @@ export const Example = () => {
if (!x) {
return;
}
action('onBrushEnd')(formatter(x[0]), formatter(x[1]));
action('onBrushEnd')(dateFormatter(x[0]), dateFormatter(x[1]));
}}
onElementClick={action('onElementClick')}
rotation={getChartRotationKnob()}
roundHistogramBrushValues={boolean('roundHistogramBrushValues', false)}
allowBrushingLastHistogramBucket={boolean('allowBrushingLastHistogramBucket', false)}
/>
<Axis
id="bottom"
position={Position.Bottom}
title="bottom"
showOverlappingTicks
tickFormat={!isVertical ? dateFormatter : numberFormatter}
/>
<Axis id="bottom" position={Position.Bottom} title="bottom" showOverlappingTicks tickFormat={formatter} />
<Axis id="left" title="left" position={Position.Left} tickFormat={(d) => Number(d).toFixed(2)} />
<Axis id="left" title="left" position={Position.Left} tickFormat={isVertical ? dateFormatter : numberFormatter} />

<HistogramBarSeries
id="bars"
Expand Down
1 change: 1 addition & 0 deletions stories/interactions/interactions.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export { Example as brushSelectionToolOnLinear } from './9_brush_selection_linea
export { Example as brushTool } from './9a_brush_selection_linear';

export { Example as brushSelectionToolOnBarChartLinear } from './10_brush_selection_bar';
export { Example as brushSelectionToolOnBarChartHistogram } from './10a_brush_selection_bar_hist';
export { Example as brushSelectionToolOnTimeCharts } from './11_brush_time';
export { Example as brushSelectionToolOnHistogramTimeCharts } from './12_brush_time_hist';
export { Example as brushDisabledOnOrdinalXAxis } from './13_brush_disabled_ordinal';
Expand Down

0 comments on commit 6d0319f

Please sign in to comment.