feat(source): ContribPie material contribution pie chart component
This commit is contained in:
parent
561c5158f1
commit
1a4b752a01
|
|
@ -0,0 +1,59 @@
|
||||||
|
export interface PieItem {
|
||||||
|
name: string
|
||||||
|
rate: number
|
||||||
|
isSrc: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
items: PieItem[]
|
||||||
|
colors?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_COLORS = ['#C0392B', '#E67E22', '#E0A100', '#159A86', '#2E7FC1', '#6FB1E0', '#9AA7B4']
|
||||||
|
|
||||||
|
const R = 74
|
||||||
|
const CX = 90
|
||||||
|
const CY = 90
|
||||||
|
|
||||||
|
function arc(cx: number, cy: number, r: number, a0: number, a1: number) {
|
||||||
|
const p = (a: number) => [cx + r * Math.cos((a - 90) * Math.PI / 180), cy + r * Math.sin((a - 90) * Math.PI / 180)]
|
||||||
|
const [x0, y0] = p(a0)
|
||||||
|
const [x1, y1] = p(a1)
|
||||||
|
const large = a1 - a0 > 180 ? 1 : 0
|
||||||
|
return `M ${cx} ${cy} L ${x0} ${y0} A ${r} ${r} 0 ${large} 1 ${x1} ${y1} Z`
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ContribPie({ items, colors = DEFAULT_COLORS }: Props) {
|
||||||
|
const total = items.reduce((s, it) => s + it.rate, 0)
|
||||||
|
if (!items.length || total <= 0) {
|
||||||
|
return <div className="muted" style={{ padding: 20 }}>该污染物无材料释放</div>
|
||||||
|
}
|
||||||
|
const color = (i: number) => colors[Math.min(i, colors.length - 1)]
|
||||||
|
let acc = 0
|
||||||
|
const slices = items.map((it, i) => {
|
||||||
|
const a0 = acc / total * 360
|
||||||
|
acc += it.rate
|
||||||
|
const a1 = acc / total * 360
|
||||||
|
return { d: arc(CX, CY, R, a0, a1), c: color(i), pct: it.rate / total * 100 }
|
||||||
|
})
|
||||||
|
return (
|
||||||
|
<div className="pie-wrap">
|
||||||
|
<svg className="pie-svg" width={180} height={180} viewBox="0 0 180 180">
|
||||||
|
{slices.map((s, i) => (
|
||||||
|
<path key={i} d={s.d} fill={s.c} stroke="#fcfcfb" strokeWidth={2}>
|
||||||
|
<title>{items[i].name}:{(items[i].rate * 100).toFixed(1)}%</title>
|
||||||
|
</path>
|
||||||
|
))}
|
||||||
|
</svg>
|
||||||
|
<div className="pie-legend">
|
||||||
|
{items.map((it, i) => (
|
||||||
|
<div className="pie-lg" key={it.name + i}>
|
||||||
|
<i style={{ background: color(i) }} />
|
||||||
|
<span className="pie-lg-nm">{it.name}{it.isSrc && <b className="pie-src"> (污染源)</b>}</span>
|
||||||
|
<span className="pie-lg-v">{(it.rate * 100).toFixed(1)}%</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -255,3 +255,13 @@
|
||||||
.rbc-lg{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted,#8a8f98);}
|
.rbc-lg{display:inline-flex;align-items:center;gap:6px;font-size:12px;color:var(--muted,#8a8f98);}
|
||||||
.rbc-lg i{width:11px;height:11px;border-radius:3px;display:inline-block;}
|
.rbc-lg i{width:11px;height:11px;border-radius:3px;display:inline-block;}
|
||||||
.rbc-lg-dash{width:14px;height:0;border-top:1px dashed #C0392B;border-radius:0;}
|
.rbc-lg-dash{width:14px;height:0;border-top:1px dashed #C0392B;border-radius:0;}
|
||||||
|
|
||||||
|
/* 材料贡献饼图 */
|
||||||
|
.pie-wrap{display:flex;gap:22px;align-items:center;flex-wrap:wrap;padding:6px 2px 2px;}
|
||||||
|
.pie-svg{flex-shrink:0;}
|
||||||
|
.pie-legend{display:flex;flex-direction:column;gap:8px;min-width:200px;flex:1;}
|
||||||
|
.pie-lg{display:flex;align-items:center;gap:9px;font-size:13px;}
|
||||||
|
.pie-lg i{width:12px;height:12px;border-radius:3px;flex-shrink:0;}
|
||||||
|
.pie-lg-nm{flex:1;color:var(--ink,#1f2328);}
|
||||||
|
.pie-src{color:#C0392B;font-weight:700;}
|
||||||
|
.pie-lg-v{font-weight:700;color:var(--ink,#1f2328);}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue