Meriadec Pillet
7 years ago
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 |
|||
|
|||
rm -rf dist && |
|||
NODE_ENV=production webpack --config webpack/internals.config.js && |
|||
NODE_ENV=production webpack-cli --config webpack/internals.config.js && |
|||
electron-webpack |
|||
|
@ -1,5 +1,5 @@ |
|||
#/bin/bash |
|||
|
|||
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" |
|||
|
@ -1,371 +1,187 @@ |
|||
// @flow
|
|||
|
|||
/* eslint-disable react/no-multi-comp */ |
|||
|
|||
import React, { Fragment, Component, PureComponent } from 'react' |
|||
|
|||
import VictoryChart from 'victory-chart/lib/components/victory-chart/victory-chart' |
|||
import VictoryArea from 'victory-chart/lib/components/victory-area/victory-area' |
|||
import VictoryAxis from 'victory-chart/lib/components/victory-axis/victory-axis' |
|||
import VictoryTooltip from 'victory-core/lib/victory-tooltip/victory-tooltip' |
|||
import VictoryVoronoiContainer from 'victory-chart/lib/components/containers/victory-voronoi-container' |
|||
|
|||
import { space, colors, fontSizes } from 'styles/theme' |
|||
|
|||
import Box from 'components/base/Box' |
|||
import Text from 'components/base/Text' |
|||
import { TooltipContainer } from 'components/base/Tooltip' |
|||
|
|||
const ANIMATION_DURATION = 600 |
|||
const DEFAULT_PROPS = { |
|||
color: 'blue', |
|||
padding: 0, |
|||
} |
|||
|
|||
type Props = { |
|||
height: number, |
|||
render: Function, |
|||
} |
|||
|
|||
type State = { |
|||
isAnimationActive: boolean, |
|||
width: number, |
|||
/** |
|||
* 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
|
|||
} |
|||
|
|||
export class WrapperChart extends PureComponent<Props, State> { |
|||
state = { |
|||
isAnimationActive: true, |
|||
width: 0, |
|||
class Chart extends PureComponent<Props> { |
|||
static defaultProps = { |
|||
color: '#000', |
|||
dateFormat: '%Y-%m-%d', |
|||
height: 400, |
|||
hideAxis: false, |
|||
id: 'chart', |
|||
interactive: true, |
|||
tickXScale: 'month', |
|||
unit: undefined, |
|||
} |
|||
|
|||
componentDidMount() { |
|||
this._timeout = setTimeout( |
|||
() => |
|||
this.setState({ |
|||
isAnimationActive: false, |
|||
}), |
|||
ANIMATION_DURATION * 2, |
|||
) |
|||
|
|||
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) { |
|||
this._ro.disconnect() |
|||
} |
|||
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) |
|||
} |
|||
}) |
|||
} |
|||
|
|||
_ro = undefined |
|||
_node = undefined |
|||
_timeout = undefined |
|||
|
|||
render() { |
|||
const { render, height } = this.props |
|||
const { isAnimationActive, width } = this.state |
|||
return ( |
|||
<Box ff="Open Sans" innerRef={n => (this._node = n)} style={{ height }}> |
|||
{render({ isAnimationActive, height, width })} |
|||
</Box> |
|||
) |
|||
componentDidUpdate(prevProps: Props) { |
|||
this.refreshChart(prevProps) |
|||
} |
|||
} |
|||
|
|||
function getLinearGradient({ |
|||
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 |
|||
} |
|||
_ruler: any |
|||
_node: any |
|||
_width: number |
|||
refreshChart: Function |
|||
|
|||
createChart() { |
|||
const ctx = { |
|||
NODES: {}, |
|||
INVALIDATED: {}, |
|||
MARGINS: {}, |
|||
COLORS: {}, |
|||
DATA: [], |
|||
WIDTH: 0, |
|||
HEIGHT: 0, |
|||
x: noop, |
|||
y: noop, |
|||
} |
|||
|
|||
componentWillReceiveProps(nextProps) { |
|||
this._shouldRender = false |
|||
this.updateState(nextProps) |
|||
} |
|||
let firstRender = true |
|||
|
|||
shouldComponentUpdate(nextProps) { |
|||
const isActive = nextProps.active === true |
|||
const wasActive = this.props.active === true && !nextProps.active |
|||
// Keep reference to mouse handler to allow destroy when refresh
|
|||
let mouseHandler = null |
|||
|
|||
return (isActive && this._shouldRender) || wasActive |
|||
} |
|||
this.refreshChart = prevProps => { |
|||
const { _node: node, props } = this |
|||
const { data: raw, color, dateFormat, height, hideAxis, interactive, renderTooltip } = props |
|||
|
|||
componentWillUnmount() { |
|||
this._mounted = false |
|||
} |
|||
ctx.DATA = enrichData(raw, d3.timeParse(dateFormat)) |
|||
|
|||
updateState = props => |
|||
window.requestAnimationFrame(() => { |
|||
this._shouldRender = true |
|||
if (this._mounted) { |
|||
this.setState(props) |
|||
// Detect what needs to be updated
|
|||
ctx.INVALIDATED = { |
|||
color: firstRender || (prevProps && color !== prevProps.color), |
|||
margin: firstRender || (prevProps && hideAxis !== prevProps.hideAxis), |
|||
} |
|||
}) |
|||
firstRender = false |
|||
|
|||
_shouldRender = false |
|||
_mounted = false |
|||
// Reset color if needed
|
|||
if (ctx.INVALIDATED.color) { |
|||
ctx.COLORS = generateColors(color) |
|||
} |
|||
|
|||
render() { |
|||
const { strokeWidth, dotColor, x, y, active, text, datum, renderer } = this.props |
|||
// Reset margins if needed
|
|||
if (ctx.INVALIDATED.margin) { |
|||
ctx.MARGINS = generateMargins(hideAxis) |
|||
} |
|||
|
|||
if (!active) { |
|||
return null |
|||
// 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, |
|||
}) |
|||
} |
|||
} |
|||
|
|||
return ( |
|||
<g> |
|||
<circle |
|||
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<*>> |
|||
|
|||
type GenericChart = { |
|||
id: string, |
|||
linearGradient: LinearGradient, |
|||
strokeWidth: number, |
|||
height: number, |
|||
padding: Object | number, |
|||
color: string, |
|||
data: Array<Object>, |
|||
} |
|||
|
|||
export const SimpleAreaChart = ({ |
|||
linearGradient, |
|||
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 & { |
|||
renderLabels: Function, |
|||
renderTickX: Function, |
|||
renderTickY: Function, |
|||
renderTooltip: Function, |
|||
tickCountX: number, |
|||
tickCountY: number, |
|||
} |
|||
|
|||
const AreaChartContainer = <VictoryVoronoiContainer voronoiDimension="x" /> |
|||
|
|||
export class AreaChart extends PureComponent<Chart> { |
|||
static defaultProps = { |
|||
height: 100, |
|||
id: 'chart', |
|||
linearGradient: [[5, 0.2], [100, 0]], |
|||
strokeWidth: 2, |
|||
renderLabels: (d: Object) => d.y, |
|||
renderTickX: (t: any) => t, |
|||
renderTickY: (t: any) => t, |
|||
...DEFAULT_PROPS, |
|||
this.refreshChart() |
|||
} |
|||
|
|||
_tooltip = ( |
|||
<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', |
|||
} |
|||
|
|||
const { height } = this.props |
|||
return ( |
|||
<WrapperChart |
|||
height={height} |
|||
render={({ width }) => ( |
|||
<Fragment> |
|||
{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 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,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 resolve = require('./resolve') |
|||
|
|||
const config = { |
|||
plugins: plugins('main'), |
|||
resolve, |
|||
} |
|||
|
|||
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