Skip to content

Commit

Permalink
feat(scatterplot): add support for layers to ScatterPlot component
Browse files Browse the repository at this point in the history
  • Loading branch information
Raphaël Benitte authored and Raphaël Benitte committed Nov 16, 2018
1 parent 3ecd510 commit f3a5a84
Show file tree
Hide file tree
Showing 5 changed files with 192 additions and 83 deletions.
194 changes: 112 additions & 82 deletions packages/scatterplot/src/ScatterPlot.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
import React, { Component } from 'react'
import React, { Component, Fragment } from 'react'
import { TransitionMotion, spring } from 'react-motion'
import setDisplayName from 'recompose/setDisplayName'
import { Container, SvgWrapper, Grid, CartesianMarkers } from '@nivo/core'
Expand Down Expand Up @@ -77,6 +77,8 @@ class ScatterPlot extends Component {
computedData,
points,

layers,

margin,
width,
height,
Expand Down Expand Up @@ -132,22 +134,21 @@ class ScatterPlot extends Component {
const onMouseMove = this.handleMouseMove(showTooltip)
const onMouseLeave = this.handleMouseLeave(hideTooltip)

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
theme={theme}
>
const layerById = {
grid: (
<Grid
key="grid"
theme={theme}
width={width}
height={height}
xScale={enableGridX ? xScale : null}
yScale={enableGridY ? yScale : null}
{...motionProps}
/>
),
axes: (
<Axes
key="axes"
xScale={xScale}
yScale={yScale}
width={width}
Expand All @@ -159,89 +160,118 @@ class ScatterPlot extends Component {
left={axisLeft}
{...motionProps}
/>
{!animate &&
points.map(point => (
<ScatterPlotItem
key={point.id}
point={point}
x={point.x}
y={point.y}
size={getSymbolSize(point.data)}
color={getColor(point.data)}
data={point.data}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={this.handleClick}
theme={theme}
/>
))}
{animate === true && (
<TransitionMotion
styles={points.map(point => ({
key: point.id,
data: point,
style: {
x: spring(point.x, springConfig),
y: spring(point.y, springConfig),
size: spring(getSymbolSize(point.data), springConfig),
},
}))}
>
{interpolatedStyles => (
<g>
{interpolatedStyles.map(
({ key, style, data: point }) => (
<ScatterPlotItem
key={key}
point={point}
x={style.x}
y={style.y}
size={style.size}
color={getColor(point.data)}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={this.handleClick}
theme={theme}
/>
)
)}
</g>
)}
</TransitionMotion>
)}
),
markers: (
<CartesianMarkers
key="markers"
markers={markers}
width={width}
height={height}
xScale={xScale}
yScale={yScale}
theme={theme}
/>
{isInteractive &&
useMesh && (
<Mesh
points={points}
width={width}
height={height}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={this.handleClick}
debug={debugMesh}
/>
),
mesh: null,
legends: legends.map((legend, i) => (
<BoxLegendSvg
key={i}
{...legend}
containerWidth={width}
containerHeight={height}
data={legendData}
theme={theme}
/>
)),
}

if (animate === true) {
layerById.points = (
<TransitionMotion
key="points"
styles={points.map(point => ({
key: point.id,
data: point,
style: {
x: spring(point.x, springConfig),
y: spring(point.y, springConfig),
size: spring(getSymbolSize(point.data), springConfig),
},
}))}
>
{interpolatedStyles => (
<g>
{interpolatedStyles.map(({ key, style, data: point }) => (
<ScatterPlotItem
key={key}
point={point}
x={style.x}
y={style.y}
size={style.size}
color={getColor(point.data)}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={this.handleClick}
theme={theme}
/>
))}
</g>
)}
{legends.map((legend, i) => (
<BoxLegendSvg
key={i}
{...legend}
containerWidth={width}
containerHeight={height}
data={legendData}
theme={theme}
/>
))}
</TransitionMotion>
)
} else {
layerById.points = points.map(point => (
<ScatterPlotItem
key={point.id}
point={point}
x={point.x}
y={point.y}
size={getSymbolSize(point.data)}
color={getColor(point.data)}
data={point.data}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={this.handleClick}
theme={theme}
/>
))
}

if (isInteractive === true && useMesh === true) {
layerById.mesh = (
<Mesh
key="mesh"
points={points}
width={width}
height={height}
onMouseEnter={onMouseEnter}
onMouseMove={onMouseMove}
onMouseLeave={onMouseLeave}
onClick={this.handleClick}
debug={debugMesh}
/>
)
}

return (
<SvgWrapper
width={outerWidth}
height={outerHeight}
margin={margin}
theme={theme}
>
{layers.map((layer, i) => {
if (typeof layer === 'function') {
return (
<Fragment key={i}>
{layer({ ...this.props, xScale, yScale })}
</Fragment>
)
}
return layerById[layer]
})}
</SvgWrapper>
)
}}
Expand Down
9 changes: 9 additions & 0 deletions packages/scatterplot/src/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ export const ScatterPlotPropTypes = {
yScale: PropTypes.func.isRequired,
}).isRequired,

layers: PropTypes.arrayOf(
PropTypes.oneOfType([
PropTypes.oneOf(['grid', 'axes', 'points', 'markers', 'mesh', 'legends']),
PropTypes.func,
])
).isRequired,

axisTop: axisPropType,
axisRight: axisPropType,
axisBottom: axisPropType,
Expand Down Expand Up @@ -91,6 +98,8 @@ export const ScatterPlotDefaultProps = {
max: 'auto',
},

layers: ['grid', 'axes', 'points', 'markers', 'mesh', 'legends'],

axisBottom: {},
axisLeft: {},
enableGridX: true,
Expand Down
53 changes: 53 additions & 0 deletions packages/scatterplot/stories/ScatterPlot.stories.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { Component } from 'react'
import omit from 'lodash/omit'
import { area, curveMonotoneX } from 'd3-shape'
import { storiesOf } from '@storybook/react'
import { withInfo } from '@storybook/addon-info'
import { ScatterPlot, ResponsiveScatterPlot } from '../index'
Expand Down Expand Up @@ -370,3 +371,55 @@ stories.add(
/>
))
)

const AreaLayer = ({ points, xScale, yScale }) => {
const areaGenerator = area()
.x(d => xScale(d.data.x))
.y0(d => yScale(d.data.low))
.y1(d => yScale(d.data.high))
.curve(curveMonotoneX)

return <path d={areaGenerator(points)} fill="rgba(232, 193, 160, .65)" />
}

stories.add(
'adding extra layers',
withInfo({
source: false,
text: `
You can use the layers property to add extra layers
to the scatterplot chart.
`,
})(() => (
<ScatterPlot
{...commonProps}
data={[
{
id: 'things',
data: [
{ x: 0, y: 3.3, low: 2.3, high: 4.2 },
{ x: 1, y: 3.5, low: 2.7, high: 4.1 },
{ x: 2, y: 3.8, low: 3.1, high: 4.6 },
{ x: 3, y: 4.1, low: 2.9, high: 4.5 },
{ x: 4, y: 4.4, low: 3.2, high: 5.1 },
{ x: 5, y: 4.7, low: 3.7, high: 5.4 },
{ x: 6, y: 4.9, low: 3.2, high: 5.8 },
{ x: 7, y: 5.2, low: 4.2, high: 6.1 },
{ x: 8, y: 5.4, low: 3.8, high: 6.7 },
{ x: 9, y: 5.6, low: 3.5, high: 7.1 },
{ x: 10, y: 5.8, low: 3.2, high: 6.8 },
{ x: 11, y: 6.0, low: 4, high: 7.2 },
{ x: 12, y: 6.2, low: 4.2, high: 9.1 },
{ x: 13, y: 6.4, low: 3.9, high: 9 },
],
},
]}
yScale={{
type: 'linear',
max: 10,
}}
legends={[]}
layers={['grid', 'axes', AreaLayer, 'points', 'markers', 'mesh', 'legends']}
/>
))
)
3 changes: 2 additions & 1 deletion website/src/components/charts/bar/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,8 @@ export default [
description: (
<div>
Defines the order of layers, available layers are:
<code>thing A</code>, <code>thing B</code>.<br />
<code>grid</code>, <code>axes</code>, <code>bars</code>, <code>markers</code>,{' '}
<code>legends</code>.<br />
You can also use this to insert extra layers to the chart, this extra layer must be
a function which will receive the chart computed data and must return a valid SVG
element.
Expand Down
16 changes: 16 additions & 0 deletions website/src/components/charts/scatterplot/props.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,22 @@ export default [
step: 5,
},
},
{
key: 'layers',
scopes: ['ScatterPlot'],
description: (
<div>
Defines the order of layers, available layers are:
<code>grid</code>, <code>axes</code>, <code>points</code>, <code>markers</code>,{' '}
<code>mesh</code>, <code>legends</code>.<br />
You can also use this to insert extra layers to the chart, this extra layer must be
a function which will receive the chart computed data and must return a valid SVG
element.
</div>
),
required: false,
default: defaults.layers,
},
{
key: 'pixelRatio',
scopes: ['ScatterPlotCanvas'],
Expand Down

0 comments on commit f3a5a84

Please sign in to comment.