Browse Source

Integrate new Chart in BalanceSummary & AccountCard

master
meriadec 7 years ago
parent
commit
8e6a141614
No known key found for this signature in database GPG Key ID: 1D2FC2305E2CB399
  1. 27
      src/components/BalanceSummary/index.js
  2. 9
      src/components/DashboardPage/AccountCard.js
  3. 23
      src/components/base/NewChart/Tooltip.js
  4. 13
      src/components/base/NewChart/handleMouseEvents.js
  5. 15
      src/components/base/NewChart/helpers.js
  6. 41
      src/components/base/NewChart/index.js
  7. 18
      src/components/base/NewChart/refreshDraw.js
  8. 6
      src/components/base/NewChart/refreshNodes.js
  9. 2
      src/helpers/balance.js

27
src/components/BalanceSummary/index.js

@ -7,9 +7,7 @@ import { formatShort, getFiatUnit } from '@ledgerhq/currencies'
import type { Accounts } from 'types/common' import type { Accounts } from 'types/common'
import { space } from 'styles/theme' import Chart from 'components/base/NewChart'
import { AreaChart } from 'components/base/Chart'
import Box, { Card } from 'components/base/Box' import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance' import CalculateBalance from 'components/CalculateBalance'
import FormattedVal from 'components/base/FormattedVal' import FormattedVal from 'components/base/FormattedVal'
@ -76,33 +74,24 @@ const BalanceSummary = ({
})} })}
</Box> </Box>
)} )}
<Box ff="Open Sans" fontSize={4} color="graphite"> <Box ff="Open Sans" fontSize={4} color="graphite" pt={6}>
<AreaChart <Chart
id={chartId}
color={chartColor} color={chartColor}
data={allBalances} data={allBalances}
height={250} height={250}
id={chartId} nbTicksX={getTickCountX(selectedTime)}
padding={{ renderTickX={renderTickX(selectedTime)}
top: space[6], renderTickY={t => formatShort(unit, t)}
bottom: space[6],
left: space[6] * 2,
right: space[6],
}}
strokeWidth={2}
renderLabels={d => d.y}
renderTooltip={d => ( renderTooltip={d => (
<FormattedVal <FormattedVal
alwaysShowSign={false} alwaysShowSign={false}
color="white" color="white"
showCode showCode
fiat={counterValue} fiat={counterValue}
val={d} val={d.value}
/> />
)} )}
renderTickX={renderTickX(selectedTime)}
renderTickY={t => formatShort(unit, t)}
tickCountX={getTickCountX(selectedTime)}
tickCountY={4}
/> />
</Box> </Box>
</Fragment> </Fragment>

9
src/components/DashboardPage/AccountCard.js

@ -5,7 +5,7 @@ import { getIconByCoinType } from '@ledgerhq/currencies/react'
import type { Account } from 'types/common' import type { Account } from 'types/common'
import { SimpleAreaChart } from 'components/base/Chart' import Chart from 'components/base/NewChart'
import Bar from 'components/base/Bar' import Bar from 'components/base/Bar'
import Box, { Card } from 'components/base/Box' import Box, { Card } from 'components/base/Box'
import CalculateBalance from 'components/CalculateBalance' import CalculateBalance from 'components/CalculateBalance'
@ -84,14 +84,13 @@ const AccountCard = ({
/> />
</Box> </Box>
</Box> </Box>
<SimpleAreaChart <Chart
data={allBalances} data={allBalances}
color={account.currency.color} color={account.currency.color}
height={52} height={52}
hideAxis
interactive={false}
id={`account-chart-${account.id}`} id={`account-chart-${account.id}`}
linearGradient={[[5, 0.2], [100, 0]]}
simple
strokeWidth={1.5}
/> />
</Box> </Box>
)} )}

23
src/components/base/NewChart/Tooltip.js

@ -1,6 +1,6 @@
// @flow // @flow
import React from 'react' import React, { Fragment } from 'react'
import { colors as themeColors } from 'styles/theme' import { colors as themeColors } from 'styles/theme'
import { TooltipContainer } from 'components/base/Tooltip' import { TooltipContainer } from 'components/base/Tooltip'
@ -8,10 +8,7 @@ import { TooltipContainer } from 'components/base/Tooltip'
import type { Item } from './types' import type { Item } from './types'
/** /**
* styled-components is not run on those components, because tooltip is * we use inline style for more perfs, as tooltip may re-render numerous times
* rendered as a string on d3 updates
*
* so, we use inline style.
*/ */
const Arrow = () => ( const Arrow = () => (
@ -32,7 +29,7 @@ const Arrow = () => (
</svg> </svg>
) )
export default ({ d }: { d: Item }) => ( const Tooltip = ({ d, renderTooltip }: { d: Item, renderTooltip?: Function }) => (
<div style={{ position: 'relative' }}> <div style={{ position: 'relative' }}>
<div <div
style={{ style={{
@ -45,10 +42,16 @@ export default ({ d }: { d: Item }) => (
}} }}
> >
<TooltipContainer style={{ textAlign: 'center' }}> <TooltipContainer style={{ textAlign: 'center' }}>
{renderTooltip ? (
renderTooltip(d)
) : (
<Fragment>
<div style={{ fontSize: 14 }}> <div style={{ fontSize: 14 }}>
<b>{Math.round(d.value)}</b> <b>{Math.round(d.value)}</b>
</div> </div>
{d.date} <span>{d.date}</span>
</Fragment>
)}
</TooltipContainer> </TooltipContainer>
<div style={{ background: 'red' }}> <div style={{ background: 'red' }}>
<Arrow /> <Arrow />
@ -56,3 +59,9 @@ export default ({ d }: { d: Item }) => (
</div> </div>
</div> </div>
) )
Tooltip.defaultProps = {
renderTooltip: undefined,
}
export default Tooltip

13
src/components/base/NewChart/handleMouseEvents.js

@ -3,6 +3,9 @@
import React from 'react' import React from 'react'
import * as d3 from 'd3' import * as d3 from 'd3'
import { renderToString } from 'react-dom/server' import { renderToString } from 'react-dom/server'
import { ThemeProvider } from 'styled-components'
import theme from 'styles/theme'
import type { Props } from '.' import type { Props } from '.'
import type { CTX } from './types' import type { CTX } from './types'
@ -14,11 +17,13 @@ export default function handleMouseEvents({
props, props,
shouldTooltipUpdate, shouldTooltipUpdate,
onTooltipUpdate, onTooltipUpdate,
renderTooltip,
}: { }: {
ctx: CTX, ctx: CTX,
props: Props, props: Props,
shouldTooltipUpdate: Function, shouldTooltipUpdate: Function,
onTooltipUpdate: Function, onTooltipUpdate: Function,
renderTooltip?: Function,
}) { }) {
const { MARGINS, HEIGHT, WIDTH, NODES, DATA, x, y } = ctx const { MARGINS, HEIGHT, WIDTH, NODES, DATA, x, y } = ctx
const { hideAxis } = props const { hideAxis } = props
@ -85,7 +90,13 @@ export default function handleMouseEvents({
onTooltipUpdate(d) onTooltipUpdate(d)
NODES.focus.attr('transform', `translate(${x(d.parsedDate)},${y(d.value)})`) NODES.focus.attr('transform', `translate(${x(d.parsedDate)},${y(d.value)})`)
NODES.tooltip NODES.tooltip
.html(renderToString(<Tooltip d={d.ref} />)) .html(
renderToString(
<ThemeProvider theme={theme}>
<Tooltip renderTooltip={renderTooltip} d={d.ref} />
</ThemeProvider>,
),
)
.style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, ${y(d.value)}px, 0)`) .style('transform', `translate3d(${MARGINS.left + x(d.parsedDate)}px, ${y(d.value)}px, 0)`)
if (!hideAxis) { if (!hideAxis) {
NODES.xBar NODES.xBar

15
src/components/base/NewChart/helpers.js

@ -21,12 +21,19 @@ export function generateColors(color) {
} }
export function generateMargins(hideAxis) { export function generateMargins(hideAxis) {
return { const margins = {
top: hideAxis ? 5 : 10, top: hideAxis ? 5 : 10,
bottom: hideAxis ? 5 : 40, bottom: hideAxis ? 5 : 30,
right: hideAxis ? 5 : 10, right: hideAxis ? 5 : 40,
left: hideAxis ? 5 : 70, left: hideAxis ? 5 : 80,
} }
// FIXME: Forced to "use" margins here to prevent babel/uglify to believe
// there is a constant variable re-assignment. I don't get it, but it
// works, so, eh.
void margins
return margins
} }
export function observeResize(node, cb) { export function observeResize(node, cb) {

41
src/components/base/NewChart/index.js

@ -51,6 +51,11 @@ export type Props = {
interactive?: boolean, // eslint-disable-line react/no-unused-prop-types interactive?: boolean, // eslint-disable-line react/no-unused-prop-types
height: number, height: number,
dateFormat?: string, // eslint-disable-line react/no-unused-prop-types dateFormat?: string, // eslint-disable-line react/no-unused-prop-types
id?: string, // eslint-disable-line react/no-unused-prop-types
nbTicksX: number, // eslint-disable-line react/no-unused-prop-types
renderTooltip?: Function, // eslint-disable-line react/no-unused-prop-types
renderTickX?: Function, // eslint-disable-line react/no-unused-prop-types
renderTickY?: Function, // eslint-disable-line react/no-unused-prop-types
} }
class Chart extends PureComponent<Props> { class Chart extends PureComponent<Props> {
@ -60,6 +65,8 @@ class Chart extends PureComponent<Props> {
interactive: true, interactive: true,
height: 400, height: 400,
dateFormat: '%Y-%m-%d', dateFormat: '%Y-%m-%d',
id: 'chart',
nbTicksX: 5,
} }
componentDidMount() { componentDidMount() {
@ -103,43 +110,36 @@ class Chart extends PureComponent<Props> {
this.refreshChart = prevProps => { this.refreshChart = prevProps => {
const { _node: node, props } = this const { _node: node, props } = this
const { data: raw, color, dateFormat, height, hideAxis, interactive } = props const { data: raw, color, dateFormat, height, hideAxis, interactive, renderTooltip } = props
let { COLORS, MARGINS } = ctx
const DATA = enrichData(raw, d3.timeParse(dateFormat)) ctx.DATA = enrichData(raw, d3.timeParse(dateFormat))
ctx.DATA = DATA
// Detect what needs to be updated // Detect what needs to be updated
const INVALIDATED = { ctx.INVALIDATED = {
color: firstRender || (prevProps && color !== prevProps.color), color: firstRender || (prevProps && color !== prevProps.color),
margin: firstRender || (prevProps && hideAxis !== prevProps.hideAxis), margin: firstRender || (prevProps && hideAxis !== prevProps.hideAxis),
} }
firstRender = false firstRender = false
ctx.INVALIDATED = INVALIDATED
// Reset color if needed // Reset color if needed
if (INVALIDATED.color) { if (ctx.INVALIDATED.color) {
COLORS = generateColors(color) ctx.COLORS = generateColors(color)
ctx.COLORS = COLORS
} }
// Reset margins if needed // Reset margins if needed
if (INVALIDATED.margin) { if (ctx.INVALIDATED.margin) {
MARGINS = generateMargins(hideAxis) ctx.MARGINS = generateMargins(hideAxis)
ctx.MARGINS = MARGINS
} }
// Derived draw variables // Derived draw variables
const HEIGHT = Math.max(0, height - MARGINS.top - MARGINS.bottom) ctx.HEIGHT = Math.max(0, height - ctx.MARGINS.top - ctx.MARGINS.bottom)
const WIDTH = Math.max(0, this._width - MARGINS.left - MARGINS.right) ctx.WIDTH = Math.max(0, this._width - ctx.MARGINS.left - ctx.MARGINS.right)
ctx.HEIGHT = HEIGHT
ctx.WIDTH = WIDTH
// Scales and areas // Scales and areas
const x = d3.scaleTime().range([0, WIDTH]) const x = d3.scaleTime().range([0, ctx.WIDTH])
const y = d3.scaleLinear().range([HEIGHT, 0]) const y = d3.scaleLinear().range([ctx.HEIGHT, 0])
x.domain(d3.extent(DATA, d => d.parsedDate)) x.domain(d3.extent(ctx.DATA, d => d.parsedDate))
y.domain([0, d3.max(DATA, d => d.value)]) y.domain([0, d3.max(ctx.DATA, d => d.value)])
ctx.x = x ctx.x = x
ctx.y = y ctx.y = y
@ -160,6 +160,7 @@ class Chart extends PureComponent<Props> {
props, props,
shouldTooltipUpdate: d => d !== lastDisplayedTooltip, shouldTooltipUpdate: d => d !== lastDisplayedTooltip,
onTooltipUpdate: d => (lastDisplayedTooltip = d), onTooltipUpdate: d => (lastDisplayedTooltip = d),
renderTooltip,
}) })
} }
} }

18
src/components/base/NewChart/refreshDraw.js

@ -9,7 +9,7 @@ import type { CTX } from './types'
export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) { export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props }) {
const { NODES, WIDTH, HEIGHT, MARGINS, COLORS, INVALIDATED, DATA, x, y } = ctx const { NODES, WIDTH, HEIGHT, MARGINS, COLORS, INVALIDATED, DATA, x, y } = ctx
const { hideAxis, interactive } = props const { hideAxis, interactive, renderTickX, renderTickY, nbTicksX } = props
const area = d3 const area = d3
.area() .area()
@ -61,8 +61,18 @@ export default function refreshDraw({ ctx, props }: { ctx: CTX, props: Props })
// Draw axis // Draw axis
if (!hideAxis) { if (!hideAxis) {
NODES.axisLeft.call(d3.axisLeft(y).ticks(3)) NODES.axisLeft.call(
NODES.axisBot.call(d3.axisBottom(x).ticks(5)) d3
.axisLeft(y)
.ticks(3)
.tickFormat(val => (renderTickY ? renderTickY(val) : val)),
)
NODES.axisBot.call(
d3
.axisBottom(x)
.ticks(nbTicksX)
.tickFormat(val => (renderTickX ? renderTickX(val) : val)),
)
stylizeAxis(NODES.axisLeft) stylizeAxis(NODES.axisLeft)
stylizeAxis(NODES.axisBot) stylizeAxis(NODES.axisBot)
} }
@ -99,7 +109,7 @@ function stylizeAxis(axis) {
axis.selectAll('path').attr('stroke', 'none') axis.selectAll('path').attr('stroke', 'none')
axis axis
.selectAll('text') .selectAll('text')
.attr('stroke', themeColors.smoke) .attr('stroke', themeColors.grey)
.style('font-size', '12px') .style('font-size', '12px')
.style('font-family', 'Open Sans') .style('font-family', 'Open Sans')
.style('font-weight', 300) .style('font-weight', 300)

6
src/components/base/NewChart/refreshNodes.js

@ -10,7 +10,7 @@ const debug = d('Chart')
export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any, props: Props }) { export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any, props: Props }) {
const { NODES, COLORS } = ctx const { NODES, COLORS } = ctx
const { hideAxis, interactive } = props const { hideAxis, interactive, id } = props
// Container // Container
@ -47,7 +47,7 @@ export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any
ensure({ NODES, key: 'gradient' }, () => ensure({ NODES, key: 'gradient' }, () =>
NODES.defs NODES.defs
.append('linearGradient') .append('linearGradient')
.attr('id', 'gradient') .attr('id', `gradient-${id || ''}`)
.attr('x1', '0%') .attr('x1', '0%')
.attr('x2', '0%') .attr('x2', '0%')
.attr('y1', '0%') .attr('y1', '0%')
@ -69,7 +69,7 @@ export default function refreshNodes({ ctx, node, props }: { ctx: CTX, node: any
) )
ensure({ NODES, key: 'fillArea' }, () => ensure({ NODES, key: 'fillArea' }, () =>
NODES.wrapper.append('path').attr('fill', 'url(#gradient)'), NODES.wrapper.append('path').attr('fill', `url(#gradient-${id || ''})`),
) )
// Line // Line

2
src/helpers/balance.js

@ -140,7 +140,7 @@ export default function calculateBalance(props: CalculateBalance) {
accounts: props.accounts, accounts: props.accounts,
counterValues: props.counterValues, counterValues: props.counterValues,
interval, interval,
}).map(e => ({ name: e.date, value: e.balance })) }).map(e => ({ date: e.date, value: e.balance }))
const firstNonEmptyDay = find(allBalances, e => e.value) const firstNonEmptyDay = find(allBalances, e => e.value)
const refBalance = firstNonEmptyDay ? firstNonEmptyDay.value : 0 const refBalance = firstNonEmptyDay ? firstNonEmptyDay.value : 0

Loading…
Cancel
Save