Skip to content

Commit

Permalink
feat: analytics
Browse files Browse the repository at this point in the history
  • Loading branch information
apexearth committed Sep 24, 2023
1 parent 1a2ea9e commit fd29292
Show file tree
Hide file tree
Showing 14 changed files with 425 additions and 0 deletions.
12 changes: 12 additions & 0 deletions libs/analytics/.babelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"presets": [
[
"@nx/react/babel",
{
"runtime": "automatic",
"useBuiltIns": "usage"
}
]
],
"plugins": []
}
18 changes: 18 additions & 0 deletions libs/analytics/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"extends": ["plugin:@nx/react", "../../.eslintrc.json"],
"ignorePatterns": ["!**/*"],
"overrides": [
{
"files": ["*.ts", "*.tsx", "*.js", "*.jsx"],
"rules": {}
},
{
"files": ["*.ts", "*.tsx"],
"rules": {}
},
{
"files": ["*.js", "*.jsx"],
"rules": {}
}
]
}
7 changes: 7 additions & 0 deletions libs/analytics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# analytics

This library was generated with [Nx](https://nx.dev).

## Running unit tests

Run `nx test analytics` to execute the unit tests via [Jest](https://jestjs.io).
16 changes: 16 additions & 0 deletions libs/analytics/project.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "analytics",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "libs/analytics/src",
"projectType": "library",
"tags": [],
"targets": {
"lint": {
"executor": "@nx/linter:eslint",
"outputs": ["{options.outputFile}"],
"options": {
"lintFilePatterns": ["libs/analytics/**/*.{ts,tsx,js,jsx}"]
}
}
}
}
1 change: 1 addition & 0 deletions libs/analytics/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './lib/analytics';
63 changes: 63 additions & 0 deletions libs/analytics/src/lib/analytics.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { faker } from '@faker-js/faker';
import { Container } from '@mui/material';
import dayjs from 'dayjs';

import { Analytics } from './analytics';

import type { Meta, StoryObj } from '@storybook/react';

faker.seed(4548);

function array<T>(n: number, initial: T, fn: (last: T) => T) {
let last: T = initial;
return new Array(n).fill(0).map((_, index) => {
if (index === 0) return last;
return (last = fn(last));
});
}

const ema = (p: number) => {
let last: number;
return (val: number) => {
return (last = (val + (last ?? val) * (p - 1)) / p);
};
};

const smooth = ema(7);

const meta: Meta<typeof Analytics> = {
component: Analytics,
title: 'Analytics/test',
args: {
title: 'APY',
data: {
datasets: [
{
type: 'line',
data: array(180, { x: new Date('2023-01-01'), y: 0.05 }, (last) => ({
x: dayjs(last.x).add(1, 'day').toDate(),
y: faker.number.float({
min: last.y * 0.9,
max: last.y * 1.1,
}),
})).map((d) => ({
x: d.x,
y: smooth(d.y),
})),
},
],
},
formatValues: (val) => {
return `${Math.floor(Number(val) * 10000) / 100}%`;
},
},
render: (args) => (
<Container>
<Analytics {...args} />
</Container>
),
};

export default meta;

export const Default: StoryObj<typeof Analytics> = {};
232 changes: 232 additions & 0 deletions libs/analytics/src/lib/analytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
import 'chartjs-adapter-dayjs-4/dist/chartjs-adapter-dayjs-4.esm';

import { useState } from 'react';

import { Box, Paper, Stack, useTheme } from '@mui/material';
import { Chart, TimeScale } from 'chart.js';
import dayjs from 'dayjs';
import { mergeDeepRight } from 'ramda';
import { Line } from 'react-chartjs-2';

import type { ChartData, ChartOptions, Plugin } from 'chart.js';
import type { ComponentProps } from 'react';

Chart.register(TimeScale);

/**
* TODO: Figure out a proper home for these?
*/
const chartTheme = {
primary1: '#CF75D5',
primary2: '#FEDBA8',
position: '#0074F0',
background: '#828699',
grid: '#2e2f3d',
positive: '#4EBE96',
negative: '#D44E66',
};

const full = { xs: 1, sm: 2, md: 3 };
const half = { xs: 0.5, sm: 1, md: 1.5 };
const quarter = { xs: 0.25, sm: 0.5, md: 0.75 };

export function Analytics(props: {
title: string;
titleProps: ComponentProps<typeof Box>;
data: ChartData<'line', { x: Date; y: number }[]>;
formatValues?: (value: number | string) => string | number;
}) {
if (!props.formatValues) {
props.formatValues = (value: number | string) => value;
}
const firstData = props.data.datasets.map((d) => d.data[0]);
const lastData = props.data.datasets.map((d) => d.data[d.data.length - 1]);
const [currentData, setCurrentData] = useState(lastData);
const [hovering, setHovering] = useState(false);
const change = currentData[0].y / firstData[0].y - 1;
return (
<Paper>
<Box sx={{ fontFamily: 'Inter' }}>
<Stack gap={full}>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
p={full}
>
<Stack>
<Box mb={half} fontSize={'0.875rem'} {...props.titleProps}>
{props.title}
</Box>
<Stack direction={'row'} mb={quarter} gap={0.5}>
<Box fontSize={'2.5rem'} color={'white'}>
{props.formatValues(currentData[0].y)}
</Box>
{change ? (
<Box
mt={0.5}
fontSize={'.75rem'}
color={
change > 0
? chartTheme.positive
: change < 0
? chartTheme.negative
: undefined
}
>
{change > 0 ? '+' : null}
{props.formatValues(change)}
</Box>
) : null}
</Stack>
<Box fontSize={'.75rem'} color={hovering ? '#ddd' : undefined}>
{dayjs(currentData[0].x).format('lll')}
</Box>
</Stack>
<Box fontSize={'0.875rem'}>Filter Options</Box>
</Stack>
<Box pr={half} pb={full}>
<DatedLineChart
data={props.data}
plugins={[
{
id: 'hooks',
afterDraw: (chart) => {
const elements = chart.getActiveElements();
if (elements.length) {
setCurrentData(
elements.map(
(element) =>
props.data.datasets[element.datasetIndex].data[
element.index
],
),
);
setHovering(true);
} else {
setCurrentData(lastData);
setHovering(false);
}
},
},
]}
options={{
scales: {
y: {
ticks: {
callback: props.formatValues,
},
},
},
}}
/>
</Box>
</Stack>
</Box>
</Paper>
);
}

export const DatedLineChart = (props: ComponentProps<typeof Line>) => {
const theme = useTheme();
let options: ChartOptions<'line'> = {
backgroundColor: chartTheme.background,
color: theme.palette.text.secondary,
responsive: true,
interaction: {
mode: 'nearest',
axis: 'x',
intersect: false,
},
animation: {
easing: 'easeInOutQuad',
},
scales: {
x: {
grid: {
display: false,
},
border: {
display: true,
color: chartTheme.grid,
},
type: 'time',
ticks: {
autoSkip: true,
autoSkipPadding: 50,
maxRotation: 0,
align: 'start',
},
time: {
unit: 'day',
},
},
y: {
grid: {
color: chartTheme.grid,
drawTicks: false,
},
border: {
display: false,
color: chartTheme.grid,
dash: [3, 3],
},
position: 'right',
ticks: {
crossAlign: 'far',
},
},
},
elements: {
line: {
borderWidth: 1,
},
point: {
radius: 0,
},
},
borderColor: (ctx) => {
const gradient = ctx.chart.ctx.createLinearGradient(
0,
0,
ctx.chart.width,
ctx.chart.height,
);
gradient.addColorStop(0, chartTheme.primary2);
gradient.addColorStop(1, chartTheme.primary1);
return gradient;
},
};
if (props.options) {
options = mergeDeepRight(props.options, options) as ChartOptions<'line'>;
}
return (
<Line
{...props}
plugins={[verticalLinePlugin, ...(props.plugins ?? [])]}
options={options}
data={props.data}
/>
);
};

const verticalLinePlugin: Plugin<'line'> = {
id: 'verticalLineAtIndex',
afterDraw: (chart) => {
const active = chart.getActiveElements();
if (active[0]) {
const ctx = chart.ctx;
const x = active[0].element.x;

ctx.save();
ctx.beginPath();
ctx.moveTo(x, chart.chartArea.top);
ctx.lineTo(x, chart.chartArea.bottom);
ctx.setLineDash([2, 2]);
ctx.strokeStyle = chartTheme.position;
ctx.lineWidth = 0.5;
ctx.stroke();
ctx.restore();
}
},
};
17 changes: 17 additions & 0 deletions libs/analytics/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"jsx": "react-jsx",
"allowJs": false,
"esModuleInterop": false,
"allowSyntheticDefaultImports": true,
"strict": true
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"extends": "../../tsconfig.base.json"
}
24 changes: 24 additions & 0 deletions libs/analytics/tsconfig.lib.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"types": [
"node",

"@nx/react/typings/cssmodule.d.ts",
"@nx/react/typings/image.d.ts"
]
},
"exclude": [
"jest.config.ts",
"src/**/*.spec.ts",
"src/**/*.test.ts",
"src/**/*.spec.tsx",
"src/**/*.test.tsx",
"src/**/*.spec.js",
"src/**/*.test.js",
"src/**/*.spec.jsx",
"src/**/*.test.jsx"
],
"include": ["src/**/*.js", "src/**/*.jsx", "src/**/*.ts", "src/**/*.tsx"]
}
Loading

0 comments on commit fd29292

Please sign in to comment.