fix(charts): единый стиль AreaChart с градиентом
Общий AreaTrendChart для мониторинга и daily-графиков, StatisticSparklineCard на дашборде. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,68 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {
|
||||||
|
AreaChart,
|
||||||
|
Area,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
ResponsiveContainer,
|
||||||
|
} from 'recharts';
|
||||||
|
|
||||||
|
export interface AreaTrendSeries {
|
||||||
|
key: string;
|
||||||
|
color: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
data: Record<string, unknown>[];
|
||||||
|
xKey: string;
|
||||||
|
series: AreaTrendSeries[];
|
||||||
|
height?: number;
|
||||||
|
xMinTickGap?: number;
|
||||||
|
yAllowDecimals?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AreaTrendChart: React.FC<Props> = ({
|
||||||
|
data,
|
||||||
|
xKey,
|
||||||
|
series,
|
||||||
|
height = 280,
|
||||||
|
xMinTickGap = 30,
|
||||||
|
yAllowDecimals = true,
|
||||||
|
}) => (
|
||||||
|
<ResponsiveContainer width="100%" height={height}>
|
||||||
|
<AreaChart data={data}>
|
||||||
|
<defs>
|
||||||
|
{series.map((s) => (
|
||||||
|
<linearGradient key={s.key} id={`grad-${s.key}`} x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor={s.color} stopOpacity={0.4} />
|
||||||
|
<stop offset="95%" stopColor={s.color} stopOpacity={0.05} />
|
||||||
|
</linearGradient>
|
||||||
|
))}
|
||||||
|
</defs>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis dataKey={xKey} minTickGap={xMinTickGap} />
|
||||||
|
<YAxis allowDecimals={yAllowDecimals} />
|
||||||
|
<Tooltip />
|
||||||
|
<Legend />
|
||||||
|
{series.map((s) => (
|
||||||
|
<Area
|
||||||
|
key={s.key}
|
||||||
|
type="monotone"
|
||||||
|
dataKey={s.key}
|
||||||
|
stroke={s.color}
|
||||||
|
fill={`url(#grad-${s.key})`}
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={false}
|
||||||
|
activeDot={{ r: 3, strokeWidth: 0 }}
|
||||||
|
name={s.name}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</AreaChart>
|
||||||
|
</ResponsiveContainer>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default AreaTrendChart;
|
||||||
@@ -1,16 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Col } from 'antd';
|
import { Card, Col } from 'antd';
|
||||||
import {
|
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
ResponsiveContainer,
|
|
||||||
} from 'recharts';
|
|
||||||
import { DayCount } from '../types/api';
|
import { DayCount } from '../types/api';
|
||||||
|
import AreaTrendChart from './AreaTrendChart';
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -32,16 +23,13 @@ const DailyLineChartCard: React.FC<Props> = ({ title, data, color, lg = 12 }) =>
|
|||||||
return (
|
return (
|
||||||
<Col xs={24} lg={lg}>
|
<Col xs={24} lg={lg}>
|
||||||
<Card title={title}>
|
<Card title={title}>
|
||||||
<ResponsiveContainer width="100%" height={300}>
|
<AreaTrendChart
|
||||||
<LineChart data={chartData}>
|
data={chartData}
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
xKey="date"
|
||||||
<XAxis dataKey="date" />
|
series={[{ key: 'count', color, name: title }]}
|
||||||
<YAxis allowDecimals={false} />
|
height={300}
|
||||||
<Tooltip />
|
yAllowDecimals={false}
|
||||||
<Legend />
|
/>
|
||||||
<Line type="monotone" dataKey="count" stroke={color} name={title} />
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Card, Statistic } from 'antd';
|
||||||
|
import { AreaChart, Area, XAxis, Tooltip } from 'recharts';
|
||||||
|
|
||||||
|
interface SparklinePoint {
|
||||||
|
date: string;
|
||||||
|
value: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
title: string;
|
||||||
|
value: number;
|
||||||
|
prefix?: React.ReactNode;
|
||||||
|
data?: SparklinePoint[];
|
||||||
|
color?: string;
|
||||||
|
gradientId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StatisticSparklineCard: React.FC<Props> = ({
|
||||||
|
title,
|
||||||
|
value,
|
||||||
|
prefix,
|
||||||
|
data,
|
||||||
|
color = '#1890ff',
|
||||||
|
gradientId,
|
||||||
|
}) => (
|
||||||
|
<Card variant="outlined">
|
||||||
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
|
<Statistic
|
||||||
|
title={title}
|
||||||
|
value={value}
|
||||||
|
prefix={prefix}
|
||||||
|
style={{ flex: '0 0 auto', marginRight: 16 }}
|
||||||
|
/>
|
||||||
|
{data && data.length > 0 && (
|
||||||
|
<AreaChart
|
||||||
|
width={150}
|
||||||
|
height={50}
|
||||||
|
data={data}
|
||||||
|
margin={{ top: 5, right: 0, left: 0, bottom: 0 }}
|
||||||
|
>
|
||||||
|
<defs>
|
||||||
|
<linearGradient id={gradientId} x1="0" y1="0" x2="0" y2="1">
|
||||||
|
<stop offset="5%" stopColor={color} stopOpacity={0.4} />
|
||||||
|
<stop offset="95%" stopColor={color} stopOpacity={0.05} />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<XAxis dataKey="date" hide />
|
||||||
|
<Tooltip labelFormatter={(label) => `Дата: ${label}`} />
|
||||||
|
<Area
|
||||||
|
type="monotone"
|
||||||
|
dataKey="value"
|
||||||
|
stroke={color}
|
||||||
|
fill={`url(#${gradientId})`}
|
||||||
|
strokeWidth={2}
|
||||||
|
dot={false}
|
||||||
|
activeDot={{ r: 3, strokeWidth: 0 }}
|
||||||
|
/>
|
||||||
|
</AreaChart>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default StatisticSparklineCard;
|
||||||
@@ -1,18 +1,9 @@
|
|||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { Card, Col, Row, Select, Table, Tabs } from 'antd';
|
import { Card, Col, Row, Select, Table, Tabs } from 'antd';
|
||||||
import {
|
|
||||||
LineChart,
|
|
||||||
Line,
|
|
||||||
XAxis,
|
|
||||||
YAxis,
|
|
||||||
CartesianGrid,
|
|
||||||
Tooltip,
|
|
||||||
Legend,
|
|
||||||
ResponsiveContainer,
|
|
||||||
} from 'recharts';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import { useMetricsStore, NodeMetric } from '../../store/metricsStore';
|
import { useMetricsStore, NodeMetric } from '../../store/metricsStore';
|
||||||
import { monitoringApi } from '../../api/monitoringApi';
|
import { monitoringApi } from '../../api/monitoringApi';
|
||||||
|
import AreaTrendChart from '../../components/AreaTrendChart';
|
||||||
|
|
||||||
const NODE_COLORS = ['#1677ff', '#52c41a', '#fa8c16', '#eb2f96', '#722ed1', '#13c2c2'];
|
const NODE_COLORS = ['#1677ff', '#52c41a', '#fa8c16', '#eb2f96', '#722ed1', '#13c2c2'];
|
||||||
const TIME_RANGES = [
|
const TIME_RANGES = [
|
||||||
@@ -51,18 +42,7 @@ const MetricChart: React.FC<{
|
|||||||
data: ReturnType<typeof buildChartData>;
|
data: ReturnType<typeof buildChartData>;
|
||||||
lines: Array<{ key: string; color: string; name: string }>;
|
lines: Array<{ key: string; color: string; name: string }>;
|
||||||
}> = ({ data, lines }) => (
|
}> = ({ data, lines }) => (
|
||||||
<ResponsiveContainer width="100%" height={280}>
|
<AreaTrendChart data={data} xKey="time" series={lines} />
|
||||||
<LineChart data={data}>
|
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
|
||||||
<XAxis dataKey="time" minTickGap={30} />
|
|
||||||
<YAxis />
|
|
||||||
<Tooltip />
|
|
||||||
<Legend />
|
|
||||||
{lines.map((line) => (
|
|
||||||
<Line key={line.key} type="monotone" dataKey={line.key} stroke={line.color} name={line.name} dot={false} />
|
|
||||||
))}
|
|
||||||
</LineChart>
|
|
||||||
</ResponsiveContainer>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const MonitoringPage: React.FC = () => {
|
const MonitoringPage: React.FC = () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user