Skip to content

Commit

Permalink
Added Chart Range.
Browse files Browse the repository at this point in the history
  • Loading branch information
ericsoderberghp committed Jul 21, 2016
1 parent 6b6b90e commit 8563ed0
Show file tree
Hide file tree
Showing 5 changed files with 291 additions and 3 deletions.
4 changes: 3 additions & 1 deletion src/js/components/chart/Chart.js
Expand Up @@ -14,6 +14,7 @@ import Line from './Line';
import Bar from './Bar';
import Threshold from './Threshold';
import HotSpots from './HotSpots';
import Range from './Range';

const CLASS_ROOT = CSSClassnames.CHART;
const CHART_BASE = CSSClassnames.CHART_BASE;
Expand Down Expand Up @@ -178,4 +179,5 @@ Chart.propTypes = {
verticalAlignWith: PropTypes.string
};

export { Axis, Layers, Stack, Base, Grid, Area, Line, Bar, Threshold, HotSpots };
export { Axis, Layers, Stack, Base, Grid, Area, Line, Bar, Threshold,
HotSpots, Range };
4 changes: 2 additions & 2 deletions src/js/components/chart/HotSpots.js
Expand Up @@ -27,9 +27,9 @@ export default class HotSpots extends Component {
const defaultBasis = 100 / (count - 1);
let items = [];
for (let index=0; index<count; index+=1) {
let classes = ['hot-spots__band'];
let classes = [`${CLASS_ROOT}__band`];
if (index === activeIndex) {
classes.push('hot-spots__band--active');
classes.push(`${CLASS_ROOT}__band--active`);
}
let basis;
if (0 === index || index === (count - 1)) {
Expand Down
229 changes: 229 additions & 0 deletions src/js/components/chart/Range.js
@@ -0,0 +1,229 @@
// (C) Copyright 2016 Hewlett Packard Enterprise Development LP

import React, { Component, PropTypes } from 'react';
import { padding } from './utils';
import DragIcon from '../icons/base/Drag';
import CSSClassnames from '../../utils/CSSClassnames';

const CLASS_ROOT = CSSClassnames.CHART_RANGE;

// Allows selecting a region.
// Click to select one.
// Press and Drag to select multiple.
// Drag edges to adjust.

export default class Range extends Component {

constructor () {
super();
this._onMouseMove = this._onMouseMove.bind(this);
this._onMouseUp = this._onMouseUp.bind(this);
this.state = {};
}

componentWillUnmount () {
const { mouseDown } = this.state;
if (mouseDown) {
window.removeEventListener('mouseup', this._onMouseUp);
}
}

_valueToIndex (value) {
const { count, vertical } = this.props;
const rect = this.refs.range.getBoundingClientRect();
const total = vertical ? rect.height : rect.width;
return Math.round((value / total) * (count - 1));
}

_percentForIndex (index) {
const { count } = this.props;
return (100 / (count - 1)) * Math.min(index, count - 1);
}

_mouseIndex (event) {
const { active, count, vertical } = this.props;
const { mouseDown, mouseDownIndex } = this.state;
const rect = this.refs.range.getBoundingClientRect();
const value = (vertical ? (event.clientY - rect.top) :
(event.clientX - rect.left));
let index = this._valueToIndex(value);

// constrain index to keep it within range as needed
if ('active' === mouseDown && mouseDownIndex >= 0) {
if (index > mouseDownIndex) {
// moving right/down
index = Math.min(mouseDownIndex + count - 1 - active.end, index);
} else if (index < mouseDownIndex) {
// moving up/left
index = Math.max(mouseDownIndex - active.start, index);
}
} else if ('start' === mouseDown) {
index = Math.min(active.end, index);
} else if ('end' === mouseDown) {
index = Math.max(active.start, index);
}

return index;
}

_mouseDown (source) {
return (event) => {
event.stopPropagation(); // so start and end don't trigger range
const index = this._mouseIndex(event);
this.setState({
mouseDown: source,
mouseDownIndex: index
});
window.addEventListener('mouseup', this._onMouseUp);
};
}

_onMouseUp (event) {
window.removeEventListener('mouseup', this._onMouseUp);
const { active, onActive } = this.props;
const { mouseDown, mouseDownIndex, moved } = this.state;
const mouseUpIndex = this._mouseIndex(event);

this.setState({
mouseDown: false,
mouseDownIndex: undefined,
mouseMoveIndex: undefined,
moved: false
});

if (onActive) {
let nextActive;

if ('range' === mouseDown) {
if (moved) {
nextActive = {
start: Math.min(mouseDownIndex, mouseUpIndex),
end: Math.max(mouseDownIndex, mouseUpIndex)
};
}

} else if ('active' === mouseDown) {
const delta = mouseUpIndex - mouseDownIndex;
nextActive = {
start: active.start + delta,
end: active.end + delta
};

} else if ('start' === mouseDown) {
nextActive = {
start: mouseUpIndex,
end: active.end
};

} else if ('end' === mouseDown) {
nextActive = {
start: active.start,
end: mouseUpIndex
};
}

onActive(nextActive);
}
}

_onMouseMove (event) {
const { mouseMoveIndex } = this.state;
const index = this._mouseIndex(event);
if (index !== mouseMoveIndex) {
this.setState({ mouseMoveIndex: index, moved: true });
}
}

render () {
const { active, count, vertical } = this.props;
const { mouseDown, mouseDownIndex, mouseMoveIndex } = this.state;

let classes = [CLASS_ROOT];
if (vertical) {
classes.push(`${CLASS_ROOT}--vertical`);
}
if (mouseDown) {
classes.push(`${CLASS_ROOT}--dragging`);
}
if (this.props.className) {
classes.push(this.props.className);
}

let indicator;
if (active || mouseDown) {

let start, end;
if ('range' === mouseDown) {
start = Math.min(mouseDownIndex, mouseMoveIndex);
end = Math.max(mouseDownIndex, mouseMoveIndex);
} else if ('active' === mouseDown && mouseMoveIndex >= 0) {
const delta = mouseMoveIndex - mouseDownIndex;
start = active.start + delta;
end = active.end + delta;
} else if ('start' === mouseDown && mouseMoveIndex >= 0) {
start = mouseMoveIndex;
end = active.end;
} else if ('end' === mouseDown && mouseMoveIndex >= 0) {
start = active.start;
end = mouseMoveIndex;
} else {
start = active.start;
end = active.end;
}
// in case the user resizes the window
start = Math.max(0, Math.min(count - 1, start));
end = Math.max(0, Math.min(count - 1, end));

let style;
if (vertical) {
style = {
marginTop: `${this._percentForIndex(start)}%`,
height: `${this._percentForIndex(end - start)}%`
};
} else {
style = {
marginLeft: `${this._percentForIndex(start)}%`,
width: `${this._percentForIndex(end - start)}%`
};
}

indicator = (
<div className={`${CLASS_ROOT}__active`} style={style}
onMouseDown={this._mouseDown('active')}>
<div className={`${CLASS_ROOT}__active-start`}
onMouseDown={this._mouseDown('start')}>
<DragIcon />
</div>
<div className={`${CLASS_ROOT}__active-end`}
onMouseDown={this._mouseDown('end')}>
<DragIcon />
</div>
</div>
);
}

let onMouseMove;
if (mouseDown) {
onMouseMove = this._onMouseMove;
}

return (
<div ref="range" className={classes.join(' ')}
style={{ padding: padding }}
onMouseDown={this._mouseDown('range')} onMouseMove={onMouseMove}>
{indicator}
</div>
);
}

};

Range.propTypes = {
count: PropTypes.number,
active: PropTypes.shape({
end: PropTypes.number.isRequired,
start: PropTypes.number.isRequired
}),
onActive: PropTypes.func, // (start, end)
vertical: PropTypes.bool
};
1 change: 1 addition & 0 deletions src/js/utils/CSSClassnames.js
Expand Up @@ -20,6 +20,7 @@ export default {
CHART_GRID: `${namespace}chart-grid`,
CHART_HOT_SPOTS: `${namespace}chart-hot-spots`,
CHART_LAYERS: `${namespace}chart-layers`,
CHART_RANGE: `${namespace}chart-range`,
CHART_STACK: `${namespace}chart-stack`,
CHART_THRESHOLD: `${namespace}chart-threshold`,
CHECK_BOX: `${namespace}check-box`,
Expand Down
56 changes: 56 additions & 0 deletions src/scss/grommet-core/_objects.chart.scss
Expand Up @@ -412,6 +412,7 @@
.#{$grommet-namespace}chart-graph--area,
.#{$grommet-namespace}chart-graph--bar,
.#{$grommet-namespace}chart-hot-spots,
.#{$grommet-namespace}chart-range,
.#{$grommet-namespace}chart-loading {
position: absolute;
left: 0;
Expand Down Expand Up @@ -470,6 +471,61 @@
cursor: pointer;
}

// Range

.#{$grommet-namespace}chart-range {
cursor: pointer;
}

.#{$grommet-namespace}chart-range__active {
height: 100%;
display: flex;
flex-direction: row;
justify-content: space-between;
background-color: rgba($brand-color-lighter, 0.4);
}

.#{$grommet-namespace}chart-range--vertical {
.#{$grommet-namespace}chart-range__active {
flex-direction: column;
}
}

.#{$grommet-namespace}chart-range__active-start,
.#{$grommet-namespace}chart-range__active-end {
flex: 0 0 $inuit-base-spacing-unit;
display: flex;
flex-direction: column;
justify-content: center;
background-color: rgba($brand-color-lighter, 0.3);

&:hover {
background-color: rgba($brand-color-lighter, 0.8);
}

svg {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
}

.#{$grommet-namespace}chart-range--vertical {
.#{$grommet-namespace}chart-range__active {
flex-direction: column;
}

.#{$grommet-namespace}chart-range__active-start,
.#{$grommet-namespace}chart-range__active-end {
flex-direction: row;
}

svg {
transform: rotate(90deg);
}
}

// Grid

.#{$grommet-namespace}chart-grid {
Expand Down

0 comments on commit 8563ed0

Please sign in to comment.