committed by
GitHub
34 changed files with 2477 additions and 934 deletions
@ -1,29 +0,0 @@ |
|||||
{ |
|
||||
"presets": [ |
|
||||
[ |
|
||||
"env", |
|
||||
{ |
|
||||
"loose": true, |
|
||||
"modules": false, |
|
||||
"targets": { |
|
||||
"electron": "1.8", |
|
||||
"node": "current" |
|
||||
} |
|
||||
} |
|
||||
], |
|
||||
"flow", |
|
||||
"react", |
|
||||
"stage-0" |
|
||||
], |
|
||||
"plugins": [["module-resolver", { "root": ["src"] }], "styled-components"], |
|
||||
"env": { |
|
||||
"test": { |
|
||||
"presets": [ |
|
||||
"env", |
|
||||
"stage-0", |
|
||||
"react", |
|
||||
], |
|
||||
"plugins": [["module-resolver", { "root": ["src"] }], "styled-components"], |
|
||||
} |
|
||||
} |
|
||||
} |
|
@ -0,0 +1,40 @@ |
|||||
|
const { NODE_ENV } = process.env |
||||
|
|
||||
|
const presets = [ |
||||
|
[ |
||||
|
'@babel/preset-env', |
||||
|
{ |
||||
|
loose: true, |
||||
|
modules: NODE_ENV === 'test' ? 'commonjs' : false, |
||||
|
targets: { |
||||
|
electron: '1.8', |
||||
|
node: 'current', |
||||
|
}, |
||||
|
}, |
||||
|
], |
||||
|
'@babel/preset-flow', |
||||
|
'@babel/preset-react', |
||||
|
'@babel/preset-stage-0', |
||||
|
] |
||||
|
|
||||
|
const plugins = [ |
||||
|
['babel-plugin-module-resolver', { root: ['src'] }], |
||||
|
[ |
||||
|
'babel-plugin-styled-components', |
||||
|
{ |
||||
|
displayName: NODE_ENV === 'development', |
||||
|
minify: NODE_ENV === 'production', |
||||
|
}, |
||||
|
], |
||||
|
] |
||||
|
|
||||
|
module.exports = { |
||||
|
presets, |
||||
|
plugins, |
||||
|
env: { |
||||
|
test: { |
||||
|
presets, |
||||
|
plugins, |
||||
|
}, |
||||
|
}, |
||||
|
} |
@ -1,5 +1,5 @@ |
|||||
#/bin/bash |
#/bin/bash |
||||
|
|
||||
rm -rf dist && |
rm -rf dist && |
||||
NODE_ENV=production webpack --config webpack/internals.config.js && |
NODE_ENV=production webpack-cli --config webpack/internals.config.js && |
||||
electron-webpack |
electron-webpack |
||||
|
@ -1,5 +1,5 @@ |
|||||
#/bin/bash |
#/bin/bash |
||||
|
|
||||
concurrently --raw \ |
concurrently --raw \ |
||||
"cross-env NODE_ENV=development webpack --watch --config webpack/internals.config.js" \ |
"cross-env NODE_ENV=development webpack-cli --watch --config webpack/internals.config.js" \ |
||||
"cross-env NODE_ENV=development electron-webpack dev" |
"cross-env NODE_ENV=development electron-webpack dev" |
||||
|
@ -1,371 +1,187 @@ |
|||||
// @flow
|
// @flow
|
||||
|
|
||||
/* eslint-disable react/no-multi-comp */ |
/** |
||||
|
* Chart |
||||
import React, { Fragment, Component, PureComponent } from 'react' |
* ----- |
||||
|
* |
||||
import VictoryChart from 'victory-chart/lib/components/victory-chart/victory-chart' |
* XX |
||||
import VictoryArea from 'victory-chart/lib/components/victory-area/victory-area' |
* XXXX |
||||
import VictoryAxis from 'victory-chart/lib/components/victory-axis/victory-axis' |
* X XX X |
||||
import VictoryTooltip from 'victory-core/lib/victory-tooltip/victory-tooltip' |
* XXXX XX X |
||||
import VictoryVoronoiContainer from 'victory-chart/lib/components/containers/victory-voronoi-container' |
* XX X XX X |
||||
|
* X XXXX X XX X |
||||
import { space, colors, fontSizes } from 'styles/theme' |
* XX XX X XX XX XX |
||||
|
* XX XX XX XXXX |
||||
import Box from 'components/base/Box' |
* XX |
||||
import Text from 'components/base/Text' |
* XX |
||||
import { TooltipContainer } from 'components/base/Tooltip' |
* Usage: |
||||
|
* |
||||
const ANIMATION_DURATION = 600 |
* <Chart |
||||
const DEFAULT_PROPS = { |
* data={data} |
||||
color: 'blue', |
* interactive // Handle mouse events, display tooltip etc.
|
||||
padding: 0, |
* color="#5f8ced" // Main color for line, gradient, etc.
|
||||
} |
* height={300} // Fix height. Width is responsive to container.
|
||||
|
* /> |
||||
type Props = { |
* |
||||
height: number, |
* `data` looks like: |
||||
render: Function, |
* |
||||
|
* [ |
||||
|
* { date: '2018-01-01', value: 10 }, |
||||
|
* { date: '2018-01-02', value: 25 }, |
||||
|
* { date: '2018-01-03', value: 50 }, |
||||
|
* ] |
||||
|
* |
||||
|
*/ |
||||
|
|
||||
|
import React, { PureComponent } from 'react' |
||||
|
import * as d3 from 'd3' |
||||
|
import noop from 'lodash/noop' |
||||
|
|
||||
|
import type { Unit } from '@ledgerhq/currencies' |
||||
|
|
||||
|
import refreshNodes from './refreshNodes' |
||||
|
import refreshDraw from './refreshDraw' |
||||
|
import handleMouseEvents from './handleMouseEvents' |
||||
|
import { enrichData, generateColors, generateMargins, observeResize } from './helpers' |
||||
|
|
||||
|
import type { Data } from './types' |
||||
|
|
||||
|
export type Props = { |
||||
|
data: Data, // eslint-disable-line react/no-unused-prop-types
|
||||
|
unit?: Unit, // eslint-disable-line react/no-unused-prop-types
|
||||
|
|
||||
|
id?: string, // eslint-disable-line react/no-unused-prop-types
|
||||
|
height?: number, |
||||
|
tickXScale: string, // eslint-disable-line react/no-unused-prop-types
|
||||
|
color?: string, // eslint-disable-line react/no-unused-prop-types
|
||||
|
hideAxis?: boolean, // eslint-disable-line react/no-unused-prop-types
|
||||
|
dateFormat?: string, // eslint-disable-line react/no-unused-prop-types
|
||||
|
interactive?: boolean, // eslint-disable-line react/no-unused-prop-types
|
||||
|
renderTooltip?: Function, // eslint-disable-line react/no-unused-prop-types
|
||||
} |
} |
||||
|
|
||||
type State = { |
class Chart extends PureComponent<Props> { |
||||
isAnimationActive: boolean, |
static defaultProps = { |
||||
width: number, |
color: '#000', |
||||
} |
dateFormat: '%Y-%m-%d', |
||||
|
height: 400, |
||||
export class WrapperChart extends PureComponent<Props, State> { |
hideAxis: false, |
||||
state = { |
id: 'chart', |
||||
isAnimationActive: true, |
interactive: true, |
||||
width: 0, |
tickXScale: 'month', |
||||
|
unit: undefined, |
||||
} |
} |
||||
|
|
||||
componentDidMount() { |
componentDidMount() { |
||||
this._timeout = setTimeout( |
const { width } = this._ruler.getBoundingClientRect() |
||||
() => |
this._width = width |
||||
this.setState({ |
this.createChart() |
||||
isAnimationActive: false, |
observeResize(this._ruler, width => { |
||||
}), |
if (width !== this._width) { |
||||
ANIMATION_DURATION * 2, |
this._width = width |
||||
) |
this.refreshChart(this.props) |
||||
|
|
||||
if (this._node) { |
|
||||
this._ro = new ResizeObserver(entries => { |
|
||||
const entry = entries.find(entry => this._node === entry.target) |
|
||||
if (entry) { |
|
||||
this.setState({ |
|
||||
width: entry.contentRect.width, |
|
||||
}) |
|
||||
} |
} |
||||
}) |
}) |
||||
|
|
||||
this._ro.observe(this._node) |
|
||||
} |
} |
||||
} |
|
||||
|
|
||||
componentWillUnmount() { |
|
||||
clearTimeout(this._timeout) |
|
||||
|
|
||||
if (this._ro) { |
componentDidUpdate(prevProps: Props) { |
||||
this._ro.disconnect() |
this.refreshChart(prevProps) |
||||
} |
|
||||
} |
} |
||||
|
|
||||
_ro = undefined |
_ruler: any |
||||
_node = undefined |
_node: any |
||||
_timeout = undefined |
_width: number |
||||
|
refreshChart: Function |
||||
|
|
||||
render() { |
createChart() { |
||||
const { render, height } = this.props |
const ctx = { |
||||
const { isAnimationActive, width } = this.state |
NODES: {}, |
||||
return ( |
INVALIDATED: {}, |
||||
<Box ff="Open Sans" innerRef={n => (this._node = n)} style={{ height }}> |
MARGINS: {}, |
||||
{render({ isAnimationActive, height, width })} |
COLORS: {}, |
||||
</Box> |
DATA: [], |
||||
) |
WIDTH: 0, |
||||
|
HEIGHT: 0, |
||||
|
x: noop, |
||||
|
y: noop, |
||||
} |
} |
||||
} |
|
||||
|
|
||||
function getLinearGradient({ |
let firstRender = true |
||||
linearGradient, |
|
||||
id, |
|
||||
color, |
|
||||
}: { |
|
||||
linearGradient: LinearGradient, |
|
||||
id: string, |
|
||||
color: string, |
|
||||
}) { |
|
||||
return linearGradient.length > 0 ? ( |
|
||||
<svg style={{ height: 0 }}> |
|
||||
<defs> |
|
||||
<linearGradient id={id} x1="0" y1="0" x2="0" y2="100%"> |
|
||||
{linearGradient.map((g, i) => ( |
|
||||
<stop |
|
||||
key={i} // eslint-disable-line react/no-array-index-key
|
|
||||
offset={`${g[0]}%`} |
|
||||
stopColor={color} |
|
||||
stopOpacity={g[1]} |
|
||||
/> |
|
||||
))} |
|
||||
</linearGradient> |
|
||||
</defs> |
|
||||
</svg> |
|
||||
) : null |
|
||||
} |
|
||||
|
|
||||
class CustomTooltip extends Component<any, any> { |
|
||||
static defaultEvents = VictoryTooltip.defaultEvents |
|
||||
|
|
||||
state = this.props |
|
||||
|
|
||||
componentWillMount() { |
|
||||
this._mounted = true |
|
||||
} |
|
||||
|
|
||||
componentWillReceiveProps(nextProps) { |
|
||||
this._shouldRender = false |
|
||||
this.updateState(nextProps) |
|
||||
} |
|
||||
|
|
||||
shouldComponentUpdate(nextProps) { |
// Keep reference to mouse handler to allow destroy when refresh
|
||||
const isActive = nextProps.active === true |
let mouseHandler = null |
||||
const wasActive = this.props.active === true && !nextProps.active |
|
||||
|
|
||||
return (isActive && this._shouldRender) || wasActive |
this.refreshChart = prevProps => { |
||||
} |
const { _node: node, props } = this |
||||
|
const { data: raw, color, dateFormat, height, hideAxis, interactive, renderTooltip } = props |
||||
|
|
||||
componentWillUnmount() { |
ctx.DATA = enrichData(raw, d3.timeParse(dateFormat)) |
||||
this._mounted = false |
|
||||
} |
|
||||
|
|
||||
updateState = props => |
// Detect what needs to be updated
|
||||
window.requestAnimationFrame(() => { |
ctx.INVALIDATED = { |
||||
this._shouldRender = true |
color: firstRender || (prevProps && color !== prevProps.color), |
||||
if (this._mounted) { |
margin: firstRender || (prevProps && hideAxis !== prevProps.hideAxis), |
||||
this.setState(props) |
|
||||
} |
} |
||||
}) |
firstRender = false |
||||
|
|
||||
_shouldRender = false |
|
||||
_mounted = false |
|
||||
|
|
||||
render() { |
// Reset color if needed
|
||||
const { strokeWidth, dotColor, x, y, active, text, datum, renderer } = this.props |
if (ctx.INVALIDATED.color) { |
||||
|
ctx.COLORS = generateColors(color) |
||||
if (!active) { |
|
||||
return null |
|
||||
} |
} |
||||
|
|
||||
return ( |
// Reset margins if needed
|
||||
<g> |
if (ctx.INVALIDATED.margin) { |
||||
<circle |
ctx.MARGINS = generateMargins(hideAxis) |
||||
cx={x} |
|
||||
cy={y + space[2]} |
|
||||
r={strokeWidth} |
|
||||
stroke={dotColor} |
|
||||
strokeWidth={strokeWidth} |
|
||||
fill={colors.white} |
|
||||
/> |
|
||||
<foreignObject> |
|
||||
<TooltipContainer |
|
||||
style={{ |
|
||||
position: 'absolute', |
|
||||
top: y - space[4], |
|
||||
left: x, |
|
||||
transform: `translate3d(-50%, 0, 0)`, |
|
||||
}} |
|
||||
> |
|
||||
<Text style={{ lineHeight: 1 }}>{renderer(text(datum))}</Text> |
|
||||
</TooltipContainer> |
|
||||
</foreignObject> |
|
||||
</g> |
|
||||
) |
|
||||
} |
} |
||||
} |
|
||||
|
|
||||
type LinearGradient = Array<Array<*>> |
// Derived draw variables
|
||||
|
ctx.HEIGHT = Math.max(0, (height || 0) - ctx.MARGINS.top - ctx.MARGINS.bottom) |
||||
|
ctx.WIDTH = Math.max(0, this._width - ctx.MARGINS.left - ctx.MARGINS.right) |
||||
|
|
||||
type GenericChart = { |
// Scales and areas
|
||||
id: string, |
const x = d3.scaleTime().range([0, ctx.WIDTH]) |
||||
linearGradient: LinearGradient, |
const y = d3.scaleLinear().range([ctx.HEIGHT, 0]) |
||||
strokeWidth: number, |
x.domain(d3.extent(ctx.DATA, d => d.parsedDate)) |
||||
height: number, |
y.domain([0, d3.max(ctx.DATA, d => d.value)]) |
||||
padding: Object | number, |
ctx.x = x |
||||
color: string, |
ctx.y = y |
||||
data: Array<Object>, |
|
||||
} |
|
||||
|
|
||||
export const SimpleAreaChart = ({ |
// Reference to last tooltip, to prevent un-necessary re-render
|
||||
linearGradient, |
let lastDisplayedTooltip = null |
||||
height, |
|
||||
data, |
|
||||
strokeWidth, |
|
||||
id, |
|
||||
padding, |
|
||||
color, |
|
||||
}: GenericChart) => ( |
|
||||
<WrapperChart |
|
||||
height={height} |
|
||||
render={({ width }) => ( |
|
||||
<Fragment> |
|
||||
{getLinearGradient({ |
|
||||
linearGradient, |
|
||||
id, |
|
||||
color, |
|
||||
})} |
|
||||
<VictoryArea |
|
||||
domainPadding={{ |
|
||||
y: [0, space[1]], |
|
||||
}} |
|
||||
data={data} |
|
||||
x="name" |
|
||||
y="value" |
|
||||
style={{ |
|
||||
data: { |
|
||||
stroke: color, |
|
||||
fill: `url(#${id})`, |
|
||||
strokeWidth, |
|
||||
}, |
|
||||
}} |
|
||||
padding={padding} |
|
||||
height={height} |
|
||||
width={width} |
|
||||
/> |
|
||||
</Fragment> |
|
||||
)} |
|
||||
/> |
|
||||
) |
|
||||
|
|
||||
SimpleAreaChart.defaultProps = { |
|
||||
height: 50, |
|
||||
id: 'simple-chart', |
|
||||
linearGradient: [], |
|
||||
strokeWidth: 1, |
|
||||
...DEFAULT_PROPS, |
|
||||
} |
|
||||
|
|
||||
type Chart = GenericChart & { |
// Add/remove nodes depending on props
|
||||
renderLabels: Function, |
refreshNodes({ ctx, node, props }) |
||||
renderTickX: Function, |
|
||||
renderTickY: Function, |
|
||||
renderTooltip: Function, |
|
||||
tickCountX: number, |
|
||||
tickCountY: number, |
|
||||
} |
|
||||
|
|
||||
const AreaChartContainer = <VictoryVoronoiContainer voronoiDimension="x" /> |
// Redraw
|
||||
|
refreshDraw({ ctx, props }) |
||||
|
|
||||
export class AreaChart extends PureComponent<Chart> { |
// Mouse handler
|
||||
static defaultProps = { |
mouseHandler && mouseHandler.remove() // eslint-disable-line no-unused-expressions
|
||||
height: 100, |
if (interactive) { |
||||
id: 'chart', |
mouseHandler = handleMouseEvents({ |
||||
linearGradient: [[5, 0.2], [100, 0]], |
ctx, |
||||
strokeWidth: 2, |
props, |
||||
renderLabels: (d: Object) => d.y, |
shouldTooltipUpdate: d => d !== lastDisplayedTooltip, |
||||
renderTickX: (t: any) => t, |
onTooltipUpdate: d => (lastDisplayedTooltip = d), |
||||
renderTickY: (t: any) => t, |
renderTooltip, |
||||
...DEFAULT_PROPS, |
}) |
||||
|
} |
||||
} |
} |
||||
|
|
||||
_tooltip = ( |
this.refreshChart() |
||||
<CustomTooltip |
|
||||
strokeWidth={this.props.strokeWidth} |
|
||||
dotColor={this.props.color} |
|
||||
renderer={this.props.renderTooltip} |
|
||||
/> |
|
||||
) |
|
||||
|
|
||||
render() { |
|
||||
const { |
|
||||
color, |
|
||||
data, |
|
||||
height, |
|
||||
id, |
|
||||
linearGradient, |
|
||||
padding, |
|
||||
renderLabels, |
|
||||
renderTickX, |
|
||||
renderTickY, |
|
||||
strokeWidth, |
|
||||
tickCountX, |
|
||||
tickCountY, |
|
||||
} = this.props |
|
||||
|
|
||||
const tickLabelsStyle = { |
|
||||
fill: colors.grey, |
|
||||
fontSize: fontSizes[4], |
|
||||
fontFamily: 'inherit', |
|
||||
fontWeight: 'inherit', |
|
||||
} |
} |
||||
|
|
||||
|
render() { |
||||
|
const { height } = this.props |
||||
return ( |
return ( |
||||
<WrapperChart |
<div style={{ position: 'relative', height }} ref={n => (this._ruler = n)}> |
||||
height={height} |
<div |
||||
render={({ width }) => ( |
style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} |
||||
<Fragment> |
ref={n => (this._node = n)} |
||||
{getLinearGradient({ |
|
||||
linearGradient, |
|
||||
id, |
|
||||
color, |
|
||||
})} |
|
||||
<VictoryChart |
|
||||
height={height} |
|
||||
width={width} |
|
||||
padding={padding} |
|
||||
domainPadding={{ |
|
||||
y: [0, space[1]], |
|
||||
}} |
|
||||
containerComponent={AreaChartContainer} |
|
||||
> |
|
||||
<VictoryAxis |
|
||||
animate={false} |
|
||||
tickCount={tickCountX} |
|
||||
tickFormat={renderTickX} |
|
||||
style={{ |
|
||||
axis: { |
|
||||
stroke: colors.fog, |
|
||||
}, |
|
||||
tickLabels: { |
|
||||
...tickLabelsStyle, |
|
||||
padding: space[2], |
|
||||
}, |
|
||||
}} |
|
||||
/> |
|
||||
<VictoryAxis |
|
||||
dependentAxis |
|
||||
tickCount={tickCountY} |
|
||||
tickFormat={renderTickY} |
|
||||
style={{ |
|
||||
grid: { |
|
||||
stroke: colors.fog, |
|
||||
strokeDasharray: 5, |
|
||||
}, |
|
||||
axis: { |
|
||||
stroke: null, |
|
||||
}, |
|
||||
tickLabels: { |
|
||||
...tickLabelsStyle, |
|
||||
padding: space[4], |
|
||||
}, |
|
||||
}} |
|
||||
/> |
|
||||
<VictoryArea |
|
||||
data={data} |
|
||||
x="name" |
|
||||
y="value" |
|
||||
labelComponent={this._tooltip} |
|
||||
labels={renderLabels} |
|
||||
style={{ |
|
||||
data: { |
|
||||
stroke: color, |
|
||||
fill: `url(#${id})`, |
|
||||
strokeWidth, |
|
||||
}, |
|
||||
}} |
|
||||
width={width} |
|
||||
/> |
|
||||
</VictoryChart> |
|
||||
</Fragment> |
|
||||
)} |
|
||||
/> |
/> |
||||
|
</div> |
||||
) |
) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
|
export default Chart |
||||
|
@ -1,186 +0,0 @@ |
|||||
// @flow
|
|
||||
|
|
||||
/** |
|
||||
* Chart |
|
||||
* ----- |
|
||||
* |
|
||||
* XX |
|
||||
* XXXX |
|
||||
* X XX X |
|
||||
* XXXX XX X |
|
||||
* XX X XX X |
|
||||
* X XXXX X XX X |
|
||||
* XX XX X XX XX XX |
|
||||
* XX XX XX XXXX |
|
||||
* XX |
|
||||
* XX |
|
||||
* Usage: |
|
||||
* |
|
||||
* <Chart |
|
||||
* data={data} |
|
||||
* interactive // Handle mouse events, display tooltip etc.
|
|
||||
* color="#5f8ced" // Main color for line, gradient, etc.
|
|
||||
* height={300} // Fix height. Width is responsive to container.
|
|
||||
* /> |
|
||||
* |
|
||||
* `data` looks like: |
|
||||
* |
|
||||
* [ |
|
||||
* { date: '2018-01-01', value: 10 }, |
|
||||
* { date: '2018-01-02', value: 25 }, |
|
||||
* { date: '2018-01-03', value: 50 }, |
|
||||
* ] |
|
||||
* |
|
||||
*/ |
|
||||
|
|
||||
import React, { PureComponent } from 'react' |
|
||||
import * as d3 from 'd3' |
|
||||
import noop from 'lodash/noop' |
|
||||
|
|
||||
import type { Unit } from '@ledgerhq/currencies' |
|
||||
|
|
||||
import refreshNodes from './refreshNodes' |
|
||||
import refreshDraw from './refreshDraw' |
|
||||
import handleMouseEvents from './handleMouseEvents' |
|
||||
import { enrichData, generateColors, generateMargins, observeResize } from './helpers' |
|
||||
|
|
||||
import type { Data } from './types' |
|
||||
|
|
||||
export type Props = { |
|
||||
data: Data, // eslint-disable-line react/no-unused-prop-types
|
|
||||
unit: Unit, // eslint-disable-line react/no-unused-prop-types
|
|
||||
|
|
||||
id?: string, // eslint-disable-line react/no-unused-prop-types
|
|
||||
height?: number, |
|
||||
tickXScale: string, // eslint-disable-line react/no-unused-prop-types
|
|
||||
color?: string, // eslint-disable-line react/no-unused-prop-types
|
|
||||
hideAxis?: boolean, // eslint-disable-line react/no-unused-prop-types
|
|
||||
dateFormat?: string, // eslint-disable-line react/no-unused-prop-types
|
|
||||
interactive?: boolean, // eslint-disable-line react/no-unused-prop-types
|
|
||||
renderTooltip?: Function, // eslint-disable-line react/no-unused-prop-types
|
|
||||
} |
|
||||
|
|
||||
class Chart extends PureComponent<Props> { |
|
||||
static defaultProps = { |
|
||||
id: 'chart', |
|
||||
color: '#000', |
|
||||
hideAxis: false, |
|
||||
interactive: true, |
|
||||
height: 400, |
|
||||
dateFormat: '%Y-%m-%d', |
|
||||
tickXScale: 'month', |
|
||||
} |
|
||||
|
|
||||
componentDidMount() { |
|
||||
const { width } = this._ruler.getBoundingClientRect() |
|
||||
this._width = width |
|
||||
this.createChart() |
|
||||
observeResize(this._ruler, width => { |
|
||||
if (width !== this._width) { |
|
||||
this._width = width |
|
||||
this.refreshChart(this.props) |
|
||||
} |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
componentDidUpdate(prevProps: Props) { |
|
||||
this.refreshChart(prevProps) |
|
||||
} |
|
||||
|
|
||||
_ruler: any |
|
||||
_node: any |
|
||||
_width: number |
|
||||
refreshChart: Function |
|
||||
|
|
||||
createChart() { |
|
||||
const ctx = { |
|
||||
NODES: {}, |
|
||||
INVALIDATED: {}, |
|
||||
MARGINS: {}, |
|
||||
COLORS: {}, |
|
||||
DATA: [], |
|
||||
WIDTH: 0, |
|
||||
HEIGHT: 0, |
|
||||
x: noop, |
|
||||
y: noop, |
|
||||
} |
|
||||
|
|
||||
let firstRender = true |
|
||||
|
|
||||
// Keep reference to mouse handler to allow destroy when refresh
|
|
||||
let mouseHandler = null |
|
||||
|
|
||||
this.refreshChart = prevProps => { |
|
||||
const { _node: node, props } = this |
|
||||
const { data: raw, color, dateFormat, height, hideAxis, interactive, renderTooltip } = props |
|
||||
|
|
||||
ctx.DATA = enrichData(raw, d3.timeParse(dateFormat)) |
|
||||
|
|
||||
// Detect what needs to be updated
|
|
||||
ctx.INVALIDATED = { |
|
||||
color: firstRender || (prevProps && color !== prevProps.color), |
|
||||
margin: firstRender || (prevProps && hideAxis !== prevProps.hideAxis), |
|
||||
} |
|
||||
firstRender = false |
|
||||
|
|
||||
// Reset color if needed
|
|
||||
if (ctx.INVALIDATED.color) { |
|
||||
ctx.COLORS = generateColors(color) |
|
||||
} |
|
||||
|
|
||||
// Reset margins if needed
|
|
||||
if (ctx.INVALIDATED.margin) { |
|
||||
ctx.MARGINS = generateMargins(hideAxis) |
|
||||
} |
|
||||
|
|
||||
// Derived draw variables
|
|
||||
ctx.HEIGHT = Math.max(0, (height || 0) - ctx.MARGINS.top - ctx.MARGINS.bottom) |
|
||||
ctx.WIDTH = Math.max(0, this._width - ctx.MARGINS.left - ctx.MARGINS.right) |
|
||||
|
|
||||
// Scales and areas
|
|
||||
const x = d3.scaleTime().range([0, ctx.WIDTH]) |
|
||||
const y = d3.scaleLinear().range([ctx.HEIGHT, 0]) |
|
||||
x.domain(d3.extent(ctx.DATA, d => d.parsedDate)) |
|
||||
y.domain([0, d3.max(ctx.DATA, d => d.value)]) |
|
||||
ctx.x = x |
|
||||
ctx.y = y |
|
||||
|
|
||||
// Reference to last tooltip, to prevent un-necessary re-render
|
|
||||
let lastDisplayedTooltip = null |
|
||||
|
|
||||
// Add/remove nodes depending on props
|
|
||||
refreshNodes({ ctx, node, props }) |
|
||||
|
|
||||
// Redraw
|
|
||||
refreshDraw({ ctx, props }) |
|
||||
|
|
||||
// Mouse handler
|
|
||||
mouseHandler && mouseHandler.remove() // eslint-disable-line no-unused-expressions
|
|
||||
if (interactive) { |
|
||||
mouseHandler = handleMouseEvents({ |
|
||||
ctx, |
|
||||
props, |
|
||||
shouldTooltipUpdate: d => d !== lastDisplayedTooltip, |
|
||||
onTooltipUpdate: d => (lastDisplayedTooltip = d), |
|
||||
renderTooltip, |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
this.refreshChart() |
|
||||
} |
|
||||
|
|
||||
render() { |
|
||||
const { height } = this.props |
|
||||
return ( |
|
||||
<div style={{ position: 'relative', height }} ref={n => (this._ruler = n)}> |
|
||||
<div |
|
||||
style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 0 }} |
|
||||
ref={n => (this._node = n)} |
|
||||
/> |
|
||||
</div> |
|
||||
) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default Chart |
|
@ -1,7 +1,9 @@ |
|||||
const plugins = require('./plugins') |
const plugins = require('./plugins') |
||||
|
const resolve = require('./resolve') |
||||
|
|
||||
const config = { |
const config = { |
||||
plugins: plugins('main'), |
plugins: plugins('main'), |
||||
|
resolve, |
||||
} |
} |
||||
|
|
||||
module.exports = config |
module.exports = config |
||||
|
@ -0,0 +1,5 @@ |
|||||
|
const path = require('path') |
||||
|
|
||||
|
module.exports = { |
||||
|
modules: [path.resolve(__dirname, '../src'), path.resolve(__dirname, '../node_modules')], |
||||
|
} |
File diff suppressed because it is too large
Loading…
Reference in new issue