diff --git a/docs/source/_static/custom.css b/docs/source/_static/custom.css index 7a73e3ef..1cea4180 100644 --- a/docs/source/_static/custom.css +++ b/docs/source/_static/custom.css @@ -1,3 +1,8 @@ +:root { + /* Make the table hover background lighter */ + --pst-color-table-row-hover-bg: color-mix(in srgb, #6432bd 30%, transparent) !important; +} + .sidebar-logo { max-width: 100px; width: auto; @@ -11,50 +16,32 @@ table.docutils th { color: white; } -/* Make dropdown rounded pill and fit in cell */ -table.docutils td { - padding: 0 +/* make dropdown background transparent, and fit in cell */ +details.sd-dropdown summary.sd-card-header { + border: 0 !important; + background-color: transparent !important; + border-radius: 0 !important; } details.sd-dropdown { - margin-top: 0.4em; - margin-bottom: 0.4em !important; - border: 0; + box-shadow: none !important; } -details.sd-dropdown>.sd-card-header { - border: 0 !important; - background-color: var(--color-table-border) !important; - border-radius: 1em !important; - padding: 0 .4em 0 .8em !important; - margin: 0 0.8em 0 0.6em !important; +details.sd-dropdown summary.sd-summary-title { + width: fit-content; + padding: .2em .3em .2em .4em; + font-weight: normal !important; } -details.sd-card { - background-color: transparent; - box-shadow: none; +.sd-mb-3, .sd-my-3 { + margin: 0 !important; } -summary.sd-summary-title { - font-weight: normal !important; +summary.sd-card-header+div.sd-summary-content { + background-color: transparent !important; } - /* Center the contents of the second column */ -table.docutils td:nth-child(2) { +table.table td:nth-child(2) { text-align: center; } - -table.docutils td:first-child { - padding-left: 0.5rem; - padding-right: 0.5rem; -} - -/* Round corners of the first and last column headers */ -table.docutils th:first-child { - border-radius: 0.5rem 0 0 0; -} - -table.docutils th:last-child { - border-radius: 0 0.5rem 0 0; -} diff --git a/docs/source/about_the_name.md b/docs/source/about/about_the_name.md similarity index 100% rename from docs/source/about_the_name.md rename to docs/source/about/about_the_name.md diff --git a/docs/source/background.md b/docs/source/about/background.md similarity index 93% rename from docs/source/background.md rename to docs/source/about/background.md index c1052812..19567ea7 100644 --- a/docs/source/background.md +++ b/docs/source/about/background.md @@ -15,4 +15,19 @@ There are at least two significant advantages to having data transformations and ## Motivation for VegaFusion Vega makes it possible to create declarative JSON specifications of rich interactive visualizations that are fully self-contained. They can run entirely in a web browser without requiring access to an external database or a Python library like Pandas. -For datasets of a few tens of thousands rows or fewer, this architecture often results in extremely smooth and responsive interactivity. However, this architecture doesn't scale very well to datasets of hundreds of thousands of rows or more. This is the gap VegaFusion aims to fill. \ No newline at end of file +For datasets of a few tens of thousands rows or fewer, this architecture often results in extremely smooth and responsive interactivity. However, this architecture doesn't scale very well to datasets of hundreds of thousands of rows or more. This is the gap VegaFusion aims to fill. + +:::{toctree} +:maxdepth: 1 +:hidden: true +:caption: About + +self +how_it_works +technology +related_projects +about_the_name +license +stewardship +citation +::: \ No newline at end of file diff --git a/docs/source/citation.md b/docs/source/about/citation.md similarity index 100% rename from docs/source/citation.md rename to docs/source/about/citation.md diff --git a/docs/source/how_it_works.md b/docs/source/about/how_it_works.md similarity index 100% rename from docs/source/how_it_works.md rename to docs/source/about/how_it_works.md diff --git a/docs/source/license.md b/docs/source/about/license.md similarity index 100% rename from docs/source/license.md rename to docs/source/about/license.md diff --git a/docs/source/related_projects.md b/docs/source/about/related_projects.md similarity index 100% rename from docs/source/related_projects.md rename to docs/source/about/related_projects.md diff --git a/docs/source/stewardship.md b/docs/source/about/stewardship.md similarity index 57% rename from docs/source/stewardship.md rename to docs/source/about/stewardship.md index 5fb3168a..14801f70 100644 --- a/docs/source/stewardship.md +++ b/docs/source/about/stewardship.md @@ -1,9 +1,2 @@ # Stewardship VegaFusion was developed by [Jon Mease](https://jonmmease.dev/) and acquired by [Hex Technologies](https://hex.tech/) in 2022. Hex donated VegaFusion to the Vega Project in 2024 and continues to support its development and maintenance. - -```{image} https://user-images.githubusercontent.com/15064365/213193272-b9617431-84a0-4733-8b58-1309d25e925b.svg -:alt: Hex Logo -:width: 200px -:align: center -:target: https://hex.tech -``` diff --git a/docs/source/technology.md b/docs/source/about/technology.md similarity index 100% rename from docs/source/technology.md rename to docs/source/about/technology.md diff --git a/docs/source/community/governance.md b/docs/source/community/governance.md new file mode 100644 index 00000000..e1124d8d --- /dev/null +++ b/docs/source/community/governance.md @@ -0,0 +1,15 @@ +# Governance +The VegaFusion project is governed by the documents that reside in the [project-docs](https://github.com/vega/.github/tree/main/project-docs) directory of the Vega Organizational GitHub repository. + +:::{toctree} +:maxdepth: 1 +:hidden: true +:caption: "Community" + +self +Changelog +Source Code +Report an Issue +Start a Discussion +Code of Conduct +::: \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 00432cce..6d46937c 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -8,18 +8,32 @@ 'sphinx.ext.autodoc', 'sphinx.ext.napoleon', 'sphinx_design', + 'ablog', + 'sphinx.ext.intersphinx', ] # Theme settings -html_theme = 'furo' +html_theme = "pydata_sphinx_theme" html_static_path = ['_static'] html_logo = "_static/VegaFusionLogo-Color.svg" html_favicon = "_static/favicon.ico" +# Add custom CSS +html_css_files = [ + 'custom.css', +] + _social_img = "https://vegafusion.io/_static/vegafusion_social.png" _description = "VegaFusion provides serverside scaling for Vega visualizations" _title = "VegaFusion" +# -- Blog configuration ------------------------------------------------------ +blog_baseurl = "https://vegafusion.io" # Replace with your actual base URL +blog_post_pattern = "posts/*/*" +blog_path = "blog" +blog_title = "VegaFusion Blog" +templates_path = ['_templates'] + # MyST settings myst_enable_extensions = [ "colon_fence", # Allow ::: for admonitions @@ -29,7 +43,3 @@ "attrs_inline", # Inline attributes ] -# Add custom CSS -html_css_files = [ - 'custom.css', -] \ No newline at end of file diff --git a/docs/source/chart_state.md b/docs/source/features/chart_state.md similarity index 100% rename from docs/source/chart_state.md rename to docs/source/features/chart_state.md diff --git a/docs/source/column_usage.md b/docs/source/features/column_usage.md similarity index 100% rename from docs/source/column_usage.md rename to docs/source/features/column_usage.md diff --git a/docs/source/features/features.md b/docs/source/features/features.md new file mode 100644 index 00000000..df3365f8 --- /dev/null +++ b/docs/source/features/features.md @@ -0,0 +1,18 @@ +# Features + +VegaFusion provides a variety of low-level features that can be used to build higher-level Vega systems that need to scale to larger datasets than Vega handles natively. + +```{toctree} +:maxdepth: 1 +:caption: Features +:hidden: true + +column_usage +transform_data +transform_spec +transform_extract +chart_state +inline_datasets +grpc +vega-lite +``` diff --git a/docs/source/grpc.md b/docs/source/features/grpc.md similarity index 100% rename from docs/source/grpc.md rename to docs/source/features/grpc.md diff --git a/docs/source/inline_datasets.md b/docs/source/features/inline_datasets.md similarity index 100% rename from docs/source/inline_datasets.md rename to docs/source/features/inline_datasets.md diff --git a/docs/source/transform_data.md b/docs/source/features/transform_data.md similarity index 100% rename from docs/source/transform_data.md rename to docs/source/features/transform_data.md diff --git a/docs/source/transform_extract.md b/docs/source/features/transform_extract.md similarity index 100% rename from docs/source/transform_extract.md rename to docs/source/features/transform_extract.md diff --git a/docs/source/transform_spec.md b/docs/source/features/transform_spec.md similarity index 100% rename from docs/source/transform_spec.md rename to docs/source/features/transform_spec.md diff --git a/docs/source/vega-lite.md b/docs/source/features/vega-lite.md similarity index 100% rename from docs/source/vega-lite.md rename to docs/source/features/vega-lite.md diff --git a/docs/source/governance.md b/docs/source/governance.md deleted file mode 100644 index 4553dfe3..00000000 --- a/docs/source/governance.md +++ /dev/null @@ -1,2 +0,0 @@ -# Governance -The VegaFusion project is governed by the documents that reside in the [project-docs](https://github.com/vega/.github/tree/main/project-docs) directory of the Vega Organizational GitHub repository. \ No newline at end of file diff --git a/docs/source/index.md b/docs/source/index.md index 3ab3894e..7749f0b1 100644 --- a/docs/source/index.md +++ b/docs/source/index.md @@ -24,52 +24,26 @@ The VegaFusion project provides Rust, Python, and JavaScript libraries for analy If you've arrived here looking for information on how to scale Vega-Altair visualizations to support larger datasets, see the Vega-Altair documentation on the [`"vegafusion"` data transformer](https://altair-viz.github.io/user_guide/large_datasets.html#vegafusion-data-transformer). ::: -```{toctree} -:maxdepth: 1 -:caption: Features - -column_usage -transform_data -transform_spec -transform_extract -chart_state -inline_datasets -grpc -vega-lite -``` +# Python Installation -```{toctree} -:maxdepth: 2 -:hidden: true -:caption: Vega Coverage +The VegaFusion Python package can be installed into a Python environment using pip -supported_transforms -supported_expressions +```bash +pip install vegafusion ``` -```{toctree} -:maxdepth: 2 -:hidden: true -:caption: About +or conda -background -how_it_works -technology -related_projects -about_the_name -license -stewardship -citation +```bash +conda install -c conda-forge vegafusion ``` ```{toctree} :maxdepth: 1 :hidden: true -:caption: "Community" -Source Code -Report an Issue -Start a Discussion -Code of Conduct -governance +features/features +Vega Coverage +About +Community ``` diff --git a/docs/source/posts/2022/2022-01-19_Release_0.0.1.md b/docs/source/posts/2022/2022-01-19_Release_0.0.1.md new file mode 100644 index 00000000..be1d50af --- /dev/null +++ b/docs/source/posts/2022/2022-01-19_Release_0.0.1.md @@ -0,0 +1,7 @@ +--- +date: 2022-01-19 +category: Release +--- + +# Announcing VegaFusion 0.0.1 +The first public version of VegaFusion has been released as 0.0.1. Full project announcement to follow! \ No newline at end of file diff --git a/docs/source/posts/2022/2022-01-27_ProjectAnnouncement.md b/docs/source/posts/2022/2022-01-27_ProjectAnnouncement.md new file mode 100644 index 00000000..0193d501 --- /dev/null +++ b/docs/source/posts/2022/2022-01-27_ProjectAnnouncement.md @@ -0,0 +1,179 @@ +--- +date: 2022-01-27 +category: Release +author: Jon Mease +--- + +# Announcing VegaFusion 0.1 +**Server-side acceleration for the Vega visualization grammar with Rust and WebAssembly** + +By: Jon Mease + +--- + +Today, I'm happy to announce a new open source project I've been working on since stepping down as Plotly's Chief Scientist this past fall. It's called VegaFusion. + +VegaFusion provides server-side acceleration for the [Vega](https://vega.github.io/) visualization grammar. While not limited to Python, the initial application of VegaFusion is the acceleration of the [Altair](https://altair-viz.github.io/) Python interface to [Vega-Lite](https://vega.github.io/vega-lite/). Its design was motivated by years of experience working on visualization libraries ([Plotly](https://plotly.com/python/), [HoloViews](https://holoviews.org/), and [Datashader](https://datashader.org/)) and dashboard technologies ([Dash](https://dash.plotly.com/), [Jupyter Widgets](https://ipywidgets.readthedocs.io/en/latest/index.html), and [Panel](https://panel.holoviz.org/)). + +## Quickstart: Accelerate Altair in Jupyter +VegaFusion can be used to provide server-side acceleration for Altair visualizations when displayed in Jupyter contexts (Classic notebook, JupyterLab, and Voila). It also eliminates the Altair `MaxRowsError` when generating a chart with over 5000 rows. + +First, install the `vegafusion-jupyter` package, along with `vega-datasets` for the example below. + +```bash +$ pip install "vegafusion-jupyter[embed]" vega-datasets +``` + +Then, open a Jupyter notebook (either the classic notebook, or a notebook inside JupyterLab), and run these two lines to import and enable the VegaFusion Altair renderer. + +```python +import vegafusion as vf +vf.jupyter.enable() +``` +VegaFusion will now be used to accelerate any Altair chart. For example, here's the [interactive average](https://altair-viz.github.io/gallery/selection_layer_bar_month.html) Altair gallery example. + +```python +import altair as alt +from vega_datasets import data + +source = data.seattle_weather() +brush = alt.selection(type='interval', encodings=['x']) + +bars = alt.Chart().mark_bar().encode( + x='month(date):O', + y='mean(precipitation):Q', + opacity=alt.condition(brush, alt.OpacityValue(1), alt.OpacityValue(0.7)), +).add_selection( + brush +) + +line = alt.Chart().mark_rule(color='firebrick').encode( + y='mean(precipitation):Q', + size=alt.SizeValue(3) +).transform_filter( + brush +) + +chart = alt.layer(bars, line, data=source) +chart +``` + + + + +Histogram binning, aggregation, selection filtering, and average calculations will now be evaluated in the Python kernel process with efficient parallelization, rather than in the single-threaded browser context. + +You can see that VegaFusion acceleration is working by noticing that the Python [kernel is running](https://experienceleague.adobe.com/docs/experience-platform/data-science-workspace/jupyterlab/overview.html?lang=en#kernel-sessions) as the selection region is created or moved. You can also notice the VegaFusion logo in the dropdown menu button. + + +## Vega Ecosystem Background +VegaFusion is designed to complement the Vega visualization ecosystem. In particular, the [Vega](https://vega.github.io/), [Vega-Lite](https://vega.github.io/vega-lite/), and [Altair](https://altair-viz.github.io/) projects. If you're not familiar with these projects, it will be helpful to take a few minutes to browse their documentation as background for understanding what VegaFusion adds. + +### Transforms and Signals +One powerful feature of the Vega visualization grammar is that it includes a rich collection of data manipulation functions called [transforms](https://vega.github.io/vega/docs/transforms/). Transforms have functionality that is similar to that provided by SQL queries or Pandas DataFrame operations, but they are specifically designed to cover data preprocessing tasks that are useful in constructing data visualizations. + +For additional flexibility, Vega also provides the concept of [signals](https://vega.github.io/vega/docs/signals/). These are scalar variables that can be constructed using the [Vega expression language](https://vega.github.io/vega/docs/expressions/), which is a subset of JavaScript. Transforms can accept and produce signals. + +There are at least two significant advantages to having data transformations and signals included in a visualization specification. + - First, it makes it possible for a visualization to accept raw data files as input and then perform its own data cleaning and manipulation. This often removes the need to generate temporary intermediary data files. + - Second, it enables higher-level libraries like Vega-Lite to automate the creation of rich interactive visualizations with features like [cross filtering](https://vega.github.io/vega-lite/examples/interactive_layered_crossfilter.html) and [drill down](https://altair-viz.github.io/gallery/select_detail.html). + +## Motivation for VegaFusion +Vega makes it possible to create declarative JSON specifications of rich interactive visualizations that are fully self-contained. They can run entirely in a web browser without requiring access to an external database or a Python library like Pandas. + +For datasets of a few tens of thousands rows or fewer, this architecture often results in extremely smooth and responsive interactivity. However, this architecture doesn't scale very well to datasets of hundreds of thousands of rows or more. This is the gap VegaFusion aims to fill. + + +## How VegaFusion Works +VegaFusion has two primary components: The Planner and the Runtime. + +### Planner +The Planner starts with an arbitrary Vega specification (typically generated by Vega-Lite, but this is not a requirement). The Planner's job is to partition the specification into two valid Vega specifications, one that will execute in the browser with the Vega JavaScript library, and one that will execute on the server with the VegaFusion Runtime. + +VegaFusion does not (yet) provide full coverage of all of Vega's transforms and all of the features of the Vega expression language. The planner uses information about which transforms and expression functions are supported to make decisions about which parts of the original Vega specification can be included in the resulting server specification. The advantage of this approach is that VegaFusion can accept any Vega specification, and as more support is added over time, more of the input specification will be eligible for inclusion in the server specification. + +Along with the client and server specifications, the planner also creates a communication plan. The communication plan determines which datasets and signals must be passed from server to client, and from client to server in order for the interactive behavior of the original specification to be preserved. + +### Runtime +After planning, the server specification is compiled into a VegaFusion task graph. The job of the Runtime is to calculate the value of requested nodes within a task graph. + +A task graph includes the values of the root nodes (those with no parents), but it does not include the values of any of the interior nodes (those with parents). Each node in the task graph is a pure function of the values of its parents. This enables the Runtime to calculate the value of any node in the Task graph from the specification, while keeping the overall task graph size small enough to be efficiently transferred between the client and the server. The Runtime uses fingerprinting and precise caching to avoid repeated calculations of the same nodes. The cache is "precise" in the sense that cached values can be shared across visualizations that share a common substructure, even if the full specifications are not identical. + +## Technology +### VegaFusion Technology Stack +VegaFusion uses a fairly diverse technology stack. The Planner and Runtime are both implemented in Rust. + +In the context of `vegafusion-jupyter`, the Planner is compiled to WebAssembly using [wasm-pack](https://github.com/rustwasm/wasm-pack) and wrapped in a TypeScript API using [wasm-bindgen](https://github.com/rustwasm/wasm-bindgen). This TypeScript API is used to integrate the WebAssembly library into the VegaFusion Jupyter Widget. + +The Runtime is wrapped in a Python API using [PyO3](https://github.com/PyO3/pyo3), resulting in the `vegafusion-python` package. The `vegafusion-jupyter` package is used to integrate `vegafusion-python` with Altair and the Python portion of VegaFusion Jupyter Widget. + +The Task Graph specifications are defined as protocol buffer messages. The [prost](https://github.com/tokio-rs/prost) library is used to generate Rust data structures from these protocol buffer messages. When Arrow tables appear as task graph root values, they are serialized inside the protocol buffer specification using the [Apache Arrow IPC format](https://arrow.apache.org/docs/format/Columnar.html#serialization-and-interprocess-communication-ipc). The binary representation of the task graph protocol buffer message is what is transferred across the Jupyter Comms protocol. + +VegaFusion Jupyter Architecture Diagram + +### DataFusion integration +[Apache Arrow DataFusion](https://github.com/apache/arrow-datafusion) is an SQL compatible query engine that integrates with the Rust implementation of Apache Arrow. VegaFusion uses DataFusion to implement many of the Vega transforms, and it compiles the Vega expression language directly into the DataFusion expression language. In addition to being really fast, a particularly powerful characteristic of DataFusion is that it provides many interfaces that can be extended with custom Rust logic. For example, VegaFusion defines many custom UDFs that are designed to implement the precise semantics of the Vega expression language and the Vega expression functions. + +DataFusion Logo + +## Examples +VegaFusion is compatible with all of the examples in the [Altair gallery](https://altair-viz.github.io/gallery/index.html). Here are a few additional examples that demonstrate the benefits of server-side acceleration. + +### Large Altair Gallery Examples +The following examples are modifications of Altair Gallery examples that use larger datasets. Each example displays the same plot with plain Altair and with Altair + VegaFusion to make is easy to compare performance. You can try them out using the binder links below. + +#### Interactive Crossfilter + + - [Notebook file](https://github.com/vegafusion/demos/blob/main/notebooks/large_altair_examples/flights_crossfilter.ipynb) + - [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/vegafusion/demos/HEAD?urlpath=voila%2Frender%2Fnotebooks%2Flarge_altair_examples%2Fflights_crossfilter.ipynb) - Voila Dashboard + - [![Binder](https://mybinder.org/badge_logo.svg)](https://gesis.mybinder.org/binder/v2/gh/vegafusion/demos/93a9a68100de0d6282b114f8cf01cbb30c672267?urlpath=lab%2Ftree%2Fnotebooks%2Flarge_altair_examples%2Fflights_crossfilter.ipynb) - Jupyterlab + + + +#### Interactive Average + + - [Notebook file](https://github.com/vegafusion/demos/blob/main/notebooks/large_altair_examples/interactive_average.ipynb) + - [![Binder](https://mybinder.org/badge_logo.svg)](https://mybinder.org/v2/gh/vegafusion/demos/HEAD?urlpath=voila%2Frender%2Fnotebooks%2Flarge_altair_examples%2Finteractive_average.ipynb) - Voila Dashboard + - [![Binder](https://mybinder.org/badge_logo.svg)](https://gesis.mybinder.org/binder/v2/gh/vegafusion/demos/93a9a68100de0d6282b114f8cf01cbb30c672267?urlpath=lab%2Ftree%2Fnotebooks%2Flarge_altair_examples%2Finteractive_average.ipynb) - JupyterLab + + + +### 10 Million Taxi Rides +This example creates a dashboard to explore 10 million taxi rides in New York City from January 2015. It supports zoom-based rasterization of the pickup locations (similar to [Datashader](https://datashader.org/)), and combines that with cross filtering across a histogram of trip distances and a heatmap of tip rate by hour and day of the week. + +See the [PyViz documentation example](https://examples.pyviz.org/nyc_taxi/nyc_taxi.html) for more information on the dataset. + + - [Notebook file](https://github.com/vegafusion/demos/blob/main/notebooks/nyc_taxi/nyc_taxi.ipynb) + + + +## About the Name +There are two meanings behind the name "VegaFusion" + - It's a reference to the [Apache Arrow DataFusion](https://github.com/apache/arrow-datafusion) library described above + - Vega and Altair are named after stars, and stars are powered by nuclear fusion + +## Licensing and Funding +At least until a sustainable funding model is established, VegaFusion will be developed under the [AGPLv3 license](https://www.gnu.org/licenses/agpl-3.0.en.html). This is a copy-left license in the GPL family of licenses. As with all [OSI approved licenses](https://opensource.org/licenses/alphabetical), there are no restrictions on what code licensed under the AGPL can be used for. However, the requirements for what must be shared publicly are greater than for licenses that are more commonly used in the Python ecosystem like [Apache-2](https://opensource.org/licenses/Apache-2.0), [MIT](https://opensource.org/licenses/MIT), and [BSD-3](https://opensource.org/licenses/BSD-3-Clause). + +## Roadmap +This is only the beginning for VegaFusion. There are a lot of possible future directions, and many of them are detailed in the roadmap. + +## Learn more +Here are some ways that you can get in touch to learn more. + - Check out the code on [GitHub](https://github.com/hex-inc/vegafusion/) + - Start a discussion on [GitHub Discussions](https://github.com/hex-inc/vegafusion/discussions) + - And of course, if something doesn't work please [open an issue](https://github.com/hex-inc/vegafusion/issues) + +Thanks! diff --git a/docs/source/posts/2023/2023-01-21_Release_1.0.0.md b/docs/source/posts/2023/2023-01-21_Release_1.0.0.md new file mode 100644 index 00000000..0c73c8d4 --- /dev/null +++ b/docs/source/posts/2023/2023-01-21_Release_1.0.0.md @@ -0,0 +1,284 @@ +--- +date: 2023-01-21 +category: Release +author: Jon Mease +--- + +# Announcing VegaFusion 1.0 +**Mime renderer, transform evaluation, and a permissive license** + +By: Jon Mease + +--- + +The VegaFusion team is happy to announce the release of VegaFusion version 1.0. This release is the culmination of several months of work on both VegaFusion itself and its new dependency: [VlConvert](https://medium.com/@jonmmease/introducing-vlconvert-c763f0076e89). This version introduces a brand new mimetype based renderer that is compatible with many notebook and compute environments without requiring custom extensions. It also includes a new `transformed_data()` function that makes it possible to evaluate the data transforms associated with an Altair `Chart` and return the result as a pandas DataFrame. + +Finally, we're thrilled to announce that VegaFusion has been adopted by Hex and is now licensed under the same permissive BSD-3 license used by Vega, Vega-Lite, and Altair. + +## Background +VegaFusion scales Vega, Vega-Lite, and Altair visualizations by moving data transformations from the browser to a server (e.g. a Python kernel). + +The initial focus was on supporting complex interactive visualizations that require a live two-way connection between the client and server. This approach works well, but it relies on a custom Jupyter widget extension which adds complexity and limits the environments it can be deployed in. + +To reduce complexity while addressing the more common scenario of non-interactive charts, VegaFusion 1.0 introduces an entirely new renderer that does not require any custom extensions but instead leverages the built-in rendering capabilities of modern notebooks and compute environments. + +## VegaFusion mime renderer +Altair comes with a default limit of 5,000 input rows to avoid crashing the user's browser. Because all data transformations are applied in the browser by the Vega JavaScript library, the full input dataset must be loaded into the browser. The VegaFusion mime renderer performs most common Vega transforms in the Python kernel, and so the row limit is applied to the transformed data rather than the input data. When the chart includes any form of aggregation, the transformed dataset typically has many fewer rows than the input dataset. + +To use the VegaFusion mime renderer, first install the `vegafusion` Python package with the `embed` extras enabled: + +```bash +pip install -U "vegafusion[embed]" +``` + +Then open a Jupyter notebook (either the classic notebook or a notebook inside JupyterLab), and create an Altair histogram of a 1 million row flights dataset + +```python +import pandas as pd +import altair as alt + +flights = pd.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/flights_1m.parquet" +) + +delay_hist = alt.Chart(flights).mark_bar().encode( + alt.X("delay", bin=alt.Bin(maxbins=30)), + alt.Y("count()") +) +delay_hist +``` +``` +--------------------------------------------------------------------------- +MaxRowsError Traceback (most recent call last) +... +MaxRowsError: The number of rows in your dataset is greater than the maximum allowed (5000). For information on how to plot larger datasets in Altair, see the documentation +``` + +As expected, this results in an Altair `MaxRowsError`. The VegaFusion mime renderer can be used to overcome this limitation by performing data intensive transforms (e.g. filtering, binning, aggregation, etc.) in the Python kernel before the resulting data is sent to the web browser. + +Run these two lines to import and enable the new VegaFusion mime renderer: + +```python +import vegafusion as vf +vf.enable() +``` + +Now the chart displays quickly without errors: +``` +delay_hist +``` +![Flight Delay Histogram](https://user-images.githubusercontent.com/15064365/209973961-948b9d10-4202-4547-bbc8-d1981dcc8c4e.png) + +## Transform evaluation + +VegaFusion now supports extracting the transformed data for an Altair Chart using the `vegafusion.transformed_data()` function. This is particularly useful when building a chart that includes a pipeline of transforms, as it's now possible to see the intermediate results of each transform step. + +### Example: Top K +Here is an example, based on the [Top-K plot with Others](https://altair-viz.github.io/gallery/top_k_with_others.html) example from the Altair documentation, of how `transformed_data()` can be helpful when building a complex chart. + +First, create an Altair `Chart` wrapping the data source URL. +```python +import altair as alt +import vegafusion as vf + +source = "https://cdn.jsdelivr.net/npm/vega-datasets@v1.29.0/data/movies.json" +chart = alt.Chart(source) +``` + +The `transformed_data()` function can be used on this empty chart to access a preview of the data that is available at the URL. Here the `row_limit` argument is used to limit the result to 3 rows and the DataFrame is transposed to make it easier to read. + +```python +vf.transformed_data(chart, row_limit=3).T +``` + +| | 0 | 1 | 2 | +|:-----------------------|:---------------|:-----------------------|:---------------------------| +| Title | The Land Girls | First Love, Last Rites | I Married a Strange Person | +| US_Gross | 146083 | 10876 | 203134 | +| Worldwide_Gross | 146083 | 10876 | 203134 | +| Production_Budget | 8000000 | 300000 | 250000 | +| Release_Date | Jun 12 1998 | Aug 07 1998 | Aug 28 1998 | +| MPAA_Rating | R | R | | +| Distributor | Gramercy | Strand | Lionsgate | +| IMDB_Rating | 6.1 | 6.9 | 6.8 | +| IMDB_Votes | 1071.0 | 207.0 | 865.0 | +| Major_Genre | | Drama | Comedy | +| Rotten_Tomatoes_Rating | nan | nan | nan | +| Source | | | | +| Creative_Type | | | | +| Director | | | | +| US_DVD_Sales | nan | nan | nan | +| Running_Time_min | nan | nan | nan | + +The first step of making this chart is to compute the average worldwide gross of all the movies for each director. This can be accomplished with the Altair [Aggregate Transform](https://altair-viz.github.io/user_guide/transform/aggregate.html). + +```python +chart = ( + alt.Chart(source) + .transform_aggregate( + aggregate_gross='mean(Worldwide_Gross)', + groupby=["Director"], + ) +) +vf.transformed_data(chart, row_limit=5) +``` + +| | Director | aggregate_gross | +|---:|:------------------|------------------:| +| 0 | | 3.59284e+07 | +| 1 | Christopher Nolan | 3.44251e+08 | +| 2 | Roman Polanski | 5.13407e+07 | +| 3 | Richard Fleischer | 2.27635e+07 | +| 4 | Blake Edwards | 5e+06 | + +Next, the directors are ranked by average gross in descending order. This can be accomplished with the Altair [Window Transform](https://altair-viz.github.io/user_guide/transform/window.html) + +```python +chart = ( + alt.Chart(source) + .transform_aggregate( + aggregate_gross='mean(Worldwide_Gross)', + groupby=["Director"], + ).transform_window( + rank='row_number()', + sort=[alt.SortField("aggregate_gross", order="descending")], + ) +) +vf.transformed_data(chart, row_limit=5) +``` + +| | Director | aggregate_gross | rank | +|---:|:----------------|------------------:|-------:| +| 0 | David Yates | 9.37984e+08 | 1 | +| 1 | James Cameron | 8.29781e+08 | 2 | +| 2 | Carlos Saldanha | 7.69293e+08 | 3 | +| 3 | Pete Docter | 7.31305e+08 | 4 | +| 4 | Andrew Stanton | 7.00319e+08 | 5 | + +Then, a new column is added that contains the director's name for the top 9 ranked directors and "All Others" for the remaining directors. This can be accomplished using the Altair [Calculate Transform](https://altair-viz.github.io/user_guide/transform/calculate.html). + +```python +chart = ( + alt.Chart(source) + .transform_aggregate( + aggregate_gross='mean(Worldwide_Gross)', + groupby=["Director"], + ).transform_window( + rank='row_number()', + sort=[alt.SortField("aggregate_gross", order="descending")], + ).transform_calculate( + ranked_director="datum.rank < 10 ? datum.Director : 'All Others'" + ) +) +vf.transformed_data(chart, row_limit=12) +``` +| | Director | aggregate_gross | rank | ranked_director | +|---:|:-----------------|------------------:|-------:|:------------------| +| 0 | David Yates | 9.37984e+08 | 1 | David Yates | +| 1 | James Cameron | 8.29781e+08 | 2 | James Cameron | +| 2 | Carlos Saldanha | 7.69293e+08 | 3 | Carlos Saldanha | +| 3 | Pete Docter | 7.31305e+08 | 4 | Pete Docter | +| 4 | Andrew Stanton | 7.00319e+08 | 5 | Andrew Stanton | +| 5 | David Slade | 6.88155e+08 | 6 | David Slade | +| 6 | George Lucas | 6.73577e+08 | 7 | George Lucas | +| 7 | Andrew Adamson | 6.43134e+08 | 8 | Andrew Adamson | +| 8 | Peter Jackson | 5.95566e+08 | 9 | Peter Jackson | +| 9 | Richard Marquand | 5.727e+08 | 10 | All Others | +| 10 | Eric Darnell | 5.66099e+08 | 11 | All Others | +| 11 | Roland Emmerich | 4.5506e+08 | 12 | All Others | + +Finally, this dataset is ready to be encoded as a bar mark: + +```python +import altair as alt +import vegafusion as vf + +vf.enable() + +source = "https://cdn.jsdelivr.net/npm/vega-datasets@v1.29.0/data/movies.json" +chart = ( + alt.Chart(source) + .transform_aggregate( + aggregate_gross='mean(Worldwide_Gross)', + groupby=["Director"], + ).transform_window( + rank='row_number()', + sort=[alt.SortField("aggregate_gross", order="descending")], + ).transform_calculate( + ranked_director="datum.rank < 10 ? datum.Director : 'All Others'" + ).mark_bar().encode( + x=alt.X("aggregate_gross:Q", aggregate="mean", title=None), + y=alt.Y( + "ranked_director:N", + sort=alt.Sort(op="mean", field="aggregate_gross", order="descending"), + title=None, + ), + ) +) +chart +``` + +![Top-K directors](https://user-images.githubusercontent.com/15064365/209851553-b6af2a14-affe-4a54-ba17-2590bdbc957d.png) + +The exact value of each bar can be accessed by applying `transformed_data()` to the final chart (which includes the implicit transforms in the bar mark encoding). + +```python +vf.transformed_data(chart) +``` + +| | ranked_director | mean_aggregate_gross | +|---:|:------------------|-----------------------:| +| 0 | David Yates | 9.37984e+08 | +| 1 | James Cameron | 8.29781e+08 | +| 2 | Carlos Saldanha | 7.69293e+08 | +| 3 | Pete Docter | 7.31305e+08 | +| 4 | Andrew Stanton | 7.00319e+08 | +| 5 | David Slade | 6.88155e+08 | +| 6 | George Lucas | 6.73577e+08 | +| 7 | Andrew Adamson | 6.43134e+08 | +| 8 | Peter Jackson | 5.95566e+08 | +| 9 | All Others | 8.87602e+07 | + +## Increased transform coverage +VegaFusion's coverage of Vega-Lite's transforms is not yet complete, but it's growing with each release. Support for the [Pivot](https://vega.github.io/vega-lite/docs/pivot.html), [Impute](https://vega.github.io/vega-lite/docs/impute.html), [Fold](https://vega.github.io/vega-lite/docs/fold.html), and [Sequence](https://vega.github.io/vega/docs/transforms/sequence/) transforms was added in this release. Here is the full set of supported Vega-Lite/Vega transforms: + + - [Aggregate](https://vega.github.io/vega-lite/docs/aggregate.html) + - [Bin](https://vega.github.io/vega-lite/docs/bin.html) + - [Calculate](https://vega.github.io/vega-lite/docs/calculate.html) + - [Collect](https://vega.github.io/vega/docs/transforms/collect/) + - [Extent](https://vega.github.io/vega/docs/transforms/extent/) + - [Filter](https://vega.github.io/vega-lite/docs/filter.html) + - [Fold](https://vega.github.io/vega-lite/docs/fold.html) + - [Identifier](https://vega.github.io/vega/docs/transforms/identifier/) + - [Impute](https://vega.github.io/vega-lite/docs/impute.html) + - [Join Aggregate](https://vega.github.io/vega-lite/docs/joinaggregate.html) + - [Pivot](https://vega.github.io/vega-lite/docs/pivot.html) + - [Project](https://vega.github.io/vega/docs/transforms/project/) + - [Sequence](https://vega.github.io/vega/docs/transforms/sequence/) + - [Stack](https://vega.github.io/vega-lite/docs/stack.html) + - [Time Unit](https://vega.github.io/vega-lite/docs/timeunit.html) + - [Window](https://vega.github.io/vega-lite/docs/window.html) + +More on the way! + +## Leveraging VlConvert + +Both the new mime renderer and the `transfomed_data()` function require the ability to convert Vega-Lite specifications (as produced by Altair) to Vega specifications (as consumed by VegaFusion). This functionality is provided by the Vega-Lite JavaScript library, which presents a challenge: How can we perform this conversion in Python without a web browser? + +To meet this challenge, we developed a new Python library called [VlConvert](https://github.com/vega/vl-convert). You can read all about VlConvert in its [announcement post](https://medium.com/@jonmmease/introducing-vlconvert-c763f0076e89), but in short it embeds the [Deno](https://deno.land/) JavaScript runtime in a Python library for the purpose of running the Vega-Lite JavaScript library. + +While motivated by the needs of VegaFusion, VlConvert turned out to be a great foundation for implementing static image export, as it is a simple Python package that doesn't require external dependencies on a web browser or Node.js. In fact, VlConvert has been adopted by the Vega community and will be the default image export engine for the coming release of Altair 5! + +## A new partnership and a new license +VegaFusion was first released in January 2022 under the copyleft AGPL license. I chose to use the AGPL license initially because I wanted to share the work with the Vega community, but I also wanted to leave open the possibility of building a company with a dual-license business model around the project. + +The day of the initial release, Barry McCardel (the CEO of [Hex Technologies](https://hex.tech/)) reached out to me, and we started talking about how VegaFusion could help scale the [Hex chart cell](https://learn.hex.tech/docs/logic-cell-types/display-cells/chart-cells) (which is itself built on Vega-Lite). I granted them a commercial license exception, and started working with the Hex team to integrate VegaFusion into Hex. After months of working together with Barry and the Hex team, and lots of conversations around the future of the project, we decided that the best path forward was for me to continue working on VegaFusion as a member of Hex team, and for VegaFusion to be re-licensed to a permissive license that is compatible with the rest of the Vega ecosystem. + +The end result is that VegaFusion 1.0 is licensed under the [BSD-3](https://opensource.org/licenses/BSD-3-Clause) license. Also, to make it easier to take advantage of Hex's testing infrastructure the VegaFusion repositories have been moved under the [hex-inc](https://github.com/hex-inc) GitHub organization. + +## Learn more +Check out these resources if you'd like to learn more: + - [VegaFusion Documentation](https://vegafusion.io/) + - [VegaFusion GitHub](https://github.com/hex-inc/vegafusion) + - [Report and Issue](https://github.com/hex-inc/vegafusion/issues) + - [Start a Discussions](https://github.com/hex-inc/vegafusion/discussions) diff --git a/docs/source/posts/2023/2023-03-25_Release_1.1.0.md b/docs/source/posts/2023/2023-03-25_Release_1.1.0.md new file mode 100644 index 00000000..b63e69fe --- /dev/null +++ b/docs/source/posts/2023/2023-03-25_Release_1.1.0.md @@ -0,0 +1,254 @@ +--- +date: 2023-03-25 +category: Release +author: Jon Mease +--- + +# Announcing VegaFusion 1.1 +**DuckDB and Polars support, Altair 5 compatibility, and lots of bug fixes** + +By: Jon Mease + +--- + +The VegaFusion team is happy to announce the release of version 1.1. In addition to the usual slew of bug fixes, this release includes support for evaluating Vega transforms in DuckDB, support for Polars and the DataFrame Interchange Protocol, and forward compatibility with the coming release of Altair 5. + +## DuckDB support +[DuckDB](https://duckdb.org/) is an in-process SQL OLAP query engine and database that provides bindings for a wide variety of languages, including Python. The VegaFusion 1.1 Python library now includes two forms of integration with DuckDB. First, it can use DuckDB in place of DataFusion to power Vega transforms over the pandas DataFrames that are referenced by Altair charts. Second, VegaFusion 1.1 makes it possible to reference externally defined DuckDB tables and views in Altair charts. + +### Vega transforms with DuckDB + +VegaFusion can now be configured to evaluate Vega transforms using the DuckDB Python library by calling `vegafusion.runtime.set_connection("duckdb")`. + +Once the DuckDB connection is enabled, pandas DataFrames referenced by Altair charts are automatically registered with DuckDB and Vega transforms are translated into DuckDB SQL queries. Here is a full example, adapted from the [2D Histogram Heatmap](https://altair-viz.github.io/gallery/histogram_heatmap.html) Altair gallery example. + +```python +import vegafusion as vf +import pandas as pd +import altair as alt + +# Configure DuckDB connection +vf.runtime.set_connection("duckdb") + +# Enable Mime Renderer +vf.enable() + +# Load 201k row version of the Vega movies dataset with pandas +movies = pd.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/movies_201k.parquet" +) + +# Create an Altair chart from the pandas DataFrame as usual. +# Binning and aggregation will be evaluated against the DataFrame with DuckDB +chart = alt.Chart(movies).mark_rect().encode( + alt.X('IMDB_Rating:Q', bin=alt.Bin(maxbins=60)), + alt.Y('Rotten_Tomatoes_Rating:Q', bin=alt.Bin(maxbins=40)), + alt.Color('count():Q', scale=alt.Scale(scheme='greenblue')) +) +chart +``` +![2D Histogram Heatmap](https://user-images.githubusercontent.com/15064365/226609213-2eba5e0b-1377-47d1-9ca9-32dc8f43fb8c.png) + +Additionally, the transformed data can be extracted as usual: +```python +vf.transformed_data(chart, row_limit=5).T +``` +| | 0 | 1 | 2 | 3 | 4 | +|:------------------------------------------|-----:|------:|------:|-------:|-------:| +| bin_maxbins_60_IMDB_Rating | 3.4 | 5.8 | 7 | 7 | 7.4 | +| bin_maxbins_60_IMDB_Rating_end | 3.6 | 6 | 7.2 | 7.2 | 7.6 | +| bin_maxbins_40_Rotten_Tomatoes_Rating | 60 | 25 | 85 | 80 | 80 | +| bin_maxbins_40_Rotten_Tomatoes_Rating_end | 65 | 30 | 90 | 85 | 85 | +| __count | 63 | 441 | 504 | 1260 | 1134 | + +The default DataFusion connection can be re-enabled by calling `vegafusion.runtime.set_connection("datafusion")` + +#### Performance benefit +DuckDB has the ability to perform queries against Pandas DataFrames without first serializing them to Arrow. This can result in 10x+ speedups when visualizing large Pandas DataFrames with the VegaFusion Mime Renderer. Presently, the VegaFusion Widget Renderer always serializes DataFrames to Arrow and writes them to disk, so significant performance gains are not expected for the Widget Renderer. + +### Access DuckDB tables +VegaFusion 1.1 also supports integration with external DuckDB connections, making it possible to reference DuckDB tables and views from Altair charts. To begin, import `duckdb` and create a new connection. + +```python +import duckdb +conn = duckdb.connect() +``` + +Pass this DuckDB connection to `vegafusion.runtime.set_connection` (instead of the string `"duckdb"` as in the previous example). + +```python +vf.runtime.set_connection(conn) +``` + +Next, use the DuckDB connection to create a table or view. Here, the DuckDB `read_parquet` method is used to load the 201k row movies dataset, and a DuckDB query is used to filter NULL values. The result of this query is registered as a table named `movies`. + +```python +relation = conn.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/movies_201k.parquet" +) + +relation.query("tbl", """ + SELECT * FROM tbl + WHERE Rotten_Tomatoes_Rating IS NOT NULL AND Imdb_Rating IS NOT NULL +""").to_table("movies") +``` + +DuckDB tables and views registered with `conn` may be referenced from Altair charts with a special URL syntax using `table://` as the prefix. To reference the DuckDB table named `movies`, the Altair chart should be passed the url string `"table://movies"`. Here is a full example + +```python +import duckdb +import vegafusion as vf +import pandas as pd +import altair as alt + +# Create DuckDB connection +conn = duckdb.connect() + +# Pass DuckDB connection to VegaFusion's set_connection method +vf.runtime.set_connection(conn) + +# Enable Mime Renderer +vf.enable() + +# Read parquet file using the DuckDB connection +relation = conn.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/movies_201k.parquet" +) + +# Filter NULL values and register the result as a table named "movies" +relation.query("tbl", """ + SELECT * FROM tbl + WHERE Rotten_Tomatoes_Rating IS NOT NULL AND Imdb_Rating IS NOT NULL +""").to_table("movies") + +# Create an Altair chart that references the registered DuckDB table +chart = alt.Chart("table://movies").mark_rect().encode( + alt.X('IMDB_Rating:Q', bin=alt.Bin(maxbins=60)), + alt.Y('Rotten_Tomatoes_Rating:Q', bin=alt.Bin(maxbins=40)), + alt.Color('count():Q', scale=alt.Scale(scheme='greenblue')) +) +chart +``` +![2D Histogram Heatmap](https://user-images.githubusercontent.com/15064365/226609213-2eba5e0b-1377-47d1-9ca9-32dc8f43fb8c.png) + +See the DuckDB connection docs for more information. + +## Polars support + +[Polars](https://www.pola.rs/) describes itself as a "Lightning-fast DataFrame library for Rust and Python". Polars has quickly [gained popularity](https://star-history.com/#pola-rs/polars&Date) as a faster alternative to pandas that also supports larger datasets. + +VegaFusion's new Polars integration makes it possible to input and output Polars DataFrames without conversion through pandas. + +### Polars as Input +Here is a full example that uses Polars to read the 201k row movies dataset from a remote parquet file. The DataFrame's `filter` method is then used to remove rows with NULL movie rating values. This filtered DataFrame is passed as the input to an Altair chart. + +```python +import polars as pl +import vegafusion as vf +import altair as alt + +# Enable Mime Renderer +vf.enable() + +# Load 201k movies parquet dataset +source = pl.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/movies_201k.parquet" +) + +# Fitler out rows with null ratings +source = source.filter( + pl.col("IMDB_Rating").is_not_null() & pl.col("Rotten_Tomatoes_Rating").is_not_null() +) + +# Create chart +chart = alt.Chart(source).mark_circle().encode( + alt.X('IMDB_Rating:Q', bin=True), + alt.Y('Rotten_Tomatoes_Rating:Q', bin=True), + size='count()' +) +chart +``` + +![visualization](https://user-images.githubusercontent.com/15064365/226755752-c88e37a5-80a8-4ccd-85d0-554974079cb6.png) + +### Polars as Output +When a Chart that references a Polars DataFrame is passed to `vegafusion.transformed_data`, the result will also be a Polars DataFrame. Here is a Polars version of the example from the Transformed Data section. + +```python +import altair as alt +import vegafusion as vf +import polars a pl + +# Enable mime renderer +vf.enable() + +# Load 201k movies parquet dataset with Polars +source = pl.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/movies_201k.parquet" +) + +# Build chart +chart = ( + alt.Chart(source) + .transform_aggregate( + aggregate_gross='mean(Worldwide_Gross)', + groupby=["Director"], + ).transform_window( + rank='row_number()', + sort=[alt.SortField("aggregate_gross", order="descending")], + ).transform_calculate( + ranked_director="datum.rank < 10 ? datum.Director : 'All Others'" + ).mark_bar().encode( + x=alt.X("aggregate_gross:Q", aggregate="mean", title=None), + y=alt.Y( + "ranked_director:N", + sort=alt.Sort(op="mean", field="aggregate_gross", order="descending"), + title=None, + ), + ) +).properties( + title="Top Directors by Average Worldwide Gross", +) +chart +``` +![visualization (4)](https://user-images.githubusercontent.com/15064365/209851553-b6af2a14-affe-4a54-ba17-2590bdbc957d.png) + +Now retrieve the chart's transformed data as a Polars DataFrame. + +```python +transformed = vf.transformed_data(chart) +print(type(transformed)) +transformed +``` +``` + +``` +shape: (10, 2) + +| ranked_director (str) | mean_aggregate_gross (f64) | +|:----------------------|---------------------------:| +| David Yates | 9.37984e+08 | +| James Cameron | 8.29781e+08 | +| Carlos Saldanha | 7.69293e+08 | +| Pete Docter | 7.31305e+08 | +| Andrew Stanton | 7.00319e+08 | +| David Slade | 6.88155e+08 | +| George Lucas | 6.73577e+08 | +| Andrew Adamson | 6.43134e+08 | +| Peter Jackson | 5.95566e+08 | +| All Others | 8.87602e+07 | + +See the Polars Integration docs for more information. + +## DataFrame Interchange Protocol +The "Polars as Input" workflow above is powered by a more general abstraction: The [DataFrame Interchange Protocol](https://data-apis.org/dataframe-protocol/latest/index.html). Along with Altair 5, VegaFusion 1.1 adds support for inputting any DataFrame object that supports this protocol including pyarrow Tables, Vaex DataFrames, cuDF DataFrames, and more. + +## Altair 5 Compatibility +Along with Altair 5.0.0rc1, VegaFusion 1.1 updates Vega-Lite from 4.17.0 to 5.6.1. This is a significant update that enables many new features for Altair users. For more info, see the [Altair 5 docs](https://altair-viz.github.io/). + +## Learn more +Check out these resources if you'd like to learn more: + - [VegaFusion Documentation](https://vegafusion.io/) + - [VegaFusion GitHub](https://github.com/hex-inc/vegafusion) + - [Report and Issue](https://github.com/hex-inc/vegafusion/issues) + - [Start a Discussions](https://github.com/hex-inc/vegafusion/discussions) \ No newline at end of file diff --git a/docs/source/posts/2023/2023-04-12_Release_1.2.0.md b/docs/source/posts/2023/2023-04-12_Release_1.2.0.md new file mode 100644 index 00000000..9b402b27 --- /dev/null +++ b/docs/source/posts/2023/2023-04-12_Release_1.2.0.md @@ -0,0 +1,123 @@ +--- +date: 2023-04-12 +category: Release +author: Jon Mease +--- + +# Announcing VegaFusion 1.2 +**Pre-transform and Save Altair Charts** + +By: Jon Mease + +--- + +The VegaFusion team is happy to announce the release of version 1.2. Along with usual bug fixes and updates to the core Arrow and DataFusion dependencies, this release includes support for saving pre-transformed Altair charts to HTML, JSON, PNG, and SVG files. + +## Standard Altair save +Altair Charts provide a [`Chart.save()`](https://altair-viz.github.io/user_guide/saving_charts.html) method that may be used to save Altair charts to HTML, JSON, or static image (PNG or SVG) files. When the chart references a pandas DataFrame, the full DataFrame is serialized to JSON and included in the chart specification that is saved. As dataset sizes get larger, the time it takes to save these files increases rapidly, and for the case of HTML and JSON formats, the file size increases rapidly as well. + +VegaFusion's new save functions improve the situation by pre-applying data transformations and removing unused columns before inlining the resulting data in the chart specification for saving. + +## HTML performance comparison +Here are two examples of the benefits of the new `save_html` function compared to the standard Altair `Chart.save` method. + +### With aggregations +Let's save a one million row histogram chart to an HTML file. First create and display the histogram with the VegaFusion Mime Renderer enabled. + +``` +import pandas as pd +import altair as alt +import vegafusion as vf +vf.enable() + +flights = pd.read_parquet( + "https://vegafusion-datasets.s3.amazonaws.com/vega/flights_1m.parquet" +) + +delay_hist = alt.Chart(flights).mark_bar().encode( + alt.X("delay", bin=alt.Bin(maxbins=30)), + alt.Y("count()") +) +delay_hist +``` + +![histogram](https://user-images.githubusercontent.com/15064365/230728055-64b05777-9925-4711-b7c4-3e4cc52e1c83.png) + +Next, save the chart to an HTML file with the standard Altair `Chart.save` method. + +```python +%%time +delay_hist.save("delay_hist_standard.html") +``` +``` +CPU times: user 5.72 s, sys: 388 ms, total: 6.11 s +Wall time: 6.18 s +``` + +This results in a ~116MB file, as all columns from the entire million row dataset are included in the HTML file. It also takes over 6 seconds to perform the save. Now, use VegaFusion's `save_html` function. + +```python +%%time +vf.save_html(delay_hist, "delay_hist_vf.html") +``` +``` +CPU times: user 227 ms, sys: 119 ms, total: 347 ms +Wall time: 427 ms +``` +The resulting file is now only ~5KB, because only ~30 rows have been included (one per histogram bin). It also takes under half a second to perform the save. + +### Without aggregations +The benefits of VegaFusion's new save functions are most dramatic for charts that use aggregations. Even so, VegaFusion's ability to remove unused columns still results in smaller file sizes for unaggregated charts, especially when the input datasets have many unused columns. + +Here's an example scatter chart using the movies dataset from the vega-datasets package, which has 3201 rows and 16 columns. + +```python +import vegafusion as vf +import altair as alt +from vega_datasets import data + +source = data.movies() + +scatter_chart = alt.Chart(source).mark_point().encode( + alt.X("IMDB_Rating:Q"), + alt.Y("Rotten_Tomatoes_Rating:Q"), +) +scatter_chart +``` +![movies scatter chart](https://user-images.githubusercontent.com/15064365/230728307-6d8a2c6c-e45e-483b-a207-3abf0c26449b.png) + +Save the chart to an HTML file with Altair's standard `Chart.save` method. + +```python +%%time +scatter_chart.save("scatter_standard.html") +``` +``` +CPU times: user 69.7 ms, sys: 14.7 ms, total: 84.4 ms +Wall time: 85 ms +``` +This results in a 1.4MB file and takes ~80ms. + +Now, save the file with VegaFusion's `save_html` function. + +```python +%%time +vf.save_html(scatter_chart, "scatter_vf.html") +``` +``` +CPU times: user 59.8 ms, sys: 15.2 ms, total: 75 ms +Wall time: 57.1 ms +``` + +This results in a 125K file and takes under 60ms. The file is more than 10x smaller because it only includes data for the two columns that are referenced by the scatter plot. + + +## Updates to Arrow and DataFusion dependencies +VegaFusion 1.2 updates the dependency on arrow-rs to [version 36.0](https://github.com/apache/arrow-rs/blob/master/CHANGELOG-old.md#3400-2023-02-24) and DataFusion to [version 22.0](https://github.com/apache/arrow-datafusion/blob/main/dev/changelog/22.0.0.md). + +## Learn more +Check out these resources if you'd like to learn more: + - [VegaFusion Documentation](https://vegafusion.io/) + - [VegaFusion GitHub](https://github.com/hex-inc/vegafusion) + - [Report and Issue](https://github.com/hex-inc/vegafusion/issues) + - [Start a Discussions](https://github.com/hex-inc/vegafusion/discussions) \ No newline at end of file diff --git a/docs/source/posts/2023/2023-06-10_Release_1.3.0.md b/docs/source/posts/2023/2023-06-10_Release_1.3.0.md new file mode 100644 index 00000000..d77c69d7 --- /dev/null +++ b/docs/source/posts/2023/2023-06-10_Release_1.3.0.md @@ -0,0 +1,94 @@ +--- +date: 2023-06-10 +category: Release +author: Jon Mease +--- + +# Announcing VegaFusion 1.3 +**Transformed data for compound charts, Java library** + +By: Jon Mease + +--- + +The VegaFusion team is happy to announce the release of version 1.3. Along with usual bug fixes and updates to the core Arrow and DataFusion dependencies, this release includes an enhancement to the `transformed_data` function to make it compatible with compound charts, adds DuckDB 0.8 support, and adds a new Java API. + +## Extract transformed data from compound Altair charts +The VegaFusion `transformed_data` function can be used to extract the transformed data from an Altair chart object as a pandas DataFrame. Prior to 1.3.0, `transformed_data` was only supported on simple charts that involve only a single mark. Now, compound layered and concatenated charts are supported as well. In the case of a compound chart, `transformed_data` will return a list of pandas DataFrame's rather than single DataFrame. + +### Example: Histogram with a Global Mean Overlay + +Here is an example based on the [Histogram with a Global Mean Overlay](https://altair-viz.github.io/gallery/histogram_with_a_global_mean_overlay.html) example from the Altair gallery. + +```python +import altair as alt +import vegafusion as vf +from vega_datasets import data + +vf.enable() + +source = data.movies.url + +base = alt.Chart(source) + +bar = base.mark_bar().encode( + alt.X('IMDB_Rating:Q').bin().axis(None), + y='count()' +) + +rule = base.mark_rule(color='red').encode( + x='mean(IMDB_Rating):Q', + size=alt.value(5) +) + +chart = bar + rule +chart +``` + +![Rule on Histogram](https://github.com/vegafusion/vegafusion.github.io/assets/15064365/645a52a1-0b37-4b0a-9da1-1f7d348c1527) + +Because `chart` is composed of two subcharts, the result of `vf.transformed_data` will be a list of two DataFrames. +```python +[hist_df, rule_df] = vf.transformed_data(chart) +hist_df +``` +``` +| | bin_maxbins_10_IMDB_Rating | bin_maxbins_10_IMDB_Rating_end | __count | +|---:|-----------------------------:|---------------------------------:|----------:| +| 0 | 6 | 7 | 985 | +| 1 | 3 | 4 | 100 | +| 2 | 7 | 8 | 741 | +| 3 | 5 | 6 | 633 | +| 4 | 8 | 9 | 204 | +| 5 | 2 | 3 | 43 | +| 6 | 4 | 5 | 273 | +| 7 | 9 | 10 | 4 | +| 8 | 1 | 2 | 5 | +``` + +```python +rule_df +``` +``` +| | mean_IMDB_Rating | +|---:|-------------------:| +| 0 | 6.28347 | +``` +## DuckDB 0.8 Support +VegaFusion's DuckDB integration has been updated to support DuckDB 0.8.0 + +## Java API +VegaFusion 1.3 may now be embedded in Java applications using the new `io.vegafusion.vegafusion` jar, which is published to [Maven Central](https://central.sonatype.com/artifact/io.vegafusion/vegafusion). + +This was accomplished by wrapping the VegaFusion Rust API with a JNI interface using the [`jni-rs` crate](https://github.com/jni-rs/jni-rs). + +## Updates to Arrow and DataFusion dependencies +VegaFusion 1.3 updates the dependency on arrow-rs to [version 39.0](https://github.com/apache/arrow-rs/blob/master/CHANGELOG-old.md#3900-2023-05-05) and DataFusion to [version 25.0](https://github.com/apache/arrow-datafusion/blob/main/dev/changelog/25.0.0.md). + +## Learn more +Check out these resources to learn more: + - [1.3.0 Changelog](https://github.com/hex-inc/vegafusion/releases/tag/v1.3.0) + - [VegaFusion Documentation](https://vegafusion.io/) + - [VegaFusion GitHub](https://github.com/hex-inc/vegafusion) + - [Report and Issue](https://github.com/hex-inc/vegafusion/issues) + - [Start a Discussions](https://github.com/hex-inc/vegafusion/discussions) \ No newline at end of file diff --git a/docs/source/posts/2023/2023-08-21_Release_1.4.0.md b/docs/source/posts/2023/2023-08-21_Release_1.4.0.md new file mode 100644 index 00000000..5b41216e --- /dev/null +++ b/docs/source/posts/2023/2023-08-21_Release_1.4.0.md @@ -0,0 +1,70 @@ +--- +date: 2023-08-21 +category: Release +author: Jon Mease +--- + +# Announcing VegaFusion 1.4 +**Improved Vega coverage, external data source foundations, extended architecture support** + +By: Jon Mease + +--- + +The VegaFusion team is happy to announce the release of version 1.4. Along with the usual bug fixes and updates to the core Arrow and DataFusion dependencies, this release improves coverage of Vega's features by supporting `q1`/`q3` aggregation functions and bitwise operators. It also lays important foundations for supporting external data sources and compute engines, and adds additional architectures for pip and conda packages. + +## Improved Vega coverage +VegaFusion 1.4 adds support for the `q1` and `q3` aggregation functions. This makes it possible for VegaFusion to evaluate all the transforms associated with a Vega-Lite boxplot. Here's a Vega-Altair example: + +```python +import altair as alt +from vega_datasets import data +import vegafusion as vf +vf.enable() + +source = data.cars() + +chart = alt.Chart(source).mark_boxplot(extent="min-max").encode( + alt.X("Miles_per_Gallon:Q").scale(zero=False), + alt.Y("Origin:N"), +) +chart +``` +![boxplot](https://github.com/vegafusion/vegafusion.github.io/assets/15064365/acf29249-314d-4582-8176-80110690d452) + +An easy way to see that the transforms are supported is to extract the transformed data with `vf.transformed_data`. + +```python +vf.transformed_data(chart) +``` +| | Origin | lower_box_Miles_per_Gallon | upper_box_Miles_per_Gallon | mid_box_Miles_per_Gallon | lower_whisker_Miles_per_Gallon | upper_whisker_Miles_per_Gallon | +|---:|:---------|-----------------------------:|-----------------------------:|---------------------------:|---------------------------------:|---------------------------------:| +| 0 | USA | 15 | 24 | 18.5 | 9 | 39 | +| 1 | Europe | 24 | 30.65 | 26.5 | 16.2 | 44.3 | +| 2 | Japan | 25.7 | 34.05 | 31.6 | 18 | 46.6 | + +In addition, the full complement of bitwise operators are now supported in the Vega expression language including `|`, `&`, `^`, `<<`, and `>>`. + +## External Data Source Foundations +VegaFusion 1.4 lays some important foundations toward the goal of supporting external data sources and compute engines. The `vegafusion.dataset.sql.SqlDataset` abstract class defines the interface for implementing SQL data sources in Python for any of VegaFusion's 10 supported SQL dialects. Implementations for DuckDB and Snowpark are also provided. In addition, the `vegafusion.dataset.DataFrameDataset` abstract class defines the interface for implementing VegaFusion's data transformations with external DataFrame libraries. Motivating examples include the future ability to dispatch VegaFusion data transformations to the [Ibis](https://ibis-project.org/) and [Polars](https://www.pola.rs/) Python libraries. + +In the coming release of Vega-Altair 5.1, it will be possible to pass implementations of `SqlDataset` and `DataFrameDataset` to Altair `Chart` objects instead of pandas DataFrames. Stay tuned for more information and examples after the release of Altair 5.1! + +## Extended Architecture Support +VegaFusion wheels are now built and published to PyPI for the aarch64 Linux architecture. In addition, conda-forge packages are now published for the Apple Silicon architecture. + +## Updates to Arrow and DataFusion dependencies +VegaFusion 1.4 updates the dependency on arrow-rs to [version 42.0.0](https://github.com/apache/arrow-rs/blob/master/CHANGELOG-old.md#4200-2023-06-16) and DataFusion to [version 27.0.0](https://github.com/apache/arrow-datafusion/blob/main/dev/changelog/27.0.0.md). + +## Looking ahead +The coming release of Vega-Altair 5.1 will include first-class integration with VegaFusion to support extracting transformed data from a chart with `chart.transformed_data()`. It will also include a `"vegafusion"` data transformer that will cause Altair to use VegaFusion to pre-evaluate data transformations and remove unused columns when saving or displaying charts. The timeline has not been decided on yet, but the plan is to eventually deprecate VegaFusion's `transformed_data` and `save_*` functions and the VegaFusion mime renderer in favor of the integrations built into Altair. + +Another near-term focus is on lowering the barrier to contributing to VegaFusion by adopting the [Pixi](https://prefix.dev/docs/pixi/overview) environment manager. + +## Learn more +Check out these resources to learn more: + - [1.4.0 Changelog](https://github.com/hex-inc/vegafusion/releases/tag/v1.4.0) + - [VegaFusion Documentation](https://vegafusion.io/) + - [VegaFusion GitHub](https://github.com/hex-inc/vegafusion) + - [Report and Issue](https://github.com/hex-inc/vegafusion/issues) + - [Start a Discussions](https://github.com/hex-inc/vegafusion/discussions) \ No newline at end of file diff --git a/docs/source/supported_expressions.md b/docs/source/vega_coverage/supported_expressions.md similarity index 99% rename from docs/source/supported_expressions.md rename to docs/source/vega_coverage/supported_expressions.md index a74148e6..7f315c49 100644 --- a/docs/source/supported_expressions.md +++ b/docs/source/vega_coverage/supported_expressions.md @@ -3,7 +3,7 @@ VegaFusion supports a subset of [Vega's expression language](https://vega.github.io/vega/docs/expressions/). Below is a detailed breakdown of supported expression features. :::{note} -When a Vega spec includes unsupported expressions, these expressions will be included in the client Vega spec for evaluation by the standard Vega library in the browser. See [How it works](./how_it_works.md) for more details. +When a Vega spec includes unsupported expressions, these expressions will be included in the client Vega spec for evaluation by the standard Vega library in the browser. See [How it works](../about/how_it_works.md) for more details. ::: > **Key**: diff --git a/docs/source/supported_transforms.md b/docs/source/vega_coverage/supported_transforms.md similarity index 97% rename from docs/source/supported_transforms.md rename to docs/source/vega_coverage/supported_transforms.md index 40cd64b0..e74e8957 100644 --- a/docs/source/supported_transforms.md +++ b/docs/source/vega_coverage/supported_transforms.md @@ -4,7 +4,7 @@ VegaFusion implements a subset of [Vega's transforms](https://vega.github.io/veg :::{note} -When a Vega spec includes unsupported transforms, these transforms will be included in the client Vega spec for evaluation by the standard Vega library in the browser. See [How it works](./how_it_works.md) for more details. +When a Vega spec includes unsupported transforms, these transforms will be included in the client Vega spec for evaluation by the standard Vega library in the browser. See [How it works](../about/how_it_works.md) for more details. ::: @@ -290,3 +290,13 @@ When a Vega spec includes unsupported transforms, these transforms will be inclu - ❌ -

::: + + +:::{toctree} +:maxdepth: 2 +:hidden: true +:caption: Vega Coverage + +self +supported_expressions +::: diff --git a/pixi.lock b/pixi.lock index 0f0a9832..b7ddc943 100644 --- a/pixi.lock +++ b/pixi.lock @@ -9,6 +9,8 @@ environments: linux-64: - conda: https://conda.anaconda.org/conda-forge/linux-64/_libgcc_mutex-0.1-conda_forge.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/_openmp_mutex-4.5-2_gnu.tar.bz2 + - conda: https://conda.anaconda.org/conda-forge/noarch/ablog-0.11.12-pyh91182bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.16-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/alsa-lib-1.2.12-h4ab18f5_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/altair-5.4.1-pyhd8ed1ab_1.conda @@ -65,6 +67,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/expat-2.6.3-h5888daf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/feedgen-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flaky-3.8.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 @@ -75,7 +78,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/fonts-conda-forge-1-0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/freetype-2.12.1-h267a509_2.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/furo-2024.8.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gcc_impl_linux-64-14.2.0-h6b349bd_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/gflags-2.2.2-h5888daf_1005.conda @@ -95,6 +97,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.29.5-pyh3099207_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.28.0-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.0-pyhd8ed1ab_0.conda @@ -178,7 +181,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcb-1.15-h0b41bf4_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxcrypt-4.4.36-hd590300_1.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libxml2-2.12.7-h4c95cb1_3.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.39-h76b75d6_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/libzlib-1.3.1-hb9d3cd8_2.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/lxml-5.3.0-py310h6ee67d5_2.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/lz4-c-1.9.4-hcb278e6_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/markupsafe-3.0.2-py310h89163eb_0.conda @@ -198,6 +203,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.16.4-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbsphinx-0.9.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbval-0.9.6-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/ncurses-6.5-he02047a_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_0.conda @@ -234,6 +240,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-16.1.0-py310hb7f781d_6.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/pyarrow-core-16.1.0-py310hac404ae_6_cpu.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyha2e5f31_6.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.3-pyhd8ed1ab_0.conda @@ -271,7 +278,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.5-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-7.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-basic-ng-1.0.0b2-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.6.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_0.conda @@ -303,6 +309,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/vega_datasets-0.9.0-pyhd3deb0d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/linux-64/vl-convert-python-1.7.0-py310ha75aee5_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/voila-0.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py310hff52083_0.conda - conda: https://conda.anaconda.org/conda-forge/linux-64/watchfiles-0.24.0-py310h505e2c1_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.8.0-pyhd8ed1ab_0.conda @@ -346,6 +353,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/40/2e/8b39cd2c347490dbe10adf21fd50bbddb1dada5bb0512c3a39371285eb62/scikit_image-0.24.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl osx-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ablog-0.11.12-pyh91182bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.16-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/altair-5.4.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda @@ -399,9 +408,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/feedgen-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flaky-3.8.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/furo-2024.8.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/gflags-2.2.2-hac325c4_1005.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/glog-0.7.1-h2790a97_0.conda @@ -417,6 +426,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.29.5-pyh57ce528_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.28.0-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.0-pyhd8ed1ab_0.conda @@ -476,8 +486,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/libutf8proc-2.8.0-hb7f2c08_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-64/libuv-1.44.2-h0dc2134_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libxml2-2.12.7-hc603aa4_3.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/libxslt-1.1.39-h03b04e6_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/libzlib-1.3.1-hd23fc13_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/llvm-openmp-19.1.2-hf78d878_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/lxml-5.3.0-py310h6dc9824_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/lz4-c-1.9.4-hf0c8a7f_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/markupsafe-3.0.2-py310h72eadd2_0.conda @@ -497,6 +509,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.16.4-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbsphinx-0.9.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbval-0.9.6-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-64/ncurses-6.5-hf036a51_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_0.conda @@ -530,6 +543,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-64/pyarrow-16.1.0-py310h58fd45c_6.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pyarrow-core-16.1.0-py310h86202ae_6_cpu.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-core-10.3.1-py310h1c7075f_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/pyobjc-framework-cocoa-10.3.1-py310h1c7075f_1.conda @@ -568,7 +582,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.5-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-7.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-basic-ng-1.0.0b2-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.6.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_0.conda @@ -599,6 +612,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/vega_datasets-0.9.0-pyhd3deb0d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-64/vl-convert-python-1.7.0-py310h837254d_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/voila-0.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-64/watchdog-6.0.0-py310hbb8c376_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-64/watchfiles-0.24.0-py310h4bd000d_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.8.0-pyhd8ed1ab_0.conda @@ -624,6 +638,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/b7/82/d4eaa6e441f28a783762093a3c74bcc4a67f1c65bf011414ad4ea85187d8/scikit_image-0.24.0-cp310-cp310-macosx_10_9_x86_64.whl - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl osx-arm64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ablog-0.11.12-pyh91182bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.16-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/altair-5.4.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda @@ -674,6 +690,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.1.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/expat-2.6.3-hf9b8971_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/feedgen-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flaky-3.8.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-dejavu-sans-mono-2.37-hab24e00_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/font-ttf-inconsolata-3.000-h77eed37_0.tar.bz2 @@ -685,7 +702,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/freetype-2.12.1-hadb7bae_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/fribidi-1.0.10-h27ca646_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/furo-2024.8.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gdk-pixbuf-2.42.10-h9bcf4fe_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/gettext-0.22.5-h8414b35_3.conda @@ -709,6 +725,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_metadata-8.5.0-hd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.29.5-pyh57ce528_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.28.0-pyh707e725_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.0-pyhd8ed1ab_0.conda @@ -783,8 +800,10 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libuv-1.44.2-hb547adb_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libwebp-base-1.2.4-h1a8c8d9_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxml2-2.11.5-he3bdae6_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libxslt-1.1.37-h1728932_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/libzlib-1.3.1-h8359307_2.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/llvm-openmp-19.1.2-hb52a8e5_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lxml-4.9.3-py310h78afa71_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/lz4-c-1.9.4-hb7217d7_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/markdown-it-py-3.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/markupsafe-3.0.2-py310h5799be4_0.conda @@ -804,6 +823,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.16.4-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbsphinx-0.9.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbval-0.9.6-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/ncurses-6.5-h7bae524_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_0.conda @@ -841,6 +861,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-16.1.0-py310h24597f5_4.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyarrow-core-16.1.0-py310h2e300fa_4_cpu.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-core-10.3.1-py310hb3dec1a_1.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/pyobjc-framework-cocoa-10.3.1-py310hb3dec1a_1.conda @@ -879,7 +900,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.5-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-7.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-basic-ng-1.0.0b2-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.6.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_0.conda @@ -910,6 +930,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/vega_datasets-0.9.0-pyhd3deb0d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/osx-arm64/vl-convert-python-1.7.0-py310h493c2e1_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/voila-0.5.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchdog-6.0.0-py310h078409c_0.conda - conda: https://conda.anaconda.org/conda-forge/osx-arm64/watchfiles-0.24.0-py310h7a930dc_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.8.0-pyhd8ed1ab_0.conda @@ -935,6 +956,8 @@ environments: - pypi: https://files.pythonhosted.org/packages/65/15/1879307aaa2c771aa8ef8f00a171a85033bffc6b2553cfd2657426881452/scikit_image-0.24.0-cp310-cp310-macosx_12_0_arm64.whl - pypi: https://files.pythonhosted.org/packages/50/0a/435d5d7ec64d1c8b422ac9ebe42d2f3b2ac0b3f8a56f5c04dd0f3b7ba83c/tifffile-2024.9.20-py3-none-any.whl win-64: + - conda: https://conda.anaconda.org/conda-forge/noarch/ablog-0.11.12-pyh91182bf_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/alabaster-0.7.16-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/altair-5.4.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/anyio-4.6.2.post1-pyhd8ed1ab_0.conda @@ -983,9 +1006,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/entrypoints-0.4-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/exceptiongroup-1.2.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/executing-2.1.0-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/feedgen-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/flaky-3.8.1-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/fqdn-1.5.1-pyhd8ed1ab_0.tar.bz2 - - conda: https://conda.anaconda.org/conda-forge/noarch/furo-2024.8.6-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/future-1.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/grpcio-1.67.1-py310h0288bfe_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/h11-0.14.0-pyhd8ed1ab_0.tar.bz2 @@ -999,6 +1022,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/importlib_resources-6.4.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/iniconfig-2.0.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/intel-openmp-2024.2.1-h57928b3_1083.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipykernel-6.29.5-pyh4bbf305_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipython-8.28.0-pyh7428d3b_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/ipywidgets-8.1.0-pyhd8ed1ab_0.conda @@ -1051,7 +1075,9 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/libthrift-0.21.0-hbe90ef8_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libutf8proc-2.8.0-h82a8f57_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/libxml2-2.12.7-h0f24e4e_4.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.39-h3df6e99_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/libzlib-1.3.1-h2466b09_2.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/lxml-5.3.0-py310hb043844_2.conda - conda: https://conda.anaconda.org/conda-forge/win-64/lz4-c-1.9.4-hcfcfb64_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libgfortran-5.3.0-6.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/win-64/m2w64-gcc-libs-5.3.0-7.tar.bz2 @@ -1078,6 +1104,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-core-7.16.4-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbconvert-pandoc-7.16.4-hd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbformat-5.10.4-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/nbsphinx-0.9.5-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/nbval-0.9.6-pyh9f0ad1d_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/nest-asyncio-1.6.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/nodejs-20.5.1-h57928b3_1.conda @@ -1109,6 +1136,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/pyarrow-16.1.0-py310h05ea346_6.conda - conda: https://conda.anaconda.org/conda-forge/win-64/pyarrow-core-16.1.0-py310h399dd74_6_cpu.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pycparser-2.22-pyhd8ed1ab_0.conda + - conda: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.16.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pygments-2.18.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/pysocks-1.7.1-pyh0701188_6.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/pytest-8.3.3-pyhd8ed1ab_0.conda @@ -1146,7 +1174,6 @@ environments: - conda: https://conda.anaconda.org/conda-forge/noarch/sortedcontainers-2.4.0-pyhd8ed1ab_0.tar.bz2 - conda: https://conda.anaconda.org/conda-forge/noarch/soupsieve-2.5-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-7.4.7-pyhd8ed1ab_0.conda - - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-basic-ng-1.0.0b2-pyhd8ed1ab_2.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-copybutton-0.5.2-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinx-design-0.6.1-pyhd8ed1ab_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/sphinxcontrib-applehelp-2.0.0-pyhd8ed1ab_0.conda @@ -1183,6 +1210,7 @@ environments: - conda: https://conda.anaconda.org/conda-forge/win-64/vl-convert-python-1.7.0-py310hdfd1e6a_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/voila-0.5.0-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/vs2015_runtime-14.40.33810-h3bf8584_22.conda + - conda: https://conda.anaconda.org/conda-forge/win-64/watchdog-6.0.0-py310h5588dad_0.conda - conda: https://conda.anaconda.org/conda-forge/win-64/watchfiles-0.24.0-py310hc226416_1.conda - conda: https://conda.anaconda.org/conda-forge/noarch/wcwidth-0.2.13-pyhd8ed1ab_0.conda - conda: https://conda.anaconda.org/conda-forge/noarch/webcolors-24.8.0-pyhd8ed1ab_0.conda @@ -1240,6 +1268,52 @@ packages: purls: [] size: 23621 timestamp: 1650670423406 +- kind: conda + name: ablog + version: 0.11.12 + build: pyh91182bf_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/ablog-0.11.12-pyh91182bf_0.conda + sha256: ba5c95951c8075742a8e67a402e8025aded336133b147a382271466784b3a849 + md5: 515bb25ac39a81b3e7667e59fe33b8a3 + depends: + - docutils + - feedgen + - invoke + - jinja2 + - myst-parser + - nbsphinx + - packaging + - pandoc + - python >=3.10 + - python-dateutil + - sphinx >=6.2 + - watchdog + license: MIT + license_family: MIT + purls: + - pkg:pypi/ablog?source=hash-mapping + size: 52808 + timestamp: 1730530115487 +- kind: conda + name: accessible-pygments + version: 0.0.5 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/accessible-pygments-0.0.5-pyhd8ed1ab_0.conda + sha256: 712c1875bcd32674e8ce2f418f0b2875ecb98e6bcbb21ec7502dae8ff4b0f73c + md5: 1bb1ef9806a9a20872434f58b3e7fc1a + depends: + - pygments + - python >=3.9 + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/accessible-pygments?source=hash-mapping + size: 1328908 + timestamp: 1718718120070 - kind: conda name: alabaster version: 0.7.16 @@ -3727,6 +3801,25 @@ packages: purls: [] size: 125005 timestamp: 1725568799108 +- kind: conda + name: feedgen + version: 1.0.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/feedgen-1.0.0-pyhd8ed1ab_0.conda + sha256: 56543b35f9c17788704033fd921ba53fba6053224b2f4a6085ae0c8e615113bf + md5: ca2add69e70f972546abb79791d09c79 + depends: + - lxml >=4.2.5 + - python >=3.6 + - python-dateutil >=2.8.0 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/feedgen?source=hash-mapping + size: 39510 + timestamp: 1703636593569 - kind: conda name: flaky version: 3.8.1 @@ -3935,28 +4028,6 @@ packages: purls: [] size: 60255 timestamp: 1604417405528 -- kind: conda - name: furo - version: 2024.8.6 - build: pyhd8ed1ab_1 - build_number: 1 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/furo-2024.8.6-pyhd8ed1ab_1.conda - sha256: 65f527cc94ab436c7746fa048e755831d3d3808ba9073cb57696c33b58d1192f - md5: 1de9286f68ce577064262b0071ac9b4e - depends: - - beautifulsoup4 - - pygments >=2.7 - - python >=3.7 - - sphinx >=6.0,<9.0 - - sphinx-basic-ng - license: MIT - license_family: MIT - purls: - - pkg:pypi/furo?source=hash-mapping - size: 83413 - timestamp: 1727407917886 - kind: conda name: future version: 1.0.0 @@ -4680,6 +4751,23 @@ packages: purls: [] size: 1852356 timestamp: 1723739573141 +- kind: conda + name: invoke + version: 2.2.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/invoke-2.2.0-pyhd8ed1ab_0.conda + sha256: bcd98cd82a4b004e14f7ceab402b37b8693c479cad947d40468c06c472704b8c + md5: 1754e27eeb29ef3df48247e8ce7fdff3 + depends: + - python >=3.6 + license: BSD-2-Clause + license_family: BSD + purls: + - pkg:pypi/invoke?source=hash-mapping + size: 132234 + timestamp: 1689210761103 - kind: conda name: ipykernel version: 6.29.5 @@ -8762,6 +8850,71 @@ packages: purls: [] size: 620129 timestamp: 1720772795289 +- kind: conda + name: libxslt + version: 1.1.37 + build: h1728932_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/libxslt-1.1.37-h1728932_1.conda + sha256: 2e4501c208ada1fbce18ffbcbcb50bc258d51ad314cae262cce091bab304ace5 + md5: 8483366e7b7ed525a5d17ef81c26b1f4 + depends: + - libxml2 >=2.11.3,<3.0.0a0 + license: MIT + license_family: MIT + purls: [] + size: 231472 + timestamp: 1684275322718 +- kind: conda + name: libxslt + version: 1.1.39 + build: h03b04e6_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/libxslt-1.1.39-h03b04e6_0.conda + sha256: decfc5614a10231a17543b7366616fb2d88c14be6dd9dd5ecde63aa9a5acfb9e + md5: a6e0cec6b3517ffc6b5d36a920fc9312 + depends: + - libxml2 >=2.12.1,<3.0.0a0 + license: MIT + license_family: MIT + purls: [] + size: 231368 + timestamp: 1701628933115 +- kind: conda + name: libxslt + version: 1.1.39 + build: h3df6e99_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/libxslt-1.1.39-h3df6e99_0.conda + sha256: 6e3d99466d2076c35e7ac8dcdfe604da3d593f55b74a5b8e96c2b2ff63c247aa + md5: 279ee338c9b34871d578cb3c7aa68f70 + depends: + - libxml2 >=2.12.1,<3.0.0a0 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: MIT + license_family: MIT + purls: [] + size: 418542 + timestamp: 1701629338549 +- kind: conda + name: libxslt + version: 1.1.39 + build: h76b75d6_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/libxslt-1.1.39-h76b75d6_0.conda + sha256: 684e9b67ef7b9ca0ca993762eeb39705ec58e2e7f958555c758da7ef416db9f3 + md5: e71f31f8cfb0a91439f2086fc8aa0461 + depends: + - libgcc-ng >=12 + - libxml2 >=2.12.1,<3.0.0a0 + license: MIT + license_family: MIT + purls: [] + size: 254297 + timestamp: 1701628814990 - kind: conda name: libzlib version: 1.3.1 @@ -8871,6 +9024,93 @@ packages: purls: [] size: 305589 timestamp: 1729145249496 +- kind: conda + name: lxml + version: 4.9.3 + build: py310h78afa71_1 + build_number: 1 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/lxml-4.9.3-py310h78afa71_1.conda + sha256: d1cbfd3dfb8a11529648dd433b439bd662d6189f8c5f5e69230a9a86d008d8a9 + md5: 88b4f670d051d5895287e24655b603e8 + depends: + - libxml2 >=2.11.5,<3.0.0a0 + - libxslt >=1.1.37,<2.0a0 + - libzlib >=1.2.13,<2.0.0a0 + - python >=3.10,<3.11.0a0 + - python >=3.10,<3.11.0a0 *_cpython + - python_abi 3.10.* *_cp310 + license: BSD-3-Clause and GPL-2.0-only and ZPL-2.0 and LicenseRef-ElementTree + purls: + - pkg:pypi/lxml?source=hash-mapping + size: 1285035 + timestamp: 1695547278437 +- kind: conda + name: lxml + version: 5.3.0 + build: py310h6dc9824_2 + build_number: 2 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/lxml-5.3.0-py310h6dc9824_2.conda + sha256: dfe7ef68702558d16307bb0c1ae2cfd9fa51d9e15c874f5b09597c8f05cb90ba + md5: 0a8cd47c47d066a9bb35f80a5742c129 + depends: + - __osx >=10.13 + - libxml2 >=2.12.7,<3.0a0 + - libxslt >=1.1.39,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - python >=3.10,<3.11.0a0 + - python_abi 3.10.* *_cp310 + license: BSD-3-Clause and MIT-CMU + purls: + - pkg:pypi/lxml?source=hash-mapping + size: 1153467 + timestamp: 1730194574241 +- kind: conda + name: lxml + version: 5.3.0 + build: py310h6ee67d5_2 + build_number: 2 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/lxml-5.3.0-py310h6ee67d5_2.conda + sha256: e95d092080e39ee762826205a84c5bdb95785fc9ebe6a9063c4713028177d265 + md5: 423e016c73ff42c834d11a72c62142e8 + depends: + - __glibc >=2.17,<3.0.a0 + - libgcc >=13 + - libxml2 >=2.12.7,<3.0a0 + - libxslt >=1.1.39,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - python >=3.10,<3.11.0a0 + - python_abi 3.10.* *_cp310 + license: BSD-3-Clause and MIT-CMU + purls: + - pkg:pypi/lxml?source=hash-mapping + size: 1352927 + timestamp: 1730194455553 +- kind: conda + name: lxml + version: 5.3.0 + build: py310hb043844_2 + build_number: 2 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/lxml-5.3.0-py310hb043844_2.conda + sha256: 74cdd9ef8c232f05ec7cff3822d8e6f100f69d21a4733d76acb3b9913ac440bc + md5: d6ca03224ef77e5c1a6210ba8afdb995 + depends: + - libxml2 >=2.12.7,<3.0a0 + - libxslt >=1.1.39,<2.0a0 + - libzlib >=1.3.1,<2.0a0 + - python >=3.10,<3.11.0a0 + - python_abi 3.10.* *_cp310 + - ucrt >=10.0.20348.0 + - vc >=14.2,<15 + - vc14_runtime >=14.29.30139 + license: BSD-3-Clause and MIT-CMU + purls: + - pkg:pypi/lxml?source=hash-mapping + size: 1026426 + timestamp: 1730194775740 - kind: conda name: lz4-c version: 1.9.4 @@ -9641,6 +9881,29 @@ packages: - pkg:pypi/nbformat?source=hash-mapping size: 101232 timestamp: 1712239122969 +- kind: conda + name: nbsphinx + version: 0.9.5 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/nbsphinx-0.9.5-pyhd8ed1ab_0.conda + sha256: 0fc92fc4e1eab73ce7808b5055c33f319a8949b4ad272fc69ebb96b2f157d5eb + md5: b808b8a0494c5cca76200c73e260a060 + depends: + - docutils + - jinja2 + - nbconvert + - nbformat + - python >=3.6 + - sphinx + - traitlets + license: MIT + license_family: MIT + purls: + - pkg:pypi/nbsphinx?source=hash-mapping + size: 33725 + timestamp: 1723612159088 - kind: conda name: nbval version: 0.9.6 @@ -11314,6 +11577,31 @@ packages: - pkg:pypi/pycparser?source=hash-mapping size: 105098 timestamp: 1711811634025 +- kind: conda + name: pydata-sphinx-theme + version: 0.16.0 + build: pyhd8ed1ab_0 + subdir: noarch + noarch: python + url: https://conda.anaconda.org/conda-forge/noarch/pydata-sphinx-theme-0.16.0-pyhd8ed1ab_0.conda + sha256: 745431f1f6dc5a4ac1fe2d5543c6f3fbe0acf9b77e08ef57471a70561ff9ecad + md5: 344261b0e77f5d2faaffb4eac225eeb7 + depends: + - accessible-pygments + - babel + - beautifulsoup4 + - docutils !=0.17.0 + - packaging + - pygments >=2.7 + - python >=3.9 + - sphinx >=6.1 + - typing_extensions + license: BSD-3-Clause + license_family: BSD + purls: + - pkg:pypi/pydata-sphinx-theme?source=hash-mapping + size: 1526232 + timestamp: 1729643324444 - kind: conda name: pygments version: 2.18.0 @@ -13290,25 +13578,6 @@ packages: - pkg:pypi/sphinx?source=hash-mapping size: 1358660 timestamp: 1721487658869 -- kind: conda - name: sphinx-basic-ng - version: 1.0.0b2 - build: pyhd8ed1ab_2 - build_number: 2 - subdir: noarch - noarch: python - url: https://conda.anaconda.org/conda-forge/noarch/sphinx-basic-ng-1.0.0b2-pyhd8ed1ab_2.conda - sha256: 091293964075ed1905731d09ff2691e053cd9d5335d99501f05683da29de0ee7 - md5: 463d989a8f1506bcf51cc37d7beebdf1 - depends: - - python >=3.7 - - sphinx >=4.0 - license: MIT - license_family: MIT - purls: - - pkg:pypi/sphinx-basic-ng?source=hash-mapping - size: 20338 - timestamp: 1727436819491 - kind: conda name: sphinx-copybutton version: 0.5.2 @@ -14359,6 +14628,81 @@ packages: purls: [] size: 17453 timestamp: 1728400827536 +- kind: conda + name: watchdog + version: 6.0.0 + build: py310h078409c_0 + subdir: osx-arm64 + url: https://conda.anaconda.org/conda-forge/osx-arm64/watchdog-6.0.0-py310h078409c_0.conda + sha256: 8538c55385397900efa5f69c8090629fb3346c00e46d457004464585d25839bd + md5: cf0d5c291d51b6bac2522f27f561a43b + depends: + - __osx >=11.0 + - python >=3.10,<3.11.0a0 + - python >=3.10,<3.11.0a0 *_cpython + - python_abi 3.10.* *_cp310 + - pyyaml >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/watchdog?source=hash-mapping + size: 124807 + timestamp: 1730493162845 +- kind: conda + name: watchdog + version: 6.0.0 + build: py310h5588dad_0 + subdir: win-64 + url: https://conda.anaconda.org/conda-forge/win-64/watchdog-6.0.0-py310h5588dad_0.conda + sha256: a55fa89c944a126f551d90f63ea3c3e4b09e223c60a7b7d17f229e80c49a18d5 + md5: a6383fa961f02e802c35bae9bdd9f0ec + depends: + - python >=3.10,<3.11.0a0 + - python_abi 3.10.* *_cp310 + - pyyaml >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/watchdog?source=hash-mapping + size: 141143 + timestamp: 1730493348685 +- kind: conda + name: watchdog + version: 6.0.0 + build: py310hbb8c376_0 + subdir: osx-64 + url: https://conda.anaconda.org/conda-forge/osx-64/watchdog-6.0.0-py310hbb8c376_0.conda + sha256: 895ca992a06152bdb3098ab49d7195312f7979c9dbb3cd0e512ba6a625967961 + md5: f000e96ab28351ad27c21d1a546a7324 + depends: + - __osx >=10.13 + - python >=3.10,<3.11.0a0 + - python_abi 3.10.* *_cp310 + - pyyaml >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/watchdog?source=hash-mapping + size: 124225 + timestamp: 1730493095862 +- kind: conda + name: watchdog + version: 6.0.0 + build: py310hff52083_0 + subdir: linux-64 + url: https://conda.anaconda.org/conda-forge/linux-64/watchdog-6.0.0-py310hff52083_0.conda + sha256: 95ca3ddffb6577c48087d7f14c8d2be9d9963b7431b3448fcce4649b6afd1a99 + md5: f47eccf447f25379f49f8b87c931baac + depends: + - python >=3.10,<3.11.0a0 + - python_abi 3.10.* *_cp310 + - pyyaml >=3.10 + license: Apache-2.0 + license_family: APACHE + purls: + - pkg:pypi/watchdog?source=hash-mapping + size: 116134 + timestamp: 1730492960947 - kind: conda name: watchfiles version: 0.24.0 diff --git a/pixi.toml b/pixi.toml index d49cbf90..cc4afede 100644 --- a/pixi.toml +++ b/pixi.toml @@ -151,8 +151,9 @@ flaky = ">=3.8.1,<4" sphinx = ">=7,<8" sphinx-copybutton = ">=0.5.0,<1" myst-parser = ">=4.0.0,<5" -furo = ">=2024.8.6" sphinx-design = ">=0.6.0,<1" +ablog = ">=0.11.0,<0.12" +pydata-sphinx-theme = ">=0.16.0,<0.17" # Dependencies are those required at runtime by the Python packages [dependencies]