Skip to content

Commit

Permalink
Merge pull request #595 from hshoff/chris--vx-demo-area-axes
Browse files Browse the repository at this point in the history
typescript(vx-demo): rewrite Areas + Axis examples in TypeScript
  • Loading branch information
williaster committed Jan 10, 2020
2 parents 723110c + 0029386 commit dfaa690
Show file tree
Hide file tree
Showing 7 changed files with 433 additions and 427 deletions.
9 changes: 8 additions & 1 deletion packages/vx-axis/src/axis/Axis.tsx
Expand Up @@ -149,7 +149,14 @@ export default function Axis<ScaleInput>({
className={cx('vx-axis-tick', tickClassName)}
transform={tickTransform}
>
{!hideTicks && <Line from={tickFromPoint} to={tickToPoint} stroke={tickStroke} />}
{!hideTicks && (
<Line
from={tickFromPoint}
to={tickToPoint}
stroke={tickStroke}
strokeLinecap="square"
/>
)}
{tickComponent ? (
tickComponent({
...tickLabelPropsObj,
Expand Down
6 changes: 3 additions & 3 deletions packages/vx-demo/src/components/gallery.js
Expand Up @@ -11,10 +11,10 @@ import Bars from './tiles/Bars.tsx';
import Dots from './tiles/Dots.tsx';
import Patterns from './tiles/Patterns.tsx';
import Gradients from './tiles/Gradients.tsx';
import Area from './tiles/area';
import Areas from './tiles/Areas.tsx';
import StackedAreas from './tiles/Stacked-Areas.tsx';
import MultiLine from './tiles/multiline';
import Axis from './tiles/axis';
import Axis from './tiles/Axis.tsx';
import BarGroup from './tiles/BarGroup.tsx';
import BarGroupHorizontal from './tiles/BarGroupHorizontal.tsx';
import BarStack from './tiles/BarStack.tsx';
Expand Down Expand Up @@ -153,7 +153,7 @@ export default function() {
<div className="image">
<ParentSize>
{({ width, height }) => (
<Area
<Areas
width={width}
height={height + detailsHeight}
margin={{
Expand Down
@@ -1,103 +1,105 @@
import React from 'react';
import { AreaClosed, Line, Bar } from '@vx/shape';
import { appleStock } from '@vx/mock-data';
import appleStock, { AppleStock } from '@vx/mock-data/lib/mocks/appleStock';
import { curveMonotoneX } from '@vx/curve';
import { GridRows, GridColumns } from '@vx/grid';
import { scaleTime, scaleLinear } from '@vx/scale';
import { withTooltip, Tooltip } from '@vx/tooltip';
import { WithTooltipProvidedProps } from '@vx/tooltip/lib/enhancers/withTooltip';
import { localPoint } from '@vx/event';
import { bisector } from 'd3-array';
import { max, extent, bisector } from 'd3-array';
import { timeFormat } from 'd3-time-format';
import { ShowProvidedProps } from '../../types';

type TooltipData = AppleStock;

const stock = appleStock.slice(800);

// util
const formatDate = timeFormat("%b %d, '%y");
const min = (arr, fn) => Math.min(...arr.map(fn));
const max = (arr, fn) => Math.max(...arr.map(fn));
const extent = (arr, fn) => [min(arr, fn), max(arr, fn)];

// accessors
const xStock = d => new Date(d.date);
const yStock = d => d.close;
const bisectDate = bisector(d => new Date(d.date)).left;
const getDate = (d: AppleStock) => new Date(d.date);
const getStockValue = (d: AppleStock) => d.close;
const bisectDate = bisector<AppleStock, Date>(d => new Date(d.date)).left;

class Area extends React.Component {
constructor(props) {
super(props);
this.handleTooltip = this.handleTooltip.bind(this);
}
handleTooltip({ event, data, xStock: getXStock, xScale, yScale }) {
const { showTooltip } = this.props;
const { x } = localPoint(event);
const x0 = xScale.invert(x);
const index = bisectDate(data, x0, 1);
const d0 = data[index - 1];
const d1 = data[index];
let d = d0;
if (d1 && d1.date) {
d = x0 - getXStock(d0.date) > getXStock(d1.date) - x0 ? d1 : d0;
}
showTooltip({
tooltipData: d,
tooltipLeft: x,
tooltipTop: yScale(d.close),
});
}
render() {
const { width, height, margin, hideTooltip, tooltipData, tooltipTop, tooltipLeft } = this.props;
export default withTooltip<ShowProvidedProps, TooltipData>(
({
width,
height,
margin = { top: 0, right: 0, bottom: 0, left: 0 },
showTooltip,
hideTooltip,
tooltipData,
tooltipTop = 0,
tooltipLeft = 0,
}: ShowProvidedProps & WithTooltipProvidedProps<TooltipData>) => {
if (width < 10) return null;

// bounds
const xMax = width - margin.left - margin.right;
const yMax = height - margin.top - margin.bottom;

// scales
const xScale = scaleTime({
const dateScale = scaleTime({
range: [0, xMax],
domain: extent(stock, xStock),
domain: extent(stock, getDate) as [Date, Date],
});
const yScale = scaleLinear({
const stockValueScale = scaleLinear({
range: [yMax, 0],
domain: [0, max(stock, yStock) + yMax / 3],
domain: [0, (max(stock, getStockValue) || 0) + yMax / 3],
nice: true,
});

// tooltip handler
const handleTooltip = (
event: React.TouchEvent<SVGRectElement> | React.MouseEvent<SVGRectElement>,
) => {
const { x } = localPoint(event) || { x: 0 };
const x0 = dateScale.invert(x);
const index = bisectDate(stock, x0, 1);
const d0 = stock[index - 1];
const d1 = stock[index];
let d = d0;
if (d1 && getDate(d1)) {
d = x0.valueOf() - getDate(d0).valueOf() > getDate(d1).valueOf() - x0.valueOf() ? d1 : d0;
}
showTooltip({
tooltipData: d,
tooltipLeft: x,
tooltipTop: stockValueScale(getStockValue(d)),
});
};

return (
<div>
<svg
ref={s => {
this.svg = s;
}}
width={width}
height={height}
>
<svg width={width} height={height}>
<rect x={0} y={0} width={width} height={height} fill="#32deaa" rx={14} />
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" stopColor="#FFFFFF" stopOpacity={1} />
<stop offset="100%" stopColor="#FFFFFF" stopOpacity={0.2} />
</linearGradient>
</defs>
<GridRows
lineStyle={{ pointerEvents: 'none' }}
scale={yScale}
<GridRows<number>
scale={stockValueScale}
width={xMax}
strokeDasharray="2,2"
stroke="rgba(255,255,255,0.3)"
pointerEvents="none"
/>
<GridColumns
lineStyle={{ pointerEvents: 'none' }}
scale={xScale}
<GridColumns<Date>
scale={dateScale}
height={yMax}
strokeDasharray="2,2"
stroke="rgba(255,255,255,0.3)"
pointerEvents="none"
/>
<AreaClosed
<AreaClosed<AppleStock>
data={stock}
x={d => xScale(xStock(d))}
y={d => yScale(yStock(d))}
yScale={yScale}
x={d => dateScale(getDate(d))}
y={d => stockValueScale(getStockValue(d))}
yScale={stockValueScale}
strokeWidth={1}
stroke="url(#gradient)"
fill="url(#gradient)"
Expand All @@ -110,44 +112,19 @@ class Area extends React.Component {
height={height}
fill="transparent"
rx={14}
data={stock}
onTouchStart={event =>
this.handleTooltip({
event,
xStock,
xScale,
yScale,
data: stock,
})
}
onTouchMove={event =>
this.handleTooltip({
event,
xStock,
xScale,
yScale,
data: stock,
})
}
onMouseMove={event =>
this.handleTooltip({
event,
xStock,
xScale,
yScale,
data: stock,
})
}
onTouchStart={handleTooltip}
onTouchMove={handleTooltip}
onMouseMove={handleTooltip}
onMouseLeave={() => hideTooltip()}
/>
{tooltipData && (
<g>
<Line
from={{ x: tooltipLeft, y: 0 }}
to={{ x: tooltipLeft, y: yMax }}
stroke="rgba(92, 119, 235, 1.000)"
stroke="rgba(92, 119, 235, 1)"
strokeWidth={2}
style={{ pointerEvents: 'none' }}
pointerEvents="none"
strokeDasharray="2,2"
/>
<circle
Expand All @@ -159,16 +136,16 @@ class Area extends React.Component {
stroke="black"
strokeOpacity={0.1}
strokeWidth={2}
style={{ pointerEvents: 'none' }}
pointerEvents="none"
/>
<circle
cx={tooltipLeft}
cy={tooltipTop}
r={4}
fill="rgba(92, 119, 235, 1.000)"
fill="rgba(92, 119, 235, 1)"
stroke="white"
strokeWidth={2}
style={{ pointerEvents: 'none' }}
pointerEvents="none"
/>
</g>
)}
Expand All @@ -179,11 +156,11 @@ class Area extends React.Component {
top={tooltipTop - 12}
left={tooltipLeft + 12}
style={{
backgroundColor: 'rgba(92, 119, 235, 1.000)',
backgroundColor: 'rgba(92, 119, 235, 1)',
color: 'white',
}}
>
{`$${yStock(tooltipData)}`}
{`$${getStockValue(tooltipData)}`}
</Tooltip>
<Tooltip
top={yMax - 14}
Expand All @@ -192,13 +169,11 @@ class Area extends React.Component {
transform: 'translateX(-50%)',
}}
>
{formatDate(xStock(tooltipData))}
{formatDate(getDate(tooltipData))}
</Tooltip>
</div>
)}
</div>
);
}
}

export default withTooltip(Area);
},
);

0 comments on commit dfaa690

Please sign in to comment.