The SSH host key has changed on 8 April, 2022 to this one: SHA256:573uTBSeh74kvOo0HJXi5ijdzRm8me27suzNEDlGyrQ
Browse Source

Make it possible to generate an SVG image

devel
Gergely Polonkai 3 months ago
parent
commit
95d7d89470
No known key found for this signature in database
GPG Key ID: 2D2885533B869ED4
  1. 9
      README.rst
  2. 681
      example-output.svg
  3. 1
      pyproject.toml
  4. 441
      seasonal_clock/svg.py

9
README.rst

@ -81,3 +81,12 @@ It can theoretically make it easier to synchronize events across multiple time
zones without actually knowing the time difference; just tell the other
attending parties that you eat your lunch during Ladybug hour, or that your
event is taking place between Gourd and Soup hours.
Generated SVG
=============
With the ``poetry run print_svg`` command, you will get an image similar to this:
.. image:: example-output.svg
:width: 700
:alt: An image rendered by this software

681
example-output.svg

@ -0,0 +1,681 @@
<svg width="700" height="700" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
.hour path {stroke: rgb(0, 0, 0); stroke-width: 2px;}
.hour text {stroke: none; fill: rgb(238, 187, 85);}
.hour text.utc {stroke: none; fill: rgb(91, 68, 38);}
.winter path {fill: rgb(70, 62, 108);}
.spring path {fill: rgb(55, 87, 55);}
.summer path {fill: rgb(113, 92, 43);}
.autumn path {fill: rgb(108, 68, 44);}
.local-hour {stroke: none; fill: rgb(238, 187, 85);}
.night-time {stroke: none; fill: rgb(19, 17, 30);}
.blue-hour {stroke: none; fill: rgb(9, 1, 119);}
.golden-hour {stroke: none; fill: rgb(170, 132, 44);}
.day-time {stroke: none; fill: rgb(125, 197, 240);}
.marker {stroke: rgb(19, 17, 30); stroke-width: 2px; fill: none;}
.moon-background {stroke: rgb(170, 170, 170); stroke-width: 2px; fill: rgb(19, 17, 30);}
.moon {stroke: none; fill: rgb(170, 170, 170);}
.sun {stroke: none; fill: rgb(238, 187, 85);}
.dial {stroke-width: 2px; stroke: rgb(238, 187, 85);}
</style>
<defs>
<path id="hour-name-path" d="M 312.1730467792776 62.6757645018362 a 289.803545 289.803545 15 0 1 75.65390644144473 0"></path>
</defs>
<rect x="0" y="0" width="700" height="700" style="stroke: none; fill: rgb(19, 17, 30);"></rect>
<g id="local-clock">
<text
transform="rotate(180, 350.0, 350.0) rotate(0, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">Midnight</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(15, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">1</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(30, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">2</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(45, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">3</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(60, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">4</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(75, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">5</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(90, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">6</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(105, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">7</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(120, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">8</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(135, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">9</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(150, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">10</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(165, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">11</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(180, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">Noon</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(195, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">13</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(210, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">14</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(225, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">15</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(240, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">16</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(255, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">17</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(270, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">18</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(285, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">19</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(300, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">20</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(315, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">21</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(330, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">22</text>
<text
transform="rotate(180, 350.0, 350.0) rotate(345, 350.0, 350.0)"
class="local-hour"
x="350.0"
y="31.88096999999999"
text-anchor="middle"
font-size="16.5">23</text>
</g>
<g id="seasonal-clock" transform="rotate(30.0, 350.0, 350.0)">
<g class="hour winter" transform="rotate(-172.5, 350.0, 350.0) rotate(0, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Candle</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 00</text>
</g>
<g class="hour winter" transform="rotate(-172.5, 350.0, 350.0) rotate(15, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Ice</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 01</text>
</g>
<g class="hour winter" transform="rotate(-172.5, 350.0, 350.0) rotate(30, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Comet</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 02</text>
</g>
<g class="hour winter" transform="rotate(-172.5, 350.0, 350.0) rotate(45, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Thimble</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 03</text>
</g>
<g class="hour winter" transform="rotate(-172.5, 350.0, 350.0) rotate(60, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Root</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 04</text>
</g>
<g class="hour winter" transform="rotate(-172.5, 350.0, 350.0) rotate(75, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Mist</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 05</text>
</g>
<g class="hour spring" transform="rotate(-172.5, 350.0, 350.0) rotate(90, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Sprout</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 06</text>
</g>
<g class="hour spring" transform="rotate(-172.5, 350.0, 350.0) rotate(105, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Rainbow</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 07</text>
</g>
<g class="hour spring" transform="rotate(-172.5, 350.0, 350.0) rotate(120, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Worm</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 08</text>
</g>
<g class="hour spring" transform="rotate(-172.5, 350.0, 350.0) rotate(135, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Bud</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 09</text>
</g>
<g class="hour spring" transform="rotate(-172.5, 350.0, 350.0) rotate(150, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Blossom</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 10</text>
</g>
<g class="hour spring" transform="rotate(-172.5, 350.0, 350.0) rotate(165, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Ladybug</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 11</text>
</g>
<g class="hour summer" transform="rotate(-172.5, 350.0, 350.0) rotate(180, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Geese</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 12</text>
</g>
<g class="hour summer" transform="rotate(-172.5, 350.0, 350.0) rotate(195, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Dust</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 13</text>
</g>
<g class="hour summer" transform="rotate(-172.5, 350.0, 350.0) rotate(210, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Peach</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 14</text>
</g>
<g class="hour summer" transform="rotate(-172.5, 350.0, 350.0) rotate(225, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Fog</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 15</text>
</g>
<g class="hour summer" transform="rotate(-172.5, 350.0, 350.0) rotate(240, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Acorn</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 16</text>
</g>
<g class="hour summer" transform="rotate(-172.5, 350.0, 350.0) rotate(255, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Gourd</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 17</text>
</g>
<g class="hour autumn" transform="rotate(-172.5, 350.0, 350.0) rotate(270, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Soup</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 18</text>
</g>
<g class="hour autumn" transform="rotate(-172.5, 350.0, 350.0) rotate(285, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Crow</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 19</text>
</g>
<g class="hour autumn" transform="rotate(-172.5, 350.0, 350.0) rotate(300, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Mushroom</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 20</text>
</g>
<g class="hour autumn" transform="rotate(-172.5, 350.0, 350.0) rotate(315, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Thunder</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 21</text>
</g>
<g class="hour autumn" transform="rotate(-172.5, 350.0, 350.0) rotate(330, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Frost</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 22</text>
</g>
<g class="hour autumn" transform="rotate(-172.5, 350.0, 350.0) rotate(345, 350.0, 350.0)">
<path
d="M 309.55397542717907 42.7819425076129 a 309.86903 309.86903 15 0 1 80.89204914564186 0 l -5.238142704197123 39.78764398844654 a 269.73806 269.73806 15 0 0 -70.41576373724762 0 z"></path>
<text
x="350.0"
y="63.54070249999999"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="13.37699"><textPath xlink:href="#hour-name-path" startOffset="50%">Lantern</textPath></text>
<text
transform="rotate(-7.5, 350.0, 350.0)"
class="utc"
x="350.0"
y="95.28533999999998"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="15.0234">U 23</text>
</g>
<circle cx="350.0" cy="350.0" r="239.69126000000003" class="day-time"></circle>
<path
class="golden-hour"
d="M 350.0 350.0
L 116.05469504530768 402.16794427657305
A 239.69126000000003 239.69126000000003 169.29166666666669 1 0 570.178029285408 255.27125283008513
z"></path>
<circle cx="350.0" cy="514.934515" r="10" class="sun" transform="rotate(208.61666666666667, 350.0, 350.0)"></circle>
<path
class="blue-hour"
d="M 350.0 350.0
L 137.1262622524969 460.16656433689354
A 239.69126000000003 239.69126000000003 198.91666666666669 0 0 587.0918462571672 314.795503713937
z"></path>
<path
class="night-time"
d="M 350.0 350.0
L 143.12202233852076 471.0512390646379
A 239.69126000000003 239.69126000000003 204.86666666666665 0 0 588.60109398448 327.1654194302391
z"></path>
<circle cx="350.0" cy="350.0" r="239.69126000000003" class="marker"></circle>
</g>
<g>
<circle cx="350.0" cy="350.0" r="50" class="moon-background"></circle>
<path
class="moon"
d="M 350.0 300.0
C 283.0 306.0, 283.0 394.0, 350.0 400.0
C 318.4132222222222 394.0, 318.4132222222222 306.0, 350.0 300.0"></path>
</g>
<line
style="stroke: red;"
transform="rotate(341.90416666666664, 350.0, 350.0)"
x1="350.0"
y1="579.69126"
x2="350.0"
y2="589.69126"></line>
<line
style="stroke: red;"
transform="rotate(162.00833333333333, 350.0, 350.0)"
x1="350.0"
y1="579.69126"
x2="350.0"
y2="589.69126"></line>
<line
id="dial"
transform="rotate(238.61666666666667, 350.0, 350.0)"
class="dial"
x1="350.0"
y1="504.93451500000003"
x2="350.0"
y2="633.11505"></line>
</svg>

After

Width:  |  Height:  |  Size: 31 KiB

1
pyproject.toml

@ -23,6 +23,7 @@ types-pytz = "^2021.3.6"
[tool.poetry.scripts]
print_data = "seasonal_clock:main"
print_svg = "seasonal_clock.svg:print_svg"
[build-system]
requires = ["poetry-core>=1.0.0"]

441
seasonal_clock/svg.py

@ -0,0 +1,441 @@
from datetime import datetime, time
from math import ceil, cos, pi, radians, sin
from typing import Union
from astral import LocationInfo, moon
from pytz import UTC, timezone
from .config import load_config
from .times import collect_day_parts
HOURS_AMPM = (
'Midnight',
'1a',
'2a',
'3a',
'4a',
'5a',
'6a',
'7a',
'8a',
'9a',
'10a',
'11a',
'Noon',
'1p',
'2p',
'3p',
'4p',
'5p',
'6p',
'7p',
'8p',
'9p',
'10p',
'11p',
)
HOURS_24 = (
'Midnight',
'1',
'2',
'3',
'4',
'5',
'6',
'7',
'8',
'9',
'10',
'11',
'Noon',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'22',
'23',
)
HOUR_NAMES = (
'Candle',
'Ice',
'Comet',
'Thimble',
'Root',
'Mist',
'Sprout',
'Rainbow',
'Worm',
'Bud',
'Blossom',
'Ladybug',
'Geese',
'Dust',
'Peach',
'Fog',
'Acorn',
'Gourd',
'Soup',
'Crow',
'Mushroom',
'Thunder',
'Frost',
'Lantern',
)
SEASONS = ('winter', 'spring', 'summer', 'autumn')
def hex_to_rgb(hex_color: str) -> str:
r = int(hex_color[1:3], 16)
g = int(hex_color[3:5], 16)
b = int(hex_color[5:7], 16)
return f'rgb({r}, {g}, {b})'
def get_utc_offset(utc_time: datetime, local_time: datetime):
assert utc_time.tzinfo
assert local_time.tzinfo
utc = utc_time.replace(tzinfo=None)
local = utc_time.replace(tzinfo=None)
return (utc - local).total_seconds()
def seconds_to_degrees(seconds: float) -> float:
one_second = 360 / 86400
return seconds * one_second
def indent_lines(string: str, indentation: int = 8) -> str:
return '\n'.join((' ' * indentation) + line for line in string.split('\n')) + '\n'
def time_to_degrees(timestamp: Union[datetime, time]) -> float:
return seconds_to_degrees(
timestamp.hour * 3600 + timestamp.minute * 60 + timestamp.second
)
def hour_name_path(image_width: int, outer_r: float, ring_width: float) -> str:
radius = outer_r - ring_width / 2
delta_x = radius * sin(radians(15) / 2)
delta_y = radius * (1 - cos(radians(15) / 2))
x1 = image_width / 2 - delta_x
y1 = (image_width / 2 - radius) + delta_y
return f'''<path id="hour-name-path" d="M {x1} {y1} a {radius} {radius} 15 0 1 {2 * delta_x} 0"></path>'''
def hour_marker(
hour: int,
hour_name: str,
image_width: int,
outer_r: float,
ring_width: float,
hour_name_font_size: float,
utc_font_size: float,
indent: int = 8,
) -> str:
season = SEASONS[ceil((hour + 1) / 6) - 1]
rotation = hour * 15
delta_x = outer_r * sin(radians(15) / 2)
delta_y = outer_r * (1 - cos(radians(15) / 2))
s_delta_x = 0 - ring_width * sin(radians(15) / 2)
s_delta_y = ring_width * cos(radians(15) / 2)
i_delta_x = -2 * (outer_r - ring_width) * sin(radians(15) / 2)
x1 = image_width / 2 - delta_x
y1 = (image_width / 2 - outer_r) + delta_y
hour_name_y = image_width / 2 - outer_r + ring_width / 2 + hour_name_font_size / 4
utc_hour_y = image_width / 2 - outer_r + ring_width + utc_font_size
ret = f'''<g class="hour {season}" transform="rotate(-172.5, {image_width / 2}, {image_width / 2}) rotate({rotation}, {image_width / 2}, {image_width / 2})">
<path
d="M {x1} {y1} a {outer_r} {outer_r} 15 0 1 {2 * delta_x} 0 l {s_delta_x} {s_delta_y} a {outer_r - ring_width} {outer_r - ring_width} 15 0 0 {i_delta_x} 0 z"></path>
<text
x="{image_width / 2}"
y="{hour_name_y}"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="{hour_name_font_size}"><textPath xlink:href="#hour-name-path" startOffset="50%">{hour_name}</textPath></text>
<text
transform="rotate(-7.5, {image_width / 2}, {image_width / 2})"
class="utc"
x="{image_width / 2}"
y="{utc_hour_y}"
text-anchor="middle"
dominant-baseline="mathematical"
font-size="{utc_font_size}">U {hour:02d}</text>
</g>'''
return indent_lines(ret, indent)
def local_hour(
image_width: int,
outer_r: float,
hour_num: int,
hour: str,
font_size: float,
indent: int = 8,
) -> str:
rotation = hour_num * 15
return indent_lines(
f'''<text
transform="rotate(180, {image_width / 2}, {image_width / 2}) rotate({rotation}, {image_width / 2}, {image_width / 2})"
class="local-hour"
x="{image_width / 2}"
y="{image_width / 2 - outer_r - font_size / 2}"
text-anchor="middle"
font-size="{font_size}">{hour}</text>''',
indent,
)
def get_range_path(
image_width: int,
radius: float,
range_name: str,
start_time: time,
end_time: time,
outer: bool = True,
indent: int = 8,
) -> str:
start_deg = time_to_degrees(start_time)
end_deg = time_to_degrees(end_time)
start_delta_x = radius * sin(radians(start_deg))
start_delta_y = radius * (1 - cos(radians(start_deg)))
end_delta_x = radius * sin(radians(end_deg))
end_delta_y = radius * (1 - cos(radians(end_deg)))
deg_diff = end_deg - start_deg
large_arc_flag = 0 if abs(deg_diff) >= 180 else 1
return indent_lines(
f'''<path
class="{range_name}"
d="M {image_width / 2} {image_width / 2}
L {image_width / 2 - start_delta_x} {image_width / 2 + radius - start_delta_y}
A {radius} {radius} {end_deg - start_deg} {large_arc_flag} 0 {image_width / 2 - end_delta_x} {image_width / 2 + radius - end_delta_y}
z"></path>''',
indent,
)
def get_moon_path(image_width: int, radius: float, moon_phase: float, indent: int = 8):
handle_x_pos = radius * 1.34
handle_y_pos = radius * 0.88
min_x = image_width / 2 - handle_x_pos
max_x = min_x + 2 * handle_x_pos
top_y = image_width / 2 - handle_y_pos
bottom_y = image_width / 2 + handle_y_pos
if moon_phase < 14:
h1_x = min_x + 2 * handle_x_pos * (1 - moon_phase / 14)
h2_x = max_x
else:
h1_x = min_x
h2_x = max_x + 2 * handle_x_pos * (1 - moon_phase / 14)
ret = f'''<path
class="moon"
d="M {image_width / 2} {image_width / 2 - radius}
C {h1_x} {top_y}, {h1_x} {bottom_y}, {image_width / 2} {image_width / 2 + radius}
C {h2_x} {bottom_y}, {h2_x} {top_y}, {image_width / 2} {image_width / 2 - radius}"></path>'''
return indent_lines(ret, indent)
def get_svg_data(
local_time: datetime,
image_width: int = 700,
line_width: str = '2px',
line_color: str = '#eebb55',
utc_color: str = '#5b4426',
hname_stroke_color: str = '#000000',
winter_color: str = '#463e6c',
spring_color: str = '#375737',
summer_color: str = '#715c2b',
autumn_color: str = '#6c442c',
night_color: str = '#13111e',
blue_color: str = '#090177',
golden_color: str = '#aa842c',
day_color: str = '#7dc5f0',
moon_color: str = '#aaaaaa',
local_hour_font_size: float = 16.5,
hour_name_font_size: float = 13.37699,
utc_hour_font_size: float = 15.0234,
hour_24: bool = True,
) -> str:
line_rgb = hex_to_rgb(line_color)
hname_stroke_rgb = hex_to_rgb(hname_stroke_color)
utc_rgb = hex_to_rgb(utc_color)
winter_rgb = hex_to_rgb(winter_color)
spring_rgb = hex_to_rgb(spring_color)
summer_rgb = hex_to_rgb(summer_color)
autumn_rgb = hex_to_rgb(autumn_color)
night_rgb = hex_to_rgb(night_color)
blue_rgb = hex_to_rgb(blue_color)
golden_rgb = hex_to_rgb(golden_color)
day_rgb = hex_to_rgb(day_color)
moon_rgb = hex_to_rgb(moon_color)
hours = HOURS_24 if hour_24 else HOURS_AMPM
outer_r = image_width / 2 - 3 * hour_name_font_size
ring_width = hour_name_font_size * 3
utc_time = local_time.astimezone(UTC)
utc_naive = utc_time.replace(tzinfo=None)
local_naive = local_time.replace(tzinfo=None)
offset = (local_naive - utc_naive).total_seconds()
config = load_config()
location = LocationInfo(
config['city'],
config['country'],
config['timezone'],
config['latitude'],
config['longitude'],
)
local_tz = location.tzinfo
day_parts = collect_day_parts(location.observer, local_time)
day_parts_dict = dict(part[0:2] for part in day_parts)
morning_blue_start = day_parts_dict.pop('morning_blue_start')
morning_blue_end = day_parts_dict.pop('morning_golden_start')
morning_golden_end = day_parts_dict.pop('morning_golden_end')
evening_golden_start = day_parts_dict.pop('evening_golden_start')
evening_blue_start = day_parts_dict.pop('evening_blue_start')
evening_blue_end = day_parts_dict.pop('evening_blue_end')
moon_phase = moon.phase(local_time)
noon = day_parts_dict.pop('noon')
midnight = day_parts_dict.pop('midnight')
ret = f'''<svg width="{image_width}" height="{image_width}" xmlns:xlink="http://www.w3.org/1999/xlink">
<style>
.hour path {{stroke: {hname_stroke_rgb}; stroke-width: {line_width};}}
.hour text {{stroke: none; fill: {line_rgb};}}
.hour text.utc {{stroke: none; fill: {utc_rgb};}}
.winter path {{fill: {winter_rgb};}}
.spring path {{fill: {spring_rgb};}}
.summer path {{fill: {summer_rgb};}}
.autumn path {{fill: {autumn_rgb};}}
.local-hour {{stroke: none; fill: {line_rgb};}}
.night-time {{stroke: none; fill: {night_rgb};}}
.blue-hour {{stroke: none; fill: {blue_rgb};}}
.golden-hour {{stroke: none; fill: {golden_rgb};}}
.day-time {{stroke: none; fill: {day_rgb};}}
.marker {{stroke: {night_rgb}; stroke-width: 2px; fill: none;}}
.moon-background {{stroke: {moon_rgb}; stroke-width: 2px; fill: {night_rgb};}}
.moon {{stroke: none; fill: {moon_rgb};}}
.sun {{stroke: none; fill: {line_rgb};}}
.dial {{stroke-width: 2px; stroke: {line_rgb};}}
</style>
<defs>
{hour_name_path(image_width, outer_r, ring_width)}
</defs>
<rect x="0" y="0" width="{image_width}" height="{image_width}" style="stroke: none; fill: {night_rgb};"></rect>
<g id="local-clock">\n'''
for hour in range(24):
ret += local_hour(
image_width, outer_r, hour, hours[hour], local_hour_font_size, indent=8
)
ret += f''' </g>
<g id="seasonal-clock" transform="rotate({seconds_to_degrees(offset)}, {image_width / 2}, {image_width / 2})">
'''
for hour in range(24):
ret += hour_marker(
hour,
HOUR_NAMES[hour],
image_width,
outer_r,
ring_width,
hour_name_font_size,
utc_hour_font_size,
indent=8,
)
marker_radius = outer_r - ring_width - 2 * utc_hour_font_size
ret += f''' <circle cx="{image_width / 2}" cy="{image_width / 2}" r="{marker_radius}" class="day-time"></circle>\n'''
ret += get_range_path(
image_width,
marker_radius,
'golden-hour',
morning_golden_end.time(),
evening_golden_start.time(),
)
sun_radius = 10
ret += f''' <circle cx="{image_width / 2}" cy="{image_width / 2 + outer_r / 2 + sun_radius}" r="{sun_radius}" class="sun" transform="rotate({time_to_degrees(local_time.astimezone(UTC))}, {image_width / 2}, {image_width / 2})"></circle>\n'''
ret += get_range_path(
image_width,
marker_radius,
'blue-hour',
morning_blue_end.time(),
evening_blue_start.time(),
)
ret += get_range_path(
image_width,
marker_radius,
'night-time',
morning_blue_start.time(),
evening_blue_end.time(),
)
ret += f''' <circle cx="{image_width / 2}" cy="{image_width / 2}" r="{marker_radius}" class="marker"></circle>
</g>
<g>\n'''
moon_radius = 50
ret += f''' <circle cx="{image_width / 2}" cy="{image_width / 2}" r="{moon_radius}" class="moon-background"></circle>\n'''
ret += get_moon_path(image_width, moon_radius, moon_phase, indent=8)
ret += f''' </g>
<line
style="stroke: red;"
transform="rotate({time_to_degrees(midnight.astimezone(local_tz))}, {image_width / 2}, {image_width / 2})"
x1="{image_width / 2}"
y1="{image_width / 2 + marker_radius - sun_radius}"
x2="{image_width / 2}"
y2="{image_width / 2 + marker_radius}"></line>
<line
style="stroke: red;"
transform="rotate({time_to_degrees(noon.astimezone(local_tz))}, {image_width / 2}, {image_width / 2})"
x1="{image_width / 2}"
y1="{image_width / 2 + marker_radius - sun_radius}"
x2="{image_width / 2}"
y2="{image_width / 2 + marker_radius}"></line>
<line
id="dial"
transform="rotate({time_to_degrees(local_time)}, {image_width / 2}, {image_width / 2})"
class="dial"
x1="{image_width / 2}"
y1="{image_width / 2 + outer_r * 0.5}"
x2="{image_width / 2}"
y2="{image_width / 2 + outer_r - ring_width + hour_name_font_size}"></line>
</svg>
'''
return ret
def print_svg():
utc_now = datetime.utcnow().replace(tzinfo=UTC)
local_now = utc_now.astimezone(timezone('Europe/Budapest'))
print(get_svg_data(local_now))
Loading…
Cancel
Save