1
0
Fork 0
mirror of https://github.com/muerwre/vault-frontend.git synced 2025-04-24 20:36:40 +07:00

made curves on basic graphs

This commit is contained in:
Fedor Katurov 2022-03-30 16:04:01 +07:00
parent ac533df3a3
commit c0f8766ec6
3 changed files with 140 additions and 16 deletions

View file

@ -1,40 +1,93 @@
import React, { VFC } from 'react'; import React, { useMemo, VFC } from 'react';
import { lighten } from 'color2k';
import { makeBezierCurve, PathPoint } from '~/utils/dom/makeBezierCurve';
import { SVGProps } from '~/utils/types'; import { SVGProps } from '~/utils/types';
interface BasicCurveChartProps extends SVGProps { interface BasicCurveChartProps extends SVGProps {
items: number[]; items: number[];
gap?: number;
fullscreen?: boolean;
} }
const gap = 5;
const BasicCurveChart: VFC<BasicCurveChartProps> = ({ const BasicCurveChart: VFC<BasicCurveChartProps> = ({
stroke = '#007962', stroke = '#007962',
items = [], items = [],
fullscreen = true,
...props ...props
}) => { }) => {
const max = Math.max(...items) + 5; const max = Math.max(...items);
const height = props.height ? parseFloat(props.height.toString()) : 100; const height = props.height ? parseFloat(props.height.toString()) : 100;
const width = props.width ? parseFloat(props.width.toString()) : 100; const width = props.width ? parseFloat(props.width.toString()) : 100;
const borderGap = fullscreen ? 0 : gap;
const d = items.reduce<string[]>( const points = useMemo(
(acc, val, index) => [ () =>
...acc, items.reduce<PathPoint[]>(
index === 0 (acc, val, index) => [
? `M 5 ${height - (val / max) * height}` ...acc,
: `L ${(width / (items.length - 1)) * index} ${height - (val / max) * height}`, index === 0
], ? { x: borderGap, y: height - (val / max) * (height - gap * 2) - gap }
[] : {
x: ((width - borderGap) / (items.length - 1)) * index,
y: height - (val / max) * (height - gap * 2) - gap,
},
],
[]
),
[height, width, items, gap]
); );
if (!points.length) {
return null;
}
return ( return (
<svg {...props} width={width} height={height} viewBox={`0 0 ${width} ${height}`}> <svg
{...props}
width="100%"
height="100%"
viewBox={`0 0 ${width} ${height}`}
preserveAspectRatio="none"
>
<defs> <defs>
<filter id="f1" x="0" y="0"> <filter id="f1" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur in="SourceGraphic" stdDeviation="2" /> <feGaussianBlur in="SourceGraphic" stdDeviation="2" />
</filter> </filter>
</defs> </defs>
<path d={d.join(' ')} fill="none" x={0} y={0} stroke={stroke} filter="url(#f1)" /> <path
<path d={d.join(' ')} fill="none" x={0} y={0} stroke={stroke} /> d={makeBezierCurve(points)}
fill="none"
x={0}
y={0}
stroke={stroke}
strokeWidth={2}
strokeLinecap="round"
filter="url(#f1)"
/>
<path
d={makeBezierCurve(points)}
fill="none"
x={0}
y={0}
stroke={stroke}
strokeWidth={2}
strokeLinecap="round"
/>
<path
d={makeBezierCurve(points)}
fill="none"
x={0}
y={0}
stroke={lighten(stroke, 0.1)}
strokeWidth={1}
strokeLinecap="round"
/>
</svg> </svg>
); );
}; };

View file

@ -6,6 +6,8 @@ import { BorisSidebar } from '~/components/boris/BorisSidebar';
import { Superpower } from '~/components/boris/Superpower'; import { Superpower } from '~/components/boris/Superpower';
import { BasicCurveChart } from '~/components/charts/BasicCurveChart'; import { BasicCurveChart } from '~/components/charts/BasicCurveChart';
import { Card } from '~/components/containers/Card'; import { Card } from '~/components/containers/Card';
import { Filler } from '~/components/containers/Filler';
import { Grid } from '~/components/containers/Grid';
import { Group } from '~/components/containers/Group'; import { Group } from '~/components/containers/Group';
import { Padder } from '~/components/containers/Padder'; import { Padder } from '~/components/containers/Padder';
import { Sticky } from '~/components/containers/Sticky'; import { Sticky } from '~/components/containers/Sticky';
@ -66,10 +68,19 @@ const BorisLayout: FC<IProps> = ({ title, setIsBetaTester, isTester, stats, isLo
<Group> <Group>
<h4>Количество нод за год</h4> <h4>Количество нод за год</h4>
<BasicCurveChart items={stats.backend.nodes.by_month} width={200} /> <Grid horizontal>
<Card style={{ padding: 0 }}>
<BasicCurveChart items={stats.backend.nodes.by_month} width={200} />
</Card>
<Card style={{ padding: 0 }}>
<BasicCurveChart items={stats.backend.comments.by_month} width={200} />
</Card>
<Filler />
</Grid>
<h4>Количество комментов за год</h4> <h4>Количество комментов за год</h4>
<BasicCurveChart items={stats.backend.comments.by_month} width={200} />
</Group> </Group>
</Group> </Group>
</Padder> </Padder>

View file

@ -0,0 +1,60 @@
// size of the tangent
const t = 1 / 5;
export interface PathPoint {
x: number;
y: number;
}
const controlPoints = (p: PathPoint[]) => {
const pc: PathPoint[][] = [];
for (let i = 1; i < p.length - 1; i++) {
const dx = p[i - 1].x - p[i + 1].x; // difference x
const dy = p[i - 1].y - p[i + 1].y; // difference y
// the first control point
const x1 = p[i].x - dx * t;
const y1 = p[i].y - dy * t;
const o1 = {
x: x1,
y: y1,
};
// the second control point
const x2 = p[i].x + dx * t;
const y2 = p[i].y + dy * t;
const o2 = {
x: x2,
y: y2,
};
// building the control points array
pc[i] = [];
pc[i].push(o1);
pc[i].push(o2);
}
return pc;
};
export const makeBezierCurve = (p: PathPoint[]) => {
const pc = controlPoints(p); // the control points array
let d = `M${p[0].x},${p[0].y} Q${pc[1][1].x},${pc[1][1].y}, ${p[1].x},${p[1].y}`;
if (p.length > 2) {
// central curves are cubic Bezier
for (let i = 1; i < p.length - 2; i++) {
d += `C${pc[i][0].x}, ${pc[i][0].y}, ${pc[i + 1][1].x}, ${pc[i + 1][1].y}, ${p[i + 1].x},${
p[i + 1].y
}`;
}
// the first & the last curve are quadratic Bezier
const n = p.length - 1;
d += `Q${pc[n - 1][0].x}, ${pc[n - 1][0].y}, ${p[n].x}, ${p[n].y}`;
}
return d;
};