Skip to content

Commit

Permalink
Merge pull request #71 from williaster/chris--band-series
Browse files Browse the repository at this point in the history
[xy-chart] add support for confidence bands
  • Loading branch information
williaster authored Nov 3, 2017
2 parents 74e88a2 + 3f3e2b1 commit 4c8bdd8
Show file tree
Hide file tree
Showing 39 changed files with 438 additions and 146 deletions.
7 changes: 7 additions & 0 deletions packages/data-ui-theme/src/svgLabel.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@ export const baseLabel = {
pointerEvents: 'none',
};

export const labelTiny = {
...font.tiny,
...font.bold,
...font.middle,
pointerEvents: 'none',
};

export const baseTickLabel = {
...font.small,
...font.light,
Expand Down
15 changes: 5 additions & 10 deletions packages/demo/examples/01-xy-chart/ScatterWithHistograms.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,6 @@ function renderTooltip({ datum }) { // eslint-disable-line react/prop-types
);
}

const RotatedLabel = ({ x, y }) => ( // eslint-disable-line react/prop-types
<text
{...theme.yAxisStyles.label.right}
transform={`translate(${-y},${x})rotate(270)`}
>
y counts
</text>
);

const propTypes = {
parentWidth: PropTypes.number.isRequired,
};
Expand Down Expand Up @@ -146,7 +137,11 @@ class ScatterWithHistogram extends React.PureComponent {
))}
<HistYAxis
orientation="right"
label={<RotatedLabel />}
label="y counts"
labelProps={{
...theme.yAxisStyles.label.right,
transform: `translate(${height / 1.75}, ${height})rotate(270)`,
}}
/>
</Histogram>
</div>
Expand Down
56 changes: 56 additions & 0 deletions packages/demo/examples/01-xy-chart/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,62 @@ export const pointData = genRandomNormalPoints(n).map(([x, y], i) => ({
label: (i % n) === 0 ? `(${parseInt(x, 10)},${parseInt(y, 10)})` : null,
}));

// band data
const stdDev = 0.1;
export const temperatureBands = groupKeys.map((city, cityIndex) => (
cityTemperature.slice(0, 25).map((d) => {
const y = Number(d[city]) - (20 * cityIndex);
return {
key: city,
x: d.date,
y,
y0: y + (stdDev * y),
y1: y - (stdDev * y),
};
})
));

export const priceBandData = {
band: [
{ x: new Date('2017-06-01'), y0: 215, y1: 260 },
{ x: new Date('2017-06-02'), y0: 203, y1: 290 },
{ x: new Date('2017-06-03'), y0: 196, y1: 279 },
{ x: new Date('2017-06-04'), y0: 190, y1: 261 },
{ x: new Date('2017-06-05'), y0: 140, y1: 250 },
{ x: new Date('2017-06-06'), y0: 120, y1: 231 },
{ x: new Date('2017-06-07'), y0: 131, y1: 211 },
{ x: new Date('2017-06-08'), y0: 123, y1: 196 },
{ x: new Date('2017-06-09'), y0: 105, y1: 171 },
{ x: new Date('2017-06-10'), y0: 100, y1: 175 },
{ x: new Date('2017-06-11'), y0: 80, y1: 150 },
{ x: new Date('2017-06-12'), y0: 83, y1: 164 },
{ x: new Date('2017-06-13'), y0: 86, y1: 155 },
{ x: new Date('2017-06-14'), y0: 80, y1: 132 },
{ x: new Date('2017-06-15'), y0: 73, y1: 125 },
{ x: new Date('2017-06-16'), y0: 71, y1: 132 },
{ x: new Date('2017-06-17'), y0: 78, y1: 123 },
{ x: new Date('2017-06-18'), y0: 82, y1: 156 },
{ x: new Date('2017-06-19'), y0: 76, y1: 150 },
{ x: new Date('2017-06-20'), y0: 87, y1: 173 },
{ x: new Date('2017-06-21'), y0: 95, y1: 168 },
{ x: new Date('2017-06-22'), y0: 105, y1: 182 },
{ x: new Date('2017-06-23'), y0: 100, y1: 202 },
{ x: new Date('2017-06-24'), y0: 116, y1: 211 },
{ x: new Date('2017-06-25'), y0: 126, y1: 230 },
{ x: new Date('2017-06-26'), y0: 137, y1: 246 },
{ x: new Date('2017-06-27'), y0: 142, y1: 262 },
{ x: new Date('2017-06-28'), y0: 170, y1: 273 },
{ x: new Date('2017-06-29'), y0: 190, y1: 285 },
{ x: new Date('2017-06-30'), y0: 201, y1: 301 },
],
};

priceBandData.points = priceBandData.band.map(({ x, y0, y1 }) => ({
x,
// Introduce noise within the y0-y1 range
y: ((y1 + y0) / 2) + ((Math.random() > 0.5 ? -1 : 1) * Math.random() * ((y1 - y0) / 4)),
}));

// interval data
const intervals = [[5, 8], [15, 19]];

Expand Down
104 changes: 101 additions & 3 deletions packages/demo/examples/01-xy-chart/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
pointData,
intervalLineData,
intervalData,
temperatureBands,
priceBandData,
} from './data';

import WithToggle from '../shared/WithToggle';
Expand Down Expand Up @@ -123,7 +125,7 @@ export default {
},
},
{
description: 'AreaSeries',
description: 'AreaSeries -- closed',
components: [AreaSeries],
example: () => (
<ResponsiveXYChart
Expand Down Expand Up @@ -159,13 +161,109 @@ export default {
<CrossHair
showHorizontalLine={false}
fullHeight
stroke={colors.categories[2]}
circleFill={colors.categories[2]}
stroke={colors.darkGray}
circleFill="white"
circleStroke={colors.darkGray}
/>
<XAxis label="Time" numTicks={5} />
</ResponsiveXYChart>
),
},
{
description: 'AreaSeries -- band',
components: [XYChart, AreaSeries, LineSeries],
example: () => (
<ResponsiveXYChart
ariaLabel="Required label"
xScale={{ type: 'time' }}
yScale={{ type: 'linear' }}
>
{temperatureBands.map((data, i) => ([
<PatternLines
id={`band-${i}`}
height={5}
width={5}
stroke={colors.categories[i + 1]}
strokeWidth={1}
orientation={['diagonal']}
/>,
<AreaSeries
key={`band-${data[0].key}`}
label="Temperature range"
data={data}
strokeWidth={0.5}
stroke={colors.categories[i + 1]}
fill={`url(#band-${i})`}
/>,
<LineSeries
key={`line-${data[0].key}`}
data={data}
stroke={colors.categories[i + 1]}
label="Temperature avg"
/>,
]))}
<YAxis label="Temperature (°F)" numTicks={4} />
<CrossHair
showHorizontalLine={false}
fullHeight
stroke={colors.gray}
circleStroke={colors.gray}
/>
</ResponsiveXYChart>
),
},
{
description: 'AreaSeries -- confidence intervals',
components: [XYChart, AreaSeries, LineSeries, PointSeries],
example: (reference = 150) => (
<ResponsiveXYChart
ariaLabel="Required label"
xScale={{ type: 'time' }}
yScale={{ type: 'linear' }}
useVoronoi
>
<XAxis numTicks={5} />
<YAxis label="Price" tickFormat={val => `$${val}`} />
<LinearGradient
id="confidence-interval-fill"
from={colors.categories[3]}
to={colors.categories[4]}
/>
<HorizontalReferenceLine
reference={reference}
label={`Min $${reference}`}
strokeDasharray="3 3"
strokeLinecap="butt"
/>
<AreaSeries
label="band"
data={priceBandData.band}
fill="url(#confidence-interval-fill)"
strokeWidth={0}
/>
<LineSeries
label="line"
data={priceBandData.points.map(d => (d.y >= reference ? d : { ...d, y: reference }))}
stroke={colors.categories[3]}
strokeWidth={2}
/>
<PointSeries
label="line"
data={priceBandData.points.filter(d => d.y < reference)}
fill="#fff"
fillOpacity={1}
stroke={colors.categories[3]}
/>
<CrossHair
showHorizontalLine={false}
fullHeight
stroke={colors.categories[3]}
circleStroke={colors.categories[3]}
circleFill="transparent"
/>
</ResponsiveXYChart>
),
},
{
description: 'PointSeries with Histogram',
components: [PointSeries],
Expand Down
2 changes: 1 addition & 1 deletion packages/demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"react-remarkable": "^1.1.1",
"react-with-styles": "^1.3.0",
"react-with-styles-interface-aphrodite": "^1.2.0",
"recompose": "^0.23.5",
"recompose": "^0.26.0",
"style-loader": "^0.18.2"
},
"peerDependencies": {
Expand Down
18 changes: 13 additions & 5 deletions packages/xy-chart/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ yScale | scaleShape.isRequired | - | scale config, see below.
showXGrid | PropTypes.bool | false | whether to show vertical gridlines
showYGrid | PropTypes.bool | false | whether to show vertical gridlines
theme | themeShape | false | theme shape, see below
useVoronoi | PropTypes.bool | false | whether to compute and use a voronoi for all datapoints / mouse interactions
useVoronoi | PropTypes.bool | false | whether to compute and use a voronoi for all datapoints (with x, y values) / mouse interactions
showVoronoi | PropTypes.bool | false | convenience prop for debugging to view the underlying voronoi if used


Expand All @@ -86,7 +86,9 @@ const scaleConfigShape = PropTypes.shape({
'band',
]).isRequired,
includeZero: PropTypes.bool,

// these would override any computation done by xyplot, allowing specific ranges or colors
// see storybook for more examples
range: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
rangeRound: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
domain: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number, PropTypes.string])),
Expand Down Expand Up @@ -157,17 +159,23 @@ tickValues | PropTypes.arrayOf( PropTypes.oneOfType([ PropTypes.number, PropType
Several types of series types are exported by the package, and can be used in combination. See the storybook source for more proptables for your series of interest. Here is an overview of scale support and data shapes:


Series | supported x scale type | supported y scale types | data shape | voronoi recommended for tooltips?
Series | supported x scale type | supported y scale types | data shape | voronoi compatible for tooltips?
------------ | ------------- | ------- | ---- | ----
`<AreaSeries/>` | time, linear | linear | `{ x, y [, fill, stroke] }` | yes
`<AreaSeries/>` | time, linear | linear | `{ x, y [, y0, y1, fill, stroke] }`* | yes*
`<BarSeries/>` | time, linear, band | linear | `{ x, y [, fill, stroke] }` | no
`<LineSeries/>` | time, linear | linear | `{ x, y [, stroke] }` | yes
`<PointSeries/>` | time, linear | time, linear | `{ x, y [size, fill, stroke, label] }` | yes
`<StackedBarSeries/>` | band | linear | `{ x, y }` (colors controlled with stackFills & stackKeys) | no
`<GroupedBarSeries/>` | band | linear | `{ x, y }` (colors controlled with groupFills & groupKeys) | no
`<CirclePackSeries/>` | time, linear | y is computed | `{ x [, size] }` | not compatible
`<CirclePackSeries/>` | time, linear | y is computed | `{ x [, size] }` | no
`<IntervalSeries/>` | time, linear | linear | `{ x0, x1 [, fill, stroke] }` | no

\* The y boundaries of the `<AreaSeries/>` may be specified by either
- defined `y0` and `y1` values or
- a single `y` value, in which case its lower bound is set to 0 (a "closed" area series)

It is worth noting that voronoi overlays require a defined `y` attribute, so use of voronoi with only `y0` and `y1` values will not work.

#### CirclePackSeries

<p align="center">
Expand Down Expand Up @@ -205,7 +213,7 @@ tooltipTimeout | PropTypes.number | 200 | Timeout in ms for the tooltip to hide
<img src="https://user-images.githubusercontent.com/4496521/29235861-015f9526-7eb8-11e7-964f-62301e5c6426.gif" width="500" />
</p>

For series components that have "small" mouse areas, such as `PointSeries` and `LineSeries`, you may opt to use an invisible <a href="https://github.com/hshoff/vx/tree/master/packages/vx-voronoi" target="_blank">Voronoi overlay</a> on top of the visualization to increase the target area of interaction sites and improve user experience. To enable this simply set `useVoronoi` to `true` on the `<XYChart />` component and optionally use the convenience prop `showVoronoi` to view or debug it. Note that this will compute a voronoi layout for _all_ data points across all series.
For series components that have "small" mouse areas, such as `PointSeries` and `LineSeries`, you may opt to use an invisible <a href="https://github.com/hshoff/vx/tree/master/packages/vx-voronoi" target="_blank">Voronoi overlay</a> on top of the visualization to increase the target area of interaction sites and improve user experience. To enable this simply set `useVoronoi` to `true` on the `<XYChart />` component and optionally use the convenience prop `showVoronoi` to view or debug it. Note that this will compute a voronoi layout for _all_ data points (with defined `x` and `y` datum values!) across all series.

#### Note ‼️
Because of the polygonal shapes generated by the voronoi layout, you probably _don't_ want to use this option if you are e.g., only rendering a `BarSeries` because the bar points represent the tops of the bars and thus polygons for one bar may overlap the rect of another bar (again, you may use `showVoronoi` to debug this).
Expand Down
8 changes: 5 additions & 3 deletions packages/xy-chart/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"license": "MIT",
"dependencies": {
"@data-ui/theme": "0.0.9",
"@vx/axis": "0.0.140",
"@vx/axis": "0.0.145",
"@vx/curve": "0.0.140",
"@vx/event": "0.0.140",
"@vx/glyph": "0.0.140",
Expand All @@ -33,7 +33,7 @@
"@vx/point": "0.0.136",
"@vx/responsive": "0.0.140",
"@vx/scale": "0.0.140",
"@vx/shape": "0.0.140",
"@vx/shape": "0.0.145",
"@vx/tooltip": "0.0.140",
"@vx/voronoi": "0.0.140",
"d3-array": "^1.2.0",
Expand All @@ -49,11 +49,13 @@
"enzyme-adapter-react-16": "^1.0.0",
"jest": "^20.0.3",
"react": "^16.0.0",
"react-dom": "^16.0.0",
"react-test-renderer": "^16.0.0",
"webpack": "^2.4.1"
},
"peerDependencies": {
"react": "^15.0.0-0 || ^16.0.0-0"
"react": "^15.0.0-0 || ^16.0.0-0",
"react-dom": "^15.0.0-0 || ^16.0.0-0"
},
"jest": {
"setupFiles": [
Expand Down
Loading

0 comments on commit 4c8bdd8

Please sign in to comment.