1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99 |
x2
x2
x2
x16
x4
x4
x16
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x4
x11
x11
x4
x4
x4
x4
x4
x4
x4
x5
x5
x5
x5
x34
x5
x5
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x8
x5
x5
x4
x4 |
|
import { d3, type d3arg, type d3data, mkcolor, mkconfig, mksvg, type options } from "./_graph.ts"
import { pick } from "@std/collections"
export type { options }
export function pie(datalist: Record<PropertyKey, { color?: string; data: number }>, options: Pick<options, "width" | "height" | "legend"> = {}): string {
const config = mkconfig(pick(options, ["width", "height"]))
const { margin, width, height } = config
const radius = Math.min(width, height) / 2
const svg = mksvg({ width, height })
const K = Object.keys(datalist)
const V = Object.values(datalist)
const I = d3.range(K.length).filter((i: number) => !Number.isNaN(V[i].data))
const D = d3.pie().padAngle(1 / radius).sort(null).value((i: number) => V[+i].data)(I)
const labels = d3.arc().innerRadius(radius / 2).outerRadius(radius / 2)
svg.append("g")
.attr("transform", `translate(${(width - (options.legend ? config.legend.width : 0)) / 2},${height / 2})`)
.attr("stroke", "white")
.attr("stroke-width", 1)
.attr("stroke-linejoin", "round")
.selectAll("path")
.data(D)
.join("path")
.attr("fill", (d: d3data) => V[+d.data].color ?? mkcolor(K[+d.data]))
.attr("d", d3.arc().innerRadius(0).outerRadius(radius) as d3arg)
.append("title")
.text((d: d3data) => `${K[+d.data]}\n${V[+d.data].data}`)
svg.append("g")
.attr("transform", `translate(${(width - (options.legend ? config.legend.width : 0)) / 2},${height / 2})`)
.attr("font-family", "sans-serif")
.attr("font-size", `${config.texts.fontsize}px`)
.attr("text-anchor", "middle")
.attr("fill", "white")
.attr("stroke", "rbga(0,0,0,.9)")
.attr("paint-order", "stroke fill")
.selectAll("text")
.data(D)
.join("text")
.attr("transform", (d: d3data) => `translate(${labels.centroid(d)})`)
.selectAll("tspan")
.data((d: d3data) => {
const lines = `${K[+d.data]}\n${V[+d.data].data}`.split(/\n/)
return (d.endAngle - d.startAngle) > 0.25 ? lines : lines.slice(0, 1)
})
.join("tspan")
.attr("x", 0)
.attr("y", (_: unknown, i: number) => `${i * 1.1}em`)
.attr("font-weight", (_: unknown, i: number) => i ? null : "bold")
.text((d: d3data) => d)
if (options.legend) {
svg.append("g")
.attr("class", "legend")
.attr("transform", `translate(${width - margin.right - config.legend.width},${margin.top})`)
.selectAll("g")
.data(Object.keys(datalist).map(([name]) => ({ name, value: datalist[name].data, color: datalist[name].color ?? mkcolor(name) })))
.enter()
.each(function (this: d3arg, d: d3data, i: number) {
d3.select(this)
.append("rect")
.attr("x", 0)
.attr("y", i * (config.legend.fontsize + config.legend.margin) + (config.legend.fontsize - config.legend.rect[1]) / 2)
.attr("width", config.legend.rect[0])
.attr("height", config.legend.rect[1])
.attr("fill", d.color)
d3.select(this)
.append("text")
.attr("x", config.legend.rect[0] + 5)
.attr("y", i * (config.legend.fontsize + config.legend.margin))
.attr("text-anchor", "start")
.attr("dominant-baseline", "hanging")
.attr("fill", d.color)
.style("font-size", `${config.legend.fontsize}px`)
.text(`${d.name} (${d.value})`)
})
}
return svg.node()!.outerHTML
}
|