-
Notifications
You must be signed in to change notification settings - Fork 3
/
svg_wheel.py
136 lines (112 loc) · 3.83 KB
/
svg_wheel.py
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
import math
import xml.etree.ElementTree as et
HEADERS = b"""<?xml version=\"1.0\" standalone=\"no\"?>
<?xml-stylesheet href="wheel.css" type="text/css"?>
<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\"
\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">
"""
PATH_TEMPLATE = """
M {start_outer_x},{start_outer_y}
A{outer_radius},{outer_radius} 0 0 1 {end_outer_x},{end_outer_y}
L {start_inner_x},{start_inner_y}
A{inner_radius},{inner_radius} 0 0 0 {end_inner_x},{end_inner_y}
Z
"""
FRACTION_LINE = 80
OFFSET = 20
PADDING = 10
OUTER_RADIUS = 180
INNER_RADIUS = OUTER_RADIUS / 2
CENTER = PADDING + OUTER_RADIUS
def annular_sector_path(start, stop):
cos_stop = math.cos(stop)
cos_start = math.cos(start)
sin_stop = math.sin(stop)
sin_start = math.sin(start)
points = {
"inner_radius": INNER_RADIUS,
"outer_radius": OUTER_RADIUS,
"start_outer_x": CENTER + OUTER_RADIUS * cos_start,
"start_outer_y": CENTER + OUTER_RADIUS * sin_start,
"end_outer_x": CENTER + OUTER_RADIUS * cos_stop,
"end_outer_y": CENTER + OUTER_RADIUS * sin_stop,
"start_inner_x": CENTER + INNER_RADIUS * cos_stop,
"start_inner_y": CENTER + INNER_RADIUS * sin_stop,
"end_inner_x": CENTER + INNER_RADIUS * cos_start,
"end_inner_y": CENTER + INNER_RADIUS * sin_start,
}
return PATH_TEMPLATE.format(**points)
def add_annular_sectors(wheel, packages, total):
for index, result in enumerate(packages):
sector = et.SubElement(
wheel,
"path",
d=annular_sector_path(*angles(index, total)),
attrib={"class": result["css_class"]},
)
title = et.SubElement(sector, "title")
title.text = f"{result['name']} {result['icon']}"
def angles(index, total):
# Angle, in radians, of one wedge of the wheel.
angle_per_wedge = math.tau / total
# Used to turn the start of the wheel from east to north.
quarter_circle = math.tau / 4
# Angle of the beginning of the wedge.
start = (index * angle_per_wedge) - quarter_circle
# Angle of the end of the wedge.
stop = start + angle_per_wedge
return start, stop
def add_fraction(wheel, packages, total):
text_attributes = {
"class": "wheel-text",
"text-anchor": "middle",
"dominant-baseline": "central",
"font-size": str(2 * OFFSET),
"font-family": '"Helvetica Neue",Helvetica,Arial,sans-serif',
}
# Packages with some sort of wheel
wheel_packages = sum(package["wheel"] for package in packages)
packages_with_wheels = et.SubElement(
wheel,
"text",
x=str(CENTER),
y=str(CENTER - OFFSET),
attrib=text_attributes,
)
packages_with_wheels.text = f"{wheel_packages}"
title = et.SubElement(packages_with_wheels, "title")
percentage = f"{wheel_packages / float(total):.0%}"
title.text = percentage
# Dividing line
et.SubElement(
wheel,
"line",
x1=str(CENTER - FRACTION_LINE // 2),
y1=str(CENTER),
x2=str(CENTER + FRACTION_LINE // 2),
y2=str(CENTER),
attrib={"class": "wheel-line", "stroke-width": "2"},
)
# Total packages
total_packages = et.SubElement(
wheel,
"text",
x=str(CENTER),
y=str(CENTER + OFFSET),
attrib=text_attributes,
)
total_packages.text = f"{total}"
title = et.SubElement(total_packages, "title")
title.text = percentage
def generate_svg_wheel(packages, total):
wheel = et.Element(
"svg",
viewBox=f"0 0 {2 * CENTER} {2 * CENTER}",
version="1.1",
xmlns="http://www.w3.org/2000/svg",
)
add_annular_sectors(wheel, packages, total)
add_fraction(wheel, packages, total)
with open("wheel.svg", "wb") as svg:
svg.write(HEADERS)
svg.write(et.tostring(wheel))