From fed35fcfd2dd7fb29c628d57502e18a6c62ee141 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 16:28:40 +0200 Subject: [PATCH 01/10] Add metadata sheet to data directory --- data/metasheet.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 data/metasheet.md diff --git a/data/metasheet.md b/data/metasheet.md new file mode 100644 index 0000000..723a346 --- /dev/null +++ b/data/metasheet.md @@ -0,0 +1,40 @@ +# Nuclear Explosions + +This week's [**data**](nuclear_explosions.csv) is from [Stockholm International Peace Research Institute](https://github.com/data-is-plural/nuclear-explosions/blob/master/documents/sipri-report-original.pdf), by way of [data is plural](https://github.com/data-is-plural/nuclear-explosions) with credit to [Jesus Castagnetto](https://github.com/rfordatascience/tidytuesday/issues/91) for sharing the dataset. + +Additional information can be found on [Wikipedia](https://en.wikipedia.org/wiki/List_of_nuclear_weapons_tests) or via the original report [PDF](https://github.com/data-is-plural/nuclear-explosions/blob/master/documents/sipri-report-original.pdf). + +Additional related datasets can be found at [Our World in Data](https://ourworldindata.org/nuclear-weapons). + +For details around units for yield/magnitude, please see the [Nuclear Yield](https://seismo.berkeley.edu/~rallen/research/nuke/yield.html) formulas. + + +# Get the data! + +``` +nuclear_explosions <- readr::read_csv("https://raw.githubusercontent.com/rfordatascience/tidytuesday/master/data/2019/2019-08-20/nuclear_explosions.csv") + +``` + +# Data Dictionary + +## `nuclear_explosions.csv` + +|variable |class |description | +|:--- |:--- |:-----------| +|date_long |date | ymd date| +|year |double | year of explosion | +|id_no |double | unique ID | +|country |character | Country deploying the nuclear device | +|region |character | Region where nuclear device was deployed | +|source |character | Source the reported the explosion event | +|latitude |double | Latitude position | +|longitude |double | Longitude position | +|magnitude_body |double | Body wave magnitude of explosion (mb)| +|magnitude_surface |double | Surface wave magnitude of explosion (Ms) | +|depth |double | Depth at detonation in Km (could be underground or above ground) -- please note that positive = depth (below ground), while negative = height (above ground) | +|yield_lower |double | Explosion yield lower estimate in kilotons of TNT | +|yield_upper |double | Explosion yield upper estimate in kilotons of TNT | +|purpose |character | Purpose of detonation: COMBAT (WWII bombs dropped over Japan), FMS (Soviet test, study phenomenon of nuclear explosion), ME (Military Exercise), PNE (Peaceful nuclear explosion), SAM (Soviet test, accidental mode/emergency), SSE (French/US tests - testing safety of nuclear weapons in case of accident), TRANSP (Transportation-storage purposes), WE (British, French, US, evaluate effects of nuclear detonation on various targets), WR (Weapons development program) | +|name |character | Name of event or bomb | +|type |character | type - method of deployment -- ATMOSPH (Atmospheric), UG (underground), BALLOON (Balloon drop), AIRDROP (Airplane deployed), ROCKET (Rocket deployed), TOWER (deplyed at top of constructed tower), WATERSURFACE (on surface of body of water), BARGE (on barge boat), SURFACE (on surface or in shallow crater), UW (Underwater), SHAFT (Vertical Shaft underground), TUNNEL/GALLERY (Horizontal tunnel) | From 7d1d929b0e48633684c056dc000b51634ee7c525 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 16:29:23 +0200 Subject: [PATCH 02/10] Update article and rename to index.qmd --- nuclear_explosions.qmd => index.qmd | 221 ++++++++++++++++++---------- 1 file changed, 145 insertions(+), 76 deletions(-) rename nuclear_explosions.qmd => index.qmd (63%) diff --git a/nuclear_explosions.qmd b/index.qmd similarity index 63% rename from nuclear_explosions.qmd rename to index.qmd index 559fb9a..c143cd7 100644 --- a/nuclear_explosions.qmd +++ b/index.qmd @@ -1,7 +1,5 @@ --- title: Nuclear Explosions -author: Marty Oehme -output-dir: out references: - type: techreport id: Bergkvist2000 @@ -18,16 +16,6 @@ references: title: "Nuclear Explosions 1945 - 1998" page: 1-42 issn: 1104-9154 -format: - html: - toc: true - code-fold: true - typst: - toc: true - echo: false - docx: - toc: true - echo: false --- ```{python} @@ -43,7 +31,21 @@ from matplotlib import pyplot as plt sns.set_theme(style="darkgrid") sns.set_context("notebook") +cp=sns.color_palette() +country_colors = { + "US": cp[0], + "USSR": cp[3], + "France": cp[6], + "UK": cp[5], + "China": cp[4], + "India": cp[1], + "Pakistan": cp[2], +} +``` +```{python} +# | label: data-prep +# | echo: false schema_overrides = ( { col: pl.Categorical @@ -53,13 +55,31 @@ schema_overrides = ( | {col: pl.String for col in ["year", "name"]} ) +cty_alias = { + "PAKIST": "Pakistan", + "FRANCE": "France", + "CHINA": "China", + "INDIA": "India", + "USA": "US", +} + + +def cty_replace(name: str) -> str: + if name in cty_alias: + return cty_alias[name] + return name + + df = ( pl.read_csv( "data/nuclear_explosions.csv", schema_overrides=schema_overrides, null_values=["NA"], ) - .with_columns(date=pl.col("year").str.strptime(pl.Date, "%Y")) + .with_columns( + date=pl.col("year").str.strptime(pl.Date, "%Y"), + country=pl.col("country").map_elements(cty_replace, return_dtype=pl.String), + ) .with_columns(year=pl.col("date").dt.year().cast(pl.Int32)) ) ``` @@ -69,7 +89,7 @@ df = ( The following is a re-creation and expansion of some of the graphs found in the @Bergkvist2000 produced report on nuclear explosions between 1945 and 1998. It is primarily a reproduction of key plots from the original report. -Additionally, it serves as a exercise in plotting with the python library +Additionally, it serves as an exercise in plotting with the python library seaborn and the underlying matplotlib. Lastly, it approaches some less well tread territory for data science in the python universe as it uses the python library polars-rs for data loading and transformation. All the code used to @@ -77,9 +97,9 @@ transform the data and create the plots is available directly within the full text document, and separately as well. PDF and Docx formats are available with the plotting results only. -Their original purpose was the collection of a long list of all the nuclear -explosions occurring between those years, as well as analysing the responsible -nations, tracking the types and purposes of the explosions, as well as +The authors' original purpose was the collection of a long list of all the +nuclear explosions occurring between those years, as well as analysing the +responsible nations, tracking the types and purposes of the explosions and connecting the rise and fall of nuclear explosion numbers to historical events throughout. @@ -90,13 +110,13 @@ throughout. ## Nuclear devices There are two main kinds of nuclear device: those based entirely, on fission, -or the splitting of heavy atomic nucleii (previously known as atomic devices) +or the splitting of heavy atomic nuclei (previously known as atomic devices) and those in which the main energy is obtained by means of fusion, or of -light -atomic nucleii (hydrogen or thermonuclear devices). A fusion explosion must +atomic nuclei (hydrogen or thermonuclear devices). A fusion explosion must however be initiated with the help of a fission device. The strength of a fusion explosion can be practically unlimited. The explosive power of a -nuclear explosion is expressed in ktlotons, (kt) or megatons (Mt), which -correspond to 1000 and i million'tonnes, of conventional explosive (TNT), +nuclear explosion is expressed in kilotons, (kt) or megatons (Mt), which +correspond to 1000 and 1 million tonnes, of conventional explosive (TNT), respectively. [@Bergkvist2000, 6] @@ -108,8 +128,9 @@ each country had explode, seen in @tbl-yields. ```{python} # | label: tbl-yields # | tbl-cap: "Total number and yields of explosions" +# | output: asis -from great_tables import GT, md +from great_tables import GT df_yields = ( df.select(["country", "id_no", "yield_lower", "yield_upper"]) @@ -119,11 +140,17 @@ df_yields = ( pl.col("id_no").len().alias("count"), pl.col("yield_avg").sum(), ) - # .with_columns(country=pl.col("country").cast(pl.String).str.to_titlecase()) + .with_columns(yield_per_ex=pl.col("yield_avg") / pl.col("count")) .sort("count", descending=True) ) -( +us_row = df_yields.filter(pl.col("country") == "US") +yields_above_us = df_yields.filter( + pl.col("yield_per_ex") > us_row["yield_per_ex"] +).sort("yield_per_ex", descending=True) +assert len(yields_above_us) == 3, "Yield per explosion desc needs updating!" + +tab=( GT(df_yields) .tab_source_note( source_note="Source: Author's elaboration based on Bergkvist and Ferm (2000)." @@ -131,22 +158,33 @@ df_yields = ( .tab_spanner(label="Totals", columns=["count", "yield_avg"]) .tab_stub(rowname_col="country") .tab_stubhead(label="Country") - .cols_label( - count="Count", - yield_avg="Yield in kt", - ) + .cols_label(count="Count", yield_avg="Yield in kt", yield_per_ex="Yield average") .fmt_integer(columns="count") .fmt_number(columns="yield_avg", decimals=1) + .fmt_number(columns="yield_per_ex", decimals=1) ) +del df_yields +tab ``` +It is interesting to note that while the US undoubtedly had the highest raw +number of explosions, it did not, in fact, output the highest estimated +detonation yields. +In fact, `{python} len(yields_above_us)` countries have a higher average +explosion yield per detonation than the US: +`{python} yields_above_us[0]["country"].item()` leads with an average of +`{python} f"{yields_above_us[0]['yield_per_ex'].item():.2f}"` kt, +before +`{python} yields_above_us[1]["country"].item()` with an average of +`{python} f"{yields_above_us[1]['yield_per_ex'].item():.2f}"` kt. + ## Numbers over time -When investigating the nuclear explosions in the world, let us first start by -looking at how many explosions occurred each year in total. This hides the -specific details of who was responsible and which types were involved but -instead paints a much stronger picture of the overall dimension of nuclear -testing, as can be seen in @fig-total. +In the examination of global nuclear detonations, our initial focus shall be +quantifying the annual incidence of the events in aggregate. While it obscures +the specific details of the responsible nations and which diversity of types +tested, it instead paints a much stronger picture of the overall abstracted +dimension of nuclear testing throughout history, as depicted in @fig-total. ```{python} # | label: fig-total @@ -171,17 +209,29 @@ with sns.axes_style( del per_year ``` -As we can see, the numbers of explosions rise increasingly towards 1957 and -sharply until 1958, before dropping off for a year in 1959. The reasons for -this drop are not entirely clear, but it is very likely that the data are -simply missing for these years. - +As we can see, the number of explosions rises increasingly towards 1957 and +sharply until 1958, before dropping off for a year in 1959. The reason for this +drop should primarily be found in the start of the 'Treaty of Test Ban' which +put limits and restraints on the testing of above-ground nuclear armaments, as +discussed in the original article. Above all the contract signals the +prohibition of radioactive debris to fall beyond a nation's respective +territorial bounds. + +However, this contract should perhaps not be viewed as the only reason: With +political and cultural shifts throughout the late 1950s and early 1960s +increasingly focusing on the fallout and horror of nuclear warfare a burgeoning +public opposition to nuclear testing and instead a push towards disarmament was +taking hold. The increased focus on the space race between the US and USSR may +have detracted from the available funds, human resources and agenda attention +for nuclear testing. Lastly, with nuclear testing policies strongly shaped by +the political dynamics of the Cold War, a period of improved diplomatic +relations such as the late 1950s prior to the Cuban missile crisis may directly +affect the output of nuclear testing facilities between various powers. There is another, very steep, rise in 1962 with over 175 recorded explosions, before an even sharper drop-off the following year down to just 50 explosions. - -Afterwards the changes appear less sharp and the changes remain between 77 and +Afterward the changes appear less sharp and the changes remain between 77 and 24 explosions per year, with a slight downward tendency. While these numbers show the overall proliferation of nuclear power, let us now @@ -191,6 +241,7 @@ of explosions over time by country can be seen in @fig-percountry. ```{python} # | label: fig-percountry # | fig-cap: "Nuclear explosions by country, 1945-98" + keys = df.select("date").unique().join(df.select("country").unique(), how="cross") per_country = keys.join( df.group_by(["date", "country"], maintain_order=True).len(), @@ -199,7 +250,7 @@ per_country = keys.join( coalesce=True, ).with_columns(pl.col("len").fill_null(0)) -g = sns.lineplot(data=per_country, x="date", y="len", hue="country") +g = sns.lineplot(data=per_country, x="date", y="len", hue="country", palette=country_colors) g.set_xlabel("Year") g.set_ylabel("Count") plt.setp( @@ -211,14 +262,14 @@ del per_country Once again we can see the visibly steep ramp-up to 1962, though it becomes clear that this was driven both by the USSR and the US. Of course the graph -also makes visible the sheer unmatched number of explosions emenating from both +also makes visible the sheer unmatched number of explosions emanating from both of the countries, with only France catching up to the US numbers and China ultimately overtaking them in the 1990s. However, here it also becomes more clear how the UK was responsible for some early explosions in the late 1950s and early 1960s already, as well as the rise in France's nuclear testing from the early 1960s onwards to around 1980, before -slowly decreasing in intensity afterwards. +slowly decreasing in intensity afterward. Let us turn to a cross-cut through the explosions in @fig-groundlevel, focusing on the number of explosions that have occurred underground and above-ground @@ -273,6 +324,7 @@ with sns.axes_style("darkgrid", {"xtick.bottom": True, "ytick.left": True}): hue="country", multiple="stack", binwidth=365, + palette=country_colors, ) g.xaxis.set_major_locator(mdates.YearLocator(base=5)) @@ -293,25 +345,19 @@ shift from above-ground to underground tests, starting with the year 1962. ## Locations -Finally, let's view a map of the world with the explosions marked. +Finally, let's view a map of the world with the explosions marked, separated by country. +::: {.content-visible when-format="html"} +Hovering over individual explosions will show their year +while a click will open more information in a panel. +::: +The map can be seen in @fig-worldmap. ```{python} -# | label: fig-worldmap -# | fig-cap: "World map of nuclear explosions, 1945-98" +# | label: worldmap-setup +# | output: false import folium import geopandas as gpd -from shapely.geometry import Point -def set_style() -> pl.Expr: - return ( - pl.when(pl.col("country") == "USSR") - .then(pl.lit({"color": "red"}, allow_object=True)) - .otherwise(pl.lit({"color": "blue"}, allow_object=True)) - ) - - -geom = [Point(xy) for xy in zip(df["longitude"], df["latitude"])] -# df_pd = df.with_columns(style=set_style()).to_pandas().set_index("date") df_pd = df.with_columns().to_pandas().set_index("date") gdf = gpd.GeoDataFrame( df_pd, @@ -320,25 +366,18 @@ gdf = gpd.GeoDataFrame( ) del df_pd -country_colors = { - "USA": "darkblue", - "USSR": "darkred", - "FRANCE": "pink", - "UK": "black", - "CHINA": "purple", - "INDIA": "orange", - "PAKIST": "green", -} +def rgb_to_hex(rgb: tuple[float,float,float]) -> str: + return "#" + "".join([format(int(c*255), '02x') for c in rgb]) m = folium.Map(tiles="cartodb positron") for country in country_colors.keys(): fg = folium.FeatureGroup(name=country, show=True).add_to(m) folium.GeoJson( - gdf[gdf["country"].str.contains(country)], + gdf[gdf["country"] == country], name="Nuclear Explosions", marker=folium.Circle(radius=3, fill_opacity=0.4), style_function=lambda x: { - "color": country_colors[x["properties"]["country"]], + "color": rgb_to_hex(country_colors[x["properties"]["country"]]), "radius": ( x["properties"]["magnitude_body"] if x["properties"]["magnitude_body"] > 0 @@ -368,15 +407,45 @@ for country in country_colors.keys(): ), ).add_to(fg) folium.LayerControl().add_to(m) +``` + +::: {#fig-worldmap} + +:::: {.content-visible when-format="html"} + +```{python} +# | label: worldmap-html m ``` -That is all for now. -There are undoubtedly more explorations to undertake, -but this is it for the time being. +:::: - +:::: {.content-visible unless-format="html" width=80%} + +```{python} +# | label: worldmap-non-html +# ENSURE SELENIUM IS INSTALLED +m.png_enabled = True +m +``` + +:::: + +World map of nuclear explosions, 1945-98 +::: + +While there are undoubtedly more aspects of the data that provide interesting +patterns for analysis, this shall be the extent of review for the time being +for this reproduction. + +We can see how the combination of python polars and seaborn makes the process +relatively approachable, understandable and, combined with the rendering output +by quarto, fully reproducible. + +Additionally, we can see how additional projects can be included to produce +interactive graphs and maps with tools such as folium and geopandas. + +## References + +::: {#refs} +::: From 824ad25e60954886836765108f5631436c0db0a6 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 16:29:48 +0200 Subject: [PATCH 03/10] Add meta-article Contains thoughts about the process writing the article. --- meta.md | 157 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 meta.md diff --git a/meta.md b/meta.md new file mode 100644 index 0000000..d3b9b97 --- /dev/null +++ b/meta.md @@ -0,0 +1,157 @@ +This page documents some meta observations about my time recreating the nuclear explosions in this post, +mostly some little tips to work well with python polars and seaborn, or little tricks to integrate them and geopandas visualizations. + +## From a lat/long polars dataframe to geopandas + +To go from a polars frame to one we can use for GIS operations with geopandas is fairly simple: +We first move from a polars to an indexed pandas frame, in this case I have indexed on the date of each explosion. + +We can use this intermediate dataframe to fill a geopandas frame which is built from the points of lat/long columns, +using the `gpd.points_from_xy()` function to create spatial `Point` objects from simple pandas Series. +Finally, we need to set a 'crs=' mapping for which this visualization simply uses the `EPSG:4326` global offsets (will generally be the same for global mappings). + +```python +df_pd = df.with_columns().to_pandas().set_index("date") +gdf = gpd.GeoDataFrame( + df_pd, + crs="EPSG:4326", + geometry=gpd.points_from_xy(x=df_pd["longitude"], y=df_pd["latitude"]), +) +del df_pd +``` + +## Keeping the same seaborn color palette for the same categories + +For the analysis, I have multiple plots which distinguish between the different countries undertaking nuclear detonations. +The country category thus appears repeatedly, and with static values (i.e. it will always contain 'US', 'USSR', 'China', 'France' and so on). + +Now, seaborn has very nice functionality to automatically give different hues to categories like these in plots, +but how do we ensure that the hues given remain _the same_ throughout? + +One way of achieving it would be to keep the order of categories the same throughout all plots. +However, this seems hidden, +often adds to the strain of just getting to the right data frame calculations, +appears a little too magic for my liking and, to top it off, +is even harder to achieve with some of polars' parallelized operations. + +Instead we can explicitly map our categories to colors. +In my case, my categories for this example will always be the different countries: + +```python +country_colors = { + "US": 'blue', + "USSR": 'red', + "France": 'pink' + "UK": 'black' + "China": 'purple' + "India": 'orange' + "Pakistan": 'green' +} +``` + +These are colors seaborn understands and can be given to a plot via the keyword option `palette=country_colors` which will pass along the colors above to the respective plot. + +However, one advantage of seaborn is its nice in-built color schemes (i.e. palettes) which we will not make use of if we instead hard-code our color preferences like this. +Instead, we can directly access seaborn's color palette with `sns.color_palette()` which we can then use to explicitly map our categories to colors: + +```python +cp=sns.color_palette() +country_colors = { + "US": cp[0], + "USSR": cp[3], + "France": cp[6], + "UK": cp[5], + "China": cp[4], + "India": cp[1], + "Pakistan": cp[2], +} +``` + +This mapping is passed exactly the same way as the other. +Now, we've ensured that colors in plots (that have the countries as hue category) will all have the same color for the same country throughout. +At the same time we have a single spot in which we can change the actual color theme seaborn uses, instead of hard-coding our preferences throughout. + +This I find very useful when creating analyses with similar categories throughout, + +In the nuclear analysis there is a folium geospatial (GeoJson) map at the very end which uses colors to distinguish between the countries once again. +Here we can make use of almost the same strategy, with the one caveat that folium expects the colors in hexadecimal format, while seaborn internally stores them as RGB value tuples. + +What we can do, then is to use a simple translation function which converts from one format to the other on the fly, +and inject that into the map creation method of folium: + +```python +def rgb_to_hex(rgb: tuple[float,float,float]) -> str: + return "#" + "".join([format(int(c*255), '02x') for c in rgb]) + + +map = folium.Map(tiles="cartodb positron") +folium.GeoJson( + gdf, + name="Nuclear Explosions", + marker=folium.Circle(radius=3, fill_opacity=0.4), + style_function=lambda x: { + "color": rgb_to_hex(country_colors[x["properties"]["country"]]), + "radius": ( + x["properties"]["magnitude_body"] + if x["properties"]["magnitude_body"] > 0 + else 1.0 + ) + * 10, + }, + ).add_to(map) +``` + +## Using dictionary keys to create folium map layers + +As a bonus we can even use our color category keys to create different layers on the folium map which can be turned on and off individually. +Thus we can decide which country's detonations we want to visualize. + +Of course, we could also create these keys dynamically from the polars dataframe by extracting the `.unique()` elements of its "country" column (even though we use pandas geoframe for display), +but here I am using my explicit mapping instead. + +The implementation works already with two additional lines and a loop, +by looping through our keys and adding a new layer for each one, +filtering out all the rows which do not exactly match the key using a pandas filter. + +```python +m = folium.Map(tiles="cartodb positron") +for country in country_colors.keys(): + fg = folium.FeatureGroup(name=country, show=True).add_to(m) + folium.GeoJson( + gdf[gdf["country"] == country], + name="Nuclear Explosions", + marker=folium.Circle(radius=3, fill_opacity=0.4), + style_function=lambda x: { + "color": rgb_to_hex(country_colors[x["properties"]["country"]]), + "radius": ( + x["properties"]["magnitude_body"] + if x["properties"]["magnitude_body"] > 0 + else 1.0 + ) + * 10, + }, + ).add_to(fg) +folium.LayerControl().add_to(m) +``` + +## Remaining issues + +While working with polars is wonderful and seaborn takes a lot of the stress of creating half-way nicely formatted plots out of mind while first creating them, +some pain points remain. + +While I am cautiously optimistic, seaborn's 'objects-style' interface still remains woefully undercooked. +It is already possible to create some basic plots with it and its declarative style is wonderful +(as in, it really matches the mental model I have of drawing individual plot elements into a coherent whole). +But for anything more complex --- which in my opinion is exactly where this interface will really shine --- +it remains out of reach because of missing methods and implementations. +This is, of course, ideally just a temporary issue until the implementation gets better, +but until then we are still stuck with the more strange mish-mash of seaborn simplicity with matplotlib exactness, +and having to know when to leave the former behind and delve into the arcane API of the latter. + +Additionally, when combined with quarto for publishing some more pain points appear. +One that has been true for the longest time, and will likely remain so for the foreseeable future, +is that tables beyond a certain complexity are just _painful_ in quarto multi-output publishing. +This project made use the fantastic python library [great tables]() which indeed lives up to its name and produces absolutely great tables with very little effort. +However, it primarily targets the html format. +Getting this format into shape for quarto to then translate it into the pandoc AST and ultimately whatever format is not pretty. +For example LaTeX routinely just crashes instead of rendering the table correctly into a PDF file. From fc22e0cc0296a622af1da2e57b1a797baa6f0e4e Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 16:29:57 +0200 Subject: [PATCH 04/10] Add selenium as dependency Required for folium maps to png file pipeline. --- poetry.lock | 109 ++++++++++++++++++++++++++++++++++++++++++++++++- pyproject.toml | 1 + 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 908ee87..2ae1bff 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2028,6 +2028,20 @@ files = [ {file = "numpy-2.0.0.tar.gz", hash = "sha256:cf5d1c9e6837f8af9f92b6bd3e86d513cdc11f60fd62185cc49ec7d1aba34864"}, ] +[[package]] +name = "outcome" +version = "1.3.0.post0" +description = "Capture the outcome of Python function calls." +optional = false +python-versions = ">=3.7" +files = [ + {file = "outcome-1.3.0.post0-py2.py3-none-any.whl", hash = "sha256:e771c5ce06d1415e356078d3bdd68523f284b4ce5419828922b6871e65eda82b"}, + {file = "outcome-1.3.0.post0.tar.gz", hash = "sha256:9dcf02e65f2971b80047b377468e72a268e15c0af3cf1238e6ff14f7f91143b8"}, +] + +[package.dependencies] +attrs = ">=19.2.0" + [[package]] name = "overrides" version = "7.7.0" @@ -2563,6 +2577,18 @@ files = [ [package.dependencies] certifi = "*" +[[package]] +name = "pysocks" +version = "1.7.1" +description = "A Python SOCKS client module. See https://github.com/Anorov/PySocks for more information." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "PySocks-1.7.1-py27-none-any.whl", hash = "sha256:08e69f092cc6dbe92a0fdd16eeb9b9ffbc13cadfe5ca4c7bd92ffb078b293299"}, + {file = "PySocks-1.7.1-py3-none-any.whl", hash = "sha256:2725bd0a9925919b9b51739eea5f9e2bae91e83288108a9ad338b2e3a4435ee5"}, + {file = "PySocks-1.7.1.tar.gz", hash = "sha256:3f8804571ebe159c380ac6de37643bb4685970655d3bba243530d6558b799aa0"}, +] + [[package]] name = "python-dateutil" version = "2.9.0.post0" @@ -3018,6 +3044,25 @@ dev = ["flake8", "flit", "mypy", "pandas-stubs", "pre-commit", "pytest", "pytest docs = ["ipykernel", "nbconvert", "numpydoc", "pydata_sphinx_theme (==0.10.0rc2)", "pyyaml", "sphinx (<6.0.0)", "sphinx-copybutton", "sphinx-design", "sphinx-issues"] stats = ["scipy (>=1.7)", "statsmodels (>=0.12)"] +[[package]] +name = "selenium" +version = "4.22.0" +description = "Official Python bindings for Selenium WebDriver" +optional = false +python-versions = ">=3.8" +files = [ + {file = "selenium-4.22.0-py3-none-any.whl", hash = "sha256:e424991196e9857e19bf04fe5c1c0a4aac076794ff5e74615b1124e729d93104"}, + {file = "selenium-4.22.0.tar.gz", hash = "sha256:903c8c9d61b3eea6fcc9809dc7d9377e04e2ac87709876542cc8f863e482c4ce"}, +] + +[package.dependencies] +certifi = ">=2021.10.8" +trio = ">=0.17,<1.0" +trio-websocket = ">=0.9,<1.0" +typing_extensions = ">=4.9.0" +urllib3 = {version = ">=1.26,<3", extras = ["socks"]} +websocket-client = ">=1.8.0" + [[package]] name = "send2trash" version = "1.8.3" @@ -3128,6 +3173,17 @@ files = [ {file = "sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc"}, ] +[[package]] +name = "sortedcontainers" +version = "2.4.0" +description = "Sorted Containers -- Sorted List, Sorted Dict, Sorted Set" +optional = false +python-versions = "*" +files = [ + {file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"}, + {file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"}, +] + [[package]] name = "soupsieve" version = "2.5" @@ -3232,6 +3288,40 @@ files = [ docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] +[[package]] +name = "trio" +version = "0.25.1" +description = "A friendly Python library for async concurrency and I/O" +optional = false +python-versions = ">=3.8" +files = [ + {file = "trio-0.25.1-py3-none-any.whl", hash = "sha256:e42617ba091e7b2e50c899052e83a3c403101841de925187f61e7b7eaebdf3fb"}, + {file = "trio-0.25.1.tar.gz", hash = "sha256:9f5314f014ea3af489e77b001861c535005c3858d38ec46b6b071ebfa339d7fb"}, +] + +[package.dependencies] +attrs = ">=23.2.0" +cffi = {version = ">=1.14", markers = "os_name == \"nt\" and implementation_name != \"pypy\""} +idna = "*" +outcome = "*" +sniffio = ">=1.3.0" +sortedcontainers = "*" + +[[package]] +name = "trio-websocket" +version = "0.11.1" +description = "WebSocket library for Trio" +optional = false +python-versions = ">=3.7" +files = [ + {file = "trio-websocket-0.11.1.tar.gz", hash = "sha256:18c11793647703c158b1f6e62de638acada927344d534e3c7628eedcb746839f"}, + {file = "trio_websocket-0.11.1-py3-none-any.whl", hash = "sha256:520d046b0d030cf970b8b2b2e00c4c2245b3807853ecd44214acd33d74581638"}, +] + +[package.dependencies] +trio = ">=0.11" +wsproto = ">=0.14" + [[package]] name = "types-python-dateutil" version = "2.9.0.20240316" @@ -3290,6 +3380,9 @@ files = [ {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, ] +[package.dependencies] +pysocks = {version = ">=1.5.6,<1.5.7 || >1.5.7,<2.0", optional = true, markers = "extra == \"socks\""} + [package.extras] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] h2 = ["h2 (>=4,<5)"] @@ -3360,6 +3453,20 @@ files = [ {file = "widgetsnbextension-4.0.11.tar.gz", hash = "sha256:8b22a8f1910bfd188e596fe7fc05dcbd87e810c8a4ba010bdb3da86637398474"}, ] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "xyzservices" version = "2024.6.0" @@ -3389,4 +3496,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "d070f377faf4ef9fdaf2f401e4af39ccf8d5989219c9b572a3e97d278d6cd438" +content-hash = "8e8f6def767c1dec2efbcb1ebde1869d77490b9fe4c11277da521a56b8eeb1ab" diff --git a/pyproject.toml b/pyproject.toml index 61f439d..0cf815b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,6 +15,7 @@ pyarrow = "^16.1.0" great-tables = "^0.9.0" geopandas = "^0.14.4" folium = "^0.17.0" +selenium = "^4.22.0" [tool.poetry.group.dev.dependencies] From d067d412673eb44033b2c7ebc8a8c5e024b827f0 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 16:30:42 +0200 Subject: [PATCH 05/10] Add quarto project file --- _quarto.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 _quarto.yml diff --git a/_quarto.yml b/_quarto.yml new file mode 100644 index 0000000..485f649 --- /dev/null +++ b/_quarto.yml @@ -0,0 +1,35 @@ +author: Marty Oehme +csl: https://www.zotero.org/styles/apa + +project: + type: default + output-dir: output + render: + - index.qmd + - meta.md + +format: + html: + code-fold: true + toc: true + echo: true + # pdf: # BREAKS ON 'GREAT TABLES' python lib tables + # echo: false # since we want to see the code in this case + # papersize: A4 + # # geometry: + # # - left=2cm + # # - right=2.5cm + # # - top=2.5cm + # # - bottom=2.5cm + # indent: true + # linestretch: 1.5 + # fontfamily: lmodern + # fontsize: "12" + # pdf-engine: tectonic + typst: + toc: true + echo: false + citeproc: true + docx: + toc: true + echo: false From 8cbe6c35718b4018805788e8a1c11e65cffeb912 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 16:32:41 +0200 Subject: [PATCH 06/10] Extend gitignore for outputs and intermediate files Ignore new output directory, any intermediary file directories and python notebook files (also usually intermediate in quarto projects). --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d23586c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.quarto/ +/output/ +/*_files/ + +*.ipynb From e90b423ebcdbc65c81e766b4fe096e1259af0fcb Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 20:00:17 +0200 Subject: [PATCH 07/10] Split into default and blog-ready render profiles --- _quarto-blog.yml | 17 +++++++++++++++++ _quarto-default.yml | 35 +++++++++++++++++++++++++++++++++++ _quarto.yml | 35 +++-------------------------------- 3 files changed, 55 insertions(+), 32 deletions(-) create mode 100644 _quarto-blog.yml create mode 100644 _quarto-default.yml diff --git a/_quarto-blog.yml b/_quarto-blog.yml new file mode 100644 index 0000000..cadadf5 --- /dev/null +++ b/_quarto-blog.yml @@ -0,0 +1,17 @@ +project: + type: default + output-dir: /home/marty/projects/hosting/webpage/src/content/blog/2024-07-02-nuclear-explosions-analysis + render: + - index.qmd + +format: + hugo-md: + preserve-yaml: true + code-fold: true + typst: + toc: true + echo: false + citeproc: true + docx: + toc: true + echo: false diff --git a/_quarto-default.yml b/_quarto-default.yml new file mode 100644 index 0000000..6388dfb --- /dev/null +++ b/_quarto-default.yml @@ -0,0 +1,35 @@ +project: + type: default + output-dir: output + render: + - index.qmd + - meta.md + +execute: + cache: true + +format: + html: + code-fold: true + toc: true + echo: true + typst: + toc: true + echo: false + citeproc: true + docx: + toc: true + echo: false + # pdf: # BREAKS ON 'GREAT TABLES' python lib tables + # echo: false # since we want to see the code in this case + # papersize: A4 + # # geometry: + # # - left=2cm + # # - right=2.5cm + # # - top=2.5cm + # # - bottom=2.5cm + # indent: true + # linestretch: 1.5 + # fontfamily: lmodern + # fontsize: "12" + # pdf-engine: tectonic diff --git a/_quarto.yml b/_quarto.yml index 485f649..920501a 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -1,35 +1,6 @@ author: Marty Oehme csl: https://www.zotero.org/styles/apa -project: - type: default - output-dir: output - render: - - index.qmd - - meta.md - -format: - html: - code-fold: true - toc: true - echo: true - # pdf: # BREAKS ON 'GREAT TABLES' python lib tables - # echo: false # since we want to see the code in this case - # papersize: A4 - # # geometry: - # # - left=2cm - # # - right=2.5cm - # # - top=2.5cm - # # - bottom=2.5cm - # indent: true - # linestretch: 1.5 - # fontfamily: lmodern - # fontsize: "12" - # pdf-engine: tectonic - typst: - toc: true - echo: false - citeproc: true - docx: - toc: true - echo: false +profile: + group: + - [default, blog] From 4306a7d246639506aab10323c86319abdd0a55f6 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 20:00:37 +0200 Subject: [PATCH 08/10] Fix plot links for astro static files --- _quarto-blog.yml | 2 ++ tools/fix-astro-img.py | 26 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 tools/fix-astro-img.py diff --git a/_quarto-blog.yml b/_quarto-blog.yml index cadadf5..a3f0b72 100644 --- a/_quarto-blog.yml +++ b/_quarto-blog.yml @@ -3,6 +3,8 @@ project: output-dir: /home/marty/projects/hosting/webpage/src/content/blog/2024-07-02-nuclear-explosions-analysis render: - index.qmd + post-render: + - tools/fix-astro-img.py format: hugo-md: diff --git a/tools/fix-astro-img.py b/tools/fix-astro-img.py new file mode 100644 index 0000000..c4badde --- /dev/null +++ b/tools/fix-astro-img.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# Replaces all img tags with markdown ![image-tags](./aiming/at/output/dir) + +import re +import os +import sys + +if not os.getenv("QUARTO_PROJECT_RENDER_ALL"): + sys.exit(0) + +q_output_dir = os.getenv("QUARTO_PROJECT_OUTPUT_DIR") +q_output_files = os.getenv("QUARTO_PROJECT_OUTPUT_FILES") +if not q_output_files: + sys.exit(1) + +for fname in q_output_files.splitlines(): + if not fname.endswith(".md"): + continue + + with open(fname, "r") as f: + content = f.read() + + modified = re.sub(r'', r"![fig](./\1)", content) + + with open(fname, "w") as f: + f.write(modified) From 4ec27ed73b0c8e195fa0130e74dd28a9db576729 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 20:27:25 +0200 Subject: [PATCH 09/10] Add pillow dependency --- poetry.lock | 155 ++++++++++++++++++++++++++----------------------- pyproject.toml | 1 + 2 files changed, 84 insertions(+), 72 deletions(-) diff --git a/poetry.lock b/poetry.lock index 2ae1bff..cb7b449 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2175,84 +2175,95 @@ ptyprocess = ">=0.5" [[package]] name = "pillow" -version = "10.3.0" +version = "10.4.0" description = "Python Imaging Library (Fork)" optional = false python-versions = ">=3.8" files = [ - {file = "pillow-10.3.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:90b9e29824800e90c84e4022dd5cc16eb2d9605ee13f05d47641eb183cd73d45"}, - {file = "pillow-10.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a2c405445c79c3f5a124573a051062300936b0281fee57637e706453e452746c"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:78618cdbccaa74d3f88d0ad6cb8ac3007f1a6fa5c6f19af64b55ca170bfa1edf"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:261ddb7ca91fcf71757979534fb4c128448b5b4c55cb6152d280312062f69599"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:ce49c67f4ea0609933d01c0731b34b8695a7a748d6c8d186f95e7d085d2fe475"}, - {file = "pillow-10.3.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:b14f16f94cbc61215115b9b1236f9c18403c15dd3c52cf629072afa9d54c1cbf"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:d33891be6df59d93df4d846640f0e46f1a807339f09e79a8040bc887bdcd7ed3"}, - {file = "pillow-10.3.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b50811d664d392f02f7761621303eba9d1b056fb1868c8cdf4231279645c25f5"}, - {file = "pillow-10.3.0-cp310-cp310-win32.whl", hash = "sha256:ca2870d5d10d8726a27396d3ca4cf7976cec0f3cb706debe88e3a5bd4610f7d2"}, - {file = "pillow-10.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:f0d0591a0aeaefdaf9a5e545e7485f89910c977087e7de2b6c388aec32011e9f"}, - {file = "pillow-10.3.0-cp310-cp310-win_arm64.whl", hash = "sha256:ccce24b7ad89adb5a1e34a6ba96ac2530046763912806ad4c247356a8f33a67b"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:5f77cf66e96ae734717d341c145c5949c63180842a545c47a0ce7ae52ca83795"}, - {file = "pillow-10.3.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e4b878386c4bf293578b48fc570b84ecfe477d3b77ba39a6e87150af77f40c57"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fdcbb4068117dfd9ce0138d068ac512843c52295ed996ae6dd1faf537b6dbc27"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9797a6c8fe16f25749b371c02e2ade0efb51155e767a971c61734b1bf6293994"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:9e91179a242bbc99be65e139e30690e081fe6cb91a8e77faf4c409653de39451"}, - {file = "pillow-10.3.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:1b87bd9d81d179bd8ab871603bd80d8645729939f90b71e62914e816a76fc6bd"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:81d09caa7b27ef4e61cb7d8fbf1714f5aec1c6b6c5270ee53504981e6e9121ad"}, - {file = "pillow-10.3.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:048ad577748b9fa4a99a0548c64f2cb8d672d5bf2e643a739ac8faff1164238c"}, - {file = "pillow-10.3.0-cp311-cp311-win32.whl", hash = "sha256:7161ec49ef0800947dc5570f86568a7bb36fa97dd09e9827dc02b718c5643f09"}, - {file = "pillow-10.3.0-cp311-cp311-win_amd64.whl", hash = "sha256:8eb0908e954d093b02a543dc963984d6e99ad2b5e36503d8a0aaf040505f747d"}, - {file = "pillow-10.3.0-cp311-cp311-win_arm64.whl", hash = "sha256:4e6f7d1c414191c1199f8996d3f2282b9ebea0945693fb67392c75a3a320941f"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:e46f38133e5a060d46bd630faa4d9fa0202377495df1f068a8299fd78c84de84"}, - {file = "pillow-10.3.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:50b8eae8f7334ec826d6eeffaeeb00e36b5e24aa0b9df322c247539714c6df19"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d3bea1c75f8c53ee4d505c3e67d8c158ad4df0d83170605b50b64025917f338"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:19aeb96d43902f0a783946a0a87dbdad5c84c936025b8419da0a0cd7724356b1"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:74d28c17412d9caa1066f7a31df8403ec23d5268ba46cd0ad2c50fb82ae40462"}, - {file = "pillow-10.3.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:ff61bfd9253c3915e6d41c651d5f962da23eda633cf02262990094a18a55371a"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d886f5d353333b4771d21267c7ecc75b710f1a73d72d03ca06df49b09015a9ef"}, - {file = "pillow-10.3.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4b5ec25d8b17217d635f8935dbc1b9aa5907962fae29dff220f2659487891cd3"}, - {file = "pillow-10.3.0-cp312-cp312-win32.whl", hash = "sha256:51243f1ed5161b9945011a7360e997729776f6e5d7005ba0c6879267d4c5139d"}, - {file = "pillow-10.3.0-cp312-cp312-win_amd64.whl", hash = "sha256:412444afb8c4c7a6cc11a47dade32982439925537e483be7c0ae0cf96c4f6a0b"}, - {file = "pillow-10.3.0-cp312-cp312-win_arm64.whl", hash = "sha256:798232c92e7665fe82ac085f9d8e8ca98826f8e27859d9a96b41d519ecd2e49a"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:4eaa22f0d22b1a7e93ff0a596d57fdede2e550aecffb5a1ef1106aaece48e96b"}, - {file = "pillow-10.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cd5e14fbf22a87321b24c88669aad3a51ec052eb145315b3da3b7e3cc105b9a2"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1530e8f3a4b965eb6a7785cf17a426c779333eb62c9a7d1bbcf3ffd5bf77a4aa"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d512aafa1d32efa014fa041d38868fda85028e3f930a96f85d49c7d8ddc0383"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:339894035d0ede518b16073bdc2feef4c991ee991a29774b33e515f1d308e08d"}, - {file = "pillow-10.3.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:aa7e402ce11f0885305bfb6afb3434b3cd8f53b563ac065452d9d5654c7b86fd"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:0ea2a783a2bdf2a561808fe4a7a12e9aa3799b701ba305de596bc48b8bdfce9d"}, - {file = "pillow-10.3.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c78e1b00a87ce43bb37642c0812315b411e856a905d58d597750eb79802aaaa3"}, - {file = "pillow-10.3.0-cp38-cp38-win32.whl", hash = "sha256:72d622d262e463dfb7595202d229f5f3ab4b852289a1cd09650362db23b9eb0b"}, - {file = "pillow-10.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:2034f6759a722da3a3dbd91a81148cf884e91d1b747992ca288ab88c1de15999"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2ed854e716a89b1afcedea551cd85f2eb2a807613752ab997b9974aaa0d56936"}, - {file = "pillow-10.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:dc1a390a82755a8c26c9964d457d4c9cbec5405896cba94cf51f36ea0d855002"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4203efca580f0dd6f882ca211f923168548f7ba334c189e9eab1178ab840bf60"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3102045a10945173d38336f6e71a8dc71bcaeed55c3123ad4af82c52807b9375"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:6fb1b30043271ec92dc65f6d9f0b7a830c210b8a96423074b15c7bc999975f57"}, - {file = "pillow-10.3.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:1dfc94946bc60ea375cc39cff0b8da6c7e5f8fcdc1d946beb8da5c216156ddd8"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b09b86b27a064c9624d0a6c54da01c1beaf5b6cadfa609cf63789b1d08a797b9"}, - {file = "pillow-10.3.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d3b2348a78bc939b4fed6552abfd2e7988e0f81443ef3911a4b8498ca084f6eb"}, - {file = "pillow-10.3.0-cp39-cp39-win32.whl", hash = "sha256:45ebc7b45406febf07fef35d856f0293a92e7417ae7933207e90bf9090b70572"}, - {file = "pillow-10.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:0ba26351b137ca4e0db0342d5d00d2e355eb29372c05afd544ebf47c0956ffeb"}, - {file = "pillow-10.3.0-cp39-cp39-win_arm64.whl", hash = "sha256:50fd3f6b26e3441ae07b7c979309638b72abc1a25da31a81a7fbd9495713ef4f"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_10_10_x86_64.whl", hash = "sha256:6b02471b72526ab8a18c39cb7967b72d194ec53c1fd0a70b050565a0f366d355"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8ab74c06ffdab957d7670c2a5a6e1a70181cd10b727cd788c4dd9005b6a8acd9"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:048eeade4c33fdf7e08da40ef402e748df113fd0b4584e32c4af74fe78baaeb2"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9e2ec1e921fd07c7cda7962bad283acc2f2a9ccc1b971ee4b216b75fad6f0463"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:4c8e73e99da7db1b4cad7f8d682cf6abad7844da39834c288fbfa394a47bbced"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:16563993329b79513f59142a6b02055e10514c1a8e86dca8b48a893e33cf91e3"}, - {file = "pillow-10.3.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:dd78700f5788ae180b5ee8902c6aea5a5726bac7c364b202b4b3e3ba2d293170"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:aff76a55a8aa8364d25400a210a65ff59d0168e0b4285ba6bf2bd83cf675ba32"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:b7bc2176354defba3edc2b9a777744462da2f8e921fbaf61e52acb95bafa9828"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:793b4e24db2e8742ca6423d3fde8396db336698c55cd34b660663ee9e45ed37f"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d93480005693d247f8346bc8ee28c72a2191bdf1f6b5db469c096c0c867ac015"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:c83341b89884e2b2e55886e8fbbf37c3fa5efd6c8907124aeb72f285ae5696e5"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1a1d1915db1a4fdb2754b9de292642a39a7fb28f1736699527bb649484fb966a"}, - {file = "pillow-10.3.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:a0eaa93d054751ee9964afa21c06247779b90440ca41d184aeb5d410f20ff591"}, - {file = "pillow-10.3.0.tar.gz", hash = "sha256:9d2455fbf44c914840c793e89aa82d0e1763a14253a000743719ae5946814b2d"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:4d9667937cfa347525b319ae34375c37b9ee6b525440f3ef48542fcf66f2731e"}, + {file = "pillow-10.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:543f3dc61c18dafb755773efc89aae60d06b6596a63914107f75459cf984164d"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7928ecbf1ece13956b95d9cbcfc77137652b02763ba384d9ab508099a2eca856"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4d49b85c4348ea0b31ea63bc75a9f3857869174e2bf17e7aba02945cd218e6f"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:6c762a5b0997f5659a5ef2266abc1d8851ad7749ad9a6a5506eb23d314e4f46b"}, + {file = "pillow-10.4.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:a985e028fc183bf12a77a8bbf36318db4238a3ded7fa9df1b9a133f1cb79f8fc"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:812f7342b0eee081eaec84d91423d1b4650bb9828eb53d8511bcef8ce5aecf1e"}, + {file = "pillow-10.4.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:ac1452d2fbe4978c2eec89fb5a23b8387aba707ac72810d9490118817d9c0b46"}, + {file = "pillow-10.4.0-cp310-cp310-win32.whl", hash = "sha256:bcd5e41a859bf2e84fdc42f4edb7d9aba0a13d29a2abadccafad99de3feff984"}, + {file = "pillow-10.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:ecd85a8d3e79cd7158dec1c9e5808e821feea088e2f69a974db5edf84dc53141"}, + {file = "pillow-10.4.0-cp310-cp310-win_arm64.whl", hash = "sha256:ff337c552345e95702c5fde3158acb0625111017d0e5f24bf3acdb9cc16b90d1"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:0a9ec697746f268507404647e531e92889890a087e03681a3606d9b920fbee3c"}, + {file = "pillow-10.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dfe91cb65544a1321e631e696759491ae04a2ea11d36715eca01ce07284738be"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5dc6761a6efc781e6a1544206f22c80c3af4c8cf461206d46a1e6006e4429ff3"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5e84b6cc6a4a3d76c153a6b19270b3526a5a8ed6b09501d3af891daa2a9de7d6"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:bbc527b519bd3aa9d7f429d152fea69f9ad37c95f0b02aebddff592688998abe"}, + {file = "pillow-10.4.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:76a911dfe51a36041f2e756b00f96ed84677cdeb75d25c767f296c1c1eda1319"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:59291fb29317122398786c2d44427bbd1a6d7ff54017075b22be9d21aa59bd8d"}, + {file = "pillow-10.4.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:416d3a5d0e8cfe4f27f574362435bc9bae57f679a7158e0096ad2beb427b8696"}, + {file = "pillow-10.4.0-cp311-cp311-win32.whl", hash = "sha256:7086cc1d5eebb91ad24ded9f58bec6c688e9f0ed7eb3dbbf1e4800280a896496"}, + {file = "pillow-10.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:cbed61494057c0f83b83eb3a310f0bf774b09513307c434d4366ed64f4128a91"}, + {file = "pillow-10.4.0-cp311-cp311-win_arm64.whl", hash = "sha256:f5f0c3e969c8f12dd2bb7e0b15d5c468b51e5017e01e2e867335c81903046a22"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_10_10_x86_64.whl", hash = "sha256:673655af3eadf4df6b5457033f086e90299fdd7a47983a13827acf7459c15d94"}, + {file = "pillow-10.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:866b6942a92f56300012f5fbac71f2d610312ee65e22f1aa2609e491284e5597"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:29dbdc4207642ea6aad70fbde1a9338753d33fb23ed6956e706936706f52dd80"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf2342ac639c4cf38799a44950bbc2dfcb685f052b9e262f446482afaf4bffca"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:f5b92f4d70791b4a67157321c4e8225d60b119c5cc9aee8ecf153aace4aad4ef"}, + {file = "pillow-10.4.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:86dcb5a1eb778d8b25659d5e4341269e8590ad6b4e8b44d9f4b07f8d136c414a"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:780c072c2e11c9b2c7ca37f9a2ee8ba66f44367ac3e5c7832afcfe5104fd6d1b"}, + {file = "pillow-10.4.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:37fb69d905be665f68f28a8bba3c6d3223c8efe1edf14cc4cfa06c241f8c81d9"}, + {file = "pillow-10.4.0-cp312-cp312-win32.whl", hash = "sha256:7dfecdbad5c301d7b5bde160150b4db4c659cee2b69589705b6f8a0c509d9f42"}, + {file = "pillow-10.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:1d846aea995ad352d4bdcc847535bd56e0fd88d36829d2c90be880ef1ee4668a"}, + {file = "pillow-10.4.0-cp312-cp312-win_arm64.whl", hash = "sha256:e553cad5179a66ba15bb18b353a19020e73a7921296a7979c4a2b7f6a5cd57f9"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:8bc1a764ed8c957a2e9cacf97c8b2b053b70307cf2996aafd70e91a082e70df3"}, + {file = "pillow-10.4.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6209bb41dc692ddfee4942517c19ee81b86c864b626dbfca272ec0f7cff5d9fb"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bee197b30783295d2eb680b311af15a20a8b24024a19c3a26431ff83eb8d1f70"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ef61f5dd14c300786318482456481463b9d6b91ebe5ef12f405afbba77ed0be"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:297e388da6e248c98bc4a02e018966af0c5f92dfacf5a5ca22fa01cb3179bca0"}, + {file = "pillow-10.4.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:e4db64794ccdf6cb83a59d73405f63adbe2a1887012e308828596100a0b2f6cc"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:bd2880a07482090a3bcb01f4265f1936a903d70bc740bfcb1fd4e8a2ffe5cf5a"}, + {file = "pillow-10.4.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4b35b21b819ac1dbd1233317adeecd63495f6babf21b7b2512d244ff6c6ce309"}, + {file = "pillow-10.4.0-cp313-cp313-win32.whl", hash = "sha256:551d3fd6e9dc15e4c1eb6fc4ba2b39c0c7933fa113b220057a34f4bb3268a060"}, + {file = "pillow-10.4.0-cp313-cp313-win_amd64.whl", hash = "sha256:030abdbe43ee02e0de642aee345efa443740aa4d828bfe8e2eb11922ea6a21ea"}, + {file = "pillow-10.4.0-cp313-cp313-win_arm64.whl", hash = "sha256:5b001114dd152cfd6b23befeb28d7aee43553e2402c9f159807bf55f33af8a8d"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:8d4d5063501b6dd4024b8ac2f04962d661222d120381272deea52e3fc52d3736"}, + {file = "pillow-10.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7c1ee6f42250df403c5f103cbd2768a28fe1a0ea1f0f03fe151c8741e1469c8b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b15e02e9bb4c21e39876698abf233c8c579127986f8207200bc8a8f6bb27acf2"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8d4bade9952ea9a77d0c3e49cbd8b2890a399422258a77f357b9cc9be8d680"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_aarch64.whl", hash = "sha256:43efea75eb06b95d1631cb784aa40156177bf9dd5b4b03ff38979e048258bc6b"}, + {file = "pillow-10.4.0-cp38-cp38-manylinux_2_28_x86_64.whl", hash = "sha256:950be4d8ba92aca4b2bb0741285a46bfae3ca699ef913ec8416c1b78eadd64cd"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d7480af14364494365e89d6fddc510a13e5a2c3584cb19ef65415ca57252fb84"}, + {file = "pillow-10.4.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:73664fe514b34c8f02452ffb73b7a92c6774e39a647087f83d67f010eb9a0cf0"}, + {file = "pillow-10.4.0-cp38-cp38-win32.whl", hash = "sha256:e88d5e6ad0d026fba7bdab8c3f225a69f063f116462c49892b0149e21b6c0a0e"}, + {file = "pillow-10.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:5161eef006d335e46895297f642341111945e2c1c899eb406882a6c61a4357ab"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:0ae24a547e8b711ccaaf99c9ae3cd975470e1a30caa80a6aaee9a2f19c05701d"}, + {file = "pillow-10.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:298478fe4f77a4408895605f3482b6cc6222c018b2ce565c2b6b9c354ac3229b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:134ace6dc392116566980ee7436477d844520a26a4b1bd4053f6f47d096997fd"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:930044bb7679ab003b14023138b50181899da3f25de50e9dbee23b61b4de2126"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:c76e5786951e72ed3686e122d14c5d7012f16c8303a674d18cdcd6d89557fc5b"}, + {file = "pillow-10.4.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:b2724fdb354a868ddf9a880cb84d102da914e99119211ef7ecbdc613b8c96b3c"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:dbc6ae66518ab3c5847659e9988c3b60dc94ffb48ef9168656e0019a93dbf8a1"}, + {file = "pillow-10.4.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:06b2f7898047ae93fad74467ec3d28fe84f7831370e3c258afa533f81ef7f3df"}, + {file = "pillow-10.4.0-cp39-cp39-win32.whl", hash = "sha256:7970285ab628a3779aecc35823296a7869f889b8329c16ad5a71e4901a3dc4ef"}, + {file = "pillow-10.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:961a7293b2457b405967af9c77dcaa43cc1a8cd50d23c532e62d48ab6cdd56f5"}, + {file = "pillow-10.4.0-cp39-cp39-win_arm64.whl", hash = "sha256:32cda9e3d601a52baccb2856b8ea1fc213c90b340c542dcef77140dfa3278a9e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:5b4815f2e65b30f5fbae9dfffa8636d992d49705723fe86a3661806e069352d4"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:8f0aef4ef59694b12cadee839e2ba6afeab89c0f39a3adc02ed51d109117b8da"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f4727572e2918acaa9077c919cbbeb73bd2b3ebcfe033b72f858fc9fbef0026"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff25afb18123cea58a591ea0244b92eb1e61a1fd497bf6d6384f09bc3262ec3e"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:dc3e2db6ba09ffd7d02ae9141cfa0ae23393ee7687248d46a7507b75d610f4f5"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:02a2be69f9c9b8c1e97cf2713e789d4e398c751ecfd9967c18d0ce304efbf885"}, + {file = "pillow-10.4.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0755ffd4a0c6f267cccbae2e9903d95477ca2f77c4fcf3a3a09570001856c8a5"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_10_15_x86_64.whl", hash = "sha256:a02364621fe369e06200d4a16558e056fe2805d3468350df3aef21e00d26214b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:1b5dea9831a90e9d0721ec417a80d4cbd7022093ac38a568db2dd78363b00908"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9b885f89040bb8c4a1573566bbb2f44f5c505ef6e74cec7ab9068c900047f04b"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:87dd88ded2e6d74d31e1e0a99a726a6765cda32d00ba72dc37f0651f306daaa8"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:2db98790afc70118bd0255c2eeb465e9767ecf1f3c25f9a1abb8ffc8cfd1fe0a"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f7baece4ce06bade126fb84b8af1c33439a76d8a6fd818970215e0560ca28c27"}, + {file = "pillow-10.4.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:cfdd747216947628af7b259d274771d84db2268ca062dd5faf373639d00113a3"}, + {file = "pillow-10.4.0.tar.gz", hash = "sha256:166c1cd4d24309b30d61f79f4a9114b7b2313d7450912277855ff5dfd7cd4a06"}, ] [package.extras] -docs = ["furo", "olefile", "sphinx (>=2.4)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinx-removed-in", "sphinxext-opengraph"] +docs = ["furo", "olefile", "sphinx (>=7.3)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"] fpx = ["olefile"] mic = ["olefile"] tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"] @@ -3496,4 +3507,4 @@ test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", [metadata] lock-version = "2.0" python-versions = "^3.12" -content-hash = "8e8f6def767c1dec2efbcb1ebde1869d77490b9fe4c11277da521a56b8eeb1ab" +content-hash = "40151747be7ccbaf8d89b9d49c90416afcd1483bfd8a62f14de0dbc693aa8df2" diff --git a/pyproject.toml b/pyproject.toml index 0cf815b..2b3a04a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ great-tables = "^0.9.0" geopandas = "^0.14.4" folium = "^0.17.0" selenium = "^4.22.0" +pillow = "^10.4.0" [tool.poetry.group.dev.dependencies] From 04bbfb09a5ad1742f3e742463480280922cedb92 Mon Sep 17 00:00:00 2001 From: Marty Oehme Date: Wed, 3 Jul 2024 20:27:49 +0200 Subject: [PATCH 10/10] Fix map display issues on blog output --- index.qmd | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/index.qmd b/index.qmd index c143cd7..2d63d6d 100644 --- a/index.qmd +++ b/index.qmd @@ -1,5 +1,7 @@ --- title: Nuclear Explosions +subtitle: "Using python polars and seaborn to visualize global detonations" +description: "Using python polars and seaborn to visualize global detonations" references: - type: techreport id: Bergkvist2000 @@ -16,6 +18,10 @@ references: title: "Nuclear Explosions 1945 - 1998" page: 1-42 issn: 1104-9154 +pubDate: "2024-07-03T18:36:26" +weight: 10 +tags: + - python --- ```{python} @@ -45,7 +51,7 @@ country_colors = { ```{python} # | label: data-prep -# | echo: false +# | code-fold: true schema_overrides = ( { col: pl.Categorical @@ -349,8 +355,12 @@ Finally, let's view a map of the world with the explosions marked, separated by ::: {.content-visible when-format="html"} Hovering over individual explosions will show their year while a click will open more information in a panel. +The map can be seen in @fig-worldmap-html. +::: + +::: {.content-visible unless-format="html"} +The map can be seen in @fig-worldmap-static. ::: -The map can be seen in @fig-worldmap. ```{python} # | label: worldmap-setup @@ -409,29 +419,44 @@ for country in country_colors.keys(): folium.LayerControl().add_to(m) ``` -::: {#fig-worldmap} - -:::: {.content-visible when-format="html"} +::: {.content-visible when-format="html"} ```{python} -# | label: worldmap-html +# | label: fig-worldmap-html +# | fig-cap: World map of nuclear explosions, 1945-98 m ``` -:::: +::: -:::: {.content-visible unless-format="html" width=80%} +::: {.content-visible unless-format="html" width=80%} ```{python} -# | label: worldmap-non-html +# | label: fig-worldmap-static +# | fig-cap: World map of nuclear explosions, 1945-98 # ENSURE SELENIUM IS INSTALLED -m.png_enabled = True -m +from PIL import Image +from IPython.display import Image as IImage +import io +img = m._to_png() + +bimg = io.BytesIO(img) +Image.open(bimg).save("map.png") +IImage(url="map.png") ``` -:::: +::: -World map of nuclear explosions, 1945-98 +::: {.callout-warning .content-visible when-format="markdown"} +Interactive maps not working + +Unfortunately, as of right now folium maps rendered within a quarto document do +not seem to translate terribly well into an astro blog such as this. +This is why, for now, there is only a static image here. + +This is very sad, but for the time being feel free to download and peruse +the ipynb notebook [here](./index.ipynb), or the [pdf](./index.pdf) +or [docx](./index.docx) versions. ::: While there are undoubtedly more aspects of the data that provide interesting