Skip to content

Commit

Permalink
Merge pull request #117 from conglei/conglei_brush
Browse files Browse the repository at this point in the history
[WIP] Brush Interaction for XY-chart
  • Loading branch information
williaster authored Aug 22, 2018
2 parents 106db9c + 34de8ea commit f7d35b9
Show file tree
Hide file tree
Showing 22 changed files with 1,970 additions and 16 deletions.
432 changes: 432 additions & 0 deletions packages/demo/examples/01-xy-chart/BrushableLineChart.jsx

Large diffs are not rendered by default.

12 changes: 10 additions & 2 deletions packages/demo/examples/01-xy-chart/LinkedXYCharts.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,10 @@ class LinkedXYCharts extends React.Component {

handleMouseMove({ datum, seriesKey }) {
if (this.state.mousedOverDatum !== datum || this.state.mousedOverKey !== seriesKey) {
this.setState(() => ({ mousedOverDatum: datum, mousedOverKey: seriesKey }));
this.setState(() => ({
mousedOverDatum: datum,
mousedOverKey: seriesKey,
}));
}
}

Expand Down Expand Up @@ -117,7 +120,12 @@ class LinkedXYCharts extends React.Component {
: null;

const stackCrossHairData = mousedOverDatum
? { datum: { ...mousedOverDatum, y: getYForKey(mousedOverDatum, mousedOverKey) } }
? {
datum: {
...mousedOverDatum,
y: getYForKey(mousedOverDatum, mousedOverKey),
},
}
: null;

return (
Expand Down
27 changes: 23 additions & 4 deletions packages/demo/examples/01-xy-chart/ScatterWithHistograms.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,18 @@ export const pointData = genRandomNormalPoints(n).forEach(([x, y], i) => {
});

const marginScatter = { top: 10, right: 10, bottom: 64, left: 64 };
const marginTopHist = { top: 10, right: marginScatter.right, bottom: 5, left: marginScatter.left };
const marginSideHist = { top: 10, right: marginScatter.bottom, bottom: 5, left: marginScatter.top };
const marginTopHist = {
top: 10,
right: marginScatter.right,
bottom: 5,
left: marginScatter.left,
};
const marginSideHist = {
top: 10,
right: marginScatter.bottom,
bottom: 5,
left: marginScatter.top,
};

// eslint-disable-next-line react/prop-types
function renderTooltip({ datum }) {
Expand Down Expand Up @@ -104,7 +114,13 @@ class ScatterWithHistogram extends React.PureComponent {

renderRightHistogram({ width, height }) {
return (
<div style={{ transform: 'rotate(90deg)', display: 'flex', alignItems: 'flex-end' }}>
<div
style={{
transform: 'rotate(90deg)',
display: 'flex',
alignItems: 'flex-end',
}}
>
<Histogram
width={width}
height={height}
Expand Down Expand Up @@ -178,7 +194,10 @@ class ScatterWithHistogram extends React.PureComponent {
{this.renderTopHistogram({ width: scatterSize, height: histSize })}
<div style={{ display: 'flex', flexDirection: 'row' }}>
{this.renderScatter({ width: scatterSize, height: scatterSize })}
{this.renderRightHistogram({ width: scatterSize, height: histSize })}
{this.renderRightHistogram({
width: scatterSize,
height: histSize,
})}
</div>
</div>
<Checkbox
Expand Down
12 changes: 10 additions & 2 deletions packages/demo/examples/01-xy-chart/TickLabelPlayground.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,11 @@ class TickLabelPlayground extends React.PureComponent {
type="radio"
value="left"
onChange={e =>
this.setState({ axisOrientation: e.target.value, textAnchor: 'end', dx: -4 })
this.setState({
axisOrientation: e.target.value,
textAnchor: 'end',
dx: -4,
})
}
checked={this.state.axisOrientation === 'left'}
/>{' '}
Expand All @@ -251,7 +255,11 @@ class TickLabelPlayground extends React.PureComponent {
type="radio"
value="right"
onChange={e =>
this.setState({ axisOrientation: e.target.value, textAnchor: 'start', dx: 4 })
this.setState({
axisOrientation: e.target.value,
textAnchor: 'start',
dx: 4,
})
}
checked={this.state.axisOrientation === 'right'}
/>{' '}
Expand Down
5 changes: 4 additions & 1 deletion packages/demo/examples/01-xy-chart/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ export const timeSeriesData = appleStock.filter((d, i) => i % 120 === 0).map(d =
y: d.close,
}));

export const categoricalData = letterFrequency.map(d => ({ x: d.letter, y: d.frequency }));
export const categoricalData = letterFrequency.map(d => ({
x: d.letter,
y: d.frequency,
}));

// stacked data
export const groupKeys = Object.keys(cityTemperature[0]).filter(attr => attr !== 'date');
Expand Down
8 changes: 8 additions & 0 deletions packages/demo/examples/01-xy-chart/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
PatternLines,
LinearGradient,
WithTooltip,
Brush,
} from '@data-ui/xy-chart';

import colors from '@data-ui/theme/lib/color';
Expand Down Expand Up @@ -52,6 +53,7 @@ import {
import WithToggle from '../shared/WithToggle';

import computeForceBasedCirclePack from './computeForceBasedCirclePack';
import BrushableLineChart from './BrushableLineChart';

PatternLines.displayName = 'PatternLines';
LinearGradient.displayName = 'LinearGradient';
Expand Down Expand Up @@ -150,6 +152,7 @@ export default {
circleStroke="white"
/>
<XAxis label="Time" numTicks={5} />
<Brush />
</ResponsiveXYChart>
)}
</WithToggle>
Expand Down Expand Up @@ -303,6 +306,11 @@ export default {
components: [XYChart, StackedBarSeries, AreaSeries, CrossHair],
example: () => <LinkedXYCharts />,
},
{
description: 'Brushable time series chart',
components: [XYChart, LineSeries],
example: () => <BrushableLineChart />,
},
{
description: 'StackedAreaSeries',
components: [XYChart],
Expand Down
16 changes: 14 additions & 2 deletions packages/demo/examples/02-histogram/HistogramPlayground.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,20 @@ class HistogramPlayground extends React.PureComponent {

this.state = {
datasets: [0, 1],
0: getDataset({ n: NUM_POINTS, mu: 0, sigma: 1, distribution: 'normal', color: colors[0] }),
1: getDataset({ n: NUM_POINTS, mu: 2, sigma: 1, distribution: 'normal', color: colors[1] }),
0: getDataset({
n: NUM_POINTS,
mu: 0,
sigma: 1,
distribution: 'normal',
color: colors[0],
}),
1: getDataset({
n: NUM_POINTS,
mu: 2,
sigma: 1,
distribution: 'normal',
color: colors[1],
}),

cumulative: false,
normalized: false,
Expand Down
12 changes: 9 additions & 3 deletions packages/xy-chart/src/chart/Voronoi.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const propTypes = {
onClick: PropTypes.func,
onMouseMove: PropTypes.func,
onMouseLeave: PropTypes.func,
onMouseDown: PropTypes.func,
showVoronoi: PropTypes.bool,
width: PropTypes.number.isRequired,
height: PropTypes.number.isRequired,
Expand All @@ -21,6 +22,7 @@ const defaultProps = {
onClick: null,
onMouseMove: null,
onMouseLeave: null,
onMouseDown: null,
showVoronoi: false,
};

Expand All @@ -31,8 +33,11 @@ class Voronoi extends React.PureComponent {
}

componentWillReceiveProps(nextProps) {
// eslint-disable-next-line react/destructuring-assignment
if (['data', 'x', 'y', 'width', 'height'].some(prop => this.props[prop] !== nextProps[prop])) {
if (
['data', 'x', 'y', 'width', 'height'].some(
prop => this.props[prop] !== nextProps[prop], // eslint-disable-line react/destructuring-assignment
)
) {
this.setState({ voronoi: Voronoi.getVoronoi(nextProps) });
}
}
Expand All @@ -44,7 +49,7 @@ class Voronoi extends React.PureComponent {
}

render() {
const { onMouseLeave, onMouseMove, onClick, showVoronoi } = this.props;
const { onMouseLeave, onMouseMove, onClick, showVoronoi, onMouseDown } = this.props;
const { voronoi } = this.state;

return (
Expand All @@ -69,6 +74,7 @@ class Voronoi extends React.PureComponent {
})
}
onMouseLeave={onMouseLeave && (() => onMouseLeave)}
onMouseDown={onMouseDown && (() => onMouseDown)}
/>
))}
</Group>
Expand Down
43 changes: 41 additions & 2 deletions packages/xy-chart/src/chart/XYChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
isCrossHair,
isReferenceLine,
isSeries,
isBrush,
getChildWithName,
numTicksForWidth,
numTicksForHeight,
Expand Down Expand Up @@ -91,6 +92,7 @@ class XYChart extends React.PureComponent {
this.handleClick = this.handleClick.bind(this);
this.handleMouseLeave = this.handleMouseLeave.bind(this);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.handleMouseDown = this.handleMouseDown.bind(this);
this.handleContainerEvent = this.handleContainerEvent.bind(this);
}

Expand All @@ -107,8 +109,11 @@ class XYChart extends React.PureComponent {

componentWillReceiveProps(nextProps) {
let shouldComputeScales = false;
// eslint-disable-next-line react/destructuring-assignment
if (['width', 'height', 'children'].some(prop => this.props[prop] !== nextProps[prop])) {
if (
['width', 'height', 'children'].some(
prop => this.props[prop] !== nextProps[prop], // eslint-disable-line react/destructuring-assignment
)
) {
shouldComputeScales = true;
}
if (
Expand Down Expand Up @@ -183,6 +188,12 @@ class XYChart extends React.PureComponent {
}
}

handleMouseDown(event) {
if (this.fireBrushStart) {
this.fireBrushStart(event);
}
}

handleMouseMove(args) {
const { snapTooltipToDataX, snapTooltipToDataY, onMouseMove } = this.props;
const isFocusEvent = args.event && args.event.type === 'focus';
Expand Down Expand Up @@ -259,6 +270,9 @@ class XYChart extends React.PureComponent {
const { numXTicks, numYTicks } = this.getNumTicks(innerWidth, innerHeight);
const barWidth = xScale.barWidth || (xScale.bandwidth && xScale.bandwidth()) || 0;
const CrossHairs = []; // ensure these are the top-most layer
let Brush = null;
let xAxisOrientation;
let yAxisOrientation;

return (
innerWidth > 0 &&
Expand All @@ -283,6 +297,11 @@ class XYChart extends React.PureComponent {
const name = componentName(Child);
if (isAxis(name)) {
const styleKey = name[0].toLowerCase();
if (name === 'XAxis') {
xAxisOrientation = Child.props.orientation;
} else {
yAxisOrientation = Child.props.orientation;
}

return React.cloneElement(Child, {
innerHeight,
Expand Down Expand Up @@ -322,6 +341,10 @@ class XYChart extends React.PureComponent {
return null;
} else if (isReferenceLine(name)) {
return React.cloneElement(Child, { xScale, yScale });
} else if (isBrush(name)) {
Brush = Child;

return null;
}

return Child;
Expand All @@ -335,6 +358,7 @@ class XYChart extends React.PureComponent {
width={innerWidth}
height={innerHeight}
onClick={this.handleClick}
onMouseDown={this.handleMouseDown}
onMouseMove={this.handleMouseMove}
onMouseLeave={this.handleMouseLeave}
showVoronoi={showVoronoi}
Expand All @@ -349,12 +373,27 @@ class XYChart extends React.PureComponent {
height={innerHeight}
fill="transparent"
fillOpacity={0}
onMouseDown={this.handleMouseDown}
onClick={this.handleContainerEvent}
onMouseMove={this.handleContainerEvent}
onMouseLeave={this.handleMouseLeave}
/>
)}

{Brush &&
React.cloneElement(Brush, {
xScale,
yScale,
innerHeight,
innerWidth,
margin,
onMouseMove: this.handleContainerEvent,
onMouseLeave: this.handleMouseLeave,
onClick: this.handleContainerEvent,
xAxisOrientation,
yAxisOrientation,
})}

{tooltipData &&
CrossHairs.length > 0 &&
CrossHairs.map((CrossHair, i) =>
Expand Down
1 change: 1 addition & 0 deletions packages/xy-chart/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,4 @@ export { PatternLines, PatternCircles, PatternWaves, PatternHexagons } from '@vx
export { withScreenSize, withParentSize, ParentSize } from '@vx/responsive';
export { default as withTheme } from './enhancer/withTheme';
export { chartTheme as theme } from '@data-ui/theme';
export { default as Brush } from './selection/Brush';
Loading

0 comments on commit f7d35b9

Please sign in to comment.