Skip to content

Commit

Permalink
[demo] factor out LineSeriesExample and update to demonstrate program…
Browse files Browse the repository at this point in the history
…matic tooltip triggering
  • Loading branch information
williaster committed Dec 5, 2017
1 parent 6d243d6 commit 8247610
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 162 deletions.
239 changes: 239 additions & 0 deletions packages/demo/examples/01-xy-chart/LineSeriesExample.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
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,
} from '@data-ui/xy-chart';

import ResponsiveXYChart, { formatYear } from './ResponsiveXYChart';
import { timeSeriesData } from './data';
import WithToggle from '../shared/WithToggle';

const seriesProps = [
{
label: 'Stock 1',
key: 'Stock 1',
data: timeSeriesData,
stroke: allColors.grape[9],
showPoints: true,
dashType: 'solid',
},
{
label: 'Stock 2',
key: 'Stock 2',
data: timeSeriesData.map(d => ({
...d,
y: Math.random() > 0.5 ? d.y * 2 : d.y / 2,
})),
stroke: allColors.grape[7],
strokeDasharray: '6 4',
dashType: 'dashed',
strokeLinecap: 'butt',
},
{
label: 'Stock 3',
key: 'Stock 3',
data: timeSeriesData.map(d => ({
...d,
y: Math.random() < 0.3 ? d.y * 3 : d.y / 3,
})),
stroke: allColors.grape[4],
strokeDasharray: '2 2',
dashType: 'dotted',
strokeLinecap: 'butt',
},
];

const TOOLTIP_TIMEOUT = 350;
const CONTAINER_TRIGGER = 'CONTAINER_TRIGGER';
const VORONOI_TRIGGER = 'VORONOI_TRIGGER';

class LineSeriesExample extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
index: 0,
programmaticTrigger: false,
trigger: CONTAINER_TRIGGER,
};
this.ref = this.ref.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);
}

componentWillUnmount() {
if (this.timeout) clearTimeout(this.timeout);
}

setTrigger(nextTrigger) {
this.setState(() => ({ trigger: nextTrigger }));
}

ref(ref) {
this.chart = ref;
this.triggerTooltip();
}

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

this.setState(({ index, trigger }) => {
this.chart.handleMouseMove({
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 : {
y: 50,
},
});

this.timeout = setTimeout(this.triggerTooltip, TOOLTIP_TIMEOUT);

return { index: index + 1, programmaticTrigger: true };
});
} else if (this.chart) {
this.chart.handleMouseLeave();
this.timeout = setTimeout(() => {
this.setState(() => ({
index: 0,
programmaticTrigger: false,
}));
}, TOOLTIP_TIMEOUT);
}
}


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

renderControls(disableMouseEvents) {
const { trigger } = this.state;
const useVoronoiTrigger = trigger === VORONOI_TRIGGER;
return (
<div style={{ display: 'flex' }}>
<Button
small
rounded
active={!disableMouseEvents && !useVoronoiTrigger}
disabled={disableMouseEvents}
onClick={() => { this.setTrigger(CONTAINER_TRIGGER); }}
> Shared Tooltip
</Button>
<div style={{ width: 8 }} />
<Button
small
rounded
active={!disableMouseEvents && useVoronoiTrigger}
disabled={disableMouseEvents}
onClick={() => { this.setTrigger(VORONOI_TRIGGER); }}
> Voronoi Tooltip
</Button>
<div style={{ width: 16 }} />
<Button
small
rounded
disabled={disableMouseEvents}
onClick={() => { this.restartProgrammaticTooltip(); }}
> Programatically trigger tooltip
</Button>
</div>
);
}

renderTooltip({ datum, series }) {
const { programmaticTrigger, trigger } = this.state;
return (
<div>
<div>
{formatYear(datum.x)}
{(!series || Object.keys(series).length === 0) &&
<div>
{datum.y.toFixed(2)}
</div>}
</div>
{trigger === CONTAINER_TRIGGER && <br />}
{seriesProps.map(({ label, stroke: color, dashType }) => (
series && series[label] &&
<div key={label}>
<span
style={{
color,
textDecoration: !programmaticTrigger && series[label] === datum
? `underline ${dashType} ${color}` : null,
fontWeight: series[label] === datum ? 600 : 200,
}}
>
{`${label} `}
</span>
{series[label].y.toFixed(2)}
</div>
))}
</div>
);
}

render() {
const { trigger } = 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}
/>
))}
<CrossHair
fullHeight
showHorizontalLine={false}
strokeDasharray=""
stroke={allColors.grape[4]}
circleStroke={allColors.grape[4]}
circleFill="#fff"
showCircle={useVoronoiTrigger || !this.state.programmaticTrigger}
/>
</ResponsiveXYChart>
</WithTooltip>
</div>
)}
</WithToggle>
);
}
}

export default LineSeriesExample;
5 changes: 3 additions & 2 deletions packages/demo/examples/01-xy-chart/ResponsiveXYChart.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {

export const parseDate = timeParse('%Y%m%d');
export const formatDate = timeFormat('%b %d');
export const formatYear = timeFormat('%b %d');
export const formatYear = timeFormat('%Y');
export const dateFormatter = date => formatDate(parseDate(date));

// this is a little messy to handle all cases across series types
Expand Down Expand Up @@ -41,13 +41,14 @@ export function renderTooltip({ datum, seriesKey, color }) {
);
}

function ResponsiveXYChart({ screenWidth, children, ...rest }) {
function ResponsiveXYChart({ screenWidth, children, innerRef, ...rest }) {
return (
<XYChart
theme={theme}
width={Math.min(700, screenWidth / 1.5)}
height={Math.min(700 / 2, screenWidth / 1.5 / 2)}
renderTooltip={renderTooltip}
ref={innerRef}
{...rest}
>
{children}
Expand Down
39 changes: 19 additions & 20 deletions packages/demo/examples/01-xy-chart/StackedAreaExample.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,26 +51,25 @@ export default function StackedAreaExample() {
return (
<WithToggle id="lineseries_toggle" label="As percent" initialChecked>
{asPercent => (
<div
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}
>
<LegendOrdinal
key="legend"
direction="row"
scale={legendScale}
shape={({ fill, width, height }) => (
<svg width={width} height={height}>
<rect
width={width}
height={height}
fill={fill}
/>
</svg>
)}
fill={({ datum }) => legendScale(datum)}
labelFormat={label => label}
/>

<div>
<div style={{ marginLeft: 24 }}>
<LegendOrdinal
key="legend"
direction="row"
scale={legendScale}
shape={({ fill, width, height }) => (
<svg width={width} height={height}>
<rect
width={width}
height={height}
fill={fill}
/>
</svg>
)}
fill={({ datum }) => legendScale(datum)}
labelFormat={label => label}
/>
</div>
<ResponsiveXYChart
ariaLabel="Stacked area chart of temperatures"
key="chart"
Expand Down
Loading

0 comments on commit 8247610

Please sign in to comment.