Skip to content

Commit dec4b3b

Browse files
committed
feat: heart rate svg animate
1 parent 73353fe commit dec4b3b

File tree

8 files changed

+193
-26
lines changed

8 files changed

+193
-26
lines changed

.github/workflows/replace_readme.yml

+21-6
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,33 @@ jobs:
2626
uses: actions/setup-python@v1
2727
with:
2828
python-version: 3.6
29-
- name: Test
29+
# from pdm
30+
- name: Set Variables
31+
id: set_variables
3032
run: |
31-
echo ${{ github.event.inputs.test }}
32-
- name: Replace README
33+
echo "::set-output name=PY::$(python -c 'import hashlib, sys;print(hashlib.sha256(sys.version.encode()+sys.executable.encode()).hexdigest())')"
34+
echo "::set-output name=PIP_CACHE::$(pip cache dir)"
35+
36+
- name: Cache PIP
37+
uses: actions/cache@v2
38+
with:
39+
path: ${{ steps.set_variables.outputs.PIP_CACHE }}
40+
key: Ubuntu-pip-${{ steps.set_variables.outputs.PY }}
41+
- name: Install dependencies
42+
run: |
43+
python -m pip install --upgrade pip
44+
pip install -r requirements.txt
45+
if: steps.pip-cache.outputs.cache-hit != 'true'
46+
47+
- name: Replace README AND generate SVG
3348
run: |
3449
python main.py '${{ github.event.inputs.time }}' '${{ github.event.inputs.value }}'
3550
- name: Push new README
3651
uses: github-actions-x/[email protected]
3752
with:
3853
github-token: ${{ secrets.G_T }}
3954
commit-message: "Replace README"
40-
files: README.md
55+
files: README.md files
4156
rebase: 'true'
42-
name: yihong0618
43-
57+
name: ${{ env.GITHUB_NAME }}
58+
email: ${{ env.GITHUB_EMAIL }}

files/heart.svg

+2-19
Loading

heart/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from .heart import Heart

heart/config.py

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
BASE_TRANSLATE = "translate(100 100)"
2+
TWO_LETTERS_TEXT_TRANSLATE = "translate(-12.5 0)"
3+
THREE_LETTERS_TEXT_TRANSLATE = "translate(-17.5 0)"
4+
BASE_PATH_TRANSLATE = "translate(-50 -50)"
5+
BASE_HEART_COLOR = "tomato"
6+
BASE_HEART_TEXT_COLOR = "bisque"
7+
BASE_TEXT_STYLE = "font-size:25px; font-family:Arial"
8+
9+
PATH_D = "M92.71,7.27L92.71,7.27c-9.71-9.69-25.46-9.69-35.18,0L50,14.79l-7.54-7.52C32.75-2.42,17-2.42,7.29,7.27v0 c-9.71,9.69-9.71,25.41,0,35.1L50,85l42.71-42.63C102.43,32.68,102.43,16.96,92.71,7.27z"

heart/heart.py

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
from bisect import bisect
2+
3+
import svgwrite
4+
from svgwrite.animate import Animate, AnimateTransform
5+
6+
from .config import (
7+
BASE_HEART_COLOR,
8+
BASE_HEART_TEXT_COLOR,
9+
BASE_PATH_TRANSLATE,
10+
BASE_TEXT_STYLE,
11+
BASE_TRANSLATE,
12+
PATH_D,
13+
THREE_LETTERS_TEXT_TRANSLATE,
14+
TWO_LETTERS_TEXT_TRANSLATE,
15+
)
16+
from .utils import intersperse, make_key_times, make_key_values
17+
18+
19+
class Heart:
20+
def __init__(self, file_path, base_height=150, base_width=150):
21+
self.file_path = file_path
22+
self.base_height = base_height
23+
self.base_width = base_width
24+
self.values = None
25+
self.dur = None
26+
self.letters_trans_dict = {
27+
2: TWO_LETTERS_TEXT_TRANSLATE,
28+
3: THREE_LETTERS_TEXT_TRANSLATE,
29+
}
30+
self._make_drawer()
31+
32+
def _make_drawer(self):
33+
self.drawer = svgwrite.Drawing(
34+
self.file_path, (str(self.base_height), str(self.base_width))
35+
)
36+
self.drawer.viewbox(0, 0, 200, 200)
37+
38+
def set_values(self, values):
39+
self.values = values
40+
# from start -> end (heart rate timeline)
41+
self.values.reverse()
42+
43+
def _make_g(self):
44+
return self.drawer.g(transform=BASE_TRANSLATE)
45+
46+
def _make_path(self):
47+
return self.drawer.path(
48+
fill=BASE_HEART_COLOR,
49+
transform=BASE_PATH_TRANSLATE,
50+
d=PATH_D,
51+
stroke="#B6BBC1",
52+
stroke_width=2,
53+
)
54+
55+
def _make_base_animate_transform(self):
56+
values = self._make_heart_scale_values()
57+
values_str = ";".join(values) + ";"
58+
return AnimateTransform(
59+
type="scale",
60+
dur=self.dur,
61+
values=values_str,
62+
repeatCount="indefinite",
63+
additive="sum",
64+
transform="scale",
65+
)
66+
67+
def _make_animate(self, index):
68+
num_count = len(self.values)
69+
a = Animate(
70+
"opacity",
71+
dur=self.dur,
72+
values=make_key_values(num_count, index),
73+
keyTimes=make_key_times(num_count),
74+
repeatCount="indefinite",
75+
)
76+
return a
77+
78+
def _make_text(self, value, index):
79+
transform = self.letters_trans_dict.get(len(value))
80+
t = self.drawer.text(
81+
str(value),
82+
transform=transform,
83+
fill=BASE_HEART_TEXT_COLOR,
84+
style=BASE_TEXT_STYLE,
85+
)
86+
text_anmiate = self._make_animate(index)
87+
t.add(text_anmiate)
88+
return t
89+
90+
def __compute_statistics(self):
91+
if not self.values:
92+
raise Exception("No heart rate values set")
93+
dur_break_points = (5, 10, 15, 20)
94+
i = bisect(dur_break_points, len(self.values))
95+
self.dur = str(12 * i) + "s"
96+
97+
def _make_heart_scale_values(self):
98+
"""
99+
break_points -> scale number
100+
"""
101+
break_points = ("0.625", "0.75", "0.875", "1.125", "1.25", "1.375", "1.5")
102+
min_value, max_value = min(self.values), max(self.values)
103+
interval = (max_value - min_value) / 5
104+
heart_rate_points = [min_value + interval * i for i in range(5)]
105+
heart_rate_points.append(max_value)
106+
heart_scale_list = [
107+
break_points[bisect(heart_rate_points, i)] for i in self.values
108+
]
109+
# Make the effect of a beating heart
110+
heart_scale_list = intersperse(heart_scale_list, "1")
111+
return heart_scale_list
112+
113+
def make_heart_svg(self):
114+
self.__compute_statistics()
115+
g = self._make_g()
116+
path = self._make_path()
117+
path_animate = self._make_base_animate_transform()
118+
g.add(path)
119+
g.add(path_animate)
120+
for index, v in enumerate(self.values):
121+
g.add(self._make_text(str(v), index))
122+
self.drawer.add(g)
123+
self.drawer.save()

heart/utils.py

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from itertools import count, takewhile
2+
3+
4+
def make_key_times(num_count):
5+
"""
6+
return: list of key times points
7+
should append `1` because the svg keyTimes rule
8+
5 -> 0;0.2;0.4;0.6;0.8;1
9+
"""
10+
s = list(takewhile(lambda n: n < 1, count(0, 1 / num_count)))
11+
if not round(s[-1], 2) == 1.0:
12+
s.append(1)
13+
return ";".join([str(round(i, 2)) for i in s])
14+
15+
16+
def make_key_values(num_count, index):
17+
l = ["0"] * (num_count + 1)
18+
l[index] = "1"
19+
return ";".join(l)
20+
21+
22+
def intersperse(lst, item):
23+
result = [item] * (len(lst) * 2 - 1)
24+
result[0::2] = lst
25+
return result

main.py

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
import re
22
import argparse
3+
import os
4+
5+
from heart import Heart
36

47
GITHUB_README_COMMENTS = (
58
"(<!--START_SECTION:{name}-->\n)(.*)(<!--END_SECTION:{name}-->\n)"
69
)
710
HEART_RATE_HEAD = "| Time | Rate | \n | ---- | ---- | \n"
811
HEART_RATE_STAT_TEMPLATE = "| {time} | {value} |\n"
12+
OUT_FOLDER = os.path.join(os.getcwd(), "files")
913

1014

1115
def replace_readme_comments(file_name, comment_str, comments_name):
@@ -39,10 +43,16 @@ def make_summary_str(time_list, value_list):
3943
def main(time_list_str, value_list_str):
4044
time_list = parse_ios_str_to_list(time_list_str)
4145
value_list = parse_ios_str_to_list(value_list_str)
42-
print(time_list, value_list)
46+
value_list = [int(i) for i in value_list]
47+
4348
s = make_summary_str(time_list, value_list)
4449
replace_readme_comments("README.md", s, "my_heart_rate")
4550

51+
# generate heart rate svg and save
52+
h = Heart(os.path.join(OUT_FOLDER, "heart.svg"))
53+
h.set_values(value_list)
54+
h.make_heart_svg()
55+
4656

4757
if __name__ == "__main__":
4858
parser = argparse.ArgumentParser()

requirements.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
git+https://github.com/yihong0618/svgwrite.git

0 commit comments

Comments
 (0)