Skip to content

Commit

Permalink
[xy-chart] clean up tooltip behavior customization and hooks, clean u…
Browse files Browse the repository at this point in the history
…p utils
  • Loading branch information
williaster committed Dec 6, 2017
1 parent 8247610 commit 8b02ffe
Show file tree
Hide file tree
Showing 13 changed files with 412 additions and 312 deletions.
135 changes: 80 additions & 55 deletions packages/demo/examples/01-xy-chart/LineSeriesExample.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { allColors } from '@data-ui/theme/build/color';
import { Button } from '@data-ui/forms';

import {
CrossHair,
XAxis,
YAxis,
LineSeries,
WithTooltip,
XAxis,
YAxis,
} from '@data-ui/xy-chart';

import ResponsiveXYChart, { formatYear } from './ResponsiveXYChart';
Expand Down Expand Up @@ -50,7 +49,8 @@ const seriesProps = [
},
];

const TOOLTIP_TIMEOUT = 350;
const MARGIN = { left: 8, top: 16 };
const TOOLTIP_TIMEOUT = 250;
const CONTAINER_TRIGGER = 'CONTAINER_TRIGGER';
const VORONOI_TRIGGER = 'VORONOI_TRIGGER';

Expand All @@ -61,12 +61,14 @@ class LineSeriesExample extends React.PureComponent {
index: 0,
programmaticTrigger: false,
trigger: CONTAINER_TRIGGER,
stickyTooltip: false,
};
this.ref = this.ref.bind(this);
this.eventTriggerRefs = this.eventTriggerRefs.bind(this);
this.triggerTooltip = this.triggerTooltip.bind(this);
this.renderTooltip = this.renderTooltip.bind(this);
this.restartProgrammaticTooltip = this.restartProgrammaticTooltip.bind(this);
this.setTrigger = this.setTrigger.bind(this);
this.handleClick = this.handleClick.bind(this);
}

componentWillUnmount() {
Expand All @@ -77,24 +79,33 @@ class LineSeriesExample extends React.PureComponent {
this.setState(() => ({ trigger: nextTrigger }));
}

ref(ref) {
this.chart = ref;
handleClick(args) {
if (this.triggers) {
this.setState(({ stickyTooltip }) => ({
stickyTooltip: !stickyTooltip,
}), () => {
this.triggers.mousemove(args);
});
}
}

eventTriggerRefs(triggers) {
this.triggers = triggers;
this.triggerTooltip();
}

triggerTooltip() {
if (this.chart && this.state.index < seriesProps[0].data.length) {
if (this.triggers && this.state.index < seriesProps[0].data.length) {
if (this.timeout) clearTimeout(this.timeout);

this.setState(({ index, trigger }) => {
this.chart.handleMouseMove({
this.triggers.mousemove({
datum: seriesProps[2].data[index],
series: trigger === VORONOI_TRIGGER ? null : {
[seriesProps[0].label]: seriesProps[0].data[index],
[seriesProps[1].label]: seriesProps[1].data[index],
[seriesProps[2].label]: seriesProps[2].data[index],
},
overrideCoords: trigger === VORONOI_TRIGGER ? null : {
coords: trigger === VORONOI_TRIGGER ? null : {
y: 50,
},
});
Expand All @@ -103,8 +114,8 @@ class LineSeriesExample extends React.PureComponent {

return { index: index + 1, programmaticTrigger: true };
});
} else if (this.chart) {
this.chart.handleMouseLeave();
} else if (this.triggers) {
this.triggers.mouseleave();
this.timeout = setTimeout(() => {
this.setState(() => ({
index: 0,
Expand All @@ -117,16 +128,16 @@ class LineSeriesExample extends React.PureComponent {

restartProgrammaticTooltip() {
if (this.timeout) clearTimeout(this.timeout);
if (this.chart) {
this.setState(() => ({ index: 0 }), this.triggerTooltip);
if (this.triggers) {
this.setState(() => ({ stickyTooltip: false, index: 0 }), this.triggerTooltip);
}
}

renderControls(disableMouseEvents) {
const { trigger } = this.state;
const { trigger, stickyTooltip } = this.state;
const useVoronoiTrigger = trigger === VORONOI_TRIGGER;
return (
<div style={{ display: 'flex' }}>
return ([
<div key="buttons" style={{ display: 'flex' }}>
<Button
small
rounded
Expand All @@ -152,19 +163,29 @@ class LineSeriesExample extends React.PureComponent {
onClick={() => { this.restartProgrammaticTooltip(); }}
> Programatically trigger tooltip
</Button>
</div>
);
</div>,
<div key="sticky" style={{ margin: '8px 0', fontSize: 14 }}>
Click chart for a&nbsp;
<span
style={{
fontWeight: stickyTooltip && 600,
textDecoration: stickyTooltip && `underline ${allColors.grape[4]}`,
}}
>sticky tooltip
</span>
</div>,
]);
}

renderTooltip({ datum, series }) {
const { programmaticTrigger, trigger } = this.state;
return (
<div>
<div>
{formatYear(datum.x)}
<strong>{formatYear(datum.x)}</strong>
{(!series || Object.keys(series).length === 0) &&
<div>
{datum.y.toFixed(2)}
${datum.y.toFixed(2)}
</div>}
</div>
{trigger === CONTAINER_TRIGGER && <br />}
Expand All @@ -181,53 +202,57 @@ class LineSeriesExample extends React.PureComponent {
>
{`${label} `}
</span>
{series[label].y.toFixed(2)}
${series[label].y.toFixed(2)}
</div>
))}
</div>
);
}

render() {
const { trigger } = this.state;
const { trigger, stickyTooltip } = this.state;
const useVoronoiTrigger = trigger === VORONOI_TRIGGER;
return (
<WithToggle id="line_mouse_events_toggle" label="Disable mouse events">
{disableMouseEvents => (
<div>
{this.renderControls(disableMouseEvents)}
<WithTooltip
snapToDataY={useVoronoiTrigger}
renderTooltip={this.renderTooltip}
>
<ResponsiveXYChart
ariaLabel="Required label"
xScale={{ type: 'time' }}
yScale={{ type: 'linear' }}
useVoronoi={useVoronoiTrigger}
showVoronoi={useVoronoiTrigger}
margin={{ left: 8, top: 16 }}
renderTooltip={null}
innerRef={this.ref}
>
<XAxis label="Time" numTicks={5} />
<YAxis label="Stock price ($)" numTicks={4} />
{seriesProps.map(props => (
<LineSeries
{...props}
disableMouseEvents={disableMouseEvents}

{/* Use WithTooltip to intercept onMouseLeave when in "clicked" state */}
<WithTooltip renderTooltip={this.renderTooltip}>
{({ onMouseLeave, onMouseMove, tooltipData }) => (
<ResponsiveXYChart
ariaLabel="Required label"
eventTrigger={useVoronoiTrigger ? 'voronoi' : 'container'}
eventTriggerRefs={this.eventTriggerRefs}
margin={MARGIN}
onClick={disableMouseEvents ? null : this.handleClick}
onMouseMove={disableMouseEvents || stickyTooltip ? null : onMouseMove}
onMouseLeave={disableMouseEvents || stickyTooltip ? null : onMouseLeave}
renderTooltip={null}
showVoronoi={useVoronoiTrigger}
snapTooltipToDataX
snapTooltipToDataY={useVoronoiTrigger}
tooltipData={tooltipData}
xScale={{ type: 'time' }}
yScale={{ type: 'linear' }}
>
<XAxis label="Time" numTicks={5} />
<YAxis label="Stock price ($)" numTicks={4} />
{seriesProps.map(props => (
<LineSeries {...props} disableMouseEvents={disableMouseEvents} />
))}
<CrossHair
fullHeight
showHorizontalLine={false}
strokeDasharray=""
stroke={allColors.grape[4]}
circleStroke={allColors.grape[4]}
circleFill="#fff"
showCircle={useVoronoiTrigger || !this.state.programmaticTrigger}
/>
))}
<CrossHair
fullHeight
showHorizontalLine={false}
strokeDasharray=""
stroke={allColors.grape[4]}
circleStroke={allColors.grape[4]}
circleFill="#fff"
showCircle={useVoronoiTrigger || !this.state.programmaticTrigger}
/>
</ResponsiveXYChart>
</ResponsiveXYChart>
)}
</WithTooltip>
</div>
)}
Expand Down
86 changes: 41 additions & 45 deletions packages/demo/examples/01-xy-chart/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import CirclePackWithCallback from './CirclePackWithCallback';
import LineSeriesExample from './LineSeriesExample';
import LinkedXYCharts from './LinkedXYCharts';
import RectPointComponent from './RectPointComponent';
import ResponsiveXYChart, { parseDate, formatYear, dateFormatter, renderTooltip } from './ResponsiveXYChart';
import ResponsiveXYChart, { parseDate, formatYear, dateFormatter } from './ResponsiveXYChart';
import StackedAreaExample from './StackedAreaExample';
import ScatterWithHistogram from './ScatterWithHistograms';
import {
Expand Down Expand Up @@ -118,51 +118,47 @@ export default {
{snapToDataX => (
<WithToggle id="area_snap_data_y" label="Snap tooltip to y">
{snapToDataY => (
<WithTooltip
snapToDataX={snapToDataX}
snapToDataY={snapToDataY}
renderTooltip={renderTooltip}
<ResponsiveXYChart
eventTrigger="container"
ariaLabel="Required label"
xScale={{ type: 'time' }}
yScale={{ type: 'linear' }}
margin={{ left: 8, top: 8, bottom: 64 }}
snapTooltipToDataX={snapToDataX}
snapTooltipToDataY={snapToDataY}
>
<ResponsiveXYChart
ariaLabel="Required label"
xScale={{ type: 'time' }}
yScale={{ type: 'linear' }}
margin={{ left: 8, top: 8 }}
renderTooltip={null}
>
<LinearGradient
id="area_gradient"
from={colors.categories[2]}
to="#fff"
/>
<PatternLines
id="area_pattern"
height={12}
width={12}
stroke={colors.categories[2]}
strokeWidth={1}
orientation={['diagonal']}
/>
<AreaSeries
data={timeSeriesData}
fill="url(#area_gradient)"
strokeWidth={null}
/>
<AreaSeries
data={timeSeriesData}
fill="url(#area_pattern)"
stroke={colors.categories[2]}
/>
<CrossHair
showHorizontalLine={false}
fullHeight
stroke={colors.darkGray}
circleFill={colors.categories[2]}
circleStroke="white"
/>
<XAxis label="Time" numTicks={5} />
</ResponsiveXYChart>
</WithTooltip>
<LinearGradient
id="area_gradient"
from={colors.categories[2]}
to="#fff"
/>
<PatternLines
id="area_pattern"
height={12}
width={12}
stroke={colors.categories[2]}
strokeWidth={1}
orientation={['diagonal']}
/>
<AreaSeries
data={timeSeriesData}
fill="url(#area_gradient)"
strokeWidth={null}
/>
<AreaSeries
data={timeSeriesData}
fill="url(#area_pattern)"
stroke={colors.categories[2]}
/>
<CrossHair
showHorizontalLine={false}
fullHeight
stroke={colors.darkGray}
circleFill={colors.categories[2]}
circleStroke="white"
/>
<XAxis label="Time" numTicks={5} />
</ResponsiveXYChart>
)}
</WithToggle>
)}
Expand Down
26 changes: 9 additions & 17 deletions packages/shared/src/enhancer/WithTooltip.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ export const propTypes = {
className: PropTypes.string,
HoverStyles: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
renderTooltip: PropTypes.func,
snapToDataX: PropTypes.bool,
snapToDataY: PropTypes.bool,
styles: PropTypes.object,
TooltipComponent: PropTypes.oneOfType([PropTypes.object, PropTypes.func]),
tooltipProps: PropTypes.object,
Expand All @@ -43,7 +41,9 @@ const defaultProps = {
styles: { display: 'inline-block', position: 'relative' },
TooltipComponent: TooltipWithBounds,
tooltipProps: {
opacity: 0.9,
style: {
opacity: 0.9,
},
},
tooltipTimeout: 200,
};
Expand All @@ -62,29 +62,21 @@ class WithTooltip extends React.PureComponent {
}
}

handleMouseMove({ event, datum, dataCoords, overrideCoords, ...rest }) {
handleMouseMove({ event, datum, coords, ...rest }) {
if (this.tooltipTimeout) {
clearTimeout(this.tooltipTimeout);
}

let coords = { x: 0, y: 0 };
let tooltipCoords = { x: 0, y: 0 };
if (event && event.target && event.target.ownerSVGElement) {
coords = localPoint(event.target.ownerSVGElement, event);
tooltipCoords = localPoint(event.target.ownerSVGElement, event);
}

if (this.props.snapToDataX && dataCoords && typeof dataCoords.x === 'number') {
coords.x = dataCoords.x;
}

if (this.props.snapToDataY && dataCoords && typeof dataCoords.y === 'number') {
coords.y = dataCoords.y;
}

coords = { ...coords, ...overrideCoords };
tooltipCoords = { ...tooltipCoords, ...coords };

this.props.showTooltip({
tooltipLeft: coords.x,
tooltipTop: coords.y,
tooltipLeft: tooltipCoords.x,
tooltipTop: tooltipCoords.y,
tooltipData: {
event,
datum,
Expand Down
Loading

0 comments on commit 8b02ffe

Please sign in to comment.