Skip to content
Snippets Groups Projects
Unverified Commit 3dfbd23f authored by Shaan Khosla's avatar Shaan Khosla Committed by GitHub
Browse files

POC Recharts charting library (#1740)

parent 21effa65
No related branches found
No related tags found
No related merge requests found
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
......@@ -56,6 +56,7 @@
"react-simple-pull-to-refresh": "^1.3.3",
"react-spring": "^9.7.1",
"react-virtualized-auto-sizer": "^1.0.2",
"recharts": "^2.8.0",
"redux": "^4.0.5",
"redux-thunk": "^2.3.0",
"remark-gfm": "^3.0.1",
......
......@@ -38,6 +38,9 @@ function Card({ flex, to, style, children }) {
height: 200,
boxShadow: '0 2px 6px rgba(0, 0, 0, .15)',
transition: 'box-shadow .25s',
'& .recharts-surface:hover': {
cursor: 'pointer',
},
':hover': to && {
boxShadow: '0 4px 6px rgba(0, 0, 0, .15)',
},
......
import React, { createElement } from 'react';
import React from 'react';
import * as d from 'date-fns';
import { css } from 'glamor';
import {
VictoryChart,
VictoryBar,
VictoryArea,
VictoryAxis,
VictoryVoronoiContainer,
VictoryGroup,
} from 'victory';
AreaChart,
Area,
CartesianGrid,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
} from 'recharts';
import { theme } from '../../../style';
import { type CSSProperties } from '../../../style';
import { chartTheme } from '../chart-theme';
import AlignedText from '../../common/AlignedText';
import Container from '../Container';
import Tooltip from '../Tooltip';
import { Area } from './common';
type NetWorthGraphProps = {
style?: CSSProperties;
......@@ -25,13 +24,92 @@ type NetWorthGraphProps = {
y?: [number, number];
};
};
type PotentialNumber = number | string | undefined | null;
const numberFormatterTooltip = (value: PotentialNumber): number | null => {
if (typeof value === 'number') {
return Math.round(value);
}
return null; // or some default value for other cases
};
function NetWorthGraph({
style,
graphData,
compact,
domain,
}: NetWorthGraphProps) {
const Chart = compact ? VictoryGroup : VictoryChart;
const tickFormatter = tick => {
return `${Math.round(tick).toLocaleString()}`; // Formats the tick values as strings with commas
};
const gradientOffset = () => {
const dataMax = Math.max(...graphData.data.map(i => i.y));
const dataMin = Math.min(...graphData.data.map(i => i.y));
if (dataMax <= 0) {
return 0;
}
if (dataMin >= 0) {
return 1;
}
return dataMax / (dataMax - dataMin);
};
const off = gradientOffset();
type PayloadItem = {
payload: {
date: string;
assets: number | string;
debt: number | string;
networth: number | string;
change: number | string;
};
};
type CustomTooltipProps = {
active?: boolean;
payload?: PayloadItem[];
label?: string;
};
const CustomTooltip = ({ active, payload, label }: CustomTooltipProps) => {
if (active && payload && payload.length) {
return (
<div
className={`${css(
{
zIndex: 1000,
pointerEvents: 'none',
borderRadius: 2,
boxShadow: '0 1px 6px rgba(0, 0, 0, .20)',
backgroundColor: theme.alt2MenuBackground,
color: theme.alt2MenuItemText,
padding: 10,
},
style,
)}`}
>
<div>
<div style={{ marginBottom: 10 }}>
<strong>{payload[0].payload.date}</strong>
</div>
<div style={{ lineHeight: 1.5 }}>
<AlignedText left="Assets:" right={payload[0].payload.assets} />
<AlignedText left="Debt:" right={payload[0].payload.debt} />
<AlignedText
left="Net worth:"
right={<strong>{payload[0].payload.networth}</strong>}
/>
<AlignedText left="Change:" right={payload[0].payload.change} />
</div>
</div>
</div>
);
}
};
return (
<Container
......@@ -42,82 +120,59 @@ function NetWorthGraph({
>
{(width, height, portalHost) =>
graphData && (
<Chart
scale={{ x: 'time', y: 'linear' }}
theme={chartTheme}
domainPadding={{ x: 0, y: 10 }}
domain={domain}
width={width}
height={height}
containerComponent={
<VictoryVoronoiContainer voronoiDimension="x" />
}
padding={
compact && {
top: 0,
bottom: 0,
left: 0,
right: 0,
}
}
>
<Area start={graphData.start} end={graphData.end} />
{createElement(
// @ts-expect-error defaultProps mismatch causing issue
graphData.data.length === 1 ? VictoryBar : VictoryArea,
{
data: graphData.data,
labelComponent: <Tooltip portalHost={portalHost} />,
labels: x => x.premadeLabel,
style: {
data:
graphData.data.length === 1
? { width: 50 }
: {
clipPath: 'url(#positive)',
fill: 'url(#positive-gradient)',
},
},
},
)}
{graphData.data.length > 1 && (
<VictoryArea
<ResponsiveContainer>
<div>
{!compact && <div style={{ marginTop: '15px' }} />}
<AreaChart
width={width}
height={height}
data={graphData.data}
style={{
data: {
clipPath: 'url(#negative)',
fill: 'url(#negative-gradient)',
stroke: chartTheme.colors.red,
strokeLinejoin: 'round',
},
}}
/>
)}
{/* Somehow the path `d` attributes are stripped from second
`<VictoryArea />` above if this is removed. I’m just as
confused as you are! */}
<VictoryArea
data={graphData.data}
style={{ data: { fill: 'none', stroke: 'none' } }}
/>
{!compact && (
<VictoryAxis
style={{ ticks: { stroke: chartTheme.colors.red } }}
// eslint-disable-next-line rulesdir/typography
tickFormat={x => d.format(x, "MMM ''yy")}
tickValues={graphData.data.map(item => item.x)}
tickCount={Math.min(width / 220, graphData.data.length)}
offsetY={50}
/>
)}
{!compact && (
<VictoryAxis
dependentAxis
tickCount={Math.round(height / 70)}
crossAxis={!graphData.hasNegative}
/>
)}
</Chart>
margin={{ top: 0, right: 0, left: 0, bottom: 0 }}
>
{compact ? null : (
<CartesianGrid strokeDasharray="3 3" vertical={false} />
)}
{compact ? null : <XAxis dataKey="x" />}
{compact ? null : (
<YAxis
dataKey="y"
domain={['auto', 'auto']}
tickFormatter={tickFormatter}
/>
)}
<Tooltip
content={<CustomTooltip />}
formatter={numberFormatterTooltip}
isAnimationActive={false}
/>
<defs>
<linearGradient id="splitColor" x1="0" y1="0" x2="0" y2="1">
<stop
offset={off}
stopColor={theme.reportsBlue}
stopOpacity={0.2}
/>
<stop
offset={off}
stopColor={theme.reportsRed}
stopOpacity={0.2}
/>
</linearGradient>
</defs>
<Area
type="linear"
dot={false}
activeDot={false}
animationDuration={0}
dataKey="y"
stroke={theme.reportsBlue}
fill="url(#splitColor)"
fillOpacity={1}
/>
</AreaChart>
</div>
</ResponsiveContainer>
)
}
</Container>
......
import React from 'react';
import * as d from 'date-fns';
import q, { runQuery } from 'loot-core/src/client/query-helpers';
......@@ -11,7 +9,6 @@ import {
amountToInteger,
} from 'loot-core/src/shared/util';
import AlignedText from '../../common/AlignedText';
import { index } from '../util';
export default function createSpreadsheet(
......@@ -119,29 +116,20 @@ function recalculate(data, start, end) {
const x = d.parseISO(month + '-01');
const change = last ? total - amountToInteger(last.y) : 0;
const label = (
<div>
<div style={{ marginBottom: 10 }}>
<strong>{d.format(x, 'MMMM yyyy')}</strong>
</div>
<div style={{ lineHeight: 1.5 }}>
<AlignedText left="Assets:" right={integerToCurrency(assets)} />
<AlignedText left="Debt:" right={`-${integerToCurrency(debt)}`} />
<AlignedText
left="Net worth:"
right={<strong>{integerToCurrency(total)}</strong>}
/>
<AlignedText left="Change:" right={integerToCurrency(change)} />
</div>
</div>
);
if (arr.length === 0) {
startNetWorth = total;
}
endNetWorth = total;
arr.push({ x, y: integerToAmount(total), premadeLabel: label });
arr.push({
x: d.format(x, 'MMM ’yy'),
y: integerToAmount(total),
assets: integerToCurrency(assets),
debt: `-${integerToCurrency(debt)}`,
change: integerToCurrency(change),
networth: integerToCurrency(total),
date: d.format(x, 'MMMM yyyy'),
});
arr.forEach(item => {
if (item.y < lowestNetWorth || lowestNetWorth === null) {
......
---
category: Enhancements
authors: [shaankhosla]
---
Update the NetWorth graph to use the Recharts library.
\ No newline at end of file
......@@ -102,6 +102,7 @@ __metadata:
react-simple-pull-to-refresh: ^1.3.3
react-spring: ^9.7.1
react-virtualized-auto-sizer: ^1.0.2
recharts: ^2.8.0
redux: ^4.0.5
redux-thunk: ^2.3.0
remark-gfm: ^3.0.1
......@@ -1680,6 +1681,15 @@ __metadata:
languageName: node
linkType: hard
 
"@babel/runtime@npm:^7.1.2":
version: 7.23.1
resolution: "@babel/runtime@npm:7.23.1"
dependencies:
regenerator-runtime: ^0.14.0
checksum: 0cd0d43e6e7dc7f9152fda8c8312b08321cda2f56ef53d6c22ebdd773abdc6f5d0a69008de90aa41908d00e2c1facb24715ff121274e689305c858355ff02c70
languageName: node
linkType: hard
"@babel/runtime@npm:^7.11.2, @babel/runtime@npm:^7.12.5, @babel/runtime@npm:^7.14.8, @babel/runtime@npm:^7.16.3, @babel/runtime@npm:^7.20.7, @babel/runtime@npm:^7.21.0, @babel/runtime@npm:^7.5.5, @babel/runtime@npm:^7.8.4, @babel/runtime@npm:^7.9.2":
version: 7.22.11
resolution: "@babel/runtime@npm:7.22.11"
......@@ -6715,6 +6725,13 @@ __metadata:
languageName: node
linkType: hard
 
"classnames@npm:^2.2.5":
version: 2.3.2
resolution: "classnames@npm:2.3.2"
checksum: 2c62199789618d95545c872787137262e741f9db13328e216b093eea91c85ef2bfb152c1f9e63027204e2559a006a92eb74147d46c800a9f96297ae1d9f96f4e
languageName: node
linkType: hard
"clean-css@npm:^5.2.2":
version: 5.3.2
resolution: "clean-css@npm:5.3.2"
......@@ -7484,6 +7501,13 @@ __metadata:
languageName: node
linkType: hard
 
"css-unit-converter@npm:^1.1.1":
version: 1.1.2
resolution: "css-unit-converter@npm:1.1.2"
checksum: 07888033346a5128f34dbe2f72884c966d24e9f29db24416dcde92860242490617ef9a178ac193a92f730834bbeea026cdc7027701d92ba9bbbe59db7a37eb2a
languageName: node
linkType: hard
"css-what@npm:^3.2.1":
version: 3.4.2
resolution: "css-what@npm:3.4.2"
......@@ -7822,6 +7846,13 @@ __metadata:
languageName: node
linkType: hard
 
"decimal.js-light@npm:^2.4.1":
version: 2.5.1
resolution: "decimal.js-light@npm:2.5.1"
checksum: f5a2c7eac1c4541c8ab8a5c8abea64fc1761cefc7794bd5f8afd57a8a78d1b51785e0c4e4f85f4895a043eaa90ddca1edc3981d1263eb6ddce60f32bf5fe66c9
languageName: node
linkType: hard
"decimal.js@npm:^10.2.1":
version: 10.4.3
resolution: "decimal.js@npm:10.4.3"
......@@ -8321,6 +8352,15 @@ __metadata:
languageName: node
linkType: hard
 
"dom-helpers@npm:^3.4.0":
version: 3.4.0
resolution: "dom-helpers@npm:3.4.0"
dependencies:
"@babel/runtime": ^7.1.2
checksum: 58d9f1c4a96daf77eddc63ae1236b826e1cddd6db66bbf39b18d7e21896d99365b376593352d52a60969d67fa4a8dbef26adc1439fa2c1b355efa37cacbaf637
languageName: node
linkType: hard
"dom-serializer@npm:0":
version: 0.2.2
resolution: "dom-serializer@npm:0.2.2"
......@@ -9409,7 +9449,7 @@ __metadata:
languageName: node
linkType: hard
 
"eventemitter3@npm:^4.0.0":
"eventemitter3@npm:^4.0.0, eventemitter3@npm:^4.0.1":
version: 4.0.7
resolution: "eventemitter3@npm:4.0.7"
checksum: 1875311c42fcfe9c707b2712c32664a245629b42bb0a5a84439762dd0fd637fc54d078155ea83c2af9e0323c9ac13687e03cfba79b03af9f40c89b4960099374
......@@ -9646,6 +9686,13 @@ __metadata:
languageName: node
linkType: hard
 
"fast-equals@npm:^5.0.0":
version: 5.0.1
resolution: "fast-equals@npm:5.0.1"
checksum: fbb3b6a74f3a0fa930afac151ff7d01639159b4fddd2678b5d50708e0ba38e9ec14602222d10dadb8398187342692c04fbef5a62b1cfcc7942fe03e754e064bc
languageName: node
linkType: hard
"fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.12, fast-glob@npm:^3.2.9":
version: 3.2.12
resolution: "fast-glob@npm:3.2.12"
......@@ -16290,6 +16337,13 @@ __metadata:
languageName: node
linkType: hard
 
"postcss-value-parser@npm:^3.3.0":
version: 3.3.1
resolution: "postcss-value-parser@npm:3.3.1"
checksum: 62cd26e1cdbcf2dcc6bcedf3d9b409c9027bc57a367ae20d31dd99da4e206f730689471fd70a2abe866332af83f54dc1fa444c589e2381bf7f8054c46209ce16
languageName: node
linkType: hard
"postcss-value-parser@npm:^4.0.0, postcss-value-parser@npm:^4.1.0, postcss-value-parser@npm:^4.2.0":
version: 4.2.0
resolution: "postcss-value-parser@npm:4.2.0"
......@@ -16505,7 +16559,7 @@ __metadata:
languageName: node
linkType: hard
 
"prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
"prop-types@npm:^15.0.0, prop-types@npm:^15.5.10, prop-types@npm:^15.6.2, prop-types@npm:^15.7.2, prop-types@npm:^15.8.1":
version: 15.8.1
resolution: "prop-types@npm:15.8.1"
dependencies:
......@@ -16790,7 +16844,7 @@ __metadata:
languageName: node
linkType: hard
 
"react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.9.0":
"react-is@npm:^16.10.2, react-is@npm:^16.13.1, react-is@npm:^16.7.0, react-is@npm:^16.9.0":
version: 16.13.1
resolution: "react-is@npm:16.13.1"
checksum: f7a19ac3496de32ca9ae12aa030f00f14a3d45374f1ceca0af707c831b2a6098ef0d6bdae51bd437b0a306d7f01d4677fcc8de7c0d331eb47ad0f46130e53c5f
......@@ -16811,7 +16865,7 @@ __metadata:
languageName: node
linkType: hard
 
"react-lifecycles-compat@npm:^3.0.0":
"react-lifecycles-compat@npm:^3.0.0, react-lifecycles-compat@npm:^3.0.4":
version: 3.0.4
resolution: "react-lifecycles-compat@npm:3.0.4"
checksum: a904b0fc0a8eeb15a148c9feb7bc17cec7ef96e71188280061fc340043fd6d8ee3ff233381f0e8f95c1cf926210b2c4a31f38182c8f35ac55057e453d6df204f
......@@ -16901,6 +16955,18 @@ __metadata:
languageName: node
linkType: hard
 
"react-resize-detector@npm:^8.0.4":
version: 8.1.0
resolution: "react-resize-detector@npm:8.1.0"
dependencies:
lodash: ^4.17.21
peerDependencies:
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 45e6b87ea7331406bed2a806d0cea98c1467d53a7cfcdf19c2dd55a3460047917d3b175d9cceea6f314b65eb54858cbb981acffd007d67aa16388e517dafb83e
languageName: node
linkType: hard
"react-router-dom@npm:6.11.2":
version: 6.11.2
resolution: "react-router-dom@npm:6.11.2"
......@@ -17002,6 +17068,20 @@ __metadata:
languageName: node
linkType: hard
 
"react-smooth@npm:^2.0.2":
version: 2.0.4
resolution: "react-smooth@npm:2.0.4"
dependencies:
fast-equals: ^5.0.0
react-transition-group: 2.9.0
peerDependencies:
prop-types: ^15.6.0
react: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 21731e2f9ebc9594eae0f0d875526185392a87c00abf013c9769ed642a4077b62c04c1001b2527a196aabafb87af208f6c7107db674538c4bb95c253ed123447
languageName: node
linkType: hard
"react-spring@npm:^9.7.1":
version: 9.7.1
resolution: "react-spring@npm:9.7.1"
......@@ -17019,6 +17099,21 @@ __metadata:
languageName: node
linkType: hard
 
"react-transition-group@npm:2.9.0":
version: 2.9.0
resolution: "react-transition-group@npm:2.9.0"
dependencies:
dom-helpers: ^3.4.0
loose-envify: ^1.4.0
prop-types: ^15.6.2
react-lifecycles-compat: ^3.0.4
peerDependencies:
react: ">=15.0.0"
react-dom: ">=15.0.0"
checksum: d8c9e50aabdc2cfc324e5cdb0ad1c6eecb02e1c0cd007b26d5b30ccf49015e900683dd489348c71fba4055858308d9ba7019e0d37d0e8d37bd46ed098788f670
languageName: node
linkType: hard
"react-virtualized-auto-sizer@npm:^1.0.2":
version: 1.0.15
resolution: "react-virtualized-auto-sizer@npm:1.0.15"
......@@ -17140,6 +17235,36 @@ __metadata:
languageName: node
linkType: hard
 
"recharts-scale@npm:^0.4.4":
version: 0.4.5
resolution: "recharts-scale@npm:0.4.5"
dependencies:
decimal.js-light: ^2.4.1
checksum: e970377190a610e684a32c7461c7684ac3603c2e0ac0020bbba1eea9d099b38138143a8e80bf769bb49c0b7cecf22a2f5c6854885efed2d56f4540d4aa7052bd
languageName: node
linkType: hard
"recharts@npm:^2.8.0":
version: 2.8.0
resolution: "recharts@npm:2.8.0"
dependencies:
classnames: ^2.2.5
eventemitter3: ^4.0.1
lodash: ^4.17.19
react-is: ^16.10.2
react-resize-detector: ^8.0.4
react-smooth: ^2.0.2
recharts-scale: ^0.4.4
reduce-css-calc: ^2.1.8
victory-vendor: ^36.6.8
peerDependencies:
prop-types: ^15.6.0
react: ^16.0.0 || ^17.0.0 || ^18.0.0
react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0
checksum: 4638bd5c6c2af8f5c79de5e13cce0e38f06e0bbb0a3c4df27a9b12632fd72c0a0604c8246f55e830f323dfa84a3da7cb2634c2243bb9c775d899fd71f9d4c87a
languageName: node
linkType: hard
"rechoir@npm:^0.8.0":
version: 0.8.0
resolution: "rechoir@npm:0.8.0"
......@@ -17158,6 +17283,16 @@ __metadata:
languageName: node
linkType: hard
 
"reduce-css-calc@npm:^2.1.8":
version: 2.1.8
resolution: "reduce-css-calc@npm:2.1.8"
dependencies:
css-unit-converter: ^1.1.1
postcss-value-parser: ^3.3.0
checksum: 8fd27c06c4b443b84749a69a8b97d10e6ec7d142b625b41923a8807abb22b9e37e44df14e26cc606a802957be07bdce5e8ee2976a6952a7b438a7727007101e9
languageName: node
linkType: hard
"redux-thunk@npm:^2.3.0":
version: 2.4.2
resolution: "redux-thunk@npm:2.4.2"
......@@ -20438,6 +20573,28 @@ __metadata:
languageName: node
linkType: hard
 
"victory-vendor@npm:^36.6.8":
version: 36.6.11
resolution: "victory-vendor@npm:36.6.11"
dependencies:
"@types/d3-array": ^3.0.3
"@types/d3-ease": ^3.0.0
"@types/d3-interpolate": ^3.0.1
"@types/d3-scale": ^4.0.2
"@types/d3-shape": ^3.1.0
"@types/d3-time": ^3.0.0
"@types/d3-timer": ^3.0.0
d3-array: ^3.1.6
d3-ease: ^3.0.1
d3-interpolate: ^3.0.1
d3-scale: ^4.0.2
d3-shape: ^3.1.0
d3-time: ^3.0.0
d3-timer: ^3.0.1
checksum: 55800076dfa6abedf7758840986a302778a904678d4b66fe47d977c48b6f9484276b780871e6e5105b31c1eb936e9f1331ee39afcc2869bf65ceb7d456143172
languageName: node
linkType: hard
"victory-voronoi-container@npm:^36.6.10":
version: 36.6.10
resolution: "victory-voronoi-container@npm:36.6.10"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment