Frog 2
A frog sitting in a pond and croaking
Personal Blog about anything - mostly programming, cooking and random thoughts
A frog sitting in a pond and croaking
I'm currently rebuilding the design of my blog and want to use a complementary color palette. As I'm not yet sure which colors I will use it would be ideal to automatically derive the secondary color from the primary color.
In the future this can be done using the relative color feature coming to CSS, but this isn't yet widely supported.
--primary: hsl(170, 66%, 28%);
--secondary: hsl(from var(--primary) calc(h + 180) s l);
I figured out that you can achieve the same effect using the color-mix. This is already supported by all major browsers.
--primary: hsl(170, 66%, 28%);
--secondary: color-mix(in hsl longer hue, var(--primary), var(--primary) 50%);
The command mixes the primary color with itself in the HSL color space. Additionally it is specified that it should use the longer pass along the hue axis. As this will always be the 360° path, a mix of 50% will end up at the 180° complementary color to the primary color. The saturation and lightness will stay the same.
This allows for fast testing of colors using the developer console:
This can also be used to create other color palettes by changing the percentage accordingly.
For visualizing the heating and cooling degree days of Germany I had to learn how to plot maps.
My data source was a bunch of CSV files, which I combined into a single DataFrame, including latitude and longitude columns. I've used geopandas to plot this data for a first visualization. The DataFrame can be turned into a GeoDataFrame to create geometry information from the longitude and latitude columns. It's important to specify the right coordinate reference system (CRS) of your data.
With this conversion the data can be plotted using the plot
function of geopandas
df = load_my_dataframe(...)
gdf = gpd.GeoDataFrame(
df, geometry=gpd.points_from_xy(df["Longitude"], df["Latitude"]), crs="EPSG:4326"
)
ax = gdf.plot(
column="ValueToVisualize",
)
The shape of Germany is already recognizable, but some more context would be beneficial. For this reason I've added the borders of the German states to the plot. The shape files of all European countries can be downloaded from eurostat in various formats. I've used the "Polygon (RG)" version and the .shp format. The files contain the borders on different levels (country, state and regional borders). With a few lines the borders can be added to the plot.
border_file = "NUTS_RG_01M_2021_3035.shp/NUTS_RG_01M_2021_3035.shp"
eu_gdf = gpd.read_file(border_file)
eu_gdf.crs = "EPSG:3035"
gdf_de = eu_gdf[(eu_gdf.CNTR_CODE == "DE") & (eu_gdf.LEVL_CODE == 1)]
gdf_de.to_crs("EPSG:4326").boundary.plot(ax=ax, color="black")
Using points to visualize this data is not the best approach. Instead I want to fill the entire map where each pixel is colored according to the nearest data point. This can be achieved by computing a Voronoi Diagram, where each data point is turned into a face. I've tried to use the geoplot library to compute the voronoi diagram, but it got stuck on my data. Luckily scipy also has a voronoi implementation, but it's a bit more work to use.
# create a box around all data
min_lat = df["Latitude"].min() - 1
max_lat = df["Latitude"].max() + 1
min_lon = df["Longitude"].min() - 1
max_lon = df["Longitude"].max() + 1
boundarycoords = np.array(
[
[min_lat, min_lon],
[min_lat, max_lon],
[max_lat, min_lon],
[max_lat, max_lon],
]
)
# convert our data point coordinates to a numpy array
coords = df[["Latitude", "Longitude"]].to_numpy()
all_coords = np.concatenate((coords, boundarycoords))
# compute voronoi
vor = scipy.spatial.Voronoi(points=all_coords)
# construct geometry from voronoi
polygons = [
shapely.geometry.Polygon(vor.vertices[vor.regions[line]])
for line in vor.point_region
if -1 not in vor.regions[line]
]
voronois = gpd.GeoDataFrame(geometry=gpd.GeoSeries(polygons), crs="EPSG:4326")
# create dataframe
gdf = gpd.GeoDataFrame(
df.reset_index(),
geometry=voronois.geometry,
crs="EPSG:4326",
)
# plot borders
gdf_de.to_crs("EPSG:4326").boundary.plot(ax=ax, color="black")
# clip geometry to inside of map
clip = eu_gdf[(eu_gdf.CNTR_CODE == "DE") & (eu_gdf.LEVL_CODE == 0)]
gdf = gdf.clip(clip.to_crs("EPSG:4326"))
# plot data
gdf.plot(
ax=ax,
column="Monatsgradtage",
cmap="Blues",
legend=True,
)
The plot is almost done, I only want to clean it up a bit. As the axis don't serve any purpose here, I removed them. Additionally I've added a title and a small text to the bottom to include the source in the graphic.
ax.set_title(f"Heizgradtage 2020", fontsize=20)
ax.axis("off")
ax.text(
0.01,
0.01,
"Quelle: https://www.dwd.de/DE/leistungen/gtz_kostenfrei/gtz_kostenfrei.html\nVisualisierung: Niko Abeler CC-BY-SA 4.0",
ha="left",
va="top",
transform=ax.transAxes,
alpha=0.5,
fontsize=8,
)
Heizgradtage werden im Energiemanagement benutzt. Um den Heizbedarf über mehrere Jahre zu vergleichen muss der Effekt des Wetters herausgerechnet werden. Ansonsten vergleicht man jediglich das Wetter, da in kalten Wintern mehr geheizt werden muss. Dazu zählt man die Grad Celsius unter einem Schwellwert (meist 15°C in Deutschland).
Als Beispiel: Ein Tag mit konstanter Temperatur von 5°C ergibt 10 Heizgradtage ((15°C-5°C) * 1 Tag
). Ein Monat mit 10°C ergibt 300 Heizgradtage ((15°C-5°C) * 30 Tage
)
Die Visualisierung zeigt die Heizgradtage der Jahre 2010 - 2023 in Deutschland. Je kälter (mehr Heizgradtage) es in einer Region ist, desto dunkler wird diese dargestellt. Die Visualisierung basiert auf den Daten des DWD.
Parallel zu den Heizgradtagen gibt es auch die Kühlgradstunden. Hier werden die Stunden über einem Schwellwert (hier 18°C) gezählt. Eine Stunde mit 30°C ergibt somit 12 Kühlgradstunden. In dieser Visualisierung werden heiße Regionen in dunklerot dargestellt.