mirror of
https://github.com/muerwre/vault-frontend.git
synced 2025-04-25 04:46:40 +07:00
made curves on basic graphs
This commit is contained in:
parent
ac533df3a3
commit
c0f8766ec6
3 changed files with 140 additions and 16 deletions
|
@ -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(
|
||||||
|
() =>
|
||||||
|
items.reduce<PathPoint[]>(
|
||||||
(acc, val, index) => [
|
(acc, val, index) => [
|
||||||
...acc,
|
...acc,
|
||||||
index === 0
|
index === 0
|
||||||
? `M 5 ${height - (val / max) * height}`
|
? { x: borderGap, y: height - (val / max) * (height - gap * 2) - gap }
|
||||||
: `L ${(width / (items.length - 1)) * index} ${height - (val / max) * height}`,
|
: {
|
||||||
|
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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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>
|
||||||
|
<Grid horizontal>
|
||||||
|
<Card style={{ padding: 0 }}>
|
||||||
<BasicCurveChart items={stats.backend.nodes.by_month} width={200} />
|
<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>
|
||||||
|
|
60
src/utils/dom/makeBezierCurve.ts
Normal file
60
src/utils/dom/makeBezierCurve.ts
Normal 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;
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue