改回折线图:数据点与日期标签精确对齐,添加圆圈标记
This commit is contained in:
parent
8a9e0226d0
commit
316fec8ac8
|
|
@ -19,7 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||||
|
|
||||||
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
import React, { useMemo, useState, useRef, useEffect } from 'react';
|
||||||
import { TrendingUp, RefreshCw, ChevronDown } from 'lucide-react';
|
import { TrendingUp, RefreshCw, ChevronDown } from 'lucide-react';
|
||||||
import BarChart from './BarChart';
|
import Sparkline from './Sparkline';
|
||||||
import { renderQuota } from '../../helpers';
|
import { renderQuota } from '../../helpers';
|
||||||
|
|
||||||
const formatMD = (d) => {
|
const formatMD = (d) => {
|
||||||
|
|
@ -224,7 +224,7 @@ const BentoTrend = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex min-h-[80px] flex-1 items-end text-blue-500'>
|
<div className='flex min-h-[80px] flex-1 items-end text-blue-500'>
|
||||||
<BarChart data={counts} height={80} barW={26} />
|
<Sparkline data={counts} height={80} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='flex items-center justify-between text-[11px]'>
|
<div className='flex items-center justify-between text-[11px]'>
|
||||||
|
|
|
||||||
|
|
@ -1,68 +1,61 @@
|
||||||
/*
|
|
||||||
Copyright (C) 2025 QuantumNous
|
|
||||||
|
|
||||||
This program is free software: you can redistribute it and/or modify
|
|
||||||
it under the terms of the GNU Affero General Public License as
|
|
||||||
published by the Free Software Foundation, either version 3 of the
|
|
||||||
License, or (at your option) any later version.
|
|
||||||
|
|
||||||
This program is distributed in the hope that it will be useful,
|
|
||||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
GNU Affero General Public License for more details.
|
|
||||||
|
|
||||||
You should have received a copy of the GNU Affero General Public License
|
|
||||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
For commercial licensing, please contact support@quantumnous.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
const Sparkline = ({
|
const Sparkline = ({ data = [], height = 80, stroke = 'currentColor' }) => {
|
||||||
data = [],
|
|
||||||
width = 200,
|
|
||||||
height = 40,
|
|
||||||
stroke = 'currentColor',
|
|
||||||
className = '',
|
|
||||||
}) => {
|
|
||||||
if (!data || data.length === 0) return null;
|
if (!data || data.length === 0) return null;
|
||||||
|
|
||||||
const max = Math.max(...data, 1);
|
const max = Math.max(...data, 1);
|
||||||
const step = data.length > 1 ? width / (data.length - 1) : 0;
|
const n = data.length;
|
||||||
|
const w = 400;
|
||||||
|
|
||||||
const points = data
|
const points = data
|
||||||
.map((v, i) => {
|
.map((v, i) => {
|
||||||
const x = i * step;
|
const x = n > 1 ? (i / (n - 1)) * w : w / 2;
|
||||||
const y = height - (v / max) * (height - 4) - 2;
|
const y = height - (v / max) * (height - 8) - 4;
|
||||||
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
return `${x.toFixed(1)},${y.toFixed(1)}`;
|
||||||
})
|
})
|
||||||
.join(' ');
|
.join(' ');
|
||||||
|
|
||||||
const areaPoints = `0,${height} ${points} ${width},${height}`;
|
const areaPoints = `0,${height} ${points} ${w},${height}`;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
width='100%'
|
width='100%'
|
||||||
height={height}
|
height={height}
|
||||||
viewBox={`0 0 ${width} ${height}`}
|
viewBox={`0 0 ${w} ${height}`}
|
||||||
preserveAspectRatio='none'
|
preserveAspectRatio='none'
|
||||||
className={className}
|
|
||||||
>
|
>
|
||||||
<defs>
|
<defs>
|
||||||
<linearGradient id='sparkline-gradient' x1='0%' y1='0%' x2='0%' y2='100%'>
|
<linearGradient id='sg' x1='0%' y1='0%' x2='0%' y2='100%'>
|
||||||
<stop offset='0%' stopColor={stroke} stopOpacity='0.18' />
|
<stop offset='0%' stopColor={stroke} stopOpacity='0.15' />
|
||||||
<stop offset='100%' stopColor={stroke} stopOpacity='0' />
|
<stop offset='100%' stopColor={stroke} stopOpacity='0' />
|
||||||
</linearGradient>
|
</linearGradient>
|
||||||
</defs>
|
</defs>
|
||||||
<polygon points={areaPoints} fill='url(#sparkline-gradient)' />
|
<polygon points={areaPoints} fill='url(#sg)' />
|
||||||
<polyline
|
<polyline
|
||||||
points={points}
|
points={points}
|
||||||
fill='none'
|
fill='none'
|
||||||
stroke={stroke}
|
stroke={stroke}
|
||||||
strokeWidth='2'
|
strokeWidth='2.5'
|
||||||
strokeLinecap='round'
|
strokeLinecap='round'
|
||||||
strokeLinejoin='round'
|
strokeLinejoin='round'
|
||||||
vectorEffect='non-scaling-stroke'
|
vectorEffect='non-scaling-stroke'
|
||||||
/>
|
/>
|
||||||
|
{data.map((v, i) => {
|
||||||
|
const x = n > 1 ? (i / (n - 1)) * w : w / 2;
|
||||||
|
const y = height - (v / max) * (height - 8) - 4;
|
||||||
|
return (
|
||||||
|
<circle
|
||||||
|
key={i}
|
||||||
|
cx={x.toFixed(1)}
|
||||||
|
cy={y.toFixed(1)}
|
||||||
|
r='3'
|
||||||
|
fill='white'
|
||||||
|
stroke={stroke}
|
||||||
|
strokeWidth='2'
|
||||||
|
vectorEffect='non-scaling-stroke'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue