diff --git a/CHANGELOG.md b/CHANGELOG.md index c14c3ed..5e70889 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.8.0] - ✨ Lazy generation of chart data, 'Artist' registry, and better SVG export - 2023-06-11 + +Now all chart-data parameters, including the overlayed zones, are included in the `ChartConfig`, +and generation of curves only happens when chart is plotted (or saved to disk). + +Any change in the chart configuration will trigger a data-regen for all items before plotting. + +When plotting over some matplotlib `Axes`, all objects created have meaningful (and deterministic) ids, +and are tracked and accessible under `chart.artists`, for easier customization of single matplotlib objects, +also adding a **recognizable hierarchy** in generated SVGs, (enabling potential for **CSS over SVG charts** 🌈) + +##### Changes + +- ✨ Name each matplotlib `Artist` obj in plot with custom readable IDs, for easier recognition and **readable object hierarchy in SVGs**, and maintain a registry of all objects in plot to access them by kind under `chart.artists` property, allowing fine-grain control to customize all details in the psychrochart +- ♻️ Refactor chart 'zones' and store zone definitions in ChartConfig, to regenerate plot patches (closed `PsychroCurve`, with `ZoneStyle`) on-demand, same as the other chart curves, generating new 'xy' points when config is changed. If `ChartParams.with_zones` is enabled, but there are no zones in config when chart is created, the default winter/summer 'dbt-rh' zones are added. +- ⚡️ Add **lazy generation** of psychro curves to avoid updating the chart data until it's needed to plot, and add a `chart.process_chart()` method to trigger data regeneration if the chart config has changed or there is no chart data yet +- 💥 Make `chart.saturation` a single `PsychroCurve`, instead of a collection of curves +- ✅ tests: Add unit tests for new ChartRegistry and adapt to behaviour changes and readable hierarchy in SVG output +- 📝 Update example notebook showing `chart.artists` usage +- 🚀 env: Update deps, bump version, and update CHANGELOG + ## [0.7.0] - ♻️ Testing suite refactor + model changes - 2023-06-09 Maintenance-only update, without new features. diff --git a/notebooks/Usage example of psychrochart.ipynb b/notebooks/Usage example of psychrochart.ipynb index f687f62..0ca2b0c 100644 --- a/notebooks/Usage example of psychrochart.ipynb +++ b/notebooks/Usage example of psychrochart.ipynb @@ -25,7 +25,7 @@ " \n", " \n", " \n", - " 2023-06-06T12:39:52.936357\n", + " 2023-06-11T11:25:04.745658\n", " image/svg+xml\n", " \n", " \n", @@ -38,9 +38,9 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: #ffbf00; fill-opacity: 0.5; stroke-dasharray: 7.4,3.2; stroke-dashoffset: 0; stroke: #ffbf00; stroke-opacity: 0.8; stroke-width: 2; stroke-linejoin: miter\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: #7f9fff; fill-opacity: 0.5; stroke-dasharray: 7.4,3.2; stroke-dashoffset: 0; stroke: #7f9fcc; stroke-width: 2; stroke-linejoin: miter\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -113,7 +113,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -153,7 +153,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -183,7 +183,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -197,7 +197,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -237,7 +237,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -251,7 +251,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -299,7 +299,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -313,7 +313,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -348,7 +348,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -362,7 +362,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -373,7 +373,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -713,19 +713,19 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -735,10 +735,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -748,10 +748,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -761,10 +761,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -806,10 +806,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -860,10 +860,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -874,10 +874,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -888,10 +888,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -902,10 +902,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -916,10 +916,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -930,10 +930,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -944,10 +944,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -958,10 +958,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -972,10 +972,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -986,10 +986,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1000,10 +1000,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1014,10 +1014,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1028,10 +1028,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1042,10 +1042,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1056,10 +1056,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1070,10 +1070,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1081,7 +1081,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -1332,717 +1332,717 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pee75016495)\" style=\"fill: none; stroke: #da0147; stroke-width: 3; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2770,7 +2770,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2789,7 +2789,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2808,7 +2808,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2839,7 +2839,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2859,7 +2859,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2879,7 +2879,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -2976,7 +2976,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3028,7 +3028,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3049,7 +3049,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3138,7 +3138,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3150,7 +3150,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3162,7 +3162,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3174,7 +3174,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3186,7 +3186,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3198,7 +3198,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3230,7 +3230,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3239,7 +3239,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3249,7 +3249,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3259,7 +3259,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3269,7 +3269,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3279,7 +3279,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3289,7 +3289,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3299,8 +3299,8 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3732,7 +3732,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3756,7 +3756,7 @@ " \n", " \n", " \n", - " 2023-06-06T12:39:53.085018\n", + " 2023-06-11T11:25:04.891291\n", " image/svg+xml\n", " \n", " \n", @@ -3769,9 +3769,9 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: #ffbf00; fill-opacity: 0.5; stroke-dasharray: 7.4,3.2; stroke-dashoffset: 0; stroke: #ffbf00; stroke-opacity: 0.8; stroke-width: 2; stroke-linejoin: miter\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: #7f9fff; fill-opacity: 0.5; stroke-dasharray: 7.4,3.2; stroke-dashoffset: 0; stroke: #7f9fcc; stroke-width: 2; stroke-linejoin: miter\"/>\n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3869,7 +3869,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3899,7 +3899,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3913,7 +3913,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3961,7 +3961,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -3996,7 +3996,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4037,7 +4037,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4080,7 +4080,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4420,19 +4420,19 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4442,10 +4442,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4467,10 +4467,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4512,10 +4512,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4523,7 +4523,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -4774,170 +4774,170 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #400080\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #007fff\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", - " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p0360dc2376)\" style=\"fill: none; stroke-dasharray: 6.4,1.6,1,1.6; stroke-dashoffset: 0; stroke: #7fdfff\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5086,7 +5083,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5175,7 +5172,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5187,7 +5184,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5199,7 +5196,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5211,7 +5208,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5244,8 +5241,8 @@ " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5456,7 +5453,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5590,7 +5587,7 @@ " \n", " \n", " \n", - " 2023-06-06T12:40:02.119299\n", + " 2023-06-11T11:25:05.262557\n", " image/svg+xml\n", " \n", " \n", @@ -5603,9 +5600,9 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5677,7 +5674,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5738,7 +5735,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5752,7 +5749,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5800,7 +5797,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -5811,7 +5808,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6104,19 +6101,19 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6126,10 +6123,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6139,10 +6136,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6173,10 +6170,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6218,10 +6215,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6272,10 +6269,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6286,10 +6283,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6300,10 +6297,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6314,10 +6311,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6328,10 +6325,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6342,10 +6339,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6356,10 +6353,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6370,10 +6367,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6381,7 +6378,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -6600,485 +6597,485 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p2ba5ee5c29)\" style=\"fill: none; stroke: #ff8c00; stroke-width: 4; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7501,7 +7498,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7628,7 +7625,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7689,7 +7686,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7701,7 +7698,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7713,7 +7710,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7725,7 +7722,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7737,7 +7734,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7761,7 +7758,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7773,7 +7770,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7805,7 +7802,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7815,7 +7812,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7825,7 +7822,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7835,7 +7832,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7848,7 +7845,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7901,7 +7898,7 @@ " \n", " \n", " \n", - " 2023-06-06T12:40:05.331662\n", + " 2023-06-11T11:25:05.388538\n", " image/svg+xml\n", " \n", " \n", @@ -7914,9 +7911,9 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -7992,7 +7989,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8033,7 +8030,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8046,7 +8043,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8059,7 +8056,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8073,7 +8070,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8087,7 +8084,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8127,7 +8124,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8141,7 +8138,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8189,7 +8186,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8203,7 +8200,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8238,7 +8235,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8252,7 +8249,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8266,7 +8263,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8280,7 +8277,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8326,7 +8323,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8340,7 +8337,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8366,7 +8363,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8380,7 +8377,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8435,7 +8432,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8449,7 +8446,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8495,7 +8492,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8509,7 +8506,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8524,7 +8521,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8539,7 +8536,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8554,7 +8551,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8569,7 +8566,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8584,7 +8581,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8599,7 +8596,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8611,7 +8608,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8885,19 +8882,19 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8907,10 +8904,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8921,10 +8918,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8935,10 +8932,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8949,10 +8946,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8963,10 +8960,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8977,10 +8974,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -8991,10 +8988,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9005,10 +9002,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9019,10 +9016,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9033,10 +9030,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9048,10 +9045,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9063,10 +9060,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9078,10 +9075,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9093,10 +9090,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9108,10 +9105,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9123,10 +9120,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9138,10 +9135,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9153,10 +9150,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9168,10 +9165,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9183,10 +9180,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9198,10 +9195,10 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9210,7 +9207,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -9538,798 +9535,798 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.25; stroke-linecap: square\"/>\n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.75; stroke-linecap: square\"/>\n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 0.5; stroke-linecap: square\"/>\n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-linecap: square\"/>\n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", " \n", - " \n", + " \n", " \n", - " \n", - " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #000000\"/>\n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#pbb0135f888)\" style=\"fill: none; stroke: #000000; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11044,7 +11041,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11064,7 +11061,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11084,7 +11081,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11104,7 +11101,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11188,7 +11185,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11208,7 +11205,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11230,7 +11227,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11250,7 +11247,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11272,7 +11269,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11333,7 +11330,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11345,7 +11342,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11357,7 +11354,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11369,7 +11366,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11394,7 +11391,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11404,7 +11401,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11414,7 +11411,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11424,7 +11421,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11434,7 +11431,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11444,7 +11441,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11454,7 +11451,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11464,7 +11461,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11474,7 +11471,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11484,7 +11481,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11494,7 +11491,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11504,7 +11501,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11514,7 +11511,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11524,7 +11521,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -11975,7 +11972,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12011,7 +12008,29 @@ "name": "stdout", "output_type": "stream", "text": [ - "** Customized style from preconfigured `minimal` style:\n" + "** Each object in plot has a readable name to access it for fine-grain customizations:\n", + "* saturation:\n", + " - saturation_100\n", + "* constant_rh:\n", + " - constant_relative_humidity_20\n", + " - constant_relative_humidity_40\n", + " - constant_relative_humidity_60\n", + " - constant_relative_humidity_80\n", + " - label_legend_constant_relative_humidity\n", + "* constant_v:\n", + " - constant_specific_volume_0_86\n", + " - constant_specific_volume_0_9\n", + " - constant_specific_volume_0_94\n", + " - label_legend_constant_specific_volume\n", + "* layout:\n", + " - chart_background\n", + " - chart_x_axis\n", + " - chart_y_axis\n", + " - chart_x_axis_bottom_line\n", + " - chart_y_axis_right_line\n", + "\n", + "\n", + "** Customized style from preconfigured `minimal` style, with extra customizations:\n" ] }, { @@ -12025,7 +12044,7 @@ " \n", " \n", " \n", - " 2023-06-06T12:40:08.161919\n", + " 2023-06-11T11:25:05.501464\n", " image/svg+xml\n", " \n", " \n", @@ -12038,9 +12057,9 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke: #008056; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke: #008056; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke: #008056; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke-dasharray: 3,4.95; stroke-dashoffset: 0; stroke: #00008b; stroke-width: 3\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke: #007fff; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke: #007fff; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke-dasharray: 3,4.95; stroke-dashoffset: 0; stroke: #00008b; stroke-width: 3\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p27c1de05c7)\" style=\"fill: none; stroke: #da251d; stroke-width: 8; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", @@ -12625,7 +12644,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12653,7 +12672,21 @@ "custom_chart.config.chart_params.with_constant_wet_temp = False\n", "custom_chart.config.chart_params.with_constant_h = False\n", "\n", - "print(\"** Customized style from preconfigured `minimal` style:\")\n", + "# plot to generate matplotlib objects, and continue customizing the chart 🌈 \n", + "custom_chart.plot()\n", + "# show names of objects in plot\n", + "print(\"** Each object in plot has a readable name to access it for fine-grain customizations:\")\n", + "print(custom_chart.artists.render_tree())\n", + "\n", + "# apply customizations for single objects in plot\n", + "custom_chart.artists.saturation[\"saturation_100\"].set_linewidth(8)\n", + "for rh_name in ('constant_relative_humidity_20', 'constant_relative_humidity_80'):\n", + " line = custom_chart.artists.constant_rh[rh_name]\n", + " line.set_linestyle(\":\")\n", + " line.set_linewidth(3)\n", + " line.set_color(\"darkblue\")\n", + "\n", + "print(\"\\n\\n** Customized style from preconfigured `minimal` style, with extra customizations:\")\n", "svg_custom = custom_chart.make_svg()\n", "HTML(svg_custom)" ] @@ -12688,7 +12721,7 @@ " \n", " \n", " \n", - " 2023-06-06T12:40:11.269502\n", + " 2023-06-11T11:25:05.568481\n", " image/svg+xml\n", " \n", " \n", @@ -12701,9 +12734,9 @@ " \n", " \n", " \n", - " \n", - " \n", - " \n", + " \n", + " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12771,7 +12804,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12812,7 +12845,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12852,7 +12885,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12866,7 +12899,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12912,16 +12945,16 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", - " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12934,7 +12967,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12947,7 +12980,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -12981,7 +13014,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13026,7 +13059,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13080,7 +13113,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13094,7 +13127,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13108,7 +13141,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13122,7 +13155,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13136,7 +13169,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13150,7 +13183,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13164,7 +13197,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13178,7 +13211,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13190,293 +13223,293 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #da251d; stroke-width: 0.75\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 0.75,1.2375; stroke-dashoffset: 0; stroke: #002060; stroke-width: 0.75\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 3.7,1.6; stroke-dashoffset: 0; stroke: #008056\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 2.5,4.125; stroke-dashoffset: 0; stroke: #007fff; stroke-opacity: 0.7; stroke-width: 2.5\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 2.5,4.125; stroke-dashoffset: 0; stroke: #007fff; stroke-opacity: 0.7; stroke-width: 2.5\"/>\n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke-dasharray: 2.5,4.125; stroke-dashoffset: 0; stroke: #007fff; stroke-opacity: 0.7; stroke-width: 2.5\"/>\n", " \n", - " \n", - " \n", + " \n", + " \n", " \n", - " \n", " \n", - " \n", + " \n", " \n", - " \n", + " \n", " \n", + "\" clip-path=\"url(#p631e402828)\" style=\"fill: none; stroke: #004cff; stroke-width: 2; stroke-linecap: square\"/>\n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13936,7 +13969,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13957,7 +13990,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -13978,7 +14011,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -14082,7 +14115,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -14094,7 +14127,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -14118,7 +14151,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", @@ -14284,7 +14317,7 @@ " \n", " \n", " \n", - " \n", + " \n", " \n", " \n", " \n", diff --git a/poetry.lock b/poetry.lock index e5d8f05..994dee2 100644 --- a/poetry.lock +++ b/poetry.lock @@ -209,18 +209,18 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.12.0" +version = "3.12.1" description = "A platform independent file lock." optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.12.0-py3-none-any.whl", hash = "sha256:ad98852315c2ab702aeb628412cbf7e95b7ce8c3bf9565670b4eaecf1db370a9"}, - {file = "filelock-3.12.0.tar.gz", hash = "sha256:fc03ae43288c013d2ea83c8597001b1129db351aad9c57fe2409327916b8e718"}, + {file = "filelock-3.12.1-py3-none-any.whl", hash = "sha256:42f1e4ff2b497311213d61ad7aac5fed9050608e5309573f101eefa94143134a"}, + {file = "filelock-3.12.1.tar.gz", hash = "sha256:82b1f7da46f0ae42abf1bc78e548667f484ac59d2bcec38c713cee7e2eb51e83"}, ] [package.extras] -docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.2.3)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] +docs = ["furo (>=2023.5.20)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "diff-cover (>=7.5)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] name = "fonttools" @@ -553,18 +553,18 @@ tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "pa [[package]] name = "platformdirs" -version = "3.5.1" +version = "3.5.3" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." optional = false python-versions = ">=3.7" files = [ - {file = "platformdirs-3.5.1-py3-none-any.whl", hash = "sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5"}, - {file = "platformdirs-3.5.1.tar.gz", hash = "sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f"}, + {file = "platformdirs-3.5.3-py3-none-any.whl", hash = "sha256:0ade98a4895e87dc51d47151f7d2ec290365a585151d97b4d8d6312ed6132fed"}, + {file = "platformdirs-3.5.3.tar.gz", hash = "sha256:e48fabd87db8f3a7df7150a4a5ea22c546ee8bc39bc2473244730d4b56d2cc4e"}, ] [package.extras] -docs = ["furo (>=2023.3.27)", "proselint (>=0.13)", "sphinx (>=6.2.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] -test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4)", "pytest-mock (>=3.10)"] +docs = ["furo (>=2023.5.20)", "proselint (>=0.13)", "sphinx (>=7.0.1)", "sphinx-autodoc-typehints (>=1.23,!=1.23.4)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.3.1)", "pytest-cov (>=4.1)", "pytest-mock (>=3.10)"] [[package]] name = "pluggy" @@ -611,47 +611,47 @@ files = [ [[package]] name = "pydantic" -version = "1.10.8" +version = "1.10.9" description = "Data validation and settings management using python type hints" optional = false python-versions = ">=3.7" files = [ - {file = "pydantic-1.10.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1243d28e9b05003a89d72e7915fdb26ffd1d39bdd39b00b7dbe4afae4b557f9d"}, - {file = "pydantic-1.10.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c0ab53b609c11dfc0c060d94335993cc2b95b2150e25583bec37a49b2d6c6c3f"}, - {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f9613fadad06b4f3bc5db2653ce2f22e0de84a7c6c293909b48f6ed37b83c61f"}, - {file = "pydantic-1.10.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:df7800cb1984d8f6e249351139667a8c50a379009271ee6236138a22a0c0f319"}, - {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0c6fafa0965b539d7aab0a673a046466d23b86e4b0e8019d25fd53f4df62c277"}, - {file = "pydantic-1.10.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e82d4566fcd527eae8b244fa952d99f2ca3172b7e97add0b43e2d97ee77f81ab"}, - {file = "pydantic-1.10.8-cp310-cp310-win_amd64.whl", hash = "sha256:ab523c31e22943713d80d8d342d23b6f6ac4b792a1e54064a8d0cf78fd64e800"}, - {file = "pydantic-1.10.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:666bdf6066bf6dbc107b30d034615d2627e2121506c555f73f90b54a463d1f33"}, - {file = "pydantic-1.10.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:35db5301b82e8661fa9c505c800d0990bc14e9f36f98932bb1d248c0ac5cada5"}, - {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f90c1e29f447557e9e26afb1c4dbf8768a10cc676e3781b6a577841ade126b85"}, - {file = "pydantic-1.10.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:93e766b4a8226e0708ef243e843105bf124e21331694367f95f4e3b4a92bbb3f"}, - {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:88f195f582851e8db960b4a94c3e3ad25692c1c1539e2552f3df7a9e972ef60e"}, - {file = "pydantic-1.10.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:34d327c81e68a1ecb52fe9c8d50c8a9b3e90d3c8ad991bfc8f953fb477d42fb4"}, - {file = "pydantic-1.10.8-cp311-cp311-win_amd64.whl", hash = "sha256:d532bf00f381bd6bc62cabc7d1372096b75a33bc197a312b03f5838b4fb84edd"}, - {file = "pydantic-1.10.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d5b8641c24886d764a74ec541d2fc2c7fb19f6da2a4001e6d580ba4a38f7878"}, - {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b1f6cb446470b7ddf86c2e57cd119a24959af2b01e552f60705910663af09a4"}, - {file = "pydantic-1.10.8-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c33b60054b2136aef8cf190cd4c52a3daa20b2263917c49adad20eaf381e823b"}, - {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:1952526ba40b220b912cdc43c1c32bcf4a58e3f192fa313ee665916b26befb68"}, - {file = "pydantic-1.10.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:bb14388ec45a7a0dc429e87def6396f9e73c8c77818c927b6a60706603d5f2ea"}, - {file = "pydantic-1.10.8-cp37-cp37m-win_amd64.whl", hash = "sha256:16f8c3e33af1e9bb16c7a91fc7d5fa9fe27298e9f299cff6cb744d89d573d62c"}, - {file = "pydantic-1.10.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ced8375969673929809d7f36ad322934c35de4af3b5e5b09ec967c21f9f7887"}, - {file = "pydantic-1.10.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:93e6bcfccbd831894a6a434b0aeb1947f9e70b7468f274154d03d71fabb1d7c6"}, - {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:191ba419b605f897ede9892f6c56fb182f40a15d309ef0142212200a10af4c18"}, - {file = "pydantic-1.10.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:052d8654cb65174d6f9490cc9b9a200083a82cf5c3c5d3985db765757eb3b375"}, - {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ceb6a23bf1ba4b837d0cfe378329ad3f351b5897c8d4914ce95b85fba96da5a1"}, - {file = "pydantic-1.10.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f2e754d5566f050954727c77f094e01793bcb5725b663bf628fa6743a5a9108"}, - {file = "pydantic-1.10.8-cp38-cp38-win_amd64.whl", hash = "sha256:6a82d6cda82258efca32b40040228ecf43a548671cb174a1e81477195ed3ed56"}, - {file = "pydantic-1.10.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e59417ba8a17265e632af99cc5f35ec309de5980c440c255ab1ca3ae96a3e0e"}, - {file = "pydantic-1.10.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:84d80219c3f8d4cad44575e18404099c76851bc924ce5ab1c4c8bb5e2a2227d0"}, - {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2e4148e635994d57d834be1182a44bdb07dd867fa3c2d1b37002000646cc5459"}, - {file = "pydantic-1.10.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:12f7b0bf8553e310e530e9f3a2f5734c68699f42218bf3568ef49cd9b0e44df4"}, - {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:42aa0c4b5c3025483240a25b09f3c09a189481ddda2ea3a831a9d25f444e03c1"}, - {file = "pydantic-1.10.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:17aef11cc1b997f9d574b91909fed40761e13fac438d72b81f902226a69dac01"}, - {file = "pydantic-1.10.8-cp39-cp39-win_amd64.whl", hash = "sha256:66a703d1983c675a6e0fed8953b0971c44dba48a929a2000a493c3772eb61a5a"}, - {file = "pydantic-1.10.8-py3-none-any.whl", hash = "sha256:7456eb22ed9aaa24ff3e7b4757da20d9e5ce2a81018c1b3ebd81a0b88a18f3b2"}, - {file = "pydantic-1.10.8.tar.gz", hash = "sha256:1410275520dfa70effadf4c21811d755e7ef9bb1f1d077a21958153a92c8d9ca"}, + {file = "pydantic-1.10.9-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e692dec4a40bfb40ca530e07805b1208c1de071a18d26af4a2a0d79015b352ca"}, + {file = "pydantic-1.10.9-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3c52eb595db83e189419bf337b59154bdcca642ee4b2a09e5d7797e41ace783f"}, + {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:939328fd539b8d0edf244327398a667b6b140afd3bf7e347cf9813c736211896"}, + {file = "pydantic-1.10.9-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b48d3d634bca23b172f47f2335c617d3fcb4b3ba18481c96b7943a4c634f5c8d"}, + {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:f0b7628fb8efe60fe66fd4adadd7ad2304014770cdc1f4934db41fe46cc8825f"}, + {file = "pydantic-1.10.9-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:e1aa5c2410769ca28aa9a7841b80d9d9a1c5f223928ca8bec7e7c9a34d26b1d4"}, + {file = "pydantic-1.10.9-cp310-cp310-win_amd64.whl", hash = "sha256:eec39224b2b2e861259d6f3c8b6290d4e0fbdce147adb797484a42278a1a486f"}, + {file = "pydantic-1.10.9-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d111a21bbbfd85c17248130deac02bbd9b5e20b303338e0dbe0faa78330e37e0"}, + {file = "pydantic-1.10.9-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2e9aec8627a1a6823fc62fb96480abe3eb10168fd0d859ee3d3b395105ae19a7"}, + {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07293ab08e7b4d3c9d7de4949a0ea571f11e4557d19ea24dd3ae0c524c0c334d"}, + {file = "pydantic-1.10.9-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ee829b86ce984261d99ff2fd6e88f2230068d96c2a582f29583ed602ef3fc2c"}, + {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:4b466a23009ff5cdd7076eb56aca537c745ca491293cc38e72bf1e0e00de5b91"}, + {file = "pydantic-1.10.9-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:7847ca62e581e6088d9000f3c497267868ca2fa89432714e21a4fb33a04d52e8"}, + {file = "pydantic-1.10.9-cp311-cp311-win_amd64.whl", hash = "sha256:7845b31959468bc5b78d7b95ec52fe5be32b55d0d09983a877cca6aedc51068f"}, + {file = "pydantic-1.10.9-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:517a681919bf880ce1dac7e5bc0c3af1e58ba118fd774da2ffcd93c5f96eaece"}, + {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67195274fd27780f15c4c372f4ba9a5c02dad6d50647b917b6a92bf00b3d301a"}, + {file = "pydantic-1.10.9-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2196c06484da2b3fded1ab6dbe182bdabeb09f6318b7fdc412609ee2b564c49a"}, + {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:6257bb45ad78abacda13f15bde5886efd6bf549dd71085e64b8dcf9919c38b60"}, + {file = "pydantic-1.10.9-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:3283b574b01e8dbc982080d8287c968489d25329a463b29a90d4157de4f2baaf"}, + {file = "pydantic-1.10.9-cp37-cp37m-win_amd64.whl", hash = "sha256:5f8bbaf4013b9a50e8100333cc4e3fa2f81214033e05ac5aa44fa24a98670a29"}, + {file = "pydantic-1.10.9-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b9cd67fb763248cbe38f0593cd8611bfe4b8ad82acb3bdf2b0898c23415a1f82"}, + {file = "pydantic-1.10.9-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:f50e1764ce9353be67267e7fd0da08349397c7db17a562ad036aa7c8f4adfdb6"}, + {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:73ef93e5e1d3c8e83f1ff2e7fdd026d9e063c7e089394869a6e2985696693766"}, + {file = "pydantic-1.10.9-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:128d9453d92e6e81e881dd7e2484e08d8b164da5507f62d06ceecf84bf2e21d3"}, + {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:ad428e92ab68798d9326bb3e5515bc927444a3d71a93b4a2ca02a8a5d795c572"}, + {file = "pydantic-1.10.9-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:fab81a92f42d6d525dd47ced310b0c3e10c416bbfae5d59523e63ea22f82b31e"}, + {file = "pydantic-1.10.9-cp38-cp38-win_amd64.whl", hash = "sha256:963671eda0b6ba6926d8fc759e3e10335e1dc1b71ff2a43ed2efd6996634dafb"}, + {file = "pydantic-1.10.9-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:970b1bdc6243ef663ba5c7e36ac9ab1f2bfecb8ad297c9824b542d41a750b298"}, + {file = "pydantic-1.10.9-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:7e1d5290044f620f80cf1c969c542a5468f3656de47b41aa78100c5baa2b8276"}, + {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83fcff3c7df7adff880622a98022626f4f6dbce6639a88a15a3ce0f96466cb60"}, + {file = "pydantic-1.10.9-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0da48717dc9495d3a8f215e0d012599db6b8092db02acac5e0d58a65248ec5bc"}, + {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0a2aabdc73c2a5960e87c3ffebca6ccde88665616d1fd6d3db3178ef427b267a"}, + {file = "pydantic-1.10.9-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9863b9420d99dfa9c064042304868e8ba08e89081428a1c471858aa2af6f57c4"}, + {file = "pydantic-1.10.9-cp39-cp39-win_amd64.whl", hash = "sha256:e7c9900b43ac14110efa977be3da28931ffc74c27e96ee89fbcaaf0b0fe338e1"}, + {file = "pydantic-1.10.9-py3-none-any.whl", hash = "sha256:6cafde02f6699ce4ff643417d1a9223716ec25e228ddc3b436fe7e2d25a1f305"}, + {file = "pydantic-1.10.9.tar.gz", hash = "sha256:95c70da2cd3b6ddf3b9645ecaa8d98f3d80c606624b6d245558d202cd23ea3be"}, ] [package.dependencies] @@ -677,13 +677,13 @@ diagrams = ["jinja2", "railroad-diagrams"] [[package]] name = "pytest" -version = "7.3.1" +version = "7.3.2" description = "pytest: simple powerful testing with Python" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.3.1-py3-none-any.whl", hash = "sha256:3799fa815351fea3a5e96ac7e503a96fa51cc9942c3753cda7651b93c1cfa362"}, - {file = "pytest-7.3.1.tar.gz", hash = "sha256:434afafd78b1d78ed0addf160ad2b77a30d35d4bdf8af234fe621919d9ed15e3"}, + {file = "pytest-7.3.2-py3-none-any.whl", hash = "sha256:cdcbd012c9312258922f8cd3f1b62a6580fdced17db6014896053d47cddf9295"}, + {file = "pytest-7.3.2.tar.gz", hash = "sha256:ee990a3cc55ba808b80795a79944756f315c67c12b56abd3ac993a7b8c17030b"}, ] [package.dependencies] @@ -695,7 +695,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pytest-cov" diff --git a/psychrochart/__init__.py b/psychrochart/__init__.py index d49c34b..4a3b76b 100644 --- a/psychrochart/__init__.py +++ b/psychrochart/__init__.py @@ -1,7 +1,7 @@ """A library to make psychrometric charts and overlay information in them.""" from psychrochart.chart import PsychroChart -from psychrochart.models.annots import ChartAnnots, ChartZones -from psychrochart.models.config import ChartConfig +from psychrochart.models.annots import ChartAnnots +from psychrochart.models.config import ChartConfig, ChartZones from psychrochart.models.curves import PsychroCurve, PsychroCurves from psychrochart.models.parsers import load_config, load_zones from psychrochart.models.styles import ( diff --git a/psychrochart/chart.py b/psychrochart/chart.py index 648756e..b07679c 100644 --- a/psychrochart/chart.py +++ b/psychrochart/chart.py @@ -5,19 +5,23 @@ from typing import Any, Iterable, Type from matplotlib import figure -from matplotlib.artist import Artist from matplotlib.axes import Axes from matplotlib.backend_bases import FigureCanvasBase from matplotlib.backends.backend_agg import FigureCanvasAgg from matplotlib.backends.backend_svg import FigureCanvasSVG -from matplotlib.legend import Legend +from psychrochart.chart_entities import ( + ChartRegistry, + make_item_gid, + reg_artist, +) from psychrochart.chartdata import ( gen_points_in_constant_relative_humidity, make_constant_dry_bulb_v_line, + make_saturation_line, ) -from psychrochart.models.annots import ChartAnnots, ChartZones -from psychrochart.models.config import ChartConfig +from psychrochart.models.annots import ChartAnnots +from psychrochart.models.config import ChartConfig, ChartZones from psychrochart.models.curves import PsychroChartModel from psychrochart.models.parsers import ( ConvexGroupTuple, @@ -34,8 +38,7 @@ plot_curve, ) from psychrochart.process_logic import ( - append_zones_to_chart, - generate_psychrochart, + get_pressure_pa, update_psychrochart_data, ) from psychrochart.util import mod_color @@ -61,16 +64,16 @@ class PsychroChart(PsychroChartModel): config: ChartConfig _fig: figure.Figure | None = None _axes: Axes | None = None - _legend: Legend | None = None - _handlers_annotations: list[Artist | list[Artist]] + _artists: ChartRegistry class Config: arbitrary_types_allowed = True underscore_attrs_are_private = True def _init_private_attributes(self) -> None: - self._handlers_annotations = [] + self._artists = ChartRegistry() super()._init_private_attributes() + self.config._has_changed = True @classmethod def create( @@ -81,12 +84,24 @@ def create( use_unit_system_si: bool = True, ): chart_config = obj_loader(ChartConfig, config) - chart_config.commit_changes() - chart_data = generate_psychrochart( - chart_config, extra_zones, use_unit_system_si + if extra_zones is not None: + zones_use = obj_loader(ChartZones, extra_zones).zones + chart_config.chart_params.with_zones = True + chart_config.chart_params.zones = zones_use + pressure = get_pressure_pa(chart_config.limits, use_unit_system_si) + return cls( + config=chart_config, + unit_system_si=use_unit_system_si, + altitude_m=chart_config.limits.altitude_m, + pressure=pressure, + saturation=make_saturation_line( + chart_config.dbt_min, + chart_config.dbt_max, + chart_config.limits.step_temp, + pressure, + style=chart_config.saturation, + ), ) - chart = cls(config=chart_config, **chart_data.dict()) - return chart def __repr__(self) -> str: """Return a string representation of the PsychroChart object.""" @@ -97,24 +112,40 @@ def __repr__(self) -> str: f"->{self.config.w_max:g} gr/kg_da]>" ) - def _ensure_updated_data(self): + def process_chart(self) -> bool: + """Apply chart config on limits to generate all curves for plot.""" if self.config.has_changed: update_psychrochart_data(self, self.config) + return True + return False + + @property + def rendered(self) -> bool: + """Check if Axes object is assigned.""" + return self._axes is not None @property def axes(self) -> Axes: """Return the Axes object plotting the chart if necessary.""" - self._ensure_updated_data() - if self._axes is None: + self.process_chart() + if not self.rendered: self.plot() assert isinstance(self._axes, Axes) return self._axes + @property + def artists(self) -> ChartRegistry: + """Access to registry of all matplotlib Artists in plot.""" + return self._artists + def append_zones( self, zones: ChartZones | dict[str, Any] | str | None = None ) -> None: """Append zones as patches to the psychrometric chart.""" - append_zones_to_chart(self.config, self, zones) + zones_use = obj_loader(ChartZones, zones).zones + self.config.chart_params.with_zones = True + self.config.chart_params.zones += zones_use + assert self.config.has_changed def plot_points_dbt_rh( self, @@ -201,9 +232,7 @@ def plot_points_dbt_rh( areas=data_areas, use_scatter=scatter_style is not None, ) - self._handlers_annotations.extend( - plot_annots_dbt_rh(self.axes, annots) - ) + self._artists.annotations.update(plot_annots_dbt_rh(self.axes, annots)) return annots def plot_arrows_dbt_rh( @@ -234,16 +263,16 @@ def plot_arrows_dbt_rh( w_g_ka2 = gen_points_in_constant_relative_humidity( [temp2], point2[1], self.pressure )[0] - - self._handlers_annotations.append( - self.axes.annotate( - "", - (temp2, w_g_ka2), - xytext=(temp1, w_g_ka1), - arrowprops=plot_params, - ) + arrow = self.axes.annotate( + "", + (temp2, w_g_ka2), + xytext=(temp1, w_g_ka1), + arrowprops=plot_params, ) - + arrow_gid = make_item_gid( + f"arrow_{key}", name=f"{temp1}_{w_g_ka1}__{temp2}_{w_g_ka2}" + ) + reg_artist(arrow_gid, arrow, self._artists.annotations) points_plot[key] = (temp1, w_g_ka1), (temp2, w_g_ka2), plot_params return points_plot @@ -268,9 +297,16 @@ def plot_vertical_dry_bulb_temp_line( type_curve="constant-dbt", reverse=reverse, ) - - if plot_curve(curve, self.axes) and label is not None: - add_label_to_curve(curve, self.axes, label, **label_params) + new_artists = plot_curve(curve, self.axes) + self._artists.annotations.update(new_artists) + if new_artists and label is not None: + reg_artist( + make_item_gid( + "label-vline", family_label="constant-dbt", name=label + ), + add_label_to_curve(curve, self.axes, label, **label_params), + self._artists.annotations, + ) def plot_legend( self, @@ -284,20 +320,24 @@ def plot_legend( **params, ) -> None: """Append a legend to the psychrochart plot.""" - self._legend = self.axes.legend( - loc=loc, - markerscale=markerscale, - frameon=frameon, - edgecolor=edgecolor, - fontsize=fontsize, - fancybox=fancybox, - labelspacing=labelspacing, - **params, + reg_artist( + "chart_legend", + self.axes.legend( + loc=loc, + markerscale=markerscale, + frameon=frameon, + edgecolor=edgecolor, + fontsize=fontsize, + fancybox=fancybox, + labelspacing=labelspacing, + **params, + ), + self._artists.layout, ) def plot(self, ax: Axes | None = None) -> Axes: """Plot the psychrochart and return the matplotlib Axes instance.""" - self._ensure_updated_data() + self.process_chart() if ax is not None: self._fig = ax.get_figure() else: @@ -306,25 +346,27 @@ def plot(self, ax: Axes | None = None) -> Axes: dpi=self.config.figure.dpi, frameon=False, ) + self._fig.set_gid("figure_psychrochart") ax = self._fig.add_subplot(position=self.config.figure.position) + ax.set_gid("chart_axes") + reg_artist("chart_background", ax.patch, self._artists.layout) self._axes = ax - apply_axis_styling(self.config, self._axes) - return plot_chart(self, self._axes) + self._artists.layout.update( + apply_axis_styling(self.config, self._axes) + ) + plot_chart(self, self._axes, self._artists) + return self._axes def remove_annotations(self) -> None: """Remove the annotations made in the chart to reuse it.""" - for line in self._handlers_annotations: - if isinstance(line, list): - line[0].remove() - else: - line.remove() - self._handlers_annotations = [] + for line in self._artists.annotations.values(): + line.remove() + self._artists.annotations = {} def remove_legend(self) -> None: """Remove the legend of the chart.""" - if self._legend is not None: - self._legend.remove() - self._legend = None + if "chart_legend" in self._artists.layout: + self._artists.layout.pop("chart_legend").remove() def save( self, @@ -339,7 +381,7 @@ def save( and not Path(path_dest).parent.exists() ): Path(path_dest).parent.mkdir(parents=True) - if self._axes is None or self.config.has_changed: + if not self.rendered or self.config.has_changed: self.plot() assert self._fig is not None canvas_use = _select_fig_canvas(path_dest, canvas_cls) diff --git a/psychrochart/chart_entities.py b/psychrochart/chart_entities.py index ac58f23..36eabd5 100644 --- a/psychrochart/chart_entities.py +++ b/psychrochart/chart_entities.py @@ -1,6 +1,67 @@ +import logging from uuid import uuid4 +from matplotlib.artist import Artist +from pydantic import BaseModel, Field +from slugify import slugify + def random_internal_value() -> float: """Generate random 'internal_value' for unnamed curves.""" return float(int(f"0x{str(uuid4())[-4:]}", 16)) + + +def make_item_gid( + kind: str, family_label: str | None = None, name: str | None = None +) -> str: + """Generate slugified ids for 'Artist' objects in matplotlib plot.""" + unique_gid = f"{kind}_" + if family_label is not None: + unique_gid += family_label + "_" + if name is not None: + unique_gid += name + else: + logging.warning("Unnamed item: %s", unique_gid) + unique_gid += f"{random_internal_value()}" + return slugify(unique_gid, separator="_") + + +def reg_artist(gid: str, artist: Artist, group: dict[str, Artist]): + """Set GID to plot item and add it to given group.""" + artist.set_gid(gid) + group[gid] = artist + + +class ChartRegistry(BaseModel): + """Artist collection of psychrochart, by kind and unique name.""" + + # psychro curves + saturation: dict[str, Artist] = Field(default_factory=dict) + constant_dry_temp: dict[str, Artist] = Field(default_factory=dict) + constant_humidity: dict[str, Artist] = Field(default_factory=dict) + constant_rh: dict[str, Artist] = Field(default_factory=dict) + constant_h: dict[str, Artist] = Field(default_factory=dict) + constant_v: dict[str, Artist] = Field(default_factory=dict) + constant_wbt: dict[str, Artist] = Field(default_factory=dict) + # info overlay + zones: dict[str, Artist] = Field(default_factory=dict) + annotations: dict[str, Artist] = Field(default_factory=dict) + # axes artists + layout: dict[str, Artist] = Field(default_factory=dict) + + class Config: + arbitrary_types_allowed = True + + def render_tree(self) -> str: # pragma: no cover + """Helper method to show all IDs in plot.""" + + def _section(name): + return f"* {name}:\n - " + "\n - ".join( + getattr(self, name).keys() + ) + + return "\n".join( + _section(group) + for group in self.__fields__ + if getattr(self, group) + ) diff --git a/psychrochart/chart_styles/default_chart_config.json b/psychrochart/chart_styles/default_chart_config.json index 1833c2c..200c337 100644 --- a/psychrochart/chart_styles/default_chart_config.json +++ b/psychrochart/chart_styles/default_chart_config.json @@ -111,6 +111,7 @@ "constant_humid_step": 1, "constant_humid_label_step": 2, "constant_humid_label_include_limits": true, - "with_zones": true + "with_zones": true, + "zones": [] } } diff --git a/psychrochart/chartdata.py b/psychrochart/chartdata.py index 59a2af3..c7cd895 100644 --- a/psychrochart/chartdata.py +++ b/psychrochart/chartdata.py @@ -19,8 +19,6 @@ ) from scipy.interpolate import interp1d -from psychrochart.chart_entities import random_internal_value -from psychrochart.models.annots import ChartZone from psychrochart.models.curves import PsychroCurve, PsychroCurves from psychrochart.models.styles import CurveStyle from psychrochart.util import solve_curves_with_iteration @@ -114,10 +112,11 @@ def make_constant_relative_humidity_lines( temp_step: float, pressure: float, rh_perc_values: list[int], - rh_label_values: list[int], + *, style: CurveStyle, - label_loc: float, - family_label: str | None, + rh_label_values: list[int] | None = None, + label_loc: float = 0.0, + family_label: str | None = None, ) -> PsychroCurves: """Generate curves of constant relative humidity for the chart.""" rh_values = sorted(rh for rh in rh_perc_values if 0 <= rh <= 100) @@ -126,6 +125,7 @@ def make_constant_relative_humidity_lines( gen_points_in_constant_relative_humidity(temps_ct_rh, rh, pressure) for rh in rh_values ] + rh_label_values = rh_label_values or [] return PsychroCurves( curves=[ PsychroCurve( @@ -147,6 +147,7 @@ def make_constant_dry_bulb_v_line( w_humidity_ratio_min: float, temp: float, pressure: float, + *, style: CurveStyle, type_curve: str | None = None, reverse: bool = False, @@ -172,8 +173,9 @@ def make_constant_dry_bulb_v_lines( w_humidity_ratio_min: float, pressure: float, temps_vl: np.ndarray, + *, style: CurveStyle, - family_label: str | None, + family_label: str | None = None, ) -> PsychroCurves: """Generate curves of constant dry bulb temperature (vertical).""" w_max_vec = _get_humid_ratio_in_saturation(temps_vl, pressure) @@ -196,8 +198,9 @@ def make_constant_humidity_ratio_h_lines( dbt_max: float, pressure: float, ws_hl: np.ndarray, + *, style: CurveStyle, - family_label: str | None, + family_label: str | None = None, ) -> PsychroCurves: """Generate curves of constant absolute humidity (horizontal).""" arr_hum_ratios = np.array(ws_hl) / _factor_out_w() @@ -224,32 +227,32 @@ def make_saturation_line( dbt_max: float, temp_step: float, pressure: float, - style: CurveStyle, -) -> PsychroCurves: + style: CurveStyle | None = None, +) -> PsychroCurve: """Generate line of saturation for the psychrochart.""" temps_sat_line = np.arange(dbt_min, dbt_max + temp_step, temp_step) w_sat = gen_points_in_constant_relative_humidity( temps_sat_line, 100, pressure ) - sat_c = PsychroCurve( + return PsychroCurve( x_data=temps_sat_line, y_data=w_sat, - style=style, + style=style if style is not None else CurveStyle(), type_curve="saturation", internal_value=100.0, ) - return PsychroCurves(curves=[sat_c]) def make_constant_enthalpy_lines( w_humidity_ratio_min: float, pressure: float, enthalpy_values: np.ndarray, - h_label_values: list[float], - style: CurveStyle, - label_loc: float, - family_label: str | None, + *, saturation_curve: PsychroCurve, + style: CurveStyle, + h_label_values: list[float] | None = None, + label_loc: float = 0.0, + family_label: str | None = None, dbt_min_seen: float | None = None, ) -> PsychroCurves | None: """Generate curves of constant enthalpy for the chart.""" @@ -311,7 +314,8 @@ def make_constant_enthalpy_lines( label_loc=label_loc, label=( _make_enthalpy_label(h) - if round(h, 3) in h_label_values + if isinstance(h_label_values, list) + and round(h, 3) in h_label_values else None ), internal_value=round(h, 3), @@ -328,11 +332,12 @@ def make_constant_specific_volume_lines( w_humidity_ratio_min: float, pressure: float, vol_values: np.ndarray, - v_label_values: list[float], - style: CurveStyle, - label_loc: float, - family_label: str | None, + *, saturation_curve: PsychroCurve, + style: CurveStyle, + v_label_values: list[float] | None = None, + label_loc: float = 0.0, + family_label: str | None = None, dbt_min_seen: float | None = None, ) -> PsychroCurves | None: """Generate curves of constant specific volume for the chart.""" @@ -390,7 +395,8 @@ def make_constant_specific_volume_lines( label_loc=label_loc, label=( _make_vol_label(vol) - if round(vol, 3) in v_label_values + if isinstance(v_label_values, list) + and round(vol, 3) in v_label_values else None ), internal_value=round(vol, 3), @@ -410,10 +416,11 @@ def make_constant_wet_bulb_temperature_lines( w_humidity_ratio_max: float, pressure: float, wbt_values: np.ndarray, - wbt_label_values: list[float], + *, style: CurveStyle, - label_loc: float, - family_label: str | None, + wbt_label_values: list[float] | None = None, + label_loc: float = 0.0, + family_label: str | None = None, ) -> PsychroCurves | None: """Generate curves of constant wet bulb temperature for the chart.""" wt_min = GetTWetBulbFromHumRatio( @@ -481,56 +488,14 @@ def make_constant_wet_bulb_temperature_lines( style=style, type_curve="constant_wbt_data", label_loc=label_loc, - label=(_make_temp_label(wbt) if wbt in wbt_label_values else None), + label=( + _make_temp_label(wbt) + if isinstance(wbt_label_values, list) + and round(wbt, 3) in wbt_label_values + else None + ), internal_value=wbt, ) curves.append(c) return PsychroCurves(curves=curves, family_label=family_label) - - -def make_zone_curve( - zone_conf: ChartZone, increment: float, pressure: float -) -> PsychroCurve: - """Generate plot-points for zone.""" - # todo better id for overlay zones if no label - zone_value = random_internal_value() if zone_conf.label is None else None - if zone_conf.zone_type == "xy-points": - # expect points in plot coordinates! - return PsychroCurve( - x_data=np.array(zone_conf.points_x), - y_data=np.array(zone_conf.points_y), - style=zone_conf.style, - type_curve="xy-points", - label=zone_conf.label, - internal_value=zone_value, - ) - - assert zone_conf.zone_type == "dbt-rh" - # points for zone between constant dry bulb temps and RH - t_min = zone_conf.points_x[0] - t_max = zone_conf.points_x[-1] - rh_min = zone_conf.points_y[0] - rh_max = zone_conf.points_y[-1] - assert rh_min >= 0.0 and rh_max <= 100.0 - assert t_min < t_max - - temps = np.arange(t_min, t_max + increment, increment) - curve_rh_up = gen_points_in_constant_relative_humidity( - temps, rh_max, pressure - ) - curve_rh_down = gen_points_in_constant_relative_humidity( - temps, rh_min, pressure - ) - abs_humid: list[float] = ( - list(curve_rh_up) + list(curve_rh_down)[::-1] + [curve_rh_up[0]] - ) - temps_zone: list[float] = list(temps) + list(temps)[::-1] + [temps[0]] - return PsychroCurve( - x_data=np.array(temps_zone), - y_data=np.array(abs_humid), - style=zone_conf.style, - type_curve="dbt-rh", - label=zone_conf.label, - internal_value=zone_value, - ) diff --git a/psychrochart/chartzones.py b/psychrochart/chartzones.py new file mode 100644 index 0000000..a56ca3e --- /dev/null +++ b/psychrochart/chartzones.py @@ -0,0 +1,68 @@ +import numpy as np + +from psychrochart.chart_entities import random_internal_value +from psychrochart.chartdata import gen_points_in_constant_relative_humidity +from psychrochart.models.config import ChartZone +from psychrochart.models.curves import PsychroCurve + + +def _make_zone_delimited_by_vertical_dbt_and_rh( + zone: ChartZone, pressure: float, *, step_temp: float = 1.0 +) -> PsychroCurve: + assert zone.zone_type == "dbt-rh" + # points for zone between constant dry bulb temps and RH + t_min = zone.points_x[0] + t_max = zone.points_x[-1] + rh_min = zone.points_y[0] + rh_max = zone.points_y[-1] + assert rh_min >= 0.0 and rh_max <= 100.0 + assert t_min < t_max + + temps = np.arange(t_min, t_max + step_temp, step_temp) + curve_rh_up = gen_points_in_constant_relative_humidity( + temps, rh_max, pressure + ) + curve_rh_down = gen_points_in_constant_relative_humidity( + temps, rh_min, pressure + ) + abs_humid: list[float] = ( + list(curve_rh_up) + list(curve_rh_down)[::-1] + [curve_rh_up[0]] + ) + temps_zone: list[float] = list(temps) + list(temps)[::-1] + [temps[0]] + return PsychroCurve( + x_data=np.array(temps_zone), + y_data=np.array(abs_humid), + style=zone.style, + type_curve="dbt-rh", + label=zone.label, + internal_value=random_internal_value() if zone.label is None else None, + ) + + +def make_zone_curve( + zone_conf: ChartZone, + *, + pressure: float, + step_temp: float, + # dbt_min: float = 0.0, + # dbt_max: float = 50.0, + # w_min: float = 0.0, +) -> PsychroCurve | None: + """Generate plot-points for zone definition.""" + if zone_conf.zone_type == "dbt-rh": + # points for zone between vertical dbt lines and RH ranges + return _make_zone_delimited_by_vertical_dbt_and_rh( + zone_conf, pressure, step_temp=step_temp + ) + + # expect points in plot coordinates! + assert zone_conf.zone_type == "xy-points" + zone_value = random_internal_value() if zone_conf.label is None else None + return PsychroCurve( + x_data=np.array(zone_conf.points_x), + y_data=np.array(zone_conf.points_y), + style=zone_conf.style, + type_curve="xy-points", + label=zone_conf.label, + internal_value=zone_value, + ) diff --git a/psychrochart/models/annots.py b/psychrochart/models/annots.py index 09ede91..0885bb7 100644 --- a/psychrochart/models/annots.py +++ b/psychrochart/models/annots.py @@ -1,9 +1,9 @@ -from typing import Any, Literal +from typing import Any import numpy as np from pydantic import BaseModel, Field, root_validator -from psychrochart.models.styles import CurveStyle, ZoneStyle +from psychrochart.models.styles import CurveStyle from psychrochart.models.validators import ( check_connector_and_areas_by_point_name, parse_curve_arrays, @@ -13,7 +13,7 @@ class ChartPoint(BaseModel): - """Pydantic model for single point annotation.""" + """Input model for single point annotations.""" xy: tuple[float | int, float | int] label: str | None = Field(default=None) @@ -21,7 +21,7 @@ class ChartPoint(BaseModel): class ChartSeries(BaseModel): - """Pydantic model for data-series point array annotation.""" + """Input model for data-series point array annotation.""" # TODO fusion with PsychroCurve, + pandas ready x_data: np.ndarray @@ -103,46 +103,3 @@ def get_point_by_name(self, key: str) -> tuple[float, float]: else: assert key in self.series return self.series[key].x_data[0], self.series[key].y_data[0] - - -class ChartZone(BaseModel): - """Pydantic model for styled fixed areas on the psychrochart.""" - - label: str | None - zone_type: Literal["dbt-rh", "xy-points"] - points_x: list[float] - points_y: list[float] - style: ZoneStyle - - -class ChartZones(BaseModel): - """Container model for a list of ChartZone items.""" - - zones: list[ChartZone] - - -# default fixed areas for winter/summer comfort zones -DEFAULT_ZONES = ChartZones( - zones=[ - ChartZone( - label="Summer", - zone_type="dbt-rh", - points_x=[23, 25], - points_y=[45, 60], - style=ZoneStyle( - edgecolor=[1.0, 0.749, 0.0, 0.8], - facecolor=[1.0, 0.749, 0.0, 0.5], - ), - ), - ChartZone( - label="Winter", - zone_type="dbt-rh", - points_x=[21, 23], - points_y=[40, 50], - style=ZoneStyle( - edgecolor=[0.498, 0.624, 0.8], - facecolor=[0.498, 0.624, 1.0, 0.5], - ), - ), - ] -) diff --git a/psychrochart/models/config.py b/psychrochart/models/config.py index 98dd923..c2b0c6b 100644 --- a/psychrochart/models/config.py +++ b/psychrochart/models/config.py @@ -1,10 +1,13 @@ -from pydantic import Extra, Field +from typing import Literal + +from pydantic import Extra, Field, root_validator from psychrochart.models.styles import ( BaseConfig, CurveStyle, LabelStyle, TickStyle, + ZoneStyle, ) _DEFAULT_RANGE_VOL_M3_KG = (0.78, 0.98) @@ -36,6 +39,8 @@ color=[0.0, 0.125, 0.376], linewidth=0.75, linestyle=":" ) +ZoneKind = Literal["dbt-rh", "xy-points"] + class ChartFigure(BaseConfig): """Psychrochart settings for matplotlib Figure.""" @@ -70,6 +75,37 @@ class ChartLimits(BaseConfig): step_temp: float = Field(default=1) +class ChartZone(BaseConfig): + """Pydantic model for styled fixed areas on the psychrochart.""" + + points_x: list[float] + points_y: list[float] + label: str | None = Field(default=None) + zone_type: ZoneKind = Field(default="xy-points") + style: ZoneStyle = Field(default_factory=ZoneStyle) + + @root_validator + def _validate_zone_definition(cls, values): + shape = len(values["points_x"]), len(values["points_y"]) + if shape[0] < 2 or shape[1] < 2 or shape[0] != shape[1]: + raise ValueError(f"Invalid shape: {shape}") + if values["zone_type"] != "xy-points" and ( + shape != (2, 2) + or (values["points_x"][1] < values["points_x"][0]) + or (values["points_y"][1] < values["points_y"][0]) + ): + raise ValueError( + f"Invalid shape for {values['zone_type']}: {shape}" + ) + return values + + +class ChartZones(BaseConfig): + """Container model for a list of ChartZone items.""" + + zones: list[ChartZone] = Field(default_factory=list) + + class ChartParams(BaseConfig): """Psychrochart plotting parameters.""" @@ -120,6 +156,7 @@ class ChartParams(BaseConfig): constant_humid_label_include_limits: bool = Field(default=True) with_zones: bool = Field(default=True) + zones: list[ChartZone] = Field(default_factory=list) class ChartConfig(BaseConfig): @@ -168,3 +205,30 @@ def w_min(self) -> float: def w_max(self) -> float: """Top limit for chart.""" return self.limits.range_humidity_g_kg[1] + + +# default fixed areas for winter/summer comfort zones +DEFAULT_ZONES = ChartZones( + zones=[ + ChartZone( + label="Summer", + zone_type="dbt-rh", + points_x=[23, 25], + points_y=[45, 60], + style=ZoneStyle( + edgecolor=[1.0, 0.749, 0.0, 0.8], + facecolor=[1.0, 0.749, 0.0, 0.5], + ), + ), + ChartZone( + label="Winter", + zone_type="dbt-rh", + points_x=[21, 23], + points_y=[40, 50], + style=ZoneStyle( + edgecolor=[0.498, 0.624, 0.8], + facecolor=[0.498, 0.624, 1.0, 0.5], + ), + ), + ] +) diff --git a/psychrochart/models/curves.py b/psychrochart/models/curves.py index d1151a1..86d92f9 100644 --- a/psychrochart/models/curves.py +++ b/psychrochart/models/curves.py @@ -37,7 +37,7 @@ def _parse_curve_data(cls, values): def curve_id(self) -> str: """Get Curve identifier (value or label).""" if self.internal_value is not None: - return f"{self.internal_value:g}" + return f"{self.internal_value:g}".replace("-", "minus") assert self.label is not None return self.label @@ -100,11 +100,11 @@ class PsychroChartModel(BaseModel): altitude_m: int pressure: float - saturation: PsychroCurves + saturation: PsychroCurve constant_dry_temp_data: PsychroCurves | None = None constant_humidity_data: PsychroCurves | None = None constant_rh_data: PsychroCurves | None = None constant_h_data: PsychroCurves | None = None constant_v_data: PsychroCurves | None = None constant_wbt_data: PsychroCurves | None = None - zones: list[PsychroCurves] = Field(default_factory=list) + zones: list[PsychroCurve] = Field(default_factory=list) diff --git a/psychrochart/models/parsers.py b/psychrochart/models/parsers.py index 1603222..b78e8cc 100644 --- a/psychrochart/models/parsers.py +++ b/psychrochart/models/parsers.py @@ -10,11 +10,9 @@ ChartLine, ChartPoint, ChartSeries, - ChartZones, ConvexGroupTuple, - DEFAULT_ZONES, ) -from psychrochart.models.config import ChartConfig +from psychrochart.models.config import ChartConfig, ChartZones, DEFAULT_ZONES PATH_STYLES = Path(__file__).absolute().parents[1] / "chart_styles" DEFAULT_CHART_CONFIG_FILE = str(PATH_STYLES / "default_chart_config.json") diff --git a/psychrochart/plot_logic.py b/psychrochart/plot_logic.py index 12611df..50b026d 100644 --- a/psychrochart/plot_logic.py +++ b/psychrochart/plot_logic.py @@ -11,6 +11,11 @@ import numpy as np from scipy.spatial import ConvexHull, QhullError +from psychrochart.chart_entities import ( + ChartRegistry, + make_item_gid, + reg_artist, +) from psychrochart.models.annots import ChartAnnots from psychrochart.models.config import ChartConfig from psychrochart.models.curves import ( @@ -109,8 +114,9 @@ def _tilt_params(x_data, y_data, idx_0, idx_f): def plot_curve( curve: PsychroCurve, ax: Axes, label_prefix: str | None = None -) -> list[Artist]: +) -> dict[str, Artist]: """Plot the curve, if it's between chart limits.""" + artists: dict[str, Artist] = {} xmin, xmax = ax.get_xlim() ymin, ymax = ax.get_ylim() if ( @@ -133,9 +139,8 @@ def plot_curve( curve.x_data, curve.y_data, ) - return [] + return {} - artists = [] if isinstance(curve.style, ZoneStyle): assert len(curve.y_data) > 2 verts = list(zip(curve.x_data, curve.y_data)) @@ -147,8 +152,16 @@ def plot_curve( path = Path(verts, codes) patch = patches.PathPatch(path, **curve.style.dict()) ax.add_patch(patch) - artists.append(patch) - + gid_zone = make_item_gid( + "zone", + family_label=label_prefix or curve.type_curve, + name=curve.curve_id, + ) + reg_artist( + gid_zone, + patch, + artists, + ) if curve.label is not None: bbox_p = path.get_extents() text_x = 0.5 * (bbox_p.x0 + bbox_p.x1) @@ -160,46 +173,62 @@ def plot_curve( } assert isinstance(curve.style, ZoneStyle) style_params["color"] = mod_color(curve.style.edgecolor, -25) - artist_label = _annotate_label( - ax, curve.label, text_x, text_y, 0, style_params + reg_artist( + "label_" + gid_zone, + _annotate_label( + ax, curve.label, text_x, text_y, 0, style_params + ), + artists, ) - artists.append(artist_label) else: - artist_line = ax.plot(curve.x_data, curve.y_data, **curve.style.dict()) - artists.append(artist_line) + [artist_line] = ax.plot( + curve.x_data, curve.y_data, **curve.style.dict() + ) + kind = ( + (label_prefix or curve.type_curve) + if len(curve.x_data) > 1 + else "point" + ) + gid_line = make_item_gid(kind or "unknown", name=curve.curve_id) + reg_artist(gid_line, artist_line, artists) if curve.label is not None: - artists.append(add_label_to_curve(curve, ax)) + reg_artist( + "label_" + gid_line, add_label_to_curve(curve, ax), artists + ) return artists -def plot_curves_family(family: PsychroCurves | None, ax: Axes) -> list[Artist]: +def plot_curves_family( + family: PsychroCurves | None, ax: Axes +) -> dict[str, Artist]: """Plot all curves in the family.""" - artists: list[Artist] = [] if family is None: - return artists - - [ - plot_curve(curve, ax, label_prefix=family.family_label) + return {} + artists: dict[str, Artist] = { + gid: item for curve in family.curves - ] + for gid, item in plot_curve( + curve, ax, label_prefix=family.family_label + ).items() + } # Curves family labelling if family.curves and family.family_label is not None: - artist_fam_label = ax.plot( + # artist for legend (1 label for each family) + min_params = {"marker": "D", "markersize": 10} + [artist_fam_label] = ax.plot( [-1], [-1], label=family.family_label, - marker="D", - markersize=10, - **family.curves[0].style.dict(), + **(min_params | family.curves[0].style.dict()), ) - artists.append(artist_fam_label) + gid_family_label = make_item_gid( + "label_legend", name=family.family_label + ) + artist_fam_label.set_gid(gid_family_label) + artists[gid_family_label] = artist_fam_label - # return [ - # art for art in artist_curves + artist_labels if art is not None - # ] - # TODO collect artists from plot_curve - return [] + return artists def _apply_spines_style(axes, style, location="right") -> None: @@ -213,8 +242,11 @@ def _apply_spines_style(axes, style, location="right") -> None: ) -def apply_axis_styling(config: ChartConfig, ax: Axes) -> None: +def apply_axis_styling(config: ChartConfig, ax: Axes) -> dict[str, Artist]: """Setup matplotlib Axes object for the chart.""" + layout_artists: dict[str, Artist] = {} + reg_artist("chart_x_axis", ax.xaxis, layout_artists) + reg_artist("chart_y_axis", ax.yaxis, layout_artists) ax.yaxis.tick_right() ax.yaxis.set_label_position("right") ax.set_xlim(config.dbt_min, config.dbt_max) @@ -224,27 +256,33 @@ def apply_axis_styling(config: ChartConfig, ax: Axes) -> None: if config.figure.x_label is not None: style_axis = config.figure.x_axis_labels.dict() style_axis["fontsize"] *= 1.2 - ax.set_xlabel(config.figure.x_label, **style_axis) + artist_xlabel = ax.set_xlabel(config.figure.x_label, **style_axis) + reg_artist("chart_x_axis_label", artist_xlabel, layout_artists) if config.figure.y_label is not None: style_axis = config.figure.y_axis_labels.dict() style_axis["fontsize"] *= 1.2 - ax.set_ylabel(config.figure.y_label, **style_axis) + artist_ylabel = ax.set_ylabel(config.figure.y_label, **style_axis) + reg_artist("chart_y_axis_label", artist_ylabel, layout_artists) if config.figure.title is not None: - ax.set_title( + artist_title = ax.set_title( config.figure.title, fontsize=config.figure.fontsize * 1.5, fontweight="bold", ) + reg_artist("chart_title", artist_title, layout_artists) _apply_spines_style(ax, config.figure.y_axis.dict(), location="right") _apply_spines_style(ax, config.figure.x_axis.dict(), location="bottom") + reg_artist("chart_x_axis_bottom_line", ax.spines["bottom"], layout_artists) + reg_artist("chart_y_axis_right_line", ax.spines["right"], layout_artists) if config.figure.partial_axis: # Hide left and top axis ax.spines["left"].set_visible(False) ax.spines["top"].set_visible(False) else: _apply_spines_style(ax, config.figure.y_axis.dict(), location="left") _apply_spines_style(ax, config.figure.x_axis.dict(), location="top") - + reg_artist("chart_x_axis_top_line", ax.spines["top"], layout_artists) + reg_artist("chart_y_axis_left_line", ax.spines["left"], layout_artists) if config.figure.x_axis_ticks is not None: ax.tick_params(axis="x", **config.figure.x_axis_ticks.dict()) if config.figure.y_axis_ticks is not None: @@ -286,53 +324,67 @@ def apply_axis_styling(config: ChartConfig, ax: Axes) -> None: ) else: ax.set_yticks([]) + return layout_artists -def plot_chart(chart: PsychroChartModel, ax: Axes) -> Axes: +def plot_chart( + chart: PsychroChartModel, ax: Axes, registry: ChartRegistry | None = None +) -> ChartRegistry: """Plot the psychrochart curves on given Axes.""" + if registry is None: + registry = ChartRegistry() # Plot curves: - plot_curves_family(chart.constant_dry_temp_data, ax) - plot_curves_family(chart.constant_humidity_data, ax) - plot_curves_family(chart.constant_h_data, ax) - plot_curves_family(chart.constant_v_data, ax) - plot_curves_family(chart.constant_rh_data, ax) - plot_curves_family(chart.constant_wbt_data, ax) - plot_curves_family(chart.saturation, ax) + if data := plot_curves_family(chart.constant_dry_temp_data, ax): + registry.constant_dry_temp = data + if data := plot_curves_family(chart.constant_humidity_data, ax): + registry.constant_humidity = data + if data := plot_curves_family(chart.constant_h_data, ax): + registry.constant_h = data + if data := plot_curves_family(chart.constant_v_data, ax): + registry.constant_v = data + if data := plot_curves_family(chart.constant_rh_data, ax): + registry.constant_rh = data + if data := plot_curves_family(chart.constant_wbt_data, ax): + registry.constant_wbt = data + registry.saturation = plot_curve(chart.saturation, ax) # Plot zones: for zone in chart.zones: - plot_curves_family(zone, ax) - return ax + registry.zones.update(plot_curve(zone, ax)) + return registry -def plot_annots_dbt_rh( - ax: Axes, annots: ChartAnnots -) -> list[Artist | list[Artist]]: +def plot_annots_dbt_rh(ax: Axes, annots: ChartAnnots) -> dict[str, Artist]: """Plot chat annotations in given matplotlib Axes, return `Artist` objs.""" - _handlers_annotations = [] + annot_artists: dict[str, Artist] = {} for d_con in annots.connectors: x_start, y_start = annots.get_point_by_name(d_con.start) x_end, y_end = annots.get_point_by_name(d_con.end) x_line = [x_start, x_end] y_line = [y_start, y_end] - _handlers_annotations.append( - ax.plot( + d_con_gid = make_item_gid( + "connector", name=d_con.label or f"{d_con.start}_{d_con.end}" + ) + [artist_connector] = ax.plot( + x_line, + y_line, + label=d_con.label, + dash_capstyle="round", + **d_con.style.dict(), + ) + reg_artist(d_con_gid, artist_connector, annot_artists) + if d_con.outline_marker_width: + [artist_connector_marker] = ax.plot( x_line, y_line, - label=d_con.label, - dash_capstyle="round", - **d_con.style.dict(), + color=[*d_con.style.color[:3], 0.15], + lw=d_con.outline_marker_width, + solid_capstyle="round", ) - ) - if d_con.outline_marker_width: - _handlers_annotations.append( - ax.plot( - x_line, - y_line, - color=[*d_con.style.color[:3], 0.15], - lw=d_con.outline_marker_width, - solid_capstyle="round", - ) + reg_artist( + d_con_gid + "_outline_mark", + artist_connector_marker, + annot_artists, ) forbidden = set() @@ -341,20 +393,24 @@ def plot_annots_dbt_rh( forbidden.add("markersize") else: f_plot = ax.plot - for series in annots.series.values(): + for name, series in annots.series.items(): style = {k: v for k, v in series.style.items() if k not in forbidden} - _handlers_annotations.append( - f_plot(series.x_data, series.y_data, label=series.label, **style) + artists = f_plot( + series.x_data, series.y_data, label=series.label, **style ) + artist_line = artists[0] if isinstance(artists, list) else artists + line_gid = make_item_gid("series", name=series.label or name) + reg_artist(line_gid, artist_line, annot_artists) - for point in annots.points.values(): + for name, point in annots.points.items(): style = {k: v for k, v in point.style.items() if k not in forbidden} - _handlers_annotations.append( - f_plot(point.xy[0], point.xy[1], label=point.label, **style) - ) + artists = f_plot(point.xy[0], point.xy[1], label=point.label, **style) + artist_point = artists[0] if isinstance(artists, list) else artists + line_gid = make_item_gid("point", name=point.label or name) + reg_artist(line_gid, artist_point, annot_artists) if ConvexHull is None or not annots.areas: - return _handlers_annotations + return annot_artists for convex_area in annots.areas: int_points = np.array( @@ -367,20 +423,21 @@ def plot_annots_dbt_rh( logging.error(f"QhullError with points: {int_points}") continue - for simplex in hull.simplices: - _handlers_annotations.append( - ax.plot( - int_points[simplex, 0], - int_points[simplex, 1], - **convex_area.line_style, - ) - ) - _handlers_annotations.append( - ax.fill( - int_points[hull.vertices, 0], - int_points[hull.vertices, 1], - **convex_area.fill_style, + area_gid = make_item_gid( + "convexhull", name=",".join(convex_area.point_names) + ) + for i, simplex in enumerate(hull.simplices): + [artist_contour] = ax.plot( + int_points[simplex, 0], + int_points[simplex, 1], + **convex_area.line_style, ) + reg_artist(area_gid + f"_s{i+1}", artist_contour, annot_artists) + [artist_area] = ax.fill( + int_points[hull.vertices, 0], + int_points[hull.vertices, 1], + **convex_area.fill_style, ) + reg_artist(area_gid, artist_area, annot_artists) - return _handlers_annotations + return annot_artists diff --git a/psychrochart/process_logic.py b/psychrochart/process_logic.py index 1da6f3e..f93a4f7 100644 --- a/psychrochart/process_logic.py +++ b/psychrochart/process_logic.py @@ -1,5 +1,4 @@ import logging -from typing import Any import numpy as np import psychrolib as psy @@ -21,12 +20,10 @@ make_constant_specific_volume_lines, make_constant_wet_bulb_temperature_lines, make_saturation_line, - make_zone_curve, ) -from psychrochart.models.annots import ChartZones, DEFAULT_ZONES -from psychrochart.models.config import ChartConfig, ChartLimits -from psychrochart.models.curves import PsychroChartModel, PsychroCurves -from psychrochart.models.parsers import obj_loader +from psychrochart.chartzones import make_zone_curve +from psychrochart.models.config import ChartConfig, ChartLimits, DEFAULT_ZONES +from psychrochart.models.curves import PsychroChartModel spec_vol_vec = np.vectorize(psy.GetMoistAirVolume) @@ -50,34 +47,15 @@ def get_pressure_pa(limits: ChartLimits, unit_system_si: bool = True) -> float: return GetStandardAtmPressure(limits.altitude_m) -def append_zones_to_chart( - config: ChartConfig, - chart: PsychroChartModel, - zones: ChartZones | dict[str, Any] | str | None = None, -) -> None: - """Append zones as patches to the psychrometric chart data-container.""" - zones_use = obj_loader(ChartZones, zones, default_obj=DEFAULT_ZONES).zones - if zones_use: - curves = PsychroCurves( - curves=[ - make_zone_curve(zone, config.limits.step_temp, chart.pressure) - for zone in zones_use - ] - ) - chart.zones.append(curves) - - -def _generate_chart_curves( - config: ChartConfig, chart: PsychroChartModel, pressure: float -): +def _generate_chart_curves(config: ChartConfig, chart: PsychroChartModel): # check chart limits are not fully above the saturation curve! - assert (chart.saturation.curves[0].y_data > config.w_min).any() + assert (chart.saturation.y_data > config.w_min).any() # check if sat curve cuts x-axis with T > config.dbt_min dbt_min_seen: float | None = None - if chart.saturation.curves[0].y_data[0] < config.w_min: + if chart.saturation.y_data[0] < config.w_min: temp_sat_interpolator = interp1d( - chart.saturation.curves[0].y_data, - chart.saturation.curves[0].x_data, + chart.saturation.y_data, + chart.saturation.x_data, assume_sorted=True, ) dbt_min_seen = temp_sat_interpolator(config.w_min) @@ -90,7 +68,7 @@ def _generate_chart_curves( temps_vl = temps_vl[temps_vl > dbt_min_seen] chart.constant_dry_temp_data = make_constant_dry_bulb_v_lines( config.w_min, - pressure, + chart.pressure, temps_vl=temps_vl, style=config.constant_dry_temp, family_label=config.chart_params.constant_temp_label, @@ -103,7 +81,7 @@ def _generate_chart_curves( step = config.chart_params.constant_humid_step chart.constant_humidity_data = make_constant_humidity_ratio_h_lines( config.dbt_max, - pressure, + chart.pressure, ws_hl=np.arange( config.w_min + step, config.w_max + step / 10, @@ -122,7 +100,7 @@ def _generate_chart_curves( config.dbt_max, config.w_min, config.w_max, - pressure, + chart.pressure, ) rh_values = sorted( rh @@ -138,7 +116,7 @@ def _generate_chart_curves( start, config.dbt_max, config.limits.step_temp, - pressure, + chart.pressure, rh_perc_values=rh_values, rh_label_values=config.chart_params.constant_rh_labels, style=config.constant_rh, @@ -154,13 +132,13 @@ def _generate_chart_curves( start, end = config.chart_params.range_h chart.constant_h_data = make_constant_enthalpy_lines( config.w_min, - pressure, + chart.pressure, enthalpy_values=np.arange(start, end, step), h_label_values=config.chart_params.constant_h_labels, style=config.constant_h, label_loc=config.chart_params.constant_h_labels_loc, family_label=config.chart_params.constant_h_label, - saturation_curve=chart.saturation.curves[0], + saturation_curve=chart.saturation, dbt_min_seen=dbt_min_seen, ) else: @@ -172,13 +150,13 @@ def _generate_chart_curves( start, end = config.chart_params.range_vol_m3_kg chart.constant_v_data = make_constant_specific_volume_lines( config.w_min, - pressure, + chart.pressure, vol_values=np.arange(start, end, step), v_label_values=config.chart_params.constant_v_labels, style=config.constant_v, label_loc=config.chart_params.constant_v_labels_loc, family_label=config.chart_params.constant_v_label, - saturation_curve=chart.saturation.curves[0], + saturation_curve=chart.saturation, dbt_min_seen=dbt_min_seen, ) else: @@ -193,7 +171,7 @@ def _generate_chart_curves( config.dbt_max, config.w_min, config.w_max, - pressure, + chart.pressure, wbt_values=np.arange(start, end, step), wbt_label_values=config.chart_params.constant_wet_temp_labels, style=config.constant_wet_temp, @@ -204,36 +182,6 @@ def _generate_chart_curves( chart.constant_wbt_data = None -def generate_psychrochart( - config: ChartConfig, - extra_zones: ChartZones | dict[str, Any] | str | None = None, - use_unit_system_si: bool = True, -) -> PsychroChartModel: - """Create the PsychroChart object.""" - # Set unit system for psychrolib and get standard pressure - pressure = get_pressure_pa(config.limits, use_unit_system_si) - - # base chart with saturation line: - chart = PsychroChartModel( - unit_system_si=use_unit_system_si, - altitude_m=config.limits.altitude_m, - pressure=pressure, - saturation=make_saturation_line( - config.dbt_min, - config.dbt_max, - config.limits.step_temp, - pressure, - style=config.saturation, - ), - ) - _generate_chart_curves(config, chart, pressure) - - if config.chart_params.with_zones: - append_zones_to_chart(config, chart, extra_zones) - - return chart - - def update_psychrochart_data( current_chart: PsychroChartModel, config: ChartConfig ) -> None: @@ -252,5 +200,21 @@ def update_psychrochart_data( current_chart.pressure, style=config.saturation, ) - _generate_chart_curves(config, current_chart, current_chart.pressure) + _generate_chart_curves(config, current_chart) + # regen all zones + if config.chart_params.with_zones and not config.chart_params.zones: + # add default zones + config.chart_params.zones = DEFAULT_ZONES.zones + zone_curves = [ + make_zone_curve( + zone, + pressure=current_chart.pressure, + step_temp=config.limits.step_temp, + # dbt_min=config.dbt_min, + # dbt_max=config.dbt_max, + # w_min=config.w_min, + ) + for zone in config.chart_params.zones + ] + current_chart.zones = [zc for zc in zone_curves if zc is not None] config.commit_changes() diff --git a/pyproject.toml b/pyproject.toml index 2aaf10c..d34d0bc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -42,7 +42,7 @@ log_date_format = "%Y-%m-%d %H:%M:%S" [tool.poetry] name = "psychrochart" -version = "0.7.0" +version = "0.8.0" description = "A python 3 library to make psychrometric charts and overlay information on them" authors = ["Eugenio Panadero "] packages = [ diff --git a/tests/example-charts/chart_overlay_style_minimal.svg b/tests/example-charts/chart_overlay_style_minimal.svg index a32d9b5..bd032ca 100644 --- a/tests/example-charts/chart_overlay_style_minimal.svg +++ b/tests/example-charts/chart_overlay_style_minimal.svg @@ -18,9 +18,9 @@ - - - + + + - + - + - + @@ -191,7 +191,7 @@ z - + @@ -549,8 +549,8 @@ z - - + + @@ -801,37 +801,37 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1527,7 +1527,7 @@ z - + @@ -1541,7 +1541,7 @@ L 5 5 - + @@ -1561,17 +1561,17 @@ z - + - + - + @@ -1639,7 +1639,7 @@ z - + @@ -1649,8 +1649,8 @@ z - - + + - - + + - + @@ -1852,7 +1852,7 @@ z - + @@ -1907,8 +1907,8 @@ z - - + + - + @@ -1950,7 +1950,7 @@ z - + - + @@ -2041,7 +2041,7 @@ z - + - + @@ -2141,7 +2141,7 @@ z - + - + @@ -2189,7 +2189,7 @@ z - + - + @@ -2258,13 +2258,13 @@ z - + - + @@ -2326,7 +2326,7 @@ z - + - + @@ -2364,7 +2364,7 @@ z - + - + @@ -2412,7 +2412,7 @@ L 3.5 3.5 - + - + diff --git a/tests/example-charts/test_ashrae_psychrochart.svg b/tests/example-charts/test_ashrae_psychrochart.svg index f1913a2..2606803 100644 --- a/tests/example-charts/test_ashrae_psychrochart.svg +++ b/tests/example-charts/test_ashrae_psychrochart.svg @@ -18,9 +18,9 @@ - - - + + + - + @@ -333,7 +333,7 @@ z - + @@ -626,7 +626,7 @@ z - + @@ -638,7 +638,7 @@ L 3.5 0 - + @@ -651,7 +651,7 @@ L 3.5 0 - + @@ -664,7 +664,7 @@ L 3.5 0 - + @@ -677,7 +677,7 @@ L 3.5 0 - + @@ -722,7 +722,7 @@ z - + @@ -776,7 +776,7 @@ z - + @@ -790,7 +790,7 @@ z - + @@ -804,7 +804,7 @@ z - + @@ -818,7 +818,7 @@ z - + @@ -832,7 +832,7 @@ z - + @@ -846,7 +846,7 @@ z - + @@ -860,7 +860,7 @@ z - + @@ -874,7 +874,7 @@ z - + @@ -888,7 +888,7 @@ z - + @@ -902,7 +902,7 @@ z - + @@ -916,7 +916,7 @@ z - + @@ -924,7 +924,7 @@ z - + @@ -1143,257 +1143,257 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1408,478 +1408,478 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -2625,7 +2625,7 @@ z - + @@ -2645,7 +2645,7 @@ z - + @@ -2742,7 +2742,7 @@ z - + @@ -2794,7 +2794,7 @@ z - + @@ -2855,7 +2855,7 @@ z - + @@ -2867,7 +2867,7 @@ z - + @@ -2879,7 +2879,7 @@ z - + @@ -2891,7 +2891,7 @@ z - + @@ -2923,7 +2923,7 @@ z - + @@ -2932,7 +2932,7 @@ z - + @@ -2942,7 +2942,7 @@ z - + @@ -2952,7 +2952,7 @@ z - + @@ -2962,7 +2962,7 @@ z - + @@ -2972,7 +2972,7 @@ z - + @@ -2982,7 +2982,7 @@ z - + diff --git a/tests/example-charts/test_ashrae_psychrochart_ip.svg b/tests/example-charts/test_ashrae_psychrochart_ip.svg index 41f66f8..300eff9 100644 --- a/tests/example-charts/test_ashrae_psychrochart_ip.svg +++ b/tests/example-charts/test_ashrae_psychrochart_ip.svg @@ -18,9 +18,9 @@ - - - + + + - + @@ -715,7 +715,7 @@ z - + @@ -989,7 +989,7 @@ z - + @@ -1001,7 +1001,7 @@ L 3.5 0 - + @@ -1014,7 +1014,7 @@ L 3.5 0 - + @@ -1028,7 +1028,7 @@ L 3.5 0 - + @@ -1042,7 +1042,7 @@ L 3.5 0 - + @@ -1056,7 +1056,7 @@ L 3.5 0 - + @@ -1070,7 +1070,7 @@ L 3.5 0 - + @@ -1084,7 +1084,7 @@ L 3.5 0 - + @@ -1098,7 +1098,7 @@ L 3.5 0 - + @@ -1112,7 +1112,7 @@ L 3.5 0 - + @@ -1126,7 +1126,7 @@ L 3.5 0 - + @@ -1140,7 +1140,7 @@ L 3.5 0 - + @@ -1155,7 +1155,7 @@ L 3.5 0 - + @@ -1170,7 +1170,7 @@ L 3.5 0 - + @@ -1185,7 +1185,7 @@ L 3.5 0 - + @@ -1200,7 +1200,7 @@ L 3.5 0 - + @@ -1215,7 +1215,7 @@ L 3.5 0 - + @@ -1230,7 +1230,7 @@ L 3.5 0 - + @@ -1245,7 +1245,7 @@ L 3.5 0 - + @@ -1260,7 +1260,7 @@ L 3.5 0 - + @@ -1275,7 +1275,7 @@ L 3.5 0 - + @@ -1290,7 +1290,7 @@ L 3.5 0 - + @@ -1305,7 +1305,7 @@ L 3.5 0 - + @@ -1314,7 +1314,7 @@ L 3.5 0 - + @@ -1642,147 +1642,147 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1797,643 +1797,643 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -3148,7 +3148,7 @@ z - + @@ -3168,7 +3168,7 @@ z - + @@ -3188,7 +3188,7 @@ z - + @@ -3208,7 +3208,7 @@ z - + @@ -3292,7 +3292,7 @@ z - + @@ -3312,7 +3312,7 @@ z - + @@ -3334,7 +3334,7 @@ z - + @@ -3354,7 +3354,7 @@ z - + @@ -3376,7 +3376,7 @@ z - + @@ -3437,7 +3437,7 @@ z - + @@ -3449,7 +3449,7 @@ z - + @@ -3461,7 +3461,7 @@ z - + @@ -3473,7 +3473,7 @@ z - + @@ -3498,7 +3498,7 @@ z - + @@ -3508,7 +3508,7 @@ z - + @@ -3518,7 +3518,7 @@ z - + @@ -3528,7 +3528,7 @@ z - + @@ -3538,7 +3538,7 @@ z - + @@ -3548,7 +3548,7 @@ z - + @@ -3558,7 +3558,7 @@ z - + @@ -3568,7 +3568,7 @@ z - + @@ -3578,7 +3578,7 @@ z - + @@ -3588,7 +3588,7 @@ z - + @@ -3598,7 +3598,7 @@ z - + @@ -3608,7 +3608,7 @@ z - + @@ -3618,7 +3618,7 @@ z - + @@ -3628,7 +3628,7 @@ z - + diff --git a/tests/example-charts/test_default_psychrochart.svg b/tests/example-charts/test_default_psychrochart.svg index 9b3f951..417ccd9 100644 --- a/tests/example-charts/test_default_psychrochart.svg +++ b/tests/example-charts/test_default_psychrochart.svg @@ -18,9 +18,9 @@ - - - + + + - + - + - + @@ -353,7 +353,7 @@ z - + @@ -693,7 +693,7 @@ z - + @@ -705,7 +705,7 @@ L 3.5 0 - + @@ -718,7 +718,7 @@ L 3.5 0 - + @@ -731,7 +731,7 @@ L 3.5 0 - + @@ -744,7 +744,7 @@ L 3.5 0 - + @@ -789,7 +789,7 @@ z - + @@ -843,7 +843,7 @@ z - + @@ -857,7 +857,7 @@ z - + @@ -871,7 +871,7 @@ z - + @@ -885,7 +885,7 @@ z - + @@ -899,7 +899,7 @@ z - + @@ -913,7 +913,7 @@ z - + @@ -927,7 +927,7 @@ z - + @@ -941,7 +941,7 @@ z - + @@ -955,7 +955,7 @@ z - + @@ -969,7 +969,7 @@ z - + @@ -983,7 +983,7 @@ z - + @@ -997,7 +997,7 @@ z - + @@ -1011,7 +1011,7 @@ z - + @@ -1025,7 +1025,7 @@ z - + @@ -1039,7 +1039,7 @@ z - + @@ -1053,7 +1053,7 @@ z - + @@ -1061,7 +1061,7 @@ z - + @@ -1312,257 +1312,257 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1577,207 +1577,207 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1792,157 +1792,157 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1957,57 +1957,57 @@ z - + - + - + - + - + - + - + - + - + - + - + @@ -2022,7 +2022,7 @@ z - + - + - + - + - + - + - + - + - + - + - + - + @@ -2605,52 +2605,52 @@ z - + - + - + - + - + - + - + - + - + - + @@ -2665,7 +2665,7 @@ z - + - + - + - + @@ -2750,7 +2750,7 @@ z - + @@ -2769,7 +2769,7 @@ z - + @@ -2788,7 +2788,7 @@ z - + @@ -2819,7 +2819,7 @@ z - + @@ -2839,7 +2839,7 @@ z - + @@ -2859,7 +2859,7 @@ z - + @@ -2956,7 +2956,7 @@ z - + @@ -3008,7 +3008,7 @@ z - + @@ -3029,7 +3029,7 @@ z - + @@ -3118,7 +3118,7 @@ z - + @@ -3130,7 +3130,7 @@ z - + @@ -3142,7 +3142,7 @@ z - + @@ -3154,7 +3154,7 @@ z - + @@ -3166,7 +3166,7 @@ z - + @@ -3178,7 +3178,7 @@ z - + @@ -3210,7 +3210,7 @@ z - + @@ -3219,7 +3219,7 @@ z - + @@ -3229,7 +3229,7 @@ z - + @@ -3239,7 +3239,7 @@ z - + @@ -3249,7 +3249,7 @@ z - + @@ -3259,7 +3259,7 @@ z - + @@ -3269,7 +3269,7 @@ z - + @@ -3279,8 +3279,8 @@ z - - + + - - + + - + diff --git a/tests/example-charts/test_minimal_psychrochart.svg b/tests/example-charts/test_minimal_psychrochart.svg index 1e48161..ba3b882 100644 --- a/tests/example-charts/test_minimal_psychrochart.svg +++ b/tests/example-charts/test_minimal_psychrochart.svg @@ -18,9 +18,9 @@ - - - + + + - + @@ -99,7 +99,7 @@ z - + @@ -457,8 +457,8 @@ z - - + + @@ -709,37 +709,37 @@ z - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -1460,7 +1460,7 @@ z - + @@ -1470,8 +1470,8 @@ z - - + + - + - + @@ -1526,7 +1526,7 @@ z - + - + @@ -1636,7 +1636,7 @@ z - + - + @@ -1736,7 +1736,7 @@ z - + - + @@ -1784,7 +1784,7 @@ z - + - + diff --git a/tests/test_artist_registry.py b/tests/test_artist_registry.py new file mode 100644 index 0000000..55a10eb --- /dev/null +++ b/tests/test_artist_registry.py @@ -0,0 +1,47 @@ +import matplotlib.pyplot as plt + +from psychrochart import PsychroChart +from psychrochart.chart_entities import ChartRegistry +from psychrochart.plot_logic import apply_axis_styling, plot_chart + + +def test_registry_fill_on_plot(): + chart = PsychroChart.create() + assert chart.config.has_changed + assert not chart.artists.saturation + assert not chart.artists.constant_humidity + assert not chart.artists.zones + assert not chart.artists.layout + assert not chart.constant_rh_data + chart.process_chart() + assert not chart.config.has_changed + assert chart.constant_rh_data is not None + assert not chart.artists.constant_humidity + + # `plot_chart` functional method + ax = plt.subplot(position=chart.config.figure.position) + artists_layout = apply_axis_styling(chart.config, ax) + new_registry = plot_chart(chart, ax) + assert isinstance(new_registry, ChartRegistry) + assert new_registry.saturation + assert new_registry.constant_humidity + assert new_registry.zones + assert not new_registry.layout + assert not new_registry.annotations + + # chart registry unchanged! + assert not chart.artists.saturation + assert not chart.artists.constant_humidity + assert not chart.artists.zones + assert not chart.artists.layout + + # with `.plot` method of chart, registry is updated + chart.plot() + assert chart.artists.saturation.keys() == new_registry.saturation.keys() + assert ( + chart.artists.constant_humidity.keys() + == new_registry.constant_humidity.keys() + ) + assert chart.artists.zones.keys() == new_registry.zones.keys() + assert not chart.artists.annotations + assert all(k in chart.artists.layout.keys() for k in artists_layout.keys()) diff --git a/tests/test_axes_limits.py b/tests/test_axes_limits.py index 6875813..efb3507 100644 --- a/tests/test_axes_limits.py +++ b/tests/test_axes_limits.py @@ -1,7 +1,7 @@ import logging from psychrochart import PsychroChart -from psychrochart.models.annots import DEFAULT_ZONES +from psychrochart.models.config import DEFAULT_ZONES from psychrochart.process_logic import set_unit_system from tests.conftest import store_test_chart diff --git a/tests/test_config_mutation.py b/tests/test_config_mutation.py index 1d99a09..e8b0d76 100644 --- a/tests/test_config_mutation.py +++ b/tests/test_config_mutation.py @@ -17,10 +17,10 @@ def test_default_config(): assert not config_json.has_changed config_style_none = PsychroChart.create().config - assert not config_style_none.has_changed + assert config_style_none.has_changed config_style = PsychroChart.create("default").config - assert not config_style.has_changed + assert config_style.has_changed assert config_default == config_style assert config_style_none == config_style diff --git a/tests/test_curves_models.py b/tests/test_curves_models.py index 0dda111..856bf55 100644 --- a/tests/test_curves_models.py +++ b/tests/test_curves_models.py @@ -97,6 +97,7 @@ def test_string_representation_for_psychrochart_objs(): """Check the string representation of objects.""" obj_repr = "50 °C, 0->40 gr/kg_da]>" data_chart = PsychroChart.create() + data_chart.process_chart() assert repr(data_chart) == obj_repr assert ( repr(data_chart.constant_rh_data) @@ -118,5 +119,4 @@ def test_string_representation_for_psychrochart_objs(): repr(data_chart.constant_dry_temp_data) == "<50 PsychroCurves (label: Dry bulb temperature)>" ) - assert repr(data_chart.saturation) == "<1 PsychroCurves>" - assert repr(data_chart.saturation.curves[0]) == "" + assert repr(data_chart.saturation) == "" diff --git a/tests/test_models.py b/tests/test_models.py index 9b81f68..ed4c3c9 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -5,14 +5,13 @@ from pydantic import parse_obj_as import pytest -from psychrochart.models.annots import ( - ChartAnnots, - ChartArea, - ChartPoint, +from psychrochart.models.annots import ChartAnnots, ChartArea, ChartPoint +from psychrochart.models.config import ( + ChartConfig, + ChartZone, ChartZones, DEFAULT_ZONES, ) -from psychrochart.models.config import ChartConfig from psychrochart.models.parsers import ( DEFAULT_CHART_CONFIG_FILE, load_extra_annots, @@ -135,6 +134,47 @@ def test_style_parsing(): assert not hasattr(style, "c") +def test_chart_zone_definition(): + style = { + "style": { + "edgecolor": "red", + "facecolor": "blue", + "linewidth": 1, + "linestyle": ":", + } + } + + z1 = { + "points_x": [23.0, 25.0, 24.0], + "points_y": [45.0, 60.0, 25.0], + **style, + } + zone1 = ChartZone.validate(z1) + assert zone1.zone_type == "xy-points" + assert zone1.label is None + + # invalid number of points + with pytest.raises(ValueError): + ChartZone.validate({"points_x": [], "points_y": [], **style}) + with pytest.raises(ValueError): + ChartZone.validate({"points_x": [25.0], "points_y": [25.0], **style}) + with pytest.raises(ValueError): + ChartZone.validate( + {"points_x": [25.0, 25.0], "points_y": [25.0, 25.0, 25.0], **style} + ) + + # dbt-rh zones have 2 points: (dbt-min, RH-min), (dbt-max, RH-max) + z1["zone_type"] = "dbt-rh" + with pytest.raises(ValueError): + ChartZone.validate(z1) + + z1["points_x"] = [23.0, 25.0] + z1["points_y"] = [45.0, 60.0] + zone2 = ChartZone.validate(z1) + assert zone2.zone_type == "dbt-rh" + assert zone2.label is None + + def test_chart_area_schema(): raw_areas_as_dict = [ # Zone 1: diff --git a/tests/test_overlay_info.py b/tests/test_overlay_info.py index 47cf4bd..7624102 100644 --- a/tests/test_overlay_info.py +++ b/tests/test_overlay_info.py @@ -3,7 +3,7 @@ import pytest from psychrochart import PsychroChart -from psychrochart.models.annots import ChartZone +from psychrochart.models.config import ChartZone from psychrochart.models.parsers import load_config from tests.conftest import store_test_chart, TEST_BASEDIR diff --git a/tests/test_reuse_chart.py b/tests/test_reuse_chart.py index d4be4dd..662dd1f 100644 --- a/tests/test_reuse_chart.py +++ b/tests/test_reuse_chart.py @@ -1,40 +1,39 @@ from psychrochart import PsychroChart from tests.conftest import TEST_BASEDIR, timeit +_TEST_ZONES_DEFINITION = { + "zones": [ + { + "zone_type": "dbt-rh", + "style": { + "edgecolor": [1.0, 0.749, 0.0, 0.8], + "facecolor": [1.0, 0.749, 0.0, 0.2], + "linewidth": 2, + "linestyle": "--", + }, + "points_x": [23, 28], + "points_y": [40, 60], + "label": "Summer", + }, + { + "zone_type": "dbt-rh", + "style": { + "edgecolor": [0.498, 0.624, 0.8], + "facecolor": [0.498, 0.624, 1.0, 0.2], + "linewidth": 2, + "linestyle": "--", + }, + "points_x": [18, 23], + "points_y": [35, 55], + "label": "Winter", + }, + ] +} + @timeit("make_chart") def _make_chart(path_save=None): - chart = PsychroChart.create("minimal") - # Zones: - zones_conf = { - "zones": [ - { - "zone_type": "dbt-rh", - "style": { - "edgecolor": [1.0, 0.749, 0.0, 0.8], - "facecolor": [1.0, 0.749, 0.0, 0.2], - "linewidth": 2, - "linestyle": "--", - }, - "points_x": [23, 28], - "points_y": [40, 60], - "label": "Summer", - }, - { - "zone_type": "dbt-rh", - "style": { - "edgecolor": [0.498, 0.624, 0.8], - "facecolor": [0.498, 0.624, 1.0, 0.2], - "linewidth": 2, - "linestyle": "--", - }, - "points_x": [18, 23], - "points_y": [35, 55], - "label": "Winter", - }, - ] - } - chart.append_zones(zones_conf) + chart = PsychroChart.create("minimal", extra_zones=_TEST_ZONES_DEFINITION) # Plotting chart.plot() # Vertical lines diff --git a/tests/test_utils.py b/tests/test_utils.py index d368cd3..5b72c33 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,7 +1,9 @@ """Tests utilities.""" import json +import logging from psychrochart.__main__ import main +from psychrochart.chart_entities import make_item_gid from psychrochart.models.parsers import DEFAULT_CHART_CONFIG_FILE, load_config from psychrochart.util import mod_color from tests.conftest import TEST_BASEDIR @@ -61,3 +63,22 @@ def test_color_palette(): color_alpha_08 = mod_color(color_base, 0.8) assert _to_8bit_color(color_alpha_08) == (121, 156, 19, 0.8) + + +def test_gid_generator(caplog): + with caplog.at_level(logging.WARNING): + assert make_item_gid("kind", name="name") == make_item_gid( + "kind", name="name" + ) + assert len(caplog.messages) == 0 + # unnamed items have random suffixes (and emit logging warnings) + assert make_item_gid("kind", "family_label") != make_item_gid( + "kind", "family_label" + ) + assert len(caplog.messages) == 2 + caplog.clear() + + # !note: even different names can generate same gid + gid1 = make_item_gid("kind", name=f"{-5.1}") + gid2 = make_item_gid("kind", name=f"{5.1}") + assert gid1 == gid2