# datachart > Data visualization package, simple to use, highly customizable datachart is a Python data visualization package built on matplotlib. It provides simple chart functions (BarChart, LineChart, StackedAreaChart, BumpChart, ScatterChart, Histogram, Heatmap, ContourChart, HexbinChart, BoxPlot, ViolinPlot, SwarmPlot, RaincloudPlot, RidgelinePlot, ParallelCoords, PyramidChart, RadialChart, CalendarHeatmap, GanttChart, DumbbellChart, SankeyChart, NetworkChart, ScatterMatrix, Treemap), figure composition via Panel (overlaying charts on shared axes) and Grid (arranging charts in a grid), and a global configuration system with predefined themes. Install with `pip install datachart`. All charts return a matplotlib Figure. Style is controlled globally via `datachart.config.config` and themes; per-chart overrides go in each chart's `style` parameter. # Getting Started Documentation ## Find your way around Start with a guide, look things up in the reference, or point your AI assistant at the docs. \[### How-to guides Every chart type, composition with Panel and Grid, styling, and the utilities, each a runnable notebook on real data. Open the guides →\](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/index.md) \[### API reference Signatures and attribute tables for the charts, utils, config, themes, constants, and typings modules. Browse the reference →\](https://eriknovak.github.io/datachart/0.10.2/references/index.md) \[### Themes and styling Seven predefined themes, the global config, colormaps, and emphasis for the series that matters. See the theme gallery →\](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) \[### Composition Overlay charts on one axes with Panel, or lay them out with Grid. Grids nest. Compose figures →\](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/composition/index.md) \[### AI assistants llms.txt, per-page markdown, and MCP servers, so a coding assistant writes datachart code from the current docs. Point your assistant here →\](https://eriknovak.github.io/datachart/0.10.2/ai-assistants/index.md) \[### Source and changelog Bug reports, feature requests, and pull requests are welcome on GitHub. Go to the repository →\](https://github.com/eriknovak/datachart) Install ## Two commands, Python 3.10 or higher Install from PyPI with pip or uv. Add the `interactive` extra for zoom, pan, and hover-to-inspect. ``` pip install -U datachart ``` ``` uv add datachart ``` ``` pip install -U "datachart[interactive]" ``` Every chart returns a plain matplotlib `Figure`, so anything matplotlib can do with it still works. See [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) and [Interactive Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/interactive/index.md). First chart ## A line chart in one call Every chart takes a list of series, each a list of dicts. Set a theme once and every chart follows it. ``` from datachart.charts import LineChart from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.INK) months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"] signups = [420, 465, 430, 510, 560, 545, 610] churned = [380, 400, 440, 435, 480, 520, 550] figure = LineChart( [ [{"x": x, "y": y} for x, y in enumerate(signups)], [{"x": x, "y": y} for x, y in enumerate(churned)], ], title="Monthly signups vs. churn", subtitle=["Signups", "Churned"], xlabel="Month", ylabel="Users", xticks=list(range(len(months))), xticklabels=months, show_legend=True, ) ``` # AI Assistants A coding assistant writes better `datachart` code when it reads the current documentation instead of recalling an older release: chart types, parameters, and constants change between versions, and a guessed keyword argument fails at runtime. The site is published in formats made for that. This page shows how to hand the docs to an assistant, from a link pasted into a prompt to a server the assistant queries on its own, and ends with a rules snippet that tells it how the package is meant to be used. ## What the site publishes Every build of the documentation writes three machine-readable views next to the pages: | View | URL | What it holds | | --------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Index | [llms.txt](https://eriknovak.github.io/datachart/latest/llms.txt) | Every page with a one-line description, grouped by section. Small enough to paste. | | Full text | [llms-full.txt](https://eriknovak.github.io/datachart/latest/llms-full.txt) | The whole site in one file: guide prose, code, and the API reference. | | Per page | append `index.md` to a page URL | The page as plain markdown, for example [how-to-guides/charts/linechart/index.md](https://eriknovak.github.io/datachart/latest/how-to-guides/charts/linechart/index.md). | The `latest` segment in the URLs is the released version. Replace it with `dev` for the documentation of the main branch, or with a version number such as `0.9.1` to pin a release. ## Paste a link The simplest route needs no setup: put the URL of the index or of the page you need in the prompt. Assistants with web access fetch it and read from the current docs. ``` Draw a grouped bar chart of the medal table with datachart. Read the guide first: https://eriknovak.github.io/datachart/latest/how-to-guides/charts/barchart/index.md ``` For a longer task, paste `llms.txt` once at the start of the session; the assistant then knows which page to fetch for each question. ## Connect an MCP server An assistant that supports the [Model Context Protocol](https://modelcontextprotocol.io/) (MCP) can query the docs without a link in every prompt. Two hosted servers cover this repository; neither needs anything installed. ### Context7 [Context7](https://context7.com/eriknovak/datachart) indexes the documentation per version and returns the pages that match a question. The library ID is `/eriknovak/datachart`. ``` claude mcp add --transport http context7 https://mcp.context7.com/mcp ``` ``` { "mcpServers": { "context7": { "url": "https://mcp.context7.com/mcp" } } } ``` Then name the library in the prompt, or add "use context7" to have the assistant resolve it: ``` Use context7 to look up datachart's Panel, then overlay a histogram and a cumulative line with the line on a secondary axis. ``` ### GitMCP [GitMCP](https://gitmcp.io/eriknovak/datachart) serves the repository's documentation as an MCP endpoint at `https://gitmcp.io/eriknovak/datachart`, with tools to fetch and search it. ``` claude mcp add --transport sse datachart-docs https://gitmcp.io/eriknovak/datachart ``` ``` { "mcpServers": { "datachart-docs": { "url": "https://gitmcp.io/eriknovak/datachart" } } } ``` Once connected, the assistant calls the server whenever a question is about `datachart`; no prompt wording is needed. ## Describe the package in the project rules A link tells the assistant where the docs are; a rules file tells it how the package fits together, so it reaches for the right function before it reads anything. Add the block below to the file your assistant reads at the start of a session: `CLAUDE.md` for Claude Code, `AGENTS.md` for Codex and others, `.cursor/rules` for Cursor. ``` ## Charts: datachart Charts are drawn with the `datachart` package (built on matplotlib). Docs: https://eriknovak.github.io/datachart/latest/ (index for LLMs: /llms.txt; any page as markdown by appending index.md to its URL). - Every chart is one function from `datachart.charts` (LineChart, BarChart, ScatterChart, Histogram, ...) that takes a list of series, each a list of dicts. Look up the dict keys and parameters of a chart in its guide before using it: https://eriknovak.github.io/datachart/latest/how-to-guides/charts/ - Style is global: `config.set_theme(THEME.X)` and `config.update_config(...)` from `datachart.config` and `datachart.constants`. Per-chart overrides go in the chart's `style` argument. Do not restyle through matplotlib directly. - Combine finished figures with `Panel` (overlay on shared axes) and `Grid` (arrange in cells) from `datachart.utils`; write files with `save_figure`. - Every chart returns a matplotlib `Figure`. ``` The block is deliberately short. The assistant fetches the details from the guides once it knows they exist, and a long copy of the reference goes stale with the next release. # How-to Guides The how-to guides showcase how to utilize the `datachart` package: creating charts that reflect the data and the message the user wants to send, composing multiple charts into panels and grids, styling everything through themes and the global configuration, and the utilities around a figure. - [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) One guide per chart type, grouped by the question the chart answers, and the highlighting guide that applies to all of them. - [Composition](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/composition/index.md) Overlaying charts with `Panel`, arranging them with `Grid`, and annotating charts and finished figures. - [Styling](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/index.md) The global configuration, the predefined themes and how to create your own, and the colormaps. - [Utility](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/index.md) The statistical helpers, saving figures to files and web pages, and interactive figures. # Charts # Charts The [datachart.charts](https://eriknovak.github.io/datachart/0.10.2/references/charts/index.md) module of the `datachart` package provides various chart types to create data visualizations. The module is designed to be highly customizable and easy to use. The charts are grouped by the question they answer. Each card names a chart, says what it is for, and links to its how-to guide. The chips under the name say how the chart composes with the other charts through the [composition](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/composition/index.md) functions: - Panel — the chart can be overlaid with other charts in one coordinate space through [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md). A chart that owns its whole axes (the heatmap, Sankey chart, treemap, network chart, calendar heatmap, and scatter matrix) or draws a mirrored or task axis (the pyramid and gantt charts) cannot, and shows Panel. The box, violin, raincloud, and ridgeline plots overlay with other kinds of charts, but a panel holds one dataset of each of these kinds. - Grid — the chart can take a cell of a combined figure through [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md). Every chart can. A finished chart goes further through the [composition](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/composition/index.md) guides, the [styling](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/index.md) guides, and the [utility](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/index.md) guides. ## Trends and Comparisons Values along an axis or across categories: how a quantity moves and how the categories compare. - [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) How a value moves along a continuous axis, and how the trajectories of several series compare. - [Stacked Area Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/stackedareachart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) How a total splits into parts along an axis: the top edge traces the total, the bands its composition. - [Bump Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/bumpchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Rank over time: who leads and who overtakes whom, with each series named at the end of its line. - [Bar Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) A numeric value across a few categories; several series can be grouped, stacked, or overlaid. - [Pyramid Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/pyramidchart/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Two groups mirrored over the same ordered categories, such as an age-sex population pyramid. - [Radial Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/radialchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Series on polar axes: a radar profile over several metrics, or bars over cyclic categories. - [Calendar Heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/calendarheatmap/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) One cell per day, weeks as columns, for a daily series with a weekly or seasonal rhythm. - [Gantt Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ganttchart/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) A schedule: one bar per task over a date axis, with groups, progress, milestones, and dependencies. - [Dumbbell Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/dumbbellchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Two values per category and the gap between them: before and after, or a minimum and a maximum. ## Distributions The spread of the values within each group, from a binned summary to every observation. - [Histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) The shape of one numeric variable, binned into counts; a few distributions can be overlaid. - [Box Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/boxplot/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) The median, quartiles, whiskers, and outliers per group, for comparing many groups compactly. - [Violin Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/violinplot/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) The density profile of each group, showing the skew and the modes that a box plot hides. - [Swarm Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/swarmplot/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Every observation as a point at its group, spread so that none hide; for small to medium samples. - [Raincloud Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/raincloudplot/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) The density, the raw observations, and the quartile box of each group, side by side in one view. - [Ridgeline Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ridgelineplot/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) One density ridge per group, stacked and overlapping: how a distribution shifts across many groups. ## Relationships How two or more variables relate to each other. - [Scatter Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Two numeric variables per observation, with an optional regression line and correlation coefficient. - [Heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/heatmap/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Every cell of a matrix as a color: correlations, confusion matrices, feature-by-time tables. - [Contour Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/contourchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) A surface sampled on a grid, as iso-lines or filled bands: densities, loss landscapes, terrain. - [Hexbin Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/hexbinchart/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Point density on hexagonal tiles, where a scatter chart would turn into an opaque blob. - [Parallel Coordinates](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/parallelcoords/index.md) [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Each record as a polyline across one axis per dimension, colored by group to compare the groups. - [Network Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/networkchart/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Relational data as a node-link diagram: edge weight sets the width, node group sets the color. - [Scatter Matrix](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scattermatrix/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) A scatter chart for every pair of dimensions, with each dimension's distribution on the diagonal. ## Flows How a quantity moves between categories: where it comes from and where it goes. - [Sankey Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/sankeychart/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Weighted flows between categories through ordered stages: funnels, label transitions, energy budgets. ## Part of a Whole How a whole splits into parts, and parts into smaller parts. - [Treemap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/treemap/index.md) Panel [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Part-of-whole data as nested rectangles whose area is the value, up to four levels deep. ## Across Charts The options every chart shares, worth a guide of their own. - [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) Emphasizing and muting data series with the `emphasis` option, by role or by rule, on any chart. # Line Chart A line chart connects ordered points to show how a value moves along a continuous axis, usually time: trends, growth, seasons, and how the trajectories of several series compare. This guide shows how to create line charts with the [datachart.charts.LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.charts.LineChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-line-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import LineChart ``` ## Basics The examples in this guide share one dataset: the global mean surface temperature anomaly, the difference of each year's global average from the 1951–1980 mean in °C, from 1880 to 2024 (source: NASA GISS Surface Temperature Analysis, GISTEMP v4, rounded to two decimals). The data lives in a hidden cell. `warming` holds the yearly anomaly as one data point per year, and `warming_by_hemisphere` holds one series each for the Northern and Southern Hemisphere over the same years, with the year-to-year spread of each five-year window as `yerr`. The series is famous for a reason: it starts flat, wobbles for a century, and then climbs, and every customization below helps to read that. Each data point is a dictionary with an `x` value (here the year) and a `y` value: ``` warming[:3] ``` **Basic example.** Only the `data` argument is required to draw the line chart: ``` LineChart( # add the data to the chart data=warming ).show() ``` ## Customizing the Line Chart Every customization is either a keyword argument of `LineChart` or a `plot_line_*` / `plot_area_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | --------------------------------------- | --------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | set custom tick positions and labels | `xticks`, `xticklabels`, `yticks`, `yticklabels` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | format the tick labels | `xticks_format`, `yticks_format` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | fix the aspect ratio of the axes | `aspect_ratio` | [Figure size and grid](#figure-size-and-grid) | | change the line color, width, or dashes | `style={"plot_line_color": ..., "plot_line_style": ...}` | [Line style](#line-style) | | mark the data points | `style={"plot_line_marker": ...}` | [Line style](#line-style) | | draw the line as steps | `style={"plot_line_drawstyle": ...}` | [Line style](#line-style) | | fill the area under the line | `show_area`, `style={"plot_area_color": ..., "plot_area_alpha": ...}` | [Area under the line](#area-under-the-line) | | print the value beside the points | `show_values`, `value_format`, `value_step` | [Value labels](#value-labels) | | mark a threshold or an event | `hlines`, `vlines` | [Reference lines](#reference-lines) | | shade a period or a range | `hspans`, `vspans` | [Reference bands](#reference-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | compare several series in one chart | `data` as a list of lists, `subtitle`, `show_legend` | [Multiple Line Charts](#multiple-line-charts) | | highlight one series, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | title and place the legend | `legend` | [Legend](#legend) | | draw a confidence band | `yerr` in `data`, `show_yerr` | [Confidence interval](#confidence-interval) | | draw each series in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | plot against real dates | `datetime` objects as `x`, `xticks_format` | [Datetime axis](#datetime-axis) | | use a logarithmic axis | `scaley`, `scalex` | [Axis scales](#axis-scales) | | plot data with other key names | `x`, `y`, `yerr` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.LineStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs) and [datachart.typings.AreaStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AreaStyleAttrs) types; the full list of parameters is in the [datachart.charts.LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.charts.LineChart) reference. ### Title, axis labels and ticks A line without a title and axis labels is a shape, not a statement; `title`, `xlabel` and `ylabel` say what moves and in what unit. The ticks are the reader's ruler: `xticks` and `yticks` set their positions (`xticklabels` and `yticklabels` replace the labels), `xticks_format` and `yticks_format` format them (a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or a `"{x:.1f}"` style string on a value axis), and `xtickrotate` and `ytickrotate` tilt them. `xmin`, `xmax`, `ymin` and `ymax` fix the axis range, so the chart shows the span you mean rather than the span of the data. Here the years tick every twenty years, the anomaly prints with a sign, and the range is symmetric around zero, where the anomaly is defined. ``` LineChart( data=warming, # add the title title="Global mean surface temperature anomaly", # add the x and y axis labels xlabel="Year", ylabel="Anomaly (°C, vs. 1951–1980)", # a tick every twenty years, values printed with a sign xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", # a range symmetric around zero ymin=-0.6, ymax=1.4, xmin=1880, xmax=2025, ).show() ``` ### Figure size and grid A long time series wants a wide, short figure, so the years have room and the slope stays honest. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. Grid lines let the eye carry a point across to the axis; `show_grid` draws them with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member, `Y` for the value axis alone, `X` for the time axis, `BOTH` for both. `aspect_ratio` fixes the ratio of the axes rather than of the figure, one data unit the same length on both axes with [ASPECT_RATIO.EQUAL](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO); a time series never needs it, but the [ROC curve](#example-1-a-roc-curve-custom-data-keys-and-an-equal-aspect-ratio) below does. ``` from datachart.constants import FIG_SIZE, SHOW_GRID LineChart( data=warming, title="Global mean surface temperature anomaly", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along both axes show_grid=SHOW_GRID.BOTH, ).show() ``` ### Line style The `style` dictionary sets the look of the line: its color and alpha, its width, its dash pattern from [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE), a marker at each point from [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER), and the draw style from [LINE_DRAW_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_DRAW_STYLE); the attributes are listed in [datachart.typings.LineStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs), and any attribute left out keeps the value of the active theme. The look should follow the data. A hundred and forty-five yearly values are a dense series, so a thin line without markers reads best; the example draws it thin and in a warm color, and the [next section](#area-under-the-line) shows where markers and steps earn their place. ``` from datachart.constants import LINE_STYLE, LINE_MARKER, LINE_DRAW_STYLE LineChart( data=warming, # a thin line in a warm color style={ "plot_line_color": "#c1121f", "plot_line_width": 1.2, }, title="Global mean surface temperature anomaly", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, ).show() ``` Averages over a period are one value per period rather than a continuous curve, and a step line says so. `decades`, the mean anomaly of each decade, is sparse enough for markers and better drawn as steps: `LINE_DRAW_STYLE.STEPS_MID` centers each step on its point, and a dashed pattern marks the series as derived. ``` # the mean anomaly of each full decade, placed at the middle of the decade decades = [ {"x": start + 5, "y": round(sum(ANOMALY[i : i + 10]) / 10, 2)} for i, start in enumerate(YEARS) if start % 10 == 0 and i + 10 <= len(ANOMALY) ] LineChart( data=decades, style={ # one value per decade: steps centered on the points, marked and dashed "plot_line_drawstyle": LINE_DRAW_STYLE.STEPS_MID, "plot_line_marker": LINE_MARKER.CIRCLE, "plot_line_style": LINE_STYLE.DASHED, }, title="Mean temperature anomaly by decade", xlabel="Decade", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Area under the line Filling the area under a line turns a trajectory into a quantity: the fill draws the eye to how much has accumulated, not only where the line is. `show_area` fills between the line and the bottom of the axes in the color of the line at a lower alpha; `plot_area_color`, `plot_area_alpha` and `plot_area_hatch` from [datachart.typings.AreaStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AreaStyleAttrs) override that. A fill measures from the bottom of the axes, so the axes must start where the measure starts: the anomaly has not been below zero since 1976, so `warming_recent`, the series from 1977 on, sits on a value axis pinned at zero and the fill measures the warming above the reference period. ``` # the years since the anomaly was last below zero warming_recent = [point for point in warming if point["x"] >= 1977] LineChart( data=warming_recent, style={ "plot_line_color": "#c1121f", # a stronger fill than the theme default "plot_area_alpha": 0.3, }, title="Global mean surface temperature anomaly since 1977", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1980, 2025, 10)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, # fill down to the zero line show_area=True, ymin=0, xmin=1977, xmax=2024, ).show() ``` ### Value labels Where the exact values matter, `show_values` prints the value beside each point; `value_format` formats it (a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string) and `value_step` labels every Nth point when the series is too dense to label them all; by default the step is chosen so that neighbouring labels stay apart. Each label sits above or below its point, wherever it overlaps least, and takes the `plot_value_*` style of the active theme ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). Labeling every yearly value would be noise; labeling the decade means, with the sign, states the trend in numbers. ``` from datachart.constants import VALUE_FORMAT LineChart( data=decades, style={"plot_line_marker": LINE_MARKER.CIRCLE}, title="Mean temperature anomaly by decade", xlabel="Decade", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # print every decade's value, with its sign show_values=True, value_format="{x:+.2f}", value_step=1, ymin=-0.6, ymax=1.4, ).show() ``` ### Reference lines A reference line gives the reader something to measure the line against: a threshold it must not cross, or the moment something happened. `hlines` draws a horizontal line at a value and `vlines` a vertical one at an x position, each a dictionary or a list of them with the position, an optional `label` for the legend and a `style`; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs) and [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs). The x position is in data coordinates, so a line can sit anywhere along the axis. The example marks the 1.5 °C of the Paris Agreement, which is measured against pre-industrial levels and lands near 1.2 °C on this 1951–1980 baseline, and the year the Agreement was adopted. ``` # 1.5 °C above pre-industrial, about 0.3 °C below the 1951-1980 mean used here PARIS_LIMIT = 1.2 PARIS_YEAR = 2015 LineChart( data=warming, subtitle="anomaly", # a dashed line at the limit hlines={ "y": PARIS_LIMIT, "label": "1.5 °C above pre-industrial", "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, # a dotted line at the year of the Agreement vlines={ "x": PARIS_YEAR, "label": "Paris Agreement", "style": {"plot_vline_color": "#555555", "plot_vline_style": LINE_STYLE.DOTTED}, }, title="Global mean surface temperature anomaly", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Reference bands Where a line marks a value, a band marks a range: a period, a tolerance, an interval the line should stay in. `hspans` shades between two values and `vspans` between two x positions, each a dictionary or a list of them with the bounds, an optional `label` and a `style`; the keys are listed in [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs). At least one bound is required and an omitted bound runs to the axis edge, so `{"xmin": 2000}` shades everything from 2000 to the right. The band sits over the grid and under the line, so the data stays readable through it. The example shades the reference period the anomaly is measured against and the range of the anomaly within it. ``` BASELINE = (1951, 1980) baseline_values = [a for y, a in zip(YEARS, ANOMALY) if BASELINE[0] <= y <= BASELINE[1]] LineChart( data=warming, subtitle="anomaly", # shade the reference period vspans={"xmin": BASELINE[0], "xmax": BASELINE[1], "label": "reference period"}, # and the range of the anomaly within it hspans={ "ymin": min(baseline_values), "ymax": max(baseline_values), "label": "reference range", "style": {"plot_hspan_color": "#2a9d8f"}, }, title="Global mean surface temperature anomaly", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Text annotations A line has moments worth naming: a record, a turn, an outlier. `texts` places a note on the chart, with an optional `target` to draw a connector to a data point; the position is in data coordinates by default or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The example names the warmest year on record and the last year below the reference mean. ``` RECORD = max(warming, key=lambda point: point["y"]) LAST_BELOW = max((point for point in warming if point["y"] < 0), key=lambda point: point["x"]) LineChart( data=warming, texts=[ { "text": f"{RECORD['x']}: warmest year on record", "x": 0.55, "y": 0.85, "coords": "axes", "target": (RECORD["x"], RECORD["y"]), }, { "text": f"{LAST_BELOW['x']}: last year below the reference mean", "x": 0.05, "y": 0.62, "coords": "axes", "target": (LAST_BELOW["x"], LAST_BELOW["y"]), }, ], title="Global mean surface temperature anomaly", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` ## Multiple Line Charts To compare several series, pass a list of lists to `data`: each inner list is one line, and the per-series attributes (`subtitle`, `style`, `emphasis`) become lists aligned with it. `warming_by_hemisphere` is such a list, one series per hemisphere, and `subtitle` names them for the legend. A single `style` dictionary applies to every line; a list styles each line separately, with `None` keeping the theme style for that line. The two hemispheres move together for a century and then part, the north warming faster; on one chart the gap is the message. ``` LineChart( # one series per hemisphere data=warming_by_hemisphere, # named for the legend subtitle=HEMISPHERES, # one style per line style=[ {"plot_line_color": "#c1121f"}, {"plot_line_color": "#1d3557"}, ], title="Temperature anomaly by hemisphere", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Emphasis When a chart carries several series, the story is usually about one of them, and emphasis makes it visible. `emphasis` takes one role per series, aligned with `data`: `"highlight"` thickens a line and brings it to the front, `"background"` mutes it (the theme's muted color at a lower alpha, thinner, behind the others, and out of the legend), and `None` leaves it as it is. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. With the global series added as context, the example highlights the Northern Hemisphere against the other two. ``` LineChart( data=[warming] + warming_by_hemisphere, subtitle=["Global"] + HEMISPHERES, # highlight the north, mute the rest emphasis=["background", "highlight", "background"], title="Temperature anomaly, the Northern Hemisphere against the rest", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` `emphasis_rule` picks the lines from the data instead of naming them. It is a one-key dictionary, `{"top": n}`, `{"bottom": n}`, `{"above": v}`, `{"below": v}` (strict) or `{"between": (lo, hi)}` (inclusive), read against a summary of each line's own `y` values: the mean by default, or the `"median"`, `"min"`, `"max"` or `"sum"` named by an extra `"by"` key. The lines that match are highlighted and the rest muted, and an explicit `emphasis` role wins over the rule. Asking for the line that warmed least, the lowest mean, needs no knowledge of which one it is: ``` LineChart( data=[warming] + warming_by_hemisphere, subtitle=["Global"] + HEMISPHERES, # the one line with the lowest mean emphasis_rule={"bottom": 1}, title="Temperature anomaly, the series that warmed least", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Legend `show_legend` lists the series; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). A rising series leaves its empty corner at the top left, which is where the legend goes. ``` from datachart.constants import LEGEND_LOCATION LineChart( data=warming_by_hemisphere, subtitle=HEMISPHERES, title="Temperature anomaly by hemisphere", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # a titled legend in the empty corner legend={"title": "Hemisphere", "location": LEGEND_LOCATION.UPPER_LEFT}, ).show() ``` ### Confidence interval A mean without its spread overstates what is known. When each data point carries a `yerr`, `show_yerr` draws a band from `y - yerr` to `y + yerr` around the line, styled with the same `plot_area_*` attributes as the area under the line. The hemisphere series carry the spread of each five-year window as `yerr`, and the bands show that the two hemispheres are indistinguishable until the late twentieth century, when the bands part. ``` LineChart( data=warming_by_hemisphere, subtitle=HEMISPHERES, style=[ {"plot_line_color": "#c1121f"}, {"plot_line_color": "#1d3557"}, ], title="Temperature anomaly by hemisphere, with the five-year spread", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # draw the spread as a band around each line show_yerr=True, ).show() ``` ### Subplots Lines that cross and overlap hide each other, and when the question is the shape of each series rather than the gap between them, `subplots=True` draws each in its own panel: `subtitle` titles the panels, `title`, `xlabel` and `ylabel` stay global, and `max_cols` limits the panels per row. `sharex=True` and `sharey=True` put the panels on common axes, so a slope in one panel is the same slope in the next; without them each panel scales to its own data, and the slower-warming south would look as steep as the north. ``` LineChart( data=warming_by_hemisphere, subtitle=HEMISPHERES, style=[ {"plot_line_color": "#c1121f"}, {"plot_line_color": "#1d3557"}, ], title="Temperature anomaly by hemisphere", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 50)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # one panel per hemisphere, side by side, on common axes subplots=True, max_cols=2, sharex=True, sharey=True, ).show() ``` ## Additional Features ### Datetime axis Yearly data sits fine on a numeric axis, but daily or monthly data wants a real time axis, where points land at their elapsed time and the ticks label themselves sensibly. An `x` value that is a real temporal object (a `datetime`, a `date`, a `numpy.datetime64` or a pandas `Timestamp`) does exactly that: the ticks pick a concise, non-repeating label for the visible span, with the part every label shares (such as `2024-Apr`) as an offset. Date strings such as `"2024-03-01"` are **not** parsed; they draw as unordered categories, like any other string. `daily_index` holds sixty days of an illustrative index level. ``` from datetime import date, timedelta # daily closing level of an index over one spring (illustrative) start = date(2024, 3, 1) daily_index = [ {"x": start + timedelta(days=i), "y": 100 + round(2.5 * i - 0.05 * i * i, 1)} for i in range(60) ] LineChart( data=daily_index, title="Daily index level", xlabel="Date", ylabel="Level", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` On a time axis everything that takes an x position takes a date: `xticks`, `xmin` and `xmax`, reference lines and bands, and annotation targets. `xticks_format` labels the ticks with a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern instead of the automatic labels. The example fixes the ticks to the first and the fifteenth of each month, marks a rebalancing date, and shades an earnings season. ``` from datachart.constants import DATE_FORMAT LineChart( data=daily_index, title="Daily index level", xlabel="Date", ylabel="Level", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # ticks, limits, lines and bands all take dates xticks_format=DATE_FORMAT.MONTH_DAY, xticks=[date(2024, 3, 1), date(2024, 3, 15), date(2024, 4, 1), date(2024, 4, 15)], xmin=date(2024, 3, 1), xmax=date(2024, 4, 20), vlines={"x": date(2024, 3, 20), "label": "rebalance"}, vspans={"xmin": date(2024, 4, 1), "xmax": date(2024, 4, 10), "label": "earnings season"}, yticks_format=VALUE_FORMAT.INTEGER, show_legend=True, ).show() ``` ### Axis scales A quantity that grows by a constant factor looks like a hockey stick on a linear axis and like a straight line on a logarithmic one, and only the second lets the reader judge whether the growth rate changed. `scaley` and `scalex` take a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member: `LINEAR`, `LOG`, `SYMLOG` (log on both sides of zero) or `ASINH`. `transistors`, defined in a hidden cell, holds the transistor count of a representative microprocessor per year from the Intel 4004 (1971) to the Apple M1 Ultra (2022), Moore's law in sixteen data points rounded from the manufacturers' figures. On a linear scale the first forty years collapse onto the x-axis; on a log scale the doubling every two years becomes the straight line it is famous for. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: LineChart( data=transistors, style={"plot_line_marker": LINE_MARKER.CIRCLE}, title=f"Transistors per microprocessor on the '{scale}' scale", xlabel="Year", ylabel="Transistors", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, # the scale of the value axis scaley=scale, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `x`, `y` and `yerr` keys, and renaming every record just to plot it is a chore. Instead, tell `LineChart` which keys to read with the `x`, `y` and `yerr` arguments. `readings` stores the Northern Hemisphere series the way a data export might, under `year`, `anomaly` and `spread`: ``` readings = [ {"year": point["x"], "anomaly": point["y"], "spread": point["yerr"]} for point in warming_by_hemisphere[0] ] readings[:3] ``` ``` LineChart( data=readings, # the keys that hold the x, y and error values x="year", y="anomaly", yerr="spread", title="Northern Hemisphere temperature anomaly", xlabel="Year", ylabel="Anomaly (°C)", xticks=list(range(1880, 2025, 20)), yticks_format="{x:+.1f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_yerr=True, ).show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: A ROC Curve (Custom Data Keys and an Equal Aspect Ratio) `roc_curves` holds the receiver operating characteristic of two illustrative binary classifiers: each point is the false positive rate (`fp`) and the true positive rate (`tp`) at one decision threshold, so the keys are mapped with the `x` and `y` arguments. The question is which classifier separates the classes better, and the answer is the area under each curve. A ROC curve is read against the diagonal of chance, so the axes keep an equal aspect ratio, and the area under each curve is filled with a hatch and its size printed in the subtitle; the subplots share both axes so the two areas compare. ``` from datachart.constants import ASPECT_RATIO, HATCH_STYLE LineChart( data=roc_curves, # name each curve by its area subtitle=[f"{name} (AUC {auc(points):.2f})" for name, points in ROC_POINTS.items()], # the points are stored as "fp" and "tp", instead of "x" and "y" x="fp", y="tp", # hatch the area under each curve (a single style applies to every chart) style={"plot_area_hatch": HATCH_STYLE.DIAGONAL}, title="ROC curves", xlabel="False positive rate", ylabel="True positive rate", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, xmin=0, xmax=1, ymin=0, ymax=1, show_area=True, subplots=True, sharex=True, sharey=True, # one unit the same length on both axes aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Example 2: Training Runs (A Confidence Band on a Log Scale, and the Best Run Picked by a Rule) `training_loss` holds the validation loss of three illustrative training methods, evaluated every five steps over 200 steps and averaged over several runs, with the standard deviation across the runs as `spread`. The question is which method converges lowest, and whether the runs agree. The loss decays toward a floor, so a log value axis keeps the late-training differences readable; `show_yerr` draws the run-to-run spread as a band around each mean; and `emphasis_rule` highlights the method with the lowest final loss without naming it, so the same cell keeps working when the methods change. ``` LineChart( data=training_loss, subtitle=list(LOSS_CURVES), # the points are stored as "step", "loss" and "spread" x="step", y="loss", yerr="spread", # the method with the lowest final loss emphasis_rule={"bottom": 1, "by": "min"}, title="Validation loss during training", xlabel="Training step", ylabel="Validation loss", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # draw the run-to-run spread as a band show_yerr=True, # a log value axis scaley=SCALE.LOG, ).show() ``` ### Example 3: Did the Campaign Work? (Reference Lines, a Band, a Note, and a Panel) `weekly_visitors` holds sixteen weeks of illustrative weekly unique visitors of a website, and `weekly_signups` the sign-ups of the same weeks. A marketing campaign ran from week 7 to week 10, and the hosting plan is sized for 60,000 weekly visitors. The chart has to answer two questions at once, whether the campaign moved the numbers and when the plan needs upgrading: a band shades the campaign weeks, a line marks the capacity, and a note names the week it was first exceeded. Visitors and sign-ups live on different scales, so [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) overlays the two line charts with the sign-ups on a second value axis; the reference marks, declared on the visitor chart, travel with it. ``` from datachart.utils import Panel visitors = LineChart( data=weekly_visitors, subtitle="unique visitors (thousands)", style={"plot_line_marker": LINE_MARKER.CIRCLE}, # shade the campaign weeks vspans={"xmin": CAMPAIGN[0], "xmax": CAMPAIGN[1], "label": "campaign"}, # mark the hosting capacity hlines={ "y": CAPACITY, "label": "hosting capacity", "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, # name the week the capacity was first exceeded texts={ "text": f"over capacity in week {OVER_CAPACITY['x']}", "x": 0.05, "y": 0.85, "coords": "axes", "target": (OVER_CAPACITY["x"], OVER_CAPACITY["y"]), }, ) signups = LineChart( data=weekly_signups, subtitle="sign-ups", style={"plot_line_color": "#2a9d8f", "plot_line_style": LINE_STYLE.DASHED}, ) Panel( [ {"figure": visitors, "y_axis": "left"}, {"figure": signups, "y_axis": "right"}, ], title="Weekly website traffic around the campaign", xlabel="Week", ylabel_left="Visitors (thousands)", ylabel_right="Sign-ups", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ymin=0, ymin_right=0, ).show() ``` # Stacked Area Chart A stacked area chart shows how a total splits into parts along an ordered axis, usually time: each series fills a band on top of the one below, so the top edge traces the total and the band thicknesses show how it is made up. This guide shows how to create stacked area charts with the [datachart.charts.StackedAreaChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.charts.StackedAreaChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-stacked-area-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import StackedAreaChart ``` ## Basics The examples in this guide share one dataset: the world's electricity generation by source, in terawatt-hours (TWh) per year from 2000 to 2023, for coal, gas, nuclear, hydro, wind, solar, and everything else (oil, bioenergy, geothermal). The values are approximate: rounded and smoothed from the annual figures in Ember's *Global Electricity Review*, so they follow the real trends and end on the 2023 totals, but a single in-between year should not be quoted from them. They live in a hidden cell. `generation` holds one series per source, in the order they stack; `generation_grouped` folds the same numbers into three groups (fossil for coal and gas, low-carbon for nuclear, hydro, wind and solar, and other) for the charts that need fewer bands. Generation is a textbook part-to-whole story: the total nearly doubled while the mix underneath it shifted, coal held its ground, and wind and solar grew from almost nothing to more than a tenth of the whole. The data is a list of series, one per source, the first at the bottom of the stack. Each series is a list of points with the year as `x` and the generation as `y`, and every series holds the same years in the same order, because the bands sit on top of one another point by point: ``` {source: points[:3] for source, points in zip(SOURCES, generation)} ``` **Basic example.** Only the `data` argument is required. Each source fills the band between the sources below it and its own value, so the top edge of the stack is the world's total generation, and the axes start where the stack does. A single list of points draws one band on its own. The default palette has six colors, so the seventh band repeats the first; the [Band style](#band-style) section gives every source a color of its own. ``` StackedAreaChart( # add the data to the chart data=generation ).show() ``` ## Customizing the Stacked Area Chart Every customization is either a keyword argument of `StackedAreaChart` or a `plot_area_*` / `plot_stackedarea_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ---------------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | set custom tick positions and labels | `xticks`, `xticklabels`, `yticks`, `yticklabels`, `xtickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | format the tick labels | `xticks_format`, `yticks_format` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | fix the aspect ratio of the axes | `aspect_ratio` | [Figure size and grid](#figure-size-and-grid) | | name the series, title and place the legend | `subtitle`, `show_legend`, `legend` | [Legend](#legend) | | change the band colors, alpha, hatch, or edges | `style={"plot_area_color": ..., "plot_stackedarea_alpha": ...}` | [Band style](#band-style) | | outline the top of every band | `style={"plot_stackedarea_outline": True}` | [Band style](#band-style) | | show shares instead of totals | `baseline=STACKED_AREA_BASELINE.PERCENT` | [Baseline](#baseline) | | centre the stack or draw a streamgraph | `baseline=STACKED_AREA_BASELINE.SYM`, `.WIGGLE`, `.WEIGHTED_WIGGLE` | [Baseline](#baseline) | | print the values on the bands | `show_values`, `value_format`, `value_step` | [Value labels](#value-labels) | | highlight some series, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a year or a level | `vlines`, `hlines` | [Reference lines](#reference-lines) | | keep reference lines visible over the bands | `style={"plot_area_zorder": 1}` | [Reference lines](#reference-lines) | | shade a period or a range of levels | `vspans`, `hspans` | [Reference bands](#reference-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw each series unstacked in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | draw a line over the stack, or arrange several | `Panel`, `Grid` | [Composing stacked areas](#composing-stacked-areas) | | plot against real dates | `datetime` objects as `x`, `xticks_format` | [Datetime axis](#datetime-axis) | | plot data with other key names | `x`, `y` | [Custom data keys](#custom-data-keys) | | restyle every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `baseline` | [`STACKED_AREA_BASELINE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.STACKED_AREA_BASELINE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.StackedAreaStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.typings.StackedAreaStyleAttrs) and [datachart.typings.AreaStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AreaStyleAttrs) types; the full list of parameters is in the [datachart.charts.StackedAreaChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.charts.StackedAreaChart) reference. ### Title, axis labels and ticks A stack without a title and axis labels is a pile of colors; `title`, `xlabel` and `ylabel` say what is stacked and in what unit. The ticks are the reader's ruler: `xticks` and `yticks` set their positions (`xticklabels` and `yticklabels` replace the labels), `xticks_format` and `yticks_format` format them (a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or a `"{x:.1f}"` style string on a value axis), and `xtickrotate` and `ytickrotate` tilt them. `xmin`, `xmax`, `ymin` and `ymax` fix the axis range; a stack rests on zero, so the value axis already starts there, and `ymax` is mostly used to leave headroom for a legend or a note. The `scalex` and `scaley` parameters take a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member as on every chart, but a logarithmic value axis cannot show the zero a stack rests on and distorts the band thicknesses, so leave them linear. Here the years tick every five years and end on the last year of the data, and the thousands print with a separator. ``` StackedAreaChart( data=generation, # add the title title="World electricity generation", # add the x and y axis labels xlabel="Year", ylabel="Generation (TWh)", # a tick every five years, ending on the last year; thousands with a separator xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", ).show() ``` ### Figure size and grid Twenty-four years of seven bands read best wide, with enough height for the thin bands to stay visible. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. Grid lines let the eye carry the top of the stack across to the value axis; `show_grid` draws them with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member, `Y` for the value axis alone, `X` for the time axis, `BOTH` for both. `aspect_ratio` fixes the ratio of the axes rather than of the figure ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); a stacked area chart never needs it, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID StackedAreaChart( data=generation, title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the value axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Legend Seven bands are seven colors, and only a legend says which is which. `subtitle` names the series, one name per series in the order of `data`, and `show_legend` lists them; the legend follows the input order, so the bottom of the stack comes first. `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). A rising stack leaves its empty corner at the top left, and a little headroom from `ymax` makes room for a two-column legend there. ``` from datachart.constants import LEGEND_LOCATION StackedAreaChart( data=generation, # name the series, bottom of the stack first subtitle=SOURCES, show_legend=True, # a titled, two-column legend in the empty corner legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, # headroom for the legend ymax=36000, ).show() ``` ### Band style Colors carry meaning in a stack: sources that belong together should look alike, and a reader should not have to consult the legend for every band. The `style` dictionary sets the look of the bands: the fill color and hatch come from the `plot_area_*` attributes ([AreaStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AreaStyleAttrs)), and `plot_stackedarea_alpha`, the stroke between the bands (`plot_stackedarea_edge_color`, `plot_stackedarea_edge_width`) and `plot_stackedarea_outline`, which draws the top edge of every band as a line in the `plot_line_*` style, are the stack's own ([StackedAreaStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.typings.StackedAreaStyleAttrs)). A single dictionary applies to every series; a list aligned with `data` styles each on its own, and any attribute left out keeps the value of the active theme. `SOURCE_STYLE` colors the fossil sources in warm tones, the low-carbon ones in cool tones and the rest in grey, with a hatch from [HATCH_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HATCH_STYLE) marking the miscellaneous band. It also sets `plot_area_zorder`, the drawing order of the bands: by default the bands are drawn over reference lines and over lines composed with them, and a lower value sends them underneath (the [Reference lines](#reference-lines) section shows why that matters). The rest of the guide reuses `SOURCE_STYLE`. ``` from datachart.constants import HATCH_STYLE # fossil sources warm, low-carbon sources cool, the rest grey SOURCE_COLORS = { "Coal": "#5b4636", "Gas": "#e07b39", "Nuclear": "#7b5ea7", "Hydro": "#2e86ab", "Wind": "#7fc8e8", "Solar": "#f4c542", "Other": "#b8b8b8", } SOURCE_STYLE = [ { "plot_area_color": SOURCE_COLORS[source], "plot_area_hatch": HATCH_STYLE.DIAGONAL if source == "Other" else None, "plot_stackedarea_alpha": 0.9, # a thin white stroke between the bands "plot_stackedarea_edge_color": "white", "plot_stackedarea_edge_width": 0.8, # draw the bands under reference lines and composed lines "plot_area_zorder": 1, } for source in SOURCES ] StackedAreaChart( data=generation, # one style per series style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ymax=36000, ).show() ``` ### Baseline The same stack answers different questions depending on where its first band starts, and `baseline` picks that with a [STACKED_AREA_BASELINE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.STACKED_AREA_BASELINE) member. The default, `ZERO`, rests the stack on zero, so the top edge is the total: the chart for *how much is generated, and by what*. `PERCENT` scales every year to 100, so each band is a share of that year's total and the total itself disappears: the chart for *how has the mix changed*. The two charts tell different stories on the same numbers: in terawatt-hours coal has never been higher, in shares it has been slipping since the early 2010s, because everything else grew faster. ``` from datachart.constants import STACKED_AREA_BASELINE StackedAreaChart( data=generation, # every year sums to 100: the bands are shares baseline=STACKED_AREA_BASELINE.PERCENT, style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, title="World electricity mix", xlabel="Year", ylabel="Share of generation (%)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` Sometimes neither the total nor the shares are the question, only how each part rises and falls: a chart of many series over time, where the reader follows the shape of every band. The remaining baselines float the stack for that, and give up the value axis in exchange. `SYM` centres the stack on zero, so it grows symmetrically up and down, which still shows the total as the overall width but no single band against the axis. `WIGGLE` moves the baseline at every year so that the bands, taken together, slope as little as possible: the streamgraph. `WEIGHTED_WIGGLE` does the same but weighs each band by its size, so the large bands stay the flattest and the eye reads their changes as thickness, not as slope. On all three only the thickness of a band carries its value, so the value axis means nothing and `yticks=[]` hides it. Use them when the series are many and the shapes matter more than any number. The three views below draw the same seven sources: `SYM` and `WIGGLE` tilt the large coal band downward as the stack grows, while `WEIGHTED_WIGGLE` keeps the large bands the flattest, so the growth shows as thickness. ``` for baseline in [ STACKED_AREA_BASELINE.SYM, STACKED_AREA_BASELINE.WIGGLE, STACKED_AREA_BASELINE.WEIGHTED_WIGGLE, ]: StackedAreaChart( data=generation, # a floating stack: only the band thickness carries the value baseline=baseline, style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, title=f"World electricity generation, '{baseline}' baseline", xlabel="Year", xticks=[2000, 2005, 2010, 2015, 2020, 2023], # the value axis has no meaning on a floating stack yticks=[], figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Value labels When the shares themselves are the message, `show_values` prints each series' value at the midpoint of its band, `value_format` formats it (a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string), and `value_step` labels every Nth year, because twenty-four labels per band would overlap (by default the step is the smallest that keeps neighbouring labels apart). The label font size, color and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). Seven bands are too many to label legibly, so the example uses the three groups on the percent baseline, labeled every fourth year; the first label sits on the left edge, and `xmin` moves the edge out a little to leave it whole. The share of coal and gas reads off directly: 56% in 2000, up to 63% in 2012, and back to 58% in 2020. ``` StackedAreaChart( data=generation_grouped, baseline=STACKED_AREA_BASELINE.PERCENT, style=[{"plot_area_color": color} for color in ("#c8553d", "#2e86ab", "#b8b8b8")], subtitle=list(GROUPS), show_legend=True, legend={"title": "Group", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, # print the share of every group every fourth year show_values=True, value_format="{:.0f}%", value_step=4, title="World electricity mix by group", xlabel="Year", ylabel="Share of generation (%)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], # room for the labels on the left edge xmin=1999, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Emphasis A stack of seven bands makes no single point; emphasis picks one. `emphasis` takes one role per series, aligned with `data` like `subtitle` and `style`: `"highlight"` brings a band to the front, `"background"` mutes it (the theme's muted color at a lower alpha, dropped from the legend), and `None` leaves it as it is. The stack itself does not change: a muted band keeps its place and thickness, so the highlighted bands stay exactly where the data puts them. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Highlighting wind and solar on the percent baseline turns the rest of the mix into context, and shows the two of them growing from a sliver to more than a tenth of the whole. ``` from datachart.constants import EMPHASIS StackedAreaChart( data=generation, baseline=STACKED_AREA_BASELINE.PERCENT, style=SOURCE_STYLE, # wind and solar are the question, the rest the context emphasis=[ EMPHASIS.HIGHLIGHT if source in ("Wind", "Solar") else EMPHASIS.BACKGROUND for source in SOURCES ], subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT}, title="Wind and solar in the world electricity mix", xlabel="Year", ylabel="Share of generation (%)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `emphasis_rule` picks the series from the data instead of naming them. It is a one-key dictionary: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive), read against a summary of each series' own `y` values (never its stacked position): the mean by default, or the `"median"`, `"min"`, `"max"` or `"sum"` named by a `"by"` key. The series that match are highlighted, the rest muted, and an explicit `emphasis` role wins over the rule. The two smallest sources on average over the period are wind and solar, so `{"bottom": 2}` finds them without naming them; the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/#emphasis-picked-by-a-rule) guide covers the rule on every chart. ``` StackedAreaChart( data=generation, style=SOURCE_STYLE, # the two series with the smallest mean generation emphasis_rule={"bottom": 2}, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT}, title="The two smallest sources, on average since 2000", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Reference lines A reference line puts a year or a level on the chart: an event on the time axis, a benchmark on the value axis. `vlines` draws a vertical line at an `x` value and `hlines` a horizontal one at a `y` value; each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style` with the `plot_vline_*` or `plot_hline_*` attributes ([VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs)); [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE) holds the dash patterns. Two things need care on a stack. A line given for the whole chart is drawn by each series, so a stack of seven would draw and list every line seven times; attach the lines to one series instead, with a list aligned with `data` that holds the lines in the first slot and `None` in the rest. And the bands are drawn over the lines by default, which hides a line wherever it crosses the stack; the `"plot_area_zorder": 1` in `SOURCE_STYLE` draws the bands underneath. The example marks the Paris Agreement and the pandemic year, and the level of total generation in 2000: every band above that line is growth the world has added since. ``` from datachart.constants import LINE_STYLE StackedAreaChart( data=generation, style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, # two events on the time axis, attached to the first series so they draw once vlines=[ [ {"x": 2015, "label": "Paris Agreement", "style": {"plot_vline_color": "#1f1f1f", "plot_vline_style": LINE_STYLE.DASHED}}, {"x": 2020, "label": "COVID-19", "style": {"plot_vline_color": "#1f1f1f", "plot_vline_style": LINE_STYLE.DOTTED}}, ] ] + [None] * (len(SOURCES) - 1), # the total generation of 2000, on the value axis hlines=[{"y": TOTALS[0], "label": "2000 total", "style": {"plot_hline_color": "#c1121f", "plot_hline_width": 1.5}}] + [None] * (len(SOURCES) - 1), title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ymax=40000, ).show() ``` ### Reference bands Where a line marks an instant, a band shades a period, or a range of levels. `vspans` shades between two `x` values and `hspans` between two `y` values; each is a dictionary or a list of them with the bounds (`xmin` / `xmax` or `ymin` / `ymax`, an omitted bound runs to the axis edge), an optional `label` for the legend and a `style` with the `plot_vspan_*` or `plot_hspan_*` attributes ([VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs)). A band given for the whole chart is shaded once, so no per-series list is needed. The example shades the two years of the financial crisis, the one period before 2020 in which the world's generation stopped growing, and the stack shows where the dip came from: coal, gas and nuclear all fell in 2009, while hydro and wind kept growing. ``` StackedAreaChart( data=generation, style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, # shade the financial crisis; one band applies to the whole stack vspans={"xmin": 2008, "xmax": 2009, "label": "financial crisis", "style": {"plot_vspan_color": "#c8553d"}}, title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ymax=36000, ).show() ``` ### Text annotations A note says what a shape means. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (year, value) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. In a stack the point to target is the top of a band, which is the sum of that series and every series below it, so the example computes it. Like a reference line, a note given for the whole chart is drawn once per series, so it goes in the first slot of a per-series list. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below points at the year solar generation passed 1,000 TWh. ``` SOLAR_1000 = next(year for year, twh in zip(YEARS, GENERATION["Solar"]) if twh >= 1000) # the top of the solar band that year: solar plus everything stacked below it solar_top = sum(GENERATION[source][YEARS.index(SOLAR_1000)] for source in SOURCES[: SOURCES.index("Solar") + 1]) StackedAreaChart( data=generation, style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, # a note pinned to the axes, pointing at the top of the solar band; drawn once texts=[ { "text": f"solar passes 1,000 TWh in {SOLAR_1000}", "x": 0.45, "y": 0.92, "coords": "axes", "target": (SOLAR_1000, solar_top), } ] + [None] * (len(SOURCES) - 1), title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ymax=36000, ).show() ``` ## Multiple Stacked Area Charts A stacked area chart is already a multi-series chart: the list-of-lists `data`, the aligned `subtitle`, `style` and `emphasis` lists, and the legend are covered above. This section takes the stack apart into subplots, and puts stacked area figures together with other figures. ### Subplots Stacking makes the total easy to read and the individual sources hard: only the bottom band has a flat base, and every other band's shape is bent by the bands below it. `subplots=True` draws each series unstacked in its own panel, from zero, so the shape of every source can be read on its own. `subtitle` titles the panels; `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the panels per row. `sharex=True` keeps one time axis for all of them and `sharey=True` one value axis, so a band in one panel is comparable with a band in the next; the shared value axis is fixed with `ymax`, a little above coal's peak. Seen this way, nuclear's flat line and the two hockey sticks of wind and solar are plain, and coal's rise is not the only story. ``` StackedAreaChart( data=generation, style=SOURCE_STYLE, subtitle=SOURCES, # one panel per source, unstacked, two per row subplots=True, max_cols=2, # one time axis and one value axis for all panels sharex=True, sharey=True, ymax=11000, title="World electricity generation by source", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2010, 2020], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_TALL, ).show() ``` ### Composing stacked areas A stacked area figure composes like any other. [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) draws several figures on one set of axes: a line chart drawn over the stack sits on top of it without joining it, which is the natural way to put a related total or a target against the stack. The line below is the low-carbon total (nuclear, hydro, wind and solar together) over the full mix, and it answers a question the stack cannot: how the four low-carbon sources together compare with coal, the bottom band. The line meets the top of the coal band around 2020, so the low-carbon sources now generate about as much as coal does. `Panel` takes the figures, the shared labels, and its own `show_legend`; the bands of `SOURCE_STYLE` sit underneath, so the line stays visible across them (a figure's `"z_order"` in `Panel` sets the order explicitly). ``` from datachart.charts import LineChart from datachart.utils import Panel low_carbon = [ {"x": year, "y": sum(GENERATION[source][i] for source in GROUPS["Low-carbon"])} for i, year in enumerate(YEARS) ] stack = StackedAreaChart(data=generation, style=SOURCE_STYLE, subtitle=SOURCES) total = LineChart( data=low_carbon, subtitle="low-carbon total", style={"plot_line_color": "#1f1f1f", "plot_line_style": LINE_STYLE.DASHED, "plot_line_width": 2}, ) Panel( [stack, total], title="World electricity generation", xlabel="Year", ylabel_left="Generation (TWh)", figsize=FIG_SIZE.FULL_MEDIUM, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, ymax=36000, ).show() ``` [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) arranges stacked area figures next to other figures, each in its own cell, with nested lists as rows. The generation in terawatt-hours spans the top row, and the mix in percent sits next to a bar chart of the 2023 generation by source below it; each figure keeps its own title and baseline, and the grid supplies the shared `xlabel`. ``` from datachart.charts import BarChart from datachart.utils import Grid top = StackedAreaChart( data=generation, style=SOURCE_STYLE, subtitle=SOURCES, title="Generation (TWh)", show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, yticks_format="{x:,.0f}", ymax=36000, ) left = StackedAreaChart( data=generation, baseline=STACKED_AREA_BASELINE.PERCENT, style=SOURCE_STYLE, subtitle=SOURCES, title="Mix (%)", xlabel="Year", ) right = BarChart( data=[{"label": source, "y": GENERATION[source][-1]} for source in SOURCES], style={"plot_bar_color": "#5b4636"}, title="2023 generation (TWh)", xtickrotate=45, yticks_format="{x:,.0f}", ) Grid([[top], [left, right]], title="World electricity, 2000 to 2023", figsize=FIG_SIZE.FULL_TALL).show() ``` ## Additional Features ### Datetime axis Yearly data sits fine on a numeric axis, but monthly or daily data wants a real time axis, where points land at their elapsed time and the ticks label themselves sensibly. An `x` value that is a real temporal object (a `datetime`, a `date`, a `numpy.datetime64` or a pandas `Timestamp`) does exactly that; date strings such as `"2015-01-01"` are **not** parsed and draw as unordered categories. On a time axis everything that takes an x position takes a date: `xticks`, `xmin` and `xmax`, reference lines and bands, and annotation targets, and `xticks_format` labels the ticks with a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. `generation_dated` keys the same generation by the first day of each year; the example starts the axis at 2010, ticks it every five years, and marks the day the Paris Agreement was adopted. ``` from datetime import date from datachart.constants import DATE_FORMAT generation_dated = [ [{"x": date(year, 1, 1), "y": twh} for year, twh in zip(YEARS, GENERATION[source])] for source in SOURCES ] StackedAreaChart( data=generation_dated, style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, # the axis reads as time: a datetime limit, one tick per five years, a dated event xmin=date(2010, 1, 1), xticks=[date(year, 1, 1) for year in range(2010, 2024, 5)], xticks_format=DATE_FORMAT.YEAR, vlines=[ {"x": date(2015, 12, 12), "label": "Paris Agreement", "style": {"plot_vline_color": "#1f1f1f", "plot_vline_style": LINE_STYLE.DASHED}} ] + [None] * (len(SOURCES) - 1), title="World electricity generation since 2010", xlabel="Year", ylabel="Generation (TWh)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ymax=36000, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `x` and `y` keys, and renaming every record just to plot it is a chore. Instead, tell `StackedAreaChart` which keys to read with the `x` and `y` arguments. `generation_records` stores the same generation the way a CSV export would, one record per year and source with a `year` and a `twh` key: ``` generation_records = [ [{"year": year, "twh": twh} for year, twh in zip(YEARS, GENERATION[source])] for source in SOURCES ] generation_records[0][:2] ``` ``` StackedAreaChart( data=generation_records, # the keys that hold the x and y values x="year", y="twh", style=SOURCE_STYLE, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ymax=36000, ).show() ``` ### Themes A theme sets the palette, the band alpha, the stroke between the bands and the furniture of every chart at once, so a report keeps one look without restyling each figure; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows the whole suite under each. Apply one with [config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) from the [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) constants, and reset the configuration afterwards so the following charts draw in the default again. The style is resolved when the chart is created, so the figure keeps the theme when it is shown after the reset. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.INK) figure = StackedAreaChart( data=generation, subtitle=SOURCES, show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, title="World electricity generation", xlabel="Year", ylabel="Generation (TWh)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_MEDIUM, ymax=36000, ) config.reset_config() figure.show() ``` ## Real-World Examples The examples below put the features above to work on the generation data, each one answering a question. Any derived data lives in a hidden cell; each example says what its data is and how it is derived. ### Example 1: Has the Fossil Share of Electricity Moved? (Grouped Series, Percent Baseline, Emphasis, and a Note) Two decades of climate policy, and how much of the world's electricity still comes from burning coal and gas? `generation_grouped` folds the seven sources into fossil (coal, gas), low-carbon (nuclear, hydro, wind, solar) and other, and the percent baseline turns the groups into shares, because the question is about the share, not the amount. The fossil band is highlighted and the other two muted, a dashed line marks the halfway point (drawn over the bands with `plot_area_zorder`), and a note gives the shares at both ends and at the peak. The chart makes one point: coal and gas never fell below half, and after rising to 63% in the early 2010s the share of coal and gas is back near where it was in 2000, because total generation grew almost as fast as the low-carbon sources did. (Oil sits in the other group, so the fossil band here is coal and gas only.) ``` StackedAreaChart( data=generation_grouped, # shares, not amounts baseline=STACKED_AREA_BASELINE.PERCENT, style=GROUP_STYLE, subtitle=list(GROUPS), # fossil is the question, the rest the context emphasis=[EMPHASIS.HIGHLIGHT, EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND], show_legend=True, legend={"title": "Group", "location": LEGEND_LOCATION.UPPER_RIGHT}, # the halfway mark, attached to the first series so it draws once hlines=[ {"y": 50, "label": "half of generation", "style": {"plot_hline_color": "#1f1f1f", "plot_hline_style": LINE_STYLE.DASHED}}, None, None, ], # the shares at both ends and the peak, pointing at the top of the fossil band in 2023 texts=[ { "text": f"coal and gas: {FOSSIL_SHARE[0]:.0f}% in 2000,\n{max(FOSSIL_SHARE):.0f}% in {PEAK_YEAR}, {FOSSIL_2023:.0f}% in 2023", "x": 0.45, "y": 0.8, "coords": "axes", "target": (2023, FOSSIL_2023), }, None, None, ], title="Fossil share of world electricity", xlabel="Year", ylabel="Share of generation (%)", xticks=[2000, 2005, 2010, 2015, 2020, 2023], figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Total, Mix, or Shape? Three Baselines on One Dataset (Zero, Percent and Wiggle Baselines, Outlines, and a Grid) The baseline is a choice about the question, and the same data answers three of them. On the zero baseline the top edge is the total: the world generates nearly twice the electricity it did in 2000. On the percent baseline the total is gone and only the mix remains: coal's share has slipped since the early 2010s and wind and solar took most of what it lost. On the wiggle baseline neither total nor share can be read, only the shape of each band, and a streamgraph of the three renewable sources (hydro, wind and solar) shows steady hydro against the late surge of wind and solar, the bands outlined so that the thin early years stay visible. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts the three views in one figure: the total across the top row, the mix and the streamgraph side by side below it. ``` total = StackedAreaChart( data=generation, style=SOURCE_STYLE, subtitle=SOURCES, title="How much: generation (TWh)", show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, yticks_format="{x:,.0f}", ymax=36000, ) mix = StackedAreaChart( data=generation, baseline=STACKED_AREA_BASELINE.PERCENT, style=SOURCE_STYLE, subtitle=SOURCES, title="What share: mix (%)", xlabel="Year", ) shape = StackedAreaChart( data=renewables, baseline=STACKED_AREA_BASELINE.WIGGLE, # outlined bands keep the thin early years visible style=[{**band, "plot_stackedarea_outline": True, "plot_line_width": 0.8} for band in RENEWABLES_STYLE], subtitle=RENEWABLES, title="What shape: renewables", xlabel="Year", show_legend=True, legend={"title": "Source", "location": LEGEND_LOCATION.UPPER_LEFT}, yticks=[], ) Grid([[total], [mix, shape]], title="World electricity, three baselines", figsize=FIG_SIZE.FULL_TALL).show() ``` ### Example 3: Wind and Solar, in Terawatt-Hours and in Share (A Panel with a Second Axis, a Reference Line, and a Note) How much do wind and solar generate, and how much of the world's electricity is that? The two answers live in different units. `wind_solar`, defined in a hidden cell, stacks the two sources in terawatt-hours, and `wind_solar_share` is their combined share of the world's generation in percent. [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) draws the stack on the left value axis and a line chart of the share on a right one, so a single chart answers both questions. A vertical line marks the year the share passed one tenth, and a note on the line chart, placed on the right axis with it, points at that point of the share line. Together the two sources passed a tenth of the world's electricity in 2021. ``` amount = StackedAreaChart( data=wind_solar, style=WIND_SOLAR_STYLE, subtitle=["Wind", "Solar"], ) share = LineChart( data=wind_solar_share, subtitle="share of world electricity", style={"plot_line_color": "#1f1f1f", "plot_line_style": LINE_STYLE.DASHED}, # the year the share passed a tenth vlines={"x": TENTH["x"], "style": {"plot_vline_color": "#c8553d", "plot_vline_style": LINE_STYLE.DOTTED}}, # the note sits on the share axis with the line chart texts={ "text": f"a tenth of the world's\nelectricity in {TENTH['x']}", "x": 0.08, "y": 0.6, "coords": "axes", "target": (TENTH["x"], TENTH["y"]), }, ) Panel( [ {"figure": amount, "y_axis": "left"}, {"figure": share, "y_axis": "right"}, ], title="Wind and solar generation", xlabel="Year", ylabel_left="Generation (TWh)", ylabel_right="Share of world electricity (%)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_legend=True, legend={"location": LEGEND_LOCATION.UPPER_LEFT}, ymin=0, ymin_right=0, ).show() ``` # Bump Chart A bump chart shows rank over time: one line per series, rank 1 at the top, so the crossings answer *who overtook whom, and when*. It suits league tables, popularity rankings and any table whose order matters more than the gaps between the values. This guide shows how to create bump charts with the [datachart.charts.BumpChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.charts.BumpChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-bump-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import BumpChart ``` ## Basics The examples in this guide share one dataset: the populations of ten large countries in 1980, 1990, 2000, 2010, 2020 and 2023, in millions (source: United Nations, World Population Prospects 2022, rounded). The data lives in a hidden cell. `population` holds one series per country, and `COUNTRIES` holds the country names in the same order; the ranks in the charts are ranks among these ten. Population is a ranking story as much as a growth one: India passed China in 2023, Pakistan and Nigeria climbed past Brazil, and Russia and Japan slid down the table. Each series is a list of `{x, y}` points, with the year as `x` and the population as `y`. These are the values, not the ranks: the chart ranks them at every year. The first two points of the first series: ``` population[0][:2] ``` **Basic example.** Only the `data` argument is required, and `subtitle` names the lines: each name prints beside the line's last point, in the line's color, so the chart needs no legend and no rank axis. At every year the most populous country takes rank 1 at the top: ``` BumpChart( # one series per country data=population, # name the lines at their ends subtitle=COUNTRIES, ).show() ``` ## Customizing the Bump Chart Every customization is either a keyword argument of `BumpChart` or an attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | choose, label, or rotate the period ticks | `xticks`, `xticklabels`, `xtickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure or show the grid | `figsize`, `show_grid` | [Figure size and grid](#figure-size-and-grid) | | rank the lowest value first | `rank_by=BUMP_RANK.VALUE_ASCENDING` | [Ranking](#ranking) | | draw ranks I already have | `rank_by=BUMP_RANK.GIVEN` | [Ranking](#ranking) | | leave a gap where a series has no rank | leave the point out of the series | [Ranking](#ranking) | | name the lines at the start or at both ends | `show_labels`, `label_position` | [End labels](#end-labels) | | curve the lines or hide the markers | `line_curve`, `show_markers` | [Line shape](#line-shape) | | change the line width, markers, or colors | `style={"plot_bump_line_width": ..., "plot_line_color": ...}` | [Line style](#line-style) | | print the values behind the ranks | `show_values`, `value_format`, `value_step` | [Value labels](#value-labels) | | highlight some series, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a period or shade a range of places | `vlines`, `hlines`, `vspans`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | show every place of the table, or crop it | `ymin`, `ymax`, `xmin`, `xmax` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | use a legend instead of end labels | `show_labels=False`, `show_legend`, `legend` | [Legend](#legend) | | draw every series in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | overlay or arrange several bump charts | `Panel`, `Grid` | [Composing with Panel and Grid](#composing-with-panel-and-grid) | | use dates as periods | `date` objects as `x`, `xticks_format` | [Datetime axis](#datetime-axis) | | plot data with other key names | `x`, `y` | [Custom data keys](#custom-data-keys) | | change the look of every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rank_by` | [`BUMP_RANK`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BUMP_RANK) | | `label_position` | [`BUMP_LABEL_POSITION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BUMP_LABEL_POSITION) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.BumpStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.typings.BumpStyleAttrs) type; the full list of parameters is in the [datachart.charts.BumpChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.charts.BumpChart) reference. ### Title, axis labels and ticks A reader needs to know what is being ranked and by what; `title`, `xlabel` and `ylabel` say it. The period axis gets one tick per period by default. `xticks` picks other positions and `xticklabels` names them, and `xtickrotate` tilts long tick labels out of each other's way. Here the labels spell out that the last step is three years, not ten. `scalex` sets the scale of the period axis with a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, which bump charts rarely need. ``` BumpChart( data=population, subtitle=COUNTRIES, # add the title title="The world's most populous countries", # add the x and y axis labels xlabel="Year", ylabel="Rank by population", # name the periods and tilt the names xticks=YEARS, xticklabels=["1980", "1990", "2000", "2010", "2020", "2023 (latest)"], xtickrotate=30, ).show() ``` ### Figure size and grid The default figure is nearly square; ten lines and their end labels read better on a full-width figure. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. A bump chart draws no rank axis and no grid, since the end labels say which line is which. When the reader should count places, `show_grid` adds grid lines with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member: `Y` draws one line per rank, `X` one per period, `BOTH` both. `aspect_ratio` fixes the ratio of the axes rather than of the figure ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); ranks and years share no unit, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID BumpChart( data=population, subtitle=COUNTRIES, title="The world's most populous countries", xlabel="Year", ylabel="Rank by population", # a full-width figure with room for ten lines figsize=FIG_SIZE.FULL_MEDIUM, # one grid line per rank show_grid=SHOW_GRID.Y, ).show() ``` ### Ranking A bump chart draws ranks, and `rank_by` says where they come from, with a [BUMP_RANK](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BUMP_RANK) member. The choice depends on what "first" means in the data: - `BUMP_RANK.VALUE_DESCENDING` (the default) gives rank 1 to the highest value at each period: population, revenue, points, votes. Every chart above uses it. - `BUMP_RANK.VALUE_ASCENDING` gives rank 1 to the lowest value: race times, golf scores, prices, error rates. - `BUMP_RANK.GIVEN` reads `y` as the rank itself, for data that already holds positions: a published league table, a music chart, a poll ranking. Each rank must be a positive whole number. With the two value rules, ties get consecutive ranks in input order: the series listed first takes the better rank. When tied series should share a place, compute the ranks yourself and pass them with `GIVEN`, which accepts a repeated rank. A period where a series has no point is a gap: the series is left out of that period's ranking, and its line breaks there. `stage_race`, defined in a hidden cell, is an illustrative seven-stage cycling race: each rider's total time in minutes after every stage. The leader is the rider with the *lowest* total, so the ranking is ascending. The sprinter leads after the flat opening stage, the time trialist takes over with the first time trial, and the climber wins the race in the mountains. ``` from datachart.constants import BUMP_RANK BumpChart( data=stage_race, subtitle=RIDERS, # the lowest total time leads the race rank_by=BUMP_RANK.VALUE_ASCENDING, title="Overall standings of a stage race", xlabel="After stage", ylabel="Place", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` `premier_league`, defined in a hidden cell, holds the final Premier League positions of six clubs over nine seasons, from 2015/16 to 2023/24 (source: the official Premier League tables). Each season is named by the year it ended in, so 2016 is the 2015/16 season. The positions already are ranks, so the chart takes them with `GIVEN`, and two things follow. The ranks may skip numbers, because the other fourteen clubs of the league hold the positions in between. And Leicester City, relegated in 2023, has no point for 2023/24, so its line ends a season early. ``` BumpChart( data=premier_league, subtitle=CLUBS, # y already holds the league position rank_by=BUMP_RANK.GIVEN, title="Final Premier League positions", xlabel="Season ending", ylabel="Position", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` ### End labels End labels replace the legend and the rank axis: the eye follows a line to its name. `show_labels` turns them on or off (on by default), and `label_position` picks the end that carries the name with a [BUMP_LABEL_POSITION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BUMP_LABEL_POSITION) member: `END` (the default), `START`, or `BOTH`. Labels at both ends show where each country started and where it finished without tracing the line across. ``` from datachart.constants import BUMP_LABEL_POSITION BumpChart( data=population, subtitle=COUNTRIES, # name every line at both ends label_position=BUMP_LABEL_POSITION.BOTH, title="The world's most populous countries", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Line shape Straight segments that cross at sharp angles are hard to follow when many lines swap places at once. `line_curve` eases every segment along an S-shaped curve: `0` (the default) draws straight segments, `1` a full curve, and the values in between blend the two. The curve only changes the path between two periods; every point stays on its rank. `show_markers=False` drops the markers when the lines alone read better, as in a dense table with many periods. ``` BumpChart( data=population, subtitle=COUNTRIES, # ease the lines between the years line_curve=0.8, # lines without the markers show_markers=False, title="The world's most populous countries", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Line style The `style` dictionary sets the look of the lines. The `plot_bump_*` attributes set what is specific to a bump chart (the line width, the marker, its size, and the gap between a line end and its label), while the color, alpha and dash come from the `plot_line_*` attributes; the attributes are listed in [datachart.typings.BumpStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.typings.BumpStyleAttrs), and the markers in [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER). A single dictionary applies to every series; a list, aligned with `data`, styles each series on its own. Coloring only the two giants and drawing the rest in grey puts the story at the top of the table. ``` from datachart.constants import LINE_MARKER GIANTS = {"China": "#c8553d", "India": "#2e86ab"} BumpChart( data=population, subtitle=COUNTRIES, # one style per series: the two giants in color, the rest in grey style=[ { "plot_bump_line_width": 2.5 if country in GIANTS else 1.5, "plot_bump_marker": LINE_MARKER.SQUARE, "plot_bump_marker_size": 5, "plot_line_color": GIANTS.get(country, "#9e9e9e"), } for country in COUNTRIES ], title="The world's most populous countries", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Value labels A rank hides the size of the gaps: rank 1 and rank 2 look the same whether they are a million or a billion apart. `show_values` prints the value behind every rank beside its marker (the original `y`, not the rank), `value_format` formats it with a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or a `"{x:.1f}"` style string, and `value_step` labels every Nth period (by default the smallest step that keeps neighbouring labels apart). Five countries keep the labels legible, and the labels show how close India and China were when they swapped places. ``` from datachart.constants import VALUE_FORMAT BumpChart( # five countries keep the labels legible data=population[:5], subtitle=COUNTRIES[:5], # print the population behind every rank show_values=True, value_format=VALUE_FORMAT.INTEGER, title="Population (millions) behind the ranks", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Emphasis A bump chart with ten lines usually tells the story of two or three of them. `emphasis` takes one role per series, aligned with `data`: `"highlight"` brings a line to the front, `"background"` mutes it (the theme's muted color at a lower alpha, with a muted end label), and `None` leaves it as it is. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Highlighting the two climbers and the country they passed picks one story out of ten lines. ``` from datachart.constants import EMPHASIS BumpChart( data=population, subtitle=COUNTRIES, # follow the countries that climbed past Brazil emphasis=[ EMPHASIS.HIGHLIGHT if country in ("Pakistan", "Nigeria", "Brazil") else EMPHASIS.BACKGROUND for country in COUNTRIES ], title="Pakistan and Nigeria climb past Brazil", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `emphasis_rule` picks the lines from the data instead of naming them. On a bump chart the rule reads each series' ranks, so `{"top": n}` means the `n` best-ranked series (the lowest rank numbers) and `{"bottom": n}` the worst. The summary is the mean rank by default; a `"by"` key picks `"median"`, `"min"`, `"max"` or `"sum"` instead. The thresholds `{"above": v}`, `{"below": v}` (strict) and `{"between": (lo, hi)}` (inclusive) compare the rank number itself, so `{"below": 4}` keeps the series that ranked in the top three on average. An explicit `emphasis` role wins over the rule. In the Premier League table, `{"top": 3}` picks the three clubs with the best mean position over the nine seasons, and `"by": "median"` or `"max"` would ask about a typical or the worst season instead: ``` BumpChart( data=premier_league, subtitle=CLUBS, rank_by=BUMP_RANK.GIVEN, # the three clubs with the best mean position emphasis_rule={"top": 3}, title="The most consistent clubs", xlabel="Season ending", ylabel="Position", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Reference lines and bands Reference lines and bands give the ranks a meaning. On a bump chart their `y` is a rank: `hspans` shades a range of places, such as the qualification places of a league, and `hlines` marks a cut-off between two places (a half-rank sits between them). `vlines` and `vspans` mark periods, such as a rule change or a disrupted season. Each takes a dictionary or a list of them, with the position, a `style`, and an optional `label` that names it in the legend when `show_legend` is on; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs). The rank axis runs from the best rank at the top (`ymin`) to the worst at the bottom (`ymax`), both defaulting to the ranks in the data; `ymax=20.5` shows all twenty places of the league, so the bands mean what they say. `xmin` and `xmax` crop the period axis the same way. The example shades the four Champions League places and the three relegation places, marks the cut-off between 17th and 18th, and shades 2019/20, the season finished behind closed doors after the pandemic pause. The season is `2020` in the data, so the band runs from `2019.5` to `2020.5`. ``` from datachart.constants import LINE_STYLE BumpChart( data=premier_league, subtitle=CLUBS, rank_by=BUMP_RANK.GIVEN, # shade the Champions League and the relegation places hspans=[ {"ymin": 0.5, "ymax": 4.5, "label": "Champions League"}, {"ymin": 17.5, "ymax": 20.5, "label": "relegation", "style": {"plot_hspan_color": "#f4cccc"}}, ], # the cut-off between 17th and 18th hlines={ "y": 17.5, "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, # the season finished after the pandemic pause vspans={"xmin": 2019.5, "xmax": 2020.5, "label": "pandemic season"}, # all twenty places of the league ymin=0.5, ymax=20.5, title="Final Premier League positions", xlabel="Season ending", ylabel="Position", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Text annotations A crossing is the event a bump chart exists to show, and a note can name it. `texts` places text on the chart, with an optional `target` to draw a connector to a point; positions are in data coordinates by default (period, rank) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the ranks. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below sits in the free space between ranks 2 and 3, in data coordinates, and points at India's first year at the top. ``` BumpChart( data=population, subtitle=COUNTRIES, # a note between ranks 2 and 3, pointing at India in 2023 texts={ "text": "India passes China", "x": 2002, "y": 2.5, "target": (2023, 1), }, title="The world's most populous countries", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Multiple Bump Charts A bump chart always compares series, so `data` is a list of lists: each inner list is one series, and the per-series attributes (`subtitle`, `style`, `emphasis`) are lists aligned with it. The ranks are computed across all the series of one figure. The subsections below cover the legend, subplots, and composing bump charts with other figures. ### Legend End labels crowd when the names are long or the lines finish close together; a legend is the alternative. With `show_labels=False` the legend takes over by default, and `show_legend` sets it explicitly either way. `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). Six club names fit to the right of the chart. ``` from datachart.constants import LEGEND_LOCATION BumpChart( data=premier_league, subtitle=CLUBS, rank_by=BUMP_RANK.GIVEN, # a legend instead of end labels show_labels=False, legend={"title": "Club", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, title="Final Premier League positions", xlabel="Season ending", ylabel="Position", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Subplots Ten crossing lines can hide one country's path. `subplots=True` draws each series in its own panel, and the ranks are still computed over every series, so each panel shows its country's place among all ten. `subtitle` titles the panels; `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the panels per row. `sharey=True` keeps rank 1 at the same height in every panel, and `sharex=True` keeps one period axis. ``` BumpChart( data=population, subtitle=COUNTRIES, # one country per panel, ranked among all ten subplots=True, max_cols=5, sharex=True, sharey=True, show_labels=False, xticks=[1980, 2023], title="Each country's place among the ten", xlabel="Year", ylabel="Rank", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Composing with Panel and Grid [Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays figures built separately on one set of axes, and every bump figure keeps the ranks it computed. Two figures ranked on their own would both start at rank 1, so rankings that should share a scale are computed once and passed with `GIVEN`. The example computes the ranks of all ten countries, then draws the 2023 top five with solid lines and the rest dashed, as two figures in one panel. [Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges a bump chart next to other charts; [Example 3](#example-3-why-did-the-table-reshuffle-a-bump-chart-and-a-bar-chart-in-a-grid) stacks one above a bar chart. The [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guides cover both. ``` from datachart.utils import Panel # the rank of every country in every year, among all ten ranks = {country: [] for country in COUNTRIES} for i in range(len(YEARS)): ordered = sorted(COUNTRIES, key=lambda country: -POPULATION[country][i]) for rank, country in enumerate(ordered, start=1): ranks[country].append(rank) top_five = [country for country in COUNTRIES if ranks[country][-1] <= 5] the_rest = [country for country in COUNTRIES if ranks[country][-1] > 5] def ranked(countries): return [[{"x": year, "y": rank} for year, rank in zip(YEARS, ranks[c])] for c in countries] Panel( [ # both figures take the shared ranks as given BumpChart(data=ranked(top_five), subtitle=top_five, rank_by=BUMP_RANK.GIVEN), BumpChart( data=ranked(the_rest), subtitle=the_rest, rank_by=BUMP_RANK.GIVEN, style={"plot_line_style": LINE_STYLE.DASHED}, ), ], title="The 2023 top five and the rest", xlabel="Year", ylabel_left="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Additional Features ### Datetime axis Periods are often dates, and they are not always evenly spaced: the population table has ten-year steps and then a three-year one. When `x` holds real temporal objects (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`), the axis reads as time and the points sit at their elapsed time, so the last step is drawn shorter. `xticks_format` takes a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. ``` from datetime import date from datachart.constants import DATE_FORMAT BumpChart( # the populations dated at mid-year data=[ [{"x": date(point["x"], 7, 1), "y": point["y"]} for point in series] for series in population ], subtitle=COUNTRIES, # print the dates as years xticks_format=DATE_FORMAT.YEAR, title="Mid-year population ranks", xlabel="Year", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Custom data keys Data from a file or an API rarely uses the `x` and `y` keys, and renaming every record just to plot it is a chore. Instead, tell `BumpChart` which keys to read with the `x` and `y` arguments. `records` stores the population table the way a CSV export would, with a `year` and a `millions` key: ``` records = [ [{"year": year, "millions": people} for year, people in zip(YEARS, POPULATION[country])] for country in COUNTRIES ] records[0][:2] ``` ``` BumpChart( data=records, # the keys that hold the period and the value x="year", y="millions", subtitle=COUNTRIES, title="The world's most populous countries", xlabel="Year", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Themes A theme sets the palette, the line width and the furniture of every chart at once; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows each one. Apply one with [config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) and a [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) member, and reset the configuration afterwards so the following charts draw in the default again. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.MATERIAL) figure = BumpChart( data=population, subtitle=COUNTRIES, line_curve=0.8, title="The world's most populous countries", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ) config.reset_config() figure.show() ``` ## Real-World Examples The examples below put the features above to work on real data, each one answering a question. The data comes from the sections above; each example says what it is and where it comes from. ### Example 1: How Close Was the Swap at the Top? (Emphasis, Curves, Value Labels, and a Note) For four decades China was the most populous country; by 2023 India had passed it. `population` (UN World Population Prospects 2022, from the Basics section) answers how narrow the crossing was. The chart keeps the four largest countries, highlights the two giants and mutes the other two, curves the lines so the one crossing stands out, prints the populations behind the ranks, and a note names the swap: 1,429 million against 1,426. ``` BumpChart( data=population[:4], subtitle=COUNTRIES[:4], # the two giants against the context emphasis=[EMPHASIS.HIGHLIGHT, EMPHASIS.HIGHLIGHT, EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND], line_curve=1, # the populations behind the ranks show_values=True, value_format=VALUE_FORMAT.INTEGER, label_position=BUMP_LABEL_POSITION.BOTH, texts={ "text": "India passes China by\nabout 3 million people", "x": 0.45, "y": 0.55, "coords": "axes", "target": (2023, 1), }, title="India overtakes China", xlabel="Year", ylabel="Rank by population", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Was Leicester's Title a One-Off? (Given Ranks, a Gap, Bands, and Value Labels) In 2015/16 Leicester City won the Premier League, a title priced at 5000 to 1 before the season, ahead of all the clubs that usually share the top places. `premier_league` (the official Premier League tables, from the [Ranking](#ranking) section) holds the final positions of Leicester and five of those clubs over the following seasons. The positions are given ranks, Leicester's line breaks after its relegation in 2023, the axis shows all twenty places with bands on the Champions League places (grey) and the relegation places (red), and Leicester is highlighted with its positions printed (value labels skip muted series). The answer: Leicester never returned to the top four, though it finished fifth twice. ``` BumpChart( data=premier_league, subtitle=CLUBS, rank_by=BUMP_RANK.GIVEN, # Leicester against the clubs that usually finish on top emphasis=[EMPHASIS.BACKGROUND] * 5 + [EMPHASIS.HIGHLIGHT], # print Leicester's positions show_values=True, line_curve=0.5, # the Champions League and the relegation places hspans=[ {"ymin": 0.5, "ymax": 4.5}, {"ymin": 17.5, "ymax": 20.5, "style": {"plot_hspan_color": "#f4cccc"}}, ], ymin=0.5, ymax=20.5, title="Leicester City after the 2016 title", xlabel="Season ending", ylabel="Position", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: Why Did the Table Reshuffle? (A Bump Chart and a Bar Chart in a Grid) A bump chart shows that the order changed, not why. Between 1980 and 2023 Pakistan and Nigeria tripled their populations while Russia and Japan barely grew (`population`, UN World Population Prospects 2022, from the Basics section), and the growth explains every crossing. The top chart ranks the ten countries, highlighting the two fastest climbers and the two countries that fell furthest; the bar chart below shows each country's growth factor (the 2023 population divided by the 1980 one), sorted, with the same four countries highlighted. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) stacks the two charts in one figure. ``` from datachart.charts import BarChart from datachart.constants import ORIENTATION, SORT from datachart.utils import Grid MOVERS = ("Pakistan", "Nigeria", "Russia", "Japan") ranking = BumpChart( data=population, subtitle=COUNTRIES, emphasis=[EMPHASIS.HIGHLIGHT if c in MOVERS else EMPHASIS.BACKGROUND for c in COUNTRIES], line_curve=0.8, title="Rank by population", xlabel="Year", ) growth = BarChart( data=[ { "label": c, "y": POPULATION[c][-1] / POPULATION[c][0], "emphasis": "highlight" if c in MOVERS else "background", } for c in COUNTRIES ], title="Growth factor, 1980 to 2023", orientation=ORIENTATION.HORIZONTAL, # the fastest growth at the top sort=SORT.ASCENDING, show_values=True, value_format="{:.1f}x", xmin=0, xmax=3.5, ) Grid( [[ranking], [growth]], title="Growth reshuffles the table", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Bar Chart A bar chart compares a numeric value across a few categories: each category gets a bar, and the bar lengths answer *which is bigger, and by how much*. This guide shows how to create bar charts with the [datachart.charts.BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.charts.BarChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-bar-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import BarChart ``` ## Basics The examples in this guide share one dataset: the top ten countries of the Paris 2024 Olympic medal table, ranked by gold medals (source: the official Paris 2024 medal table). The data lives in a hidden cell. `medals_total` holds the total medals of each country, one data point per country, and `medals_by_metal` holds one series per metal (gold, silver, bronze) over the same countries. The table has stories in it, and the customizations below tell them: two countries tied on gold, a host nation, and a ranking that changes with the way it is counted. Each data point is a dictionary with a `label` (the category) and a `y` value: ``` medals_total[:3] ``` **Basic example.** Only the `data` argument is required. The bars follow the input order, which here is the official gold-medal ranking: ``` BarChart( # add the data to the chart data=medals_total ).show() ``` ## Customizing the Bar Chart Every customization is either a keyword argument of `BarChart` or a `plot_bar_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | rotate the tick labels | `xtickrotate`, `ytickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | fix the aspect ratio of the axes | `aspect_ratio` | [Figure size and grid](#figure-size-and-grid) | | draw the bars horizontally | `orientation` | [Horizontal bars](#horizontal-bars) | | order the categories by value | `sort`, `sort_by` | [Sorting](#sorting) | | print the value on each bar | `show_values`, `value_format` | [Value labels](#value-labels) | | change the bar color, width, hatch, or edge | `style={"plot_bar_color": ..., "plot_bar_hatch": ...}` | [Bar style](#bar-style) | | highlight some bars, mute the rest | `emphasis_rule`, the `"emphasis"` key of a data point | [Emphasis](#emphasis) | | mark a threshold or a boundary | `hlines`, `vlines` | [Reference lines and bands](#reference-lines-and-bands) | | shade a range or a group of bars | `hspans`, `vspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | use dates as category labels | `date` objects as `label`, `xticks_format` | [Date labels](#date-labels) | | compare several series in one chart | `data` as a list of lists, `subtitle`, `show_legend` | [Multiple Bar Charts](#multiple-bar-charts) | | group, stack, or overlay the series | `bar_mode` | [Bar mode](#bar-mode) | | highlight one series, mute the rest | `emphasis` | [Multiple Bar Charts](#multiple-bar-charts) | | title and place the legend | `legend` | [Legend](#legend) | | draw each series in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | show the uncertainty of each bar | `yerr` in `data`, `show_yerr` | [Error bars](#error-bars) | | use a logarithmic axis | `scaley`, `scalex` | [Axis scales](#axis-scales) | | plot data with other key names | `label`, `y`, `yerr` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `bar_mode` | [`BAR_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.BarStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs) type; the full list of parameters is in the [datachart.charts.BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.charts.BarChart) reference. ### Title, axis labels and ticks A chart without a title and axis labels leaves the reader guessing what the bars measure; `title`, `xlabel` and `ylabel` say it. Ten country names crowd the category axis, so `xtickrotate` (or `ytickrotate`) tilts them out of each other's way. `xmin`, `xmax`, `ymin` and `ymax` fix the axis range: bars encode value by length, so the value axis should start at zero, and a little headroom leaves space for labels added later. ``` BarChart( data=medals_total, # add the title title="Paris 2024 medal table", # add the x and y axis labels xlabel="Country", ylabel="Medals", # rotate the x-axis tick labels xtickrotate=45, # fix the y-axis range ymin=0, ymax=140, ).show() ``` ### Figure size and grid The default figure is nearly square, while a bar chart with many categories reads best wide and short. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. Grid lines let the eye carry the top of a bar across to the axis. `show_grid` draws them along the value axis with [SHOW_GRID.Y](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID), the reading aid a bar chart needs, without cluttering the category axis (`SHOW_GRID.X` and `SHOW_GRID.BOTH` are the other options). `aspect_ratio` fixes the ratio of the axes rather than of the figure ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); bar charts rarely need it, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID BarChart( data=medals_total, title="Paris 2024 medal table", xlabel="Country", ylabel="Medals", xtickrotate=45, ymin=0, ymax=140, # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the value axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Horizontal bars A ranking reads best top to bottom, and long category names read best unrotated. Horizontal bars give both: `orientation=ORIENTATION.HORIZONTAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) puts the categories on the y-axis and the values on the x-axis, so the axis labels and the grid swap with it. The first data point is drawn at the bottom, so the data is reversed to keep the ranking top-down. ``` from datachart.constants import ORIENTATION BarChart( # reversed, so the first country ends up at the top data=medals_total[::-1], title="Paris 2024 medal table", # the axis labels swap with the orientation xlabel="Medals", ylabel="Country", figsize=FIG_SIZE.FULL_MEDIUM, # and so does the grid show_grid=SHOW_GRID.X, # draw the bars horizontally orientation=ORIENTATION.HORIZONTAL, xmin=0, ).show() ``` ### Sorting The medal table ranks by gold, but that is a convention, and the same numbers tell a different story ranked by total medals. `sort` orders the categories by value: `SORT.DESCENDING` puts the largest bar first, `SORT.ASCENDING` the smallest, and `None` keeps the input order ([SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT)). Ranked by total, Great Britain climbs from seventh to third and France moves above Japan and Australia. ``` from datachart.constants import SORT BarChart( data=medals_total, title="Paris 2024 medal table, ranked by total medals", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # largest total first sort=SORT.DESCENDING, ymin=0, ).show() ``` With several series in one chart, one order serves all of them, keyed by the total across the series; `sort_by` names the series (by its `subtitle`) that keys the order instead. `medals_by_metal` holds one series per metal (the [Multiple Bar Charts](#multiple-bar-charts) section covers the list-of-lists form), and ranking it by silver medals moves France to third: ``` BarChart( data=medals_by_metal, subtitle=METALS, title="Paris 2024 medal table, ranked by silver medals", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # one order for every series, keyed by one of them sort=SORT.DESCENDING, sort_by="Silver", ).show() ``` ### Value labels When the exact numbers matter, as they do in a medal table, `show_values` prints each bar's value at its edge, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. The label font size, color and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), shared by every chart that prints values. Value labels need headroom, so the value axis is extended a little past the longest bar. ``` from datachart.constants import VALUE_FORMAT BarChart( data=medals_total[::-1], style={"plot_value_fontsize": 9, "plot_value_padding": 4}, title="Paris 2024 medal table", xlabel="Medals", ylabel="Country", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.X, orientation=ORIENTATION.HORIZONTAL, # room for the labels past the longest bar xmin=0, xmax=145, # print the value of each bar show_values=True, value_format=VALUE_FORMAT.INTEGER, ).show() ``` ### Bar style The `style` dictionary sets the look of the bars: the color and alpha, the width as a fraction of the category width, the hatch pattern, and the edge; the attributes are listed in [datachart.typings.BarStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs), and any attribute left out keeps the value of the active theme. A chart that will be printed or photocopied has to survive without color: a hatch pattern from [HATCH_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HATCH_STYLE) and a dark edge keep the bars distinct in greyscale. ``` from datachart.constants import HATCH_STYLE BarChart( data=medals_total, # a print-safe look: hatched bars with a dark edge style={ "plot_bar_color": "#f4f1de", "plot_bar_width": 0.6, "plot_bar_hatch": HATCH_STYLE.DIAGONAL, "plot_bar_edge_width": 1.0, "plot_bar_edge_color": "#3d405b", }, title="Paris 2024 medal table", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymin=0, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. A data point can carry its own `"emphasis"` key: `"highlight"` bolds the bar's edges and brings it to the front, `"background"` mutes it (the theme's muted color at a lower alpha), so marking the host nation is a matter of tagging one record and muting the rest. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. ``` # tag the host nation, mute the rest host_marked = [ {**point, "emphasis": "highlight" if point["label"] == HOST else "background"} for point in medals_total ] BarChart( data=host_marked, title="Paris 2024 medal table, the host nation", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymin=0, ).show() ``` `emphasis_rule` picks the bars from the data instead of tagging them by hand. It is a one-key dictionary: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive); the bars that match are highlighted, the rest muted. A data point's own `"emphasis"` key wins over the rule, so tagging the host alone and letting the rule handle the rest says *the podium, and also the host*: ``` # tag the host only; the rule decides the rest host_tagged = [ {**point, "emphasis": "highlight"} if point["label"] == HOST else point for point in medals_total ] BarChart( data=host_tagged, title="Paris 2024 medal table, the podium and the host", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, sort=SORT.DESCENDING, # the three largest bars; France's own key keeps it highlighted emphasis_rule={"top": 3}, ymin=0, ).show() ``` ### Reference lines and bands Reference lines and bands put the bars in context. `hlines` draws a horizontal line at a value, such as the mean of the table, and `vlines` a vertical one at a bar position; positions along the category axis are bar indices (`0`, `1`, `2`, …), so a half-integer sits between two bars. `hspans` and `vspans` shade a range instead of marking a value: a band of acceptable values, or a group of bars. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs). The example marks the mean of the top ten with a dashed line and shades the three podium positions. ``` from datachart.constants import LINE_STYLE mean_medals = sum(point["y"] for point in medals_total) / len(medals_total) BarChart( data=medals_total, # a dashed line at the mean of the top ten hlines={ "y": mean_medals, "label": "top-ten mean", "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, # shade the first three bars vspans={"xmin": -0.5, "xmax": 2.5, "label": "podium"}, title="Paris 2024 medal table, ranked by total medals", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, sort=SORT.DESCENDING, ymin=0, ).show() ``` ### Text annotations Where a reference line marks a value, a note explains it. `texts` places text on the chart, with an optional `target` to draw a connector to a data point; the position is in data coordinates by default (bar index, value) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below explains the tie at the top of the table. ``` BarChart( data=medals_total, # a note pinned to the axes, pointing at China's bar texts={ "text": "tied on 40 golds; the United States\nleads on silver and bronze", "x": 0.5, "y": 0.8, "coords": "axes", "target": (1, MEDAL_TABLE["China"][0]), }, title="Paris 2024 medal table", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymin=0, ymax=140, ).show() ``` ### Date labels Categories are often dates: quarters, months, editions of an event. A `label` that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) keeps its categorical position but prints through `xticks_format`, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern, so the tick labels come out tidy without hand-writing them. `france_golds`, defined in a hidden cell, holds France's gold medals at the last five Summer Games, labeled by the opening day of each Games (Tokyo 2020 was held in 2021, and the year format shows it). ``` from datachart.constants import DATE_FORMAT BarChart( data=france_golds, title="France's gold medals by Summer Games", xlabel="Games", ylabel="Gold medals", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # print the date labels as years xticks_format=DATE_FORMAT.YEAR, yticks=[0, 5, 10, 15], ymin=0, ).show() ``` ## Multiple Bar Charts To compare several series, pass a list of lists to `data`: each inner list is one series, and the per-series attributes (`subtitle`, `style`, `emphasis`) become lists aligned with it. Series that share a label are drawn side by side in one group, and `show_legend` names them by their subtitles. `medals_by_metal` is such a list, one series per metal, and a style per series colors the bars like the metals they stand for. ``` METAL_STYLE = [ {"plot_bar_color": "#d4af37"}, # gold {"plot_bar_color": "#a8a9ad"}, # silver {"plot_bar_color": "#cd7f32"}, # bronze ] BarChart( # one series per metal data=medals_by_metal, # named for the legend subtitle=METALS, # and colored like the metal style=METAL_STYLE, title="Paris 2024 medal table by metal", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` When the question is about one of the series, `emphasis` takes one role per series, aligned with `data` like `subtitle` and `style`: `"highlight"` bolds a series, `"background"` mutes it and drops it from the legend, `None` leaves it as it is. Asking only about gold turns the silver and bronze bars into context: ``` BarChart( data=medals_by_metal, subtitle=METALS, style=METAL_STYLE, # gold is the question, silver and bronze the context emphasis=["highlight", "background", "background"], title="Paris 2024 medal table, gold against the rest", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Bar mode Grouped bars compare the series within each category, but hide the totals. `bar_mode` changes how the series share a category ([BAR_MODE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE)): `BAR_MODE.STACK` stacks them, so the height of each stack is the total and the segments are its split; `BAR_MODE.OVERLAY` draws them at the same position, one over the other, which suits a before-and-after pair; `BAR_MODE.GROUP` is the default. Stacked and sorted, the chart shows the ranking by total medals and what each total is made of. ``` from datachart.constants import BAR_MODE BarChart( data=medals_by_metal, subtitle=METALS, style=METAL_STYLE, title="Paris 2024 medal table by metal, ranked by total medals", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # stack the metals; the sort keys on the total across them bar_mode=BAR_MODE.STACK, sort=SORT.DESCENDING, ).show() ``` An overlay pairs each country's Paris result with its Tokyo result. `golds_2020`, defined in a hidden cell, holds the same ten countries' gold medals at Tokyo 2020 (source: the official Tokyo 2020 medal table). The Tokyo series is drawn first, in grey, and the Paris series over it in gold, so a grey bar showing above a gold one is a country that won fewer golds in Paris than in Tokyo: Japan, the previous host, and Great Britain. ``` BarChart( data=[golds_2020, golds_2024], subtitle=["Tokyo 2020", "Paris 2024"], # the earlier Games in grey, the later ones in gold over them style=GAMES_STYLE, title="Gold medals, Tokyo 2020 and Paris 2024", xlabel="Country", ylabel="Gold medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # both series at the same positions bar_mode=BAR_MODE.OVERLAY, ).show() ``` ### Legend `show_legend` lists the series; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). Ten groups of three bars leave no empty corner inside the axes, so the legend goes outside them. ``` from datachart.constants import LEGEND_LOCATION BarChart( data=medals_by_metal, subtitle=METALS, style=METAL_STYLE, title="Paris 2024 medal table by metal", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # a titled legend outside the axes, to the right legend={"title": "Medal", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Subplots When the series are many, or the question is about the shape of each rather than the comparison within a category, `subplots=True` draws each series in its own panel. `subtitle` titles the panels; `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the panels per row. `sharey=True` puts the panels on one value axis, so a bar in one panel is comparable with a bar in the next; without it each panel scales to its own maximum and the bronze counts would look as large as the golds. `sharex=True` keeps one category axis for all of them. ``` BarChart( data=medals_by_metal, subtitle=METALS, style=METAL_STYLE, title="Paris 2024 medal table by metal", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_TALL, show_grid=SHOW_GRID.Y, # one panel per metal, stacked in a column subplots=True, max_cols=1, # one value axis and one category axis for all panels sharex=True, sharey=True, ).show() ``` ## Additional Features ### Error bars A bar shows an estimate; an error bar shows how sure the estimate is. Each data point carries its uncertainty as `yerr`, `show_yerr` draws it, and the `plot_bar_error_color` style attribute colors the whiskers. Medal counts are exact, so this example switches dataset: `poll`, defined in a hidden cell, is an illustrative pre-election poll, the support for five parties with the survey's margin of error. Two parties whose error bars overlap are not shown to be apart, which is what the error bars are there to say. ``` BarChart( data=poll, # the color of the whiskers style={"plot_bar_error_color": "#333333"}, title="Voting intention, with the margin of error", xlabel="Party", ylabel="Support (%)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # draw the error bars show_yerr=True, ymin=0, ).show() ``` ### Axis scales Bars encode value by length, so a linear axis is the honest default, and a logarithmic one is the exception for values that span orders of magnitude. `scaley` (or `scalex` for horizontal bars) takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member. `populations`, defined in a hidden cell, holds the approximate mid-2024 population of seven countries in thousands (UN World Population Prospects 2024, rounded), from about 1.45 billion down to about 10 thousand. On a linear scale the small countries vanish; on a log scale every bar is readable, at the price that bar lengths no longer compare. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: BarChart( data=populations, title=f"Population on the '{scale}' scale", xlabel="Country", ylabel="Population (thousands)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # the scale of the value axis scaley=scale, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `label`, `y` and `yerr` keys, and renaming every record just to plot it is a chore. Instead, tell `BarChart` which keys to read with the `label`, `y` and `yerr` arguments. `medal_records` stores the table the way a CSV export would, one record per country with a `country` and a `total` key: ``` medal_records = [ {"country": country, "gold": gold, "silver": silver, "bronze": bronze, "total": gold + silver + bronze} for country, (gold, silver, bronze) in MEDAL_TABLE.items() ] medal_records[:2] ``` ``` BarChart( data=medal_records, # the keys that hold the label and the value label="country", y="total", title="Paris 2024 medal table", xlabel="Country", ylabel="Medals", xtickrotate=45, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymin=0, ).show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Where Python Stands (Ranked Horizontal Bars with Value Labels) `languages` holds the share of respondents who worked with each of the ten most-used programming languages in the past year, from the Stack Overflow Developer Survey 2024 (all respondents). The question is where Python stands among them. Horizontal bars keep the names readable and `sort` ranks them, value labels print the exact share (the values already are percentages, so a positional `"{:.1f}%"` format appends the sign; `VALUE_FORMAT.PERCENT` would multiply by 100), and Python's record carries its own `"emphasis"` key while the rest are muted. ``` BarChart( data=languages, style={"plot_value_fontsize": 9, "plot_value_padding": 4}, title="Most used programming languages, 2024", xlabel="Share of respondents", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.X, orientation=ORIENTATION.HORIZONTAL, # smallest first, so the most used ends up at the top sort=SORT.ASCENDING, xmin=0, xmax=75, show_values=True, value_format="{:.1f}%", ).show() ``` ### Example 2: A Trade Balance Turns Around (Diverging Bars with a Note) `trade_balance` holds two years of illustrative monthly trade balance figures (exports minus imports, in billion EUR): a run of deficits in the first year turning into surpluses in the second. Positive and negative months want different colors, and `BarChart` applies one color per series, so the data is split into a surplus series and a deficit series drawn at the same positions with `bar_mode=BAR_MODE.OVERLAY` (see the tip below). The months are `date` labels printed as year and month, a solid line marks zero, and a note points at the first month in surplus. ``` BarChart( data=trade_balance, style=[ {"plot_bar_color": "#2a9d8f"}, # surplus {"plot_bar_color": "#e76f51"}, # deficit ], # draw both series at the same positions bar_mode=BAR_MODE.OVERLAY, # mark the zero line hlines={ "y": 0, "style": {"plot_hline_color": "black", "plot_hline_style": LINE_STYLE.SOLID, "plot_hline_width": 1}, }, # point at the first month in surplus texts={ "text": "first surplus", "x": 0.3, "y": 0.85, "coords": "axes", "target": (FIRST_SURPLUS, BALANCE[FIRST_SURPLUS]), }, title="Monthly trade balance", ylabel="Billion EUR", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, xticks_format=DATE_FORMAT.YEAR_MONTH, xtickrotate=90, ).show() ``` Tip: Diverging Bar Charts Each month is zero in one of the two series (positive values in one, negative in the other). Drawing them with `bar_mode=BAR_MODE.OVERLAY` puts both series at the same positions, so only one bar is visible per month: the visual effect of a single diverging bar chart with two colors. ### Example 3: The Host Effect (Overlaid Games, Highlighted Home Games, and a Grid) Hosting the Games is said to lift a country's medal haul, and the medal tables of the last five Summer Games let us check. The top chart overlays each country's gold medals at Tokyo 2020 and Paris 2024 for the ten countries of the shared dataset, ranked by the Paris result with `sort_by`. The two charts below it follow the two most recent hosts, France and Japan, across five Games: `japan_golds`, defined in a hidden cell, holds Japan's gold medals (source: the official medal tables), and `emphasis_rule={"top": 1}` highlights each country's best Games, which in both cases is the one it hosted; a note on each says so. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts the three charts in one figure, the comparison across the full top row and the two histories side by side below it, and the notes travel with their charts. ``` from datachart.utils import Grid games = BarChart( data=[golds_2020, golds_2024], subtitle=["Tokyo 2020", "Paris 2024"], style=GAMES_STYLE, title="Gold medals at the last two Games", ylabel="Gold medals", xtickrotate=45, show_grid=SHOW_GRID.Y, show_legend=True, bar_mode=BAR_MODE.OVERLAY, # ranked by the Paris result sort=SORT.DESCENDING, sort_by="Paris 2024", ) def host_history(data, country, host_index): # a country's golds over five Games, its best Games highlighted and annotated return BarChart( data=data, title=f"{country}'s gold medals by Games", ylabel="Gold medals", show_grid=SHOW_GRID.Y, xticks_format=DATE_FORMAT.YEAR, emphasis_rule={"top": 1}, texts={ "text": "home Games", "x": 0.08 if host_index == 4 else 0.62, "y": 0.9, "coords": "axes", "target": (host_index, data[host_index]["y"]), }, # the same value axis for both countries yticks=[0, 10, 20, 30], ymin=0, ymax=30, ) Grid( [ [games], [host_history(france_golds, "France", 4), host_history(japan_golds, "Japan", 3)], ], title="The host effect", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Pyramid Chart A pyramid chart compares two groups over the same ordered categories: the first group extends to the left of a shared zero line, the second to the right, and the shape they make together (a wide base, a bulge, a lopsided top) is the message. It is the classic age-sex population pyramid, and it serves any paired comparison over ordered categories, such as arrivals against departures by hour. This guide shows how to create pyramid charts with the [datachart.charts.PyramidChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/pyramidchart/#datachart.charts.PyramidChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-pyramid-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import PyramidChart ``` ## Basics The examples in this guide share one dataset: the illustrative age structure of a country of about ten million people, by sex and five-year age band, in thousands. The figures are hand-written to show the shape of an ageing population (a baby-boom bulge now aged 55 to 64, smaller cohorts below it, and a top where women outnumber men two to one); they are not the census of any real country. The data lives in a hidden cell: `AGE_BANDS` lists the eighteen bands from `0-4` to `85+`, and `men` and `women` hold one data point per band for each sex. Each data point is a dictionary with a `label` (the age band) and a positive `y` value; the same labels appear on both sides, in the same order: ``` men[:3] ``` **Basic example.** Only the `data` argument is required: exactly two lists of data points, the first drawn to the left and the second to the right. Both sides are given as positive values, the chart mirrors the left side itself, and the value ticks read as positive on both halves. The first band is drawn at the bottom, so the youngest cohort forms the base: ``` PyramidChart( # the two sides of the pyramid: [left_side, right_side] data=[men, women] ).show() ``` ## Customizing the Pyramid Chart Every customization is either a keyword argument of `PyramidChart` or a `plot_bar_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ---------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title and axis labels](#title-and-axis-labels) | | name the two sides and show the legend | `subtitle`, `show_legend`, `legend` | [Naming the sides](#naming-the-sides) | | resize the figure or show grid lines | `figsize`, `show_grid` | [Figure size and grid](#figure-size-and-grid) | | fix the value range or format the value ticks | `xmax`, `xticks`, `xticklabels`, `xticks_format`, `xtickrotate` | [Value axis range and ticks](#value-axis-range-and-ticks) | | thin out or relabel the category ticks | `yticks`, `yticklabels`, `ytickrotate` | [Category ticks](#category-ticks) | | print the value at the end of each bar | `show_values`, `value_format` | [Value labels](#value-labels) | | change the bar color, hatch, or edge, per side | `style` | [Bar style](#bar-style) | | rank the categories by size | `sort`, `sort_by` | [Sorting](#sorting) | | highlight some bars, mute the rest | `emphasis_rule`, the `"emphasis"` key of a data point | [Emphasis](#emphasis) | | mark a value or an age boundary | `vlines`, `hlines` | [Reference lines and bands](#reference-lines-and-bands) | | shade a value range or a run of bands | `vspans`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | put several pyramids side by side | `Grid` | [Multiple Pyramid Charts](#multiple-pyramid-charts) | | show the uncertainty of each bar | `yerr` in `data`, `show_yerr` | [Error bars](#error-bars) | | use times or dates as category labels | `datetime` objects as `label`, `yticks_format` | [Date labels](#date-labels) | | plot data with other key names | `label`, `y`, `yerr` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.BarStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs) type; the full list of parameters is in the [datachart.charts.PyramidChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/pyramidchart/#datachart.charts.PyramidChart) reference. ### Title and axis labels A pyramid without labels is a shape; the title and the axis labels say what it measures and in what units. Unlike the other charts, the axis parameters of a pyramid are spatial: `xlabel` describes the horizontal value axis (the population of a band) and `ylabel` the vertical category axis (the age bands). ``` PyramidChart( data=[men, women], # add the title title="Population by age and sex", # the value axis runs horizontally, the category axis vertically xlabel="Population (thousands)", ylabel="Age band", ).show() ``` ### Naming the sides Nothing on the chart says which side is which until the sides are named. `subtitle` takes the two names in the order of `data`, and `show_legend` puts them in the legend. `legend` then says where and how: a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols` and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The top corners of an ageing pyramid are empty, so the legend fits in one of them. ``` from datachart.constants import LEGEND_LOCATION PyramidChart( data=[men, women], # name the sides, in the order of the data subtitle=SIDES, # show them in a titled legend, in the empty upper-left corner show_legend=True, legend={"title": "Sex", "location": LEGEND_LOCATION.UPPER_LEFT}, title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", ).show() ``` ### Figure size and grid Eighteen bands per side need vertical room, or the bars turn into thin stripes. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE); a tall preset gives every band a readable bar. Grid lines let the eye carry the end of a bar down to the value axis, on either half: `show_grid` draws them along the value axis with [SHOW_GRID.X](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) (`SHOW_GRID.Y` and `SHOW_GRID.BOTH` are the other options). ``` from datachart.constants import FIG_SIZE, SHOW_GRID PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", # a tall figure, one readable bar per band figsize=FIG_SIZE.FULL_TALL, # grid lines along the value axis, on both halves show_grid=SHOW_GRID.X, ).show() ``` ### Value axis range and ticks The value axis is always symmetric around zero, so the two sides compare at a glance; `xmax` sets the per-side maximum and the axis spans `(-xmax, xmax)`. Passing `xmin` raises a `ValueError`. A fixed `xmax` matters when two pyramids are read against each other: it gives them the same scale. Give `xticks` along with it, ending on `xmax`, so the axis ends on a labeled tick. `xticks` places the value ticks, as positive positions mirrored to both halves, and `xticks_format` formats them ([VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) or any `"{x:.0f}"` style string, applied to the absolute value); `xticklabels` replaces the labels outright when a format string cannot produce them, and `xtickrotate` tilts them. The counts are in thousands, and a `k` suffix says so without an axis label. ``` PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, title="Population by age and sex", ylabel="Age band", # the axis spans -400 to 400, with a tick every 100 on both halves xmax=400, xticks=[0, 100, 200, 300, 400], # the ticks read as absolute values, here with a thousands suffix xticks_format="{x:.0f}k", ).show() ``` ### Category ticks Eighteen band labels are a lot to read, and a demographer's pyramid is usually labeled by age, not by band. `yticks` picks the category positions to label (the bands are numbered from the bottom, `0`, `1`, `2`, …), `yticklabels` gives them new text, and `ytickrotate` tilts them when they are long. Labeling every second band with the age it starts at turns the band axis into an age axis: ``` PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age", # every second band, labeled by the age it starts at yticks=list(range(0, 18, 2)), yticklabels=[str(age) for age in range(0, 90, 10)], ).show() ``` ### Value labels When the exact size of a cohort matters, `show_values` prints it at the end of each bar, as a positive number on both sides, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. The label font size and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). Thirty-six labels need a tall figure and a small font, and the axis is widened a little so the labels of the longest bars stay inside it. ``` from datachart.constants import VALUE_FORMAT PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, # a small font for thirty-six labels style={"plot_value_fontsize": 7}, title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", figsize=FIG_SIZE.FULL_TALL, # room for the labels past the longest bars xmax=450, xticks=[0, 150, 300, 450], # print the size of each cohort at the end of its bar show_values=True, value_format=VALUE_FORMAT.INTEGER, ).show() ``` ### Bar style Pyramid bars take the same `plot_bar_*` style attributes as the bar chart: the color and alpha, the width as a fraction of the band, the hatch pattern and the edge ([BarStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs)); an attribute left out keeps the value of the active theme. One `style` dictionary applies to both sides; a list of two styles them individually, which is how a printed pyramid keeps its sides apart without color: one side filled, the other hatched with a pattern from [HATCH_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HATCH_STYLE), both with a dark edge. Without explicit colors the sides take the first two colors of the theme's palette. ``` from datachart.constants import HATCH_STYLE PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, # a print-safe look: one side filled, the other hatched, both edged style=[ {"plot_bar_color": "#3d405b", "plot_bar_edge_color": "#3d405b", "plot_bar_edge_width": 0.8}, { "plot_bar_color": "#f4f1de", "plot_bar_hatch": HATCH_STYLE.DIAGONAL, "plot_bar_edge_color": "#3d405b", "plot_bar_edge_width": 0.8, }, ], title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", ).show() ``` ### Sorting Age bands have a natural order, and a pyramid usually keeps it. But the same chart serves paired comparisons with no natural order, and even age bands are sometimes worth ranking to read off the largest cohorts. `sort` orders the categories by value, one order for both sides, keyed by the total of the two ([SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT)); `sort_by` keys it on one side instead, named by its `subtitle`. The first category is drawn at the bottom, so `SORT.DESCENDING` puts the largest band at the base and `SORT.ASCENDING` at the top. Ranked by the number of women, the three baby-boom bands come first, and the cohort being born now (0-4) ranks below every band up to 75-79: this population has fewer young children than adults of any working age. ``` from datachart.constants import SORT PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, title="Age bands ranked by the number of women", xlabel="Population (thousands)", ylabel="Age band", # one order for both sides, keyed by one of them; the largest at the base sort=SORT.DESCENDING, sort_by="Women", ).show() ``` ### Emphasis A pyramid usually makes one point, and emphasis makes it visible. `emphasis_rule` picks the bars from the data: a one-key dictionary, `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), `{"top": n}` or `{"bottom": n}`, read against each bar's positive value on both sides; the bars that match are highlighted and the rest muted. The roles are the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Every bar above 345 thousand belongs to the three baby-boom bands, on both sides: ``` PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, title="The baby-boom cohorts", xlabel="Population (thousands)", ylabel="Age band", # every bar above 345 thousand, either side emphasis_rule={"above": 345}, ).show() ``` A data point can also carry its own `"emphasis"` key (`"highlight"` or `"background"`), which wins over the rule. Tagging by hand is the way to make a point the values alone do not select: the two oldest bands on the women's side, with everything else muted, say that the very old are mostly women. ``` # highlight the two oldest bands of women, mute everything else oldest_women = [ {**point, "emphasis": "highlight" if point["label"] in ("80-84", "85+") else "background"} for point in women ] men_muted = [{**point, "emphasis": "background"} for point in men] PyramidChart( data=[men_muted, oldest_women], subtitle=SIDES, show_legend=True, title="The very old are mostly women", xlabel="Population (thousands)", ylabel="Age band", ).show() ``` ### Reference lines and bands Reference lines and bands put the bars in context. `vlines` draws a vertical line at a value and `hlines` a horizontal one at a category position; the bands are numbered from the bottom, so a half-integer position sits between two bands. `vspans` and `hspans` shade a range instead: a range of values, or a run of bands. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs) and [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs). Two things are particular to the pyramid. The value axis is mirrored, so a line on the left half sits at a negative `x`. And a single line applies to both sides and is drawn once per side, which doubles its legend entry; a list aligned with `data`, with the lines on the first side and `None` on the second, draws each line once. Bands are drawn once whichever way they are given. The example draws the size of the youngest cohort up through each side (every band from 5-9 to 70-74 is larger than the cohort being born now), marks the retirement age between the 60-64 and 65-69 bands, and shades the working-age bands from 15-19 to 60-64. ``` from datachart.constants import LINE_STYLE DASHED = {"plot_vline_style": LINE_STYLE.DASHED, "plot_vline_color": "#555555"} PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, # the size of the youngest cohort on each side; the left half is negative vlines=[ [ {"x": -MEN[0], "label": "size of the 0-4 cohort", "style": DASHED}, {"x": WOMEN[0], "style": DASHED}, ], None, ], # the retirement age, between the 60-64 (index 12) and 65-69 (index 13) bands hlines=[{"y": 12.5, "label": "retirement age", "style": {"plot_hline_color": "#c1121f"}}, None], # the working-age bands, 15-19 (index 3) to 60-64 (index 12) hspans={"ymin": 2.5, "ymax": 12.5, "label": "working age"}, title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", figsize=FIG_SIZE.FULL_TALL, ).show() ``` ### Text annotations Where a highlight shows the point, a note explains it. `texts` places text on the chart, with an optional `target` to draw a connector to a position in data coordinates (value, band index); the text itself sits in data coordinates by default or in axes fractions with `"coords": "axes"`, which keeps it in place whatever the axis limits. A target on the left half has a negative value. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. Two notes name the two features of this population: ``` PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, legend={"location": LEGEND_LOCATION.LOWER_RIGHT}, # two notes pinned to the axes, each pointing at a bar texts=[ { "text": "the baby-boom cohorts", "x": 0.08, "y": 0.9, "coords": "axes", "target": (-MEN[11], 11), }, { "text": "twice as many\nwomen as men", "x": 0.74, "y": 0.93, "coords": "axes", "target": (WOMEN[17], 17), }, ], title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", ).show() ``` ## Multiple Pyramid Charts One `PyramidChart` call draws one pyramid, and its two sides are the whole of its data. To compare pyramids, compose the rendered figures with [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md): each pyramid keeps its mirrored value axis inside its own cell, and a shared `xmax` puts them on the same scale. A pyramid cannot take part in a [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md), which overlays figures on one value axis; unmirrored data on a mirrored axis would mislead, so `Panel` raises a `ValueError` for it. `men_2050` and `women_2050`, defined in a hidden cell, hold an illustrative projection of the same population 25 years on: every cohort alive today moves up five bands with a survival rate applied, and the five youngest bands are children not yet born. Side by side, the pyramids show the baby-boom bulge moving from the working ages into the oldest bands. `Grid` takes one `xlabel` and `ylabel` for all its cells. ``` from datachart.utils import Grid DECADES = {"yticks": list(range(0, 18, 2)), "yticklabels": [str(age) for age in range(0, 90, 10)]} # the same value range in every cell, so the bars compare across the cells SCALE = {"xmax": 500, "xticks": [0, 250, 500]} Grid( [ PyramidChart( data=[men, women], subtitle=SIDES, title="Today", # the legend once, in the empty upper-right corner, without a title show_legend=True, legend={"title": "", "location": LEGEND_LOCATION.UPPER_RIGHT}, **SCALE, **DECADES, ), PyramidChart(data=[men_2050, women_2050], subtitle=SIDES, title="2050", **SCALE, **DECADES), ], title="An ageing population, today and in 25 years", # one pair of axis labels for the whole grid xlabel="Population (thousands)", ylabel="Age", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Additional Features ### Error bars A projection is an estimate, and an estimate should show how sure it is. Each data point carries its uncertainty as `yerr`, `show_yerr` draws it as a whisker along the value axis at the end of the bar, and the `plot_bar_error_color` style attribute colors the whiskers. On the 2050 projection the whiskers say where the uncertainty lives: the five youngest bands are children not yet born, whose number depends on future births, so their intervals are wide; the cohorts alive today are known, and their intervals grow only with the mortality of old age. ``` PyramidChart( data=[men_2050, women_2050], subtitle=SIDES, show_legend=True, # the oldest bars fill the top corners legend={"location": LEGEND_LOCATION.LOWER_RIGHT}, # the color of the whiskers style={"plot_bar_error_color": "#333333"}, title="Projected population in 2050, with the projection interval", xlabel="Population (thousands)", ylabel="Age band", **SCALE, # draw the error bars show_yerr=True, ).show() ``` ### Date labels The categories of a pyramid are not always age bands; arrivals against departures by hour, or births against deaths by month, are pyramids too. A `label` that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) keeps its categorical position but prints through `yticks_format`, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern, so the tick labels come out tidy without hand-writing them. `entries` and `exits`, defined in a hidden cell, hold the illustrative passengers entering and leaving a commuter station in each hour from 06:00 to 22:00, labeled by the start of the hour; `DATE_FORMAT.TIME` prints the labels as clock times. ``` from datachart.constants import DATE_FORMAT PyramidChart( data=[entries, exits], subtitle=["Entries", "Exits"], show_legend=True, title="Station passengers by hour", xlabel="Passengers per hour", # print the datetime labels as clock times yticks_format=DATE_FORMAT.TIME, ).show() ``` ### Custom data keys Census tables come as one row per age band with a column per sex, not as two lists of `label` and `y` points, and reshaping them just to plot is a chore. Instead, tell `PyramidChart` which keys to read: `label`, `y` and `yerr` take one key name for both sides or a list of two, one per side. With a list, the same records can serve as both sides, each side reading its own column. `census_rows` stores the population the way a census table would: ``` census_rows = [ {"age_band": band, "men": men_count, "women": women_count} for band, men_count, women_count in zip(AGE_BANDS, MEN, WOMEN) ] census_rows[:2] ``` ``` PyramidChart( # the same rows on both sides, each side reading its own column data=[census_rows, census_rows], label="age_band", y=["men", "women"], subtitle=SIDES, show_legend=True, title="Population by age and sex", xlabel="Population (thousands)", ylabel="Age band", ).show() ``` ## Real-World Examples The examples below put the features above to work on realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: How Big Is the Retirement Wave? (Emphasis Rule, Reference Line, and a Note) The shared population has its three largest bands, 50-54 to 60-64, just below the retirement age, and the question a pension planner asks is how many people that is. An `emphasis_rule` picks every bar above 345 thousand, which are the six bars of those three bands, an `hlines` entry marks the retirement age they are about to cross, and a note gives the sum: about 2.2 million people, more than a fifth of the population, reaching 65 within fifteen years. Decade ticks keep the age axis quiet, and the shared `SCALE` leaves room for the note. ``` PyramidChart( data=[men, women], subtitle=SIDES, show_legend=True, legend={"title": "Sex", "location": LEGEND_LOCATION.LOWER_RIGHT}, title="The cohorts about to retire", xlabel="Population (thousands)", ylabel="Age", **SCALE, **DECADES, # the three largest bands, on both sides emphasis_rule={"above": 345}, # the retirement age, between the 60-64 and 65-69 bands hlines=[{"y": 12.5, "label": "retirement age", "style": {"plot_hline_color": "#c1121f"}}, None], # the size of the wave texts={ "text": f"{BOOMERS / 1000:.1f} million people, aged 50 to 64,\nreach 65 within fifteen years", "x": 0.03, "y": 0.93, "coords": "axes", "target": (-MEN[11], 11), }, ).show() ``` ### Example 2: When Does the Station Fill Up? (Time Labels, Reference Bands, and a Formatted Value Axis) `entries` and `exits` are the illustrative hourly passenger counts of a commuter station from the [Date labels](#date-labels) section: people entering on the left, people leaving on the right. The question is when the platforms are busiest, and in which direction. The asymmetry answers it: the morning peak flows in and the evening peak flows out, which is the signature of a station in a residential district. Two `hspans` shade the rush hours, `yticks_format` prints the hours as clock times, and `xticks_format` puts a thousands separator on the value axis ([VALUE_FORMAT.THOUSANDS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT)); the legend moves outside the axes, since both peaks reach into the corners. ``` PyramidChart( data=[entries, exits], subtitle=["Entries", "Exits"], show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 4}, # the rush hours: 07:00 to 09:59 (indices 1 to 3) and 16:00 to 18:59 (indices 10 to 12) hspans=[ {"ymin": 0.5, "ymax": 3.5, "label": "morning rush", "style": {"plot_hspan_color": "#f4a261", "plot_hspan_alpha": 0.25}}, {"ymin": 9.5, "ymax": 12.5, "label": "evening rush", "style": {"plot_hspan_color": "#2a9d8f", "plot_hspan_alpha": 0.25}}, ], title="Station passengers by hour", xlabel="Passengers per hour", xmax=1500, xticks=[0, 500, 1000, 1500], xticks_format=VALUE_FORMAT.THOUSANDS, yticks_format=DATE_FORMAT.TIME, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: Young, Stationary, or Ageing? (Shares, a Shared Value Axis, and a Grid) Demographers sort populations by the shape of their pyramid: expansive (a wide base of children and a narrow top), stationary (straight sides, each cohort about as large as the one before), and constrictive (a base narrower than the middle, the shape of an ageing population). `young` and `stationary`, defined in a hidden cell, are two illustrative populations of the first two shapes, generated from a small cohort model (a birth rate, a life expectancy that is five years longer for women, and a logistic survival curve); `ageing` is the shared population. All three are expressed as the share of the total population in each band, in percent, so countries of any size compare, and a shared `xmax` puts the three cells of the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) on the same scale: the widest bars of the young population are twice the widest bars of the other two. ``` SHAPES = [("Young", young), ("Stationary", stationary), ("Ageing", ageing)] Grid( [ [ PyramidChart( data=data, subtitle=SIDES, title=shape, # the same value axis in every cell, in percent of the population xmax=9, xticks=[0, 3, 6, 9], xticks_format="{x:.0f}%", # the legend once, in the empty corner of the first cell show_legend=(shape == "Young"), legend={"location": LEGEND_LOCATION.UPPER_RIGHT}, **DECADES, ) for shape, data in SHAPES ] ], title="Three age structures, share of the population by age and sex", ylabel="Age", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Radial Chart A radial chart draws a series around a circle instead of along a line: the categories run around the rim and the values grow outward from the center. That suits a cyclic quantity, one whose last category sits next to its first (hours of the day, months, compass directions), and a radar profile that compares a few entities across several metrics on one scale. This guide shows how to create radial charts with the [datachart.charts.RadialChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/#datachart.charts.RadialChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-radial-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import RadialChart ``` ## Basics The examples in this guide share one dataset: a year of measurements at a small coastal weather station. The figures are illustrative (hand-written averages, and direction readings drawn from a seeded generator), and they live in a hidden cell. `wind_by_direction` holds the average wind speed for each of the eight compass directions, with the standard deviation of the gusts as `yerr`; `wind_directions` holds 500 raw wind direction readings in degrees; `sunshine_by_month` holds the sunshine hours of each month. All three are cyclic: the direction after north-west is north again, and the month after December is January. That is the case for polar coordinates, where the last category sits next to the first instead of at the far end of an axis. Each data point is a dictionary with a `label` (the category around the circle) and a `y` value (the distance from the center): ``` wind_by_direction[:3] ``` **Basic example.** Only the `data` argument is required. The labels are spaced evenly around the circle, starting at the top and running clockwise like a compass, and the line closes its own loop, so the north-west value connects back to north: ``` RadialChart( # add the data to the chart data=wind_by_direction ).show() ``` ## Customizing the Radial Chart Every customization is either a keyword argument of `RadialChart` or an attribute of its `style` dictionary; the style attributes follow the visual (`plot_line_*` and `plot_area_*` for the line, `plot_bar_*` for the bars, `plot_scatter_*` for the scatter, `plot_hist_*` for the histogram). The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ----------------------------------------- | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | pick the visual | `type`, `num_bins` | [The radial visuals](#the-radial-visuals) | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and radial range](#title-axis-labels-and-radial-range) | | fix the radial range | `ymin`, `ymax` | [Title, axis labels and radial range](#title-axis-labels-and-radial-range) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show the rings and spokes | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | rotate where the circle starts | `startangle` | [Start angle and direction](#start-angle-and-direction) | | flip the angular direction | `direction` | [Start angle and direction](#start-angle-and-direction) | | cut a donut hole in the middle | `innerradius` | [Inner radius](#inner-radius) | | change the color, width, marker, or hatch | `style={"plot_line_color": ..., "plot_bar_hatch": ...}` | [Mark style](#mark-style) | | highlight some bars, mute the rest | `emphasis_rule`, the `"emphasis"` key of a data point | [Sorting and emphasis](#sorting-and-emphasis) | | order the sectors by value | `sort`, `sort_by` | [Sorting and emphasis](#sorting-and-emphasis) | | print the value at each tip | `show_values`, `value_format` | [Values and labels at the tips](#values-and-labels-at-the-tips) | | move the category labels to the tips | `show_tip_labels` | [Values and labels at the tips](#values-and-labels-at-the-tips) | | hide the outer border circle | `show_border` | [Values and labels at the tips](#values-and-labels-at-the-tips) | | shade a wedge or a ring | `vspans`, `hspans` | [Reference bands](#reference-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | compare several series in one chart | `data` as a list of lists, `subtitle`, `show_legend` | [Multiple Radial Charts](#multiple-radial-charts) | | highlight one series, mute the rest | `emphasis` | [Multiple Radial Charts](#multiple-radial-charts) | | group, stack, or overlay the bar series | `bar_mode` | [Bar mode](#bar-mode) | | title and place the legend | `legend` | [Legend](#legend) | | draw each series in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | draw an error band or fill the area | `show_yerr`, `show_area` | [Error bands and filled areas](#error-bands-and-filled-areas) | | use a logarithmic radial axis | `scaley` | [Radial axis scale](#radial-axis-scale) | | plot data with other key names | `label`, `x`, `y`, `yerr` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | [`RADIAL_TYPE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RADIAL_TYPE) | | `direction` | [`RADIAL_DIRECTION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RADIAL_DIRECTION) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `bar_mode` | [`BAR_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | The full lists of style attributes are in the [datachart.typings.LineStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs), [BarStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs), [ScatterStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs) and [HistStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs) types; the full list of parameters is in the [datachart.charts.RadialChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/#datachart.charts.RadialChart) reference. ### The radial visuals The first decision is which visual to draw, and the data answers it. `type` takes a [RADIAL_TYPE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RADIAL_TYPE) member: - `RADIAL_TYPE.LINE` (the default) connects the values into a closed profile, the radar chart. Use it when the shape is the message: a few entities compared across several metrics on one scale, or a cycle whose continuity matters. - `RADIAL_TYPE.BAR` draws one sector per category. Use it for one value per cyclic category (a month, an hour, a direction) when the sizes matter more than the shape; the sectors read like the hours on a clock face. - `RADIAL_TYPE.SCATTER` draws one point per category. Use it when the values stand alone and connecting them would suggest a shape that is not there. - `RADIAL_TYPE.HISTOGRAM` takes raw angular observations in degrees (an `x` key instead of `label` and `y`) and counts them in `num_bins` sectors around the full circle. Use it for a distribution of directions: the wind rose. Monthly sunshine hours are one value per month, so they want bars: the long summer sectors at the bottom of the clock, the short winter ones at the top. ``` from datachart.constants import RADIAL_TYPE RadialChart( data=sunshine_by_month, # one sector per month type=RADIAL_TYPE.BAR, title="Monthly sunshine hours", ).show() ``` The raw direction readings are a distribution, not one value per category, so they want the histogram. Binned into 16 sectors, they show where the wind comes from: mostly the south-west, with a second, smaller lobe from the north-east. ``` RadialChart( data=wind_directions, type=RADIAL_TYPE.HISTOGRAM, # count the readings in 16 sectors around the full circle num_bins=16, title="Wind direction frequency", ).show() ``` ### Title, axis labels and radial range A polar plot has two axes like any other chart, and the reader needs to know what each measures: `xlabel` names the angular axis (the categories around the circle), `ylabel` the radial axis (the values), and `title` says what the chart is about. `ymin` and `ymax` fix the radial range. Bars and areas measure from the center, so the radial axis should start at zero, and a `ymax` above the largest value leaves room for value labels added later. ``` RadialChart( data=wind_by_direction, # add the title title="Average wind speed by direction", # name the angular and the radial axis xlabel="Direction", ylabel="Wind speed (km/h)", # fix the radial range ymin=0, ymax=25, ).show() ``` ### Figure size and grid A polar plot is round, so it wants a square figure, and a chart that goes into one column of a two-column page wants a small one. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE): `FIG_SIZE.SQUARE` for a standalone circle, `FIG_SIZE.HALF_SQUARE` for one column. The grid of a polar plot is rings and spokes: the rings let the eye carry a bar tip to the radial ticks, the spokes separate the categories. Left alone, a polar plot draws both, and the theme default (`Y`, the rings) takes the soft grid style while the other set keeps a darker grey. Passing `show_grid` picks exactly what is drawn, with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member: `X` the spokes only, `Y` the rings only, `BOTH` both in the soft style, and `False` neither. With `SHOW_GRID.BOTH` the spokes recede too, so the sectors stand out against a quiet background. The grid is always drawn below the marks, so a bar never hides behind a ring. ``` from datachart.constants import FIG_SIZE, SHOW_GRID RadialChart( data=sunshine_by_month, type=RADIAL_TYPE.BAR, title="Monthly sunshine hours", # a small square figure for one column figsize=FIG_SIZE.HALF_SQUARE, # soft rings and soft spokes show_grid=SHOW_GRID.BOTH, ).show() ``` ### Start angle and direction Where the circle starts and which way it runs is a convention the reader brings along, and the chart should meet it. The default puts the first label at the top and runs clockwise, which is how a compass and a clock work, so directions and months read without instructions. `startangle` moves the first label: a compass point (`"N"`, `"NE"`, `"E"`, `"SE"`, `"S"`, `"SW"`, `"W"`, `"NW"`) or a bearing in degrees clockwise from the top. `direction` flips the way the angles increase, with a [RADIAL_DIRECTION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RADIAL_DIRECTION) member. Readers of scientific polar plots expect the mathematical convention instead, zero at the right and angles increasing counterclockwise; the example follows it, so January sits at the right and the year runs the other way round. ``` from datachart.constants import RADIAL_DIRECTION RadialChart( data=sunshine_by_month, type=RADIAL_TYPE.BAR, title="Monthly sunshine hours", # the mathematical convention: start at the right, run counterclockwise startangle="E", direction=RADIAL_DIRECTION.COUNTERCLOCKWISE, ).show() ``` ### Inner radius Sectors that meet at the center shrink to slivers there, so the small values, and the bottom segments of stacked bars, are hard to read. `innerradius` cuts a donut hole, given as a fraction (between 0 and 1) of the radial extent: every bar starts at the hole instead of the center and keeps a readable width along its whole length. The short winter sectors, thin wedges near the center above, get a readable width with a quarter of the radius reserved for the hole. ``` RadialChart( data=sunshine_by_month, type=RADIAL_TYPE.BAR, title="Monthly sunshine hours", # reserve the middle quarter of the radius for the hole innerradius=0.25, ).show() ``` ### Mark style The `style` dictionary sets the look of the marks, and a radial visual reads the same attributes as its cartesian counterpart: the line visual takes the `plot_line_*` and `plot_area_*` attributes of [LineStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs), the bars the `plot_bar_*` attributes of [BarStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs), and the scatter and histogram visuals the `plot_scatter_*` and `plot_hist_*` attributes. Any attribute left out keeps the value of the active theme, so [themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) style radial charts like every other chart. A radar profile with eight vertices reads better when the vertices are marked: a marker from [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER) puts a dot on each measured direction, and a dashed line from [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE) says the segments between them are interpolation. ``` from datachart.constants import LINE_MARKER, LINE_STYLE RadialChart( data=wind_by_direction, # the line visual takes the line chart's style attributes style={ "plot_line_color": "#aa3355", "plot_line_width": 2, "plot_line_style": LINE_STYLE.DASHED, "plot_line_marker": LINE_MARKER.CIRCLE, }, title="Average wind speed by direction", ymin=0, ).show() ``` ### Sorting and emphasis A chart usually makes one point. On the bar visual, `emphasis_rule` picks the bars that make it from the data: a one-key dictionary, `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), `{"top": n}` or `{"bottom": n}`; the matching bars are highlighted, the rest muted, and the values print on the highlighted bars only. A data point's own `"emphasis"` key (`"highlight"` or `"background"`, also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants) wins over the rule. The [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Asking which directions average above 15 km/h leaves three western sectors standing out of a grey compass. ``` RadialChart( data=wind_by_direction, type=RADIAL_TYPE.BAR, title="Directions averaging above 15 km/h", # highlight the strong directions, mute the rest emphasis_rule={"above": 15}, show_values=True, value_format="%.1f", ymin=0, ).show() ``` `sort` orders the sectors around the circle by value instead of by input order, with a [SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) member: `SORT.DESCENDING` puts the largest sector first, at the start angle, `SORT.ASCENDING` the smallest. Sorting gives up the compass positions, so it answers a ranking question ("how do the directions rank by speed?") and not a directional one; the labels travel with their sectors, so nothing is lost. With several series one order serves all of them, keyed by the total across the series, or by the series named in `sort_by` (see [Bar mode](#bar-mode)). ``` from datachart.constants import SORT RadialChart( data=wind_by_direction, type=RADIAL_TYPE.BAR, title="Directions ranked by average wind speed", # the strongest direction first, clockwise from the top sort=SORT.DESCENDING, emphasis_rule={"above": 15}, ymin=0, ).show() ``` ### Values and labels at the tips When the exact numbers matter, `show_values` prints each mark's value at its tip, rotated along its spoke, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"` or `"%.1f"` style string. The label font size and color are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), shared by every chart that prints values. ``` from datachart.constants import VALUE_FORMAT RadialChart( data=sunshine_by_month, type=RADIAL_TYPE.BAR, title="Monthly sunshine hours", # a wider hole spreads the short winter labels apart innerradius=0.4, # print each month's hours at the tip of its bar show_values=True, value_format=VALUE_FORMAT.INTEGER, ).show() ``` The category labels sit on a ring around the circle by default, at some distance from the bars they name. `show_tip_labels` moves them to the tips instead: each label hugs the outermost mark on its spoke and flips on the left half so it always reads outward. With `show_border=False`, which hides the outer circle, and a donut hole, this is the circular bar plot that reads at a glance from a slide. ``` RadialChart( data=sunshine_by_month, type=RADIAL_TYPE.BAR, title="Monthly sunshine hours", innerradius=0.3, # the month names ride the bar tips, and the border circle goes show_tip_labels=True, show_border=False, figsize=FIG_SIZE.SQUARE, ).show() ``` ### Reference bands A straight reference line has no place on a circle, but a shaded band does. `vspans` shades an angular **wedge** over the full radius, bounded by `xmin` and `xmax` in degrees measured from the start angle in the chart's direction, so with the compass defaults `180` to `270` is the quadrant from south to west. `hspans` shades a **ring** over the full circle, bounded by `ymin` and `ymax` in radial values. Both take a dictionary or a list of them, with an optional `label` for the legend and a `style` with the `plot_vspan_*` or `plot_hspan_*` attributes ([VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs)); an omitted bound runs to the edge. The example shades the quadrant the strongest winds come from and the speed range of a gentle breeze (about 12 to 20 km/h, force 3 on the Beaufort scale), with `ymax` above it so the ring ends inside the border. ``` from datachart.constants import LEGEND_LOCATION RadialChart( data=wind_by_direction, # a wedge from south (180°) to west (270°) vspans={"xmin": 180, "xmax": 270, "label": "SW quadrant"}, # a ring between 12 and 20 km/h hspans={ "ymin": 12, "ymax": 20, "label": "gentle breeze", "style": {"plot_hspan_color": "#e9a03b"}, }, title="Average wind speed by direction", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ymin=0, ymax=25, ).show() ``` ### Text annotations Where a band marks a range, a note explains a point. `texts` places text on the chart, with an optional `target` to draw a connector to a mark. Data coordinates on a polar axes are (angle in radians, radius), with the angle counted from the start angle in the chart's direction, so the west value of an eight-point compass sits at 270°. Axes fractions (`"coords": "axes"`) are the other option, for a note in a corner; here the note sits inside the profile, on the west spoke, and points outward at the value. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. ``` import math RadialChart( data=wind_by_direction, # a note on the west spoke (angle in radians, radius), pointing at the value texts={ "text": "strongest\nfrom the west", "x": math.radians(270), "y": 9, "target": (math.radians(270), SPEED[COMPASS.index("W")]), }, title="Average wind speed by direction", ymin=0, ).show() ``` ## Multiple Radial Charts To compare several series, pass a list of lists to `data`: each inner list is one series, all drawn with the figure's one `type`, and the per-series attributes (`subtitle`, `style`, `emphasis`) become lists aligned with it. `show_legend` names the series by their subtitles. `wind_last_year`, defined in a hidden cell, holds the same station's averages of the year before, and two radar profiles on one circle show where the wind picked up: the western directions grew, the north-east barely moved. ``` RadialChart( # one series per year data=[wind_last_year, wind_this_year], # named for the legend subtitle=["Last year", "This year"], # last year in grey, this year in color style=[{"plot_line_color": "#9a9a9a"}, {"plot_line_color": "#1f77b4"}], title="Average wind speed by direction", show_legend=True, # outside the circle, clear of the category labels legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ymin=0, ).show() ``` When the question is about one of the series, `emphasis` takes one role per series, aligned with `data` like `subtitle` and `style`: `"highlight"` bolds a series and brings it to the front, `"background"` mutes it and drops it from the legend, `None` leaves it as it is. This year is the story; last year becomes the context: ``` RadialChart( data=[wind_last_year, wind_this_year], subtitle=["Last year", "This year"], # mute last year, highlight this year emphasis=["background", "highlight"], title="Average wind speed by direction", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ymin=0, ).show() ``` ### Bar mode Several bar series share each sector the way cartesian bars share a category, and `bar_mode` says how, with a [BAR_MODE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) member: `BAR_MODE.GROUP` (the default) puts them side by side within the sector, `BAR_MODE.STACK` stacks them, so the outer edge is the total and the segments its split, and `BAR_MODE.OVERLAY` draws them at the same position, one over the other. `morning` and `afternoon`, defined in a hidden cell, split each month's sunshine into the hours before and after noon; stacked, they keep the monthly total readable and show the afternoon's larger share. ``` from datachart.constants import BAR_MODE RadialChart( data=[morning, afternoon], type=RADIAL_TYPE.BAR, subtitle=["Morning", "Afternoon"], # stack the two halves of the day in each sector bar_mode=BAR_MODE.STACK, title="Monthly sunshine hours", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, innerradius=0.25, ).show() ``` With several series, `sort` orders the sectors by the total across them, and `sort_by` names the series (by its `subtitle`) that keys the order instead. Ranked by the afternoon hours alone, the months come out in the order of their afternoons, July first: ``` RadialChart( data=[morning, afternoon], type=RADIAL_TYPE.BAR, subtitle=["Morning", "Afternoon"], bar_mode=BAR_MODE.STACK, title="Months ranked by afternoon sunshine", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, innerradius=0.25, # one order for both series, keyed by one of them sort=SORT.DESCENDING, sort_by="Afternoon", ).show() ``` ### Legend `show_legend` lists the series; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The default corner of the axes is where the category labels of a polar plot sit, so a legend there covers one of them; the examples above use `LEGEND_LOCATION.OUTSIDE_RIGHT` to keep it clear of the circle. Below the circle, a title and two columns turn the legend into one tidy row. ``` RadialChart( data=[wind_last_year, wind_this_year], subtitle=["Last year", "This year"], title="Average wind speed by direction", show_legend=True, # a titled, two-column legend below the circle legend={"title": "Year", "location": LEGEND_LOCATION.OUTSIDE_BOTTOM, "ncols": 2}, ymin=0, ).show() ``` ### Subplots When the profiles are many, or two of them overlap so much that one circle turns into a tangle, `subplots=True` draws each series in its own polar subplot. `subtitle` titles the panels, `title` stays global, and `max_cols` limits the panels per row. `sharey=True` gives every panel the same radial range, so a profile in one panel is comparable with a profile in the next; without it each panel scales to its own maximum and last year's smaller profile would look as large as this year's. `sharex=True` keeps one angular axis for all of them. ``` RadialChart( data=[wind_last_year, wind_this_year], subtitle=["Last year", "This year"], title="Average wind speed by direction", figsize=FIG_SIZE.FULL_SHORT, # one polar panel per year, side by side subplots=True, max_cols=2, # the same radial range for both panels sharey=True, ymin=0, ).show() ``` ## Additional Features ### Error bands and filled areas The line visual takes the enrichments of the line chart. An average hides the spread behind it: `show_yerr=True` draws a band of `yerr` around the line, so the gusty western directions show a wide band and the calm eastern ones a narrow one. The band, like the area below, uses the `plot_area_*` style attributes. ``` RadialChart( data=wind_by_direction, # the gust standard deviation as a band around the line show_yerr=True, title="Average wind speed by direction, with the gust spread", ymin=0, ).show() ``` `show_area=True` fills the polygon the line encloses, which turns a profile into a footprint: the area says how much wind there is over all directions at once, and two filled profiles compare as shapes rather than as lines. ``` RadialChart( data=wind_by_direction, # fill the polygon the line encloses show_area=True, title="Average wind speed by direction", ymin=0, ).show() ``` ### Radial axis scale The radial axis is a value axis, and `scaley` changes its scale with a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, like the y-axis of a cartesian chart; the angular axis has no scale. A logarithmic radius is for values that span orders of magnitude around the cycle. `lightning_by_month`, defined in a hidden cell, holds the illustrative monthly count of lightning strikes recorded by the station, from a handful in winter to thousands in summer. On a linear radius the winter months would sit on the center; on a log radius starting at `ymin=1` every month is readable. The scatter visual fits here: each count is a point that stands alone, and a line through them would draw a shape that is mostly the scale. ``` from datachart.constants import SCALE RadialChart( data=lightning_by_month, type=RADIAL_TYPE.SCATTER, # a log radius: from a handful to thousands on one circle scaley=SCALE.LOG, # start the radius at one strike, so the winter counts leave the center ymin=1, title="Lightning strikes by month", ylabel="Strikes (log scale)", ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `label`, `y`, `yerr` and `x` keys, and renaming every record just to plot it is a chore. Instead, tell `RadialChart` which keys to read with the `label`, `y`, `yerr` and `x` arguments (`x` names the key holding the degree readings of the histogram visual). `station_records` stores the wind table the way a CSV export would, one record per direction with a `direction`, a `speed` and a `gust_sd` key: ``` station_records = [ {"direction": d, "speed": s, "gust_sd": g} for d, s, g in zip(COMPASS, SPEED, GUST_STD) ] station_records[:2] ``` ``` RadialChart( data=station_records, # the keys that hold the label, the value and the error label="direction", y="speed", yerr="gust_sd", show_yerr=True, title="Average wind speed by direction", ymin=0, ).show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question with the visual that fits it. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Does the Wind Turn with the Season? (Angular Histograms in a Grid) `winter_directions` and `summer_directions` hold illustrative wind direction readings of the station for the two seasons, drawn from a seeded generator: a coast where the winter storms come from the south-west and a summer sea breeze sets in from the south-east. The question is about a distribution of directions, so each season gets a wind rose (the histogram visual with 16 sectors), and the two roses go side by side in a [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) with the same radial range, so the sizes of the lobes compare across the seasons and not just their directions. ``` from datachart.utils import Grid def wind_rose(data, season): # a wind rose with 16 sectors on a shared radial range return RadialChart( data=data, type=RADIAL_TYPE.HISTOGRAM, num_bins=16, title=season, show_grid=SHOW_GRID.BOTH, ymin=0, ymax=100, ) Grid( [[wind_rose(winter_directions, "Winter"), wind_rose(summer_directions, "Summer")]], title="Wind direction frequency by season", figsize=(6.3, 3.4), ).show() ``` ### Example 2: Which Candidate Fits the Role? (Radar Profiles with Areas and a Requirement) `role_profile`, `candidate_a` and `candidate_b` hold illustrative interview scores on six skills, on a 0 to 10 scale, and the minimum the role requires on each. The question compares two entities across several metrics on one scale, which is what the radar form is for: `show_area` turns each profile into a footprint, and a fixed radial range of 0 to 10 keeps the scale honest. The requirement is drawn as a dashed profile without an area, so wherever a candidate's footprint stays inside the dashed line, that candidate falls short: A on databases, B on visualization. ``` RadialChart( data=[role_profile, candidate_a, candidate_b], subtitle=["Role requirement", "Candidate A", "Candidate B"], style=[ # the requirement: a dashed dark outline and no footprint {"plot_line_color": "#333333", "plot_line_style": LINE_STYLE.DASHED, "plot_area_alpha": 0}, {"plot_line_color": "#e76f51"}, {"plot_line_color": "#2a9d8f"}, ], # filled footprints on a fixed 0-10 scale show_area=True, ymin=0, ymax=10, title="Interview skill assessment", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: Was This Season Busier Than Last? (Stacked Donut Bars and a Panel) `weekday_visits` and `weekend_visits` hold illustrative monthly visitor counts at a mountain hut, split by weekdays and weekends, and `visits_last_year` the monthly totals of the year before. Months around a circle read like a calendar clock, and the hut's season is one long summer swing on it. The two splits are stacked bars with a donut hole, so the outer edge of each stack is the month's total; last year's totals are the comparison, drawn as a line over the same scale. `RadialChart` draws one visual per figure, so the bars and the line are two figures overlaid with [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md), which keeps the first figure's start angle and donut hole. The panel owns how bars share a sector, so `bar_mode` is passed to `Panel` as well. Wherever the line pokes out of the bars, last year was busier: September, and nowhere else. ``` from datachart.utils import Panel this_year = RadialChart( data=[weekday_visits, weekend_visits], type=RADIAL_TYPE.BAR, subtitle=["Weekdays", "Weekends"], # stacked, so the outer edge is the month's total bar_mode=BAR_MODE.STACK, innerradius=0.3, ) last_year = RadialChart( data=visits_last_year, subtitle="Last year, total", style={"plot_line_color": "#222222", "plot_line_marker": LINE_MARKER.CIRCLE}, ) Panel( # the bars first: the panel keeps their donut hole [this_year, last_year], # the panel stacks the bar series too bar_mode=BAR_MODE.STACK, title="Mountain hut visitors by month", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Gantt Chart A gantt chart shows a schedule: one bar per task from its start to its end over a date axis, so a plan answers *what happens when, what is late, and what waits on what*. This guide shows how to create gantt charts with the [datachart.charts.GanttChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.charts.GanttChart) function, starting with the basics and building up to worked examples on realistic schedules. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-gantt-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import GanttChart ``` ## Basics The examples in this guide share one schedule: building a family house, from the architect's drawings on 1 March 2025 to the handover at the end of October. The schedule is illustrative, written out in a hidden cell with the shape such builds take: drawings and a permit first, then groundwork, the structure, the services, and the finishes. It has a story in it. Rain held up the foundations for two weeks, and every task after them slipped by the same two weeks, while the kitchen, ordered early, kept its dates. `house` holds the task records as the site manager sees them on Monday 16 June 2025, `TODAY`; `baseline` holds the plan as it was signed, before the rain. The customizations below ask the questions a site manager asks of such a plan: what is next, what is late, what blocks what, and how far along the build is. Each task record is a dictionary. `task` names the task and labels its row, and `start` and `end` are temporal objects (`datetime.date` here; `datetime.datetime`, `numpy.datetime64` and pandas `Timestamp` work too, while date strings are never parsed). The other keys are optional: the `group` the task belongs to, the `progress` done as a fraction, the tasks it `depends_on`, and its own `emphasis`. The foundations record carries all but the last: ``` house[4] ``` **Basic example.** Only the `data` argument is required. Every task is a bar from its start to its end, one row per task in input order with the first at the top, and the date ticks step from the first start to the last end, in whole months here. The groups color the bars and fill the legend, a `progress` fraction fills part of a bar with a darker inner bar, and a task that ends when it starts (the permit, the weathertight shell, the handover) is a milestone marker: ``` GanttChart( # add the data to the chart data=house ).show() ``` ## Customizing the Gantt Chart Every customization is either a keyword argument of `GanttChart`, a key of the task records, or a `plot_gantt_*` (or `plot_bar_*`) attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | -------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel`, `ytickrotate` | [Title and axis labels](#title-and-axis-labels) | | resize the figure or show grid lines | `figsize`, `show_grid` | [Figure size and grid](#figure-size-and-grid) | | zoom to a date window | `xmin`, `xmax` | [Date axis](#date-axis) | | change or rotate the date labels | `xticks_format`, `xtickrotate` | [Date axis](#date-axis) | | divide the date axis into weeks or months | `period` | [Periods](#periods) | | order the rows by start or by group | `sort`, `sort_by` | [Row order](#row-order) | | color the tasks by group, place the legend | the `"group"` key, `show_legend`, `legend` | [Groups and legend](#groups-and-legend) | | give each group a header row and summary bar | `show_group_headers` | [Group headers](#group-headers) | | mark a milestone | a task with `end` equal to `start` | [Milestones](#milestones) | | show how far along each task is | the `"progress"` key, `show_values`, `value_format` | [Progress and value labels](#progress-and-value-labels) | | label each bar with its duration | `show_values`, `value_format` | [Progress and value labels](#progress-and-value-labels) | | draw what blocks what | the `"depends_on"` key, `show_dependencies` | [Dependencies](#dependencies) | | choose where the arrows enter a task | `style={"plot_gantt_dependency_entry": ...}` | [Dependencies](#dependencies) | | mark today | `show_today`, `today`, `today_label` | [Today line](#today-line) | | mark a deadline or shade a period | `vlines`, `vspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | change the bar, progress, or arrow look | `style={"plot_gantt_bar_height": ..., ...}` | [Gantt style](#gantt-style) | | highlight some tasks, mute the rest | the `"emphasis"` key, `emphasis_rule` | [Emphasis](#emphasis) | | compare several schedules | `data` as a list of lists, `subtitle`, `max_cols`, `emphasis` | [Multiple Gantt Charts](#multiple-gantt-charts) | | place a schedule beside other charts | `Grid` | [Composing gantt charts](#composing-gantt-charts) | | schedule by the hour, or use numpy dates | `datetime` or `numpy.datetime64` values | [Other date types](#other-date-types) | | restyle every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `period` | [`GANTT_DATE_PERIOD`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_DATE_PERIOD) | | `show_values` | [`GANTT_VALUE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_VALUE) | | `sort_by` | [`GANTT_SORT_KEY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_SORT_KEY) | | `style={"plot_gantt_dependency_entry": ...}` | [`GANTT_ARROW_ENTRY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_ARROW_ENTRY) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `xticks_format` | [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.GanttStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttStyleAttrs) type, and the task record keys in [datachart.typings.GanttTaskAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttTaskAttrs); the full list of parameters is in the [datachart.charts.GanttChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.charts.GanttChart) reference. ### Title and axis labels A plan pinned to a site office wall needs to say which project it is; `title` says it, and `xlabel` and `ylabel` name the axes. The gantt chart is always horizontal, so the axis parameters are spatial: `xlabel` names the horizontal date axis and `ylabel` the vertical task axis. Task names are read left to right, so `ytickrotate` is rarely needed; it is there for very short labels such as ticket numbers. ``` from datachart.constants import FIG_SIZE GanttChart( data=house, # add the title title="House build, 2025", # add the x and y axis labels xlabel="Date", ylabel="Task", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Figure size and grid A schedule has one row per task, so its height grows with the plan while its width is set by the page. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE); sixteen tasks fit a full-width, medium-height figure. Vertical grid lines carry a date down the rows, so the reader can see which tasks run in the same month. `show_grid=SHOW_GRID.X` draws them along the date axis ([SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID)); `SHOW_GRID.Y` would add a line per row, which a gantt chart rarely needs. Plumbing, wiring, and the windows share August: ``` from datachart.constants import SHOW_GRID GanttChart( data=house, title="House build, 2025", # a full-width figure, tall enough for sixteen rows figsize=FIG_SIZE.FULL_MEDIUM, # grid lines along the date axis only show_grid=SHOW_GRID.X, ).show() ``` ### Date axis The whole build spans eight months, but the site manager's question on a Monday is *what happens in the next few weeks?* `xmin` and `xmax` take temporal objects and zoom the date axis to that window; bars that run past its edges are cut at them. The default tick labels are concise; `xticks_format` prints every tick in one format, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern, and `xtickrotate` tilts the labels when they crowd. Rows of tasks outside the window would stay empty, so the example first keeps the tasks that touch it (and the dependencies among them, since `depends_on` must name tasks in the chart). The eight weeks from 9 June show the foundations finishing, the framing starting on their heels, and the roofing following it. ``` WINDOW = (date(2025, 6, 9), date(2025, 8, 4)) def in_window(records, first, last): # the tasks that touch the window, their dependencies cut to the tasks kept kept = [r for r in records if r["end"] > first and r["start"] < last] names = {r["task"] for r in kept} return [{**r, "depends_on": [d for d in r.get("depends_on", []) if d in names]} for r in kept] next_weeks = in_window(house, *WINDOW) GanttChart( data=next_weeks, title="House build, the next eight weeks", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, show_legend=False, # zoom to a date window xmin=WINDOW[0], xmax=WINDOW[1], # print every tick as day and month, tilted xticks_format="%d %b", xtickrotate=30, ).show() ``` ### Periods Builders count in weeks, finance counts in months or quarters, and a printed plan shows those periods as columns. `period` divides the date axis into calendar periods ([GANTT_DATE_PERIOD](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_DATE_PERIOD)): `DAY`, `WEEK` (ISO weeks, starting on Monday), `MONTH`, `QUARTER`, `YEAR`, or `PROJECT_MONTH`, which numbers the months M1, M2, … from `xmin` or the earliest start. Lines mark the period edges, each period is labeled at its center, and a parent row beneath names the enclosing month (for days and weeks), year (for months and quarters), or project year. `xticks_format` sets the period labels. In ISO weeks, the window above shows that the framing takes six weeks, from week 26 to week 31: ``` from datachart.constants import GANTT_DATE_PERIOD GanttChart( data=next_weeks, title="House build, by week", figsize=FIG_SIZE.FULL_SHORT, show_legend=False, xmin=WINDOW[0], xmax=WINDOW[1], # ISO weeks, named under their months period=GANTT_DATE_PERIOD.WEEK, ).show() ``` Over the whole build, months are the natural column. A bank that releases the mortgage in stages reads the same plan by project month instead, since its contract counts from the first month of work: ``` for period in [GANTT_DATE_PERIOD.MONTH, GANTT_DATE_PERIOD.PROJECT_MONTH]: GanttChart( data=house, title=f"House build, by '{period}'", figsize=FIG_SIZE.FULL_MEDIUM, # months under their year, or M1, M2, ... under project years period=period, ).show() ``` ### Row order The rows follow the input order, which is how the plan was written, but the question *what starts next?* wants the rows in start order. `sort` orders them ([SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT)), and `sort_by` names the key ([GANTT_SORT_KEY](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_SORT_KEY)): `START`, the default, orders every row by its start date, so the chart reads as a staircase and the kitchen order, written under the finishes, moves up to June where it starts. Ties keep the input order. ``` from datachart.constants import SORT, GANTT_SORT_KEY GanttChart( data=house, title="House build, in start order", figsize=FIG_SIZE.FULL_MEDIUM, # earliest start at the top sort=SORT.ASCENDING, sort_by=GANTT_SORT_KEY.START, ).show() ``` `GANTT_SORT_KEY.GROUP` clusters the rows by group instead, the groups ordered by their earliest start and the tasks within a group by start, so each trade reads as one block. The finishes start with the kitchen order in June, so they come before the structure. `sort_by` without `sort`, or `GROUP` when no task has a `group`, raises a `ValueError`. ``` GanttChart( data=house, title="House build, by group", figsize=FIG_SIZE.FULL_MEDIUM, # cluster the rows by group, earliest group first sort=SORT.ASCENDING, sort_by=GANTT_SORT_KEY.GROUP, ).show() ``` ### Groups and legend On a building site, the question is often *which trade is on site?* A task's `group` key answers it: tasks of one group share a color from the theme's palette, in first-seen order, and the legend lists one entry per group. The legend is on whenever a task has a `group`; `show_legend=False` hides it, and `legend` gives it a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), a number of columns `ncols`, and an `alignment` from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The schedule runs corner to corner, so the legend goes outside the axes. Tasks without a `group` draw in the chart's palette color and have no legend entry. ``` from datachart.constants import LEGEND_LOCATION GanttChart( data=house, title="House build, 2025", figsize=FIG_SIZE.FULL_MEDIUM, # a titled legend outside the axes, to the right legend={"title": "Trade", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Group headers A client does not ask when the plumbing starts; they ask *how long is each phase?* `show_group_headers=True` gives every group a header row with its name in bold and a summary bar from its first start to its last end, the group's tasks beneath it and a gap before the next group. The headers name the groups, so the legend is off unless `show_legend` turns it on. The summary bar and the gap follow the `plot_gantt_summary_height`, `plot_gantt_summary_color`, and `plot_gantt_group_gap` style attributes; without any `group`, `show_group_headers` raises a `ValueError`. The finishes phase is the longest, because it opens with the early kitchen order: ``` GanttChart( data=house, title="House build, by phase", # five header rows join the sixteen tasks figsize=FIG_SIZE.FULL_TALL, # a header row and a summary bar for every group show_group_headers=True, period=GANTT_DATE_PERIOD.MONTH, ).show() ``` ### Milestones Some events take no time but everything waits for them: the permit, the day the shell is weathertight, the handover. A task whose `end` equals its `start` is a milestone, drawn as a marker instead of a bar. Under `show_values` a milestone prints its date beside the marker, in the `xticks_format` or as day and month, since it has no duration to print; the `plot_gantt_milestone_marker` and `plot_gantt_milestone_size` style attributes set its look. `milestones` keeps only the three milestones of the build (without their dependencies, whose tasks are left out), so the chart answers *when are the key dates?* ``` from datachart.constants import GANTT_VALUE milestones = [ {k: v for k, v in r.items() if k != "depends_on"} for r in house if r["start"] == r["end"] ] GanttChart( data=milestones, title="House build, key dates", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, # room around the first and the last date xmin=date(2025, 4, 1), xmax=date(2025, 12, 1), period=GANTT_DATE_PERIOD.MONTH, # a milestone prints its date show_values=GANTT_VALUE.DURATION, # a larger star style={"plot_gantt_milestone_marker": "*", "plot_gantt_milestone_size": 14}, show_legend=False, ).show() ``` ### Progress and value labels *How far along are we?* is answered by a task's `progress` key, the fraction done in `[0, 1]`, drawn as an inner bar from the task's start. `show_values` prints a label past each bar end ([GANTT_VALUE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_VALUE)): `PROGRESS` prints the progress as a percentage (a task without `progress` stays unlabeled), `DURATION` the duration in days. `value_format` formats the number, a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"` style string: it formats the progress fraction or the days. On 16 June the design and site work are done, the foundations are at 80%, and the kitchen order at 20%: ``` GanttChart( data=house, title="House build, progress on 16 June", figsize=FIG_SIZE.FULL_MEDIUM, # label each bar with its progress show_values=GANTT_VALUE.PROGRESS, ).show() ``` The durations answer a different question: *which tasks tie up the crew longest?* A duration counts the days from `start` to `end`, weekends included; `value_format` adds the unit: ``` GanttChart( data=house, title="House build, days per task", figsize=FIG_SIZE.FULL_MEDIUM, # label each bar with its duration, in days show_values=GANTT_VALUE.DURATION, value_format="{x:.0f} days", # room for the labels past the last bar xmax=date(2025, 11, 20), ).show() ``` ### Dependencies The framing crew cannot start until the foundations have cured, and the drywall waits for the pipes and wires; *what blocks what?* is the question behind every schedule change. A task's `depends_on` key lists the tasks that must finish before it, and `show_dependencies=True` draws an arrow from the end of each of them to the start of the dependent task. An unknown name in `depends_on` raises a `ValueError`, drawn or not. With the rows in start order, the chain from the permit to the handover reads down the chart: ``` GanttChart( data=house, title="House build, what blocks what", figsize=FIG_SIZE.FULL_MEDIUM, sort=SORT.ASCENDING, # draw an arrow for every dependency show_dependencies=True, show_legend=False, ).show() ``` By default an arrow runs along the dependency's row and turns down onto the top of the dependent task. The `plot_gantt_dependency_entry` style attribute set to `GANTT_ARROW_ENTRY.LEFT` ([GANTT_ARROW_ENTRY](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_ARROW_ENTRY)) drops the arrow from the dependency's end and enters the task from the left instead, the look of most project planning tools. A task that starts before its dependency ends leaves no room on its left and is entered from the top: the roofing, whose trusses go up while the last walls are framed. ``` from datachart.constants import GANTT_ARROW_ENTRY GanttChart( data=house, title="House build, arrows entering from the left", figsize=FIG_SIZE.FULL_MEDIUM, sort=SORT.ASCENDING, show_dependencies=True, show_legend=False, # enter each task from the left where there is room style={"plot_gantt_dependency_entry": GANTT_ARROW_ENTRY.LEFT}, ).show() ``` ### Today line A plan is read against the present: everything left of today should be done. `show_today=True` draws a line at `today`, which is the current date unless the `today` parameter sets one (as here, so the guide renders the same every day), and `today_label` prints a label at the foot of the line. On 16 June the foundations should be nearly done, and they are at 80%; the framing starts next week. ``` GanttChart( data=house, title="House build, where we stand", figsize=FIG_SIZE.FULL_MEDIUM, show_values=GANTT_VALUE.PROGRESS, # mark the day the project stands on show_today=True, today=TODAY, today_label="16 June", ).show() ``` ### Reference lines and bands A schedule also lives against dates it does not control: a contract date, a rainy fortnight, a holiday. `vlines` draws a vertical line at a temporal `x`, and `vspans` shades the band between a temporal `xmin` and `xmax`; each takes a dictionary or a list of them, with a `style` ([VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs)). The grey band below shows where the two weeks went, and the dashed red line, the contract date, shows what they cost: the handover now lands twelve days after it. The gantt legend lists the task groups only, so the next subsection names the line and the band with notes. ``` from datachart.constants import LINE_STYLE GanttChart( data=house, title="House build, the rain delay and the contract date", figsize=FIG_SIZE.FULL_MEDIUM, show_legend=False, # the date the contract promises the house vlines={ "x": CONTRACT_DATE, "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DASHED}, }, # the two weeks the rain took from the foundations vspans={"xmin": FOUNDATIONS["end"] - RAIN_DELAY, "xmax": FOUNDATIONS["end"]}, ).show() ``` ### Text annotations A line marks a date; a note explains it. `texts` places text on the chart, with an optional `target` to draw a connector to a point ([TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs)); the [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connectors, and styling. On a gantt chart the vertical data coordinate is the row index (the first row is `0`), and the horizontal one is a matplotlib date number, which `matplotlib.dates.date2num` converts a date into; `texts` does not take a `date` directly. Placing a note in axes fractions with `"coords": "axes"` keeps it clear of the bars. The two notes below name the band and the line of the previous chart: one points at the end of the foundations, the task that slipped, the other at the contract date on the handover's row, with a straight arrow from [ARROW_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ARROW_STYLE) set in its `style` so it runs under the bars rather than over them. ``` from matplotlib.dates import date2num from datachart.constants import ARROW_STYLE HANDOVER_ROW = 15 GanttChart( data=house, title="House build, what slipped", figsize=FIG_SIZE.FULL_MEDIUM, show_legend=False, vlines={ "x": CONTRACT_DATE, "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DASHED}, }, vspans={"xmin": FOUNDATIONS["end"] - RAIN_DELAY, "xmax": FOUNDATIONS["end"]}, texts=[ # in the empty upper right, pointing at the foundations' end (row 4) { "text": "rain: foundations two weeks late,\nevery later task moves with them", "x": 0.45, "y": 0.85, "coords": "axes", "target": (date2num(FOUNDATIONS["end"]), 4), }, # under the last row, a straight arrow to the contract date { "text": "contract date, 17 Oct", "x": 0.58, "y": 0.04, "coords": "axes", "target": (date2num(CONTRACT_DATE), HANDOVER_ROW), "style": {"plot_text_arrow_style": ARROW_STYLE.ARROW}, }, ], ).show() ``` ### Gantt style The `style` dictionary sets the look of the schedule. The bars take the bar chart's `plot_bar_*` attributes (color, alpha, edge, hatch), and the attributes specific to a gantt chart are listed in [datachart.typings.GanttStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttStyleAttrs); any attribute left out keeps the value of the active theme. | Attribute | Description | | ------------------------------ | ----------------------------------------------------------------------------------------- | | `plot_gantt_bar_height` | The height of a task bar, as a fraction of its row. | | `plot_gantt_progress_color` | The color of the progress bar; `None` darkens the task bar's color. | | `plot_gantt_progress_alpha` | The alpha (transparency) of the progress bar. | | `plot_gantt_progress_height` | The height of the progress bar, as a fraction of the task bar. | | `plot_gantt_dependency_color` | The color of the dependency arrows. | | `plot_gantt_dependency_width` | The line width of the dependency arrows. | | `plot_gantt_dependency_style` | The arrow head, as a matplotlib arrow style string such as `"->"`. | | `plot_gantt_dependency_zorder` | The zorder of the dependency arrows. | | `plot_gantt_dependency_entry` | The side a dependency arrow enters its task: `"top"` or `"left"`. | | `plot_gantt_summary_height` | The height of a group's summary bar under `show_group_headers`, as a fraction of its row. | | `plot_gantt_summary_color` | The color of the summary bars; `None` takes each group's color. | | `plot_gantt_group_gap` | The empty space before each group header, in rows. | | `plot_gantt_milestone_marker` | The marker of a milestone. | | `plot_gantt_milestone_size` | The size of the milestone marker, in points. | | `plot_gantt_today_color` | The color of the today line. | | `plot_gantt_today_style` | The line style of the today line. | | `plot_gantt_today_width` | The line width of the today line. | | `plot_gantt_today_alpha` | The alpha (transparency) of the today line. | A progress report is read for the done part first, so the example below paints every task a pale grey, fills the done part at full height in a strong color, and draws light, thin arrows and a solid today line that stay out of the way. ``` GanttChart( data=house, title="House build, a progress report", figsize=FIG_SIZE.FULL_MEDIUM, show_dependencies=True, show_today=True, today=TODAY, show_legend=False, style={ # pale task bars, the done part at full height "plot_bar_color": "#dfe3e8", "plot_bar_edge_width": 0, "plot_gantt_bar_height": 0.7, "plot_gantt_progress_color": "#2a9d8f", "plot_gantt_progress_height": 1.0, # light arrows with open heads "plot_gantt_dependency_color": "#adb5bd", "plot_gantt_dependency_width": 0.8, "plot_gantt_dependency_style": "->", # a solid today line "plot_gantt_today_color": "#264653", "plot_gantt_today_style": LINE_STYLE.SOLID, "plot_gantt_today_width": 1.5, }, ).show() ``` ### Emphasis A schedule makes a point when it shows only the tasks that matter to the question. A task record's own `"emphasis"` key takes a role from [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS): `"highlight"` strokes the bar bolder and brings it to the front, `"background"` mutes it in the theme's muted color; a group whose every task is muted leaves the legend. Tagging the critical path, the chain of tasks with no slack, answers *which delays move the handover?* The [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. ``` # the critical path highlighted, the rest muted critical = [ {**r, "emphasis": "highlight" if r["task"] in CRITICAL_PATH else "background"} for r in house ] GanttChart( data=critical, title="House build, the critical path", figsize=FIG_SIZE.FULL_MEDIUM, show_dependencies=True, show_legend=False, ).show() ``` `emphasis_rule` picks the tasks from the data instead: it is a one-key dictionary on each task's duration in days, `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), `{"top": n}` or `{"bottom": n}`. The tasks that match are highlighted and the rest muted, and a record's own `"emphasis"` key wins over the rule. The tasks that run longer than a month are the ones a delay is most likely to hit: ``` GanttChart( data=house, title="House build, tasks longer than a month", figsize=FIG_SIZE.FULL_MEDIUM, show_values=GANTT_VALUE.DURATION, xmax=date(2025, 11, 10), # highlight the tasks longer than 30 days emphasis_rule={"above": 30}, ).show() ``` ## Multiple Gantt Charts *Where did the plan go wrong?* is answered by setting the plan beside the forecast. A list of lists in `data` draws one schedule per subplot, one per row unless `max_cols` sets more columns, and the per-schedule parameters (`subtitle`, `style`, `emphasis`) become lists aligned with it. The whole-schedule `emphasis` mutes the signed plan, so the eye goes to the forecast, where every task from the foundations on sits two weeks later. ``` GanttChart( # the signed plan and the current forecast data=[baseline, house], subtitle=["Plan, as signed", "Forecast, 16 June"], title="House build, plan against forecast", figsize=FIG_SIZE.A4_PORTRAIT, # one schedule per row, on one date window max_cols=1, xmin=date(2025, 3, 1), xmax=date(2025, 11, 7), # the plan is context, the forecast is the point emphasis=["background", None], show_legend=False, ).show() ``` ### Composing gantt charts A gantt chart draws time along one axis and tasks down the other, so there is no shared coordinate space to overlay another chart on: [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) rejects a gantt figure. [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges it beside other charts, each in its own cell ([Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide). The example sets the schedule over a bar chart of the task days each trade books, the number a builder prices the job by. ``` from datachart.charts import BarChart from datachart.utils import Grid trades = list(dict.fromkeys(r["group"] for r in house)) task_days = [ {"label": trade, "y": sum((r["end"] - r["start"]).days for r in house if r["group"] == trade)} for trade in trades ] schedule = GanttChart(data=house, title="Schedule", show_legend=False, period=GANTT_DATE_PERIOD.MONTH) effort = BarChart(data=task_days, title="Task days per trade", show_grid=SHOW_GRID.Y, ymin=0) Grid([[schedule], [effort]], title="House build, 2025", figsize=FIG_SIZE.FULL_TALL).show() ``` ## Additional Features ### Other date types Records often come from a file or a database rather than from `datetime` code. `start` and `end` accept any temporal type: `numpy.datetime64` values, as numpy reads dates from a CSV file, pandas `Timestamp` values, as a data frame holds them, and `datetime.datetime` values, which place a bar's ends at a time of day. Date strings are never parsed, so a column of strings has to be converted first. `curing`, defined in a hidden cell, is the illustrative plan for the four weeks after the foundation slab is poured, written as `numpy.datetime64` days: the slab is kept moist for a week while it gains strength, and test cubes are crushed at 7 and 28 days to check that the concrete reaches it. ``` GanttChart( data=curing, title="Foundation slab, the first four weeks", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, show_values=GANTT_VALUE.DURATION, xmax=date(2025, 7, 23), # print the ticks as day and month xticks_format="%d %b", ).show() ``` ### Themes A theme sets the palette, the fonts, and the bar, arrow, and today-line styles of every chart at once; themes that tell series apart by hatching give each task group its own hatch, which keeps a printed plan readable in black and white. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) as the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide shows; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows the whole suite under each theme. Style is resolved when the chart is created, so a theme set before the call and reset after it applies to that chart alone. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.HATCH) figure = GanttChart( data=house, title="House build, 2025", figsize=FIG_SIZE.FULL_MEDIUM, show_dependencies=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ) config.set_theme(THEME.DEFAULT) figure.show() ``` ## Real-World Examples The examples below put the features above to work on three schedules, each one answering a planning question. The schedules are illustrative, written out in hidden cells with the shape real ones take; each example says what its data stands for. ### Example 1: Will the Sprint Finish on Time? (Day Periods, Progress Labels, and the Today Line) `sprint` holds the tickets of an illustrative two-week sprint of an app team, Monday 1 to Friday 12 September 2025, with each ticket's `progress` as the board shows it on the morning of Thursday 11 September, `SPRINT_TODAY`. A ticket's `end` is the morning after its last working day, so a ticket worked on Monday and Tuesday ends on Wednesday and fills the Monday and Tuesday columns. The question is which tickets are behind: any ticket whose end has passed and whose progress is short of 100%. Day periods give the board its columns, the weekend is shaded, progress labels print the numbers, the today line marks the morning, and the late tickets carry a `"highlight"` role while the rest are muted (a muted ticket prints no label), so the eye lands on the two tickets to chase in the stand-up. A milestone on Saturday morning marks the end of the sprint. ``` GanttChart( data=sprint, title="Sprint 18, two tickets late on 11 September", figsize=FIG_SIZE.FULL_MEDIUM, # one column per day, under its month period=GANTT_DATE_PERIOD.DAY, xmin=date(2025, 9, 1), xmax=date(2025, 9, 15), # the weekend vspans={"xmin": date(2025, 9, 6), "xmax": date(2025, 9, 8)}, show_values=GANTT_VALUE.PROGRESS, show_today=True, today=SPRINT_TODAY, today_label="today", legend={"title": "Team", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Example 2: What Does the Grant Deliver, and When? (Quarters, Group Headers, Milestones, and Dependencies) `grant` holds the work plan of an illustrative three-year research grant, January 2026 to December 2028, in the layout funders ask for: work packages (WP1 to WP4) as groups, their tasks, and the milestones the reviewers check. A task runs from the first day of its first project month to the first day of the month after its last, which the hidden cell's `month(n)` helper computes. The reviewer's questions are *what is delivered in which year, and what depends on what?* Quarters under their years give the columns a funder reports in, group headers show how long each work package runs, the milestones mark the review points, a dotted line marks the mid-term review in July 2027, and the dependency arrows, entering from the left, show that the pilot study hangs on the validated model. ``` GanttChart( data=grant, title="Research grant work plan, 2026 to 2028", figsize=FIG_SIZE.FULL_MEDIUM, # quarters under their years period=GANTT_DATE_PERIOD.QUARTER, xmin=date(2026, 1, 1), xmax=date(2029, 1, 1), show_group_headers=True, show_dependencies=True, style={ "plot_gantt_dependency_entry": GANTT_ARROW_ENTRY.LEFT, "plot_gantt_group_gap": 0.3, }, # the mid-term review vlines={"x": date(2027, 7, 1), "style": {"plot_vline_style": LINE_STYLE.DOTTED}}, ).show() ``` ### Example 3: Which Team Is Holding Up the Launch? (Two Schedules, a Shared Window, a Note, and a Grid) An illustrative app launch on Monday 1 December 2025 depends on two teams. `platform` holds the platform team's tasks and `mobile` the mobile team's, as forecast on Monday 3 November, `LAUNCH_TODAY`. The tasks that end after the launch date carry a `"highlight"` role and the rest are muted, so a team whose chart is all grey is on time. The question is which team puts the launch date at risk. Each team's schedule is its own `GanttChart` with the same date window, the same today line, and a launch line, so [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) can stack them and the dates line up down the page. The platform team is on time; the mobile team's app store review ends after the launch, and a note on its chart says so. ``` def launch_schedule(data, team, **kwargs): # one team's forecast, on the shared window with the today and launch lines return GanttChart( data=data, title=team, xmin=date(2025, 9, 22), xmax=date(2025, 12, 15), period=GANTT_DATE_PERIOD.WEEK, show_today=True, today=LAUNCH_TODAY, today_label="today", # the launch date, solid and dark vlines={"x": LAUNCH, "style": {"plot_vline_color": "#264653", "plot_vline_style": LINE_STYLE.SOLID}}, **kwargs, ) review_end = MOBILE[2][2] Grid( [ [launch_schedule(platform, "Platform team")], [ launch_schedule( mobile, "Mobile team", # a note in the empty upper right, pointing at the review's end texts={ "text": "store review ends after\nthe 1 December launch", "x": 0.6, "y": 0.8, "coords": "axes", "target": (date2num(review_end), 2), }, ) ], ], title="App launch, forecast on 3 November", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Dumbbell Chart A dumbbell chart shows two values per category, a dot at each and a connector between them, so the reader sees the change from one state to the other (before and after, one year and a later one) or the gap between two groups, and how that change or gap compares across the categories. This guide shows how to create dumbbell charts with the [datachart.charts.DumbbellChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.charts.DumbbellChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-dumbbell-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import DumbbellChart ``` ## Basics The examples in this guide share one dataset: life expectancy at birth in twelve countries in 2000 and in 2019, the last year before the COVID-19 pandemic, in years (source: the World Health Organization's Global Health Observatory, indicator WHOSIS_000001, 2024 release, rounded to one decimal). The data lives in a hidden cell. `life` holds one record per country for both sexes together, `women` and `men` hold the same records per sex, and `recent` runs from 2019 to 2021, across the pandemic. Two decades of gains, a gap between women and men that is closing in some countries and opening in others, and a pandemic that undid part of the progress: the customizations below help to read each of these. Each record is a dictionary with a `label` (the category), a `start` value and an `end` value; here the value in 2000 and the value in 2019: ``` life[:3] ``` **Basic example.** Only the `data` argument is required. Every record is one row, the first at the top: a dot at its start, a dot at its end, and a connector between them. Every country gained, and the length of each connector is the gain: ``` DumbbellChart( # add the data to the chart data=life ).show() ``` ## Customizing the Dumbbell Chart Every customization is either a keyword argument of `DumbbellChart` or a `plot_dumbbell_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | --------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | rotate the tick labels | `xtickrotate`, `ytickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | change the grid lines | `show_grid`, `style={"plot_dumbbell_grid_minor": ...}` | [Figure size and grid](#figure-size-and-grid) | | run the values up the page | `orientation` | [Orientation](#orientation) | | order the categories by start, end, or change | `sort`, `sort_by` | [Category order](#category-order) | | name the two endpoints in a legend | `start_name`, `end_name`, `show_legend`, `legend` | [Endpoint names and legend](#endpoint-names-and-legend) | | print the endpoint values or the change | `show_values`, `value_format` | [Value labels](#value-labels) | | show whether each value rose or fell | `show_direction` | [Rises and falls](#rises-and-falls) | | tell the endpoints apart by shape | `marker` | [Markers and connectors](#markers-and-connectors) | | dash the connectors | `connector_style` | [Markers and connectors](#markers-and-connectors) | | change the dot and connector colors and sizes | `style={"plot_dumbbell_start_color": ..., ...}` | [Dumbbell style](#dumbbell-style) | | highlight some categories, mute the rest | `emphasis_rule`, the `"emphasis"` key of a record | [Emphasis](#emphasis) | | mark a reference value | `vlines`, `hlines` | [Reference lines and bands](#reference-lines-and-bands) | | shade a range of values | `vspans`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | compare several groups on the same categories | `data` as a list of lists, `subtitle` | [Multiple Dumbbell Charts](#multiple-dumbbell-charts) | | highlight one group, mute the rest | `emphasis` | [Multiple Dumbbell Charts](#multiple-dumbbell-charts) | | draw each group in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | combine with other charts | `Panel`, `Grid` | [Composing dumbbell charts](#composing-dumbbell-charts) | | use a logarithmic value axis | `scaley` | [Axis scales](#axis-scales) | | restyle every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `show_values` | [`DUMBBELL_VALUE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DUMBBELL_VALUE) | | `sort_by` | [`DUMBBELL_SORT_KEY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DUMBBELL_SORT_KEY) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `marker` | [`LINE_MARKER`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER) | | `connector_style` | [`LINE_STYLE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | The full list of style attributes is in the [datachart.typings.DumbbellStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.typings.DumbbellStyleAttrs) type; the full list of parameters is in the [datachart.charts.DumbbellChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.charts.DumbbellChart) reference. ### Title, axis labels and ticks Without a title and axis labels the reader cannot tell what the two dots stand for; `title`, `xlabel` and `ylabel` say it. The axis arguments are spatial: in the default horizontal chart `xlabel` names the value axis and `ylabel` the category axis. `xtickrotate` and `ytickrotate` tilt the tick labels when they crowd each other, which country names on the vertical axis do not need. `xmin`, `xmax`, `ymin` and `ymax` fix the axis range: a dumbbell encodes value by position, not by length, so the value axis need not start at zero, and a tighter range spreads the dots out. ``` DumbbellChart( data=life, # add the title title="Life expectancy at birth, 2000 to 2019", # add the x and y axis labels xlabel="Years", ylabel="Country", # fix the value axis range xmin=50, xmax=90, ).show() ``` ### Figure size and grid Twelve rows need height, and a chart with a few rows should not get it. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. The grid lines let the eye carry a dot across to the value axis. By default they follow the values, whichever way the chart runs, with fainter lines halfway between the labelled values so a value reads off quickly; the `plot_dumbbell_grid_minor` style attribute sets how many parts each step splits into (`0` draws no fainter lines). An explicit `show_grid`, a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member, names the axes literally (`SHOW_GRID.X`, `SHOW_GRID.Y` or `SHOW_GRID.BOTH`). The example keeps the vertical value grid and drops the fainter lines. ``` from datachart.constants import FIG_SIZE, SHOW_GRID DumbbellChart( data=life, title="Life expectancy at birth, 2000 to 2019", xlabel="Years", ylabel="Country", # a full-width, medium-height figure figsize=FIG_SIZE.FULL_MEDIUM, # grid lines along the value axis, without the fainter lines in between show_grid=SHOW_GRID.X, style={"plot_dumbbell_grid_minor": 0}, ).show() ``` ### Orientation Rows suit a long list of names, but a change reads naturally as a rise or a fall, and for that the values should run up the page. `orientation=ORIENTATION.VERTICAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) draws one column per category, the first at the left, so the end dot of a gain sits above its start dot. The axis labels, the grid and the axis limits swap with it; `xtickrotate` turns the country names out of each other's way. ``` from datachart.constants import ORIENTATION DumbbellChart( data=life, title="Life expectancy at birth, 2000 to 2019", # the axis labels swap with the orientation ylabel="Years", # run the values up the page orientation=ORIENTATION.VERTICAL, xtickrotate=45, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Category order The input order ranks the countries by their life expectancy in 2000, which is one story; ranking them by how much they gained is another. `sort` orders the categories ([SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT)): `SORT.DESCENDING` puts the largest first, `SORT.ASCENDING` the smallest, `None` keeps the input order. `sort_by` names the key ([DUMBBELL_SORT_KEY](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DUMBBELL_SORT_KEY)): `START` (the default), `END`, or `DELTA`, the change `end - start`. Ties keep the input order, and `sort_by` needs a `sort` to act on. Ordered by the gain, the ranking flips: the countries that started lowest gained the most, Nigeria nine years, while the United States gained two. ``` from datachart.constants import SORT, DUMBBELL_SORT_KEY DumbbellChart( data=life, title="Life expectancy at birth, largest gain first", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, # order the countries by their change, the largest first sort=SORT.DESCENDING, sort_by=DUMBBELL_SORT_KEY.DELTA, ).show() ``` ### Endpoint names and legend Two colors of dot mean nothing until the legend says which is which. `start_name` and `end_name` name the endpoints, each gets one legend entry, and the legend switches on as soon as a name is given (`show_legend=False` hides it again). `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The top rows of this chart reach the right edge, so the legend goes outside the axes. ``` from datachart.constants import LEGEND_LOCATION DumbbellChart( data=life, title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, # name the endpoints; the legend switches on with them start_name="2000", end_name="2019", # a titled legend outside the axes, to the right legend={"title": "Year", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Value labels When the exact numbers matter, `show_values` prints them ([DUMBBELL_VALUE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DUMBBELL_VALUE)): `ENDPOINTS` prints each endpoint's value past its dot, on the side away from the connector, and `DELTA` prints the change `end - start` at the connector midpoint. `value_format` formats the numbers: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:+.1f}"` or `"%g"` style string, and the label font size, color and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). Endpoint labels answer *what were the values*; the value axis is widened a little so the outer labels have room. ``` from datachart.constants import DUMBBELL_VALUE DumbbellChart( data=life, title="Life expectancy at birth, 2000 and 2019", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", # print both endpoint values, with one decimal show_values=DUMBBELL_VALUE.ENDPOINTS, value_format="{:.1f}", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, # room for the labels on both sides xmin=45, xmax=95, ).show() ``` Delta labels answer *how much did it change*, and a signed format (`"{:+.1f}"`) keeps a gain and a loss apart at a glance. Sorted by the change, the labels turn the chart into a ranked table of gains: ``` DumbbellChart( data=life, title="Years gained, 2000 to 2019", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, sort=SORT.DESCENDING, sort_by=DUMBBELL_SORT_KEY.DELTA, # print the signed change at every connector show_values=DUMBBELL_VALUE.DELTA, value_format="{:+.1f}", ).show() ``` ### Rises and falls A value that fell has its end dot on the other side of its start dot, and when some rows rise while others fall, the color of the dots is a slow way to tell them apart. `show_direction=True` draws a thin arrow beside each connector from the start to the end: above a horizontal dumbbell, right of a vertical one, with the delta label moving out past it. The `plot_dumbbell_arrow_*` style attributes set its look. From 2019 to 2021, across the pandemic, life expectancy fell in most of these countries, by more than three years in Brazil, Russia, India and South Africa, rose a little in Norway, China and Nigeria, and held in Japan, whose record draws a single dot and no arrow. ``` DumbbellChart( data=recent, title="Life expectancy at birth, 2019 to 2021", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2019", end_name="2021", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, # an arrow beside every connector, from start to end show_direction=True, show_values=DUMBBELL_VALUE.DELTA, value_format="{:+.1f}", xmin=55, xmax=90, ).show() ``` ### Markers and connectors A chart that will be printed in greyscale loses the color of the dots, and then the shape has to tell the endpoints apart. `marker` takes a `(start, end)` pair of [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER) members, and `connector_style` sets the line style of the connectors with a [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE) member. Both replace the theme's choice; a chart's `style` dictionary still wins over them. ``` from datachart.constants import LINE_MARKER, LINE_STYLE DumbbellChart( data=life, title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, # a square for the start, a circle for the end marker=(LINE_MARKER.SQUARE, LINE_MARKER.CIRCLE), # dotted connectors connector_style=LINE_STYLE.DOTTED, ).show() ``` ### Dumbbell style The `style` dictionary sets the look of the dots and the connectors: the two endpoint colors, the dot size and alpha, the markers and edges, the connector color, width and style, and the direction arrows; the attributes are listed in [datachart.typings.DumbbellStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.typings.DumbbellStyleAttrs), and any attribute left out keeps the value of the active theme. A muted start and a saturated end put the weight on where each country ended up, and a wider connector in a lighter shade makes the gain read as a bar between the two: ``` DumbbellChart( data=life, title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, # a muted start, a saturated end, and a wide light connector style={ "plot_dumbbell_start_color": "#b0bec5", "plot_dumbbell_end_color": "#00796b", "plot_dumbbell_size": 90, "plot_dumbbell_edge_width": 0, "plot_dumbbell_connector_color": "#cfd8dc", "plot_dumbbell_connector_width": 5, }, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. A record can carry its own `"emphasis"` key: `"highlight"` rims the dots in the text color and thickens the connector, `"background"` mutes the dots and the connector in the theme's muted color and drops their labels. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Asking only about Slovenia is a matter of tagging one record and muting the rest: ``` # tag Slovenia, mute the rest slovenia_marked = [ {**record, "emphasis": "highlight" if record["label"] == "Slovenia" else "background"} for record in life ] DumbbellChart( data=slovenia_marked, title="Life expectancy at birth, Slovenia", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, ).show() ``` `emphasis_rule` picks the records from the data instead of tagging them by hand. It is a one-key dictionary read against each record's change `end - start`: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive); the records that match are highlighted, the rest muted, and a record's own `"emphasis"` key wins over the rule. With the delta labels, the three largest gains stand out and keep their labels while the muted rows drop theirs: ``` DumbbellChart( data=life, title="The three largest gains, 2000 to 2019", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, # highlight the three largest changes, mute the rest emphasis_rule={"top": 3}, show_values=DUMBBELL_VALUE.DELTA, value_format="{:+.1f}", ).show() ``` ### Reference lines and bands A reference value puts the rows in context: where does the world stand, which rows have crossed a threshold. In the default horizontal chart a value is marked with `vlines` (a vertical line at a value) and a range shaded with `vspans`; `hlines` and `hspans` take positions along the category axis, which are row positions (`0` for the first row, `1` for the second, …), so a half-integer sits between two rows; a vertical chart swaps the pairs. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs) and [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs). The example marks the global life expectancy of 2019 with a dotted line and shades the band above 80 years, which five countries had reached by 2019 and only Japan had in 2000. ``` DumbbellChart( data=life, # a dotted line at the global value of 2019 vlines={ "x": WORLD_2019, "label": "World, 2019", "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DOTTED}, }, # shade the band above 80 years vspans={"xmin": 80, "xmax": 90, "label": "80 years and above"}, title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, xmin=50, xmax=90, ).show() ``` ### Text annotations Where a reference line marks a value, a note explains a row. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (value and row position in a horizontal chart, the first row at `0`) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below points at Russia's start dot and explains the largest gain among the European countries. ``` # row positions start at 0 RUSSIA = [record["label"] for record in life].index("Russia") DumbbellChart( data=life, # a note pinned to the axes, pointing at Russia's start dot texts={ "text": "Russia started at 65.2 years\nin 2000 and gained 8.0", "x": 0.04, "y": 0.62, "coords": "axes", "target": (LIFE["Russia"][0][0], RUSSIA), }, title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, xmin=50, xmax=90, ).show() ``` ## Multiple Dumbbell Charts To compare several groups on the same categories, pass a list of lists to `data`: each inner list is one chart, and the per-chart attributes (`subtitle`, `style`, `emphasis`) become lists aligned with it. The charts share one category axis (a category any chart lists gets a row) and overlay at its center, each in its own palette color, the end dot in the color and the start dot in a lighter shade of it. With `subtitle` and the endpoint names, the legend reads *subtitle (name)* per endpoint. `women` and `men` overlaid show the two changes side by side in each country, and the gap between them: ``` DumbbellChart( # one chart per sex, overlaid on the same rows data=[women, men], # named for the legend subtitle=["Women", "Men"], start_name="2000", end_name="2019", title="Life expectancy at birth, by sex", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` When the question is about one of the groups, `emphasis` takes one role per chart, aligned with `data` like `subtitle` and `style`: `"highlight"` bolds a chart, `"background"` mutes it and drops it from the legend, `None` leaves it as it is. Asking about men turns the women's rows into context, and it shows that men in Russia, Switzerland and Norway gained almost twice as many years as women: ``` DumbbellChart( data=[women, men], subtitle=["Women", "Men"], start_name="2000", end_name="2019", # men are the question, women the context emphasis=["background", "highlight"], title="Life expectancy at birth, men against women", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Subplots When the overlay gets crowded, or the question is about the shape of each group rather than the gap between them, `subplots=True` draws each chart in its own panel. `subtitle` titles the panels; `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the panels per row. `sharex=True` puts the panels on one value axis, so a dot in one panel is comparable with a dot in the next; without it each panel scales to its own range and the men's shorter lives would look as long as the women's. `sharey=True` keeps one category axis for all of them, so the country names print once and each country sits on the same row in every panel: ``` DumbbellChart( data=[women, men], subtitle=["Women", "Men"], title="Life expectancy at birth, 2000 to 2019", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, # one panel per sex, on one value axis and one category axis subplots=True, sharex=True, sharey=True, ).show() ``` ### Composing dumbbell charts A dumbbell chart places its rows on the category axis that the box, violin and swarm plots share, so [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) overlays it with them and with other dumbbell charts, and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts it beside any chart. Bars place their categories differently, so a dumbbell chart does not overlay a bar chart. A panel is the way to combine two dumbbell charts drawn separately, with their own styles and names, and its legend merges their entries; the `subtitle` of each chart labels it. The example overlays the change for both sexes with the gap between men and women in 2019, drawn in its own colors: ``` from datachart.utils import Panel change = DumbbellChart( data=life, subtitle="Both sexes", start_name="2000", end_name="2019", ) gap_2019 = DumbbellChart( data=[{"label": w["label"], "start": m["end"], "end": w["end"]} for w, m in zip(women, men)], subtitle="2019", start_name="Men", end_name="Women", style={"plot_dumbbell_start_color": "#6c9a78", "plot_dumbbell_end_color": "#c9a227"}, ) Panel( [change, gap_2019], title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` A grid keeps the charts apart, each in its own coordinate space, which suits two questions that share the data but not the axis: the change over two decades and the gap in the latest year. ``` from datachart.utils import Grid Grid( [[change, gap_2019]], title="Life expectancy at birth", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Additional Features ### Axis scales A dumbbell encodes value by position, and a linear axis is the honest default. When the values span orders of magnitude, a logarithmic axis is what keeps the small categories readable: `scaley` takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member and applies to the value axis whichever way it runs. `populations`, defined in a hidden cell, holds the population of the twelve countries in 2000 and in 2024, in millions (source: the World Bank's World Development Indicators, indicator SP.POP.TOTL, rounded). On a linear axis Slovenia, Norway and Switzerland collapse into one dot at the left edge; on a log axis every country's change is visible, at the price that equal connector lengths now mean equal ratios, not equal differences. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: DumbbellChart( data=populations, title=f"Population, 2000 to 2024, on the '{scale}' scale", xlabel="Population (millions)", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2024", show_direction=True, # the scale of the value axis scaley=scale, ).show() ``` ### Themes A theme sets the endpoint colors, the connector, the fonts and the dot edges of every chart at once, which is the way to restyle a whole document rather than one chart. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) as the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide shows; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows every chart under each theme. The style is resolved when the chart is built, so the theme can be reset right after the call: ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.QUILL) figure = DumbbellChart( data=life, title="Life expectancy at birth", xlabel="Years", figsize=FIG_SIZE.FULL_MEDIUM, start_name="2000", end_name="2019", legend={"title": "Year", "location": LEGEND_LOCATION.LOWER_RIGHT}, ) config.set_theme(THEME.DEFAULT) figure.show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: How Much Longer Do Women Live? (A Gap Sorted by Its Width, With Delta Labels and a Rule) A dumbbell also shows a gap between two groups rather than a change over time. `gender_gap` runs each country from men's to women's life expectancy at birth in 2019, from the shared dataset (source: WHO Global Health Observatory). The question is how wide the gap is and where it is widest, so the rows are sorted by the delta, the delta is printed on every connector, and `emphasis_rule` highlights the gaps above six years and mutes the rest. Women outlive men in every one of these countries: by 2.8 years in Nigeria, by almost ten in Russia. A square and a circle tell the endpoints apart even in print. ``` DumbbellChart( data=gender_gap, title="How much longer women live, 2019", xlabel="Life expectancy at birth (years)", figsize=FIG_SIZE.FULL_MEDIUM, start_name="Men", end_name="Women", # widest gap first sort=SORT.DESCENDING, sort_by=DUMBBELL_SORT_KEY.DELTA, # the width of each gap, in years show_values=DUMBBELL_VALUE.DELTA, value_format="{:.1f} y", # the gaps above six years emphasis_rule={"above": 6}, marker=(LINE_MARKER.SQUARE, LINE_MARKER.CIRCLE), legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Example 2: Did the Triage Redesign Cut the Waiting Time? (Before and After, With Direction Arrows, a Target Band and a Note) `waits` holds the illustrative median waiting time, in minutes, of eight emergency departments in the quarter before and the quarter after a triage redesign. The question every intervention raises is whether it worked, and where: `show_direction` marks each row as a fall or a rise, the signed delta says by how much, `emphasis_rule` highlights the departments that cut their wait by more than fifteen minutes, a shaded band marks the 40-minute target, and a note points at the one department where the wait got longer. Sorted by the delta, the largest cut is at the top and the exception at the bottom. ``` # the row of the one department whose wait got longer, once sorted by the change by_change = sorted(waits, key=lambda record: record["end"] - record["start"]) worse = next(record for record in by_change if record["end"] > record["start"]) WORSE_ROW = by_change.index(worse) DumbbellChart( data=waits, title="Median waiting time before and after the triage redesign", xlabel="Minutes", figsize=FIG_SIZE.FULL_MEDIUM, start_name="Before", end_name="After", # largest cut first sort=SORT.ASCENDING, sort_by=DUMBBELL_SORT_KEY.DELTA, # which way each department moved, and by how much show_direction=True, show_values=DUMBBELL_VALUE.DELTA, value_format="{:+.0f} min", # the departments that cut more than fifteen minutes emphasis_rule={"below": -15}, # the target band vspans={"xmin": 0, "xmax": TARGET, "label": "within target"}, # the exception texts={ "text": "the only department where\nthe wait got longer", "x": 0.62, "y": 0.14, "coords": "axes", "target": (worse["end"], WORSE_ROW), }, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, xmin=20, xmax=80, ).show() ``` ### Example 3: Is the Gap Between Women and Men Closing? (Rises and Falls in Two Colors, and a Grid) `narrowed` and `widened` hold the gap between women's and men's life expectancy in each country of the shared dataset, in years, in 2000 and in 2019 (source: WHO Global Health Observatory), split by whether the gap shrank or grew. A dumbbell chart draws one color pair per chart, so the split gives the two directions their own colors when the charts are overlaid; each chart sorts its own rows, and the first chart's rows come first, so the widened gaps lead and the narrowed ones follow, both ordered by the change. The gap narrowed in eight countries, most of all in Russia, and widened in four, all of them countries where women gained more years than men. The second chart of the grid shows those gains: `women` and `men` overlaid in the same row order, so each row of the top chart can be read against the two changes behind it. ``` gap = DumbbellChart( # widened gaps first, narrowed gaps after, each chart in its own colors data=[widened, narrowed], subtitle=["Widened", "Narrowed"], style=[ {"plot_dumbbell_start_color": "#f2b5a0", "plot_dumbbell_end_color": "#c1121f"}, {"plot_dumbbell_start_color": "#9bbcd6", "plot_dumbbell_end_color": "#1f5f8b"}, ], title="The gap between women and men, 2000 to 2019", xlabel="Years women outlive men", start_name="2000", end_name="2019", sort=SORT.DESCENDING, sort_by=DUMBBELL_SORT_KEY.DELTA, # the colors carry the direction, the labels the size show_values=DUMBBELL_VALUE.DELTA, value_format="{:+.1f}", legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, xmin=0, xmax=15, ) gains = DumbbellChart( # the same rows, in the same order data=[women_ordered, men_ordered], subtitle=["Women", "Men"], title="Life expectancy at birth by sex, 2000 to 2019", xlabel="Years", start_name="2000", end_name="2019", legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ) Grid( [[gap], [gains]], title="Is the gap closing?", # taller than FIG_SIZE.FULL_TALL, so twelve labelled rows fit per chart figsize=(6.3, 9), ).show() ``` # Calendar Heatmap A calendar heatmap draws one colored cell per day, the weeks as columns and the weekdays as rows, so a daily series shows its rhythm at a glance: which day of the week something happens, and in which season. This guide shows how to create calendar heatmaps with the [datachart.charts.CalendarHeatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.charts.CalendarHeatmap) function, starting with the basics and building up to worked examples on illustrative daily data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-calendar-heatmap), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import CalendarHeatmap ``` ## Basics The examples in this guide share one dataset: the number of cyclists counted on each day of 2024 by an automatic counter on a city cycle path. The counts are illustrative, drawn from a seeded generator in a hidden cell with the rhythm such a counter records: commuter traffic on weekdays, quieter weekends, a summer peak, rainy days that halve the count, and a week in November when the counter was down. `cyclists` holds the dates and the counts. The rhythm is the point of the chart, and every customization below helps to read it: the weekend rows, the summer columns, and the blank week. The data is a dictionary with two lists: `date` holds one temporal object per day (`datetime.date`, `datetime.datetime`, `numpy.datetime64`, or a pandas `Timestamp`; date strings are never parsed), and `value` the number of that day. Every date appears once. A day that is missing from the list, or valued `None`, is drawn as a blank cell, which is how the outage week will show: ``` list(zip(cyclists["date"][:3], cyclists["value"][:3])) ``` **Basic example.** Only the `data` argument is required. Each column is a week and each row a weekday, Monday at the top; the calendar spans the months that hold data, the whole year here. The cells are colored by their value, a stepped line separates the months, which are labelled along the bottom, and every other weekday is labelled on the left. The two pale rows at the bottom are the weekends, the dark columns in the middle are the summer, and the blank column in November is the outage: ``` CalendarHeatmap( # add the data to the chart data=cyclists ).show() ``` ## Customizing the Calendar Heatmap Every customization is either a keyword argument of `CalendarHeatmap` or a `plot_calendar_heatmap_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | add a title | `title` | [Title and figure size](#title-and-figure-size) | | resize the figure or stretch the cells | `figsize`, `aspect_ratio` | [Title and figure size](#title-and-figure-size) | | start the week on Sunday | `week_start` | [Week start](#week-start) | | hide the month or weekday labels | `show_month_labels`, `show_weekday_labels` | [Month and weekday labels](#month-and-weekday-labels) | | show and label the colorbar | `show_colorbars`, `colorbar` | [Colorbar and cell values](#colorbar-and-cell-values) | | write the values into the cells | `show_values`, `value_format` | [Colorbar and cell values](#colorbar-and-cell-values) | | change the colormap or transparency | `style={"plot_calendar_heatmap_cmap": ..., "plot_calendar_heatmap_alpha": ...}` | [Calendar style](#calendar-style) | | style the cell values | `style={"plot_calendar_heatmap_font_size": ..., ...}` | [Calendar style](#calendar-style) | | change the cell borders or month separators | `style={"plot_calendar_heatmap_edge_width": ..., "plot_calendar_heatmap_month_line_color": ...}` | [Calendar style](#calendar-style) | | fix the value range of the colormap | `vmin`, `vmax` | [Normalization](#normalization) | | spread skewed values over the colormap | `norm` | [Normalization](#normalization) | | highlight one series, mute the rest | not supported | [Emphasis](#emphasis) | | put a note on a day | `texts` | [Text annotations](#text-annotations) | | draw several years | `data` spanning several years, `max_cols` | [One calendar per year](#one-calendar-per-year) | | draw one year of a longer series | `year` | [The year filter](#the-year-filter) | | compare several series | `data` as a list of dicts, `subtitle` | [Multiple datasets](#multiple-datasets) | | place a calendar beside other charts | `Grid` | [Composing calendar heatmaps](#composing-calendar-heatmaps) | | plot dates from numpy or pandas | `numpy.datetime64` or `Timestamp` objects as `date` | [Other date types](#other-date-types) | | restyle every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `week_start` | [`CALENDAR_WEEKDAY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.CALENDAR_WEEKDAY) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | The full list of style attributes is in the [datachart.typings.CalendarHeatmapStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.typings.CalendarHeatmapStyleAttrs) type; the full list of parameters is in the [datachart.charts.CalendarHeatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.charts.CalendarHeatmap) reference. ### Title and figure size A calendar names its own axes, the months and the weekdays, so the one label it needs is a `title` that says what the colors measure. The cells are square by default, which is what makes the chart read as a calendar, and the default figure is wide and short to fit them; `figsize` takes a `(width, height)` tuple in inches or a preset from [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), and a wider figure means larger cells. `aspect_ratio` decides what happens when the figure does not fit 53 square weeks: [ASPECT_RATIO.EQUAL](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) (the default) keeps the cells square and leaves the spare space empty, `ASPECT_RATIO.AUTO` stretches them to fill the figure. Stretched cells give a full year more height on a page-wide figure, at the price of the calendar look. ``` from datachart.constants import FIG_SIZE, ASPECT_RATIO CalendarHeatmap( data=cyclists, # add the title title="Cyclists counted per day, 2024", # a taller figure than the square cells need figsize=(6.3, 3.0), # stretch the cells to fill it aspect_ratio=ASPECT_RATIO.AUTO, ).show() ``` ### Week start Whether the weekend sits at the bottom of the calendar or wraps around it depends on the day the week starts on. The weeks run from Monday to Sunday by default, which keeps Saturday and Sunday together in the bottom two rows; `week_start` with [CALENDAR_WEEKDAY.SUNDAY](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.CALENDAR_WEEKDAY) starts them on Sunday, as American calendars and the GitHub contributions graph do, and the weekend splits into the top and bottom rows. The default comes from the theme's `plot_calendar_heatmap_week_start` attribute, so it can be set once for every chart through the [config](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md). ``` from datachart.constants import CALENDAR_WEEKDAY CalendarHeatmap( data=cyclists, # start the weeks on Sunday: the weekend splits into the top and bottom rows week_start=CALENDAR_WEEKDAY.SUNDAY, title="Cyclists counted per day, 2024", ).show() ``` ### Month and weekday labels The month labels along the bottom and the weekday labels on the left are the reader's map of the calendar, and the default keeps both: each month over the middle of its weeks, and every other weekday down the side. In a dense figure, a small multiple or a thumbnail, they cost more room than they earn; `show_month_labels=False` and `show_weekday_labels=False` drop them. The month separators stay, so the months still read from the shape. ``` CalendarHeatmap( data=cyclists, # drop the labels, keep the separators show_month_labels=False, show_weekday_labels=False, title="Cyclists counted per day, 2024", ).show() ``` ### Colorbar and cell values A calendar on its own shows which days are busier and which are quieter, not by how much. `show_colorbars` draws the colorbar that maps the colors back to values, and `colorbar` says how: a `label` for the unit, a `location` from [COLORBAR_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), a `format` for its tick labels (a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or a `"{x:.0f}"` style string), and explicit `ticks`; the fields are listed in [ColorbarSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ColorbarSettingAttrs). A calendar is wide and short, so a colorbar along the bottom costs the least space. ``` from datachart.constants import COLORBAR_LOCATION, VALUE_FORMAT CalendarHeatmap( data=cyclists, title="Cyclists counted per day, 2024", # a colorbar along the bottom, labelled and ticked every 300 show_colorbars=True, colorbar={ "label": "Cyclists per day", "location": COLORBAR_LOCATION.BOTTOM, "format": VALUE_FORMAT.INTEGER, "ticks": [300, 600, 900, 1200, 1500], }, ).show() ``` Where the exact numbers matter, `show_values` writes every day's value into its cell and `value_format` formats it, the same way as the colorbar ticks; on dark cells the value is written in white, so it stays legible across the colormap. A year of cells is too small to carry numbers, so the values suit a short span: `winter`, the counts of January and February, draws only those two months, and its cells are large enough to read. ``` # January and February only: the calendar spans just these two months winter = { "date": [d for d in cyclists["date"] if d.month <= 2], "value": [v for d, v in zip(cyclists["date"], cyclists["value"]) if d.month <= 2], } CalendarHeatmap( data=winter, # write the count into every cell show_values=True, value_format=VALUE_FORMAT.INTEGER, title="Cyclists counted per day, January and February 2024", ).show() ``` ### Calendar style The `style` dictionary sets the look of the calendar: the colormap and alpha of the cells, the font of the cell values, the borders between the cells, and the separators between the months; the attributes are listed in [datachart.typings.CalendarHeatmapStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.typings.CalendarHeatmapStyleAttrs), and any attribute left out keeps the value of the active theme. The colormap is a [COLORS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORS) constant, a single color, a list of hex colors, or a matplotlib colormap, as the [colormaps guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/colormaps/index.md) explains; `None` takes the theme's heatmap colormap. The borders are white by default, standing in for the gaps of a printed calendar, and a thin grey border with a heavier, darker month separator turns the calendar into a ruled grid where the months stand out. ``` from datachart.constants import COLORS CalendarHeatmap( data=cyclists, style={ # a blue-green colormap "plot_calendar_heatmap_cmap": COLORS.GnBu, # thin grey borders between the days "plot_calendar_heatmap_edge_width": 0.5, "plot_calendar_heatmap_edge_color": "#BBBBBB", # heavy dark separators between the months "plot_calendar_heatmap_month_line_width": 1.5, "plot_calendar_heatmap_month_line_color": "#0B1F44", }, title="Cyclists counted per day, 2024", ).show() ``` ### Normalization The colors come from a two-step mapping: the values are first normalized to the 0–1 range, then each normalized value picks its color from the colormap, exactly as on the [heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/heatmap/#normalization). By default the smallest value maps to the first color and the largest to the last, which is the wrong range for two common questions. When a few extreme days wash out the rest, `vmin` and `vmax` pin the endpoints instead: the days beyond them saturate and the ordinary days spread over the whole colormap. With the range pinned at 0 to 1200, the busiest summer weekdays saturate and the difference between a weekday and a weekend in winter becomes visible. ``` CalendarHeatmap( data=cyclists, # pin the range: the busiest days saturate, the ordinary days spread out vmin=0, vmax=1200, title="Cyclists counted per day, 2024", show_colorbars=True, colorbar={"ticks": [0, 400, 800, 1200]}, ).show() ``` When the values are skewed, a few very busy days above many quiet ones, `norm` changes how they spread over the 0–1 range: `"linear"` (the default), `"log"`, `"symlog"`, `"asinh"`, or `"logit"`, as the [NORMALIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) constants name them. `"log"` and `"asinh"` give the low values more of the colormap, so the quiet days stop looking alike; the colorbar ticks show the stretched scale. ``` from datachart.constants import NORMALIZE CalendarHeatmap( data=cyclists, # spread the quiet days over more of the colormap norm=NORMALIZE.LOG, title="Cyclists counted per day, 2024", show_colorbars=True, ).show() ``` ### Emphasis The other charts accept an `emphasis` attribute that highlights one series and mutes the rest. The calendar heatmap does not: like the heatmap it is a single raster layer, not a set of series, so there is nothing to bring forward or push back, and `CalendarHeatmap` raises a `ValueError` if `emphasis` is passed. To draw attention to part of a calendar, pin the value range so the days of interest saturate, pick a colormap that turns dark at that level, or point at a day with `texts`, as the next section shows; the [highlighting guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) covers the charts that take `emphasis`. ### Text annotations A blank week or a dark column raises a question, and a note answers it on the chart. `texts` places text on the calendar with an optional `target` to draw a connector to a cell; the [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The cells sit at integer positions: the week column along `x` and the weekday row along `y`, both counted from zero at the top-left cell of the drawn range, which is the week that holds the first day of the first drawn month. Rows count downwards, so `y=-1.5` is above the calendar. A text is anchored at its left edge, and the connector stops inside the cell it points at. The note below explains the blank week in November, its column computed from the outage date. ``` from datachart.constants import ARROW_STYLE # the outage week's column, counted from the week of 1 January (the first drawn month) first_drawn = datetime.date(2024, 1, 1) outage_week = (first_drawn.weekday() + (OUTAGE_START - first_drawn).days) // 7 CalendarHeatmap( data=cyclists, # a note above the calendar, an arrow down into the blank week texts={ "text": "counter down for a week", "x": outage_week, "y": -2.4, "target": (outage_week, 0), "style": {"plot_text_arrow_style": ARROW_STYLE.ARROW}, }, title="Cyclists counted per day, 2024", ).show() ``` ## Multiple Calendar Heatmaps ### One calendar per year The question a second year answers is whether the pattern repeats: the same summer peak, the same weekend rows. Data spanning several years draws one calendar per year, in year order, each subtitled by its year, and the years share one value range, so the same count takes the same color in every calendar (`vmin` and `vmax` still pin the range when set). The `title` is global; `max_cols` limits the calendars per row, one by default, so the years stack and the weeks line up. `two_years` joins a 2023 series from the same generator to the 2024 counts. ``` DATES_2023, COUNTS_2023 = daily_cyclists(2023, seed=3) two_years = { "date": DATES_2023 + cyclists["date"], "value": COUNTS_2023 + cyclists["value"], } CalendarHeatmap( # two years of data: one calendar per year, on one value range data=two_years, title="Cyclists counted per day", show_colorbars=True, ).show() ``` ### The year filter A long series often needs one year drawn on its own, colored over its own values rather than the range of the whole series. `year` keeps that one year; a year with no data raises a `ValueError`. ``` CalendarHeatmap( data=two_years, # keep one year, colored over its own range year=2023, title="Cyclists counted per day, 2023", ).show() ``` ### Multiple datasets To compare several series, pass a list of dictionaries to `data`. Each is drawn in its own calendar, named by its `subtitle`; a dataset spanning several years still splits into one calendar per year, named by the subtitle and the year. The per-calendar attributes, `style`, `norm`, `vmin`, `vmax`, `colorbar` and `texts`, take a list with one entry per dataset, `None` keeping the default for that one. `quieter` is a second counter on a hillside path with less than half the traffic; drawn in its own colormap, each calendar shows its own rhythm rather than the difference in volume. ``` DATES_B, COUNTS_B = daily_cyclists(2024, seed=11) quieter = {"date": DATES_B, "value": [int(c * 0.4) for c in COUNTS_B]} CalendarHeatmap( # one calendar per dataset data=[cyclists, quieter], # named by their subtitles subtitle=["Riverside path", "Hillside path"], # the first keeps the theme colormap, the second gets its own style=[None, {"plot_calendar_heatmap_cmap": COLORS.Oranges}], title="Cyclists counted per day, 2024", show_colorbars=True, ).show() ``` ### Composing calendar heatmaps A calendar shows the pattern; a second chart puts numbers to it. A calendar owns its axes, the weeks and weekdays are not a coordinate space another chart can share, so [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) rejects a calendar figure. [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges it beside other charts, each in its own cell, and a multi-year calendar keeps its stack of years in its cell; the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers the layout options. The example sets the calendar over a bar chart of the monthly totals in thousands, so the summer peak can be read as a number. ``` from datachart.charts import BarChart from datachart.utils import Grid MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] monthly = [ {"label": name, "y": sum(c for d, c in zip(cyclists["date"], cyclists["value"]) if d.month == m) / 1000} for m, name in enumerate(MONTHS, start=1) ] calendar = CalendarHeatmap(data=cyclists, title="Per day") totals = BarChart(data=monthly, title="Per month", ylabel="Cyclists (thousands)", ymin=0) # the calendar over the monthly totals Grid([[calendar], [totals]], title="Cyclists counted in 2024", figsize=FIG_SIZE.FULL_MEDIUM).show() ``` ## Additional Features ### Other date types Daily data seldom arrives as `datetime.date` objects: it comes out of numpy as `datetime64` values or out of pandas as `Timestamp` objects, and both are accepted as they are, with the time of day ignored. Date strings are the one thing that is not parsed, so a column of `"2024-01-01"` strings is converted first (`numpy.array(strings, dtype="datetime64[D]")` or `pandas.to_datetime`). `spring` holds the same counter's March to May values on a `datetime64` range: ``` # a numpy date range and the counts that fall in it days = np.arange("2024-03-01", "2024-06-01", dtype="datetime64[D]") by_date = dict(zip(cyclists["date"], cyclists["value"])) spring = { "date": list(days), "value": [by_date[day.astype(datetime.date)] for day in days], } CalendarHeatmap( data=spring, title="Cyclists counted per day, spring 2024", ).show() ``` ### Themes A theme sets the colormap, the fonts, the borders and the week start of every calendar at once, so a report's charts match without styling each one. The calendar's colormap follows the theme's heatmap colormap unless `plot_calendar_heatmap_cmap` sets its own. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) as the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide shows; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows the whole suite under each theme. Style is resolved when the chart is created, so a theme set before the call and reset after it applies to that chart alone. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.INK) figure = CalendarHeatmap(data=cyclists, title="Cyclists counted per day, 2024") config.set_theme(THEME.DEFAULT) figure.show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question about a daily rhythm. The data is illustrative, drawn from seeded generators in hidden cells; each example says what its data stands for. ### Example 1: When Does the Work Get Done? (Sunday Weeks, a Custom Colormap, No Colorbar) `contributions` holds the number of commits on each day of 2025 by one illustrative developer: a few commits on most weekdays, rarely any at the weekend, and a burst of them in two release weeks in March and September. The calendar is laid out like the GitHub contributions graph, because that is how developers read commit activity: the weeks start on Sunday, the colormap is GitHub's green scale passed as a list of hex colors, the month separators are off, and there is no colorbar, since the graph is read by pattern rather than by value. The release weeks would drown the everyday commits on a linear colormap, so `NORMALIZE.ASINH` spreads the low counts over the greens. The answer is in the rows: the work happens Monday to Friday, and the two dark columns are the releases. ``` # the green scale of the GitHub contributions graph, from no commits to many GITHUB_GREENS = ["#ebedf0", "#9be9a8", "#40c463", "#30a14e", "#216e39"] CalendarHeatmap( data=contributions, style={ "plot_calendar_heatmap_cmap": GITHUB_GREENS, # the graph has no month separators, only the gaps between the squares "plot_calendar_heatmap_month_line_width": 0, }, # spread the everyday counts over the greens despite the release weeks norm=NORMALIZE.ASINH, week_start=CALENDAR_WEEKDAY.SUNDAY, title=f"Contributions in {YEAR}", ).show() ``` ### Example 2: Which Season Is the Wet One? (Three Years, a Shared Range, Blank Dry Days) `rainfall` holds the illustrative daily precipitation in millimetres at a station with wet winters and dry summers, over 2022 to 2024. A dry day has no entry at all rather than a zero, so it is drawn blank and only the wet days carry color; the season then reads from the density of the cells as much as from their shade. Three years draw as three stacked calendars on one value range, so a downpour looks the same whichever year it fell in, and the `colorbar` names the unit and prints whole millimetres. The answer repeats three times: the wet cells crowd the winter months at both ends of each calendar and thin out through the summer. ``` CalendarHeatmap( data=rainfall, style={"plot_calendar_heatmap_cmap": COLORS.Blues}, title="Daily rainfall", # one colorbar for the three years, in whole millimetres show_colorbars=True, colorbar={"label": "mm", "format": VALUE_FORMAT.INTEGER}, ).show() ``` ### Example 3: Which Day Fills the Shop? (Cell Values, a Note, a Pinned Range, and a Grid) `footfall` holds the illustrative number of visitors to a shop on each day of the last quarter of 2024: busy Saturdays, a closed Sunday every week, and the run-up to Christmas, and `by_weekday` the mean visitors per weekday over the quarter. The calendar answers the question by row: the values are written into the cells, the range is pinned so the December rush saturates while the ordinary weeks keep their contrast, and a `texts` note above the calendar names the two dark December columns, its target column computed from the first drawn month, October. A bar chart of the weekday means under it puts a number on the Saturday row, with `emphasis_rule` highlighting the busiest day, and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) stacks the two in one figure. ``` # the second week of the Christmas rush, counted from the week of 1 October # (the first drawn month) first_drawn = datetime.date(2024, 10, 1) rush = datetime.date(2024, 12, 16) rush_week = (first_drawn.weekday() + (rush - first_drawn).days) // 7 visits = CalendarHeatmap( data=footfall, title="Visitors per day", show_values=True, value_format=VALUE_FORMAT.INTEGER, # the Christmas rush saturates, the ordinary weeks keep their contrast vmin=100, vmax=450, texts={ "text": "Christmas rush", # above and to the right, an arrow down onto the Monday cell "x": rush_week + 3.2, "y": -1, "target": (rush_week, 0), "style": {"plot_text_arrow_style": ARROW_STYLE.ARROW}, }, ) weekdays = BarChart( data=by_weekday, title="Mean visitors per weekday", ylabel="Visitors", # the busiest day highlighted, the rest muted emphasis_rule={"top": 1}, show_values=True, value_format=VALUE_FORMAT.INTEGER, ymin=0, ymax=400, ) Grid([[visits], [weekdays]], title="Shop visitors, October to December 2024", figsize=FIG_SIZE.FULL_MEDIUM).show() ``` # Histogram A histogram sorts the values of one numeric variable into bins and counts them, so it answers *what shape does this distribution have*: where the values pile up, how widely they spread, whether they lean to one side, have two peaks, or trail off into outliers. This guide shows how to create histograms with the [datachart.charts.Histogram](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.charts.Histogram) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-histogram), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import Histogram ``` ## Basics The examples in this guide share one dataset: the flipper length, in millimeters, of the 342 penguins measured on three islands of the Palmer Archipelago, Antarctica (source: the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset, Gorman, Williams and Fraser 2014, released under CC0). The data lives in a hidden cell. `penguins` holds one data point per penguin, and `penguins_by_species` holds one list per species (Adelie, Chinstrap and Gentoo, in the order of `SPECIES`). The pooled flipper lengths hide a story: they come from three species of different build, and the histogram shows it as two peaks that the customizations below bring out. Each data point is a dictionary with an `x` value, here the flipper length, which the histogram bins and counts. Other keys, such as the species, are carried along and ignored: ``` penguins[:3] ``` **Basic example.** Only the `data` argument is required. The values are split into 20 equal-width bins by default, and the two peaks are already visible: ``` Histogram( # add the data to the chart data=penguins ).show() ``` ## Customizing the Histogram Every customization is either a keyword argument of `Histogram` or a `plot_hist_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | -------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | set tick positions, labels, and rotation | `xticks`, `xticklabels`, `xtickrotate` (and the `y` versions) | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | fix the aspect ratio of the axes | `aspect_ratio` | [Figure size and grid](#figure-size-and-grid) | | change how finely the values are binned | `num_bins` | [Number of bins](#number-of-bins) | | draw the bins as bars or a step outline | `style={"plot_hist_type": ...}` | [Histogram style](#histogram-style) | | change the color, hatch, or edge of the bins | `style={"plot_hist_color": ..., "plot_hist_hatch": ...}` | [Histogram style](#histogram-style) | | draw the bars horizontally | `orientation` | [Orientation](#orientation) | | print the count at the top of each bin | `show_values`, `value_format` | [Value labels](#value-labels) | | mark a mean, a median, or a cut-off | `vlines`, `hlines` | [Reference lines and bands](#reference-lines-and-bands) | | shade a range of values | `vspans`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | compare several distributions in one chart | `data` as a list of lists, `subtitle`, `style`, `show_legend` | [Multiple Histograms](#multiple-histograms) | | stack or overlay the series | `bar_mode` | [Bar mode](#bar-mode) | | highlight one series, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | title and place the legend | `legend` | [Legend](#legend) | | draw each series in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | compare samples of different sizes | `show_density` | [Density view](#density-view) | | overlay a smooth density curve | `stats.kde1d`, `LineChart`, `Panel` | [Density curve](#density-curve) | | read off how many values fall below a value | `show_cumulative` | [Cumulative view](#cumulative-view) | | use a logarithmic axis | `scaley`, `scalex` | [Axis scales](#axis-scales) | | plot data with other key names | `x` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `style={"plot_hist_type": ...}` | [`HISTOGRAM_TYPE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HISTOGRAM_TYPE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `bar_mode` | [`BAR_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | The full list of style attributes is in the [datachart.typings.HistStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs) type; the full list of parameters is in the [datachart.charts.Histogram](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.charts.Histogram) reference. ### Title, axis labels and ticks Without a title and axis labels the reader cannot tell what was measured or what the bars count; `title`, `xlabel` and `ylabel` say it. The automatic ticks rarely land on values people think in, so `xticks` places them every 10 mm (`xticklabels` would rename them, and `xtickrotate` would tilt them). `xmin` and `xmax` fix the range of the binned axis, which keeps several charts of the same variable aligned. ``` Histogram( data=penguins, # add the title title="Flipper length of Palmer penguins", # add the x and y axis labels xlabel="Flipper length (mm)", ylabel="Number of penguins", # tick the flipper length every 10 mm xticks=FLIPPER_TICKS, # fix the range of the binned axis xmin=165, xmax=235, ).show() ``` ### Figure size and grid A distribution is wider than it is tall, so a wide, short figure suits it. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. The counts are read off the y-axis, so [SHOW_GRID.Y](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) is the grid a vertical histogram needs; `SHOW_GRID.X` and `SHOW_GRID.BOTH` are the other options. `aspect_ratio` ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)) fixes the ratio of the axes rather than of the figure; a histogram has counts on one axis and values on the other, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID Histogram( data=penguins, title="Flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the count axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Number of bins The bin count decides which story the histogram tells, so it is worth choosing on purpose. `num_bins` sets it (20 by default). The flipper lengths span 172 to 231 mm. With 4 bins, each about 15 mm wide, the two peaks merge into one, and the distribution looks like a single peak with a long right tail. With 20 bins the dip between the peaks shows. With 120 bins each bin is half a millimeter wide, narrower than the whole millimeters the lengths were recorded in, so every other bin is empty and the chart is mostly noise. ``` for num_bins in [4, 20, 120]: Histogram( data=penguins, title=f"Flipper length in {num_bins} bins", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # the number of equal-width bins num_bins=num_bins, ).show() ``` ### Histogram style The `style` dictionary sets the look of the bins; the attributes are listed in [datachart.typings.HistStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs), and any attribute left out keeps the value of the active theme. `plot_hist_type` takes a [HISTOGRAM_TYPE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HISTOGRAM_TYPE): `BAR` draws one bar per bin (the default), `STEP` an unfilled outline, and `STEP_FILLED` a filled outline with no lines between the bins, which reads as one shape rather than a row of bars. For `STEP` the outline is the mark itself and takes the series color; `plot_hist_edge_color` and `plot_hist_edge_width` override it. A hatch from [HATCH_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HATCH_STYLE) and a dark edge keep the shape readable when printed in greyscale. ``` from datachart.constants import HATCH_STYLE, HISTOGRAM_TYPE Histogram( data=penguins, # one filled shape with a hatch and a dark outline style={ "plot_hist_type": HISTOGRAM_TYPE.STEP_FILLED, "plot_hist_color": "#a8dadc", "plot_hist_alpha": 0.8, "plot_hist_hatch": HATCH_STYLE.DIAGONAL, "plot_hist_edge_width": 1.5, "plot_hist_edge_color": "#1d3557", }, title="Flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Orientation A horizontal histogram puts the values on the y-axis, which suits a variable people read top to bottom (depth, altitude, age) or a histogram placed beside another chart that shares that axis. `orientation=ORIENTATION.HORIZONTAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) draws the bars from the y-axis; the axis labels, the ticks and the grid swap with it. ``` from datachart.constants import ORIENTATION Histogram( data=penguins, title="Flipper length of Palmer penguins", # the axis labels swap with the orientation xlabel="Number of penguins", ylabel="Flipper length (mm)", yticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_MEDIUM, # and so does the grid show_grid=SHOW_GRID.X, # draw the bars from the y-axis orientation=ORIENTATION.HORIZONTAL, ).show() ``` ### Value labels When the reader needs the exact counts, say to check how many penguins fall in the dip between the peaks, `show_values` prints each bin's count at its top, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. Empty bins stay bare. Labels need room, so the example uses fewer bins and extends the count axis a little. ``` from datachart.constants import VALUE_FORMAT Histogram( data=penguins, title="Flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, num_bins=12, ymax=75, # print the count of every bin show_values=True, value_format=VALUE_FORMAT.INTEGER, ).show() ``` ### Reference lines and bands A summary statistic means more when it sits on the distribution it summarizes. `vlines` draws a vertical line at a value of the binned variable (a mean, a median, a cut-off) and `hlines` a horizontal one at a count. `vspans` and `hspans` shade a range instead. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend, and a `style`; the keys are listed in [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs) and [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs). The example marks the mean (201 mm) and the median (197 mm) and shades one standard deviation around the mean. The mean sits right of the median because the long-flippered Gentoo penguins pull it, and it lands on the slope down into the dip between the peaks: for a two-peaked distribution the "typical" value describes few of the penguins. The taller figure and `ymax` leave the legend room above the bars. ``` from datachart.constants import LINE_STYLE flippers = [point["x"] for point in penguins] mean_flipper = sum(flippers) / len(flippers) median_flipper = sorted(flippers)[len(flippers) // 2] std_flipper = (sum((f - mean_flipper) ** 2 for f in flippers) / len(flippers)) ** 0.5 Histogram( data=penguins, subtitle="penguins", # the mean and the median as lines vlines=[ { "x": mean_flipper, "label": "mean", "style": {"plot_vline_color": "#1d3557", "plot_vline_style": LINE_STYLE.DASHED}, }, { "x": median_flipper, "label": "median", "style": {"plot_vline_color": "#e76f51", "plot_vline_style": LINE_STYLE.DOTTED}, }, ], # one standard deviation around the mean as a band vspans={ "xmin": mean_flipper - std_flipper, "xmax": mean_flipper + std_flipper, "label": "mean ± 1 SD", }, title="Flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_MEDIUM, ymax=60, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Text annotations A peak that has an explanation deserves one. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (value, count) or in axes fractions with `"coords": "axes"`. The keys are listed in [TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs), and the [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement and styling. The notes below name the species behind each peak. ``` Histogram( data=penguins, # one note per peak, each pointing at the top of its peak texts=[ {"text": "Adelie and Chinstrap", "x": 172, "y": 40, "target": (191, 38)}, {"text": "Gentoo", "x": 224, "y": 36, "target": (216, 27)}, ], title="Flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymax=50, ).show() ``` ## Multiple Histograms To compare distributions, pass a list of lists to `data`: each inner list is one series, and the per-series attributes (`subtitle`, `style`, `emphasis`, `x`) become lists aligned with it. A single `style` dictionary applies to every series, while a list styles each one (`None` keeps the theme style). `penguins_by_species` is such a list, and splitting the pooled data by species explains the two peaks. By default the series share one set of bins and are **stacked**, so the outline of the stack is the pooled histogram from the Basics section and the colors show which species fills each bin: the Adelie and Chinstrap penguins make the left peak, the Gentoo penguins the right one. ``` Histogram( # one series per species data=penguins_by_species, # named for the legend subtitle=SPECIES, # one style per series; None keeps the theme color style=[{"plot_hist_color": "#e76f51"}, {"plot_hist_color": "#e9c46a"}, None], title="Flipper length of Palmer penguins by species", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Bar mode A stack answers *what makes up each bin*, but hides the shape of every series except the bottom one. `bar_mode` ([BAR_MODE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE)) changes that: `BAR_MODE.OVERLAY` draws every series from zero on the shared bins, one over the other, so each species shows its own shape and the overlap between Adelie and Chinstrap becomes visible. `BAR_MODE.STACK` is the default, and `BAR_MODE.GROUP` behaves like overlay. A step outline keeps the overlaid series from hiding each other. ``` from datachart.constants import BAR_MODE Histogram( data=penguins_by_species, subtitle=SPECIES, # outlines, so no series hides another style={"plot_hist_type": HISTOGRAM_TYPE.STEP, "plot_hist_edge_width": 2}, title="Flipper length of Palmer penguins by species", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # every series from zero, on the shared bins bar_mode=BAR_MODE.OVERLAY, ).show() ``` ### Emphasis When the question is about one of the series, `emphasis` takes one role per series, aligned with `data`: `"highlight"` bolds a series and brings it to the front, `"background"` mutes it and drops it from the legend, `None` leaves it as it is ([EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS)). As soon as any series carries a role, the histograms are overlaid rather than stacked, since a muted series stacked under a highlighted one would lift it off the axis. Asking *how do the Gentoo penguins differ* turns the other two species into context. The [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. ``` Histogram( data=penguins_by_species, subtitle=SPECIES, # the Gentoo penguins are the question, the rest the context emphasis=["background", "background", "highlight"], title="Gentoo penguins against the other species", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` `emphasis_rule` picks the series from the data instead. It is a one-key rule, `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive), read against a summary of each series' own values: the mean by default, or the `"median"`, `"min"`, `"max"` or `"sum"` named by a `"by"` key. An explicit `emphasis` role wins over the rule. The rule below highlights the species whose median flipper is shortest: ``` Histogram( data=penguins_by_species, subtitle=SPECIES, # the series with the smallest median emphasis_rule={"bottom": 1, "by": "median"}, title="The species with the shortest flippers", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Legend `show_legend` lists the series; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The two peaks leave little room at the top of the axes, so the legend moves above them, in one row. ``` from datachart.constants import LEGEND_LOCATION Histogram( data=penguins_by_species, subtitle=SPECIES, style=[{"plot_hist_color": "#e76f51"}, {"plot_hist_color": "#e9c46a"}, None], title="Flipper length of Palmer penguins by species", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_legend=True, # a titled, one-row legend above the axes legend={"title": "Species", "location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 3}, ).show() ``` ### Subplots Overlaid series get hard to read once their shapes cross. `subplots=True` draws each series in its own panel: `subtitle` titles the panels, `title`, `xlabel` and `ylabel` stay global, and `max_cols` limits the panels per row. On their own, the panels bin and scale independently, so a bar in one panel does not compare with a bar in the next. `sharex=True` puts the panels on one value axis with shared bins, so the species line up bin for bin, and `sharey=True` puts them on one count axis, so the smaller Chinstrap sample (68 penguins against 151 Adelie) no longer fills its panel. ``` Histogram( data=penguins_by_species, subtitle=SPECIES, title="Flipper length of Palmer penguins by species", xlabel="Flipper length (mm)", ylabel="Number of penguins", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_TALL, show_grid=SHOW_GRID.Y, # one panel per species, stacked in a column subplots=True, max_cols=1, # one set of bins and one count axis for all panels sharex=True, sharey=True, ).show() ``` ## Additional Features ### Density view Counts depend on the sample size: in the panels above the Chinstrap histogram is small because fewer Chinstrap penguins were measured, not because their flippers are unusual. `show_density=True` rescales the bars so that the total area of each series is 1, which makes samples of different sizes comparable. Per density, the Adelie and Chinstrap distributions have about the same height and width, with the Chinstrap one shifted about 6 mm to the right. ``` Histogram( data=penguins_by_species, subtitle=SPECIES, title="Flipper length of Palmer penguins by species", xlabel="Flipper length (mm)", ylabel="Density", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_TALL, show_grid=SHOW_GRID.Y, subplots=True, max_cols=1, sharex=True, sharey=True, # the density instead of the count show_density=True, ).show() ``` ### Density curve A histogram's shape depends on where its bin edges fall; a kernel density estimate smooths the same values into a curve that does not. [datachart.utils.stats.kde1d](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/#datachart.utils.stats.kde1d) computes the curve and returns `{x, y}` points that a [LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.charts.LineChart) draws, and [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) lays the curve over a density histogram; both integrate to 1, so they share the y-axis. The `bandwidth` argument of `kde1d` sets how smooth the curve is: a [BANDWIDTH](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) rule (Scott's by default) or a number, where smaller values follow the data more closely. The [Statistics](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/stats/index.md) guide covers the estimate in more depth. ``` from datachart.charts import LineChart from datachart.utils import Panel from datachart.utils.stats import kde1d # the default bandwidth and a narrower one smooth = kde1d(flippers) detailed = kde1d(flippers, bandwidth=0.15) Panel( [ Histogram(data=penguins, subtitle="binned", show_density=True), LineChart(data=smooth, subtitle="kernel density (Scott)"), LineChart(data=detailed, subtitle="kernel density (bandwidth 0.15)"), ], title="Flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel_left="Density", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_legend=True, # the curves run past the data, so the range is fixed xmin=165, xmax=240, ).show() ``` ### Cumulative view Some questions are about thresholds, not shapes: *how many penguins have flippers of 200 mm or less?* `show_cumulative=True` makes each bar hold the count of all values up to and including its bin, so the bars climb to the sample size. Combined with `show_density=True`, the bars hold the share of values instead and every series climbs to 1, which is the empirical cumulative distribution. At 200 mm, 95% of the Adelie penguins and 74% of the Chinstrap penguins are counted, and not a single Gentoo penguin. A `STEP` outline ends at its final level here: a running total never falls back, so a cumulative outline has no closing drop to zero. ``` Histogram( data=penguins_by_species, subtitle=SPECIES, style={"plot_hist_type": HISTOGRAM_TYPE.STEP, "plot_hist_edge_width": 2}, bar_mode=BAR_MODE.OVERLAY, # the 200 mm threshold vlines={"x": 200, "style": {"plot_vline_style": LINE_STYLE.DASHED}}, title="Share of penguins up to each flipper length", xlabel="Flipper length (mm)", ylabel="Cumulative share", xticks=FLIPPER_TICKS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, legend={"location": LEGEND_LOCATION.UPPER_LEFT}, num_bins=59, xmax=231, # the running share of each species show_cumulative=True, show_density=True, ).show() ``` ### Axis scales On a linear count axis the bins in the tail of a distribution hold a handful of values and vanish next to the peak. `scaley` (or `scalex` for a horizontal histogram) takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, and a logarithmic count axis gives every occupied bin a visible bar. The bins stay equal-width on the data scale whichever axis scale is applied, so a log scale on the binned axis stretches them unevenly and is rarely what you want. A log axis has no zero, so a `STEP` outline breaks over the empty bins instead of dropping to the axis floor. Flipper lengths have no long tail, so this example switches dataset. `quakes`, defined in a hidden cell, holds 5,000 illustrative earthquake magnitudes from a seeded generator that follows the Gutenberg-Richter law: every step of one magnitude up makes earthquakes about ten times rarer. On a linear axis the strong earthquakes are invisible; on a log axis the counts fall along a straight line, which is how seismologists read the law. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: Histogram( data=quakes, title=f"Earthquake magnitudes on a '{scale}' count axis", xlabel="Magnitude", ylabel="Number of earthquakes", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, num_bins=30, # the scale of the count axis scaley=scale, ).show() ``` ### Custom data keys Data from a file or an API rarely calls its column `x`, and renaming every record just to plot it is a chore. The `x` argument names the key that holds the value to bin (a list of keys for several series). `penguin_records` stores the penguins the way the published dataset names its columns, so the same records can be binned by body mass instead of flipper length: ``` penguin_records = [ {"species": species, "flipper_length_mm": flipper, "body_mass_g": mass} for species in SPECIES for flipper, mass in PENGUINS[species] ] penguin_records[:2] ``` ``` Histogram( data=penguin_records, # the key that holds the value to bin x="body_mass_g", title="Body mass of Palmer penguins", xlabel="Body mass (g)", ylabel="Number of penguins", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, num_bins=30, ).show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Do Marathoners Race the Clock? (Fine Bins, Hour Ticks, and Reference Lines) A study of millions of marathon results found that finish times bunch up just before round-number goals such as four hours, as runners push to beat them (Allen, Dechow, Pope and Wu, *Management Science*, 2017). `finish_times` holds 20,000 illustrative finish times in minutes from a seeded generator that reproduces the effect: a broad spread of times, where some runners heading for just over 3:00, 3:30, 4:00, 4:30 or 5:00 are pulled in under the mark, most strongly at the full hours. With the default 20 bins each bin is 12 minutes wide and the bunching disappears into a smooth hump, so the example uses one-minute bins. `xticks` and `xticklabels` print the axis in hours and minutes, and `vlines` mark the goals, so each spike can be seen to sit just left of its line. ``` Histogram( data=finish_times, style={"plot_hist_type": HISTOGRAM_TYPE.STEP_FILLED}, # the round-number goals vlines=[ { "x": goal, "style": {"plot_vline_color": "#1d3557", "plot_vline_style": LINE_STYLE.DASHED, "plot_vline_width": 0.8}, } for goal in GOALS ], title="Marathon finish times, in one-minute bins", xlabel="Finish time (h:mm)", ylabel="Number of runners", # the axis in hours and minutes xticks=HOUR_TICKS, xticklabels=HOUR_LABELS, xmin=150, xmax=390, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # one bin per minute num_bins=240, ).show() ``` ### Example 2: Did the New Onboarding Lengthen Sessions? (Emphasis, Density View, and Medians) `session_durations` holds illustrative session durations, in minutes, from an A/B test of a redesigned onboarding flow: 5,000 sessions of the control group and 600 of the new variant, drawn from seeded log-normal generators. The groups differ eightfold in size, so `show_density` compares their shapes rather than their counts. `emphasis` mutes the control group into a reference and highlights the variant, which also overlays the two instead of stacking them, and `vlines` mark each group's median, the robust summary for a right-skewed duration. ``` Histogram( data=session_durations, subtitle=["control", "variant"], # the control group is the reference, the variant the question emphasis=["background", "highlight"], # each group's median vlines=[ { "x": median, "label": f"{group} median ({median:.1f} min)", "style": {"plot_vline_color": color, "plot_vline_style": LINE_STYLE.DASHED}, } for (group, median), color in zip(MEDIANS.items(), ["#6c757d", "#c1121f"]) ], title="Session duration with the redesigned onboarding", xlabel="Session duration (minutes)", ylabel="Density", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, num_bins=60, xmin=0, xmax=25, # compare the shapes, not the sample sizes show_density=True, show_legend=True, ).show() ``` ### Example 3: Did the Release Fatten the Latency Tail? (Log Counts, the Cumulative View, and a Grid) `latency` holds the response times, in milliseconds, of 20,000 illustrative requests to a web service in the week before and the week after a release, from seeded log-normal generators; after the release, about 3% of requests take a slow path. The service promises that 99% of requests answer within 200 ms. The typical request did not change, so the peaks overlap, and the question lives in the tail. The left chart overlays both weeks as outlines on a log count axis, where the new bump of slow requests shows up. The right chart shows the cumulative share of requests, zoomed to the top 10% with `ymin`, with the 99% promise as a horizontal line and the 200 ms limit as a vertical one: before the release the curve reaches 99% left of the limit, after it only to the right. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts the two views side by side. ``` from datachart.utils import Grid LIMIT = {"x": 200, "style": {"plot_vline_color": "#1d3557", "plot_vline_style": LINE_STYLE.DASHED}} counts = Histogram( data=latency, subtitle=WEEKS, style=WEEK_STYLE, bar_mode=BAR_MODE.OVERLAY, vlines=LIMIT, title="Requests per bin", xlabel="Response time (ms)", ylabel="Requests", show_grid=SHOW_GRID.Y, num_bins=80, # the tail shows on a log count axis scaley=SCALE.LOG, show_legend=True, ) cumulative = Histogram( data=latency, subtitle=WEEKS, style=WEEK_STYLE, bar_mode=BAR_MODE.OVERLAY, vlines=LIMIT, # the 99% promise hlines={"y": 0.99, "style": {"plot_hline_color": "#1d3557", "plot_hline_style": LINE_STYLE.DOTTED}}, title="Share of requests answered", xlabel="Response time (ms)", ylabel="Cumulative share", show_grid=SHOW_GRID.Y, num_bins=400, show_cumulative=True, show_density=True, # zoom into the top 10%, and past the slowest requests xmax=700, ymin=0.9, ymax=1.0, ) Grid( [[counts, cumulative]], title="Latency before and after the release", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Box Plot A box plot compares the center, the spread and the outliers of several groups at a glance: each group gets a box, and the boxes answer *which group is higher, which varies more, and which has unusual values*. This guide shows how to create box plots with the [datachart.charts.BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.charts.BoxPlot) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-box-plot), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import BoxPlot ``` ## Basics The examples in this guide share one dataset: the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) (Gorman, Williams and Fraser, 2014; released under CC0), measurements of the penguins of three species on the islands of the Palmer Archipelago in Antarctica. The hidden cell holds the 342 penguins with a recorded body mass, grouped by species and sex, with the flipper length of each. `body_mass` holds one data point per penguin, its body mass in grams labeled with its species, and `flipper_length` the same for the flipper length in millimeters. The question running through the guide is how the three species differ in size: Adelie and Chinstrap penguins weigh about the same, Gentoo penguins are much heavier, and the boxes show by how much. Each data point is a dictionary with a `label` (the group) and a `value`. The points that share a `label` form one box, so the three species give three boxes: ``` body_mass[:3] ``` **Basic example.** Only the `data` argument is required. Each box spans the middle half of its group (from the first to the third quartile), the line inside it is the median, the whiskers reach the furthest values within 1.5 box heights of the box, and the values beyond the whiskers are drawn as outliers. The boxes follow the order in which the labels first appear in the data: ``` BoxPlot( # add the data to the chart data=body_mass ).show() ``` ## Customizing the Box Plot Every customization is either a keyword argument of `BoxPlot` or a `plot_box_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | --------------------------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | set the axis range, ticks and tick format | `ymin`, `ymax`, `yticks`, `yticks_format` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | order the boxes by their median | `sort` | [Box order](#box-order) | | change the box fill, edge, median or outliers | `style={"plot_box_color": ..., "plot_box_median_color": ...}` | [Box style](#box-style) | | draw the boxes horizontally | `orientation` | [Horizontal boxes](#horizontal-boxes) | | hide the outliers | `show_outliers` | [Showing and hiding outliers](#showing-and-hiding-outliers) | | check whether two medians differ | `show_notch` | [Notched boxes](#notched-boxes) | | print the median of each box | `show_values`, `value_format` | [Value labels](#value-labels) | | highlight some boxes, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a threshold or a summary value | `hlines`, `vlines` | [Reference lines](#reference-lines) | | shade a range of values | `hspans`, `vspans` | [Reference bands](#reference-bands) | | title and place the legend | `show_legend`, `legend` | [Reference bands](#reference-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | use dates as group labels | `date` objects as `label`, `xticks_format` | [Date labels](#date-labels) | | draw the observations or a violin with boxes | `Panel` with `SwarmPlot`, `ViolinPlot` | [Boxes with swarms and violins](#boxes-with-swarms-and-violins) | | compare several datasets side by side | `data` as a list of lists, `subtitle`, `subplots` | [Multiple Box Plots](#multiple-box-plots) | | arrange the subplots and share their axes | `max_cols`, `sharex`, `sharey` | [Shared axes across subplots](#shared-axes-across-subplots) | | draw every subplot horizontally | `orientation` | [Subplot orientation](#subplot-orientation) | | use a logarithmic value axis | `scaley` | [Axis scales](#axis-scales) | | plot data with other key names | `label`, `value` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.BoxStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxStyleAttrs) type; the full list of parameters is in the [datachart.charts.BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.charts.BoxPlot) reference. ### Title, axis labels and ticks A box plot without labels leaves the reader guessing what is measured and in which unit; `title`, `xlabel` and `ylabel` say it. `ymin` and `ymax` fix the value range, which matters when several charts should be read against each other, and `yticks` picks the tick positions. Body masses run into the thousands, so `yticks_format` prints them with a thousands separator through [VALUE_FORMAT.THOUSANDS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT); `xtickrotate` and `ytickrotate` tilt long tick labels. ``` from datachart.constants import VALUE_FORMAT BoxPlot( data=body_mass, # add the title title="Body mass of Palmer penguins", # add the x and y axis labels xlabel="Species", ylabel="Body mass (g)", # fix the value range and its ticks ymin=2500, ymax=6500, yticks=[3000, 4000, 5000, 6000], # print the ticks with a thousands separator yticks_format=VALUE_FORMAT.THOUSANDS, ).show() ``` ### Figure size and grid Three boxes do not need a square figure, and a wide, short one fits a page better. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. The values of a vertical box plot are read off the y-axis, so grid lines along it help the eye carry a median or a quartile across to the scale. `show_grid` draws them with [SHOW_GRID.Y](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) (`SHOW_GRID.X` and `SHOW_GRID.BOTH` are the other options). `aspect_ratio` fixes the ratio of the axes rather than of the figure ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); box plots rarely need it, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID BoxPlot( data=body_mass, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the value axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Box order The boxes follow the order in which their labels first appear in the data, which is often an accident of how the file was written. Ordered by their median, the boxes read as a ranking. `sort` orders the boxes by their median, `"ascending"` or `"descending"`, also available as the [SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) constants: here by the median body mass of each species, heaviest first. Adelie and Chinstrap penguins share a median of 3,700 g, and ties keep their input order. An `emphasis` list stays aligned with the input order, whatever the sort. ``` from datachart.constants import SORT BoxPlot( data=body_mass, # heaviest species first sort=SORT.DESCENDING, title="Body mass of Palmer penguins, heaviest species first", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Box style The `style` dictionary sets the look of the boxes: the fill and its alpha, the edge, the hatch, the median line, the whiskers and caps, and the outlier markers; the attributes are listed in [datachart.typings.BoxStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxStyleAttrs), and any attribute left out keeps the value of the active theme. The median is the one line every reader looks for, so it earns a contrasting color and a heavier width. The outlier marker takes a [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER) value; the diamonds below make the two Chinstrap outliers stand out from the whisker ends. ``` from datachart.constants import LINE_MARKER BoxPlot( data=body_mass, # a light box, a strong median, and diamond outliers style={ "plot_box_color": "#c6dbef", "plot_box_alpha": 1.0, "plot_box_edgecolor": "#08519c", "plot_box_linewidth": 1.2, "plot_box_median_color": "#d62728", "plot_box_median_linewidth": 2.5, "plot_box_whisker_color": "#08519c", "plot_box_cap_color": "#08519c", "plot_box_outlier_marker": LINE_MARKER.DIAMOND, "plot_box_outlier_size": 6, "plot_box_outlier_color": "#d62728", }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Horizontal boxes Long group names and many groups read best down the page. `orientation=ORIENTATION.HORIZONTAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) puts the groups on the y-axis and the values on the x-axis, so the axis labels, the tick format and the grid swap with it. The first group is drawn at the bottom. ``` from datachart.constants import ORIENTATION BoxPlot( data=body_mass, # draw the boxes horizontally orientation=ORIENTATION.HORIZONTAL, title="Body mass of Palmer penguins", # the axis labels, the tick format and the grid swap with the orientation xlabel="Body mass (g)", ylabel="Species", xticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, ).show() ``` ### Showing and hiding outliers Outliers are the values beyond the whiskers, and whether to draw them depends on the question. When they are measurement errors or a distraction, `show_outliers=False` hides them; when they are the story, keep them (the default). The Chinstrap penguins have two: one of 2,700 g and one of 4,800 g. Hiding them does not move the whiskers, which still end at the furthest values within 1.5 box heights, so hiding outliers changes what is drawn, not what the boxes summarize. ``` for show_outliers in [True, False]: BoxPlot( data=body_mass, # show or hide the values beyond the whiskers show_outliers=show_outliers, title=f"Body mass of Palmer penguins, outliers {'shown' if show_outliers else 'hidden'}", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Notched boxes Two medians that look different may still be the same up to sampling noise, and a notch shows which. `show_notch=True` cuts a notch around each median that spans an approximate 95% confidence interval of it. When the notches of two boxes do not overlap, that is good evidence that their medians differ; when they overlap, the data cannot tell the medians apart. The notch narrows as the group grows, so the 68 Chinstrap penguins get a wider notch than the 151 Adelie. Here the Adelie and Chinstrap notches overlap (both medians are 3,700 g), while the Gentoo notch sits far above both: Gentoo penguins are heavier, beyond doubt. ``` BoxPlot( data=body_mass, # cut a confidence-interval notch around each median show_notch=True, title="Body mass of Palmer penguins, with median notches", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Value labels The median is the number readers take away from a box, and reading it off the axis is imprecise. `show_values` prints each box's median beside its median line, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f} g"` or `"%g"` style string. The label font size, color and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). ``` BoxPlot( data=body_mass, # print the median of every box, with its unit show_values=True, value_format="{x:,.0f} g", title="Body mass of Palmer penguins, with the medians", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. `emphasis` takes one role per box, in the order the labels first appear in the data (here Adelie, Chinstrap, Gentoo): `"highlight"` bolds the box edges and the median, `"background"` mutes the box together with its whiskers, caps, median and outliers, and `None` leaves it as it is; a single value applies to every box. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. The example puts the heavy Gentoo penguins in front. ``` from datachart.constants import EMPHASIS BoxPlot( data=body_mass, # one role per box: Adelie, Chinstrap, Gentoo emphasis=[EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], title="Body mass of Palmer penguins, the Gentoo stand apart", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` `emphasis_rule` picks the boxes from the data instead of naming them. It is a one-key dictionary read against a summary of each box: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive). The summary is the median by default, what the box already draws; a `"by"` key picks `"mean"`, `"min"`, `"max"` or `"sum"` instead. The boxes that match are highlighted, the rest muted, and an explicit `emphasis` role wins over the rule. Asking which species has penguins lighter than 3 kg picks the boxes by their minimum: ``` BoxPlot( data=body_mass, # the species whose lightest penguin is under 3,000 g emphasis_rule={"below": 3000, "by": "min"}, title="Species with penguins under 3 kg", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Reference lines A box on its own says little about whether its values are high or low; a reference line gives it something to be compared with, such as a threshold or the overall mean. `hlines` draws a horizontal line at a value and `vlines` a vertical one; positions along the group axis are box positions, and the first box sits at `0`, as the first bar does, so a half-integer sits between two boxes. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style` whose line style is a [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE) value; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs) and [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs). The dashed line marks the mean body mass of all 342 penguins: the whole Gentoo box sits above it, and the Adelie and Chinstrap boxes below it. A dotted vertical line separates the two small species from the Gentoo. ``` from datachart.constants import LINE_STYLE masses = [point["value"] for point in body_mass] mean_mass = sum(masses) / len(masses) BoxPlot( data=body_mass, # a dashed line at the mean of all penguins hlines={ "y": mean_mass, "style": {"plot_hline_color": "#d62728", "plot_hline_style": LINE_STYLE.DASHED, "plot_hline_width": 1.5}, }, # a dotted line between the second and the third box vlines={"x": 1.5, "style": {"plot_vline_color": "#888888", "plot_vline_style": LINE_STYLE.DOTTED}}, title="Body mass of Palmer penguins against the overall mean", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Reference bands A line marks one value; a band shades a range, such as a normal range or a tolerance around a value. `hspans` shades between `ymin` and `ymax` and `vspans` between `xmin` and `xmax`; at least one bound is required, and an omitted bound runs to the axis edge. Each takes a dictionary or a list of them, with an optional `label` and a `style` of `plot_hspan_*` or `plot_vspan_*` attributes ([HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs)). Labeled lines and bands are what the legend of a box plot lists: `show_legend` turns it on, and `legend` gives it a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols` and the `alignment` of the entries ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The example shades one standard deviation around the overall mean: the Adelie and Chinstrap boxes sit inside the band, the Gentoo box mostly above it. The legend goes outside the axes, where it covers no box. ``` from datachart.constants import LEGEND_LOCATION std_mass = (sum((mass - mean_mass) ** 2 for mass in masses) / len(masses)) ** 0.5 BoxPlot( data=body_mass, # shade one standard deviation around the mean hspans={ "ymin": mean_mass - std_mass, "ymax": mean_mass + std_mass, "label": "mean ± 1 SD", "style": {"plot_hspan_color": "#d62728", "plot_hspan_alpha": 0.12}, }, # and keep the mean itself as a line hlines={ "y": mean_mass, "label": "mean", "style": {"plot_hline_color": "#d62728", "plot_hline_style": LINE_STYLE.DASHED}, }, title="Body mass of Palmer penguins against the overall spread", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # a titled legend outside the axes show_legend=True, legend={"title": "All penguins", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Text annotations Where a reference line marks a value, a note explains it. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (box position, value) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below points at the lightest Chinstrap penguin, the lowest outlier of the dataset. ``` lightest = min(point["value"] for point in body_mass if point["label"] == "Chinstrap") BoxPlot( data=body_mass, # a note pinned to the axes, pointing at the Chinstrap outlier texts={ "text": f"the lightest penguin\nof the dataset: {lightest:,} g", "x": 0.6, "y": 0.2, "coords": "axes", "target": (1, lightest), }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Date labels Groups are often days, weeks or months: one box per day of measurements. A `label` that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) keeps its categorical position but prints through `xticks_format`, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. `daily_latency`, defined in a hidden cell, holds illustrative response times (in ms) of 100 requests per day to a web service over two weeks, drawn from a seeded log-normal generator, with a slow release on the ninth day that was rolled back two days later. One box per day shows the release as a jump in the median and a longer upper whisker. ``` from datachart.constants import DATE_FORMAT BoxPlot( data=daily_latency, title="Daily response times, a slow release on 9 March", xlabel="Day (2024)", ylabel="Response time (ms)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_outliers=False, # print the date labels as month and day xticks_format=DATE_FORMAT.MONTH_DAY, xtickrotate=45, ).show() ``` ### Boxes with swarms and violins A box summarizes its group but hides how many values it holds and how they are spread inside it. A [datachart.charts.SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.charts.SwarmPlot) shows every observation and a [datachart.charts.ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.charts.ViolinPlot) the shape of the distribution. Over the same labels they draw at the same positions as the boxes, so [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays them. A panel holds one box plot dataset, and it overlays with other kinds of chart; the [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide covers the rest. The swarm already draws every penguin, so the boxes hide their outliers. The swarm shows what the boxes cannot: there are fewer than half as many Chinstrap penguins as Adelie. ``` from datachart.charts import SwarmPlot, ViolinPlot from datachart.utils import Panel Panel( [ # the boxes summarize; the swarm draws every penguin, outliers included BoxPlot(data=body_mass, show_outliers=False, style={"plot_box_alpha": 0.4}), SwarmPlot(data=body_mass, style={"plot_swarm_size": 6}), ], title="Body mass of Palmer penguins, every penguin", xlabel="Species", ylabel_left="Body mass (g)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` A violin body behind the box adds the shape of the distribution: draw the violin with `inner=None`, since the box supplies the summaries, and give the box a white fill so it reads over the body. The Gentoo violin stays wide across its whole box instead of peaking at the median, a hint of two groups of different size inside the species: the two sexes, which the [Multiple Box Plots](#multiple-box-plots) section splits apart. ``` Panel( [ # the body only; the box supplies the summaries ViolinPlot(data=body_mass, inner=None, style={"plot_violin_alpha": 0.3}), BoxPlot( data=body_mass, show_outliers=False, style={"plot_box_color": "#FFFFFF", "plot_box_alpha": 0.9}, ), ], title="Body mass of Palmer penguins, with the distribution shape", xlabel="Species", ylabel_left="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Multiple Box Plots To compare several datasets over the same groups, pass a list of lists to `data`: each inner list is one dataset, and the per-dataset attributes (`subtitle`, `style`, `hlines` and the other reference settings) become lists aligned with it. Boxes never overlay each other, so each dataset is drawn in its own subplot, which `subplots=True` requires; `subtitle` titles the subplots, while `title`, `xlabel` and `ylabel` stay global. The hidden cell splits the penguins by sex into `body_mass_by_sex`, the 165 female and the 168 male penguins (the 9 penguins without a recorded sex are left out). A style per subplot colors each sex. ``` SEX_STYLE = [{"plot_box_color": "#e07a5f"}, {"plot_box_color": "#3d85c6"}] BoxPlot( # one dataset per sex data=body_mass_by_sex, # a subtitle and a color per subplot subtitle=SEXES, style=SEX_STYLE, title="Body mass of Palmer penguins by sex", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # each dataset in its own subplot subplots=True, ).show() ``` The two subplots above scale their value axes separately, so the female Gentoo box looks as high as the male one although it is 800 g lighter. The next section fixes that. ### Shared axes across subplots Boxes in two subplots only compare when they share a scale. `sharey=True` puts every subplot on one value axis and `sharex=True` on one group axis; a shared axis is labeled once, on the outer subplots. `ymin` and `ymax` set the shared range so that it covers the boxes of every subplot. `max_cols` limits the subplots per row, so `max_cols=1` stacks them. Side by side and on a shared mass axis, the males of every species are heavier than the females, and the gap is largest for the Gentoo. ``` BoxPlot( data=body_mass_by_sex, subtitle=SEXES, style=SEX_STYLE, title="Body mass of Palmer penguins by sex", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, subplots=True, # one mass axis for both sexes, covering both sharey=True, ymin=2500, ymax=6500, ).show() ``` When the subplots hold different quantities, only the group axis can be shared. Body mass and flipper length stacked in one column share the species axis, labeled once under the bottom subplot, and each keeps its own value axis; the subtitles carry the units. ``` BoxPlot( data=[body_mass, flipper_length], subtitle=["Body mass (g)", "Flipper length (mm)"], title="Size of Palmer penguins", xlabel="Species", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, subplots=True, # stack the subplots and share the species axis max_cols=1, sharex=True, ).show() ``` ### Subplot orientation `orientation` turns every subplot at once. Horizontal boxes move the species to the y-axis, so side by side it is `sharey` that labels them once, next to the left subplot, and the grid follows the values to the x-axis. ``` BoxPlot( data=[body_mass, flipper_length], subtitle=["Body mass (g)", "Flipper length (mm)"], # every subplot horizontal orientation=ORIENTATION.HORIZONTAL, title="Size of Palmer penguins", ylabel="Species", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, subplots=True, # the species are now on the y-axis sharey=True, ).show() ``` ## Additional Features ### Axis scales Response times, incomes and file sizes are skewed: most values are small and a long tail runs far above them. On a linear axis the tail squeezes the boxes into a thin strip at the bottom. `scaley` takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, and a logarithmic scale spreads the boxes out so that their medians and quartiles can be compared. `response_times`, defined in a hidden cell, holds illustrative response times (in ms) of 200 requests to each of four services, drawn from a seeded log-normal generator. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: BoxPlot( data=response_times, title=f"Response times on the '{scale}' scale", xlabel="Service", ylabel="Response time (ms)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # the scale of the value axis scaley=scale, ).show() ``` ### Custom data keys Data from a file or an API rarely uses the `label` and `value` keys, and renaming every record just to plot it is a chore. The `label` and `value` arguments name the keys to read instead. `penguin_records` stores the penguins the way the published CSV file does, one record per penguin with a `species` and a `body_mass_g` key: ``` penguin_records = [ {"species": group["species"], "sex": group["sex"], "body_mass_g": mass} for group in PENGUINS for mass in group["body_mass"] ] penguin_records[:2] ``` ``` BoxPlot( data=penguin_records, # the keys that hold the group and the value label="species", value="body_mass_g", title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format=VALUE_FORMAT.THOUSANDS, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Which Model Should Ship? (Seed Spread, Notches, Median Labels, and a Rule-Picked Winner) `benchmark` holds the illustrative test accuracy of five models, each trained with 20 random seeds, drawn from a seeded generator. A single accuracy per model hides how much of the difference between models is seed noise; one box per model shows the spread, and the notches say whether two medians really differ. `emphasis_rule={"top": 1}` highlights the model with the best median accuracy and value labels print its median. The notches temper the verdict: the Ensemble notch overlaps the winner's, so the data cannot say the Ensemble is worse, while Baseline and Wide are behind beyond doubt. A notch that folds back past its box, as for Wide, means the confidence interval is wider than the box itself, a sign that 20 seeds are too few to pin that median down. ``` BoxPlot( data=benchmark, # highlight the model with the best median, mute the rest emphasis_rule={"top": 1}, # do the medians really differ? show_notch=True, # print the medians as percentages show_values=True, value_format=VALUE_FORMAT.PERCENT, title=f"Test accuracy across {N_SEEDS} seeds", xlabel="Model", ylabel="Accuracy", yticks_format=VALUE_FORMAT.PERCENT_INT, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` ### Example 2: How Cold Does Each Month Get? (Horizontal Boxes, a Freezing Line, and a Band) `daily_temperatures` holds one illustrative year of daily mean temperatures (in °C) in a central European city, drawn from a seeded generator around approximate monthly means, with larger day-to-day swings in winter. Twelve boxes read best as horizontal boxes, with January at the top; since the first box is drawn at the bottom, the data is ordered from December to January. A dashed `vlines` line marks the freezing point, so the months with frosty days are the boxes that reach left of it, and a `vspans` band shades the 18 to 24 °C range of warm days, where only the summer boxes sit. The outliers stay: an unusually cold or warm day is what a reader of this chart looks for. ``` BoxPlot( data=daily_temperatures, # twelve labeled boxes read best top to bottom orientation=ORIENTATION.HORIZONTAL, # the freezing point vlines={ "x": 0, "label": "freezing point", "style": {"plot_vline_color": "#4c72b0", "plot_vline_style": LINE_STYLE.DASHED, "plot_vline_width": 1.5}, }, # the range of warm days vspans={ "xmin": 18, "xmax": 24, "label": "warm days", "style": {"plot_vspan_color": "#f4a261", "plot_vspan_alpha": 0.2}, }, title="Daily mean temperature by month", xlabel="Temperature (°C)", ylabel="Month", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.X, show_legend=True, legend={"title": "Reference", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Example 3: Do the Services Meet Their SLA? (A Log Scale, an SLA Line, a Note, and a Grid with Swarm Panels) A service level agreement (SLA) promises that requests finish within 500 ms, and the `response_times` of the [Axis scales](#axis-scales) section show which of the four services keeps the promise. The top chart draws all four services on a log scale with the SLA as a dashed line; `emphasis_rule={"above": 500, "by": "max"}` highlights the services whose slowest request breaks the SLA, and a note points at Reports, whose median alone is above the line. Search is highlighted although its whole box sits below the line. The bottom row zooms into Search and Checkout on one linear range: a [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) per service overlays the box on a swarm of every request, so the breaches are counted, not guessed. Search breaks the SLA with a handful of slow requests, Checkout never. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts the three charts in one figure. ``` from datachart.utils import Grid SLA_MS = 500 SLA_LINE = { "y": SLA_MS, "style": {"plot_hline_color": "#d62728", "plot_hline_style": LINE_STYLE.DASHED, "plot_hline_width": 1.5}, } overview = BoxPlot( data=response_times, scaley=SCALE.LOG, hlines=SLA_LINE, # the services whose slowest request breaks the SLA emphasis_rule={"above": SLA_MS, "by": "max"}, texts={ "text": "median above the SLA", "x": 0.55, "y": 0.9, "coords": "axes", "target": (3, 700), }, title="All services, log scale", ylabel="Response time (ms)", show_grid=SHOW_GRID.Y, ) def service_zoom(service): # one service: a box over every request, with the SLA line requests = [point for point in response_times if point["label"] == service] breaches = sum(point["value"] > SLA_MS for point in requests) return Panel( [ BoxPlot(data=requests, show_outliers=False, style={"plot_box_alpha": 0.4}, hlines=SLA_LINE), SwarmPlot(data=requests, style={"plot_swarm_size": 4}), ], title=f"{service}: {breaches} of {N_REQUESTS} over {SLA_MS} ms", ylabel_left="Response time (ms)", show_grid=SHOW_GRID.Y, # the same range for both services, the SLA line included ymin=0, ymax=700, ) Grid( [ [overview], [service_zoom("Search"), service_zoom("Checkout")], ], title="Response times against a 500 ms SLA", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Violin Plot A violin plot compares the distribution of a numeric value across a few groups. Each group gets a smoothed outline of where its values fall, so the plot answers what a box plot cannot: *is the group one cluster or two, and where do its values pile up?* This guide shows how to create violin plots with the [datachart.charts.ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.charts.ViolinPlot) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-violin-plot), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import ViolinPlot ``` ## Basics The examples in this guide share one dataset: the body mass of the 342 penguins with a recorded mass in the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset (CC0), three species measured on islands of the Palmer Archipelago in Antarctica. The data lives in a hidden cell. `penguins` holds one record per penguin: the species as `label`, the body mass in grams as `value`, and the `sex` of the bird (`None` for the few birds whose sex was not recorded). `flippers` holds the flipper length in millimeters of the same birds. The species differ in more than their average: Gentoo males and females are so far apart in mass that the Gentoo group has two peaks, and a box plot cannot show that. Each data point is one observation, a dictionary with a `label` (the group) and a `value`. The points that share a `label` form one violin, so three species give three violins; extra keys such as `sex` are ignored until a parameter asks for them: ``` penguins[:3] ``` **Basic example.** Only the `data` argument is required. Each body is a kernel density estimate of its group's values (a smoothed histogram), mirrored around the group's position, so the body is wide where many penguins share a mass and thin where few do. Inside it, a thin bar spans the middle half of the values (the first to the third quartile), the line through it reaches the furthest values within 1.5 times the bar length, and the dot is the median. The Gentoo body is pinched at the waist: two clusters of birds, with fewer in between. ``` ViolinPlot( # add the data to the chart data=penguins ).show() ``` ## Customizing the Violin Plot Every customization is either a keyword argument of `ViolinPlot` or a `plot_violin_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ---------------------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | format or place the ticks | `yticks_format`, `yticks`, `xtickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | fix the aspect ratio of the axes | `aspect_ratio` | [Figure size and grid](#figure-size-and-grid) | | change what is drawn inside the body | `inner` | [Inner marks](#inner-marks) | | smooth or sharpen the body | `bandwidth` | [Bandwidth](#bandwidth) | | compare two subgroups within each violin | `split`, `show_legend` | [Split violins](#split-violins) | | title and place the legend | `legend` | [Legend](#legend) | | print the median of each violin | `show_values`, `value_format` | [Value labels](#value-labels) | | change the body fill, edge, width, or hatch | `style={"plot_violin_color": ..., "plot_violin_width": ...}` | [Violin style](#violin-style) | | style the inner marks and the median dot | `style={"plot_violin_inner_color": ..., "plot_violin_median_color": ...}` | [Violin style](#violin-style) | | draw the violins horizontally | `orientation` | [Horizontal violins](#horizontal-violins) | | highlight some violins, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a threshold or shade a range | `hlines`, `vlines`, `hspans`, `vspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | use dates as group labels | `date` objects as `label`, `xticks_format` | [Date labels](#date-labels) | | draw a box plot or the observations over the violins | `Panel` with `BoxPlot` or `SwarmPlot` | [Violins with boxes and swarms](#violins-with-boxes-and-swarms) | | compare several datasets side by side | `data` as a list of lists, `subtitle`, `subplots` | [Multiple Violin Plots](#multiple-violin-plots) | | arrange the subplots and share their axes | `max_cols`, `sharex`, `sharey` | [Shared axes across subplots](#shared-axes-across-subplots) | | use a logarithmic value axis | `scaley` | [Axis scales](#axis-scales) | | label the ticks of transformed values | `yticks`, `yticklabels` | [Axis scales](#axis-scales) | | plot data with other key names | `label`, `value` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `inner` | [`VIOLIN_INNER`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VIOLIN_INNER) | | `bandwidth` | [`BANDWIDTH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) | The full list of style attributes is in the [datachart.typings.ViolinStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinStyleAttrs) type; the full list of parameters is in the [datachart.charts.ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.charts.ViolinPlot) reference. ### Title, axis labels and ticks A violin without labels leaves the reader guessing what is being measured and in which unit; `title`, `xlabel` and `ylabel` say it. Masses in the thousands read easier with a thousands separator, which `yticks_format` adds with any `"{x:,.0f}"` style string, and `yticks` places the ticks at round values. `ymin` and `ymax` fix the value range; each body ends at its lightest and heaviest bird, so a range with a margin keeps the tips off the frame. ``` ViolinPlot( data=penguins, # add the title title="Body mass of Palmer penguins", # add the x and y axis labels xlabel="Species", ylabel="Body mass (g)", # thousands separator on the value axis, one tick per 1,000 g yticks_format="{x:,.0f}", yticks=[3000, 4000, 5000, 6000, 7000], # fix the value range, with a margin around the tips ymin=2500, ymax=7000, ).show() ``` ### Figure size and grid The default figure is nearly square, while three violins read well in a wide, short figure that fits the width of a page. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE). Grid lines let the eye carry a median or a peak across to the value axis: [SHOW_GRID.Y](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) draws them along the values only, which is all a vertical violin needs. `aspect_ratio` fixes the ratio of the axes rather than of the figure ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); violins rarely need it, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID ViolinPlot( data=penguins, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the value axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Inner marks The body shows the shape, and the marks inside it give the numbers to read against: where the median is, and where the middle half of the birds sits. `inner` picks them from [VIOLIN_INNER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VIOLIN_INNER): `VIOLIN_INNER.BOX` (the default) draws a thin quartile bar, a whisker line and a median dot, the summary of a box plot; `VIOLIN_INNER.QUARTILES` draws a dashed median and dotted quartile lines across the body, which keep the shape in view; `VIOLIN_INNER.MEDIAN` draws the median line alone; `None` draws the body only. The quartile lines show what the box hides: the Gentoo median falls near the waist of the body, between the two clusters, where fewer birds actually sit. ``` from datachart.constants import VIOLIN_INNER for inner in [VIOLIN_INNER.QUARTILES, None]: ViolinPlot( data=penguins, # the marks drawn inside each body inner=inner, title=f"Body mass of Palmer penguins, inner={inner!r}", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Bandwidth The body is a smoothed estimate, and how much it is smoothed decides what the reader sees. `bandwidth` sets the width of the smoothing kernel: a rule of thumb from [BANDWIDTH](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) (`BANDWIDTH.SCOTT`, the default, or `BANDWIDTH.SILVERMAN`, which is nearly the same), or a number that scales the spread of the values. Too small a number follows every clump of repeated masses (penguins were weighed to the nearest 25 or 50 g) and draws bumps that are noise; too large a number irons the Gentoo body into a single hump and hides the two clusters that make it interesting. The summary marks do not change, because they are computed from the values, not from the estimate. ``` from datachart.constants import BANDWIDTH for bandwidth in [0.1, BANDWIDTH.SCOTT, 1.0]: ViolinPlot( data=penguins, # the width of the smoothing kernel bandwidth=bandwidth, inner=VIOLIN_INNER.QUARTILES, title=f"Body mass of Palmer penguins, bandwidth={bandwidth!r}", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Split violins The Gentoo waist asks a question: what are the two clusters? A split violin answers it by cutting each violin in half along a second variable. `split` names the key whose values pick the half; it must take **exactly two** values across the data, the first one seen draws on the left and the second on the right, each half in its own color and with its own inner marks, and `show_legend` names the halves. Split by `sex`, the two Gentoo clusters turn out to be the females and the males, and the males of every species are heavier. The birds with no recorded sex would be a third value, so they are dropped first. ``` sexed = [penguin for penguin in penguins if penguin["sex"] is not None] ViolinPlot( data=sexed, # the left half holds the females, the right half the males split="sex", inner=VIOLIN_INNER.QUARTILES, # name the two halves show_legend=True, title="Body mass of Palmer penguins by sex", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Legend The default legend lands where the theme puts it, which may cover a body. `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The heavy Gentoo violin fills the upper right, so a titled legend in the upper left, in one row, stays clear of it. ``` from datachart.constants import LEGEND_LOCATION ViolinPlot( data=sexed, split="sex", inner=VIOLIN_INNER.QUARTILES, show_legend=True, # a titled legend in one row, clear of the Gentoo violin legend={"title": "Sex", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, title="Body mass of Palmer penguins by sex", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymax=7000, ).show() ``` ### Value labels When the exact center matters, `show_values` prints each violin's median beside its median mark, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:,.0f}"` style string. With `split`, each half prints its own median, so the gap between the sexes can be read off directly. The label font size, color and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). ``` ViolinPlot( data=sexed, split="sex", inner=VIOLIN_INNER.MEDIAN, show_legend=True, legend={"title": "Sex", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, # print the median of each half show_values=True, value_format="{x:,.0f} g", style={"plot_value_fontsize": 8}, title="Median body mass of Palmer penguins by sex", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ymax=7000, ).show() ``` ### Violin style The `style` dictionary sets the look of every violin of the chart: `plot_violin_color`, `plot_violin_alpha`, `plot_violin_edgecolor`, `plot_violin_linewidth` and `plot_violin_hatch` style the body, `plot_violin_width` sets its maximum width, the `plot_violin_inner_*` attributes style the marks inside it and the `plot_violin_median_*` attributes the median dot; the attributes are listed in [datachart.typings.ViolinStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinStyleAttrs), and any attribute left out keeps the value of the active theme. A chart that will be printed in greyscale needs to survive without color: a light fill with a dark edge, a hatch from [HATCH_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HATCH_STYLE), and a red median dot that stands out on the dark quartile bar. ``` from datachart.constants import HATCH_STYLE ViolinPlot( data=penguins, # a print-safe look: light hatched bodies with a dark edge style={ "plot_violin_color": "#f4f1de", "plot_violin_alpha": 1.0, "plot_violin_edgecolor": "#3d405b", "plot_violin_linewidth": 1.2, "plot_violin_hatch": HATCH_STYLE.DIAGONAL, "plot_violin_width": 0.7, "plot_violin_inner_color": "#3d405b", "plot_violin_inner_linewidth": 1.5, "plot_violin_median_color": "#c1121f", "plot_violin_median_size": 6, }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Horizontal violins Horizontal violins read like a set of distributions stacked on a shared scale, and long group names stay readable without rotation. `orientation=ORIENTATION.HORIZONTAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) puts the groups on the y-axis and the values on the x-axis, so the axis labels, the tick format and the grid swap with it. The first group is drawn at the bottom. ``` from datachart.constants import ORIENTATION ViolinPlot( data=penguins, # draw the violins horizontally orientation=ORIENTATION.HORIZONTAL, title="Body mass of Palmer penguins", # the axis labels, the tick format and the grid swap with the orientation xlabel="Body mass (g)", ylabel="Species", xticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. `emphasis` takes one role per violin, aligned with the groups in the order they first appear in the data (here Adelie, Chinstrap, Gentoo): `"highlight"` bolds the body edge, `"background"` mutes the body and its inner marks, `None` leaves it as it is. A single role applies to every violin. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Adelie and Chinstrap penguins weigh about the same, and muting the Gentoo makes that the point. ``` from datachart.constants import EMPHASIS ViolinPlot( data=penguins, # one role per group: Adelie, Chinstrap, Gentoo emphasis=[EMPHASIS.HIGHLIGHT, EMPHASIS.HIGHLIGHT, EMPHASIS.BACKGROUND], title="Adelie and Chinstrap penguins weigh about the same", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` `emphasis_rule` picks the violins from the data instead of by position. It is a one-key dictionary read against a summary of each group: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive). The summary is the median by default, the value the inner marks already show; a `"by"` key picks `"mean"`, `"min"`, `"max"` or `"sum"` instead. An explicit `emphasis` role wins over the rule. The rule below highlights every species whose heaviest bird tops 5,000 g: ``` ViolinPlot( data=penguins, # read the rule against the heaviest bird of each species emphasis_rule={"above": 5000, "by": "max"}, title="Species with a bird over 5,000 g", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Reference lines and bands Reference lines and bands put the violins in context. `hlines` draws a horizontal line at a value, such as the mean of all birds, and `vlines` a vertical one; the violins sit at positions `0`, `1`, `2`, … along the group axis, as bars do, so a half-integer falls between two violins. `hspans` and `vspans` shade a range instead of marking a value. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs). The example marks the mean of all 342 birds and shades one standard deviation around it: the mean falls in the gap between the lighter species and the Gentoo, where few birds of any species sit, which is why a single average describes none of them well. ``` from statistics import mean, pstdev from datachart.constants import LINE_STYLE masses = [penguin["value"] for penguin in penguins] mass_mean, mass_sd = mean(masses), pstdev(masses) ViolinPlot( data=penguins, # a dashed line at the mean of all birds hlines={ "y": mass_mean, "label": "mean of all birds", "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, # shade one standard deviation around it hspans={ "ymin": mass_mean - mass_sd, "ymax": mass_mean + mass_sd, "label": "mean ± 1 SD", "style": {"plot_hspan_color": "#c1121f"}, }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # one row above the axes, clear of the band legend={"location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 2}, ).show() ``` ### Text annotations Where a reference line marks a value, a note explains a shape. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (group position, value) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below points at the Gentoo waist and says what it is. ``` ViolinPlot( data=penguins, inner=VIOLIN_INNER.QUARTILES, # a note pinned to the axes, pointing at the Gentoo waist texts={ "text": "two clusters:\nfemales below, males above", "x": 0.3, "y": 0.85, "coords": "axes", # the Gentoo violin sits at position 2 "target": (1.78, 4900), }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Date labels Groups are often periods: months, quarters, sampling campaigns. A `label` that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) keeps its group position but prints through `xticks_format`, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. `daily_temperatures`, defined in a hidden cell, holds 30 illustrative daily mean temperatures per month, drawn around the 1991–2020 monthly normals of Ljubljana and labeled by the first day of each month. The violins show the seasonal cycle, and that winter days vary more than summer days. ``` from datachart.constants import DATE_FORMAT ViolinPlot( data=daily_temperatures, inner=VIOLIN_INNER.MEDIAN, title="Daily mean temperature in Ljubljana (illustrative)", ylabel="Temperature (°C)", # print the month labels as abbreviated month names xticks_format="%b", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Violins with boxes and swarms The violin shows the shape, a box plot the standard summary, and a swarm plot every single observation; one chart rarely needs all three, but a figure that must convince a skeptical reader sometimes does. A violin plot draws its groups at the same positions as a [BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.charts.BoxPlot) or a [SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.charts.SwarmPlot) with the same labels, so [Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays them: the violin with `inner=None` supplies the body, the box plot drawn over it supplies the whiskers and outliers. A panel takes one violin plot, and all its figures must group the same labels in the same order. The [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide covers the rest. ``` from datachart.charts import BoxPlot, SwarmPlot from datachart.utils import Panel Panel( [ # the body only; the box plot supplies the summary ViolinPlot(data=penguins, inner=None, style={"plot_violin_alpha": 0.4}), BoxPlot( data=penguins, style={"plot_box_color": "#ffffff", "plot_box_alpha": 0.9, "plot_box_width": 0.15}, ), ], title="Body mass of Palmer penguins", xlabel="Species", ylabel_left="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` A swarm plot draws every bird as a point, so the reader can check that the smoothed body is not inventing structure. The points draw over the body, so a faded body keeps them legible: ``` Panel( [ # the body only, faded behind the points ViolinPlot(data=penguins, inner=None, style={"plot_violin_alpha": 0.25}), SwarmPlot(data=penguins, style={"plot_swarm_size": 6}), ], title="Body mass of Palmer penguins, every bird", xlabel="Species", ylabel_left="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Multiple Violin Plots To compare several datasets, pass a list of lists to `data`: each inner list is one dataset, drawn in its own subplot, and `subplots=True` is required (violins of two datasets cannot share one axes; to compare two subgroups within each violin, use [split](#split-violins)). The per-chart attributes (`subtitle`, `style`) become lists aligned with the datasets; `title`, `xlabel` and `ylabel` stay global. Body mass and flipper length are different quantities, so each subplot carries its unit in its subtitle, and a style per chart keeps them apart. ``` ViolinPlot( # one dataset per subplot data=[penguins, flippers], # a subtitle and a style per subplot subtitle=["Body mass (g)", "Flipper length (mm)"], style=[{"plot_violin_color": "#457b9d"}, {"plot_violin_color": "#e76f51"}], title="Palmer penguins", xlabel="Species", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # draw each dataset in its own subplot subplots=True, ).show() ``` ### Shared axes across subplots Subplots side by side invite comparison, and shared axes make it fair. `sharex` and `sharey` put the subplots on one axis, which is then labeled once, on the outer subplots; `max_cols` limits the subplots per row, and `max_cols=1` stacks them. The two datasets have different units, so only the group axis can be shared: with horizontal violins the species sit on the y-axis, and `sharey=True` labels them once, next to the left subplot. ``` ViolinPlot( data=[penguins, flippers], subtitle=["Body mass (g)", "Flipper length (mm)"], style=[{"plot_violin_color": "#457b9d"}, {"plot_violin_color": "#e76f51"}], orientation=ORIENTATION.HORIZONTAL, title="Palmer penguins", ylabel="Species", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, subplots=True, # at most two subplots per row, one species axis for both max_cols=2, sharey=True, ).show() ``` ## Additional Features ### Axis scales Waiting times, file sizes and incomes are skewed: most values are small and a few are huge, so on a linear axis the bodies are squashed against zero with a long thin tail. `scaley` takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, and a logarithmic axis spreads the small values out. Body masses are not skewed, so this example switches dataset: `first_reply`, defined in a hidden cell, holds 200 illustrative first-reply times (in minutes) of support requests per channel, from a seeded generator. On the linear axis only the forum body is visible; on the log axis all three are, minutes to days on one axis. The body is still estimated from the raw values, though, so on the log axis it keeps a linear-space shape: a flat, wide base and a thin spike toward the long tail. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: ViolinPlot( data=first_reply, inner=VIOLIN_INNER.QUARTILES, title=f"Time to first reply, '{scale}' scale (illustrative)", xlabel="Channel", ylabel="Minutes", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # the scale of the value axis scaley=scale, ).show() ``` When the shape on the log scale matters, estimate the body on that scale: take the base-10 logarithm of the values, keep the linear axis, and put the original units back with `yticks` and `yticklabels` (the two lists must have the same length). The bodies turn into smooth humps, and the forum requests turn out to wait anywhere from an hour to several days: ``` import math log_reply = [{**point, "value": math.log10(point["value"])} for point in first_reply] ViolinPlot( data=log_reply, inner=VIOLIN_INNER.QUARTILES, title="Time to first reply, estimated on the log scale (illustrative)", xlabel="Channel", ylabel="Time", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # ticks at powers of ten, labeled in the original units yticks=[0, 1, 2, math.log10(1440), math.log10(14400)], yticklabels=["1 min", "10 min", "100 min", "1 day", "10 days"], ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `label` and `value` keys, and renaming every record just to plot it is a chore. Instead, tell `ViolinPlot` which keys to read with the `label` and `value` arguments. `penguin_records` stores the birds the way a CSV export would, with a `species` and a `body_mass_g` key: ``` penguin_records = [ {"species": group["species"], "sex": group["sex"], "body_mass_g": mass} for group in PENGUINS for mass in group["body_mass"] ] penguin_records[:2] ``` ``` ViolinPlot( data=penguin_records, # the keys that hold the group and the value label="species", value="body_mass_g", title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", yticks_format="{x:,.0f}", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: What the Box Hides (Bimodal Response Times, Quartile Lines, and a Box Overlay in a Grid) `response_times` holds the illustrative response time (in ms) of 300 requests to each of three services, from a seeded generator. Catalog and Search answer some requests from a cache and the rest from the database, so their times have two modes, fast cache hits and slow misses, with almost nothing in between; Checkout never uses the cache. A box plot reduces each service to a median and a spread, and for Catalog and Search the box stretches across the empty gap between the two modes. The left chart shows the two humps with quartile lines, the right one draws the box over a faded body, and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts them side by side on one value axis. ``` from datachart.utils import Grid violins = ViolinPlot( data=response_times, # quartile lines keep the two humps visible inner=VIOLIN_INNER.QUARTILES, title="Violin", ylabel="Response time (ms)", show_grid=SHOW_GRID.Y, ) boxes = Panel( [ ViolinPlot(data=response_times, inner=None, style={"plot_violin_alpha": 0.25}), BoxPlot(data=response_times, show_outliers=False), ], title="Box over the body", show_grid=SHOW_GRID.Y, ) Grid( [[violins, boxes]], title=f"Response time of {N_REQUESTS} requests per service (illustrative)", figsize=FIG_SIZE.FULL_SHORT, sharey=True, ).show() ``` ### Example 2: Does the Validation Winner Hold Up on Test? (Split Violins, Value Labels, and an Emphasis Rule) `benchmark` holds the illustrative accuracy of four models, each trained with 20 random seeds and evaluated on the validation and the test split, from a seeded generator. Split by the evaluation split, each violin shows how much of a model's score is seed noise (the height of the body) and how much it drops from validation to test (the offset between the halves). The medians are printed on each half, and `emphasis_rule={"top": 2}` keeps the two contenders with the best median in view while the rest are muted. Deep looks best on validation but drops on test; Deep + aug. holds its score. ``` from datachart.constants import VALUE_FORMAT ViolinPlot( data=benchmark, # validation on the left, test on the right split="split", inner=VIOLIN_INNER.MEDIAN, show_legend=True, legend={"title": "Evaluated on", "location": LEGEND_LOCATION.UPPER_LEFT, "ncols": 2}, # the median accuracy of each half show_values=True, value_format=VALUE_FORMAT.PERCENT, style={"plot_value_fontsize": 7}, # the two models with the best median accuracy emphasis_rule={"top": 2}, title=f"Accuracy across {N_SEEDS} seeds (illustrative)", xlabel="Model", ylabel="Accuracy", yticks_format=VALUE_FORMAT.PERCENT, ymax=0.94, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` ### Example 3: Can a Scale Tell the Species Apart? (Violins and Swarms in Panels, Notes, and a Grid) A field team without a bill gauge has a scale and a ruler: can body mass or flipper length alone tell the three Palmer species apart? The shared `penguins` and `flippers` data answer it. Each measurement gets a panel of faded violins with every bird drawn over them as a swarm, a note says what the panel shows, and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) stacks the two panels so the answer reads top to bottom: both measurements separate the Gentoo from the rest, but the Adelie and Chinstrap bodies overlap almost completely in mass and largely in flipper length. ``` def measurement_panel(data, title, unit, note): # faded violins with every bird on top, and a note on what it shows return Panel( [ ViolinPlot( data=data, inner=None, style={"plot_violin_alpha": 0.25}, texts={"text": note, "x": 0.02, "y": 0.92, "coords": "axes"}, ), SwarmPlot(data=data, style={"plot_swarm_size": 4}), ], title=title, ylabel_left=unit, show_grid=SHOW_GRID.Y, ) Grid( [ [measurement_panel(penguins, "Body mass", "g", "Adelie and Chinstrap overlap")], [measurement_panel(flippers, "Flipper length", "mm", "Gentoo stands apart again;\nChinstrap flippers run a little longer")], ], title="Can a scale or a ruler tell the species apart?", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Swarm Plot A swarm plot draws every observation as its own point, grouped by category and spread sideways so no point hides another. Where a box plot summarizes a sample, a swarm shows it whole, which matters when the sample is small or when single points are the story: an outlier with a name, a cluster, a gap. This guide shows how to create swarm plots with the [datachart.charts.SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.charts.SwarmPlot) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-swarm-plot), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import SwarmPlot ``` ## Basics The examples in this guide share one small dataset: the 47 presidencies of the United States in their official numbering (Grover Cleveland and Donald Trump each count twice), with the age of each president on the day he took office. The ages are computed from the birth and inauguration dates published in the White House presidential biographies. The data lives in a hidden cell. `PRESIDENCIES` holds one row per presidency (name, party, birth date, first day and last day in office), and `inauguration_ages` holds one data point per presidency, labeled with its era: the presidencies that began in 1789–1897, in 1901–1993, and in 2001–2025. With 47 points, every point is a person, and the extremes have names: the youngest president, Theodore Roosevelt, and the two oldest, both sworn in during the last era. Each data point is a dictionary with a `label` (the group) and a `value`. The points that share a `label` form one swarm, so three eras give three swarms: ``` inauguration_ages[:3] ``` **Basic example.** Only the `data` argument is required. Each presidency is one point above its era, and points with the same or a close age spread sideways instead of stacking, so the width of a swarm at any height shows how many presidents took office at that age: ``` SwarmPlot( # add the data to the chart data=inauguration_ages ).show() ``` ## Customizing the Swarm Plot Every customization is either a keyword argument of `SwarmPlot` or a `plot_swarm_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | --------------------------------------------- | --------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range or the ticks | `ymin`, `ymax`, `yticks`, `xtickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | change the point color, size, marker, or edge | `style={"plot_swarm_color": ..., "plot_swarm_size": ...}` | [Point style](#point-style) | | jitter the points instead of packing them | `mode`, `jitter` | [Swarm and strip modes](#swarm-and-strip-modes) | | draw the swarms horizontally | `orientation` | [Horizontal swarms](#horizontal-swarms) | | change the order of the groups | the order of `data` | [Horizontal swarms](#horizontal-swarms) | | print each group's min, median, and max | `show_values`, `value_format` | [Value labels](#value-labels) | | highlight some groups, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | highlight single points | a series of their own, `emphasis` | [Emphasis](#emphasis) | | mark a threshold or shade a range | `hlines`, `vlines`, `hspans`, `vspans` | [Reference lines and bands](#reference-lines-and-bands) | | name a point on the chart | `texts` | [Text annotations](#text-annotations) | | use dates as group labels | `date` objects as `label`, `xticks_format` | [Date labels](#date-labels) | | put the points over a box or violin plot | `Panel` | [Swarms over boxes and violins](#swarms-over-boxes-and-violins) | | compare several datasets in one chart | `data` as a list of lists, `subtitle`, `show_legend` | [Multiple Swarm Plots](#multiple-swarm-plots) | | title and place the legend | `legend` | [Legend](#legend) | | draw each dataset in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | use a logarithmic value axis | `scaley` | [Logarithmic scale](#logarithmic-scale) | | plot data with other key names | `label`, `value` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `mode` | [`SWARM_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SWARM_MODE) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.SwarmStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmStyleAttrs) type; the full list of parameters is in the [datachart.charts.SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.charts.SwarmPlot) reference. ### Title, axis labels and ticks The basic chart does not say what the points measure; `title`, `xlabel` and `ylabel` do. `ymin` and `ymax` fix the value range, and `yticks` picks the tick positions: a round range from 40 to 80 frames every age, and a tick every five years makes the ages easy to read off. Long group names can be tilted with `xtickrotate` (or `ytickrotate`); three short eras do not need it. ``` SwarmPlot( data=inauguration_ages, # add the title title="Age of US presidents at inauguration", # add the x and y axis labels xlabel="Took office in", ylabel="Age (years)", # fix the value range and the ticks ymin=40, ymax=80, yticks=[40, 45, 50, 55, 60, 65, 70, 75, 80], ).show() ``` ### Figure size and grid Three swarms do not need a square figure; a wide, short one fits a page better. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE). Grid lines let the eye carry a point across to the value axis: `show_grid` takes a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member, and `SHOW_GRID.Y` draws them along the value axis only. `aspect_ratio` fixes the ratio of the axes ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); a swarm has no reason to, so the examples leave it at the default. ``` from datachart.constants import FIG_SIZE, SHOW_GRID SwarmPlot( data=inauguration_ages, title="Age of US presidents at inauguration", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the value axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Point style The `style` dictionary sets the look of the points: color, alpha, size, marker, edge and z-order; the attributes are listed in [datachart.typings.SwarmStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmStyleAttrs), and any attribute left out keeps the value of the active theme. With few points, larger markers make each one count, and a white edge keeps neighbors apart where the swarm packs them tightly. The swarm packs from the marker size, so larger points spread wider. The marker shapes are listed in [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER). ``` from datachart.constants import LINE_MARKER SwarmPlot( data=inauguration_ages, # large diamonds with a white edge style={ "plot_swarm_color": "#1d3557", "plot_swarm_size": 40, "plot_swarm_alpha": 0.9, "plot_swarm_marker": LINE_MARKER.DIAMOND, "plot_swarm_edge_color": "#ffffff", "plot_swarm_edge_width": 0.8, }, title="Age of US presidents at inauguration", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Swarm and strip modes A swarm computes a place for every point so none overlap, which is what makes single points readable. With thousands of points that placement adds nothing: the swarm fills its whole width anyway, and grows wider than the category. `mode` picks the placement from [SWARM_MODE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SWARM_MODE): `SWARM_MODE.SWARM` packs the points from the marker size at the moment the chart is drawn (the default; axis limits changed on the figure afterwards can shift the spacing), and `SWARM_MODE.STRIP` scatters them at random across the category, a *strip plot*. `jitter` sets the width of the strip as a fraction of the category width (0.4 by default), and the jitter is seeded, so the same data draws the same chart. On the presidents the strip lets points overlap, so the swarm is the better choice for a sample this small; the strip earns its place in [Example 1](#example-1-which-services-breach-the-sla-strip-mode-log-scale-and-an-sla-line), with 1,200 points. ``` from datachart.constants import SWARM_MODE SwarmPlot( data=inauguration_ages, # scatter the points at random instead of packing them mode=SWARM_MODE.STRIP, # a strip a quarter of the category wide jitter=0.25, title="Age of US presidents at inauguration, strip mode", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Horizontal swarms Long group names read best unrotated, and many groups read best top to bottom. `orientation=ORIENTATION.HORIZONTAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) puts the groups on the y-axis and the values on the x-axis, so the axis labels, the value range and the grid swap with it. The groups follow the order in which their labels first appear in `data`, and the first group is drawn at the bottom; `SwarmPlot` has no sorting parameter, so the order is set by ordering the data. Reversing it puts the first era on top, and the eras read as a timeline from top to bottom. ``` from datachart.constants import ORIENTATION SwarmPlot( # reversed, so the first era ends up at the top data=inauguration_ages[::-1], title="Age of US presidents at inauguration", # the axis labels, the range and the grid swap with the orientation xlabel="Age (years)", ylabel="Took office in", xmin=40, xmax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.X, # draw the swarms horizontally orientation=ORIENTATION.HORIZONTAL, ).show() ``` ### Value labels A reader who wants the numbers behind a swarm usually asks three things: the youngest, the oldest, and the typical age. `show_values` prints each group's minimum, median and maximum beside the points that hold them (the median beside the point nearest it), and `value_format` formats the labels: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. The label font size, color and padding are the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)). The median age barely moves across the eras; the range is what grows. ``` from datachart.constants import VALUE_FORMAT SwarmPlot( data=inauguration_ages, style={"plot_value_fontsize": 9}, title="Age of US presidents at inauguration", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # print the min, median and max of every era show_values=True, value_format=VALUE_FORMAT.INTEGER, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. `emphasis` takes one role per group, aligned with the group labels in the order they first appear: `"highlight"` bolds the edges of a group's points, `"background"` mutes them into the theme's muted color, and `None` leaves them as they are; a single value applies to every group. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. Highlighting the last era and muting the others asks the question of the chart: are recent presidents older? ``` from datachart.constants import EMPHASIS SwarmPlot( data=inauguration_ages, # one role per era, in the order the eras appear emphasis=[EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], title="Age of US presidents at inauguration, the last era", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` `emphasis_rule` picks the groups from the data instead of naming them. It is a dictionary with one condition, `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive), read against a summary of each group: the median by default, or the `"mean"`, `"min"`, `"max"` or `"sum"` named by a `"by"` key. The groups that match are highlighted, the rest muted, and an explicit `emphasis` role wins over the rule. Asking which era swore in a president older than 70 highlights the last one, through its maximum: ``` SwarmPlot( data=inauguration_ages, # the eras whose oldest president was over 70 emphasis_rule={"above": 70, "by": "max"}, title="Age of US presidents at inauguration, eras with a president over 70", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` Emphasis works on whole groups, and a data point has no emphasis of its own. To single out a few points, move them into a series of their own (the [Multiple Swarm Plots](#multiple-swarm-plots) section covers the list-of-lists form) and give each series a role: the rest in the background, the chosen points highlighted. The two series are packed separately, so this suits points that stand apart from their group, like the two presidents who took office at 78: ``` # the two oldest presidents in a series of their own oldest = [point for point in inauguration_ages if point["value"] >= 78] others = [point for point in inauguration_ages if point["value"] < 78] SwarmPlot( data=[others, oldest], # one role per series: mute the others, highlight the oldest emphasis=[EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], title="Age of US presidents at inauguration, the two oldest", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Reference lines and bands Reference lines and bands put the points in context. `hlines` draws a horizontal line at a value, such as a limit or the overall median, and `vlines` a vertical one; `hspans` and `vspans` shade a range instead. The groups sit at positions `1`, `2`, `3`, … along the category axis, so a vertical line at `1.5` falls between the first two groups. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs), and the line styles in [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE). The US Constitution requires a president to be at least 35: a band shades the ages below it, and a dashed line marks the median age of all 47 presidencies. Nobody came close to the limit. ``` from statistics import median from datachart.constants import LEGEND_LOCATION, LINE_STYLE SwarmPlot( data=inauguration_ages, # a dashed line at the median age of all presidencies hlines={ "y": median(point["value"] for point in inauguration_ages), "label": "median age", "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, # shade the ages below the constitutional minimum hspans={ "ymax": 35, "label": "below the minimum age", "style": {"plot_hspan_color": "#6c757d", "plot_hspan_alpha": 0.2}, }, title="Age of US presidents at inauguration", xlabel="Took office in", ylabel="Age (years)", ymin=30, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # outside the axes, clear of the points legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Text annotations When the story is a single point, a note says whose it is. `texts` places text on the chart, with an optional `target` that draws a connector to a point; the position is in data coordinates by default (group position, value), or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The target is a group position and a value, so the connector lands on a point that sits on its group's center line, as a lone extreme usually does. The keys are listed in [TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs), and the [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connectors and styling. ``` SwarmPlot( data=inauguration_ages, # name the youngest and the oldest presidents texts=[ { "text": "Theodore Roosevelt, 42", "x": 0.22, "y": 0.07, "coords": "axes", "target": (1, 42), }, { "text": "Joe Biden and\nDonald Trump, 78", "x": 0.62, "y": 0.85, "coords": "axes", "target": (2, 78), }, ], title="Age of US presidents at inauguration", xlabel="Took office in", ylabel="Age (years)", # room below the youngest for the note ymin=35, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Date labels Groups are often dates: race days, releases, sampling rounds. A `label` that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) keeps its group position but prints through `xticks_format` (or `yticks_format` for horizontal swarms), a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. `race_times`, defined in a hidden cell, holds the illustrative finishing times (in minutes) of a weekly 5 km community run on six Saturdays, labeled by the date of each run; the day and month are enough to tell the runs apart. ``` SwarmPlot( data=race_times, title="Finishing times of a weekly 5 km run", xlabel="Run", ylabel="Time (minutes)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # print the date labels as day and month xticks_format="%d %b", ).show() ``` ### Swarms over boxes and violins A swarm shows every observation but leaves the reader to estimate the median and the spread; a box plot draws them but hides the observations. [Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays the two: a [BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.charts.BoxPlot) and a `SwarmPlot` of the same data share their group positions, so the points sit on the boxes, and the points draw above them. The box plot's outliers are already in the swarm, so `show_outliers=False` hides them, and muting the box figure with the per-figure `"emphasis"` option lets the points carry the color. The [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide covers the per-figure options. ``` from datachart.charts import BoxPlot, ViolinPlot from datachart.utils import Panel Panel( [ # the boxes in the background; the swarm already draws the outliers {"figure": BoxPlot(data=inauguration_ages, show_outliers=False), "emphasis": EMPHASIS.BACKGROUND}, SwarmPlot(data=inauguration_ages), ], title="Age of US presidents at inauguration", xlabel="Took office in", ylabel_left="Age (years)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` A [ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.charts.ViolinPlot) outlines the shape of each distribution instead. `inner=None` draws the body only, since the swarm already shows where the values sit, and a low alpha keeps the points legible. With five points in the last era the outline is a guess, which is exactly what the points on top reveal. ``` Panel( [ # the body only, faded behind the points ViolinPlot(data=inauguration_ages, inner=None, style={"plot_violin_alpha": 0.3}), SwarmPlot(data=inauguration_ages), ], title="Age of US presidents at inauguration", xlabel="Took office in", ylabel_left="Age (years)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Multiple Swarm Plots To compare datasets over the same groups, pass a list of lists to `data`: each inner list is one series, and the per-series attributes (`subtitle`, `style`, `emphasis`) become lists aligned with it. The series share one category axis, and the swarms of the same group overlay at the same position in distinct colors; `show_legend` names them by their subtitles. `ages_by_party`, defined in a hidden cell, splits the presidencies into the 16 Democratic and the 20 Republican ones (the 11 presidencies of other parties, all in the first era, are left out), and a style per series colors each party in its customary color. The two series are packed separately, so a Democrat and a Republican of the same age land on the same spot; large, translucent points for one series and small, solid points for the other keep both visible. ``` # large, translucent Democrats under small, solid Republicans PARTY_STYLE = [ {"plot_swarm_color": PARTY_COLORS[0], "plot_swarm_size": 70, "plot_swarm_alpha": 0.35, "plot_swarm_edge_width": 0}, {"plot_swarm_color": PARTY_COLORS[1], "plot_swarm_size": 14}, ] SwarmPlot( # one series per party data=ages_by_party, # named for the legend subtitle=PARTIES, # and colored like the party style=PARTY_STYLE, title="Age of US presidents at inauguration, by party", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Legend `show_legend` lists the series; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The swarms reach the top corners of the chart, so the legend moves above the axes, titled and with both entries in one row. ``` SwarmPlot( data=ages_by_party, subtitle=PARTIES, style=PARTY_STYLE, title="Age of US presidents at inauguration, by party", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, # a titled legend above the axes, in one row legend={"title": "Party", "location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 2}, ).show() ``` ### Subplots Overlaid swarms pack separately, so two series at the same group can cover each other, and the size trick above only goes so far. `subplots=True` draws each series in its own panel instead: `subtitle` titles the panels, `title`, `xlabel` and `ylabel` stay global, and `max_cols` limits the panels per row. `sharey=True` puts the panels on one value axis, so an age in one panel compares with an age in the next, and `sharex=True` keeps one category axis for both. ``` SwarmPlot( data=ages_by_party, subtitle=PARTIES, # one color per party, the same point size in both panels style=[{"plot_swarm_color": color} for color in PARTY_COLORS], title="Age of US presidents at inauguration, by party", xlabel="Took office in", ylabel="Age (years)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # one panel per party, side by side subplots=True, # one value axis and one category axis for both panels sharex=True, sharey=True, ).show() ``` ## Additional Features ### Logarithmic scale Some values span orders of magnitude, and on a linear axis the small ones pile up at the bottom. `scaley` takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, and the swarm packs the points on the scaled axis, so they stay apart. `days_in_office`, defined in a hidden cell, holds the length of each completed presidency in days, from William Henry Harrison's 31 days to Franklin D. Roosevelt's 4,422 (the current presidency is left out). The log axis spreads the short presidencies, cut short by death or resignation, as clearly as the long ones, and the value labels name the extremes of each era. ``` from datachart.constants import SCALE SwarmPlot( data=days_in_office, title="Length of US presidencies", xlabel="Took office in", ylabel="Days in office", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_values=True, # draw the value axis on a logarithmic scale scaley=SCALE.LOG, # ticks at readable round numbers yticks=[30, 100, 300, 1000, 3000], ymin=20, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `label` and `value` keys, and renaming every record just to plot it is a chore. Instead, tell `SwarmPlot` which keys to read with the `label` and `value` arguments. `president_records` stores the presidencies the way a CSV export would, one record per presidency with a `name`, an `era` and an `age` key: ``` president_records = [ {"name": name, "era": era(took), "age": age_on(born, took)} for name, _, born, took, _ in PRESIDENCIES ] president_records[:2] ``` ``` SwarmPlot( data=president_records, # the keys that hold the group and the value label="era", value="age", title="Age of US presidents at inauguration", xlabel="Took office in", ylabel="Age (years)", ymin=40, ymax=80, figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Real-World Examples The examples below put the features above to work on realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Which Services Breach the SLA? (Strip Mode, Log Scale and an SLA Line) `response_times` holds the illustrative response times (in ms) of 300 requests to each of four services, drawn from a seeded log-normal generator: most requests are fast, and a long tail of slow ones stretches each distribution. The question is which services break the 500 ms service level agreement (SLA), and how often. With 1,200 points a swarm would overflow its category, so the strip mode scatters them instead, with small, translucent points so the dense regions read darker. On a log axis the tail reads at the same resolution as the bulk, and a shaded band above the SLA line holds the requests that breach it. ``` SwarmPlot( data=response_times, # scatter the many points across the category width mode=SWARM_MODE.STRIP, # small, translucent points style={"plot_swarm_size": 6, "plot_swarm_alpha": 0.4, "plot_swarm_edge_width": 0}, # the long tail reads at the same resolution as the bulk scaley=SCALE.LOG, # the SLA, and the requests that breach it hlines={ "y": SLA_MS, "label": f"SLA ({SLA_MS} ms)", "style": {"plot_hline_color": "#c1121f", "plot_hline_style": LINE_STYLE.DASHED}, }, hspans={ "ymin": SLA_MS, "style": {"plot_hspan_color": "#c1121f", "plot_hspan_alpha": 0.08}, }, title=f"Response time of {N_REQUESTS} requests per service", xlabel="Service", ylabel="Response time (ms)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, legend={"location": LEGEND_LOCATION.UPPER_LEFT}, ).show() ``` ### Example 2: Which Months Bring Frost? (Horizontal Swarms, a Rule and a Frost Band) `daily_temperatures` holds one year of illustrative daily mean temperatures (in °C) in Ljubljana, drawn from a seeded generator around the city's published 1991–2020 monthly climate normals, with larger day-to-day swings in winter. The question is which months bring freezing days. Twelve labeled swarms read best top to bottom, so the swarms are horizontal and the data is reversed to put January on top. A band shades the temperatures below zero, so the frost days are the points inside it, and `emphasis_rule` highlights the months whose coldest day fell below freezing, through the `"min"` summary. ``` SwarmPlot( # reversed, so January ends up at the top data=daily_temperatures[::-1], orientation=ORIENTATION.HORIZONTAL, style={"plot_swarm_size": 8}, # the months whose coldest day was below zero emphasis_rule={"below": 0, "by": "min"}, # shade the freezing temperatures vspans={ "xmax": 0, "label": "below freezing", "style": {"plot_vspan_color": "#4c72b0", "plot_vspan_alpha": 0.12}, }, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", ylabel="Month", figsize=FIG_SIZE.FULL_TALL, show_grid=SHOW_GRID.X, show_legend=True, legend={"location": LEGEND_LOCATION.LOWER_RIGHT}, ).show() ``` ### Example 3: Is the Best Model Better, or Just Luckier? (Box Overlay, Emphasis and a Note) `benchmark` holds the illustrative test accuracy of five models, each trained with 10 random seeds, drawn from a seeded generator. Ten runs per model is a small sample, and the question is whether the model with the best single run is also the most reliable. A box plot alone hides the runs; the swarm on top shows every one, so a tight cluster reads apart from a wide one. [Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays the two, the same `emphasis` roles on both figures highlight the two leading models and mute the rest, and [Annotate](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Annotate) adds a note to the finished panel, pointing at the single best run. ``` from datachart.utils import Annotate LEADERS = ["Deep", "Deep + aug."] roles = [EMPHASIS.HIGHLIGHT if model in LEADERS else EMPHASIS.BACKGROUND for model in MODELS] panel = Panel( [ BoxPlot(data=benchmark, show_outliers=False, emphasis=roles), # the same roles align with the same models in both figures SwarmPlot(data=benchmark, emphasis=roles), ], title=f"Test accuracy across {N_SEEDS} seeds", xlabel="Model", ylabel_left="Test accuracy", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ) # point at the single best run; groups sit at positions 0, 1, 2, ... position = list(MODELS).index(BEST_RUN["label"]) Annotate( panel, texts={ "text": f"best single run ({BEST_RUN['value']:.3f}),\nbut the widest spread", "x": 0.2, "y": 0.9, "coords": "axes", "target": (position, BEST_RUN["value"]), }, ).show() ``` # Raincloud Plot A raincloud plot shows a distribution three ways at once: the **cloud** (a half violin) gives its shape, the **rain** (one point per observation) shows every value, and the **box** sums it up with the median and quartiles. The reader gets all three without the author having to choose one. This guide shows how to create raincloud plots with the [datachart.charts.RaincloudPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.charts.RaincloudPlot) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-raincloud-plot), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import RaincloudPlot ``` ## Basics The examples in this guide share one dataset: the body mass of the 342 penguins with a recorded mass in the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset (CC0), three species measured on the islands of the Palmer Archipelago in Antarctica. The data lives in a hidden cell. `chart_data` holds the body mass (in g) of every penguin, labeled with its species; `flipper_data` holds the flipper length (in mm) the same way; `PENGUINS` keeps the sex of every penguin, which the later sections reuse. The data has a story a single summary would hide: Gentoo penguins are far heavier than the other two species, and within each species the males are heavier than the females, so each distribution is really two. Each data point is a dictionary with a `label` (the group) and a `value`. The points that share a `label` form one raincloud, so three species give three rainclouds: ``` chart_data[:3] ``` **Basic example.** Only the `data` argument is required. Each group draws its three parts side by side at one category position: the rain on the left, packed outward so no two penguins overlap, the box next to it, and the cloud on the right. The groups follow the order in which their labels first appear in the data (there is no sorting parameter, so reorder the data to reorder the groups), and each group takes one palette color for all three parts. ``` RaincloudPlot( # add the data to the chart data=chart_data ).show() ``` ## Customizing the Raincloud Plot Every customization is either a keyword argument of `RaincloudPlot` or an attribute of its `style` dictionary: the `plot_violin_*` attributes style the cloud, the `plot_swarm_*` attributes the rain, and the `plot_box_*` attributes the box. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | -------------------------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range or format the ticks | `ymin`, `ymax`, `yticks`, `yticks_format` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | change the cloud, rain, or box style | `style={"plot_violin_alpha": ..., "plot_swarm_size": ..., "plot_box_linewidth": ...}` | [Cloud, rain and box style](#cloud-rain-and-box-style) | | smooth or sharpen the cloud | `bandwidth` | [Cloud bandwidth](#cloud-bandwidth) | | jitter the rain instead of packing it | `mode`, `jitter` | [Rain modes](#rain-modes) | | hide the box outliers | `show_outliers` | [Box outliers](#box-outliers) | | print each group's median, min and max | `show_values`, `value_format` | [Value labels](#value-labels) | | draw the rainclouds horizontally | `orientation` | [Horizontal rainclouds](#horizontal-rainclouds) | | highlight some groups, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a threshold or shade a range | `hlines`, `vlines`, `hspans`, `vspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | use dates as group labels | `date` objects as `label`, `xticks_format` | [Date labels](#date-labels) | | list the groups in a titled legend | `show_legend`, `legend` | [Legend](#legend) | | draw several datasets side by side | `data` as a list of lists, `subtitle`, `sharey`, `max_cols` | [Multiple Raincloud Plots](#multiple-raincloud-plots) | | overlay or arrange the raincloud with other charts | `Panel`, `Grid` | [Composing with Panel and Grid](#composing-with-panel-and-grid) | | use a logarithmic value axis | `scaley` | [Logarithmic scale](#logarithmic-scale) | | plot data with other key names | `label`, `value` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `mode` | [`SWARM_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SWARM_MODE) | | `bandwidth` | [`BANDWIDTH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.RaincloudStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.typings.RaincloudStyleAttrs) type; the full list of parameters is in the [datachart.charts.RaincloudPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.charts.RaincloudPlot) reference. ### Title, axis labels and ticks Without a title and axis labels the reader cannot tell what the rainclouds measure or in which unit; `title`, `xlabel` and `ylabel` say it. `ymin` and `ymax` fix the value axis, which matters when several charts must be read against each other, and `yticks` with `yticks_format` place and format the ticks: a thousands separator makes gram values easier to read. ``` RaincloudPlot( data=chart_data, # add the title title="Body mass of Palmer penguins", # add the x and y axis labels xlabel="Species", ylabel="Body mass (g)", # fix the value axis and format its ticks ymin=2500, ymax=6500, yticks=[2500, 3500, 4500, 5500, 6500], yticks_format="{x:,.0f}", ).show() ``` ### Figure size and grid A few groups side by side read best in a wide, short figure. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. Grid lines let the eye carry a median across to the axis; `show_grid` draws them with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member, and `SHOW_GRID.Y` keeps them on the value axis, where they help. `aspect_ratio` ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)) fixes the ratio of the axes; a raincloud rarely needs it. ``` from datachart.constants import FIG_SIZE, SHOW_GRID RaincloudPlot( data=chart_data, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the value axis only show_grid=SHOW_GRID.Y, ).show() ``` ### Cloud, rain and box style Three parts in one color can compete for attention. The `style` dictionary tunes each part: the `plot_violin_*` attributes the cloud ([ViolinStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinStyleAttrs)), the `plot_swarm_*` attributes the rain ([SwarmStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmStyleAttrs)), and the `plot_box_*` attributes the box ([BoxStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxStyleAttrs)); any attribute left out keeps the value of the active theme. A lighter cloud, smaller and fainter rain, and a bolder box put the summary in front and leave the shape and the raw values as context. The box takes the group color as its fill and the theme's font color for its edges, median, whiskers and caps. ``` RaincloudPlot( data=chart_data, style={ # a lighter, wider cloud "plot_violin_alpha": 0.4, "plot_violin_width": 0.9, # smaller, fainter rain "plot_swarm_size": 4, "plot_swarm_alpha": 0.5, # a bolder box "plot_box_linewidth": 1.5, "plot_box_median_linewidth": 2.5, }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Cloud bandwidth The cloud is a kernel density estimate, and its smoothing decides what shape the reader sees: too smooth, and a distribution with two peaks looks like one. `bandwidth` takes a rule from [BANDWIDTH](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) (`BANDWIDTH.SCOTT`, the default, or `BANDWIDTH.SILVERMAN`) or a scalar factor, where smaller values follow the data more closely. Each species mixes lighter females and heavier males, and a narrow bandwidth shows it: the Gentoo cloud splits into two humps. ``` RaincloudPlot( data=chart_data, # a narrow bandwidth follows the data closely bandwidth=0.3, title="Body mass of Palmer penguins, a narrow bandwidth", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Rain modes A swarm packs the rain so no two points overlap, which is exact but costs width and time as the groups grow. `mode` chooses how the rain spreads, with a [SWARM_MODE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SWARM_MODE) member: `SWARM_MODE.SWARM` (the default) packs the points outward from the box, `SWARM_MODE.STRIP` scatters them at random across the rain's width. `jitter` sets the width of that band as a fraction of the category width (0.4, the default, fills the rain's cell); the jitter is seeded, so the same data draws the same chart. The strip mode suits many thousands of points, where a swarm would fill its whole width anyway. ``` from datachart.constants import SWARM_MODE RaincloudPlot( data=chart_data, # scatter the rain instead of packing it mode=SWARM_MODE.STRIP, # in a band half as wide as the rain's cell jitter=0.2, title="Body mass of Palmer penguins, strip rain", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Box outliers The box marks the values beyond 1.5 times the interquartile range as outliers, but in a raincloud those penguins are already in the rain, so the markers repeat them (the two Chinstrap circles above). `show_outliers=False` hides them and keeps the box a plain summary. ``` RaincloudPlot( data=chart_data, # the rain already shows every value show_outliers=False, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Value labels When the numbers matter, `show_values` prints each group's median beside its box, and its minimum and maximum beside the rain points holding them; the cloud carries no labels. `value_format` formats them with a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"` style string, and the `plot_value_*` style attributes ([ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)) set their font. The labels show that the heaviest Adelie and Chinstrap penguins weigh about as much as a typical Gentoo. ``` from datachart.constants import VALUE_FORMAT RaincloudPlot( data=chart_data, # print the median, min and max of every group show_values=True, value_format=VALUE_FORMAT.THOUSANDS, style={"plot_value_fontsize": 8}, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_outliers=False, ).show() ``` ### Horizontal rainclouds Long group names and a value axis that reads left to right both call for horizontal rainclouds. `orientation=ORIENTATION.HORIZONTAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) puts the groups on the y-axis, with the cloud above and the box and rain below it; the axis labels and the grid swap with the orientation, and the first group sits at the bottom. ``` from datachart.constants import ORIENTATION RaincloudPlot( data=chart_data, # draw the rainclouds horizontally orientation=ORIENTATION.HORIZONTAL, title="Body mass of Palmer penguins", # the axis labels swap with the orientation xlabel="Body mass (g)", ylabel="Species", figsize=FIG_SIZE.FULL_MEDIUM, # and so does the grid show_grid=SHOW_GRID.X, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. `emphasis` takes one role per group, aligned with the group labels in the order they first appear (here Adelie, Chinstrap, Gentoo), and applies to the cloud, the rain and the box together: `"highlight"` bolds the edges, `"background"` mutes the group, `None` leaves it as it is, and a single value applies to every group. The roles are the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants; the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across chart types and themes. Asking only about Gentoo penguins turns the other two species into context: ``` from datachart.constants import EMPHASIS RaincloudPlot( data=chart_data, # one role per group label: Adelie, Chinstrap, Gentoo emphasis=[EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], title="Gentoo penguins against the rest", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` `emphasis_rule` picks the groups from the data instead. It is a one-key rule, `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), `{"top": n}` or `{"bottom": n}`, read against a summary of each group: the median by default, the line the box already draws, or the `"mean"`, `"min"`, `"max"` or `"sum"` with a `"by"` key. An explicit `emphasis` role wins over the rule. Which species have penguins lighter than 3 kg? Reading the rule against each group's minimum answers it: Adelie and Chinstrap do, Gentoo does not. ``` RaincloudPlot( data=chart_data, # highlight the groups whose lightest penguin is under 3,000 g emphasis_rule={"below": 3000, "by": "min"}, title="Species with a penguin under 3 kg", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Reference lines and bands Reference lines and bands put the rainclouds in context. `hlines` draws a horizontal line at a value, such as the mean of all penguins, and `vlines` a vertical one; group positions along the category axis start at `0`, as for bars, so a half-integer sits between two groups. `hspans` and `vspans` shade a range instead of marking a value. Each takes a dictionary or a list of them, with the position and a `style`; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs). A band of one standard deviation around the overall mean shows that a typical Adelie or Chinstrap penguin falls inside it, while most Gentoo penguins sit above it. ``` from datachart.constants import LINE_STYLE masses = [point["value"] for point in chart_data] mean_mass = sum(masses) / len(masses) std_mass = (sum((mass - mean_mass) ** 2 for mass in masses) / len(masses)) ** 0.5 RaincloudPlot( data=chart_data, # shade one standard deviation around the mean of all penguins hspans={ "ymin": mean_mass - std_mass, "ymax": mean_mass + std_mass, "style": {"plot_hspan_color": "#d62728"}, }, # and mark the mean itself hlines={ "y": mean_mass, "style": {"plot_hline_color": "#d62728", "plot_hline_style": LINE_STYLE.DASHED}, }, title="Body mass against the overall mean (dashed) ± 1 SD (band)", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Text annotations Where a reference line marks a value, a note explains it. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (group position, value) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below points at the heaviest penguin of the dataset, a 6.3 kg Gentoo male, whose rain point sits just left of the Gentoo position `2`. ``` heaviest = max(point["value"] for point in chart_data) RaincloudPlot( data=chart_data, # a note pinned to the axes, pointing at the heaviest penguin texts={ "text": f"heaviest penguin: {heaviest:,} g", "x": 0.36, "y": 0.93, "coords": "axes", "target": (1.87, heaviest), }, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ### Date labels Groups are often periods: months, weeks, editions of a survey. A `label` that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) keeps its categorical position but prints through `xticks_format`, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. `daily_temperatures`, defined in a hidden cell, holds twenty illustrative daily mean temperatures per month, drawn around the 1991-2020 monthly normals of Ljubljana and labeled by the first day of each month. The rainclouds show the seasonal cycle and that winter days vary more than summer days. ``` from datachart.constants import DATE_FORMAT RaincloudPlot( data=daily_temperatures, title="Daily mean temperature by month, Ljubljana (illustrative)", xlabel="Month", ylabel="Temperature (°C)", # print the date labels as year and month xticks_format=DATE_FORMAT.YEAR_MONTH, xtickrotate=45, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_outliers=False, ).show() ``` ### Legend The category axis already names the groups, so a legend is mostly for reference marks or for charts whose tick labels are hidden. `show_legend` lists the groups; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The Adelie and Chinstrap rainclouds fill the lower half of the axes, so a titled legend goes above the plot, in one row. ``` from datachart.constants import LEGEND_LOCATION RaincloudPlot( data=chart_data, title="Body mass of Palmer penguins", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, show_legend=True, # a titled, one-row legend above the axes legend={"title": "Species", "location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 3}, ).show() ``` ## Multiple Raincloud Plots Three parts per group leave no room to overlay a second dataset at the same positions, so a list of lists in `data` draws each inner list in its own subplot. `subtitle` titles the subplots; `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the subplots per row. `sharey=True` puts the subplots on one value axis, so a raincloud in one can be read against a raincloud in the next, and `sharex=True` does the same for the category axis. A per-chart attribute (`style`, `emphasis`, `hlines`, …) can be a list aligned with `data`. Splitting the penguins by sex answers the question the [bandwidth](#cloud-bandwidth) example raised: each species' two humps are its females and its males. `body_mass_by_sex` holds the 165 female and the 168 male penguins, one list per sex (the 9 penguins without a recorded sex are left out). ``` SEXES = ["Female", "Male"] # one list of data points per sex body_mass_by_sex = [ [ {"label": penguin["species"], "value": mass} for penguin in PENGUINS if penguin["sex"] == sex for mass in penguin["body_mass"] ] for sex in SEXES ] ``` ``` RaincloudPlot( # one chart per sex data=body_mass_by_sex, # one subplot title per chart subtitle=SEXES, title="Body mass of Palmer penguins by sex", xlabel="Species", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_outliers=False, # the same mass axis for both charts, with room for the heaviest males sharey=True, ymin=2500, ymax=6500, ).show() ``` Each sex now has a single-humped cloud, and a shared axis shows that a male of one species is heavier than a female of the same species. A single value of `emphasis` applies to every group of every chart, and a list of lists gives each chart its own roles; the example keeps the Gentoo groups in front in both subplots, and stacks the subplots in a column with `max_cols=1`: ``` RaincloudPlot( data=body_mass_by_sex, subtitle=SEXES, # the same roles in both charts: Adelie, Chinstrap, Gentoo emphasis=[ [EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], [EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], ], title="Gentoo penguins by sex", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_TALL, show_grid=SHOW_GRID.Y, show_outliers=False, # one chart per row, on one mass axis max_cols=1, sharex=True, sharey=True, ymin=2500, ymax=6500, ).show() ``` ### Composing with Panel and Grid [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) overlays figures on shared axes, one raincloud dataset per panel. The groups keep their positions (`1`, `2`, `3`, …), so a chart drawn on the same numeric positions lines up with them. A [LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.charts.LineChart) of the mean body mass per species traces the step from the small species to the large one, and shows where the mean sits against the median line of each box. ``` from datachart.charts import LineChart from datachart.utils import Panel mean_by_species = [ { # group positions start at 0 "x": position, "y": sum(p["value"] for p in chart_data if p["label"] == species) / sum(1 for p in chart_data if p["label"] == species), } for position, species in enumerate(SPECIES) ] Panel( [ RaincloudPlot(data=chart_data, show_outliers=False), # the means, one per group position LineChart(data=mean_by_species, style={"plot_line_color": "#333333"}), ], title="Body mass of Palmer penguins, with the species means", xlabel="Species", ylabel_left="Body mass (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) arranges figures side by side, each in its own cell. Is the size gap between the species a matter of weight alone? A raincloud of the flipper lengths next to the body mass one says no: Gentoo penguins have longer flippers too, and the Chinstrap flippers sit between the other two species, a difference the body mass hides. ``` from datachart.utils import Grid Grid( [ RaincloudPlot(data=chart_data, title="Body mass (g)", show_grid=SHOW_GRID.Y), RaincloudPlot(data=flipper_data, title="Flipper length (mm)", show_grid=SHOW_GRID.Y), ], title="Palmer penguins by species", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ## Additional Features ### Logarithmic scale Some measurements are strongly right-skewed: most values are small and a few are many times larger, so on a linear axis the bulk of each group is squeezed against the bottom. `scaley` takes a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member, and on `SCALE.LOG` the cloud, the rain and the box all follow the scaled axis. Penguin masses are not skewed, so this example switches dataset: `load_times`, defined in a hidden cell, holds 150 illustrative page load times (in ms) for each of three page types, drawn from log-normal distributions. On the log scale the three clouds become symmetric and readable, and the tails stay in view. ``` from datachart.constants import SCALE for scale in [SCALE.LINEAR, SCALE.LOG]: RaincloudPlot( data=load_times, title=f"Page load time on the '{scale}' scale", xlabel="Page type", ylabel="Load time (ms)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_outliers=False, # the scale of the value axis scaley=scale, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `label` and `value` keys, and renaming every record just to plot it is a chore. Instead, tell `RaincloudPlot` which keys to read with the `label` and `value` arguments. `flipper_records` stores the flipper lengths the way a CSV export would, one record per penguin with a `species` and a `flipper_mm` key: ``` flipper_records = [ {"species": penguin["species"], "sex": penguin["sex"], "flipper_mm": length} for penguin in PENGUINS for length in penguin["flipper_length"] ] flipper_records[:2] ``` ``` RaincloudPlot( data=flipper_records, # the keys holding the label and the value label="species", value="flipper_mm", title="Flipper length of Palmer penguins", xlabel="Species", ylabel="Flipper length (mm)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, ).show() ``` ## Real-World Examples The examples below put the features above to work on realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Does Conflict Slow People Down? (Strip Rain, a Log Scale, Median Labels, and a Baseline) The raincloud plot was made for experimental results like these. `stroop_trials` holds illustrative reaction times (in ms) from a Stroop task, 300 trials per condition: the colour word matches its ink (*congruent*), is a neutral string (*neutral*), or names another colour (*incongruent*). The times are drawn from an ex-Gaussian distribution, the usual model of reaction times: a normal bulk plus an exponential tail of slow responses. With 300 trials per group the rain is drawn as a strip, the skewed times go on a log axis, the median labels give each condition's typical time, and a dashed line at the congruent median is the baseline the other two conditions are read against. The incongruent median sits about 150 ms above it, and its tail of slow responses is the longest. ``` RaincloudPlot( data=stroop_trials, style={"plot_swarm_size": 3, "plot_swarm_alpha": 0.5, "plot_value_fontsize": 8}, # 300 trials per condition: scatter instead of packing mode=SWARM_MODE.STRIP, # reaction times are right-skewed scaley=SCALE.LOG, yticks=[400, 600, 800, 1000, 1500], yticks_format=VALUE_FORMAT.INTEGER, # print the median, min and max show_values=True, value_format=VALUE_FORMAT.INTEGER, show_outliers=False, # the congruent median as the baseline hlines={ "y": CONGRUENT_MEDIAN, "style": {"plot_hline_color": "#555555", "plot_hline_style": LINE_STYLE.DASHED}, }, title="Stroop task reaction times (illustrative)", xlabel="Condition", ylabel="Reaction time (ms)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ).show() ``` ### Example 2: Did the Course Lift the Scores? (Subplots, a Pass Band, Custom Keys, and a Rule) `course_scores` holds illustrative test scores (0 to 100) of two sections of the same statistics course, 60 students each, before and after the course. The records use the keys a gradebook export would, `test` and `score`, so the `label` and `value` arguments read them directly. The pass mark is 50: a band shades the failing range, and `emphasis_rule` highlights the tests whose median passes. The shared axis shows that both sections improved, and the rain shows what the medians hide: the evening section still leaves a group of students below the pass mark. ``` RaincloudPlot( data=course_scores, # the gradebook's key names label="test", value="score", subtitle=[f"{section} section" for section in SECTIONS], # shade the failing range in both charts hspans={"ymin": 0, "ymax": PASS_MARK, "label": "fail", "style": {"plot_hspan_color": "#d62728"}}, # the tests whose median passes emphasis_rule={"above": PASS_MARK}, show_outliers=False, title="Test scores before and after the course (illustrative)", xlabel="Test", ylabel="Score", # headroom for the perfect scores ymin=0, ymax=105, yticks=[0, 25, 50, 75, 100], figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, sharey=True, ).show() ``` ### Example 3: Which Courier Keeps the 48-Hour Promise? (Horizontal Rainclouds, a Promise Line, a Note, and a Grid) An online shop promises delivery within 48 hours and uses four couriers. `delivery_hours` holds illustrative delivery times (in hours) of 120 parcels per courier, and `late_share` the share of each courier's parcels that missed the promise. Horizontal rainclouds give the hours a left-to-right axis, a line marks the promise, and a note points at the courier with the longest tail. The median alone would rank the couriers wrongly: Swift has the fastest typical delivery but the most late parcels. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) sets a bar chart of the late share beside the rainclouds, in the same order, so the two views of each courier sit on one row. ``` from datachart.charts import BarChart rainclouds = RaincloudPlot( data=delivery_hours, orientation=ORIENTATION.HORIZONTAL, style={"plot_swarm_size": 4}, show_outliers=False, # the delivery promise vlines={ "x": PROMISE, "style": {"plot_vline_color": "#d62728", "plot_vline_style": LINE_STYLE.DASHED}, }, texts=[ # name the promise line {"text": "promise", "x": 0.36, "y": 0.97, "coords": "axes"}, # point at Swift's long tail { "text": "Swift: fastest median,\nlongest tail", "x": 0.62, "y": 0.72, "coords": "axes", "target": (80, 2), }, ], title="Delivery time", xlabel="Hours", xmin=0, show_grid=SHOW_GRID.X, ) late = BarChart( data=late_share, orientation=ORIENTATION.HORIZONTAL, # Swift, the courier the note is about emphasis_rule={"top": 1}, show_values=True, value_format=VALUE_FORMAT.PERCENT_INT, title="Parcels late", xlabel="Share of parcels", xmin=0, xmax=0.2, xticks_format=VALUE_FORMAT.PERCENT_INT, show_grid=SHOW_GRID.X, ) Grid( [[rainclouds, late]], title="Couriers against the 48-hour promise (illustrative)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Ridgeline Plot A ridgeline plot stacks many distributions in rows, one ridge per group, so the eye can follow how a shape shifts across an ordered variable: months, hours, releases, epochs. It answers *where do the values sit, how spread are they, and how does that change from row to row*, for more groups than a grid of histograms could hold. This guide shows how to create ridgeline plots with the [datachart.charts.RidgelinePlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.charts.RidgelinePlot) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-ridgeline-plot), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import RidgelinePlot ``` ## Basics The examples in this guide share one dataset: a year of daily mean temperatures in Ljubljana. The monthly means are the station's published 1991–2020 climate normals, rounded; the thirty daily values of each month are illustrative, drawn with a fixed seed around that month's normal, with a wider day-to-day spread in winter than in summer. The data lives in a hidden cell. `temperatures` holds one data point per day, labeled with its month, so the twelve months give twelve ridges. The year has a story in it: a warm, settled summer, and winters where one day can be mild and the next freezing. Each data point is a dictionary with a `label` (the row) and a numeric `value`; the points that share a label form one ridge: ``` temperatures[:3] ``` **Basic example.** Only the `data` argument is required. Every label draws one ridge, the smoothed density of its values, on its own row. The rows follow the order the labels first appear in the data, the first at the top, and each ridge rises from its row's tick into the row above it, so the year reads top to bottom and the summer bulge shows at a glance: ``` RidgelinePlot( # add the data to the chart data=temperatures ).show() ``` ## Customizing the Ridgeline Plot Every customization is either a keyword argument of `RidgelinePlot` or a `plot_ridgeline_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | place and format the value ticks | `xticks`, `xticks_format` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure or show the grid | `figsize`, `show_grid` | [Figure size and grid](#figure-size-and-grid) | | change the ridge fill, outline, or alpha | `style={"plot_ridgeline_color": ...}` | [Ridge style](#ridge-style) | | make the rows overlap more or less | `overlap` | [Row overlap](#row-overlap) | | compare the ridge heights, not only shapes | `normalize` | [Ridge scale](#ridge-scale) | | mark the median or the quartiles | `inner` | [Inner marks](#inner-marks) | | draw only the outlines or only the fills | `fill`, `show_outline` | [Fill and outline](#fill-and-outline) | | order the rows by their median | `sort` | [Row order](#row-order) | | smooth or sharpen the ridges | `bandwidth` | [Bandwidth](#bandwidth) | | fix the value range of the ridges | `xmin`, `xmax` | [Value range](#value-range) | | stack the rows along the x-axis | `orientation` | [Orientation](#orientation) | | highlight some rows, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a threshold or shade a range | `vlines`, `vspans`, `hlines`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | title and place the legend | `show_legend`, `legend` | [Legend](#legend) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw several ridgelines side by side | `data` as a list of lists, `subtitle`, `sharex`, `max_cols` | [Multiple Ridgeline Plots](#multiple-ridgeline-plots) | | overlay the ridges with another chart | `Panel` | [Composing with Panel and Grid](#composing-with-panel-and-grid) | | put the ridgeline next to other charts | `Grid` | [Composing with Panel and Grid](#composing-with-panel-and-grid) | | show values that span orders of magnitude | `scaley`, `xticks`, `xticklabels` | [Axis scales](#axis-scales) | | plot data with other key names | `label`, `value` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `normalize` | [`RIDGELINE_SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RIDGELINE_SCALE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `bandwidth` | [`BANDWIDTH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) | | `inner` | [`VIOLIN_INNER`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VIOLIN_INNER) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | The full list of style attributes is in the [datachart.typings.RidgelineStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.typings.RidgelineStyleAttrs) type; the full list of parameters is in the [datachart.charts.RidgelinePlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.charts.RidgelinePlot) reference. ### Title, axis labels and ticks Without a title and axis labels the reader cannot tell that the ridges are temperatures, or that the rows are months; `title`, `xlabel` and `ylabel` say it. With the default orientation the value axis is the x-axis and the rows stack along the y-axis. `xticks` places the value ticks where a reader expects them (every 10 °C here), and `xticks_format` formats their labels with any `"{x:.0f}"` style string, so the unit can travel with the numbers. ``` RidgelinePlot( data=temperatures, # add the title title="Daily mean temperature in Ljubljana", # add the x and y axis labels xlabel="Temperature", ylabel="Month", # value ticks every 10 °C, with the unit on each xticks=[-20, -10, 0, 10, 20, 30, 40], xticks_format="{x:.0f} °C", ).show() ``` ### Figure size and grid Twelve rows need height, and a ridgeline reads best on a figure taller than it is by default. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. Vertical grid lines let the eye carry a ridge's peak down to the value axis; `show_grid=SHOW_GRID.X` ([SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID)) draws them along the value axis only, since the rows already have their baselines. ``` from datachart.constants import FIG_SIZE, SHOW_GRID RidgelinePlot( data=temperatures, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", ylabel="Month", # a full-width, medium-height figure figsize=FIG_SIZE.FULL_MEDIUM, # grid lines along the value axis only show_grid=SHOW_GRID.X, ).show() ``` ### Ridge style The default look, a translucent fill with an outline of the same color, lets the rows behind show through. The `style` dictionary changes it: the fill color and alpha, the outline color and width, the default overlap, and the color and width of the inner marks; the attributes are listed in [datachart.typings.RidgelineStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.typings.RidgelineStyleAttrs), and any attribute left out keeps the value of the active theme. An opaque fill with a white outline is the classic *joy plot* look: every ridge cuts cleanly into the row behind it, which suits many rows that overlap a lot. ``` RidgelinePlot( data=temperatures, # the classic look: an opaque fill cut by a white outline style={ "plot_ridgeline_color": "#2E86AB", "plot_ridgeline_alpha": 1.0, "plot_ridgeline_edgecolor": "#FFFFFF", "plot_ridgeline_linewidth": 1.5, }, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Row overlap Ridges that overlap save space and make the shift from row to row easy to follow; too much overlap hides the rows behind. `overlap` sets how far each ridge rises into the row above, between 0 and 1: a ridge's peak stands `1 + overlap` rows above its tick, so `0` makes the peaks just touch the next row and `1` sends them a whole row further. Without it the theme's `plot_ridgeline_overlap` applies (`0.5` in the predefined themes); values outside `[0, 1]` raise a `ValueError`. Side by side, the flat version is easier to read row by row, and the overlapping one shows the seasonal sweep better. ``` from datachart.utils import Grid Grid( [ # the peaks just touch the next row RidgelinePlot(data=temperatures, overlap=0.0, title="overlap=0"), # the peaks rise a whole row further RidgelinePlot(data=temperatures, overlap=1.0, title="overlap=1"), ], title="Daily mean temperature in Ljubljana", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Ridge scale Summer days in Ljubljana vary less than winter days, so a summer month's values are packed more tightly. The default scale hides that: every ridge is stretched to the same peak height, so the chart compares shapes and positions, the honest choice when the question is *where* each row sits. `normalize` picks the scale from [RIDGELINE_SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RIDGELINE_SCALE): `RIDGELINE_SCALE.PER_ROW` (the default) or `RIDGELINE_SCALE.COMMON`, where every ridge shares one density scale, the tallest reaches the peak height and the rest stay in proportion. The common scale is the honest choice when the question is *how concentrated* each row is: the narrow summer months stand tall and the variable winter months flatten. Neither scale shows how many values a row holds, since every ridge is a density with the same area; when the row sizes differ a lot, say so in the labels. ``` from datachart.constants import RIDGELINE_SCALE RidgelinePlot( data=temperatures, # one density scale for every ridge normalize=RIDGELINE_SCALE.COMMON, title="Daily mean temperature in Ljubljana, on a common scale", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Inner marks A ridge's peak is not its median, and a skewed row can mislead the eye. `inner` draws summary marks inside each ridge, from its baseline up to the curve: `VIOLIN_INNER.MEDIAN` draws one solid line at the median, `VIOLIN_INNER.QUARTILES` a dashed median and dotted first and third quartiles, and `None` (the default) no marks ([VIOLIN_INNER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VIOLIN_INNER); `"box"` is not supported and raises a `ValueError`). The quartiles show the middle half of each month's days, wide in winter and narrow in summer. `plot_ridgeline_inner_color` and `plot_ridgeline_inner_linewidth` style the marks. ``` from datachart.constants import VIOLIN_INNER RidgelinePlot( data=temperatures, # mark the median and quartiles of every month inner=VIOLIN_INNER.QUARTILES, title="Daily mean temperature in Ljubljana, with quartiles", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Fill and outline Filled ridges hide part of the rows behind them, and with a high overlap that can hide the very shift the chart is about. `fill` and `show_outline` turn the fill and the density curve on and off independently; both are on by default. Outlines alone keep every row visible through the overlap, like a stack of contour lines; turning both off raises a `ValueError`, since nothing would be drawn. ``` RidgelinePlot( data=temperatures, # draw only the density curves fill=False, overlap=1.0, title="Daily mean temperature in Ljubljana, outlines only", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Row order When the rows have a natural order, like months, keep it: the ridgeline is about the shift along that order. When they do not, or when the question is a ranking, `sort` orders the rows by their median ([SORT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT)): `SORT.ASCENDING` puts the smallest median at the top, `SORT.DESCENDING` the largest, and `None` keeps the input order; rows with the same median keep their input order. Sorted from warmest to coldest, the months fall into pairs on either side of midsummer. ``` from datachart.constants import SORT RidgelinePlot( data=temperatures, # the warmest median at the top sort=SORT.DESCENDING, inner=VIOLIN_INNER.MEDIAN, title="Months from warmest to coldest", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Bandwidth Each ridge is a kernel density estimate, a smoothed version of the values, and how much it smooths decides what the reader sees: too little and thirty days turn into noise, too much and a real second peak disappears. `bandwidth` takes a rule from [BANDWIDTH](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) (`BANDWIDTH.SCOTT`, the default, or `BANDWIDTH.SILVERMAN`) or a scalar factor, where smaller values follow the data more closely and larger ones smooth it more. With only thirty values per row, a narrow bandwidth shows bumps that are sampling noise, not weather; the rules are the safer choice. ``` from datachart.constants import BANDWIDTH Grid( [ # a narrow bandwidth follows every day RidgelinePlot(data=temperatures, bandwidth=0.25, title="bandwidth=0.25"), # Silverman's rule RidgelinePlot(data=temperatures, bandwidth=BANDWIDTH.SILVERMAN, title="bandwidth=SILVERMAN"), ], title="Daily mean temperature in Ljubljana", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Value range All ridges are evaluated on one shared grid of values, so the curves line up point for point. By default the grid spans every row's values, extended a little past the extremes so the tails fade out. When a chart must match another one, or a range has a meaning of its own, `xmin` and `xmax` (`ymin` and `ymax` when the rows run vertically) fix both the axis and the grid, so the ridges stop exactly at the axis limits. Every day of the year lies between -10 and 30 °C, a round frame for a temperate climate; the smoothing spreads the winter ridges a little past the coldest day, and the limit cuts those tails. ``` RidgelinePlot( data=temperatures, # the ridges and the axis both span -10 to 30 °C xmin=-10, xmax=30, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Orientation Temperature is a height on a thermometer, and some readers expect it on a vertical axis. `orientation=ORIENTATION.VERTICAL` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) puts the values on the y-axis and runs the rows along the x-axis, the first on the left, each ridge rising to the right from its tick; the axis labels swap with it. `ORIENTATION.HORIZONTAL` is the default. The vertical layout suits a wide figure with short row labels; with long labels, stay horizontal. ``` from datachart.constants import ORIENTATION RidgelinePlot( data=temperatures, # the rows run left to right orientation=ORIENTATION.VERTICAL, title="Daily mean temperature in Ljubljana", # the axis labels swap with the orientation xlabel="Month", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. `emphasis` takes one role per row, aligned with the row labels in the order they first appear in the data (here January to December), whatever the `sort`: `"highlight"` bolds a ridge's outline, `"background"` mutes the ridge and its inner marks into the theme's muted color, and `None` leaves it as it is; a single value applies to every row. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across chart types and themes. Muting every month but July points at the warmest month: ``` from datachart.constants import EMPHASIS RidgelinePlot( data=temperatures, # one role per month, January first emphasis=[EMPHASIS.BACKGROUND] * 6 + [EMPHASIS.HIGHLIGHT] + [EMPHASIS.BACKGROUND] * 5, inner=VIOLIN_INNER.MEDIAN, title="July, the warmest month", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `emphasis_rule` picks the rows from the data instead of listing them by hand. It is a one-key rule: `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), `{"top": n}` or `{"bottom": n}`, read against a summary of each row, the median by default; a `"by"` key picks `"mean"`, `"min"`, `"max"` or `"sum"` instead. The rows that match are highlighted and the rest muted, and an explicit `emphasis` role wins over the rule. Reading the rule against each month's coldest day (`"by": "min"`) picks out every month with at least one freezing day: ``` RidgelinePlot( data=temperatures, # highlight the months whose coldest day was below freezing emphasis_rule={"below": 0, "by": "min"}, title="Months with at least one freezing day", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Reference lines and bands A threshold turns a distribution into an answer: how much of each month lies below freezing, or inside a comfortable range. With the default orientation the value axis is the x-axis, so `vlines` draws a vertical line at a value and `vspans` shades a range of values; `hlines` and `hspans` work along the row axis, where the rows sit at positions `1`, `2`, `3`, … from the top. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs) and [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs). The example marks the freezing point with a dashed line and shades 18 to 24 °C, a comfortable range for a day outdoors. ``` from datachart.constants import LINE_STYLE RidgelinePlot( data=temperatures, # a dashed line at the freezing point vlines={ "x": 0, "label": "freezing", "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DASHED}, }, # shade the comfortable range vspans={"xmin": 18, "xmax": 24, "label": "comfortable"}, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Legend The ridges add no legend entries, since their labels already sit on the row axis; the legend is for the reference lines and bands. `show_legend` lists them, and `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols` and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). The ridges fill the plot area, so the legend goes outside it, to the right. ``` from datachart.constants import LEGEND_LOCATION RidgelinePlot( data=temperatures, vlines={ "x": 0, "label": "freezing", "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DASHED}, }, vspans={"xmin": 18, "xmax": 24, "label": "comfortable"}, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, # list the reference line and band show_legend=True, # a titled legend outside the axes legend={"title": "Reference", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Text annotations Where a reference line marks a value, a note explains a row. `texts` places text on the chart, with an optional `target` to draw a connector to a point; the position is in data coordinates by default (value, row position) or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis limits. Rows sit at positions `0`, `1`, `2`, … from the top, and a ridge rises toward smaller positions: `(4, -0.4)` is a point inside January's ridge, just above its baseline. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. ``` RidgelinePlot( data=temperatures, # a note pinned to the axes, pointing into January's ridge texts={ "text": "January: the coldest month,\nand the widest day-to-day swing", "x": 0.62, "y": 0.9, "coords": "axes", "target": (4, -0.4), }, title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Multiple Ridgeline Plots To compare two sets of rows, pass a list of lists to `data`: each inner list is one ridgeline, drawn in its own subplot, and the per-chart attributes (`subtitle`, `style`, `emphasis`, `vlines`, …) become lists aligned with it. `subtitle` titles the subplots, while `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the subplots per row. `sharex=True` puts every subplot on one value axis, so a ridge in one subplot compares with a ridge in the next (`sharey` shares the row axis, which only makes sense when the subplots have the same rows); the ridges of every subplot are evaluated over one shared value range either way. Here the year is split into its cold half (October to March) and its warm half (April to September), each in its own color. ``` COLD = ["Oct", "Nov", "Dec", "Jan", "Feb", "Mar"] WARM = ["Apr", "May", "Jun", "Jul", "Aug", "Sep"] # each half in its own month order, the rows follow it halves = [ [point for month in half for point in temperatures if point["label"] == month] for half in (COLD, WARM) ] RidgelinePlot( # one ridgeline per half of the year data=halves, # a title and a color per subplot subtitle=["Cold half", "Warm half"], style=[{"plot_ridgeline_color": "#457b9d"}, {"plot_ridgeline_color": "#e76f51"}], title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, # side by side, on one temperature axis max_cols=2, sharex=True, ).show() ``` ### Composing with Panel and Grid A ridgeline says where a row's values sit; it does not show the values themselves. [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays figures in one coordinate space, with one ridgeline per panel, and a [SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.charts.SwarmPlot) of the same data puts every day under its month's ridge: over ridges, the panel packs the swarm on the side the ridges rise to, so each day sits inside its month's ridge. The swarm must share the horizontal orientation, and a small, dark point keeps the days readable over the fill. The panel keeps the first row at the top for every chart in it, and labels its axes by role: `xlabel` names the category axis and `ylabel_left` the value axis, wherever they are drawn. The [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide covers the rest. ``` from datachart.charts import SwarmPlot from datachart.utils import Panel Panel( [ RidgelinePlot(data=temperatures, overlap=0.2), # one point per day, on its month's row SwarmPlot( data=temperatures, orientation=ORIENTATION.HORIZONTAL, style={"plot_swarm_color": "#2C3E50", "plot_swarm_size": 8}, ), ], title="Daily mean temperature in Ljubljana", # Panel labels the axes by role: the category axis, then the value axis xlabel="Month", ylabel_left="Temperature (°C)", figsize=FIG_SIZE.FULL_TALL, ).show() ``` [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) puts a ridgeline next to other figures, each in a cell of its own; here a horizontal [BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.charts.BoxPlot) of the same months sits beside it, so the reader sees the shape and the summary together. With `sharex` and `sharey` the two charts share both axes: the months line up row for row, and the box plot follows the ridgeline's first-row-at-the-top order. The [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers layouts. ``` from datachart.charts import BoxPlot Grid( [ RidgelinePlot(data=temperatures, title="Ridgeline"), BoxPlot(data=temperatures, orientation=ORIENTATION.HORIZONTAL, title="Box plot"), ], title="Daily mean temperature in Ljubljana", # one temperature axis and one month axis for both charts sharex=True, sharey=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Additional Features ### Axis scales Some values span orders of magnitude, and on a linear axis the small ones collapse into a spike at zero. `scaley` sets the scale of the value axis, whichever direction it runs, with a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member. The ridges, though, are estimated at evenly spaced values, so on a log axis the rows with small values get only a few of them and turn jagged. For data that spans orders of magnitude, estimate the ridges on the logarithm of the values instead, and label the ticks in the original unit with `xticks` and `xticklabels`. `response_times`, defined in a hidden cell, holds illustrative response times of three API endpoints, from a health check of a few milliseconds to a report that takes over a second; on the log scale all three ridges are readable, and the right-skewed response times turn into symmetric bumps. ``` # the ridges are estimated on the log of each value log_response_times = [ {"label": point["label"], "value": np.log10(point["value"])} for point in response_times ] RidgelinePlot( data=log_response_times, # ticks at the powers of ten, labeled in milliseconds xticks=[0, 1, 2, 3, 4], xticklabels=["1", "10", "100", "1,000", "10,000"], title="Response time by endpoint", xlabel="Response time (ms, log scale)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `label` and `value` keys, and renaming every record just to plot it is a chore. Instead, tell `RidgelinePlot` which keys to read with the `label` and `value` arguments. `records` stores the days the way a weather export would, with a `month` and a `celsius` key: ``` records = [{"month": point["label"], "celsius": point["value"]} for point in temperatures] records[:2] ``` ``` RidgelinePlot( data=records, # the keys that hold the row and the value label="month", value="celsius", title="Daily mean temperature in Ljubljana", xlabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question. Their data is illustrative: drawn with a fixed seed around realistic values, so every run draws the same chart. The data lives in hidden cells; each example says what its data is. ### Example 1: Which Releases Slowed the Service Down? (Median Marks, an Emphasis Rule, and a Target Line) A service team tracks the response times of eight releases against a 300 ms target. `latency` holds 400 illustrative response times per release, skewed to the right like real latencies: release 2.3 regressed, 2.4 fixed it, and 2.6 slipped again. The rows stay in release order, so a shift reads as a change over time. The median marks show where each release typically lands, `emphasis_rule={"above": 200}` highlights the releases whose median crossed 200 ms, and the dashed line shows the target, named in a legend outside the axes. `xmin` and `xmax` cut the long tail at 600 ms so the bulk of each ridge stays readable. ``` RidgelinePlot( data=latency, inner=VIOLIN_INNER.MEDIAN, # highlight the releases whose median crossed 200 ms emphasis_rule={"above": 200}, vlines={ "x": 300, "label": "target", "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DASHED}, }, show_legend=True, legend={"title": "", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, xmin=0, xmax=600, title="Response time by release", xlabel="Response time (ms)", ylabel="Release", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Does Training Make the Model More Confident? (Common Density Scale, Outlines, and Quartiles) A research figure tracks how confident a classifier is on its validation set after each epoch. `confidence` holds 500 illustrative predicted probabilities of the true class per epoch, drawn from beta distributions that move toward 1 as training goes on. With `normalize=RIDGELINE_SCALE.COMMON` the ridges share one density scale, so the distribution visibly narrows and grows taller as training sharpens it; a per-row scale would draw every epoch at the same height and hide exactly that. Outlines alone keep the later epochs visible through the overlap, the quartile marks show the middle half of each epoch moving right, and `xmin` and `xmax` bound the ridges to the valid range of a probability. ``` RidgelinePlot( data=confidence, # one density scale, so the heights compare normalize=RIDGELINE_SCALE.COMMON, fill=False, inner=VIOLIN_INNER.QUARTILES, # a probability lives between 0 and 1 xmin=0, xmax=1, title="Prediction confidence on the validation set", xlabel="Predicted probability of the true class", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: How Do Schools Compare, Student by Student? (Sorted Rows, a Pass Mark, and a Strip in a Panel) An education report ranks eight schools by their median exam score and shows every student. `scores` holds 60 illustrative scores (0 to 100) per school. The ridgeline sorts the schools by median, the best at the top, and `emphasis_rule={"bottom": 1}` highlights the school with the lowest median, the one the report is about; a dashed line marks a pass mark of 50. [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) lays a [SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/swarmplot/index.md) in strip mode over the same rows, so the reader sees how many students each ridge stands for and how many fall below the line; small, translucent points keep 480 students readable. ``` from datachart.constants import SWARM_MODE Panel( [ RidgelinePlot( data=scores, # the best median at the top sort=SORT.DESCENDING, overlap=0.3, # a score lives between 0 and 100 xmin=0, xmax=100, # the school with the lowest median emphasis_rule={"bottom": 1}, # the pass mark vlines={ "x": 50, "style": {"plot_vline_color": "#c1121f", "plot_vline_style": LINE_STYLE.DASHED}, }, ), # every student, on their school's row SwarmPlot( data=scores, orientation=ORIENTATION.HORIZONTAL, mode=SWARM_MODE.STRIP, jitter=0.5, style={"plot_swarm_color": "#2C3E50", "plot_swarm_size": 6, "plot_swarm_alpha": 0.6}, ), ], title="Exam scores by school, with the pass mark", xlabel="School", ylabel_left="Score", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Scatter Chart A scatter chart places each observation by two numeric values, so it answers *do these two quantities move together, and which points break the pattern*. This guide shows how to create scatter charts with the [datachart.charts.ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.charts.ScatterChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-scatter-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import ScatterChart ``` ## Basics The examples in this guide share one dataset: the GDP per capita and the life expectancy at birth of 49 countries in 2019, the last year before the COVID-19 pandemic. GDP per capita is in current US dollars, rounded to the nearest hundred, and population is in millions (source: World Bank, World Development Indicators); life expectancy is in years for both sexes, and the region is the country's WHO region (source: WHO Global Health Observatory). The data lives in a hidden cell. `countries` holds one data point per country, with the GDP per capita as `x`, the life expectancy as `y`, and the `country`, `region` and `population` as extra keys; `countries_by_region` holds one list per WHO region, in the order of `REGIONS`. The pattern is well known, richer countries live longer, and the interesting part is the countries that break it. Each data point is a dictionary with an `x` and a `y` value; the other keys are ignored until a parameter asks for them: ``` countries[:3] ``` **Basic example.** Only the `data` argument is required. Even without labels the shape is visible: life expectancy climbs steeply at low incomes and flattens out above them: ``` ScatterChart( # add the data to the chart data=countries ).show() ``` ## Customizing the Scatter Chart Every customization is either a keyword argument of `ScatterChart` or a `plot_scatter_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | --------------------------------------------- | --------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | set the tick positions and labels | `xticks`, `xticklabels`, `yticks`, `yticklabels` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | format or rotate the tick labels | `xticks_format`, `yticks_format`, `xtickrotate`, `ytickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range | `xmin`, `xmax`, `ymin`, `ymax` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | change the marker color, shape, size, or edge | `style={"plot_scatter_color": ..., "plot_scatter_marker": ...}` | [Scatter style](#scatter-style) | | color the points by a category | `hue`, `show_legend` | [Hue grouping](#hue-grouping) | | scale the markers by a third value | `size`, `size_range` | [Bubble chart](#bubble-chart) | | name some or all of the points | `label`, the `"label"` key of a data point | [Point labels](#point-labels) | | print the value beside each point | `show_values`, `value_format`, `value_step` | [Value labels](#value-labels) | | fit a trend line and measure the correlation | `show_regression`, `show_ci`, `ci_level`, `show_correlation` | [Regression line](#regression-line) | | keep one unit the same length on both axes | `aspect_ratio` | [Aspect ratio](#aspect-ratio) | | highlight some series, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | mark a threshold or a reference value | `hlines`, `vlines` | [Reference lines and bands](#reference-lines-and-bands) | | shade a range of values | `hspans`, `vspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | compare several series in one chart | `data` as a list of lists, `subtitle`, `show_legend` | [Multiple Scatter Charts](#multiple-scatter-charts) | | title and place the legend | `legend` | [Legend](#legend) | | draw each series in its own subplot | `subplots`, `sharex`, `sharey`, `max_cols` | [Subplots](#subplots) | | use a logarithmic axis | `scalex`, `scaley` | [Axis scales](#axis-scales) | | plot dates on the x-axis | `date` or `datetime` values as `x`, `xticks_format` | [Datetime axis](#datetime-axis) | | plot data with other key names | `x`, `y`, `size`, `hue`, `label` | [Custom data keys](#custom-data-keys) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | The full list of style attributes is in the [datachart.typings.ScatterStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs) type; the full list of parameters is in the [datachart.charts.ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.charts.ScatterChart) reference. ### Title, axis labels and ticks A chart without a title and axis labels leaves the reader guessing what the axes measure; `title`, `xlabel` and `ylabel` say it. GDP per capita runs from about 500 to 86,000 dollars, and on a linear axis the poorer half of the countries piles up against the left edge. `scalex=SCALE.LOG` ([SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE)) spreads them out, so the examples below use it; the [Axis scales](#axis-scales) section compares the two. A log axis labels its ticks as powers of ten, so `xticks` places a tick at 1,000, 10,000 and 100,000 dollars and `xticklabels` names them. `xticks_format` (and `yticks_format`) is the alternative when the labels follow a pattern: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or a `"{x:,.0f}"` style string. `xtickrotate` and `ytickrotate` tilt crowded labels, and `xmin`, `xmax`, `ymin` and `ymax` fix the axis range. ``` from datachart.constants import SCALE GDP_TICKS = [1_000, 10_000, 100_000] GDP_TICK_LABELS = ["$1k", "$10k", "$100k"] ScatterChart( data=countries, # add the title title="Life expectancy and income, 2019", # add the x and y axis labels xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", # a logarithmic income axis scalex=SCALE.LOG, # one labeled tick per power of ten xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, # fix the y-axis range ymin=55, ymax=90, ).show() ``` ### Figure size and grid The default figure is 6.4 by 4.8 inches, a little wider than the text column of an A4 page. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width; `FIG_SIZE.FULL_MEDIUM` fits the full width. The default grid draws only horizontal lines, but a scatter chart has no baseline to read from, so grid lines in both directions help the eye carry a point to either axis: `show_grid=SHOW_GRID.BOTH` ([SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID)); `SHOW_GRID.X` and `SHOW_GRID.Y` draw one set only. ``` from datachart.constants import FIG_SIZE, SHOW_GRID ScatterChart( data=countries, title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, # a figure as wide as the page figsize=FIG_SIZE.FULL_MEDIUM, # grid lines in both directions show_grid=SHOW_GRID.BOTH, ).show() ``` ### Scatter style The `style` dictionary sets the look of the markers: the color and alpha, the size (the marker area in points squared), the shape from [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER), and the edge; the attributes are listed in [datachart.typings.ScatterStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs), and any attribute left out keeps the value of the active theme. Where points overlap, as in the crowded top right corner of this chart, a lower alpha and a thin dark edge keep each marker visible. ``` from datachart.constants import LINE_MARKER ScatterChart( data=countries, # translucent diamonds with a thin dark edge style={ "plot_scatter_color": "#e76f51", "plot_scatter_alpha": 0.7, "plot_scatter_size": 60, "plot_scatter_marker": LINE_MARKER.DIAMOND, "plot_scatter_edge_width": 0.8, "plot_scatter_edge_color": "#1d3557", }, title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Hue grouping Is the pattern the same everywhere, or do regions sit apart? `hue` names the key that holds a category, here the `region` of each country; each category gets its own color from the theme's palette and its own legend entry, which `show_legend` shows. The colors show that the bottom left corner, low income and short lives, is mostly African, while Europe and the Western Pacific share the top right. ``` ScatterChart( data=countries, # color the points by WHO region hue="region", title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, # name the regions show_legend=True, ).show() ``` ### Bubble chart A point for China and a point for Slovenia look the same, though one stands for 700 times as many people. `size` names the key whose value scales the marker, here `population`, which turns the chart into a bubble chart. The values map linearly onto the marker areas in `size_range` (the default is `(20, 200)`): the smallest value gets the smallest marker, the largest the largest. With populations from 1.7 million to 1.4 billion, a wide range keeps the difference visible, and a low alpha with an edge keeps overlapping bubbles readable. The two largest bubbles, China and India, show that most of the people in this chart live in the middle of the income range. `hue` and `size` combine, but the sizes are scaled within each hue group, so the largest country of every region would get the largest bubble; for sizes that compare across the whole chart, keep the points in one group. ``` ScatterChart( data=countries, # scale the markers by population size="population", # a wide range of marker areas size_range=(10, 1500), style={ "plot_scatter_alpha": 0.5, "plot_scatter_edge_width": 0.6, "plot_scatter_edge_color": "#1d3557", }, title="Life expectancy and income, sized by population, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Point labels A scatter chart invites the question *which one is that?* `label` names the key that holds each point's label, here `country`. Each label is placed beside its marker at the spot with the least overlap with the other markers, the labels already placed, and the axes edge, so a chart of a dozen points stays readable without hand-placed notes; the labels use the `plot_text_*` font of the theme. The Americas alone show the spread within one region, from Haiti to the United States. ``` americas = [point for point in countries if point["region"] == "Americas"] ScatterChart( data=americas, # name each point after its country label="country", title="Life expectancy and income in the Americas, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` Fifty labels would bury the chart, so label only the points the reader needs. Points without the label key stay unlabeled, and `"label"` is the default key, so adding it to a few records is enough. Here it names the five most populous countries in the chart: ``` most_populous = sorted(countries, key=lambda point: point["population"])[-5:] most_populous_names = {point["country"] for point in most_populous} populous_marked = [ {**point, "label": point["country"]} if point["country"] in most_populous_names else point for point in countries ] ScatterChart( # only the five most populous countries carry a "label" key data=populous_marked, hue="region", title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` ### Value labels When the exact values matter more than the names, `show_values` prints each point's `y` value beside it, placed like the point labels. `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. On a crowded chart the default prints only every Nth value, choosing the step that keeps neighboring labels apart; `value_step` sets the step, and `1` prints them all. A point carries either its name or its value, so combining `label` with `show_values` raises a `ValueError`. ``` ScatterChart( data=americas, # print the life expectancy beside every point show_values=True, value_format="{x:.1f}", value_step=1, title="Life expectancy and income in the Americas, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Regression line How strong is the link, and what does it predict? `show_regression` fits a straight line through the points by least squares, `show_ci` shades the confidence band of that line and `ci_level` sets its level (the default is 0.95), and `show_correlation` prints the Pearson correlation coefficient `r` in the top left corner. With `hue`, one line is fitted to all groups together. The line is fitted to the values as they are in the data, whatever the axis scale, so on a log axis it would be a straight-line fit to raw dollars. Life expectancy grows with the *order of magnitude* of income, so the example plots `log10` of the GDP per capita and labels the ticks with the dollar amounts they stand for. The fit is tight, and the countries far below the band are the ones worth a closer look. ``` import math countries_log_gdp = [{**point, "x": math.log10(point["x"])} for point in countries] ScatterChart( data=countries_log_gdp, # fit a straight line through the points show_regression=True, # shade its 95% confidence band show_ci=True, ci_level=0.95, # print the correlation coefficient show_correlation=True, title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", # the x values are log10(GDP); label them with the dollar amounts xticks=[3, 4, 5], xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Aspect ratio Dollars and years have nothing in common, so the axes of the charts above can stretch freely. When both axes share a unit (coordinates, distances, a prediction against a measurement), stretching distorts the picture. `aspect_ratio=ASPECT_RATIO.EQUAL` ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)) keeps one data unit the same length on both axes, while the default `ASPECT_RATIO.AUTO` lets the axes fill the figure. `cities`, defined in a hidden cell, holds the longitude and latitude of 16 European capitals, rounded to two decimals; with an equal aspect ratio they draw a recognizable map (a plain longitude-latitude grid, without a map projection). ``` from datachart.constants import ASPECT_RATIO ScatterChart( data=cities, label="city", title="European capitals", xlabel="Longitude (°E)", ylabel="Latitude (°N)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, # one degree is the same length on both axes aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. With the data split into several series (the [Multiple Scatter Charts](#multiple-scatter-charts) section covers the list-of-lists form), `emphasis` takes one role per series: `"highlight"` gives the markers a contrasting edge and brings them to the front, `"background"` mutes a series (the theme's muted color at a lower alpha, behind the others, without a legend entry), and `None` leaves it as it is. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type and theme. `label` also takes one entry per series, with `None` for a series left unlabeled, so highlighting and naming go together. Which countries break the pattern? Four sit well below the trend (Nigeria, South Africa, Equatorial Guinea, and the United States among the rich), and three well above it (Cuba, Costa Rica and Sri Lanka). ``` OUTLIERS = {"Nigeria", "South Africa", "Equatorial Guinea", "United States", "Cuba", "Costa Rica", "Sri Lanka"} outliers = [point for point in countries if point["country"] in OUTLIERS] others = [point for point in countries if point["country"] not in OUTLIERS] ScatterChart( data=[others, outliers], subtitle=["other countries", "far from the trend"], # mute the rest, highlight the outliers emphasis=["background", "highlight"], # name the outliers only label=[None, "country"], title="Countries that break the pattern, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` `emphasis_rule` picks the series from the data instead. It is a one-key dictionary read against a summary of each series's `y` values: `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), or `{"top": n}` or `{"bottom": n}` by rank. The summary is the mean by default; a `"by"` key picks `"median"`, `"min"`, `"max"` or `"sum"` instead. The series that match are highlighted and the rest muted, and an explicit `emphasis` role wins over the rule. Asking which regions have a country with a life expectancy below 65 years is `{"below": 65, "by": "min"}`, and the answer is three regions, not only Africa: ``` ScatterChart( data=countries_by_region, subtitle=REGIONS, # highlight the regions whose lowest life expectancy is below 65 emphasis_rule={"below": 65, "by": "min"}, title="Regions with a life expectancy below 65 years, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` ### Reference lines and bands Is a country above or below the world as a whole? Reference lines answer by marking a value: `hlines` draws a horizontal line at a `y` value and `vlines` a vertical one at an `x` value. `hspans` and `vspans` shade a range instead; a band needs at least one bound, and a missing bound runs to the axis edge. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs) and [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs), and the line patterns in [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE). The lines below split the chart into quadrants: the world life expectancy in 2019 (73.1 years, WHO) and the median GDP per capita of the countries shown. ``` from statistics import median from datachart.constants import LINE_STYLE median_gdp = median(point["x"] for point in countries) ScatterChart( data=countries, hue="region", # the world life expectancy hlines={ "y": WORLD_LIFE_EXPECTANCY, "label": "world life expectancy", "style": {"plot_hline_color": "#1d3557", "plot_hline_style": LINE_STYLE.DASHED}, }, # the median income of the countries shown vlines={ "x": median_gdp, "label": "median GDP per capita", "style": {"plot_vline_color": "#9d0208", "plot_vline_style": LINE_STYLE.DOTTED}, }, title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, # a fixed income range, so the horizontal line spans it xmin=400, xmax=100_000, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` Bands work the same way. The chart below shades the countries with a life expectancy of 80 years or more, a band with no upper bound, and those with a GDP per capita under 1,000 dollars, a band with no lower bound. Costa Rica and Chile make it into the top band on a fraction of the income of the other countries in it. ``` ScatterChart( data=countries, hue="region", # no upper bound: the band runs to the top edge hspans={"ymin": 80, "label": "80 years or more"}, # no lower bound: the band runs to the left edge vspans={ "xmax": 1_000, "label": "under $1,000 per person", "style": {"plot_vspan_color": "#e9c46a"}, }, title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` ### Text annotations Where a label names a point, a note explains it. `texts` places text on the chart, with an optional `target` to draw a connector to a data point; the position is in data coordinates by default, or in axes fractions with `"coords": "axes"`, which keeps the note in place whatever the axis range and scale. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connector looks and styling. The note below points at the United States, the richest large country in the chart and one of the shortest-lived among the rich. ``` US_GDP, US_LIFE = COUNTRIES["United States"][1:3] ScatterChart( data=countries, # a note pinned to the axes, pointing at the United States texts={ "text": "United States: $65k per person,\nyet shorter lives than Chile", "x": 0.6, "y": 0.2, "coords": "axes", "target": (US_GDP, US_LIFE), }, title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ## Multiple Scatter Charts To compare several groups as separate series, pass a list of lists to `data`: each inner list is one series, and the per-series attributes (`subtitle`, `style`, `emphasis`, `label`, and the data keys) become lists aligned with it. `subtitle` names each series and `show_legend` lists them. `countries_by_region` is such a list, one series per WHO region. Unlike `hue`, which colors the groups of one series, separate series take separate styles: a list of `style` dictionaries gives each region its own marker shape, which keeps the groups apart in greyscale too (`None` in the list keeps the theme style for a series). ``` REGION_MARKERS = [ {"plot_scatter_marker": marker} for marker in [ LINE_MARKER.CIRCLE, LINE_MARKER.SQUARE, LINE_MARKER.TRIANGLE, LINE_MARKER.DIAMOND, LINE_MARKER.PENTAGON, LINE_MARKER.HEXAGON, ] ] ScatterChart( # one series per region data=countries_by_region, # named for the legend subtitle=REGIONS, # one marker shape per region style=REGION_MARKERS, title="Life expectancy and income by WHO region, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` ### Legend `show_legend` lists the series; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). No country in the chart is rich and short-lived, so the bottom right corner is empty and holds the legend without covering a point. ``` from datachart.constants import LEGEND_LOCATION ScatterChart( data=countries_by_region, subtitle=REGIONS, style=REGION_MARKERS, title="Life expectancy and income by WHO region, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, # a titled legend in the empty corner legend={"title": "WHO region", "location": LEGEND_LOCATION.LOWER_RIGHT}, ).show() ``` ### Subplots Six overlapping groups are hard to tell apart, however they are styled. `subplots=True` draws each series in its own panel: `subtitle` titles the panels, `title`, `xlabel` and `ylabel` stay global, and `max_cols` limits the panels per row. `sharex=True` and `sharey=True` put every panel on the same axes, so a position means the same in each panel; without them each panel zooms to its own points, and the African countries would fill their panel just like the European ones. ``` ScatterChart( data=countries_by_region, subtitle=REGIONS, title="Life expectancy and income by WHO region, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, # one panel per region, three per row subplots=True, max_cols=3, # the same axes in every panel sharex=True, sharey=True, # a y range that holds every region ymin=55, ymax=90, ).show() ``` ## Additional Features ### Axis scales A linear axis suits values of one order of magnitude; values that span several read better on a logarithmic one, where equal distances stand for equal ratios. `scalex` and `scaley` take a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member: `SCALE.LINEAR` (the default), `SCALE.LOG`, and `SCALE.SYMLOG` and `SCALE.ASINH` for data that also crosses zero. On the linear scale the countries under 10,000 dollars crowd into the left edge and the relationship looks like a sharp bend; on the log scale they spread out and the relationship is close to a straight line. ``` for scale in [SCALE.LINEAR, SCALE.LOG]: ScatterChart( data=countries, title=f"Life expectancy and income on the '{scale}' scale", xlabel="GDP per capita (USD)", ylabel="Life expectancy (years)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, # the scale of the income axis scalex=scale, ).show() ``` ### Datetime axis When `x` is a date, the question becomes how a value changes over time, and whether the change is steady. An `x` value that is a real temporal object (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) puts the chart on a time axis: points sit at their elapsed time, and the ticks choose concise labels for the visible span; date strings are not parsed. `xticks_format` takes a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern, and explicit `xticks`, `xmin` and `xmax`, reference lines and bands take dates as well. `records`, defined in a hidden cell, holds the progression of the men's marathon world record since 2003: the date of each record race, the finishing time in minutes, and the record holder as the label (source: World Athletics). The regression line shows a steady pace, about 13 seconds off the record per year. ``` from datachart.constants import DATE_FORMAT ScatterChart( data=records, title="Men's marathon world record", xlabel="Race date", ylabel="Finishing time (minutes)", # a tick every five years, labeled with the year xticks=[date(year, 1, 1) for year in range(2005, 2025, 5)], xticks_format=DATE_FORMAT.YEAR, show_regression=True, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Custom data keys Data that comes from a file or an API rarely uses the `x` and `y` keys, and renaming every record just to plot it is a chore. The `x` and `y` arguments name the keys to read instead, just as `size`, `hue` and `label` name the keys of the bubble size, the category and the point label. `country_records` stores the countries the way a CSV export would: ``` country_records = [ {"country": name, "region": region, "gdp_per_capita": gdp, "life_expectancy": life, "population": population} for name, (region, gdp, life, population) in COUNTRIES.items() ] country_records[:2] ``` ``` ScatterChart( data=country_records, # the keys that hold the x and y values x="gdp_per_capita", y="life_expectancy", # and the category hue="region", title="Life expectancy and income, 2019", xlabel="GDP per capita (USD, log scale)", ylabel="Life expectancy (years)", scalex=SCALE.LOG, xticks=GDP_TICKS, xticklabels=GDP_TICK_LABELS, figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Does Accuracy Keep Growing with Model Size? (Log-Transformed Regression with a Confidence Band) `model_accuracy` holds the benchmark accuracy, in percent, of 24 illustrative language models with 0.1 to 100 billion parameters, drawn from a seeded random generator around a trend of 12 points per tenfold increase in size. Accuracy grows with the logarithm of the size, so the points are plotted against `log10` of the parameter count with the ticks labeled in billions; `show_regression` fits the trend, `show_ci` shades its 90% confidence band, and `show_correlation` reports how tight the trend is. The band is narrowest in the middle of the range, where the fit has data on both sides. ``` ScatterChart( data=model_accuracy, style={"plot_scatter_alpha": 0.8}, # the trend, its 90% confidence band, and its strength show_regression=True, show_ci=True, ci_level=0.9, show_correlation=True, title="Benchmark accuracy and model size", xlabel="Parameters (log scale)", ylabel="Accuracy (%)", # the x values are log10(billions of parameters) xticks=[-1, 0, 1, 2], xticklabels=["0.1B", "1B", "10B", "100B"], figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, ).show() ``` ### Example 2: Heavier Penguins Have Longer Flippers, Within Each Species Too (Per-Series Regression and Custom Data Keys) `penguins` holds the flipper length in millimeters and the body mass in grams of the 342 penguins with both measurements in the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset (Gorman, Williams and Fraser, 2014; released under CC0), one list per species, stored under the keys `flipper_length_mm` and `body_mass_g`. Across all penguins the link is strong, but part of it only says that Gentoo penguins are bigger than the other two species. Does it hold within a species? One series per species answers it: `x` and `y` read the stored keys, and `show_regression` fits one line per series, in the series' color. All three lines rise. ``` ScatterChart( data=penguins, # the keys that hold the measurements x="flipper_length_mm", y="body_mass_g", subtitle=SPECIES, style={"plot_scatter_alpha": 0.5, "plot_scatter_size": 16}, # one regression line per species show_regression=True, title="Body mass and flipper length of Palmer penguins", xlabel="Flipper length (mm)", ylabel="Body mass (g)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, legend={"title": "Species", "location": LEGEND_LOCATION.UPPER_LEFT}, ).show() ``` ### Example 3: Did the Final Sweep Beat the Search? (Emphasis, a Frontier Line with Panel, and a Note) `tuning_runs` holds two sets of illustrative hyperparameter tuning runs, each run a point of training time in minutes against validation accuracy in percent: 150 runs of a broad random search and the 12 runs of a final, narrowed-down sweep, both drawn from seeded random generators. The question is whether the sweep found anything the search had not. `emphasis` mutes the search into a background cloud and highlights the sweep. `search_frontier` holds the best accuracy the search reached within each training time, drawn as a dashed [LineChart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) that runs flat to the longest run, and [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) puts the frontier over the runs in one axes; a note points at the best run of the sweep, above the frontier. ``` from datachart.charts import LineChart from datachart.utils import Panel runs = ScatterChart( data=tuning_runs, subtitle=["random search", "final sweep"], # mute the search, highlight the sweep emphasis=["background", "highlight"], # point at the best run of the sweep texts={ "text": f"best sweep run: {BEST_SWEEP['y']:.1f}%", "x": 0.1, "y": 0.9, "coords": "axes", "target": (BEST_SWEEP["x"], BEST_SWEEP["y"]), }, ) frontier = LineChart( data=search_frontier, subtitle="best of the search so far", style={"plot_line_color": "#6c757d", "plot_line_style": LINE_STYLE.DASHED}, ) Panel( [runs, frontier], title="Validation accuracy of the tuning runs", xlabel="Training time (minutes)", ylabel_left="Validation accuracy (%)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.BOTH, show_legend=True, legend={"title": "Tuning runs", "location": LEGEND_LOCATION.LOWER_RIGHT}, ).show() ``` # Heatmap A heatmap colors every cell of a table by its value, so a grid of numbers over two categorical dimensions reads at a glance: where the hot cells are, and what pattern they form. This guide shows how to create heatmaps with the [datachart.charts.Heatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.charts.Heatmap) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-heatmap), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import Heatmap ``` ## Basics The examples in this guide share one dataset: the monthly climate of six cities on four continents, Reykjavik, Moscow, Ljubljana, Cairo, Singapore and Sydney. `temperatures` holds the mean air temperature of every month in °C, and `precipitation` the mean monthly rainfall in mm. The values are approximate, rounded from the published 1991 to 2020 climate normals of each city's main weather station, and live in a hidden cell. The table has several stories in it, and the customizations below bring them out: a freezing Moscow winter, a Singapore that never changes, a Sydney whose seasons run backwards, and a Cairo where it almost never rains. The data is a dictionary: `z` is a 2D list, one inner list per row and one value per cell, while `x` names the columns and `y` the rows. The first row is drawn at the top and the first column at the left: ``` {key: value[:2] for key, value in temperatures.items()} ``` **Basic example.** Only the `data` argument is required. Every cell is colored by its value, the lowest value in the table getting the first color of the colormap and the highest the last, and the `x` and `y` labels name the columns and rows. The pattern is visible straight away: the Cairo and Singapore rows at the warm end all year, Moscow's winter corner at the cold end, and a Sydney row that is warmest where the others are coldest. ``` Heatmap( # add the data to the chart data=temperatures ).show() ``` ## Customizing the Heatmap Every customization is either a keyword argument of `Heatmap` or a `plot_heatmap_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------ | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title and axis labels](#title-and-axis-labels) | | tick only some rows or columns | `xticks`, `xticklabels`, `yticks`, `yticklabels` | [Ticks and labels](#ticks-and-labels) | | rotate the tick labels | `xtickrotate`, `ytickrotate` | [Ticks and labels](#ticks-and-labels) | | resize the figure or keep the cells square | `figsize`, `aspect_ratio` | [Figure size and aspect ratio](#figure-size-and-aspect-ratio) | | show the colorbar | `show_colorbars` | [Colorbar and cell values](#colorbar-and-cell-values) | | write the values into the cells | `show_heatmap_values`, `valfmt` | [Colorbar and cell values](#colorbar-and-cell-values) | | caption, move, or format the colorbar | `colorbar={"label": ..., "location": ..., "format": ..., "ticks": ...}` | [Colorbar placement](#colorbar-placement) | | change the colormap or transparency | `style={"plot_heatmap_cmap": ..., "plot_heatmap_alpha": ...}` | [Heatmap style](#heatmap-style) | | style the cell values | `style={"plot_heatmap_font_size": ..., "plot_heatmap_font_color": ..., ...}` | [Heatmap style](#heatmap-style) | | draw borders between the cells | `style={"plot_heatmap_edge_width": ..., "plot_heatmap_edge_color": ...}` | [Heatmap style](#heatmap-style) | | center a diverging colormap on a value | `vmin`, `vmax` | [Normalization](#normalization) | | spread skewed values over the colormap | `norm` | [Normalization](#normalization) | | highlight some cells, mute the rest | `emphasis_rule`, an `emphasis` grid in `data` | [Emphasis](#emphasis) | | put a note on a cell | `texts` | [Text annotations](#text-annotations) | | compare several tables side by side | `data` as a list of dicts, `subtitle` | [Multiple Heatmaps](#multiple-heatmaps) | | arrange the subplots | `max_cols`, `sharex`, `sharey` | [Subplot layout and shared axes](#subplot-layout-and-shared-axes) | | leave cells without data empty | `None` in `z` | [Blank cells](#blank-cells) | | show only part of the table | `xmin`, `xmax`, `ymin`, `ymax` | [Axis range](#axis-range) | | use dates as row or column labels | `date` objects as `x` or `y`, `xticks_format`, `yticks_format` | [Date labels](#date-labels) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `valfmt` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | The full list of style attributes is in the [datachart.typings.HeatmapStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.typings.HeatmapStyleAttrs) type; the full list of parameters is in the [datachart.charts.Heatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.charts.Heatmap) reference. ### Title and axis labels A heatmap has three quantities, the two axes and the color, and the reader needs all three named. `title` says what the colors measure, with its unit, and `xlabel` and `ylabel` name the dimensions of the table. ``` Heatmap( data=temperatures, # say what the colors measure title="Mean monthly temperature (°C)", # name the columns and the rows xlabel="Month", ylabel="City", ).show() ``` ### Ticks and labels Twelve month names are more labels than a small figure has room for, and a table with a hundred columns can never label them all. Column *j* sits at `x = j` and row *i* at `y = i`, counting from zero, so `xticks` and `yticks` take the indices to tick and `xticklabels` and `yticklabels` their labels; an explicit pair replaces the `x` or `y` labels of the data. Here only the first month of each season is ticked. `xtickrotate` and `ytickrotate` tilt the tick labels, whichever labels are shown, which keeps long names from crowding. ``` Heatmap( data=temperatures, title="Mean monthly temperature (°C)", xlabel="Month", ylabel="City", # tick the first month of each season xticks=[0, 3, 6, 9], xticklabels=["Jan", "Apr", "Jul", "Oct"], # tilt the city names ytickrotate=30, ).show() ``` ### Figure size and aspect ratio A table of six rows and twelve columns is twice as wide as it is tall, and the default figure is nearly square, so the cells come out tall and narrow. `figsize` takes a `(width, height)` tuple in inches or a preset from [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE). The cells stretch to fill the axes by default ([ASPECT_RATIO.AUTO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)); `ASPECT_RATIO.EQUAL` keeps them square and shrinks the axes to fit, which suits a matrix whose rows and columns are the same kind of thing, like a correlation matrix, and a wide, short figure like this one. ``` from datachart.constants import FIG_SIZE, ASPECT_RATIO Heatmap( data=temperatures, title="Mean monthly temperature (°C)", xlabel="Month", ylabel="City", # a page-wide figure figsize=FIG_SIZE.FULL_SHORT, # square cells aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Colorbar and cell values Colors show which cells are warmer, not by how much. `show_colorbars` adds the scale that maps colors back to values, and `show_heatmap_values` writes each value into its cell; a value on a dark cell is written in white, so it stays legible across the colormap. `valfmt` formats the cell values: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or a format string that names the value `x`, such as `"{x:.1f}"` (a string without `x`, such as `"{z:.1f}"`, is not valid). The temperatures carry one decimal, so `VALUE_FORMAT.DECIMAL` keeps it. With the values written in, the chart answers both questions: the pattern from the colors, the exact numbers from the cells. ``` from datachart.constants import VALUE_FORMAT Heatmap( data=temperatures, title="Mean monthly temperature (°C)", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, # add the color scale show_colorbars=True, # write the values into the cells, with one decimal show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL, ).show() ``` ### Colorbar placement A colorbar on the right takes width from the table, and a wide table needs every bit of it. `colorbar` takes a dictionary ([ColorbarSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ColorbarSettingAttrs)): `location` puts the bar on any edge with a [COLORBAR_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION) member, `label` captions it, `format` formats its tick labels (a `VALUE_FORMAT` member or a string naming the value `x`), and `ticks` picks the tick positions. `orientation`, an [ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) member, is the older control: with no `location`, a vertical bar sits on the right and a horizontal one on top; when both are given, `location` wins. Under the table, captioned and ticked every 5 °C, the bar reads like a legend; the month names need no axis label, which would otherwise print below the bar. ``` from datachart.constants import COLORBAR_LOCATION Heatmap( data=temperatures, title="Mean monthly temperature", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_colorbars=True, # a captioned colorbar under the table, ticked every 5 degrees colorbar={ "location": COLORBAR_LOCATION.BOTTOM, "label": "Temperature (°C)", "format": "{x:.0f}°", "ticks": [-5, 0, 5, 10, 15, 20, 25], }, ).show() ``` ### Heatmap style The colormap is the style choice that matters most, because it decides what the reader sees as "a lot". A sequential colormap such as `COLORS.YlOrRd` or `COLORS.Blues` runs from light to dark and suits a magnitude, where more is simply more. A diverging colormap such as `COLORS.Coolwarm` or `COLORS.RdBu` runs through a neutral middle and suits signed values around a meaningful midpoint ([Normalization](#normalization) shows how to place that midpoint). `plot_heatmap_cmap` takes a [COLORS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORS) member or a list of hex colors, and the [Colormaps](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/colormaps/index.md) guide renders them all. The other attributes set the transparency (`plot_heatmap_alpha`), the look of the cell values (`plot_heatmap_font_size`, `plot_heatmap_font_color`, `plot_heatmap_font_style` with [FONT_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FONT_STYLE), `plot_heatmap_font_weight` with [FONT_WEIGHT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FONT_WEIGHT)), the frame around the table (`plot_heatmap_frame_color`), and the borders between the cells (`plot_heatmap_edge_width`, 0 by default, and `plot_heatmap_edge_color`). Thin white borders separate neighboring cells of similar shade, which the flat Singapore row needs. Any attribute left out keeps the value of the active theme. `show_grid` ([SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID)) draws the axis grid, which runs through the cell centers, so borders are the better way to separate cells. ``` from datachart.constants import COLORS, FONT_WEIGHT Heatmap( data=temperatures, style={ # a sequential colormap: warmer is darker "plot_heatmap_cmap": COLORS.YlOrRd, "plot_heatmap_alpha": 0.9, # small bold cell values "plot_heatmap_font_size": 7, "plot_heatmap_font_weight": FONT_WEIGHT.BOLD, # white borders between the cells, a dark red frame around them "plot_heatmap_edge_width": 1.5, "plot_heatmap_edge_color": "#FFFFFF", "plot_heatmap_frame_color": "#7f2704", }, title="Mean monthly temperature (°C)", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_colorbars=True, show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL, ).show() ``` ### Normalization The colors come from a two-step mapping: each value is first normalized to the 0 to 1 range, then picks its color from the colormap. Both steps can be tuned, and each tuning is a claim about the data, so it should be an honest one. **Value range.** By default the smallest value maps to the first color and the largest to the last. `vmin` and `vmax` pin the endpoints instead. With a diverging colormap this is what places the neutral middle on a meaningful value: the temperatures run from −6.7 to 28.5 °C, so the white center of `COLORS.Coolwarm` would land on about 11 °C, a value that means nothing. A range of −30 to 30 °C centers it on freezing, and every blue cell is now a month below zero. Pinning the range is also how two heatmaps get comparable colors (see [Example 3](#example-3-did-fine-tuning-fix-the-confusion-shared-value-range-and-a-grid)). ``` Heatmap( data=temperatures, # a diverging colormap, centered on 0 °C by a symmetric range style={"plot_heatmap_cmap": COLORS.Coolwarm}, vmin=-30, vmax=30, title="Mean monthly temperature (°C), centered on freezing", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_colorbars=True, show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL, ).show() ``` **Normalization.** `norm` changes how the values spread over the 0 to 1 range, with a [NORMALIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) member: `LINEAR` (the default); `LOG`, for positive values spanning orders of magnitude, where zero and negative values have no logarithm and are left blank; `SYMLOG` and `ASINH`, which are linear near zero and logarithmic beyond, so they keep zeros and signed values; and `LOGIT`, for proportions strictly between 0 and 1. It rescales the colors, not an axis, unlike the `scalex` and `scaley` of the other charts. A non-linear normalization is honest when the question is about ratios rather than differences, or when a few large values would otherwise flatten everything else, and the colorbar must stay on so the reader can see the scale is not linear. The `precipitation` table is such a case: Singapore's 290 mm December claims the dark end of a linear colormap, and every other city's wet and dry seasons fade into the same pale shades. A log normalization would blank Cairo's rainless months; `SYMLOG` keeps them and spreads the low end, so Cairo's wet winter and dry summer show, at the price of compressing the differences between the wetter cities. The colorbar makes that trade visible, which is why it stays on. ``` from datachart.constants import NORMALIZE for norm in [NORMALIZE.LINEAR, NORMALIZE.SYMLOG]: Heatmap( data=precipitation, # how the values spread over the colormap norm=norm, title=f"Mean monthly precipitation (mm), '{norm}' normalization", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_colorbars=True, show_heatmap_values=True, valfmt=VALUE_FORMAT.INTEGER, ).show() ``` ### Emphasis A heatmap shows every cell with the same weight, but a question is usually about a few of them. A heatmap has no series to mute, so the `emphasis` parameter of the series charts raises a `ValueError`; emphasis works per cell instead. `emphasis_rule` picks the cells from their values with a one-key rule: `{"above": v}` or `{"below": v}` (strict), `{"between": (lo, hi)}` (inclusive), `{"top": n}` or `{"bottom": n}`. The matching cells are outlined, the rest fade to the theme's muted alpha and still read on the colormap, and a blank cell never matches. Asking which months average below freezing picks out Moscow's long winter and a single Reykjavik month: ``` Heatmap( data=temperatures, # highlight the months below freezing, mute the rest emphasis_rule={"below": 0}, title="Months with a mean temperature below 0 °C", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL, ).show() ``` When the cells to mark do not follow from one threshold, `data` takes an `emphasis` grid aligned with `z`, one role per cell: `"highlight"`, `"background"`, or `None` to leave the cell as it is ([EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS)). A role in the grid wins over the rule. Marking each city's warmest month, and muting the rest, shows Sydney's summer at the start of the year, Singapore's in May and June, and Cairo's July and August tied; the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/#emphasis-picked-by-a-rule) guide covers emphasis across every chart. ``` # each city's warmest month highlighted, every other cell muted warmest = [ ["highlight" if value == max(row) else "background" for value in row] for row in TEMPERATURES ] Heatmap( data={**temperatures, "emphasis": warmest}, title="The warmest month of each city", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL, ).show() ``` ### Text annotations A pattern that surprises the reader deserves a sentence. `texts` places a note on the chart; its position is in data coordinates by default, where a cell sits at (column index, row index), or in axes fractions with `"coords": "axes"`, and a `target` draws a connector to a cell. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement and styling ([TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs)). The note below explains why Sydney's row runs backwards. ``` Heatmap( data=temperatures, style={"plot_heatmap_cmap": COLORS.Coolwarm}, vmin=-30, vmax=30, # a note pointing at Sydney's July, the middle of its winter texts={ "text": "southern hemisphere:\nwinter in July", "x": 8.5, "y": 1.5, "target": (6, 5), }, title="Mean monthly temperature (°C)", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_MEDIUM, show_colorbars=True, ).show() ``` ## Multiple Heatmaps To compare several tables, pass a list of dictionaries to `data`. Each table gets its own subplot, since two tables cannot share one set of cells, with its `subtitle` above it, while `title`, `xlabel` and `ylabel` stay global. The per-chart parameters (`subtitle`, `style`, `valfmt`, `norm`, `vmin`, `vmax`, `colorbar`, and the tick parameters) take a list with one entry per table, or a single value for all of them; `None` in a list keeps the default for that table. Temperature and rainfall are different quantities, so each table gets its own colormap and its own normalization. ``` Heatmap( # one table per subplot data=[temperatures, precipitation], subtitle=["Temperature (°C)", "Precipitation (mm)"], # a colormap and a normalization per table style=[{"plot_heatmap_cmap": COLORS.YlOrRd}, {"plot_heatmap_cmap": COLORS.Blues}], norm=[None, NORMALIZE.SYMLOG], title="The climate of six cities", xlabel="Month", ylabel="City", xticks=[0, 3, 6, 9], xticklabels=["Jan", "Apr", "Jul", "Oct"], figsize=FIG_SIZE.FULL_SHORT, show_colorbars=True, ).show() ``` ### Subplot layout and shared axes Side by side, twelve columns each leave no room for the cell values. `max_cols` limits the subplots per row, so `max_cols=1` stacks the tables and gives each the full width. `sharex` and `sharey` share an axis across subplots and label it once, on the outer subplots; both tables have the same months, so `sharex=True` drops the repeated month labels. `valfmt` as a list keeps the decimal on the temperatures and writes the rainfall as whole numbers. ``` Heatmap( data=[temperatures, precipitation], subtitle=["Temperature (°C)", "Precipitation (mm)"], style=[{"plot_heatmap_cmap": COLORS.YlOrRd}, {"plot_heatmap_cmap": COLORS.Blues}], norm=[None, NORMALIZE.SYMLOG], # one format per table valfmt=[VALUE_FORMAT.DECIMAL, VALUE_FORMAT.INTEGER], title="The climate of six cities", xlabel="Month", ylabel="City", figsize=FIG_SIZE.FULL_TALL, show_colorbars=True, show_heatmap_values=True, # stack the tables, one month axis for both max_cols=1, sharex=True, ).show() ``` ## Additional Features ### Blank cells Real tables have holes: a station that was not yet running, a pair of variables never measured together. A `None` in `z` leaves its cell blank, which is honest, where a zero would claim a value. `monthly_2024`, defined in a hidden cell, holds illustrative monthly temperatures of three weather stations in 2024; the newest station started recording in April, so its first three months are `None`. ``` Heatmap( # None cells stay blank data=monthly_2024, title="Monthly mean temperature in 2024 (°C)", figsize=FIG_SIZE.FULL_SHORT, show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL, ).show() ``` ### Axis range A large table often has one interesting corner. `xmin`, `xmax`, `ymin` and `ymax` crop the view in cell indices, where a cell spans half a unit on either side of its index: `xmin=-0.5` and `xmax=2.5` keep the first three columns whole. The first row is drawn at the top, so the row limits run the other way: `ymin=2.5` and `ymax=-0.5` keep the first three rows in their order. Cropped to the first three months and the three European cities, the chart shows only where winter bites. The colormap still spans the whole table, so the colors stay comparable with the full chart. ``` Heatmap( data=temperatures, style={"plot_heatmap_cmap": COLORS.Coolwarm}, vmin=-30, vmax=30, # January to March xmin=-0.5, xmax=2.5, # the first three rows, the first row on top ymin=2.5, ymax=-0.5, title="Winter in Europe (°C)", figsize=FIG_SIZE.HALF_SQUARE, show_colorbars=True, ).show() ``` ### Date labels Rows or columns are often dates: months, weeks, years. `x` and `y` can hold real temporal objects (`datetime`, `date`, `numpy.datetime64`, or a pandas `Timestamp`); the cells keep their grid positions, and the dates print through `xticks_format` or `yticks_format`, a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern. The same `monthly_2024` table, labelled by the first day of each month, prints the months with their year. ``` from datetime import date from datachart.constants import DATE_FORMAT Heatmap( # the first day of each month as the column labels data={**monthly_2024, "x": [date(2024, month, 1) for month in range(1, 13)]}, title="Monthly mean temperature in 2024 (°C)", figsize=FIG_SIZE.FULL_SHORT, # print the dates as year and month xticks_format=DATE_FORMAT.YEAR_MONTH, xtickrotate=45, ).show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: How Do Penguin Measurements Move Together? (Diverging Colormap, Pinned Range, and a Note) `correlations` holds the Pearson correlation between four body measurements (bill length, bill depth, flipper length and body mass) of the 342 penguins in the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset (CC0). A correlation is signed, so the chart needs a diverging colormap whose white middle sits on zero: `COLORS.RdBu` pinned to the −1 to 1 range with `vmin` and `vmax` gives equally strong correlations of either sign equally dark shades. The variables label both axes, square cells keep the matrix symmetric, and the cells carry the coefficients. One cell is a known trap: bill length and depth correlate negatively across all penguins but positively within each species, a case of Simpson's paradox, and a note says so. ``` Heatmap( data=correlations, # a diverging colormap, pinned so that zero sits on white style={ "plot_heatmap_cmap": COLORS.RdBu, "plot_heatmap_edge_width": 1, "plot_heatmap_edge_color": "#FFFFFF", }, vmin=-1, vmax=1, # a note on the bill length and depth cell texts={ "text": "positive within\neach species", "x": 0.2, "y": -0.95, "target": (1, 0), }, title="Correlation of Palmer penguin measurements", xtickrotate=30, figsize=FIG_SIZE.SQUARE, aspect_ratio=ASPECT_RATIO.EQUAL, show_colorbars=True, colorbar={"label": "Pearson r", "ticks": [-1, -0.5, 0, 0.5, 1]}, show_heatmap_values=True, valfmt=VALUE_FORMAT.DECIMAL_2, ).show() ``` ### Example 2: Which Topics Does the Classifier Confuse? (Integer Cells and an Emphasis Grid) `confusion` holds the illustrative confusion matrix of a topic classifier evaluated on 1,000 news articles, 250 per topic: each row is the true topic, each column the predicted one, and each cell a count of articles. The diagonal holds the correct predictions; the question is where the errors go. An `emphasis` grid highlights the two cells where business and politics articles are mistaken for each other and mutes the rest, so the largest confusion stands out without hiding the other counts. `VALUE_FORMAT.INTEGER` writes the counts, and the colorbar is left out because the cells already carry the numbers. ``` # the business-politics mix-ups, both ways MIXUPS = {(0, 1), (1, 0)} roles = [ ["highlight" if (i, j) in MIXUPS else "background" for j in range(4)] for i in range(4) ] Heatmap( data={**confusion, "emphasis": roles}, style={"plot_heatmap_cmap": COLORS.Blues}, title="Topic classifier on 1,000 news articles", xlabel="Predicted topic", ylabel="True topic", figsize=FIG_SIZE.SQUARE, aspect_ratio=ASPECT_RATIO.EQUAL, # write the counts into the cells show_heatmap_values=True, valfmt=VALUE_FORMAT.INTEGER, ).show() ``` ### Example 3: Did Fine-Tuning Fix the Confusion? (Shared Value Range and a Grid) `fine_tuned` holds the illustrative confusion matrix of a fine-tuned version of the Example 2 classifier, evaluated on the same 1,000 articles. The question is whether the business and politics mix-ups shrank. The two matrices are drawn as two heatmaps with the same `vmin` and `vmax`, pinned to the 0 to 250 range, so an equally dark cell means an equally large count in either one; without it, each matrix would stretch its own colormap and the shades would not compare. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) sets them side by side and adds a bar chart of each topic's recall (the share of its 250 articles classified correctly) underneath, which states the improvement in one number per topic. ``` from datachart.charts import BarChart from datachart.constants import LEGEND_LOCATION, SHOW_GRID from datachart.utils import Grid def matrix(data, title, ylabel=None): # one value range for every matrix, so the shades compare return Heatmap( data=data, style={"plot_heatmap_cmap": COLORS.Blues}, vmin=0, vmax=250, title=title, xlabel="Predicted topic", ylabel=ylabel, xtickrotate=30, aspect_ratio=ASPECT_RATIO.EQUAL, show_heatmap_values=True, valfmt=VALUE_FORMAT.INTEGER, ) recalls = BarChart( data=recall, subtitle=["Baseline", "Fine-tuned"], style=[{"plot_bar_color": "#b0b7c3"}, {"plot_bar_color": "#2171b5"}], title="Recall per topic", ylabel="Recall", show_grid=SHOW_GRID.Y, show_legend=True, legend={"title": "Model", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ymin=0, ymax=1.1, show_values=True, value_format=VALUE_FORMAT.PERCENT_INT, ) Grid( [ [matrix(confusion, "Baseline", ylabel="True topic"), matrix(fine_tuned, "Fine-tuned")], [recalls], ], title="Fine-tuning the topic classifier", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Contour Chart A contour chart shows a surface over two continuous variables, such as the elevation of a landscape, the loss of a model over two parameters, or the density of scattered points, through lines of equal value (or the bands between them). The lines answer *where are the peaks and the valleys, and how steep is the way between them*. This guide shows how to create contour charts with the [datachart.charts.ContourChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.charts.ContourChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-contour-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import ContourChart ``` ## Basics The examples in this guide share one surface: an illustrative hill, 10 km from west to east and 8 km from south to north, the way a hiking map would show it. It has two peaks, a higher West Peak (1738 m) and a lower East Peak (1386 m), joined by a saddle (the lowest point of the ridge between them, 1075 m), and the valley floor rises gently to the north. The values come from a formula (two smooth bumps on a tilted plane), defined in a hidden cell. `terrain` holds the surface: `x` is the distance east in km, `y` the distance north in km, and `z` the elevation in m, one row per `y` value and one column per `x` value. `WEST_PEAK`, `EAST_PEAK` and `SADDLE` hold the (east, north) position of the three landmarks. The data is one dictionary. `z` is a list of rows, so its length matches `y` and the length of each row matches `x`: ``` len(terrain["x"]), len(terrain["y"]), len(terrain["z"]), len(terrain["z"][0]) ``` **Basic example.** Only the `data` argument is required. The surface is cut at a handful of round elevations and each cut is drawn as an iso-line, a line of equal value like the elevation lines of a map. The small closed loops mark the two peaks; the line that wraps around both of them passes just below the saddle: ``` ContourChart( # add the data to the chart data=terrain ).show() ``` ## Customizing the Contour Chart Every customization is either a keyword argument of `ContourChart` or a `plot_contour_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ---------------------------------------- | ------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | set or format the ticks | `xticks`, `yticks`, `xticks_format`, `yticks_format` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size, grid and aspect ratio](#figure-size-grid-and-aspect-ratio) | | show grid lines | `show_grid` | [Figure size, grid and aspect ratio](#figure-size-grid-and-aspect-ratio) | | keep one unit equal on both axes | `aspect_ratio` | [Figure size, grid and aspect ratio](#figure-size-grid-and-aspect-ratio) | | fill the bands between the levels | `filled` | [Filled contours and colorbar](#filled-contours-and-colorbar) | | add and caption a colorbar | `show_colorbars`, `colorbar` | [Filled contours and colorbar](#filled-contours-and-colorbar) | | write the level values on the lines | `show_labels`, `valfmt` | [Inline labels](#inline-labels) | | choose the values that cut the surface | `levels` | [Levels](#levels) | | change the line color, width, or style | `style={"plot_contour_color": ..., "plot_contour_line_width": ...}` | [Contour style](#contour-style) | | color the lines or bands with a colormap | `style={"plot_contour_cmap": ...}` | [Contour style](#contour-style) | | pin or rescale the colormap range | `vmin`, `vmax`, `norm` | [Normalization](#normalization) | | mark a position or shade a region | `vlines`, `hlines`, `vspans`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | overlay several surfaces | `data` as a list, `subtitle`, `show_legend` | [Multiple Contour Charts](#multiple-contour-charts) | | title and place the legend | `legend` | [Legend](#legend) | | highlight one surface, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | draw each surface in its own subplot | `subplots`, `max_cols`, `sharex`, `sharey` | [Subplots and shared axes](#subplots-and-shared-axes) | | draw a path or points over a surface | `Panel`, `Grid` | [Composing with Panel and Grid](#composing-with-panel-and-grid) | | draw the density of scattered points | `stats.kde2d`, `bandwidth` | [Density of scattered points](#density-of-scattered-points) | | use a logarithmic axis | `scalex`, `scaley` | [Axis scales](#axis-scales) | | draw a surface over time | temporal `x` values, `xticks_format` | [Datetime axis](#datetime-axis) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `levels` | [`CONTOUR_LEVELS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.CONTOUR_LEVELS) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `valfmt` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | The full list of style attributes is in the [datachart.typings.ContourStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.typings.ContourStyleAttrs) type; the full list of parameters is in the [datachart.charts.ContourChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.charts.ContourChart) reference. ### Title, axis labels and ticks A contour chart without axis labels leaves the reader guessing what the axes and the lines measure; `title`, `xlabel` and `ylabel` say it. `xticks` and `yticks` place the ticks, here one every 2 km, and `xticks_format` and `yticks_format` format them: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"` style string, so the unit can travel with the numbers. `xtickrotate` and `ytickrotate` tilt the labels when they crowd, and `xmin`, `xmax`, `ymin` and `ymax` crop the view; this map needs neither. ``` ContourChart( data=terrain, # add the title title="Elevation of the hill", # add the x and y axis labels xlabel="Distance east", ylabel="Distance north", # one tick every 2 km, with the unit xticks=[0, 2, 4, 6, 8, 10], yticks=[0, 2, 4, 6, 8], xticks_format="{x:.0f} km", yticks_format="{x:.0f} km", ).show() ``` ### Figure size, grid and aspect ratio `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. `show_grid` draws grid lines ([SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID)); on a map they help read a position off an iso-line, so both axes get them. A map has the same unit on both axes, and a stretched map misrepresents the shape of the land: by default the axes fill the figure, so 1 km east and 1 km north are drawn at different lengths. `aspect_ratio=ASPECT_RATIO.EQUAL` ([ASPECT_RATIO](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO)) draws them at the same length, and the slopes keep their true shape. Keep the default `ASPECT_RATIO.AUTO` when the two axes measure different things. ``` from datachart.constants import ASPECT_RATIO, FIG_SIZE, SHOW_GRID ContourChart( data=terrain, title="Elevation of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", # a full-width figure figsize=FIG_SIZE.FULL_MEDIUM, # grid lines on both axes show_grid=SHOW_GRID.BOTH, # 1 km is the same length on both axes aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Filled contours and colorbar Iso-lines show the shape of the surface, but the eye has to count lines to tell high from low. `filled=True` colors the bands between the levels by their value instead (with the heatmap colormap by default), so the high ground reads at a glance; grid lines are off by default, as the bands would cover them. `show_colorbars=True` adds a colorbar that maps the shades back to values, and `colorbar` configures it ([ColorbarSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ColorbarSettingAttrs)): `label` names the quantity, `location` places it with a [COLORBAR_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION) member, `format` formats its ticks and `ticks` places them. An `orientation` ([ORIENTATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION)) on its own also works. ``` from datachart.constants import COLORBAR_LOCATION ContourChart( data=terrain, # fill the bands between the levels filled=True, # a captioned colorbar on the right, one tick every 400 m show_colorbars=True, colorbar={ "label": "Elevation (m)", "location": COLORBAR_LOCATION.RIGHT, "format": "{x:.0f}", "ticks": [400, 800, 1200, 1600], }, title="Elevation of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Inline labels A reader of a map wants the elevation of a line without looking it up. `show_labels=True` writes the value of each level along its iso-line, and `valfmt` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"` style string with the value named `x`. The labels take the line color and a font smaller than the general font; the `plot_contour_label_font_size` and `plot_contour_label_font_color` style attributes change them (see [Contour style](#contour-style)). ``` ContourChart( data=terrain, # write the elevation along each line, with its unit show_labels=True, valfmt="{x:.0f} m", title="Elevation of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Levels The levels decide what a contour chart can show: a feature that falls between two levels is invisible. `levels` takes one of the following: | Value | Description | | --------------------- | --------------------------------------------------------------------------------------------------- | | `CONTOUR_LEVELS.AUTO` | Matplotlib's own choice: about eight round values across the range of the surface (the default). | | `CONTOUR_LEVELS.RICE` | The Rice rule: `2 * n ** (1/3)` levels, where `n` is the number of grid points along an axis. | | `CONTOUR_LEVELS.FD` | The Freedman-Diaconis rule: the value range over `2 * IQR * n ** (-1/3)`, usually denser than Rice. | | an integer | A target number of levels, snapped to round values. | | a list | The exact level values to draw. | The rules live in [datachart.constants.CONTOUR_LEVELS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.CONTOUR_LEVELS). They follow the grid resolution rather than the surface, so they are opt-ins. An integer is the quick way to ask for more detail: twenty levels show the gentle rise of the valley floor that the default hides. ``` from datachart.constants import CONTOUR_LEVELS ContourChart( data=terrain, # about twenty round levels levels=20, title="Elevation of the hill, about twenty levels", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` An explicit list says exactly which elevations matter. The saddle sits at 1075 m, so a level at 1000 m still wraps around both peaks as one ridge, while a level at 1150 m splits into two separate hills. Levels every 150 m from 700 m, which include both (plus one at 400 m for the valley floor), put the saddle between two lines where the reader can find it; `SADDLE_LEVELS` keeps the list for the charts below: ``` # 1000 m wraps both peaks, 1150 m splits them SADDLE_LEVELS = [400, 700, 850, 1000, 1150, 1300, 1450, 1600] ContourChart( data=terrain, levels=SADDLE_LEVELS, show_labels=True, valfmt="{x:.0f} m", title="The saddle lies between 1000 m and 1150 m", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` The rules are handy when the surface is unfamiliar and the right spacing is not obvious. On this 101 by 81 grid the Rice rule gives about ten levels: ``` ContourChart( data=terrain, # the number of levels follows the grid resolution levels=CONTOUR_LEVELS.RICE, show_labels=True, valfmt="{x:.0f}", title="Elevation of the hill, Rice rule", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Contour style The `style` dictionary sets the look of the contour: the line color, width and style, the colormap, the alpha, the z-order among other layers, and the font of the inline labels. The attributes are listed in [datachart.typings.ContourStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.typings.ContourStyleAttrs), and any attribute left out keeps the value of the active theme. A topographic map traditionally draws its elevation lines thin and brown, with the labels in the same color; the line styles are in [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE). ``` from datachart.constants import LINE_STYLE ContourChart( data=terrain, # thin brown lines, as on a topographic map style={ "plot_contour_color": "#8c5a2b", "plot_contour_line_width": 0.8, "plot_contour_line_style": LINE_STYLE.SOLID, "plot_contour_label_font_size": 7, "plot_contour_label_font_color": "#8c5a2b", }, levels=list(range(400, 1800, 100)), show_labels=True, valfmt="{x:.0f}", title="Elevation of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` With `plot_contour_cmap` set, iso-lines are colored by their level instead of in one color, which tells low from high without filling the chart. The colormap is a name from [COLORS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORS) or a list of colors; lines are colored from its darker part, since the lightest shades would vanish on a white background. For filled contours it sets the colors of the bands, and a colormap from a green valley to brown summits reads like terrain: ``` from datachart.constants import COLORS ContourChart( data=terrain, # color each line by its elevation style={"plot_contour_cmap": COLORS.Viridis}, levels=list(range(400, 1800, 100)), title="Elevation of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() TERRAIN_COLORS = ["#d9f0d3", "#a6d96a", "#e6c587", "#a6611a", "#5c3310"] ContourChart( data=terrain, filled=True, show_colorbars=True, colorbar={"label": "Elevation (m)"}, # a custom colormap, from a green valley to brown summits style={"plot_contour_cmap": TERRAIN_COLORS}, levels=list(range(300, 1900, 100)), title="Elevation of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Normalization The colors of a filled contour come from two steps: each level is normalized to the 0 to 1 range, then picks its color from the colormap. By default the lowest level gets the first color and the highest the last, so two maps of different hills each use the full colormap, and the same shade means different elevations on each. `vmin` and `vmax` pin the range instead. Pinned to the 0 to 3000 m of a mountain region, the same hill looks as modest as it is: ``` ContourChart( data=terrain, filled=True, show_colorbars=True, colorbar={"label": "Elevation (m)"}, levels=list(range(300, 1900, 100)), # the color range of the whole region, not of this hill vmin=0, vmax=3000, title="Elevation of the hill, on the regional color scale", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` `norm` changes how the values spread over the 0 to 1 range, with a [NORMALIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) member (`LINEAR`, `LOG`, `SYMLOG`, `ASINH`, `LOGIT`), and a surface that spans orders of magnitude needs it. `sighting_density`, computed in a hidden cell, is the density of illustrative chamois sightings on the hill, in sightings per km² (the [Multiple Contour Charts](#multiple-contour-charts) section introduces the data). The sightings cluster in a few places, and the density falls a thousandfold towards the edges of the map. With log-spaced levels and `NORMALIZE.LOG`, every tenfold step gets an equally distinct shade, where a linear normalization would spend the colormap on the busy centers and paint the faint outskirts all one color. ``` from datachart.constants import NORMALIZE ContourChart( data=sighting_density, filled=True, show_colorbars=True, colorbar={"label": "Sightings per km²", "ticks": [0.01, 0.1, 1, 10], "format": "{x:g}"}, # log-spaced levels, one shade per step levels=[0.003, 0.01, 0.03, 0.1, 0.3, 1, 3, 10, 30, 100], norm=NORMALIZE.LOG, title="Density of chamois sightings", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Reference lines and bands Reference lines and bands put the surface in context. `vlines` and `hlines` draw a line at an x or a y position, and a pair of them crosses at a point, here the saddle, the natural pass between the peaks. `vspans` and `hspans` shade a range of x or y, here the nature reserve that covers the land north of 6 km. Each takes a dictionary or a list of them, with the position, an optional `label` for the legend and a `style`; the keys are listed in [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs) and [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs). The legend goes outside the axes (see [Legend](#legend)), so it covers no part of the map. ``` from datachart.constants import LEGEND_LOCATION ContourChart( data=terrain, # cross-hairs on the saddle vlines={"x": SADDLE[0], "label": "saddle", "style": {"plot_vline_style": LINE_STYLE.DASHED}}, hlines={"y": SADDLE[1], "style": {"plot_hline_style": LINE_STYLE.DASHED}}, # the reserve covers everything north of 6 km hspans={"ymin": 6, "label": "nature reserve", "style": {"plot_hspan_color": "#2a9d8f"}}, levels=SADDLE_LEVELS, title="The saddle and the nature reserve", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ).show() ``` ### Text annotations A map names its landmarks. `texts` places text on the chart, with an optional `target` that draws a connector to a point; the position is in data coordinates by default (here km east and north) or in axes fractions with `"coords": "axes"`. A list places several notes at once. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connectors and styling. ``` ContourChart( data=terrain, # one note per landmark, each pointing at it texts=[ {"text": "West Peak, 1738 m", "x": 0.6, "y": 7.3, "target": WEST_PEAK}, {"text": "East Peak, 1386 m", "x": 7.4, "y": 6.8, "target": EAST_PEAK}, {"text": "saddle, 1075 m", "x": 5.8, "y": 0.8, "target": SADDLE}, ], levels=SADDLE_LEVELS, title="Landmarks of the hill", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ## Multiple Contour Charts To compare several surfaces on one map, pass a list of them to `data`: each is drawn as its own set of iso-lines in its own color, and the per-chart attributes (`subtitle`, `style`, `emphasis`, `valfmt`, `norm`, `vmin`, `vmax`, `colorbar`) become lists aligned with it. `subtitle` names each surface in the legend that `show_legend` draws. Filled surfaces would cover each other, so fills belong in [subplots](#subplots-and-shared-axes). The surfaces here come from an illustrative survey of chamois (a mountain goat-antelope) on the hill: `sightings` holds the GPS position of every sighting by season (`SEASONS`), and `season_density` one density surface per season, estimated with [datachart.utils.stats.kde2d](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/#datachart.utils.stats.kde2d) (see [Density of scattered points](#density-of-scattered-points)) on the extent of the map, so the surfaces line up. The story is a seasonal migration: the herd grazes near the West Peak in summer, splits over both peaks in autumn, and moves down to the southern slopes in winter. ``` ContourChart( # one surface per season data=season_density, # named for the legend subtitle=SEASONS, show_legend=True, # the same levels on every surface levels=[0.5, 2, 8, 32], title="Where the chamois are seen, by season", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Legend `show_legend` lists the surfaces; `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). Iso-lines can reach any corner of a map, and a legend outside the axes never hides one. ``` ContourChart( data=season_density, subtitle=SEASONS, show_legend=True, # a titled legend outside the axes, to the right legend={"title": "Season", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, levels=[0.5, 2, 8, 32], title="Where the chamois are seen, by season", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Emphasis A chart usually makes one point, and emphasis makes it visible. `emphasis` takes one role per surface, aligned with `data`: `"highlight"` bolds the iso-lines and brings them to the front, `"background"` mutes them into the theme's muted color and drops them from the legend, and `None` leaves them as they are. The roles are also the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across every chart type. Emphasis bolds and mutes lines, so it applies to iso-lines only: with `filled=True` it raises a `ValueError`. Asking where the herd spends the winter turns the other seasons into context: ``` from datachart.constants import EMPHASIS ContourChart( data=season_density, subtitle=SEASONS, # winter is the question, summer and autumn the context emphasis=[EMPHASIS.BACKGROUND, EMPHASIS.BACKGROUND, EMPHASIS.HIGHLIGHT], show_legend=True, levels=[0.5, 2, 8, 32], title="Where the chamois spend the winter", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` `emphasis_rule` picks the surfaces from the data instead. It is a one-key rule, `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive), read against a summary of each surface's own `z` values: the mean by default, or the `"median"`, `"min"`, `"max"` or `"sum"` named by a `"by"` key. The surfaces that match are highlighted and the rest muted; an explicit `emphasis` role wins over the rule. The season with the highest peak density is the one where the herd is most concentrated: ``` ContourChart( data=season_density, subtitle=SEASONS, # the surface with the highest maximum emphasis_rule={"top": 1, "by": "max"}, show_legend=True, levels=[0.5, 2, 8, 32], title="The season with the most concentrated herd", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_MEDIUM, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Subplots and shared axes Overlaid iso-lines get busy when the surfaces overlap, and filled surfaces cannot overlap at all. `subplots=True` draws each surface in its own panel: `subtitle` titles the panels, `title`, `xlabel` and `ylabel` stay global, and `max_cols` limits the panels per row. `sharex` and `sharey` put the panels on the same axes, labeled once on the outer panels. One `levels` list for all panels means the same shade is the same density in every season, and the log normalization (see [Normalization](#normalization)) keeps the sparse autumn and winter herds visible next to the dense summer one. ``` ContourChart( data=season_density, subtitle=SEASONS, # one filled panel per season, side by side filled=True, subplots=True, max_cols=3, # the same axes for every season sharex=True, sharey=True, # the same levels, so the shades compare across panels levels=[0.25, 0.5, 1, 2, 4, 8, 16, 32, 64], norm=NORMALIZE.LOG, style={"plot_contour_cmap": COLORS.YlGnBu}, title="Density of chamois sightings by season", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_SHORT, aspect_ratio=ASPECT_RATIO.EQUAL, ).show() ``` ### Composing with Panel and Grid A surface is often the background for something else: a path across it, or the points it was estimated from. [Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays figures in one coordinate space. `trail`, defined in a hidden cell, is an illustrative hiking trail from the southwest corner over the saddle to the northeast; a [LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.charts.LineChart) draws it and a [ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.charts.ScatterChart) draws the winter sightings, both over the filled terrain. Two per-figure options matter here: `"y_axis": "left"` keeps the trail and the points on the map's own axis (the panel would otherwise move a figure with a much narrower value span to a second axis), and `"z_order"` keeps the filled terrain below them. The [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide covers the options. ``` from datachart.charts import LineChart, ScatterChart from datachart.utils import Panel Panel( [ # the terrain at the bottom { "figure": ContourChart( data=terrain, filled=True, levels=list(range(300, 1900, 100)), style={"plot_contour_cmap": TERRAIN_COLORS}, ), "z_order": 1, }, # the trail and the sightings on top, on the map's own axis { "figure": LineChart(data=trail, subtitle="trail", style={"plot_line_color": "#c1121f"}), "y_axis": "left", "z_order": 2, }, { "figure": ScatterChart( data=sightings["Winter"], subtitle="winter sightings", style={"plot_scatter_color": "#1d3557"}, ), "y_axis": "left", "z_order": 3, }, ], title="The trail over the saddle, and where to look in winter", xlabel="Distance east (km)", ylabel_left="Distance north (km)", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` [Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) puts figures side by side instead, each in its own coordinate space. The labeled map sits next to the density of all sightings, so the reader can match the herd's favorite places to the landmarks. The [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers layouts. ``` from datachart.utils import Grid Grid( [ [ ContourChart( data=terrain, levels=[400, 600, 800, 1000, 1200, 1400, 1600], show_labels=True, valfmt="{x:.0f}", title="Elevation (m)", aspect_ratio=ASPECT_RATIO.EQUAL, ), ContourChart( data=sighting_density, filled=True, levels=[0.5, 1, 2, 4, 8, 16, 32, 64], norm=NORMALIZE.LOG, style={"plot_contour_cmap": COLORS.YlGnBu}, title="Chamois sightings per km²", aspect_ratio=ASPECT_RATIO.EQUAL, ), ] ], title="The hill and its chamois", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ## Additional Features ### Density of scattered points A contour of a density is the two-dimensional counterpart of a histogram: it shows where scattered points concentrate, without the overplotting of a crowded scatter chart. [datachart.utils.stats.kde2d](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/#datachart.utils.stats.kde2d) estimates the density with a Gaussian kernel and returns the `{x, y, z}` surface that `ContourChart` takes, so `ContourChart(data=kde2d(x, y))` is a density chart. Its options: - `bandwidth` sets how smooth the estimate is: a [BANDWIDTH](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) rule (Scott's by default, or Silverman's) or a number that replaces the rule's factor, where smaller values follow the points more closely. - `gridsize` sets the resolution of the surface. - `cut` extends the grid past the points by that many bandwidths, so the outer contours close instead of being clipped; `xlim` and `ylim` fix the grid instead, so several surfaces share one (as `season_density` does). The result is a probability density (it integrates to 1); multiplied by the number of points, it reads as points per unit area. The autumn sightings come from two groups: Scott's rule shows both, while a wide bandwidth (`2.0`) smooths them into one blob, the classic way a density estimate hides structure. ``` from datachart.constants import BANDWIDTH from datachart.utils.stats import kde2d autumn_east = [p["x"] for p in sightings["Autumn"]] autumn_north = [p["y"] for p in sightings["Autumn"]] Grid( [ [ ContourChart( # Scott's rule: two groups data=kde2d(autumn_east, autumn_north, bandwidth=BANDWIDTH.SCOTT, xlim=(0, 10), ylim=(0, 8)), filled=True, title="Scott's rule", aspect_ratio=ASPECT_RATIO.EQUAL, ), ContourChart( # a wide kernel: one blob, on the same map extent data=kde2d(autumn_east, autumn_north, bandwidth=2.0, xlim=(0, 10), ylim=(0, 8)), filled=True, title="bandwidth=2.0", aspect_ratio=ASPECT_RATIO.EQUAL, ), ] ], title="Autumn sightings, two bandwidths", xlabel="Distance east (km)", ylabel="Distance north (km)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Axis scales Some surfaces are sampled over values that span orders of magnitude, and a linear axis crams the interesting part into a corner. `scalex` and `scaley` take a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member. `sweep`, defined in a hidden cell, holds the illustrative validation loss of a model trained over a grid of learning rates (1e-5 to 1e-1) and weight decays (1e-6 to 1e-1), both sampled evenly on a log scale, the way hyperparameter searches are. On log axes the valley of good settings is a clear oval; on linear axes it would be squeezed against the left and bottom edges. ``` from datachart.constants import SCALE ContourChart( data=sweep, # both hyperparameters on a log scale scalex=SCALE.LOG, scaley=SCALE.LOG, filled=True, show_colorbars=True, colorbar={"label": "Validation loss"}, levels=[0.32, 0.35, 0.4, 0.5, 0.6, 0.8, 1.0, 1.4, 2.0], style={"plot_contour_cmap": COLORS.YlGnBu}, title="Validation loss over the hyperparameter grid", xlabel="Learning rate", ylabel="Weight decay", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Datetime axis A surface sampled over time, such as a measurement by date and depth or by date and elevation, belongs on a time axis. The `x` values may be real temporal objects (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`); they are placed at their elapsed time and the ticks pick readable labels for the span, while date strings are not parsed. `xticks_format` takes a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern, and `xticks`, `xmin`, `xmax`, reference lines and bands take datetimes as well. `snow`, defined in a hidden cell, holds the illustrative snow depth on the hill over one winter, by day and elevation: the snow line comes down through December and January, the pack is deepest in mid-February, and the lower slopes melt out first. ``` from datachart.constants import DATE_FORMAT ContourChart( # dates on the x-axis data=snow, filled=True, show_colorbars=True, colorbar={"label": "Snow depth (cm)"}, levels=[1, 20, 40, 60, 80, 100, 120, 140, 160], style={"plot_contour_cmap": COLORS.Blues}, # the month of each tick xticks_format=DATE_FORMAT.YEAR_MONTH, title="Snow depth on the hill, winter 2024/25", ylabel="Elevation (m)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Why Does Gradient Descent Crawl? (Log Surface, Level Rule, Labels, and a Panel) The [Rosenbrock function](https://en.wikipedia.org/wiki/Rosenbrock_function), `(1 - x)² + 100 (y - x²)²`, is the standard test surface for optimizers: a long, curved, flat-bottomed valley with the minimum at (1, 1), which gradient methods reach quickly but follow slowly. `rosenbrock` samples it on the square from -2 to 2 in x and -1 to 3 in y. Its values span six orders of magnitude, so the surface is drawn as `log(1 + z)`, which keeps the valley floor visible. `descent` traces 2000 steps of plain gradient descent (step size 0.001) from (-1.5, 2.5), computed in the hidden cell, with every 40th step kept. The Freedman-Diaconis rule cuts the surface densely enough for the narrow valley to get its own lines, and a `Panel` draws the path over the labeled iso-lines: the path drops into the valley within a few steps, then crawls along its floor towards the minimum. ``` from datachart.constants import LINE_MARKER, VALUE_FORMAT Panel( [ ContourChart( data=rosenbrock, subtitle="log(1 + Rosenbrock)", # dense levels, so the narrow valley gets its own lines levels=CONTOUR_LEVELS.FD, show_labels=True, valfmt=VALUE_FORMAT.DECIMAL, style={"plot_contour_cmap": COLORS.Viridis, "plot_contour_line_width": 0.8}, # point at the minimum texts={"text": "minimum (1, 1)", "x": 1.0, "y": -0.6, "target": (1, 1)}, ), { "figure": LineChart( data=descent, subtitle="gradient descent", style={"plot_line_color": "#c1121f", "plot_line_marker": LINE_MARKER.CIRCLE, "plot_line_width": 1.2}, ), # the path shares the surface's axes "y_axis": "left", }, ], title="Gradient descent on the Rosenbrock function", xlabel="x", ylabel_left="y", show_legend=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: How Cold Does the Wind Make It Feel? (Explicit Levels, Emphasis, Labels, and a Panel) The wind chill index, used by Environment Canada and the US National Weather Service since 2001, gives the temperature that feels the same on exposed skin in calm air: `13.12 + 0.6215 T - 11.37 V^0.16 + 0.3965 T V^0.16`, for the air temperature `T` in °C (up to 10 °C) and the wind speed `V` in km/h (from 5 km/h). `wind_chill` evaluates the formula on a grid of temperatures from -40 °C to 10 °C and wind speeds from 5 to 80 km/h. Environment Canada ties the risk of frostbite to the index: the risk is high from -28, very high from -40 and severe from -48, when exposed skin can freeze within minutes. A `Panel` draws the index twice: faint iso-lines every 5 degrees as the background, and the three thresholds highlighted and labeled. A note reads one point off the chart: -20 °C in a 40 km/h wind feels like about -34 °C. ``` background = ContourChart( data=wind_chill, subtitle="wind chill, every 5 °C", levels=list(range(-65, 15, 5)), emphasis=EMPHASIS.BACKGROUND, ) thresholds = ContourChart( data=wind_chill, subtitle="frostbite risk thresholds", # only the three thresholds, bold and labeled levels=FROSTBITE, emphasis=EMPHASIS.HIGHLIGHT, show_labels=True, valfmt="{x:.0f} °C", style={"plot_contour_color": "#1d3557"}, texts={"text": "-20 °C at 40 km/h\nfeels like -34 °C", "x": -8, "y": 70, "target": (-20, 40)}, ) Panel( [background, thresholds], title="Wind chill and the risk of frostbite", xlabel="Air temperature (°C)", ylabel_left="Wind speed (km/h)", show_legend=True, legend={"location": LEGEND_LOCATION.LOWER_RIGHT}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: Where Do the Penguin Species Overlap? (Densities over Points, Shared Levels, Panel and Grid) The [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset (Gorman, Williams and Fraser, 2014; CC0) records the flipper length and body mass of 342 penguins of three species on the Palmer Archipelago, Antarctica. `penguins` holds every penguin as a point (flipper length in mm, body mass in g), and `species_density` one density surface per species, estimated with `kde2d` on one shared grid (the range of all penguins, padded by 10%) and scaled to penguins per mm per kg. The question is which species these two measurements tell apart. The top row overlays two density outlines per species on the points with a `Panel`; the bottom row draws each species as a filled density with one shared `levels` list, so the same shade means the same density, in a `Grid`. Gentoo penguins stand apart, heavier and longer-flippered, while the Adelie and Chinstrap densities cover the same ground. ``` SPECIES_STYLE = [{"plot_contour_color": color} for color in ["#e76f51", "#8338ec", "#2a9d8f"]] DENSITY_LEVELS = [0.5, 1, 2, 3, 4, 5, 6, 7, 8] overlap = Panel( [ ScatterChart(data=penguins, subtitle="penguins", style={"plot_scatter_color": "#adb5bd"}), { # an outer and an inner outline per species "figure": ContourChart(data=species_density, subtitle=SPECIES, style=SPECIES_STYLE, levels=[0.5, 3]), "y_axis": "left", }, ], title="Density outlines over the penguins", xlabel="Flipper length (mm)", ylabel_left="Body mass (g)", show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_RIGHT}, ) def species_panel(index): # one species as a filled density, on the shared levels return ContourChart( data=species_density[index], filled=True, levels=DENSITY_LEVELS, style={"plot_contour_cmap": COLORS.YlGnBu}, title=SPECIES[index], xlabel="Flipper length (mm)", ylabel="Body mass (g)" if index == 0 else None, ) Grid( [ [overlap], [species_panel(0), species_panel(1), species_panel(2)], ], title="Where the penguin species overlap", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Hexbin Chart A hexbin chart is a scatter chart for when there are too many points to see. It tiles the plane with hexagons and colors each one by the number of points that fall in it, or by an aggregate of a value the points carry, so the density and the trends of thousands of points stay readable. This guide shows how to create hexbin charts with the [datachart.charts.HexbinChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.charts.HexbinChart) function, starting with the basics and building up to worked examples on illustrative data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-hexbin-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import HexbinChart, ScatterChart ``` ## Basics The examples in this guide share one dataset: 8,000 apartment listings of a mid-sized city. The listings are illustrative, generated with a seeded random generator in the hidden cell below, but they follow the shape of real rental markets: floor areas cluster around 60 m² with a long tail of large apartments, the rent grows with the area at a rate set by the district, and overpriced apartments stay on the market longer. `listings` holds three columns: `x` is the floor area of each listing (m²), `y` its monthly rent (€), and `c` the number of days it stayed on the market. `points` holds the `x` and `y` columns alone, for the charts that count listings, and `district` holds the district of each listing (0 for the outskirts, 1 for midtown, 2 for the center). The data is a dictionary of columns, one value per listing in each column: ``` {key: values[:5] for key, values in listings.items()} ``` **The problem.** Plotted as a scatter chart, the 8,000 listings merge into one dark smear. The chart shows where the listings are, but not where most of them are: a region with ten listings and a region with three hundred look the same. ``` ScatterChart( # one record per listing data=[{"x": x, "y": y} for x, y in zip(points["x"], points["y"])], ).show() ``` **Basic example.** A hexbin chart of the same points needs only the `data` argument. Every hexagon is colored by the number of listings in it, and the colorbar maps the colors back to counts. The dense core around 50 m² and 1,000 € now stands out from the thin tail of large apartments. Every hexagon of the tiling is drawn, the empty ones in the lowest color, so a few large, expensive apartments stretch the tiling over a lot of empty plane; the [Minimum count](#minimum-count) section trims it. ``` HexbinChart( # add the data to the chart data=points ).show() ``` ## Customizing the Hexbin Chart Every customization is either a keyword argument of `HexbinChart` or a `plot_hexbin_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------ | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | fix the axis range or the ticks | `xmin`, `xmax`, `ymin`, `ymax`, `xticks`, `yticks` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | format or rotate the tick labels | `xticks_format`, `yticks_format`, `xticklabels`, `xtickrotate` | [Title, axis labels and ticks](#title-axis-labels-and-ticks) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show the grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | caption, move, or hide the colorbar | `colorbar`, `valfmt`, `show_colorbars` | [Colorbar](#colorbar) | | make the hexagons larger or smaller | `gridsize` | [Grid size](#grid-size) | | leave the sparse hexagons blank | `mincnt` | [Minimum count](#minimum-count) | | spread heavy-tailed counts over the colors | `norm`, `vmin`, `vmax` | [Normalization](#normalization) | | color the hexagons by a value | `c` in `data`, `reduce` | [Aggregating a value](#aggregating-a-value) | | change the colormap or draw hexagon edges | `style={"plot_hexbin_cmap": ..., "plot_hexbin_edge_width": ...}` | [Hexagon style](#hexagon-style) | | highlight the densest hexagons | `emphasis_rule` | [Emphasis](#emphasis) | | mark a value or shade a range | `vlines`, `hlines`, `vspans`, `hspans` | [Reference lines and bands](#reference-lines-and-bands) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw each dataset in its own subplot | `data` as a list, `subplots`, `max_cols`, `sharex`, `sharey` | [Multiple Hexbin Charts](#multiple-hexbin-charts) | | draw points or a trend over the hexagons | `Panel` | [Composing with Panel](#composing-with-panel) | | add a legend | `Panel` with `show_legend`, `legend` | [Composing with Panel](#composing-with-panel) | | place the chart next to other charts | `Grid` | [Composing with Grid](#composing-with-grid) | | keep one unit equal on both axes | `aspect_ratio` | [Aspect ratio](#aspect-ratio) | | bin the points along a time axis | `date` objects as `x`, `xticks_format` | [Datetime axis](#datetime-axis) | | render the chart in another theme | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reduce` | [`HEXBIN_REDUCE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HEXBIN_REDUCE) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `valfmt` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | The full list of style attributes is in the [datachart.typings.HexbinStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.typings.HexbinStyleAttrs) type; the full list of parameters is in the [datachart.charts.HexbinChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.charts.HexbinChart) reference. ### Title, axis labels and ticks A hexbin chart shows two quantities at once, and without axis labels the reader cannot tell which is which; `title`, `xlabel` and `ylabel` say it. `xmin`, `xmax`, `ymin` and `ymax` fix the axis range, which here cuts off the thin tail of apartments above 160 m² so the dense part gets the room. `xticks` and `yticks` place the ticks, and `yticks_format` formats their labels with a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) member or any `"{x:.1f}"` style string, so the rent axis reads in thousands with a separator. `xticklabels`, `xtickrotate` and their `y` counterparts replace or tilt the labels when they need it. ``` from datachart.constants import VALUE_FORMAT HexbinChart( data=points, # add the title title="Apartment listings", # add the x and y axis labels xlabel="Floor area (m²)", ylabel="Rent (€/month)", # focus on the apartments up to 160 m² xmin=15, xmax=160, ymin=0, ymax=3500, # one tick every 20 m², rents with a thousands separator xticks=[20, 40, 60, 80, 100, 120, 140, 160], yticks_format=VALUE_FORMAT.THOUSANDS, ).show() ``` ### Figure size and grid The default figure is nearly square, while a chart in a report usually spans the page width. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE). The grid is off by default, because the hexagons would cover it; `show_grid` with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member draws it over the hexagons, which helps to read off the rent of the dense core. ``` from datachart.constants import FIG_SIZE, SHOW_GRID HexbinChart( data=points, title="Apartment listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the rent axis show_grid=SHOW_GRID.Y, ).show() ``` ### Colorbar The colorbar is the legend of a hexbin chart: without a caption, the reader does not know that the colors count listings. `colorbar` takes a [ColorbarSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ColorbarSettingAttrs) dictionary: `label` captions the bar, `location` moves it to any edge with a [COLORBAR_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION) member, `ticks` places its ticks, and `format` formats their labels (the `valfmt` parameter does the same when `format` is not set). A bar above a wide chart takes less width from the hexagons than one beside it. `show_colorbars=False` hides the bar, which suits a chart whose colors are explained elsewhere, as in the [Composing with Panel](#composing-with-panel) section. ``` from datachart.constants import COLORBAR_LOCATION HexbinChart( data=points, # a captioned colorbar above the chart, with integer ticks colorbar={ "label": "Listings per hexagon", "location": COLORBAR_LOCATION.TOP, "ticks": [0, 50, 100, 150, 200], "format": VALUE_FORMAT.INTEGER, }, title="Apartment listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Grid size The hexagon size decides what the chart can show. Large hexagons hold many points each, so the colors are smooth but the shape is coarse; small hexagons resolve finer structure until each holds too few points to color reliably. `gridsize` sets the number of hexagons across the x-axis (30 by default, from the `plot_hexbin_gridsize` style attribute). With 12 hexagons the listings reduce to a rough diagonal; with 60 the core shows its finer shape, at the price of a noisier color. ``` for gridsize in [12, 60]: HexbinChart( data=points, # the number of hexagons across the x-axis gridsize=gridsize, title=f"Apartment listings, {gridsize} hexagons across", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Minimum count Every hexagon of the tiling is drawn by default, so the tiling fills the whole bounding box of the points and empty plane looks like a region with few listings. `mincnt` leaves a hexagon blank unless at least that many points fall in it. `mincnt=1` draws only the hexagons that hold a listing, which shows the real outline of the data; a higher value also hides the hexagons with too few listings to trust. ``` HexbinChart( data=points, # blank the hexagons with fewer than five listings mincnt=5, title="Apartment listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Normalization Counts are heavy-tailed: a few hexagons in the core hold hundreds of listings while most hold a handful, so on a linear color scale nearly every hexagon draws in the palest shade. `norm` changes how the values map to colors with a [NORMALIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) member; `NORMALIZE.LOG` spreads the counts, so the tail of the distribution becomes visible. A log scale needs positive values, so pair it with `mincnt=1`. `vmin` and `vmax` pin the color range instead of taking it from the data, which keeps the colors of several charts comparable. ``` from datachart.constants import NORMALIZE HexbinChart( data=points, # log-scaled counts, so the sparse tail stays visible norm=NORMALIZE.LOG, mincnt=1, # the color range, from one listing to 300 vmin=1, vmax=300, colorbar={"label": "Listings (log scale)"}, title="Apartment listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Aggregating a value Density is one question; the other is how a third value varies across the plane. With a `c` column in the data, each hexagon shows an aggregate of the `c` values of its points instead of the count. `reduce` picks the aggregate with a [HEXBIN_REDUCE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HEXBIN_REDUCE) member: the mean by default, or the sum, median, minimum, or maximum. Only the hexagons holding a point are drawn, since an empty hexagon has nothing to aggregate, and an aggregate of one or two points is noisy, so `mincnt` hides those. Here `c` is the number of days a listing stayed on the market: the mean rises with the floor area and, at every area, with the rent. ``` from datachart.constants import HEXBIN_REDUCE HexbinChart( # x, y, and the per-point c to aggregate data=listings, # the mean of the c values in every hexagon reduce=HEXBIN_REDUCE.MEAN, # blank the hexagons with fewer than three listings mincnt=3, colorbar={"label": "Mean days on the market"}, title="How long apartments take to rent", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` The other aggregates answer other questions. The median resists the few listings that stayed on the market for months; the maximum finds exactly those listings, which the mean smooths away: ``` HexbinChart( data=listings, # the longest-listed apartment in every hexagon reduce=HEXBIN_REDUCE.MAX, mincnt=3, colorbar={"label": "Longest time on the market (days)"}, title="The slowest listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Hexagon style The colormap sets the tone of the chart: a sequential one for counts and other values that only grow, a diverging one for values with a meaningful middle. `plot_hexbin_cmap` takes a [COLORS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORS) member, a matplotlib colormap name, or a list of colors (the [Colormaps](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/colormaps/index.md) guide shows them all). Where neighboring hexagons have similar colors, they run together into a blur; `plot_hexbin_edge_width` and `plot_hexbin_edge_color` draw a thin edge that separates the tiles, and `plot_hexbin_alpha` makes the hexagons translucent. The attributes are listed in [HexbinStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.typings.HexbinStyleAttrs). ``` from datachart.constants import COLORS HexbinChart( data=listings, reduce=HEXBIN_REDUCE.MEAN, mincnt=3, # a warm colormap and white edges between the hexagons style={ "plot_hexbin_cmap": COLORS.YlOrRd, "plot_hexbin_edge_width": 0.6, "plot_hexbin_edge_color": "#FFFFFF", }, gridsize=20, colorbar={"label": "Mean days on the market"}, title="How long apartments take to rent", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Emphasis Sometimes the question is not the whole distribution but a part of it: where most listings are, or which sizes and prices rent slowest. `emphasis_rule` outlines the hexagons whose value (the count, or the aggregate of `c`) matches a one-key rule and fades the rest: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive). A hexbin chart is one colormapped layer, so it does not take the per-series `emphasis` parameter of other charts. The [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/#emphasis-picked-by-a-rule) guide covers the rule on every chart. The chart below outlines the hexagons where the mean wait is above 40 days: the overpriced apartments. ``` HexbinChart( data=listings, mincnt=3, # outline the hexagons with a mean wait above 40 days, fade the rest emphasis_rule={"above": 40}, colorbar={"label": "Mean days on the market"}, title="Where apartments wait longest", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Reference lines and bands Reference lines and bands give the hexagons a frame to be read against. `vlines` and `hlines` draw a line at an x or y value, such as the median area and rent, which split the listings into four quadrants; `vspans` and `hspans` shade a range, such as a renter's budget. Each takes a dictionary or a list of them, with the position, an optional `label` and a `style`; the keys are listed in [VLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineSettingAttrs), [HLineSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineSettingAttrs), [VSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanSettingAttrs) and [HSpanSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanSettingAttrs). A band needs at least one bound; an omitted bound runs to the edge of the axes. The chart below crosses the medians with dashed lines and shades a budget of 800 to 1,200 €, which shows how much of the market around the median a renter on that budget can reach. ``` from datachart.constants import LINE_STYLE # a translucent band drawn over the hexagons BUDGET_STYLE = {"plot_hspan_color": "#2a9d8f", "plot_hspan_alpha": 0.25, "plot_hspan_zorder": 3} HexbinChart( data=points, # the median area and rent, as dashed cross-hairs vlines={ "x": float(np.median(points["x"])), "style": {"plot_vline_style": LINE_STYLE.DASHED, "plot_vline_color": "#333333"}, }, hlines={ "y": float(np.median(points["y"])), "style": {"plot_hline_style": LINE_STYLE.DASHED, "plot_hline_color": "#333333"}, }, # a rent budget of 800 to 1,200 € hspans={"ymin": 800, "ymax": 1200, "style": BUDGET_STYLE}, mincnt=1, title="Apartment listings against a rent budget", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` A standalone hexbin chart has no legend, so a line's `label` only shows when the chart is composed in a `Panel` with a legend (see [Composing with Panel](#composing-with-panel)); on its own, a note from the next section names a line better. ### Text annotations A note on the chart says what the reader should see. `texts` places text at a position in data coordinates (or in axes fractions with `"coords": "axes"`), and an optional `target` draws a connector to a point; the [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement and styling. The note below points at the thin tail of large, expensive apartments, which is easy to overlook. ``` HexbinChart( data=points, mincnt=1, norm=NORMALIZE.LOG, # a note in the empty corner, pointing at the tail texts={ "text": "a few large, expensive\napartments", "x": 0.62, "y": 0.2, "coords": "axes", "target": (160, 2800), }, title="Apartment listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ## Multiple Hexbin Charts To compare several groups of points, pass a list of datasets to `data` and set `subplots=True`. Hexagons are opaque, so several datasets on one axes would cover each other; subplots keep each group visible. `subtitle` titles the subplots, while `title`, `xlabel` and `ylabel` stay global; `max_cols` limits the subplots per row, and `sharex` and `sharey` put the subplots on one axis range. The per-chart parameters (`subtitle`, `style`, `gridsize`, `reduce`, `mincnt`, `norm`, `vmin`, `vmax`, `valfmt`, `colorbar`) take either one value for every chart or a list with one value per chart. Each subplot scales its colors to its own data, so the same `vmin` and `vmax` on every subplot are what makes the shades comparable. `points_by_district`, defined in a hidden cell, splits the listings by district: the center has fewer listings, and they sit higher on the rent axis. ``` HexbinChart( # one dataset per district data=points_by_district, # one subplot title per chart subtitle=DISTRICTS, # one subplot per district, in one column subplots=True, max_cols=1, # the same axes for every district sharex=True, sharey=True, # the same color range for every district vmin=1, vmax=60, mincnt=1, gridsize=40, title="Apartment listings by district", xlabel="Floor area (m²)", ylabel="Rent (€/month)", xmax=160, ymax=3500, figsize=FIG_SIZE.FULL_TALL, ).show() ``` ### Composing with Panel A hexbin chart shows the crowd; a few points or a line on top of it show where individuals or a model sit in that crowd. [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) overlays figures on shared axes, and the [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide covers it in full. Here a [ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.charts.ScatterChart) of five listings a renter has shortlisted sits on the hexagons, which shows whether they are typical or overpriced; the shortlist spans a much smaller rent range than the market, so a `"y_axis"` on both figures pins them to one left axis instead of giving the shortlist a second one. The hexbin figure hides its colorbar, and the panel's `show_legend` and `legend` label the shortlist and the rent budget, which a standalone hexbin chart cannot do. ``` from datachart.constants import LEGEND_LOCATION from datachart.utils import Panel shortlist = [ {"x": 48, "y": 820}, {"x": 55, "y": 1150}, {"x": 62, "y": 990}, {"x": 70, "y": 1550}, {"x": 85, "y": 1380}, ] Panel( [ { "figure": HexbinChart( data=points, mincnt=1, norm=NORMALIZE.LOG, # the budget band, labeled in the panel legend hspans={"ymin": 800, "ymax": 1200, "label": "Budget", "style": BUDGET_STYLE}, style={"plot_hexbin_cmap": COLORS.Greys}, show_colorbars=False, ), "y_axis": "left", }, # the shortlisted listings, as points over the hexagons { "figure": ScatterChart( data=shortlist, subtitle="Shortlisted", style={"plot_scatter_size": 60, "plot_scatter_color": "#d62828"}, ), # the same rent axis for both figures "y_axis": "left", }, ], title="A shortlist against the market", xlabel="Floor area (m²)", ylabel_left="Rent (€/month)", xmax=160, ymax=3500, # a legend for the band and the points show_legend=True, legend={"location": LEGEND_LOCATION.UPPER_LEFT}, figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Composing with Grid A hexbin chart answers a question about two variables together, and a histogram of one of them often belongs beside it. [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges figures in rows, each keeping its own axes; the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers layouts. The counts span the top row, and the days on the market and a histogram of the rents share the bottom one. ``` from datachart.charts import Histogram from datachart.utils import Grid Grid( [ [ HexbinChart( data=points, mincnt=1, norm=NORMALIZE.LOG, title="Listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", ) ], [ HexbinChart( data=listings, mincnt=3, colorbar={"location": COLORBAR_LOCATION.BOTTOM}, title="Mean days on the market", xlabel="Floor area (m²)", ylabel="Rent (€/month)", ), Histogram( data=[{"x": value} for value in listings["y"]], title="Rents", xlabel="Rent (€/month)", ylabel="Listings", ), ], ], figsize=FIG_SIZE.FULL_TALL, ).show() ``` ## Additional Features ### Aspect ratio By default the axes stretch to fill the figure, so one unit on the x-axis can be longer than one unit on the y-axis. For two variables in different units that is fine; for a map it distorts the city. `aspect_ratio` with [ASPECT_RATIO.EQUAL](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) keeps one unit equal on both axes. `locations`, defined in a hidden cell, holds the illustrative position of every listing in kilometers east and north of the city center, with the listings bunched in the old town and two other neighborhoods. ``` from datachart.constants import ASPECT_RATIO HexbinChart( data=locations, # one kilometer is as long on both axes aspect_ratio=ASPECT_RATIO.EQUAL, mincnt=1, xmin=-10, xmax=10, ymin=-8, ymax=8, title="Where the listings are", xlabel="km east of the center", ylabel="km north of the center", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Datetime axis Points spread over time are often too many for a scatter chart too: every listing of a year, every transaction of a quarter. An `x` column of real temporal objects (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) bins the points along a time axis. `xticks_format` formats the ticks with a [DATE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) member or any `strftime` pattern, and `xticks`, `xmin`, `xmax`, reference lines and bands take dates as well; date strings are not parsed and draw as categories. `rents_by_date`, defined in a hidden cell, dates every listing to the day it was posted in 2024, with an illustrative seasonal pattern: many listings go up in late summer, ahead of the academic year that starts on 1 October. The hexagons show the rush as a dense block of listings in September, at the usual rents. ``` HexbinChart( data=rents_by_date, mincnt=1, gridsize=24, # one tick per quarter, labeled with the month name xticks=[date(2024, month, 1) for month in (1, 4, 7, 10)] + [date(2025, 1, 1)], xticks_format="%b", # the start of the academic year vlines={"x": date(2024, 10, 1), "style": {"plot_vline_style": LINE_STYLE.DASHED, "plot_vline_color": "#333333"}}, ymax=2500, title="Rents by the day the listing was posted, 2024", xlabel="Posted", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Themes A theme sets the colormap and the furniture of every chart at once; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows each. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) and a [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) member, and reset the configuration afterwards so the following charts draw in the default. Style is read when the chart is built, so the figure keeps the theme after the reset. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.INK) figure = HexbinChart( data=points, mincnt=1, norm=NORMALIZE.LOG, title="Apartment listings", xlabel="Floor area (m²)", ylabel="Rent (€/month)", figsize=FIG_SIZE.FULL_SHORT, ) config.reset_config() figure.show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question about the rental market. The data is the illustrative, seeded listings of this guide, with the extra columns each example needs derived in a hidden cell. ### Example 1: Where Renting Is Cheap per Square Meter (Aggregation, a Diverging Colormap, and a Trend) Renters compare apartments of different sizes by the rent per square meter. The hidden cell derives it for every listing as `per_m2`, the city-wide mean as `CITY_MEAN`, and a straight-line fit of the rent on the floor area as `fit`. Colored by the mean rent per square meter, the hexagons show what the counts hide: small apartments cost the most per square meter, and at every size the pricier listings sit above the fitted line. A diverging colormap centered on the city-wide mean by `vmin` and `vmax` splits the plane into the cheaper-than-average blues and the pricier reds, and a `Panel` lays the fitted rent over the hexagons, with a legend that labels it. ``` from datachart.charts import LineChart Panel( [ HexbinChart( data={"x": listings["x"], "y": listings["y"], "c": per_m2}, reduce=HEXBIN_REDUCE.MEAN, mincnt=3, gridsize=40, # a diverging colormap centered on the city-wide mean; # the "_r" suffix reverses it, so the cheap side is blue style={"plot_hexbin_cmap": "RdBu_r"}, vmin=CITY_MEAN - 6, vmax=CITY_MEAN + 6, colorbar={"label": "Mean rent (€/m²)", "format": VALUE_FORMAT.INTEGER}, ), LineChart( data=fit, subtitle=f"Fitted rent ({slope:.1f} €/m² + {intercept:.0f} €)", style={"plot_line_color": "#1F1F1F", "plot_line_style": LINE_STYLE.DASHED}, ), ], title="Rent per square meter", xlabel="Floor area (m²)", ylabel_left="Rent (€/month)", xmin=15, xmax=160, ymin=0, ymax=3500, show_legend=True, legend={"location": LEGEND_LOCATION.UPPER_LEFT}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Where the Listings Cluster (Equal Aspect, Log Counts, Emphasis, and Notes) `locations` from the [Aspect ratio](#aspect-ratio) section places every listing on an illustrative map of the city, and a renter looking for a flat wants to know where the offer is. Log counts keep the thin suburbs visible next to the busy neighborhoods, the equal aspect ratio keeps the map undistorted, `emphasis_rule` outlines the 25 fullest hexagons, and a note names each neighborhood, placed in empty space at `LABEL_AT` and pointing at the center stored in `NEIGHBORHOODS`. ``` HexbinChart( data=locations, mincnt=1, gridsize=40, norm=NORMALIZE.LOG, aspect_ratio=ASPECT_RATIO.EQUAL, # outline the 25 fullest hexagons emphasis_rule={"top": 25}, # one note per neighborhood, pointing at its center texts=[ {"text": name, "x": x, "y": y, "target": NEIGHBORHOODS[name][0]} for name, (x, y) in LABEL_AT.items() ], colorbar={"label": "Listings (log scale)"}, xmin=-10, xmax=10, ymin=-8, ymax=8, title="Where the listings cluster", xlabel="km east of the center", ylabel="km north of the center", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: Which Apartments Rent Fastest, by District (Log Counts, a Shared Color Range, and a Grid) `by_district` from the [Multiple Hexbin Charts](#multiple-hexbin-charts) section splits the listings into the three districts, with the days on the market as `c`. Each row of the figure is one district. The left chart counts its listings on a log scale, so the sparse edges stay visible next to the dense core; the right chart shows the median days on the market, under one `vmin` and `vmax` for every district, so the same shade means the same wait everywhere. Read down the right column: the center's apartments are fewer, pricier, and slower to rent at every size, while the outskirts rent their small apartments fastest. A `Grid` lays out the six charts, and its `sharex`, `sharey`, `xlabel` and `ylabel` give them one frame. ``` def district_row(name, data): # the listings and the median wait of one district, on the same axes frame = dict(gridsize=18, xmin=15, xmax=160, ymin=0, ymax=3500) count = HexbinChart( data={"x": data["x"], "y": data["y"]}, norm=NORMALIZE.LOG, mincnt=1, vmin=1, vmax=300, title=f"{name}: listings", **frame, ) wait = HexbinChart( data=data, reduce=HEXBIN_REDUCE.MEDIAN, mincnt=3, # the same range in every district, so the shades compare vmin=5, vmax=45, style={"plot_hexbin_cmap": COLORS.YlOrRd}, title=f"{name}: median days on the market", **frame, ) return [count, wait] Grid( [district_row(name, data) for name, data in zip(DISTRICTS, by_district)], title="Which apartments rent fastest", xlabel="Floor area (m²)", ylabel="Rent (€/month)", sharex=True, sharey=True, figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Sankey Chart A Sankey chart shows how a quantity splits and merges as it flows between stages: each stage is a column of nodes and each flow a ribbon as thick as its value, so the chart answers *where does it go, and where is it lost*. This guide shows how to create Sankey charts with the [datachart.charts.SankeyChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.charts.SankeyChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-sankey-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import SankeyChart ``` ## Basics The examples in this guide share one dataset: the energy flow of a country, from the primary sources (oil, natural gas, coal, nuclear, wind, biomass, hydro and solar) through electricity generation to the four end-use sectors (transport, industrial, residential and commercial), and from there to the energy that does useful work (*energy services*) and the energy lost as waste heat (*rejected energy*). The figures are **illustrative**: they are shaped like the national energy flow charts that the Lawrence Livermore National Laboratory publishes for the United States, but they are not a published table. They are scaled so the primary sources add up to 100, so every value reads as a percentage of the primary energy. The data lives in a hidden cell as `energy`. The story is the one those charts are known for: most of the energy a country uses is lost before it does anything useful. The data is one dictionary with a `links` list. Each link is a record with the `source` node, the `target` node, and the `value` that flows between them; a node is just its name, which is also its label: ``` energy["links"][:3] ``` Every `value` must be above zero, a link cannot join a node to itself, and the links cannot form a cycle; each of these raises a `ValueError`. **Basic example.** Only the `data` argument is required. Each node's column is its longest path from any source, so the sources sit on the left and the two outcomes on the right. The sectors that draw on electricity land in the third column, but transport takes no electricity here, so it sits in the second column beside it; [Node columns](#node-columns) moves it. A node's height is the larger of what flows in and what flows out, and each ribbon takes the color of the node it leaves: ``` SankeyChart( # add the data to the chart data=energy ).show() ``` ## Customizing the Sankey Chart Every customization is either a keyword argument of `SankeyChart` or a `plot_sankey_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | -------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title | `title` | [Title and figure size](#title-and-figure-size) | | resize the figure | `figsize` | [Title and figure size](#title-and-figure-size) | | set the columns or reorder the nodes | `nodes` | [Node columns](#node-columns) | | head the columns | `column_labels` | [Column labels and ribbon values](#column-labels-and-ribbon-values) | | write the flow values on the ribbons | `show_values`, `value_format` | [Column labels and ribbon values](#column-labels-and-ribbon-values) | | color the ribbons by target | `style={"plot_sankey_link_color": "target"}` | [Node and ribbon style](#node-and-ribbon-style) | | change the node width, gaps, or stroke | `style={"plot_sankey_node_width": ..., "plot_sankey_node_pad": ...}` | [Node and ribbon style](#node-and-ribbon-style) | | grey the ribbons, or outline the nodes | `style={"plot_sankey_link_color": "grey", "plot_sankey_node_fill": False}` | [Grey ribbons and emphasis](#grey-ribbons-and-emphasis) | | change the halo behind the labels | `style={"plot_sankey_label_halo_width": ...}` | [Grey ribbons and emphasis](#grey-ribbons-and-emphasis) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw several Sankeys in one figure | `data` as a list, `subtitle`, `max_cols` | [Subplots](#subplots) | | arrange a Sankey next to other charts | `Grid` | [Composing with Grid](#composing-with-grid) | | restyle every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------- | --------------------------------------------------------------------------------------------------------------------- | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | The full list of style attributes is in the [datachart.typings.SankeyStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.typings.SankeyStyleAttrs) type; the full list of parameters is in the [datachart.charts.SankeyChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.charts.SankeyChart) reference. ### Title and figure size A Sankey chart without a title leaves the reader guessing what flows; `title` says it. A Sankey has no axes, so there are no axis labels to set: the node names carry the meaning. With eight sources and four sectors the default figure is crowded, and `figsize` gives the nodes room: a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. ``` from datachart.constants import FIG_SIZE SankeyChart( data=energy, # add the title title="Where a country's energy goes", # a full-width figure with room for the labels figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Node columns The inferred layout splits the sectors over two columns and keeps the nodes in the order they first appear in the links, which is rarely the layout that tells the story. `nodes` sets the columns yourself: a list of columns, left to right, each a list of node names, top to bottom. It must name every node in the links exactly once, and it fixes the vertical order too, so it is the way to sort the nodes. The example puts all four sectors in one column, ranks the sources by size, puts the sectors in the order of their intake, and keeps *Energy services* above *Rejected energy*. A node that stops early (a leaf reached in one hop) stays in the column it was reached in by default; with `nodes`, it can sit in any column to the right of its sources. ``` # largest source first; the order within a column is top to bottom ENERGY_NODES = [ ["Oil", "Natural gas", "Coal", "Nuclear", "Wind", "Biomass", "Hydro", "Solar"], ["Electricity"], ["Transport", "Industrial", "Residential", "Commercial"], ["Energy services", "Rejected energy"], ] SankeyChart( data=energy, # set the columns and the order of the nodes nodes=ENERGY_NODES, title="Where a country's energy goes", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Column labels and ribbon values Four columns of names do not say what each column stands for; `column_labels` heads them, one label per column, left to right, in the subtitle style. When the exact amounts matter, `show_values` writes each flow's value at the end of its ribbon, just before the node it enters, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant (`VALUE_FORMAT.THOUSANDS` for large counts, as in [Example 2](#example-2-where-does-a-signup-funnel-leak-explicit-columns-grey-ribbons-and-a-note)) or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. The values here are already percentages of the primary energy, so a positional `"{:.0f}%"` string appends the sign (`VALUE_FORMAT.PERCENT` would multiply by 100). A ribbon too thin for its value slides the value along the ribbon to the first clear spot, keeping clear of the node bars; where the chart is too dense to leave any clear spot, the value is left out rather than written over a bar or another value. ``` from datachart.constants import VALUE_FORMAT SankeyChart( data=energy, nodes=ENERGY_NODES, # head the four columns column_labels=["Source", "Conversion", "Sector", "Outcome"], # write each flow as a share of the primary energy show_values=True, value_format="{:.0f}%", title="Where a country's energy goes", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Node and ribbon style The ribbons take the color of the node they leave, which answers *where does this flow come from*. `plot_sankey_link_color` switches the question: `"target"` colors each ribbon like the node it enters, so the two outcomes read across the whole chart and the color of the rejected energy shows how much of every sector's intake is lost. `plot_sankey_link_alpha` sets the ribbon alpha. `plot_sankey_node_width` is the width of the node bars and `plot_sankey_node_pad` the vertical room shared by the gaps of the tallest column, both as fractions of the drawing; `plot_sankey_node_edge_color` and `plot_sankey_node_edge_width` draw the stroke around every node. The attributes are listed in [datachart.typings.SankeyStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.typings.SankeyStyleAttrs), and any attribute left out keeps the value of the active theme. ``` SankeyChart( data=energy, nodes=ENERGY_NODES, style={ # ribbons in the color of the node they enter "plot_sankey_link_color": "target", "plot_sankey_link_alpha": 0.5, # wider nodes, more room between them, a dark stroke "plot_sankey_node_width": 0.05, "plot_sankey_node_pad": 0.2, "plot_sankey_node_edge_color": "#333333", "plot_sankey_node_edge_width": 0.8, }, title="Where a country's energy goes", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Grey ribbons and emphasis Other charts point the reader at a few marks with `emphasis`, but a Sankey has no series to highlight or mute, so passing `emphasis` raises a `ValueError` (the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers the charts that support it). The quiet alternative is `"grey"` for `plot_sankey_link_color`: the ribbons turn neutral and only the nodes carry color, which suits a chart about the stages rather than the individual flows. `plot_sankey_node_fill=False` draws the nodes as outlines, and `plot_sankey_label_halo_width` sets the halo, in the background color, that keeps the labels readable over the ribbons; `0` drops it. ``` SankeyChart( data=energy, nodes=ENERGY_NODES, style={ # neutral ribbons, outlined nodes "plot_sankey_link_color": "grey", "plot_sankey_node_fill": False, "plot_sankey_node_edge_color": "#333333", "plot_sankey_node_edge_width": 1.0, # a wider halo behind the labels "plot_sankey_label_halo_width": 3, }, title="Where a country's energy goes", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Text annotations A Sankey shows the losses; a note states the number. `texts` places text on the chart ([TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs)). The columns span `0` to `1` horizontally and the tallest column `0` to `1` vertically, so data coordinates and axes fractions (`"coords": "axes"`) are nearly the same thing. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connectors and styling. The note sums the rejected energy from the data, and its `target` draws a connector to the *Rejected energy* node. ``` rejected = sum(link["value"] for link in energy["links"] if link["target"] == "Rejected energy") SankeyChart( data=energy, nodes=ENERGY_NODES, style={"plot_sankey_link_color": "target"}, # the share of the primary energy lost as waste heat texts={ "text": f"{rejected}% of the primary energy\nis lost as waste heat", "x": 0.45, "y": 0.03, "target": (0.965, 0.08), }, title="Where a country's energy goes", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Multiple Sankey Charts ### Subplots Two flows compare best in one figure. A list of `{"links": [...]}` dictionaries draws one Sankey per subplot; there is no overlay of two Sankeys on one drawing, so `subplots` is implied. `subtitle` titles each Sankey, `title` stays global, and `max_cols` limits the Sankeys per row. `energy_2050`, defined in a hidden cell, is an equally **illustrative** future for the same country: more wind and solar, electric transport and heating, and less primary energy (80 instead of 100, on the same scale). Stacked one above the other, the two charts show the future delivering more energy services (39 instead of 31) from less primary energy, because less of it is burned. `nodes` and `column_labels` apply to every subplot, so the example leaves them out. ``` SankeyChart( # one Sankey per scenario data=[energy, energy_2050], subtitle=["Today", "2050 scenario"], # one above the other max_cols=1, title="Energy flow today and in a 2050 scenario", figsize=FIG_SIZE.FULL_TALL, ).show() ``` ### Composing with Grid A Sankey owns its drawing: its 0 to 1 layout is not a coordinate space another chart can share, so [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) rejects a Sankey figure. [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges it next to other charts, each in its own cell, and a `layout_spec` lets a cell span more than one row or column; the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers the layout options. The Sankey shows where the energy goes, but not how efficient each sector is, so a [BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.charts.BarChart) below it prints the share of each sector's intake that becomes useful work, computed from the same links: transport, the largest sector, wastes the most. ``` from datachart.charts import BarChart from datachart.constants import ORIENTATION from datachart.utils import Grid SECTORS = ["Transport", "Industrial", "Residential", "Commercial"] def efficiency(flows, sector): # the share of a sector's intake that becomes energy services intake = sum(link["value"] for link in flows["links"] if link["target"] == sector) useful = sum( link["value"] for link in flows["links"] if link["source"] == sector and link["target"] == "Energy services" ) return useful / intake flows = SankeyChart( data=energy, nodes=ENERGY_NODES, style={"plot_sankey_link_color": "target"}, title="Where the energy goes", ) sector_efficiency = BarChart( # reversed, so the first sector ends up at the top data=[{"label": s, "y": 100 * efficiency(energy, s)} for s in SECTORS[::-1]], orientation=ORIENTATION.HORIZONTAL, show_values=True, value_format="{:.0f}%", title="Useful share of the intake", xlabel="%", xmin=0, xmax=100, ) Grid( [ # the Sankey spans two of the three rows {"figure": flows, "layout_spec": {"row": 0, "col": 0, "rowspan": 2, "colspan": 1}}, {"figure": sector_efficiency, "layout_spec": {"row": 2, "col": 0, "rowspan": 1, "colspan": 1}}, ], figsize=FIG_SIZE.FULL_TALL, ).show() ``` ## Additional Features ### Themes A theme sets the palette, the node stroke, the ribbon alpha and the fonts of every chart at once, which is the way to restyle a whole document. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) and a [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) member, as the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide shows; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows every chart under each theme. Style is resolved when the chart is created, so a theme set before the call and reset after it applies to that chart alone. The `QUILL` theme draws the nodes as ink outlines over faint ribbons. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.QUILL) figure = SankeyChart( data=energy, nodes=ENERGY_NODES, column_labels=["Source", "Conversion", "Sector", "Outcome"], title="Where a country's energy goes", figsize=FIG_SIZE.FULL_MEDIUM, ) config.reset_config() figure.show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question about where something goes. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Where Do Two Annotators Disagree? (Inferred Columns, Column Labels, and Ribbons by Source) Two annotators labeled the same 150 sentences as positive, neutral or negative, and an adjudicator settled the final label. The counts in `agreement` are **illustrative**. The node names carry the annotator (`pos (A)`, `pos (B)`), so the same label becomes a separate node in each column, and the inferred layout puts A, B and the final label in three columns. With the ribbons colored by their source, the wide ribbons are the agreements and the thin crossing ones the disagreements: A's neutral sentences scatter the most, and the final column shows that the adjudicator mostly kept B's label. ``` SankeyChart( data=agreement, # the inferred columns, headed column_labels=["Annotator A", "Annotator B", "Final label"], show_values=True, title="Sentiment labels from annotator A to B to the final label", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Where Does a Signup Funnel Leak? (Explicit Columns, Grey Ribbons, and a Note) A thousand visitors either bounce or sign up; the signups activate or churn, and the activated users pay or stay on the free tier. The counts in `FUNNEL` are **illustrative**. By default each drop-off sits in the column where it happens, and the explicit `nodes` keep that layout while putting the continuing flow on top at every stage, so the funnel narrows along the top edge and the leaks fall away below it. Grey ribbons leave the color to the stages, the values print with a thousands separator, and a note states the conversion, computed from the data. ``` visitors = sum(v for s, t, v in FUNNEL if s == "Visited") paying = sum(v for s, t, v in FUNNEL if t == "Paid") SankeyChart( data=funnel, # the continuing flow on top, the drop-off below it nodes=[["Visited"], ["Signed up", "Bounced"], ["Activated", "Churned"], ["Paid", "Free tier"]], column_labels=["Visit", "Signup", "Activation", "Plan"], show_values=True, value_format=VALUE_FORMAT.THOUSANDS, style={"plot_sankey_link_color": "grey"}, texts={ "text": f"{paying / visitors:.0%} of visitors pay", "x": 0.98, "y": 0.02, "coords": "axes", "style": {"plot_text_halign": "right"}, }, title="Where the signup funnel leaks", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: Who Survived the Titanic? (Explicit Order, Values, and a Grid with a Bar Chart) `TITANIC` holds the 2,201 people aboard the Titanic, counted by class (first, second, third or crew), sex, and whether they survived; the counts are the classic `Titanic` table shipped with R, with children and adults combined. The Sankey follows everyone from their class through their sex to the outcome, with the women above the men and the survivors above the lost, and prints the counts. A Sankey shows the sizes of the flows but not the rates, so a [BarChart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) of the survival rate per class goes beside it, and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) sets the two in one figure: most crew members and third-class passengers were lost, and first class had the best odds. ``` voyage = SankeyChart( data=titanic, # women above men, survivors above the lost nodes=[CLASSES, SEXES, ["Survived", "Lost"]], column_labels=["Class", "Sex", "Outcome"], show_values=True, title="Everyone aboard", ) rates = BarChart( data=survival_rate, show_values=True, value_format="{:.0f}%", title="Survival rate by class", ylabel="%", ymin=0, ymax=100, ) Grid( [ # the Sankey spans two of the three columns {"figure": voyage, "layout_spec": {"row": 0, "col": 0, "rowspan": 1, "colspan": 2}}, {"figure": rates, "layout_spec": {"row": 0, "col": 2, "rowspan": 1, "colspan": 1}}, ], title="Who survived the Titanic", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` # Network Chart A network chart shows who connects to whom: each node is an entity, each edge a relationship, and the picture answers *who is central, which clusters form, and who bridges them*. This guide shows how to create network charts with the [datachart.charts.NetworkChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.charts.NetworkChart) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-network-chart), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import NetworkChart ``` ## Basics The examples in this guide share one dataset: the marriage ties among fifteen leading families of Florence around 1430, the years in which the Medici rose to power (source: John Padgett's Florentine families data, from Padgett and Ansell, *Robust Action and the Rise of the Medici, 1400-1434*, American Journal of Sociology, 1993). An edge joins two families linked by a marriage. The data lives in a hidden cell: `marriages` holds the twenty ties, `TIES` counts the ties of each family, `STEPS` counts the marriages between each family and the Medici, and `FAMILIES` lists the families in that order. The network has a famous story in it, and the customizations below tell it: the Medici sat in the middle of the network, and some families could reach the others only through them. The data is a dictionary with an `edges` list. Each edge names the two nodes it joins by their ids, a `source` and a `target`: ``` marriages["edges"][:3] ``` The nodes can be listed too, as a `nodes` list of dictionaries with a unique `id`. Without the list, as here, the nodes are read from the edges in the order they are first named. Everything else is optional: a node's `label` (the id by default), `size`, `group` and `emphasis`, and an edge's `weight`; the sections below show each one. An edge that names an unknown node or joins a node to itself, a repeated id, and a `size` or `weight` that is not above zero raise a `ValueError`. **Basic example.** Only the `data` argument is required. A force-directed layout places the nodes, so families joined by a marriage sit close and every pair pushes apart, and curved edges join them: ``` NetworkChart( # add the data to the chart data=marriages ).show() ``` ## Customizing the Network Chart Every customization is either a keyword argument of `NetworkChart`, a key of the node and edge records, or a `plot_network_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ----------------------------------------- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and resize the figure | `title`, `figsize` | [Title and figure size](#title-and-figure-size) | | move or rename the node labels | `label_position`, `"label"` on the nodes | [Node labels](#node-labels) | | place the nodes another way | `layout`, `seed` | [Layouts](#layouts) | | put every node where I say | `layout=NETWORK_LAYOUT.FIXED`, `"x"` and `"y"` on the nodes | [Layouts](#layouts) | | draw a large network | aggregate first; `NETWORK_LAYOUT.CIRCULAR` past ~1,000 nodes | [Large networks](#large-networks) | | show the direction of the edges | `directed` | [Directed edges](#directed-edges) | | size the edges and the nodes by a value | `"weight"` on the edges, `"size"` on the nodes | [Edge weights and node sizes](#edge-weights-and-node-sizes) | | let heavy edges pull their nodes together | `layout=NETWORK_LAYOUT.WEIGHTED` | [Edge weights and node sizes](#edge-weights-and-node-sizes) | | color the nodes by a category | `"group"` on the nodes, `show_legend`, `legend` | [Groups and the legend](#groups-and-the-legend) | | cluster the nodes by their group | `layout=NETWORK_LAYOUT.GROUPED` | [Groups and the legend](#groups-and-the-legend) | | highlight or mute a node | `"emphasis"` on the nodes | [Emphasis](#emphasis) | | highlight the nodes that match a rule | `emphasis_rule` | [Emphasis](#emphasis) | | write the weights on the edges | `show_values`, `value_format` | [Edge values](#edge-values) | | draw straight edges, or bow them more | `style={"plot_network_edge_style": ..., "plot_network_edge_curve": ...}` | [Node and edge style](#node-and-edge-style) | | change the node markers or colors | `style={"plot_network_node_marker": ..., "plot_network_node_color": ...}` | [Node and edge style](#node-and-edge-style) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw several networks side by side | `data` as a list of networks, `subtitle`, `max_cols` | [Subplots](#subplots) | | arrange a network next to other charts | `Grid` | [Composing network charts](#composing-network-charts) | | restyle every chart at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `layout` | [`NETWORK_LAYOUT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LAYOUT) | | `label_position` | [`NETWORK_LABEL_POSITION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LABEL_POSITION) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | The full list of style attributes is in the [datachart.typings.NetworkStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.typings.NetworkStyleAttrs) type; the full list of parameters is in the [datachart.charts.NetworkChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.charts.NetworkChart) reference. ### Title and figure size A network has no axes to label, so the title carries the whole message: `title` says what the nodes and the edges are. The layout always keeps a square aspect, so a wide figure leaves empty space beside the drawing rather than stretching it; `figsize` takes a `(width, height)` tuple in inches or one of the [FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) presets, and `FIG_SIZE.SQUARE` fits a network best. ``` from datachart.constants import FIG_SIZE NetworkChart( data=marriages, # say what the nodes and the edges are title="Marriages among Florentine families, c. 1430", # a square figure for a square layout figsize=FIG_SIZE.SQUARE, ).show() ``` ### Node labels Family names are longer than the markers, and printed on the node, as in the basic example, they run over the edges and over each other. `label_position` moves them, using [NETWORK_LABEL_POSITION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LABEL_POSITION): `CENTER` (the default) prints on the marker, `ABOVE` prints above it like a place name on a map, and `BEST` picks the spot beside each marker with the least overlap. A node's `label` key changes what is printed without changing its id, and an empty label prints nothing. The example prints the names above the markers, where they stay clear of the edges, and adds each family's number of ties to its label. The rest of the guide prints the names above the markers too. ``` from datachart.constants import NETWORK_LABEL_POSITION labeled = { # the number of ties in the label; the edges still use the id "nodes": [{"id": family, "label": f"{family} ({TIES[family]})"} for family in FAMILIES], "edges": marriages["edges"], } NetworkChart( data=labeled, # the names above the markers label_position=NETWORK_LABEL_POSITION.ABOVE, title="Marriages among Florentine families, c. 1430", figsize=FIG_SIZE.SQUARE, ).show() ``` ### Layouts Where a node sits is not data: the layout decides it, and the layout decides what the picture seems to say. `layout` picks the rule from [NETWORK_LAYOUT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LAYOUT), and each one tells the truth about something different. `SPRING`, the default, pulls linked nodes together and pushes every pair apart, so tightly knit groups end up close and a hub ends up in the middle. It is the layout for the question *what is connected to what*. The distances are only approximate, and the arrangement is one of many: `seed` (0 by default) picks it, the same seed always draws the same picture, and another seed turns and flips it. Read the connections, never the exact positions. The two drawings below are the same network, and in both the Medici sit between two halves of it. ``` from datachart.constants import NETWORK_LAYOUT from datachart.utils import Grid Grid( [[ NetworkChart(marriages, label_position=NETWORK_LABEL_POSITION.ABOVE, title="SPRING, seed=0"), # another arrangement of the same network NetworkChart(marriages, seed=2, label_position=NETWORK_LABEL_POSITION.ABOVE, title="SPRING, seed=2"), ]], figsize=(6.3, 3.4), ).show() ``` `CIRCULAR` spaces the nodes evenly on a circle in input order, starting at the top, and invents no clusters, so it is honest when the order of the nodes means something or when the picture must be the same every time. Listing the families from the most ties to the fewest puts the hubs at the top. `FIXED` places each node at its own `x` and `y` in the 0-1 layout space; it tells the truth when a position means something, such as a place on a map or a level in a hierarchy, and a node without `x` and `y` raises a `ValueError`. Here each family sits on a row by its number of marriages from the Medici, so the height of a node means something: ``` # the families from the most ties to the fewest, for the circle by_ties = { "nodes": [{"id": family} for family in sorted(TIES, key=TIES.get, reverse=True)], "edges": marriages["edges"], } # one row per marriage step from the Medici, ordered to limit the crossings ROWS = [ ["Medici"], ["Salviati", "Acciaiuoli", "Albizzi", "Tornabuoni", "Ridolfi", "Barbadori"], ["Pazzi", "Ginori", "Guadagni", "Strozzi", "Castellani"], ["Lamberteschi", "Bischeri", "Peruzzi"], ] levels = { "nodes": [ # every other family a little lower, so the names do not collide {"id": family, "x": (i + 0.5) / len(row), "y": 1 - 0.3 * step - 0.1 * (i % 2)} for step, row in enumerate(ROWS) for i, family in enumerate(row) ], "edges": marriages["edges"], } Grid( [[ # the nodes on a circle, in input order NetworkChart( by_ties, layout=NETWORK_LAYOUT.CIRCULAR, label_position=NETWORK_LABEL_POSITION.ABOVE, title="CIRCULAR, by ties", ), # every node at its own x and y NetworkChart( levels, layout=NETWORK_LAYOUT.FIXED, label_position=NETWORK_LABEL_POSITION.ABOVE, title="FIXED, rows by distance", ), ]], figsize=(6.3, 3.4), ).show() ``` Two more layouts let the data shape the picture: `WEIGHTED` lets heavy edges pull harder (see [Edge weights and node sizes](#edge-weights-and-node-sizes)), and `GROUPED` clusters the nodes by their group (see [Groups and the legend](#groups-and-the-legend)). `layout` and `seed` apply to every network in a figure, which is why the comparisons above draw separate figures and arrange them with [Grid](#composing-network-charts). ### Large networks A network chart is meant for a network a reader can follow, and the drawing cost sets a ceiling well before the readable one. Every edge is its own curved patch, a few milliseconds to draw and as many again to save, and the spring layouts weigh every pair of nodes, so their cost grows with the square of the node count. Measured on a laptop, these sizes draw without a problem: | Layout | Nodes | Edges | Time to draw and save | | ----------------------------------------- | ------------ | ------------- | --------------------- | | `SPRING` (default), `WEIGHTED`, `GROUPED` | up to ~1,000 | up to ~3,000 | a few seconds | | `CIRCULAR`, `FIXED` | up to ~5,000 | up to ~15,000 | under a minute | Past those, the spring layouts slow down first: 2,000 nodes take about half a minute, 5,000 several minutes and over a gigabyte of memory. Nothing is enforced, but a picture that dense reads as a hairball anyway. Aggregate the nodes (one node per group, one edge per pair of groups, weighted by the count) or keep only the heaviest edges before drawing, and switch to `CIRCULAR` or `FIXED` past a thousand nodes. The example does that with an illustrative code base, generated with a seeded generator: 3,000 modules in 120 packages owned by six teams, and 15,000 random imports. It is collapsed into one node per package, colored by team and sized by the imports crossing its border, and one edge per pair of packages with at least five imports between them. The labels are empty, since 120 package names would bury the drawing. ``` import random rng = random.Random(0) N_MODULES, N_PACKAGES, N_IMPORTS = 3000, 120, 15000 package = {f"m{i}": f"pkg{i % N_PACKAGES}" for i in range(N_MODULES)} team = {f"pkg{p}": f"team {p // 20 + 1}" for p in range(N_PACKAGES)} module_imports = [ (f"m{rng.randrange(N_MODULES)}", f"m{rng.randrange(N_MODULES)}") for _ in range(N_IMPORTS) ] # one edge per pair of packages with at least five imports between them crossing = Counter( tuple(sorted((package[s], package[t]))) for s, t in module_imports if package[s] != package[t] ) edges = [{"source": a, "target": b, "weight": n} for (a, b), n in crossing.items() if n >= 5] # a package's size is the number of imports crossing its border traffic = Counter() for edge in edges: traffic[edge["source"]] += edge["weight"] traffic[edge["target"]] += edge["weight"] nodes = [{"id": pkg, "label": "", "group": owner, "size": traffic[pkg]} for pkg, owner in team.items()] NetworkChart( data={"nodes": nodes, "edges": edges}, show_legend=True, legend={"title": "Owner"}, title="Imports between the packages of a large code base", figsize=(6.3, 5.4), # smaller markers keep 120 nodes apart style={"plot_network_node_size_min": 20, "plot_network_node_size_max": 160}, ).show() ``` ### Directed edges A marriage joins two families both ways, but many relationships have a direction: who imports whom, who pays whom, who follows whom. `directed=True` ends each edge in an arrowhead at its target, and an edge and its reverse draw as two arrows bowing to either side; without it an edge and its reverse draw as one line. The marriages have no direction, so this section switches dataset: `imports`, defined in a hidden cell, is an illustrative dependency graph of a small web service. Each module has a size in lines of code, and each edge points from the importing module to the imported one, weighted by the number of names it imports; `import_edges` holds the same edges without the weights. ``` NetworkChart( data=import_edges, # arrowheads from the importing module to the imported one directed=True, label_position=NETWORK_LABEL_POSITION.ABOVE, title="Imports between the modules of a service", figsize=FIG_SIZE.SQUARE, ).show() ``` ### Edge weights and node sizes A plain edge says two nodes are linked; a weighted one says how strongly. An edge's `weight` sets its width, linearly between `plot_network_edge_width_min` and `plot_network_edge_width_max` over the weights in the chart, and an edge without one draws at the minimum. A node's `size` sets its marker area, by square root between `plot_network_node_size_min` and `plot_network_node_size_max`, so a node four times the size draws a marker twice as wide; a node without one draws at `plot_network_node_size`. In `imports` the width of an import is the number of names it pulls in and a module's marker grows with its lines of code, so the heavy path from `api` to `models` stands out. ``` NetworkChart( # the edges carry a weight, the nodes a size data=imports, directed=True, label_position=NETWORK_LABEL_POSITION.ABOVE, title="Imports weighted by names, modules sized by lines", figsize=FIG_SIZE.SQUARE, ).show() ``` Under `SPRING` a weight is only drawn, it never moves a node. `NETWORK_LAYOUT.WEIGHTED` lets it: the lightest edge pulls at a tenth of the plain pull and the heaviest at three times, so modules that share many names draw close and loosely coupled ones drift apart. The exact rule is in the [NETWORK_LAYOUT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LAYOUT) reference. ``` Grid( [[ NetworkChart(imports, directed=True, label_position=NETWORK_LABEL_POSITION.ABOVE, title="SPRING"), # heavy imports pull their modules close NetworkChart( imports, directed=True, layout=NETWORK_LAYOUT.WEIGHTED, label_position=NETWORK_LABEL_POSITION.ABOVE, title="WEIGHTED", ), ]], figsize=(6.3, 3.4), ).show() ``` ### Groups and the legend Coloring the nodes by a category shows whether the connections follow it. A node's `group` colors it: the groups take the palette colors in the order they are first seen, and a node without a group draws in the edge color. `show_legend` names the groups beside the drawing, and `legend` sets its `title`, `location` ([LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION)), columns and alignment ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). Grouping the families by their number of marriages from the Medici shows how far the Medici reach: six families in one step, and every family within three. ``` from datachart.constants import LEGEND_LOCATION STEP_NAMES = ["the Medici", "one marriage", "two marriages", "three marriages"] by_step = { # in step order, so the legend lists the groups in order "nodes": [{"id": family, "group": STEP_NAMES[STEPS[family]]} for family in FAMILIES], "edges": marriages["edges"], } NetworkChart( data=by_step, label_position=NETWORK_LABEL_POSITION.ABOVE, # name the groups, under a title, below the drawing show_legend=True, legend={"title": "Distance from the Medici", "location": LEGEND_LOCATION.OUTSIDE_BOTTOM, "ncols": 2}, title="How far the Medici reach", figsize=(4.8, 5.2), ).show() ``` `NETWORK_LAYOUT.GROUPED` also places the nodes by group: each group is laid out on its own, the groups are then arranged by the edges between them, and a translucent disc in the group color marks each cluster (`plot_network_group_alpha` sets its alpha, `0` removes it). It pays off when most edges run inside the groups, like the teams in [Example 1](#example-1-who-reviews-whose-code-undirected-grouped-layout-sizes-and-a-highlight). The families above are grouped by distance, so their ties run between the groups, and the spring layout serves them better. ### Emphasis A network chart usually makes one point about a few nodes. A node's `emphasis` key sets its role, one of the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) roles: `"highlight"` strokes the node's border, `"background"` mutes the node, its label and every edge touching it. The roles are explicit: highlighting one node does not mute the others, and the `emphasis` argument of the function is not supported. The example shows the Medici as a bridge: it highlights them and mutes every family that stays connected without them, so what is left in color are the three families that reach the rest of the network only through the Medici. ``` from datachart.constants import EMPHASIS # the families that reach the others only through the Medici THROUGH_MEDICI = {"Acciaiuoli", "Salviati", "Pazzi"} def role(family): if family == "Medici": return EMPHASIS.HIGHLIGHT return None if family in THROUGH_MEDICI else EMPHASIS.BACKGROUND bridge = { "nodes": [{"id": family, "emphasis": role(family)} for family in FAMILIES], "edges": marriages["edges"], } NetworkChart( data=bridge, label_position=NETWORK_LABEL_POSITION.ABOVE, title="Three families tied to the rest only by the Medici", figsize=FIG_SIZE.SQUARE, ).show() ``` `emphasis_rule` picks the nodes from the data instead: a one-key rule, `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive), read against each node's `size`. The nodes that match are highlighted and the rest muted; a node without a `size` raises a `ValueError`, and a node's own `emphasis` key wins over the rule. Sizing each family by its number of ties and highlighting the top three shows that the Medici had more marriages than anyone, with the Strozzi and the Guadagni behind them. The [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis on every chart. ``` by_ties_sized = { "nodes": [{"id": family, "size": TIES[family]} for family in FAMILIES], "edges": marriages["edges"], } NetworkChart( data=by_ties_sized, label_position=NETWORK_LABEL_POSITION.ABOVE, # the three families with the most ties emphasis_rule={"top": 3}, title="The best-married families", figsize=FIG_SIZE.SQUARE, ).show() ``` ### Edge values Widths show which edges are heavy, but not by how much. `show_values` writes each edge's weight at its midpoint, on the bow of a curved edge and behind the same halo as the labels, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. An edge without a weight gets no value. On `imports` the values give the number of names behind each import. ``` from datachart.constants import VALUE_FORMAT NetworkChart( data=imports, directed=True, label_position=NETWORK_LABEL_POSITION.ABOVE, # write the number of imported names on the edges show_values=True, value_format=VALUE_FORMAT.INTEGER, title="Names imported between the modules", figsize=FIG_SIZE.SQUARE, ).show() ``` ### Node and edge style The `style` dictionary sets the look of the nodes and the edges; the attributes are listed in [datachart.typings.NetworkStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.typings.NetworkStyleAttrs), and any attribute left out keeps the value of the active theme. The nodes take the `plot_network_node_*` keys: the color (which overrides the palette), alpha, marker, stroke and the areas the sizes map onto. The edges take their color, alpha and width range, and `plot_network_label_halo_width` sets the white halo behind the labels (`0` removes it). The edge geometry is `plot_network_edge_style`, one of two [ARROW_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ARROW_STYLE) looks: `ARROW_STYLE.CURVE` (the default) bows every edge, and `ARROW_STYLE.STRAIGHT` draws straight lines. The other arrow looks raise a `ValueError`, since the arrowhead of a network edge comes from `directed`. A marriage is symmetric and the network is sparse, so straight edges and plain square markers in one color suit it, like a family tree printed in black and white. ``` from datachart.constants import ARROW_STYLE, LINE_MARKER NetworkChart( data=marriages, label_position=NETWORK_LABEL_POSITION.ABOVE, style={ # straight, darker edges "plot_network_edge_style": ARROW_STYLE.STRAIGHT, "plot_network_edge_color": "#444444", "plot_network_edge_alpha": 0.9, # square markers in one color, with a dark stroke "plot_network_node_marker": LINE_MARKER.SQUARE, "plot_network_node_color": "#e9e4d4", "plot_network_node_edge_color": "#444444", }, title="Marriages among Florentine families, c. 1430", figsize=FIG_SIZE.SQUARE, ).show() ``` A curved edge bows by `plot_network_edge_curve` (0.2 by default). A larger bow keeps the edges of a dense network apart, a negative one bows them to the other side, and `0` draws them straight: ``` NetworkChart( data=[import_edges] * 3, subtitle=["edge_curve=0.2 (default)", "edge_curve=0.5", "edge_curve=-0.2"], # one style per subplot; None keeps the default style=[None, {"plot_network_edge_curve": 0.5}, {"plot_network_edge_curve": -0.2}], directed=True, max_cols=3, figsize=(6.3, 2.6), ).show() ``` ### Text annotations A highlighted node shows *who*; a note says *why*. `texts` places text on the chart, with an optional `target` that draws a connector to a point ([TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs)). The layout spans 0-1 in both directions, so `x`, `y` and `target` are positions in the drawing; under the fixed layout the positions of the nodes are known, so a note can point at one. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement and styling. The note below points at the Medici, at the top of the rows. ``` NetworkChart( data=levels, layout=NETWORK_LAYOUT.FIXED, label_position=NETWORK_LABEL_POSITION.ABOVE, # a note beside the Medici, pointing at them texts={ "text": "six marriages,\nmore than any family", "x": 0.02, "y": 0.92, "target": (0.5, 0.8), }, title="Florentine families by distance from the Medici", figsize=FIG_SIZE.SQUARE, ).show() ``` ## Multiple Network Charts ### Subplots Comparing two networks side by side shows what changed between them. A list of networks in `data` draws each in its own subplot; networks never share one drawing, so the subplots are implied. `subtitle` titles each network, `title` stays global, `max_cols` limits the subplots per row, and `style` takes one dictionary per network. The example removes the Medici and their marriages. Both networks use the fixed rows from [Layouts](#layouts), so the families stay in place and only the missing ties change: the Acciaiuoli, the Salviati and the Pazzi are cut off from the rest. ``` without_medici = { # the same positions; a family without a tie stays in the drawing "nodes": [node for node in levels["nodes"] if node["id"] != "Medici"], "edges": [edge for edge in marriages["edges"] if "Medici" not in (edge["source"], edge["target"])], } NetworkChart( # one network per subplot data=[levels, without_medici], subtitle=["With the Medici", "Without the Medici"], layout=NETWORK_LAYOUT.FIXED, label_position=NETWORK_LABEL_POSITION.ABOVE, max_cols=2, title="The network falls apart without the Medici", figsize=(6.3, 3.4), ).show() ``` ### Composing network charts A network shows the structure; a second chart puts numbers to it. A network owns its axes, and its layout space is not a coordinate space another chart can share, so [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) rejects a network figure. [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges it beside other charts, each in its own cell and with the layout it was drawn with; a `layout_spec` gives a cell more than one column, and the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers the other layout options. The example sets the bridge roles from [Emphasis](#emphasis), drawn on the fixed rows, beside a [BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.charts.BarChart) of the ties per family, the Medici highlighted in both. ``` from datachart.charts import BarChart from datachart.constants import ORIENTATION, SHOW_GRID network = NetworkChart( # the bridge roles on the fixed rows data={"nodes": [{**node, "emphasis": role(node["id"])} for node in levels["nodes"]], "edges": marriages["edges"]}, layout=NETWORK_LAYOUT.FIXED, label_position=NETWORK_LABEL_POSITION.ABOVE, title="Marriages", ) ties = BarChart( # fewest first, so the most ties end up at the top data=[ {"label": family, "y": TIES[family], "emphasis": "highlight" if family == "Medici" else "background"} for family in sorted(TIES, key=TIES.get) ], orientation=ORIENTATION.HORIZONTAL, title="Marriage ties", show_grid=SHOW_GRID.X, xmin=0, ) # the network across two columns, the bars in the third Grid( [ {"figure": network, "layout_spec": {"row": 0, "col": 0, "rowspan": 1, "colspan": 2}}, {"figure": ties, "layout_spec": {"row": 0, "col": 2, "rowspan": 1, "colspan": 1}}, ], title="The Medici as a bridge", figsize=(6.3, 4.2), ).show() ``` ## Additional Features ### Themes A theme sets the palette, the node strokes, the fonts and even the label position of every chart at once, which is the way to restyle a whole document. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) and a [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) member, as the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide shows; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows every chart under each theme. Style is resolved when the chart is created, so a theme set before the call and reset after it applies to that chart alone. The `QUILL` theme prints the node names above the markers without being asked. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.QUILL) figure = NetworkChart( data=by_step, show_legend=True, legend={"title": "Distance from the Medici", "location": LEGEND_LOCATION.OUTSIDE_BOTTOM, "ncols": 2}, title="How far the Medici reach", figsize=(4.8, 5.2), ) config.reset_config() figure.show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question about who connects to whom. Their data is illustrative, written by hand in hidden cells; each example says what its data stands for. ### Example 1: Who Reviews Whose Code? (Undirected, Grouped Layout, Sizes, and a Highlight) `REVIEWERS` and `REVIEW_PAIRS` hold a quarter of code reviews in an illustrative engineering department of eight people in three teams. An edge joins two people who reviewed each other's pull requests, weighted by how many reviews they exchanged, and a person's `size` is the number of reviews they gave. Reviews flow both ways, so the network is undirected. The question is whether the teams review only among themselves. The grouped layout draws one cluster per team, placed by how much the teams review each other, the edge widths show the heavy pairs, and the one person who reviews across every team is highlighted. ``` reviews = { "nodes": [ { "id": person, "group": team, "size": given, # the reviewer every team leans on "emphasis": EMPHASIS.HIGHLIGHT if person == "Ana" else None, } for person, (team, given) in REVIEWERS.items() ], "edges": [{"source": a, "target": b, "weight": n} for a, b, n in REVIEW_PAIRS], } NetworkChart( data=reviews, # one cluster per team, the teams placed by how much they review each other layout=NETWORK_LAYOUT.GROUPED, show_legend=True, legend={"title": "Team"}, title="Code reviews exchanged this quarter", figsize=(5.4, 4.8), ).show() ``` ### Example 2: Where Do the Containers Go? (Directed, Edge Values, and a Fixed Layout) `SHIPMENTS` holds one illustrative month of container shipments between six large ports, in thousands of units, and `PORTS` their rough positions on a world map in the 0-1 layout space. A shipment goes one way, so the network is directed, and a pair of ports trading both ways shows as two arrows. The fixed layout keeps the ports where a reader expects them, so a lane reads as a route, and the values print the volume of each lane with a `"{x:.0f}k"` format. The two ports at the ends of the busiest lane, found from the data, are highlighted. ``` busiest = max(SHIPMENTS, key=lambda shipment: shipment[2]) shipping = { "nodes": [ { "id": port, "x": x, "y": y, # the two ends of the busiest lane "emphasis": EMPHASIS.HIGHLIGHT if port in busiest[:2] else None, } for port, (x, y) in PORTS.items() ], "edges": [{"source": s, "target": t, "weight": n} for s, t, n in SHIPMENTS], } NetworkChart( data=shipping, layout=NETWORK_LAYOUT.FIXED, directed=True, label_position=NETWORK_LABEL_POSITION.ABOVE, show_values=True, value_format="{x:.0f}k", title="Container shipments, thousand units a month", figsize=FIG_SIZE.SQUARE, ).show() ``` ### Example 3: Which Ports Ship More Than They Receive? (Node Sizes, a Note, and a Grid with a Bar Chart) The same shipments answer a second question: which ports are net exporters. The network sizes each port by the containers it handles, shipped plus received, and a note points at Shanghai, the largest. A network cannot show by how much a port ships more than it receives, so a grouped [BarChart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) of shipped and received containers per port goes beside it, and [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) sets the two in one figure. Shanghai, Singapore and Santos ship more than they receive; Rotterdam receives almost four times what it ships. ``` from datachart.constants import BAR_MODE shipped, received = Counter(), Counter() for s, t, n in SHIPMENTS: shipped[s] += n received[t] += n # the busiest port first PORT_ORDER = sorted(PORTS, key=lambda port: shipped[port] + received[port], reverse=True) throughput = NetworkChart( data={ # each port sized by the containers it handles "nodes": [ {"id": port, "x": x, "y": y, "size": shipped[port] + received[port]} for port, (x, y) in PORTS.items() ], "edges": shipping["edges"], }, layout=NETWORK_LAYOUT.FIXED, directed=True, label_position=NETWORK_LABEL_POSITION.ABOVE, # a note pointing at Shanghai texts={"text": "the largest port", "x": 0.7, "y": 0.02, "target": PORTS["Shanghai"]}, title="Container traffic", ) balance = BarChart( data=[ [{"label": port, "y": shipped[port]} for port in PORT_ORDER], [{"label": port, "y": received[port]} for port in PORT_ORDER], ], subtitle=["shipped", "received"], bar_mode=BAR_MODE.GROUP, show_legend=True, legend={"title": "Containers"}, title="Shipped and received", ylabel="Thousand containers", xtickrotate=45, show_grid=SHOW_GRID.Y, ymin=0, ) Grid([[throughput, balance]], title="Container traffic between six ports", figsize=(6.3, 3.8)).show() ``` # Treemap A treemap shows how a total divides into parts, and how those parts divide again: every tile is a rectangle whose area is its value, so the reader sees at a glance which parts dominate, and inside which groups. This guide shows how to create treemaps with the [datachart.charts.Treemap](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.charts.Treemap) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-treemap), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import Treemap ``` ## Basics The examples in this guide share one dataset: the world's population in 2024 by continent and country, in millions, from the United Nations *World Population Prospects 2024* (approximate mid-year estimates, rounded). The data lives in a hidden cell. `WORLD` maps each continent (the UN regions, so the Americas split into Northern America and Latin America) to its most populous countries and an "Other" record that makes the continent whole, and `world` turns it into treemap records. Population is a textbook part-of-whole story: the world splits into continents, each continent into countries, and a treemap shows both splits at once. It answers questions a table hides: how much of humanity lives in two countries, and how small Europe has become next to Africa. The data is one dict with a `data` list of records. A record is a dict with a `label` and a `value` above zero; a record that carries `children` is a group, and its children are records of the same shape. A group either omits its `value` or carries the sum of its children; anything else raises a `ValueError`, as does a zero or negative value. The order of the records does not matter: every level is sorted largest first before it is tiled. The first continent and three of its countries: ``` world["data"][0]["label"], world["data"][0]["children"][:3] ``` **Basic example.** Only the `data` argument is required. Each continent is a box in its own color with a header band, its countries are tiles in a lighter tint inside it, and the largest tile of every level sits top-left. The tiles are squarified in the drawn aspect of the axes, so they stay near square whatever the figure size. A label that does not fit its tile wraps, then shrinks, then is dropped; a group too short for its band goes unlabeled, and the [legend](#legend) is what names it. ``` Treemap( # add the data to the chart data=world ).show() ``` ## Customizing the Treemap Every customization is either a keyword argument of `Treemap` or a `plot_treemap_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | --------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title | `title` | [Title and figure size](#title-and-figure-size) | | resize the figure | `figsize` | [Title and figure size](#title-and-figure-size) | | tile one level without groups | records without `children` | [Flat data](#flat-data) | | nest groups inside groups | `children` on a child record, up to four levels | [Nested groups](#nested-groups) | | write the values on the tiles | `show_values`, `value_format` | [Tile values](#tile-values) | | mute or outline a tile or a group | `"emphasis"` on the record | [Emphasis](#emphasis) | | highlight the tiles that match a rule | `emphasis_rule` | [Emphasis](#emphasis) | | name the groups in a legend | `show_legend` | [Legend](#legend) | | title and place the legend | `legend` | [Legend](#legend) | | widen the gap around groups | `style={"plot_treemap_group_pad": ...}` | [Tile style](#tile-style) | | change the tint of the tiles in a group | `style={"plot_treemap_level_shade": ...}` | [Tile style](#tile-style) | | change the strokes or the label sizes | `style={"plot_treemap_edge_width": ..., "plot_treemap_min_fontsize": ...}` | [Tile style](#tile-style) | | drop the halo behind the labels | `style={"plot_treemap_label_halo_width": 0}` | [Tile style](#tile-style) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw several treemaps side by side | a list of dicts as `data`, `subtitle`, `max_cols` | [Subplots](#subplots) | | arrange a treemap next to other charts | `Grid` | [Composing treemaps](#composing-treemaps) | | draw the chart in another look | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | The full list of style attributes is in the [datachart.typings.TreemapStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.typings.TreemapStyleAttrs) type; the full list of parameters is in the [datachart.charts.Treemap](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.charts.Treemap) reference. ### Title and figure size A treemap has no axes to label, so the title is the only place that says what the areas measure; `title` sets it, units included. The chart sits in a page column, so its size should match it. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE); the tiles are laid out in the aspect of the figure you choose, so a wide figure and a tall one both keep them near square. `FIG_SIZE.FULL_MEDIUM` fills the width of a page and leaves the forty tiles room for their labels. ``` from datachart.constants import FIG_SIZE Treemap( data=world, # say what the areas measure title="World population by continent and country, 2024 (millions)", # room for the small tiles' labels figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Flat data Sometimes the question stops at the first split: how do the continents compare? Records without `children` are tiles of their own, each in the next palette color, with no bands or borders. The example tiles the continent totals, the top level of the nested chart on its own: Asia alone covers more than half of the drawing. ``` # one record per continent, its total as the value continents = { "data": [ {"label": continent, "value": sum(n for _, n in countries)} for continent, countries in WORLD.items() ] } Treemap( data=continents, title="World population by continent, 2024 (millions)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Nested groups Real hierarchies are often deeper than two levels: regions inside continents, folders inside folders. A child record may carry `children` of its own, and every level follows the rules of the top one: a group is a box in its color with a header band, its children one tint lighter inside it, the largest top-left. Every group insets its children by `plot_treemap_group_pad`, so a nested box sits visibly inside its parent's color. A nested group whose box is short draws its band in a smaller font, and one too short even for that draws its border only; the [legend](#legend) names the top-level groups alone. The example adds a level above the continents, the landmasses: Afro-Eurasia, the Americas, and Oceania. The records are the same as before, grouped once more, and the picture answers a new question: how much of humanity lives on one landmass. ``` LANDMASSES = { "Afro-Eurasia": ["Asia", "Africa", "Europe"], "Americas": ["Latin America", "Northern America"], "Oceania": ["Oceania"], } by_continent = {record["label"]: record for record in world["data"]} # three levels: landmass, continent, country landmasses = { "data": [ {"label": land, "children": [by_continent[c] for c in names]} for land, names in LANDMASSES.items() ] } Treemap( data=landmasses, title="World population by landmass, continent and country, 2024 (millions)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` !!! note "Four levels of nesting" A treemap nests its records four levels deep: the `data` list is the first level, and a record at the fourth cannot carry `children` of its own. The cap is a readability limit: every level takes a header band and a lighter tint from its parent, and at the usual figure sizes a fifth level has no room left for its labels. For a deeper hierarchy, fold the lowest levels into "Other" records, or draw a subtree as its own treemap with [subplots](#subplots). [Example 1](#example-1-where-did-the-disk-space-go-four-levels-values-and-a-highlight) nests four levels. ### Tile values Area shows the proportions, but not the numbers behind them: is India bigger than China, and by how much? `show_values` writes each tile's value under its label, and `value_format` formats it: a [VALUE_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) constant or any `"{x:.1f}"`, `"{:.1f}%"` or `"%g"` style string. Values follow the labels' fitting rules, and when a tile has room for its label but not both, the value is dropped first. The format below appends the unit, and the two largest tiles settle the question: India, 1,451 million, just ahead of China. ``` Treemap( data=world, # write the population on the tiles, with its unit show_values=True, value_format="{x:,} M", title="World population by continent and country, 2024", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` When the share matters more than the count, turn the values into fractions of the total and format them with `VALUE_FORMAT.PERCENT`, which multiplies by 100 and keeps one decimal. The tiling is unchanged, since only the proportions count, and the two largest tiles now read as more than a third of humanity: ``` from datachart.constants import VALUE_FORMAT total = sum(record["value"] for record in continents["data"]) # the same records, each value as a share of the world shares = { "data": [ {"label": continent, "children": [{"label": c, "value": n / total} for c, n in countries]} for continent, countries in WORLD.items() ] } Treemap( data=shares, show_values=True, # a fraction printed as a percent with one decimal value_format=VALUE_FORMAT.PERCENT, title="Share of the world population, 2024", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Emphasis A treemap of forty countries makes no point until one is picked out. Every record takes an `emphasis` key with one of the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) roles: `"background"` mutes a tile or a whole group into the theme's muted color, and `"highlight"` strokes its border in the text color. A role applies to the record's whole subtree, and a descendant's own role overrides it, so a highlighted country stays vivid inside a muted continent. Roles are explicit: highlighting one record does not mute the others. The `emphasis` argument of the function itself is not supported (it raises a `ValueError`); pass the roles on the records. The example asks where the fastest-growing populations live: Africa is highlighted, the rest muted, and Pakistan keeps its color inside a muted Asia. ``` from datachart.constants import EMPHASIS africa_marked = { "data": [ { "label": continent, # Africa stands out, every other continent is muted "emphasis": EMPHASIS.HIGHLIGHT if continent == "Africa" else EMPHASIS.BACKGROUND, "children": [ # Pakistan's own role wins over muted Asia {"label": c, "value": n, **({"emphasis": EMPHASIS.HIGHLIGHT} if c == "Pakistan" else {})} for c, n in countries ], } for continent, countries in WORLD.items() ] } Treemap( data=africa_marked, title="Africa and Pakistan against the rest of the world, 2024", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `emphasis_rule` picks the tiles from the data instead of tagging them by hand. It is a one-key dictionary read against each leaf's `value`: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive); the leaves that match are highlighted and the other leaves muted. A record's own `emphasis` key wins over the rule, and so does a group's for every leaf inside it. The rule below asks which countries pass 200 million. The "Other" records would match too, so they are muted by their own key. The [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers the rule across every chart. ``` # the "Other" records are not countries: mute them by hand countries_only = { "data": [ { "label": record["label"], "children": [ {**child, "emphasis": EMPHASIS.BACKGROUND} if child["label"] == "Other" else child for child in record["children"] ], } for record in world["data"] ] } Treemap( data=countries_only, # every country above 200 million emphasis_rule={"above": 200}, title="Countries above 200 million people, 2024", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Legend A group too short for its header band goes unlabeled: in the charts above, Oceania is a thin strip with no name. `show_legend` lists the top-level records beside the tiles, since the tiles fill the axes, and names every group whatever its size. `legend` says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). Below the tiles, in three columns, the legend leaves the full width to the tiling. ``` from datachart.constants import LEGEND_LOCATION Treemap( data=world, # name every continent, Oceania included show_legend=True, legend={"title": "Continent", "location": LEGEND_LOCATION.OUTSIDE_BOTTOM, "ncols": 3}, title="World population by continent and country, 2024 (millions)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Tile style The `style` dictionary sets how the boxes are separated and how the levels are told apart; the attributes are listed in [datachart.typings.TreemapStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.typings.TreemapStyleAttrs), and any attribute left out keeps the value of the active theme. - `plot_treemap_group_pad` is the gap between the top-level records and, in the group's color, between a group's border and its children, as a fraction of the drawing. - Children meet at a stroke only, drawn in `plot_treemap_edge_color` at `plot_treemap_edge_width`; `plot_treemap_group_edge_width` draws the border around a group, and `plot_treemap_highlight_edge_width` the border of a highlighted record. - `plot_treemap_level_shade` says how much lighter than its parent each level is, from `0` (the group color) to `1` (white). - Labels shrink by `plot_treemap_level_font_scale` per nesting level and down to `plot_treemap_min_fontsize` before they are dropped; each sits on a halo of `plot_treemap_label_halo_width`, and `0` drops it. A treemap bound for a slide is read from afar: wider gaps between the continents and countries close to the continent color read as solid blocks, and a smaller minimum font lets a label shrink further before it is dropped. ``` Treemap( data=world, style={ # a wider gap around the continents "plot_treemap_group_pad": 0.015, # the countries close to the continent color "plot_treemap_level_shade": 0.15, # white strokes between the countries "plot_treemap_edge_color": "white", "plot_treemap_edge_width": 1.5, # keep smaller labels, without a halo "plot_treemap_min_fontsize": 5, "plot_treemap_label_halo_width": 0, }, title="World population by continent and country, 2024 (millions)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Text annotations Some facts are not a tile: a share, a comparison, a caveat. `texts` places notes on the chart; the tiling spans `0` to `1` in both directions, so `x` and `y` are fractions of the drawing, with `y` running up from the bottom; a `target` would add a connector to a point. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connectors and boxes. The note computes how much of the world lives in India and China together and sits on the boundary between their tiles. ``` asia = dict(WORLD["Asia"]) two_giants = (asia["India"] + asia["China"]) / total Treemap( data=world, texts={ "text": f"India and China: {two_giants:.0%} of the world", # on the boundary of the two largest tiles; y runs from the bottom "x": 0.3, "y": 0.55, "ha": "center", }, title="World population by continent and country, 2024 (millions)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Multiple Treemaps ### Subplots A large group squeezes its countries into a corner; drawn alone, its countries get the whole drawing. A list of `{"data": [...]}` dicts draws one treemap per subplot; two treemaps never share axes, so `subplots` is implied. `subtitle` titles each subplot, `title` stays global, and `max_cols` limits the subplots per row. Each subplot fills its own area, so the areas compare within a subplot, not across them: Asia and Africa below look the same size although Asia has three times the people. ``` Treemap( # one treemap per continent data=[{"data": [{"label": c, "value": n} for c, n in WORLD[name]]} for name in ("Asia", "Africa")], subtitle=["Asia", "Africa"], max_cols=2, show_values=True, title="Population by country, 2024 (millions)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Composing treemaps A treemap shows shares; a second chart can put the counts beside them. A treemap owns its axes: its tiling is not a coordinate space another chart can share, so [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) rejects a treemap figure. [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) arranges it next to other figures as an ordinary cell, and the tiles are laid out again in the cell's own aspect; the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) guide covers the layout options. The example pairs the continents with a horizontal [BarChart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) of the same totals, which reads the exact ranking a treemap only hints at. ``` from datachart.charts import BarChart from datachart.constants import ORIENTATION, SHOW_GRID, SORT from datachart.utils import Grid tiles = Treemap(data=continents, title="Shares") totals = BarChart( data=[{"label": r["label"], "y": r["value"]} for r in continents["data"]], title="Totals (millions)", orientation=ORIENTATION.HORIZONTAL, # largest at the top sort=SORT.ASCENDING, show_grid=SHOW_GRID.X, xmin=0, ) Grid([[tiles, totals]], title="World population by continent, 2024", figsize=FIG_SIZE.FULL_SHORT).show() ``` ## Additional Features ### Themes A theme sets the palette, the strokes and the fonts of every chart at once; see the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) for the whole suite under each. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) from the [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) constant, and reset the configuration afterwards so the following charts draw in the default again. A treemap destined for a black-and-white print needs its groups told apart without color: `THEME.QUILL` etches each top-level group in its own hatch pattern, and its `plot_treemap_etch_density` style attribute sets how densely each nesting level repeats it. ``` from datachart.config import config from datachart.constants import THEME config.set_theme(THEME.QUILL) figure = Treemap( data=world, show_legend=True, title="World population by continent and country, 2024 (millions)", figsize=FIG_SIZE.FULL_MEDIUM, ) config.reset_config() figure.show() ``` ## Real-World Examples The examples below put the features above to work, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Where Did the Disk Space Go? (Four Levels, Values, and a Highlight) A full disk raises one question: which folder to clean up. `HOME` is an illustrative home folder four levels deep, sized in gigabytes the way a disk-usage tool reports it: a dict is a folder, a number a size. A small recursive helper turns it into records, `show_values` writes the sizes with their unit, and the one folder worth cleaning, a thesis's raw data, is highlighted. The treemap shows what a folder listing hides: the largest sub-folder sits three levels down, inside Projects, and it outweighs every other sub-folder on the disk. ``` def records(tree, flagged="raw data"): # a number is a tile, a dict a group; the flagged folder is highlighted return [ { "label": name, **({"value": size} if not isinstance(size, dict) else {"children": records(size, flagged)}), **({"emphasis": EMPHASIS.HIGHLIGHT} if name == flagged else {}), } for name, size in tree.items() ] Treemap( data={"data": records(HOME)}, show_values=True, value_format="{x} GB", title="Home folder by size", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Which Costs Can the Team Change? (Muted Fixed Costs, Thousands, and a Legend) A budget review asks where money can be saved, and fixed costs are not the answer. `BUDGET` is an illustrative yearly budget of a software company, split by department and then by line, in thousands of EUR. The fixed costs (rent and insurance) are context, not a lever, so their group is muted; `VALUE_FORMAT.THOUSANDS` groups the digits; and the legend, placed below, names every department, including the ones too small for a band, while the muted fixed costs stay out of it like any background mark. Engineering salaries and the cloud bill are where a saving would show. ``` budget = { "data": [ { "label": department, # the fixed costs are context, not a lever **({"emphasis": EMPHASIS.BACKGROUND} if department == "Fixed costs" else {}), "children": [{"label": line, "value": k} for line, k in lines], } for department, lines in BUDGET.items() ] } Treemap( data=budget, show_values=True, value_format=VALUE_FORMAT.THOUSANDS, show_legend=True, legend={"title": "Department", "location": LEGEND_LOCATION.OUTSIDE_BOTTOM, "ncols": 3}, title="Yearly budget by department and line (thousands of EUR)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: How Has the World's Population Shifted? (Shares Across Years, a Rule, and a Grid) In 1950 one person in five lived in Europe; today it is fewer than one in ten, while Africa's share has doubled. `WORLD_1950` holds the continent populations in 1950, in millions, from the same UN *World Population Prospects 2024* estimates (approximate, rounded), and the 2024 totals come from the shared dataset. Two flat treemaps show each year's shares as percentages, each filling its own cell, and `emphasis_rule={"top": 2}` keeps the eye on the two largest continents of each year. The palette follows the size order, so the orange tile is the runner-up in each year: Europe in 1950, Africa in 2024. A bar chart below gives the growth factor from 1950 to 2024, which says why the shares moved: Africa's population grew more than sixfold, Europe's by little more than a third. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) puts the two treemaps in the top row and the bar chart across the bottom. ``` def share_map(year, populations): # a flat treemap of the continents' shares, the two largest highlighted whole = sum(populations.values()) return Treemap( data={"data": [{"label": c, "value": n / whole} for c, n in populations.items()]}, show_values=True, value_format=VALUE_FORMAT.PERCENT_INT, emphasis_rule={"top": 2}, title=str(year), ) growth = BarChart( data=[{"label": c, "y": WORLD_2024[c] / WORLD_1950[c]} for c in WORLD_1950], title="Growth, 1950 to 2024 (times)", sort=SORT.DESCENDING, show_values=True, value_format="{x:.1f}×", show_grid=SHOW_GRID.Y, ymin=0, ymax=8, ) Grid( [[share_map(1950, WORLD_1950), share_map(2024, WORLD_2024)], [growth]], title="Share of the world population by continent", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Parallel Coordinates A parallel coordinates chart draws one vertical axis per variable and one line per record, so it shows many variables of many records at once: records with a similar profile run together as a bundle, and the segments between two neighboring axes show how those two variables relate (parallel segments for a positive relation, crossing segments for a negative one, a trade-off). This guide shows how to create parallel coordinates charts with the [datachart.charts.ParallelCoords](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.charts.ParallelCoords) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-parallel-coordinates), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import ParallelCoords ``` ## Basics The examples in this guide share one dataset: 30 penguins from the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset (Gorman, Williams and Fraser, 2014; CC0), the first ten recorded penguins of each species (Adelie, Chinstrap, Gentoo). Every penguin has four body measurements, its `bill length` and `bill depth`, its `flipper length` (all in mm) and its `body mass` (in g), and three categorical attributes: its `species`, the `island` it was observed on, and its `sex`. The data lives in a hidden cell as `penguins`, a list of one dictionary per penguin. The measurements hold a question a parallel coordinates chart answers well: do the three species differ on every measurement, or only on some? Each data point is a dictionary: the dictionary is one line of the chart, and each key is one axis. Numeric values make a numeric axis; string values make a categorical axis with one tick per category: ``` penguins[:2] ``` **Basic example.** Only the `data` argument is required. Every key becomes an axis, in the order the keys first appear, and every axis covers its values from bottom to top (a numeric axis rounds outward to about five ticks labeled with round values, so the smallest and largest values sit at or just inside the ends). Even without color, two bundles show: a group of lines with shallow bills, long flippers and heavy bodies, and everyone else. ``` ParallelCoords( # add the data to the chart data=penguins ).show() ``` ## Customizing the Parallel Coordinates Every customization is either a keyword argument of `ParallelCoords` or a `plot_parallel_*` attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel` | [Title and axis labels](#title-and-axis-labels) | | resize the figure | `figsize` | [Figure size](#figure-size) | | choose the axes and their order | `dimensions` | [Selecting and ordering dimensions](#selecting-and-ordering-dimensions) | | color the lines by a category or a value | `hue`, `show_legend` | [Hue](#hue) | | order the categories on an axis | `category_orders` | [Category order](#category-order) | | title and place the legend | `legend` | [Legend](#legend) | | change the line color, alpha, width, marker | `style={"plot_parallel_color": ..., "plot_parallel_alpha": ...}` | [Line style](#line-style) | | style the vertical axes | `style={"plot_parallel_axis_color": ..., "plot_parallel_axis_width": ...}` | [Axis style](#axis-style) | | style the tick marks and their labels | `style={"plot_parallel_tick_color": ..., "plot_parallel_tick_label_size": ...}` | [Tick marks and labels](#tick-marks-and-labels) | | style or rotate the axis names | `style={"plot_parallel_dim_label_size": ..., "plot_parallel_dim_label_rotation": ...}` | [Dimension labels](#dimension-labels) | | highlight some records, mute the rest | `emphasis`, `emphasis_rule` | [Emphasis](#emphasis) | | put a note on the chart | `texts` | [Text annotations](#text-annotations) | | draw several sets of records on one chart | `data` as a list of lists; `style`, `hue` per set; `dimensions` | [Multiple Parallel Coordinates Charts](#multiple-parallel-coordinates-charts) | | use dates as an axis | `date` values in `data` | [Date dimensions](#date-dimensions) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | The full list of style attributes is in the [datachart.typings.ParallelCoordsStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.typings.ParallelCoordsStyleAttrs) type; the full list of parameters is in the [datachart.charts.ParallelCoords](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.charts.ParallelCoords) reference. ### Title and axis labels A reader who does not know the data cannot tell what the lines stand for; `title` says it. The axes name themselves, so `xlabel` is for what they have in common (here, the attributes of a penguin). The height of a line on an axis is its position within that axis's own range, not a shared unit; `ylabel` can say so, though most charts leave it out. ``` ParallelCoords( data=penguins, # add the title title="Palmer penguins", # add the x and y axis labels xlabel="Penguin attribute", ylabel="Position within the range", ).show() ``` ### Figure size Every axis carries its tick labels beside it, so a chart with many axes needs width to keep the labels of neighbors apart. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), sized for a full or half page width. A full-width, short figure fits the chart into a page of text; the lines flatten, but the bundles still show. `show_grid` and `aspect_ratio` are accepted like on the other charts, but a parallel coordinates chart has no value axis for grid lines: the vertical axes are its grid. ``` from datachart.constants import FIG_SIZE ParallelCoords( data=penguins, title="Palmer penguins", # a full-width, short figure figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Selecting and ordering dimensions Only neighboring axes can be compared: the segments between two axes show how those two variables relate, and a relation between axes that are far apart is lost. `dimensions` lists the keys to draw, in order, so it both drops the axes a question does not need and puts the variables to compare next to each other. Here the categorical attributes go, and bill depth sits between bill length and flipper length. The segments from bill depth to flipper length cross in an X: the penguins with the shallowest bills have the longest flippers, a negative relation. The segments from flipper length to body mass run parallel: long flippers go with heavy bodies. The four measurements are the axes of most examples below, so they are kept in `MEASUREMENTS`. ``` MEASUREMENTS = ["bill length", "bill depth", "flipper length", "body mass"] ParallelCoords( data=penguins, title="Palmer penguins: the measurements", # the axes to draw, in this order dimensions=MEASUREMENTS, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Hue One color for every line hides which record belongs to which group. `hue` names the key the lines are colored by; the key is left out of the automatic axes, so list it in `dimensions` to keep it as an axis too. **Categorical hue.** When the hue values are strings, every category gets its own color from the theme's `color_parallel_hue` palette, and `show_legend` names them. Colored by species, the bundles of the basic example become three profiles: the Gentoo are the heavy penguins with long flippers and shallow bills, the Adelie have the shortest bills, and the Chinstrap have deep bills like the Adelie but long ones like the Gentoo. No single axis separates all three species; the profile across the axes does. ``` ParallelCoords( data=penguins, title="Palmer penguins by species", dimensions=MEASUREMENTS, # color the lines by the species hue="species", # name the species in a legend show_legend=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` **Numeric hue.** When the hue values are numbers, the lines are shaded along the theme's `color_parallel_hue_continuous` ramp, from the lightest color at the smallest value to the darkest at the largest. A continuous hue has no legend: keep its key as an axis, and that axis is the scale. Shaded by body mass, the heaviest penguins are the darkest lines, and they can be followed back to the long-flipper, shallow-bill end of the other axes. ``` ParallelCoords( data=penguins, title="Palmer penguins by body mass", # the hue key stays as the last axis, which serves as the scale dimensions=MEASUREMENTS, # shade the lines by the body mass hue="body mass", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Category order Categories are spaced evenly along their axis in alphabetical order, from the bottom up, and alphabetical order rarely matches the data: lines then cross on their way into a categorical axis only because of how its categories are sorted. `category_orders` maps a dimension to the order of its categories (any category left out follows, sorted). The island axis sorts as Biscoe, Dream, Torgersen, which sends the heavy Gentoo (all from Biscoe) to the bottom; putting Biscoe on top lets the heavy lines run straight across, and the crossings that remain are the Adelie, who live on all three islands. ``` ParallelCoords( data=penguins, title="Palmer penguins by island", dimensions=MEASUREMENTS + ["island"], hue="species", show_legend=True, # bottom to top, instead of alphabetical category_orders={"island": ["Torgersen", "Dream", "Biscoe"]}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Legend The default legend sits where the theme puts it, which on a chart full of lines is often over some of them. `legend` sets the `title`, the `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols`, and the `alignment` of the entries from [LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN); a field left out falls back to the theme ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)). A titled legend in one row above the axes covers no line at all. ``` from datachart.constants import LEGEND_LOCATION ParallelCoords( data=penguins, title="Palmer penguins by species", dimensions=MEASUREMENTS, hue="species", show_legend=True, # a titled, one-row legend above the axes legend={"title": "Species", "location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 3}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Line style Lines pile up on a parallel coordinates chart, and the line style decides whether a dense region reads as a band or as a solid block. The `plot_parallel_*` line attributes set the color (which overrides the hue colors, so leave it out when coloring by `hue`), the alpha, the width, the line style from [LINE_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE), the marker drawn where a line crosses an axis from [LINE_MARKER](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER), and the draw order (`plot_parallel_zorder`; the axes are drawn above the lines). A lower alpha lets overlapping lines darken where the records agree, and markers show where the values sit, which helps on a categorical axis where many lines meet at one tick. Any attribute left out keeps the value of the active theme. ``` from datachart.constants import LINE_STYLE, LINE_MARKER ParallelCoords( data=penguins, # translucent lines with a marker at every axis crossing style={ "plot_parallel_color": "#2a6f97", "plot_parallel_alpha": 0.35, "plot_parallel_width": 1.5, "plot_parallel_style": LINE_STYLE.SOLID, "plot_parallel_marker": LINE_MARKER.CIRCLE, }, title="Palmer penguins", dimensions=MEASUREMENTS + ["species"], figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Axis style The default axes are black and heavier than the lines, so they read as the frame of the chart. When the story is in the lines, lighter and thinner axes hand the attention to them: `plot_parallel_axis_color` and `plot_parallel_axis_width` set the look, and `plot_parallel_axis_zorder` the draw order (above the lines by default; the tick marks and labels are drawn just above the axes). ``` ParallelCoords( data=penguins, # light, thin axes style={ "plot_parallel_axis_color": "#9a9a9a", "plot_parallel_axis_width": 1.0, }, title="Palmer penguins by species", dimensions=MEASUREMENTS, hue="species", show_legend=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Tick marks and labels The tick labels sit right where the lines cross the axes, so they have to stay legible over the lines. Every axis carries tick marks (about five round values on a numeric axis, one per category on a categorical one), each with a label on a background box, white at 80% alpha by default. `plot_parallel_tick_color`, `plot_parallel_tick_width` and `plot_parallel_tick_length` (a fraction of the spacing between two axes) style the marks; `plot_parallel_tick_label_size`, `plot_parallel_tick_label_color`, `plot_parallel_tick_label_bg_color` and `plot_parallel_tick_label_bg_alpha` style the labels. The example matches the marks to grey axes and puts the labels on an opaque light box, so no line shows through them. ``` ParallelCoords( data=penguins, style={ "plot_parallel_axis_color": "#9a9a9a", "plot_parallel_axis_width": 1.0, # grey, longer tick marks "plot_parallel_tick_color": "#9a9a9a", "plot_parallel_tick_width": 1.0, "plot_parallel_tick_length": 0.04, # small labels on an opaque box "plot_parallel_tick_label_size": 8, "plot_parallel_tick_label_color": "#4a4a4a", "plot_parallel_tick_label_bg_color": "#f3f3f3", "plot_parallel_tick_label_bg_alpha": 1.0, }, title="Palmer penguins by species", dimensions=MEASUREMENTS, hue="species", show_legend=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Dimension labels The dimension labels name the axes along the bottom of the chart, and with many axes or long names they run into each other. `plot_parallel_dim_label_rotation` tilts them (in degrees), `plot_parallel_dim_label_pad` moves them away from the bottom tick labels (in points), and `plot_parallel_dim_label_size` and `plot_parallel_dim_label_color` set the font. Tilted, the names of all seven attributes of the dataset fit at full width. ``` ParallelCoords( data=penguins, # tilted, padded axis names style={ "plot_parallel_dim_label_size": 10, "plot_parallel_dim_label_color": "#2a6f97", "plot_parallel_dim_label_rotation": 20, "plot_parallel_dim_label_pad": 10, }, title="Palmer penguins", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Emphasis When the question is about some of the records, the others should step back without leaving. `emphasis` takes one role per record, aligned with `data` (a single string applies to every record): `"background"` mutes a record (the theme's muted color, a lower alpha, a thinner line, drawn behind the rest, with no hue color and no legend entry), `"highlight"` bolds it and brings it to the front of the lines (still below the axes and labels), and `None` leaves it as it is. The roles are also the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis on every chart. Singling out the Chinstrap shows the species in between: Adelie bill depth, Gentoo bill length. ``` from datachart.constants import EMPHASIS ParallelCoords( data=penguins, # one role per penguin: the Chinstrap in front, the rest muted emphasis=[ EMPHASIS.HIGHLIGHT if p["species"] == "Chinstrap" else EMPHASIS.BACKGROUND for p in penguins ], title="Palmer penguins: the Chinstrap", dimensions=MEASUREMENTS, hue="species", show_legend=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `emphasis_rule` picks the records from the data instead. It is a one-key dictionary read against each record's numeric `hue` value: `{"top": n}` or `{"bottom": n}` by rank, `{"above": v}` or `{"below": v}` (strict), or `{"between": (lo, hi)}` (inclusive). The records that match are highlighted, the rest muted, and an explicit `emphasis` role wins over the rule. Without a numeric `hue` the rule raises a `ValueError`. The hue ramp still spans every record, muted ones included, so a highlighted line keeps the color it had before the rule; a fixed `plot_parallel_color` instead paints every line one color, while the rule still reads the `hue` values. Keeping the penguins above 5 kg answers *what do the heaviest penguins have in common*: long flippers and shallow bills. ``` ParallelCoords( data=penguins, title="Palmer penguins above 5 kg", dimensions=MEASUREMENTS, # the rule reads the body mass; the style fixes the color hue="body mass", style={"plot_parallel_color": "#0f7173"}, # highlight the records whose hue value is above 5000 g emphasis_rule={"above": 5000}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Text annotations A crossing between two axes is easy to miss for a reader who does not know to look for it; a note points it out. `texts` places text on the chart, with an optional `target` to draw a connector. In data coordinates, `x` counts the axes from `0` (a half-integer sits between two axes) and `y` runs from `0` at the bottom of every axis to `1` at the top; `"coords": "axes"` places the text in axes fractions instead. The [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers placement, connectors and styling. ``` ParallelCoords( data=penguins, title="Palmer penguins by species", dimensions=MEASUREMENTS, hue="species", show_legend=True, # a note on the crossing between bill depth (axis 1) and flipper length (axis 2) texts={ "text": "shallow bills, long flippers:\na negative relation", "x": 1.5, "y": 0.9, "target": (1.5, 0.5), }, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Multiple Parallel Coordinates Charts To draw several sets of records on one chart, pass a list of lists to `data`. The sets share the axes: every axis spans the values of all sets together, so a value lands at the same height whichever set it is in. The per-set attributes `dimensions`, `style` and `hue` take lists aligned with `data`, and a single value applies to every set; for `dimensions` a flat list of names is that single value. The sets share one row of axes, so per-set `dimensions` lists must be equal. `subtitle` is accepted for consistency with the other charts, but the chart has no per-set heading to draw it in: the sets are told apart by their style or by the hue legend. The example draws the Adelie and Chinstrap as a grey context and the Gentoo in a bold color over them, on the four measurements. ``` gentoo = [p for p in penguins if p["species"] == "Gentoo"] others = [p for p in penguins if p["species"] != "Gentoo"] ParallelCoords( # one list per set of records data=[others, gentoo], # one list of axes for every set dimensions=MEASUREMENTS, # one style per set: grey context, bold foreground style=[ {"plot_parallel_color": "#c0c0c0", "plot_parallel_alpha": 0.8}, {"plot_parallel_color": "#0f7173", "plot_parallel_width": 2.0}, ], title="Palmer penguins: the Gentoo against the rest", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` With `hue` as a list, each set is colored by its own key, or not at all. Coloring only the Gentoo by their sex shows that, within the Gentoo, the males are the heavier half, while the other species stay a grey context. A hue key is not an axis, so the axes are still the four measurements. ``` ParallelCoords( data=[others, gentoo], dimensions=MEASUREMENTS, style=[{"plot_parallel_color": "#c0c0c0", "plot_parallel_alpha": 0.8}, None], # no hue for the context, the sex for the Gentoo hue=[None, "sex"], show_legend=True, title="Gentoo penguins by sex, against the rest", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Additional Features ### Date dimensions A record often carries a date: a release, a survey wave, a measurement day. A dimension whose values are real temporal objects (`datetime`, `date`, `numpy.datetime64` or a pandas `Timestamp`) becomes a categorical axis ordered by time, each date printed as its ISO label; date strings are not parsed, and sort as plain text. `releases`, defined in a hidden cell, holds six illustrative releases of a mobile app: the `version`, the `released` date, the app size, the share of crash-free sessions, and the average store rating. With the date as the first axis, the segments from the date to the size run nearly parallel: the later the release, the larger the app. Highlighting release 3.0 shows the one release whose stability dropped, and its rating dropped with it. ``` ParallelCoords( data=releases, title="App releases", # the date is an axis ordered by time dimensions=["released", "size (MB)", "crash-free (%)", "rating"], # release 3.0 in front, the others muted emphasis=[ EMPHASIS.HIGHLIGHT if r["version"] == "3.0" else EMPHASIS.BACKGROUND for r in releases ], figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Heavier Cars Travel Fewer Miles per Gallon (Categorical Axis and Category Order) `cars` holds 30 cars from the [Auto MPG](https://archive.ics.uci.edu/dataset/9/auto+mpg) dataset of the UCI Machine Learning Repository (CC BY 4.0), sold in the United States between 1970 and 1982: the `model` name, the number of `cylinders`, the `horsepower`, the `weight (lb)`, the fuel economy in `mpg`, and the region of `origin`. The question is the trade-off between size and economy, so `weight (lb)` sits right next to `mpg`, where the segments cross in an X. The model name is a label, not a variable (as an axis it would have 30 ticks), so `dimensions` leaves it out. The origin is both the `hue` and the last axis; `category_orders` puts the USA at the bottom and Japan at the top, the order the regions take on the mpg axis next to it, so the lines reach the last axis without needless crossings. The American cars have the most cylinders, the most power and the heaviest bodies, and the fewest miles per gallon; the Japanese cars are their mirror image. ``` ParallelCoords( data=cars, title="Cars of the 1970s by region of origin", # the model name is a label, not an axis; weight sits next to mpg dimensions=["cylinders", "horsepower", "weight (lb)", "mpg", "origin"], # color by the origin, and keep it as the last axis hue="origin", show_legend=True, legend={"title": "Origin", "location": LEGEND_LOCATION.OUTSIDE_RIGHT}, # bottom to top, the order the regions take on the mpg axis category_orders={"origin": ["USA", "Europe", "Japan"]}, style={"plot_parallel_alpha": 0.7, "plot_parallel_width": 1.5}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: What the Best Runs Share (Emphasis Rule on a Numeric Hue) `runs` holds the 24 runs of an illustrative hyperparameter search of an image classifier, in the shape a tracking tool such as MLflow or Optuna reports them: the `optimizer`, the `log10 learning rate` (the rate was sampled on a logarithmic grid from 10⁻⁴ to 10⁻², and on a linear axis the raw values would pile up at the bottom), the `batch size`, the `dropout`, the number of `epochs`, and the validation `accuracy` the run reached. A parallel coordinates chart is the standard view of such a search, and its question is which settings lead to a high score. The `hue` on the accuracy gives `emphasis_rule={"top": 3}` its values, the rule mutes all but the three best runs, and a fixed line color keeps the three equally visible (see [Emphasis](#emphasis)). The accuracy stays as the last axis, so the three lines end at the top of it, and their shared path stands out: Adam, a learning rate of 10⁻³, a dropout of 0.2 to 0.3 and 25 to 30 epochs. The batch size is where they disagree, so it matters least. ``` ParallelCoords( data=runs, title="Hyperparameter search: the three best runs", # the accuracy stays as the last axis dimensions=RUN_COLUMNS, # the rule ranks the runs by the hue key hue="accuracy", # keep the three most accurate runs, mute the rest emphasis_rule={"top": 3}, style={"plot_parallel_color": "#c1121f"}, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: What Sets Each Penguin Species Apart (Emphasis, Notes and a Grid) Back to the 30 Palmer penguins of the shared dataset, with one chart per species. Each chart highlights one species against the other two with `emphasis`, and a note from `texts` points at the trait that sets it apart: the Adelie's short bills, the Chinstrap's bills that are both long and deep, the Gentoo's shallow bills and long flippers. [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) stacks the three charts in one figure, and the notes travel with their charts. The charts draw the same records, so their axes span the same ranges and a height means the same value in each. ``` from datachart.utils import Grid # each species' note, its position, and the point it names (x = axis index) TRAITS = { "Adelie": ("short bills", (0.45, 0.1), (0.02, 0.15)), "Chinstrap": ("long, deep bills", (0.5, 1.05), (0.1, 0.9)), "Gentoo": ("shallow bills,\nlong flippers", (1.3, 0.12), (1.02, 0.05)), } def species_profile(species): # one species in front, the other two muted, and a note on its trait text, (x, y), target = TRAITS[species] return ParallelCoords( data=penguins, title=species, dimensions=MEASUREMENTS, hue="species", emphasis=[ EMPHASIS.HIGHLIGHT if p["species"] == species else EMPHASIS.BACKGROUND for p in penguins ], texts={"text": text, "x": x, "y": y, "target": target}, ) Grid( [[species_profile("Adelie")], [species_profile("Chinstrap")], [species_profile("Gentoo")]], title="What sets each penguin species apart", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Highlighting When a figure carries many series, the story is usually about one of them: one model run among its competitors, one cohort inside the population, one trend over raw observations. The `emphasis` parameter expresses that relationship directly — per chart, not via global styling: - `"background"` mutes a series: it takes the active theme's `muted_color` at `muted_alpha`, gets thinner strokes, drops behind the other series, claims no color-cycle slot, and is excluded from the legend. - `"highlight"` bolds a series and brings it to the front of the data layers (never above axes or reference lines). It keeps its theme-assigned color and legend entry. - Leaving it unset (`None`) draws the series exactly as before. `emphasis` is accepted by `LineChart`, `BarChart`, `ScatterChart`, `Histogram`, `ParallelCoords` (per data row), and `BoxPlot` (per box label), and as a per-figure `"emphasis"` option in `Panel`. Bar records — in `BarChart`, `PyramidChart`, and the `RadialChart` bar visual — also carry their own `"emphasis"` key. Every chart that carries emphasis, plus `Heatmap` and `HexbinChart`, also takes an `emphasis_rule` that fills the roles in from the values — see [Emphasis picked by a rule](#emphasis-picked-by-a-rule). Because muting derives from the theme's `muted_color`/`muted_alpha` attributes, background series harmonize with whatever theme is active. The role strings are also available as constants: `datachart.constants.EMPHASIS.BACKGROUND` and `EMPHASIS.HIGHLIGHT`. ``` import numpy as np from datachart.charts import ( BarChart, BoxPlot, Heatmap, HexbinChart, Histogram, LineChart, ParallelCoords, ScatterChart, ) from datachart.utils import Panel from datachart.config import config from datachart.constants import SORT, THEME ``` ## One Walk Among Many The `emphasis` list aligns with the charts, like `style` and `subtitle`. Background walks fade into context; the highlighted walk keeps its cycle color and doubles its line width. Only emphasized-or-unset series appear in the legend. ``` def walk(seed, n=60): rng = np.random.RandomState(seed) return [{"x": i, "y": float(v)} for i, v in enumerate(np.cumsum(rng.randn(n)))] walks = [walk(seed) for seed in range(6)] figure = LineChart( data=walks, subtitle=[f"run {i}" for i in range(6)], emphasis=["background", "background", "background", None, "highlight", "background"], show_legend=True, title="One walk among many", ) figure.show() ``` ## The Same Figure Under Another Theme Muting is defined by the theme's `muted_color` and `muted_alpha` attributes, so the same chart code stays harmonious under any theme — no hand-picked greys. ``` config.set_theme(THEME.MATERIAL) figure = LineChart( data=walks, subtitle=[f"run {i}" for i in range(6)], emphasis=["background", "background", "background", None, "highlight", "background"], show_legend=True, title="One walk among many (MATERIAL)", ) figure.show() config.reset_config() ``` ## A Cohort Inside a Scatter Cloud A highlighted scatter series keeps its marker size but gains a contrasting edge; the background cloud recedes without disappearing. ``` rng = np.random.RandomState(3) population = [ {"x": float(x), "y": float(x * 0.8 + rng.randn() * 2)} for x in rng.rand(80) * 20 ] cohort = [ {"x": float(x), "y": float(x * 1.4 + 4 + rng.randn())} for x in rng.rand(20) * 20 ] figure = ScatterChart( data=[population, cohort], subtitle=["population", "cohort"], emphasis=["background", "highlight"], show_legend=True, title="Cohort against the population", ) figure.show() ``` ## Best Runs in Parallel Coordinates For `ParallelCoords` the `emphasis` list aligns with the data **rows**. Highlighted rows come forward but stay below the axis furniture, so the axis lines and tick labels remain readable. ``` rng = np.random.RandomState(11) runs = [ { "speed": float(rng.rand() * 10), "cost": float(rng.rand() * 100), "score": float(rng.rand()), } for _ in range(15) ] best = sorted(range(len(runs)), key=lambda i: runs[i]["score"])[-2:] figure = ParallelCoords( data=runs, dimensions=["speed", "cost", "score"], emphasis=["highlight" if i in best else "background" for i in range(len(runs))], title="Best runs", ) figure.show() ``` ## A Cohort Against a Reference Distribution Multi-series histograms normally draw stacked. Stacking a muted background is meaningless, so as soon as any series carries an emphasis role the histograms draw individually overlaid — on shared bins, with the background distribution behind the cohort. ``` rng = np.random.RandomState(7) reference = [{"x": float(v)} for v in rng.randn(400) * 1.4 + 0.5] cohort = [{"x": float(v)} for v in rng.randn(160) * 0.8 + 2.0] figure = Histogram( data=[reference, cohort], subtitle=["reference", "cohort"], emphasis=["background", None], num_bins=18, show_legend=True, title="Cohort vs reference", ) figure.show() ``` ## Per-Label Emphasis in a Box Plot Box charts never overlay, so their `emphasis` aligns with the box **labels** of one call. Whiskers, caps, medians, and outliers mute together with their box; a highlighted box gets bolder edges and a bolder median. ``` rng = np.random.RandomState(9) data = [ {"label": lab, "value": float(v + off)} for lab, off in [("A", 0.0), ("B", 2.0), ("C", 1.0), ("D", 3.0)] for v in rng.randn(30) ] figure = BoxPlot( data=data, emphasis=["background", None, "highlight", "background"], title="One group under scrutiny", ) figure.show() ``` ## Emphasis Picked by a Rule Often the emphasis follows from the data — the best runs, the groups above a target, the densest region — and writing the roles by hand repeats what the values already say. `emphasis_rule` is a one-key dict that highlights every unit matching it and mutes the rest: | Rule | Highlights | | ------------------------------ | --------------------------------------------------------- | | `{"above": v}`, `{"below": v}` | values strictly above or below `v` | | `{"between": (lo, hi)}` | values from `lo` to `hi`, both included | | `{"top": n}`, `{"bottom": n}` | the `n` largest or smallest values; ties keep input order | Each chart applies the rule to the unit it already gives emphasis to, and reads one number per unit: | Unit | Number the rule reads | | --------------------------------------------------------------------------- | ------------------------------------------------------ | | a bar record, treemap leaf, or network node | its own value: `y`, `value`, or `size` | | a parallel coordinates row | its numeric `hue` value | | a heatmap cell or hexbin bin | the cell value, or the bin's aggregated value | | a group of a box, violin, swarm, or raincloud plot | a summary of the group's values, the median by default | | a series of a line, scatter, stacked area, histogram, or line contour chart | a summary of the series' values, the mean by default | Groups and series take an optional `"by"` key next to the rule — `"mean"`, `"median"`, `"min"`, `"max"`, or `"sum"` — to choose the summary; a chart that reads one value per unit rejects it. On every chart an explicit role wins over the rule, and `top`/`bottom` rank across every unit of the call, across subplots too — except hexbin bins, which exist only once drawn and rank within their own chart. The rule's shape is documented in [EmphasisRuleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.EmphasisRuleAttrs). ### Bars A bar chart's emphasis can come from the data instead of a hand-written list. `emphasis_rule` is a one-key dict — `{"above": v}`, `{"below": v}`, `{"between": (lo, hi)}`, `{"top": n}`, or `{"bottom": n}` — that highlights every bar matching it and mutes the rest; the contrast is the point, so the rule commits to both ends. Each bar record may also carry its own `"emphasis"` key, and an explicit record role always wins over the rule, which is how "the top three, and also this one" is said. `sort` orders the categories by value so the ranking reads left to right; it never changes which bars the rule picks. ``` rng = np.random.RandomState(13) teams = [ {"label": f"team {chr(65 + i)}", "y": float(round(rng.rand() * 80 + 10))} for i in range(10) ] # the team under review is highlighted whatever its score teams[7]["emphasis"] = "highlight" figure = BarChart( data=teams, title="Top three by score, plus the team under review", ylabel="Score", sort=SORT.DESCENDING, emphasis_rule={"top": 3}, show_values=True, value_format="{:.0f}", ) figure.show() ``` ### Series The random walks from above, picked by their peak instead of by hand: `"by": "max"` summarises each walk by its highest point, and `{"top": 2}` keeps the two highest. ``` figure = LineChart( data=walks, subtitle=[f"run {i}" for i in range(6)], emphasis_rule={"top": 2, "by": "max"}, show_legend=True, title="The two highest peaks", ) figure.show() ``` ### Groups The box plot from above, with the groups picked by their median. The rule reads the same median line the box draws, so the highlighted boxes are the ones whose line clears the threshold. ``` figure = BoxPlot( data=data, emphasis_rule={"above": 1.5}, title="Groups with a median above 1.5", ) figure.show() ``` ### Records Parallel coordinates read the rule against each row's `hue` value — the column the rows are already colored by — so the best runs from above no longer need a hand-built role list. The highlighted rows keep the hue ramp, spread over their own scores. ``` figure = ParallelCoords( data=runs, dimensions=["speed", "cost", "score"], hue="score", emphasis_rule={"top": 4}, title="The four best runs, picked by score", ) figure.show() ``` ### Cells and Bins A heatmap cell fades to the theme's `muted_alpha` when muted, so it still reads on the colormap, and a highlighted cell is outlined; a blank cell never matches. Cells also take explicit roles through an `emphasis` grid aligned with `z`, which wins over the rule. A hexbin's bins exist only once they are drawn, so `HexbinChart` takes the rule alone, read against each bin's count (or its reduced `c` value). ``` rng = np.random.RandomState(21) hours = [f"{h:02d}h" for h in range(8, 18)] days = ["Mon", "Tue", "Wed", "Thu", "Fri"] load = { "x": hours, "y": days, "z": [[int(rng.poisson(20 + 15 * np.sin((h - 8) / 3))) for h in range(8, 18)] for _ in days], } figure = Heatmap( data=load, emphasis_rule={"top": 5}, show_heatmap_values=True, title="The five busiest hours", ) figure.show() points = rng.multivariate_normal([0, 0], [[1, 0.6], [0.6, 1]], size=3000) figure = HexbinChart( data={"x": points[:, 0].tolist(), "y": points[:, 1].tolist()}, gridsize=20, emphasis_rule={"above": 40}, show_colorbars=True, title="Bins with more than 40 points", ) figure.show() ``` ## Composing Context and Focus with Panel `Panel` accepts a per-figure `"emphasis"` option next to `y_axis`, `z_order`, and `legend_label`. The role applies to every layer of that figure — here the raw observations become context under a highlighted trend. The muted figure drops out of the legend automatically. ``` rng = np.random.RandomState(5) observations = [{"x": float(v)} for v in rng.randn(400)] xs = np.linspace(-3.5, 3.5, 60) trend = [{"x": float(x), "y": float(60 * np.exp(-x * x / 2))} for x in xs] hist_fig = Histogram(data=observations, num_bins=24, subtitle="observations") trend_fig = LineChart(data=trend, subtitle="trend") figure = Panel( [ {"figure": hist_fig, "emphasis": "background"}, {"figure": trend_fig, "emphasis": "highlight"}, ], title="Trend over observations", show_legend=True, ) figure.show() ``` Composed parallel-coordinates figures also normalize against shared per-dimension ranges inside a `Panel`, so a muted context figure and a highlighted runs figure line up on the same axis scales. # Scatter Matrix A scatter matrix is the first look at a table with several numeric columns: it draws a scatter chart for every pair of columns at once, so you can see which pairs move together, which groups separate, and where the outliers are, before picking one pair to study in a [scatter chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md). This guide shows how to create scatter matrices with the [datachart.charts.ScatterMatrix](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/#datachart.charts.ScatterMatrix) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-scatter-matrix), which maps common tasks to the parameter or style attribute that does the job. ``` from datachart.charts import ScatterMatrix ``` ## Basics The examples in this guide share one dataset: the 342 penguins measured on three islands of the Palmer Archipelago, Antarctica (source: the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset, Gorman, Williams and Fraser 2014, released under CC0; the two penguins with no measurements are left out). The data lives in a hidden cell. `penguins` is a list of records, one dictionary per penguin, with four measurements (`"bill length"` and `"bill depth"` in millimeters, `"flipper length"` in millimeters, `"body mass"` in grams) and two categories (`"species"`, one of Adelie, Chinstrap and Gentoo, and `"sex"`, `None` for the nine penguins whose sex was not recorded). `MEASUREMENTS` lists the four measurement names. The table hides a trap that a scatter matrix is good at exposing: pooled over all penguins, deeper bills go with shorter ones, yet within every species the opposite is true. Each record is one penguin: ``` penguins[:3] ``` **Basic example.** Only the `data` argument is required. Every numeric column becomes a dimension, in the order the columns first appear, so the four measurements make a four-by-four matrix and the text columns are left out. Each cell below the diagonal plots the variable of its row (y-axis) against the variable of its column (x-axis), the diagonal shows a histogram of each variable, and the cells above the diagonal mirror the ones below. Flipper length and body mass rise together almost on a line; the other pairs form clumps, a first hint that the table mixes groups. ``` ScatterMatrix( # add the data to the chart data=penguins, ).show() ``` ## Customizing the Scatter Matrix Every customization is either a keyword argument of `ScatterMatrix` or an attribute of its `style` dictionary. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ----------------------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title or resize the figure | `title`, `figsize` | [Title and figure size](#title-and-figure-size) | | pass the table as columns instead of records | `data` as a dictionary of lists | [Columns or records](#columns-or-records) | | choose and order the variables | `dimensions` | [Selecting dimensions](#selecting-dimensions) | | color the points by a category | `hue` | [Hue groups](#hue-groups) | | show a density curve or nothing on the diagonal | `diagonal` | [Diagonal](#diagonal) | | drop the mirrored cells | `lower_only` | [Lower triangle only](#lower-triangle-only) | | print the correlation of each pair | `show_correlation` | [Correlation](#correlation) | | fit a line to each group | `show_regression` | [Regression lines](#regression-lines) | | give each cell its own axis range | `sharex`, `sharey` | [Shared axes](#shared-axes) | | show grid lines | `show_grid` | [Grid lines](#grid-lines) | | title, arrange or hide the legend | `legend`, `show_legend` | [Legend](#legend) | | restyle points, bars, curves and lines | `style` | [Scatter matrix style](#scatter-matrix-style) | | put two matrices side by side | `Grid` | [Scatter matrices in a Grid](#scatter-matrices-in-a-grid) | | change the whole look at once | `config.set_theme` | [Themes](#themes) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `diagonal` | [`SCATTER_MATRIX_DIAGONAL`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCATTER_MATRIX_DIAGONAL) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | The full list of style attributes is in the [datachart.typings.ScatterMatrixStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/#datachart.typings.ScatterMatrixStyleAttrs) type; the full list of parameters is in the [datachart.charts.ScatterMatrix](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/#datachart.charts.ScatterMatrix) reference. ### Title and figure size A matrix of sixteen cells needs a title to say what table it shows, and `title` adds one above the whole figure. By default every cell gets 2.2 inches a side, shrunk so the whole figure fits a page width of 6.3 inches; a four-by-four matrix gets cells of about 1.6 inches. `figsize` takes a `(width, height)` tuple in inches for the whole figure instead, and the cells stay square inside it; here it adds height for the title. The title is also the place for the units, since the axis labels are the column names. ``` ScatterMatrix( data=penguins, # say what the table is, and its units title="Palmer penguins (mm, g)", # the whole figure, not one cell figsize=(6.3, 6.6), ).show() ``` ### Columns or records Data rarely arrives as a list of records: a CSV reader or a data frame hands over one list per column. `data` also takes a dictionary of equal-length columns, and draws the same matrix. Records suit data built row by row; columns suit data read column by column (`frame.to_dict("list")` turns a pandas data frame into this shape). Unlike most charts, a scatter matrix draws one table, so `data` is never a list of tables. Here the three bill and flipper measurements, as columns: ``` columns = {name: [penguin[name] for penguin in penguins] for name in MEASUREMENTS[:3]} ScatterMatrix( # one list per column instead of one dictionary per penguin data=columns, ).show() ``` ### Selecting dimensions A wide table makes a matrix too large to read, and the column order decides which pairs sit next to each other. `dimensions` lists the numeric columns to plot, in order: the first labels the top row and the left column. Here the two bill measurements come first, since their relationship is the story of this guide, followed by body mass. Missing values (`None`) are left out pair by pair, so a record missing one measurement still appears in every cell that does not need it. ``` BILL_AND_MASS = ["bill length", "bill depth", "body mass"] ScatterMatrix( data=penguins, # three measurements, bills first dimensions=BILL_AND_MASS, ).show() ``` ### Hue groups The clumps in the matrix above are the three species, and naming them turns clumps into an answer. `hue` names a categorical column: every category gets one color from the theme's palette, the same color in every cell, and one legend beside the matrix names them all. The diagonal overlays one histogram per category. Colored by species, the bill cells separate cleanly: Adelie penguins have short, deep bills, Gentoo penguins long, shallow ones, and Chinstrap penguins long, deep ones. The hue column must be text and must have no missing values; a numeric column raises an error, so bin it into categories first. ``` ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, # one color per species, one legend for the figure hue="species", ).show() ``` ### Diagonal The diagonal cell of a variable shows its own distribution, and the right view depends on the question ([SCATTER_MATRIX_DIAGONAL](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCATTER_MATRIX_DIAGONAL)). `SCATTER_MATRIX_DIAGONAL.HIST`, the default, draws a histogram: honest about the counts, but overlaid histograms of several groups get busy. `SCATTER_MATRIX_DIAGONAL.KDE` draws one smooth density curve per hue group, the clearest way to compare where the groups sit on one variable. The diagonal cell's axis shows its row's scale, like the other cells; the height of the histogram or curve is drawn to its own, unlabelled scale. The density curves show that bill length alone tells Adelie penguins apart, while bill depth alone tells Gentoo penguins apart. ``` from datachart.constants import SCATTER_MATRIX_DIAGONAL ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", # one density curve per species diagonal=SCATTER_MATRIX_DIAGONAL.KDE, ).show() ``` When the distributions are not the question, `SCATTER_MATRIX_DIAGONAL.NONE` leaves the diagonal blank, which suits a matrix whose readers only care about the pairs. The tick labels and variable names move to the outermost cell that is drawn: ``` ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", # nothing on the diagonal diagonal=SCATTER_MATRIX_DIAGONAL.NONE, ).show() ``` ### Lower triangle only The cells above the diagonal repeat the cells below it with the axes swapped, so half the matrix is redundant. `lower_only=True` leaves the upper cells empty, which halves the ink and makes a large matrix easier to scan: ``` ScatterMatrix( data=penguins, hue="species", # draw only the cells below the diagonal lower_only=True, ).show() ``` With a blank diagonal as well, the empty top row and right column are dropped, so the four measurements take a three-by-three grid: ``` ScatterMatrix( data=penguins, hue="species", lower_only=True, # no diagonal: the empty top row and right column go diagonal=SCATTER_MATRIX_DIAGONAL.NONE, ).show() ``` ### Correlation A cloud of points shows the shape of a relationship, but not its strength as a number. `show_correlation=True` replaces the cells above the diagonal with the Pearson correlation coefficient of each pair, from -1 (a perfect falling line) to 1 (a perfect rising line). Pooled over all penguins, bill length and bill depth have a coefficient of -0.24: deeper bills seem to go with shorter ones. `lower_only` wins over `show_correlation`: with both set, the upper cells stay empty. The same coefficient is available in code as [datachart.utils.stats.correlation](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/#datachart.utils.stats.correlation). ``` ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, # print the correlation of each pair above the diagonal show_correlation=True, ).show() ``` With a `hue`, each cell prints one coefficient per group, in the group's color, and the story flips. Within every species, bill length and bill depth rise together (0.39, 0.65 and 0.64), the opposite of the pooled -0.24. This is Simpson's paradox: Gentoo penguins have the longest and the shallowest bills, so pooling the species creates a falling trend that no single species has. Always split by the groups you know about before trusting a pooled coefficient. ``` ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, # one coefficient per species hue="species", show_correlation=True, ).show() ``` ### Regression lines A coefficient summarizes a trend; a line shows it where the points are. `show_regression=True` fits a least-squares line to each hue group in every scatter cell, in the group's color. In the bill cell every species slopes up, while the three clusters themselves sit on a falling diagonal: the paradox in one picture. With 342 points the lines are hard to pick out; the [Scatter matrix style](#scatter-matrix-style) section below makes them stand out. ``` ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", # a least-squares line per species show_regression=True, show_correlation=True, ).show() ``` ### Shared axes By default the cells of a column share one x-axis and the cells of a row share one y-axis, the diagonal cells included: only the bottom row and the left column label their ticks, and a point sits at the same position in every cell of its row. That is what makes the matrix scannable. When a group is small and its cells cramped, `sharex=False` and `sharey=False` fit each cell to its own data instead; every cell then labels its own ticks, and the diagonal shows its counts or densities. Here only the Chinstrap penguins are plotted, so each cell zooms in on one species: ``` chinstraps = [penguin for penguin in penguins if penguin["species"] == "Chinstrap"] ScatterMatrix( data=chinstraps, dimensions=BILL_AND_MASS, title="Chinstrap penguins", # fit every cell to its own data sharex=False, sharey=False, ).show() ``` ### Grid lines Grid lines help read a point's value off the axes of a crowded cell, far from the tick labels. `show_grid` draws them in every scatter and diagonal cell ([SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID)): `SHOW_GRID.BOTH`, `SHOW_GRID.X` or `SHOW_GRID.Y`. The cells with correlation text never show them. ``` from datachart.constants import SHOW_GRID ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", # grid lines on both axes of every cell show_grid=SHOW_GRID.BOTH, ).show() ``` ### Legend With a `hue`, one legend beside the matrix names the groups, whatever the number of cells, and the hue column's name titles it. A column name is often a poor legend title, and `legend` sets the `title` (an empty string hides it), the number of columns `ncols` and the `alignment` of the entries ([LEGEND_ALIGN](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN)); see [LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs). The legend sits outside the cells, so its `location` takes one of the four outside edges of [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION): `OUTSIDE_RIGHT`, the default, `OUTSIDE_LEFT`, `OUTSIDE_TOP` or `OUTSIDE_BOTTOM`; any other location raises an error. Above or below the matrix, the entries sit side by side in one row, which leaves the full page width to the cells. `show_legend=False` hides the legend, for a figure whose caption already names the colors. ``` from datachart.constants import LEGEND_LOCATION ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", # a proper title, and one row of entries under the matrix legend={"title": "Penguin species", "location": LEGEND_LOCATION.OUTSIDE_BOTTOM}, ).show() ``` ### Scatter matrix style With 342 penguins the points overlap, and the regression lines drown in them. `style` applies to every cell. The points take the `plot_scatter_*` attributes and the histograms the `plot_hist_*` attributes, as in the [scatter chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md) and the [histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md); the `plot_scatter_matrix_*` attributes style what the matrix adds: the regression lines, the correlation text, the density curves and the transparency of the overlaid histograms. The current values come from the [config](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/#what-a-theme-controls): ``` from datachart.config import config {key: value for key, value in config.config.items() if key.startswith("plot_scatter_matrix_")} ``` Smaller, fainter points let the density show through, dashed dark lines stand out from any group color, and thin unfilled density curves keep the diagonal light: ``` from datachart.constants import LINE_STYLE ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", diagonal=SCATTER_MATRIX_DIAGONAL.KDE, show_regression=True, style={ # smaller, fainter points "plot_scatter_size": 14, "plot_scatter_alpha": 0.4, # dark dashed regression lines "plot_scatter_matrix_regression_color": "#222222", "plot_scatter_matrix_regression_style": LINE_STYLE.DASHED, # thin density curves without a fill "plot_scatter_matrix_kde_width": 1.0, "plot_scatter_matrix_kde_alpha": 0, }, ).show() ``` ## Multiple Scatter Matrices A scatter matrix owns its whole grid of axes, so it cannot be overlaid on another chart with [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel). It can sit beside other charts, or beside another matrix, in a [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid). ### Scatter matrices in a Grid Two groupings of the same table are easiest to compare side by side. `Grid` places each matrix in one cell, with its title as the cell's heading. Colored by species, the two bill measurements split into three clusters; colored by sex, the colors mix within every cluster. Males have somewhat larger bills than females of their species, but the differences between species are far larger, so species is the grouping to split by. The penguins without a recorded sex are left out, since a `hue` column cannot have missing values. ``` from datachart.utils import Grid sexed = [penguin for penguin in penguins if penguin["sex"] is not None] by_species = ScatterMatrix( data=sexed, dimensions=["bill length", "bill depth"], hue="species", diagonal=SCATTER_MATRIX_DIAGONAL.KDE, legend={"title": "Species"}, style={"plot_scatter_size": 12}, title="By species", ) by_sex = ScatterMatrix( data=sexed, dimensions=["bill length", "bill depth"], hue="sex", diagonal=SCATTER_MATRIX_DIAGONAL.KDE, legend={"title": "Sex"}, style={"plot_scatter_size": 12}, title="By sex", ) # one matrix per cell Grid([[by_species, by_sex]], title="Penguin bills", figsize=(6.3, 3.0)).show() ``` ## Additional Features ### Themes A theme sets the palette, the fonts and the marks of every cell at once, which matters for a matrix of many cells that a per-call `style` would have to restyle one attribute at a time. Apply one with [datachart.config.Config.set_theme](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme); the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows every chart under each theme. The figure is drawn when the chart is created, so the theme is reset right after: ``` from datachart.constants import THEME config.set_theme(THEME.MINIMAL) figure = ScatterMatrix( data=penguins, dimensions=BILL_AND_MASS, hue="species", ) config.set_theme(THEME.DEFAULT) figure.show() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: Which Car Specs Predict Fuel Economy? (Density Diagonal, Correlation and Regression) `cars` holds four columns for the 32 cars of the classic `mtcars` table (Henderson and Velleman 1981, from the 1974 *Motor Trend* US magazine): fuel economy in miles per gallon (`"mpg"`), gross horsepower (`"horsepower"`), weight in thousands of pounds (`"weight"`) and the number of cylinders (`"cylinders"`, as text so it can color the points). The question is which spec tracks fuel economy most closely. Coloring by cylinders shows that the engine size already splits the cars into three bands, the density curves on the diagonal show how far apart the bands sit, and the correlations and regression lines show how much of the trend survives within each band. Pooled over all cars, weight (r = -0.87) and horsepower (r = -0.78) both track fuel economy. Within each engine size, weight still does (-0.65 to -0.71), while the link to horsepower weakens (-0.13 to -0.52): heavier cars use more fuel whatever their engine. ``` ScatterMatrix( data=cars, hue="cylinders", # one density curve per engine size diagonal=SCATTER_MATRIX_DIAGONAL.KDE, # the strength and the slope of each trend, per engine size show_correlation=True, show_regression=True, legend={"title": "Engine"}, title="Fuel economy, power and weight of 32 cars (1974)", figsize=(6.3, 5.4), ).show() ``` ### Example 2: Which Evaluation Metrics Move Together? (Lower Triangle, Blank Diagonal and Regression) `runs` holds the scores of twenty training runs of a classifier with different decision thresholds (illustrative values): precision, recall, F1 and the mean prediction latency in milliseconds. Before reporting one metric, it is worth knowing which ones carry the same information. Only the pairs matter, so the matrix drops the redundant upper cells and the diagonal, and a regression line per cell shows the trend. Precision and recall trade off along a falling line. Recall moves more than precision across these thresholds, so F1 follows recall and falls as precision rises: reporting F1 alone would hide the precision gain. Latency is unrelated to the other three; its slopes are noise. ``` ScatterMatrix( data=runs, # only the pairs: no upper cells, no diagonal lower_only=True, diagonal=SCATTER_MATRIX_DIAGONAL.NONE, # the trend of each pair show_regression=True, title="Twenty training runs", ).show() ``` ### Example 3: Do Sepals or Petals Tell the Iris Species Apart? (Two Matrices in a Grid) `sepals` and `petals` hold the length and width, in centimeters, of the sepals and the petals of 150 iris flowers, 50 of each species (source: Anderson 1935, published by Fisher 1936, the classic `iris` dataset). The question is which part of the flower tells the species apart. Two small matrices in a `Grid` put the answer side by side: the sepal clouds of *versicolor* and *virginica* overlap, while the petal clouds separate all three species, *setosa* by a wide margin. The density curves on the diagonals show the same thing one measurement at a time, and one legend is enough for the two matrices. ``` sepal_matrix = ScatterMatrix( data=sepals, hue="species", diagonal=SCATTER_MATRIX_DIAGONAL.KDE, # the petal matrix names the species show_legend=False, title="Sepals (cm)", ) petal_matrix = ScatterMatrix( data=petals, hue="species", diagonal=SCATTER_MATRIX_DIAGONAL.KDE, legend={"title": "Species"}, title="Petals (cm)", ) Grid( [[sepal_matrix, petal_matrix]], title="Iris flowers: which part tells the species apart?", figsize=(6.3, 3.0), ).show() ``` # Composition # Composition The composition functions of the [datachart.utils](https://eriknovak.github.io/datachart/0.10.2/references/utils/index.md) module take figures already drawn by the chart functions and draw them again: overlaid in one coordinate space, side by side in a grid, or with annotations added. Each card names a guide, says what it is for, and links to it. - [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) Overlays several charts in one coordinate space, with a shared x-axis and up to two y-axes, through [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel). - [Grid Layout](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) Arranges several charts in the cells of one figure, with nested rows for the layout, through [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid). - [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) Attaches text, boxes, and connectors to a chart through its `texts` parameter, or to a finished figure through [datachart.utils.Annotate](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Annotate). # Panel Some questions need two charts in one coordinate space: measurements against the model that should explain them, a count against a rate, an event against its background. A panel overlays figures already drawn by the chart functions of the [datachart.charts](https://eriknovak.github.io/datachart/0.10.2/references/charts/index.md) module (any chart from the [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) guides) and redraws them on a shared x-axis with up to two y-axes, so they read against each other. Where the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) keeps every figure in a coordinate space of its own, the panel merges them: reach for a grid to compare charts side by side, and for a panel to overlay them. This guide shows how to build panels with the [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) function, starting with the basics and building up to worked examples. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-panel), which maps common tasks to the parameter or per-figure option that does the job. ``` from datachart.charts import BarChart, LineChart from datachart.utils import Panel ``` ## Basics The examples in this guide share one dataset: the monthly climate normals of Ljubljana's weather station, the mean temperature (in °C) and the total precipitation (in mm) of each month, rounded from the published 1991–2020 values. Plotted together they form a *climograph*, the standard chart of a climate and a panel by nature: precipitation as bars, temperature as a line, each on its own value axis. The data lives in a hidden cell. A panel overlays figures, so the first step is to draw each chart on its own. The precipitation is a bar chart with one labeled bar per month; the temperature is a line chart whose `x` values are the month indices, so its points land on the bars. The `subtitle` of each chart becomes its label in the panel legend: ``` precipitation = BarChart(data=precipitation_data, subtitle="Precipitation (mm)") temperature = LineChart(data=temperature_data, subtitle="Temperature (°C)") ``` **Basic example.** Only the list of figures is required. The figures are drawn in the order given, the months of the bar chart label the shared x-axis, and the value axes are assigned automatically: the temperature spans about 20 units and the precipitation about 80, so the temperature moves to a second axis on the right. The [Axis assignment](#axis-assignment) section explains the rule and how to override it. ``` Panel( # add the figures to the panel [precipitation, temperature] ).show() ``` ## Customizing the Panel Every customization is either a keyword argument of `Panel` or a per-figure option of the dictionary wrapping a figure. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------ | ------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------- | | add a title and axis labels | `title`, `xlabel`, `ylabel_left`, `ylabel_right` | [Title and axis labels](#title-and-axis-labels) | | resize the figure | `figsize` | [Figure size and grid](#figure-size-and-grid) | | show the grid lines | `show_grid` | [Figure size and grid](#figure-size-and-grid) | | show which figure is which | `show_legend`, the charts' `subtitle`, per-figure `"legend_label"` | [Legend](#legend) | | title and place the legend | `legend` | [Legend](#legend) | | put a figure on the right value axis | per-figure `"y_axis"` | [Axis assignment](#axis-assignment) | | tune the automatic axis assignment | `auto_secondary_axis` | [Axis assignment](#axis-assignment) | | bring a figure to the front | per-figure `"z_order"` | [Drawing order](#drawing-order) | | highlight one figure, mute the rest | per-figure `"emphasis"` | [Emphasis](#emphasis) | | limit the axes | `xmin`, `xmax`, `ymin`, `ymax`, `ymin_right`, `ymax_right` | [Axis limits](#axis-limits) | | scale an axis (linear, log, …) | `scalex`, `scaley`, `scaley_right`, or the charts' own `scaley` | [Axis scales](#axis-scales) | | overlay several bar charts | `bar_mode` | [Bar mode](#bar-mode) | | add a figure to an existing panel | nest `Panel` figures | [Nesting panels](#nesting-panels) | | overlay horizontal bars | `orientation` on the bar charts, the same `Panel` parameters | [Horizontal panels](#horizontal-panels) | | change the defaults of every panel | `config.update_config` with the `overlay_*` settings | [Panel configuration](#panel-configuration) | | save the panel to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The full list of parameters and per-figure options is in the [datachart.utils.Panel](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Panel) reference. The look of each figure (colors, line widths, markers) is set on the chart itself through its `style` attribute; see the guide of each chart in the [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) section. ### Title and axis labels Two value axes need two labels, or the reader cannot tell which scale a mark is read against. `title` names the panel, `xlabel` the shared axis, and `ylabel_left` and `ylabel_right` the two value axes; `ylabel_right` is only drawn when a figure sits on the right axis. ``` Panel( [precipitation, temperature], # add the title title="Climate of Ljubljana", # add the x and y axis labels, one per value axis xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", ).show() ``` ### Figure size and grid A panel is usually the centerpiece of a page, and it should be sized like one. `figsize` takes a `(width, height)` tuple in inches or one of the presets in [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE); the panel owns its figure size, whatever the sizes of the figures it overlays. Grid lines let the eye carry a mark across to an axis, and with two value axes they can only follow one, the left: `show_grid` draws them with a [SHOW_GRID](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) member. ``` from datachart.constants import FIG_SIZE, SHOW_GRID Panel( [precipitation, temperature], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", # a wide, short figure figsize=FIG_SIZE.FULL_SHORT, # grid lines along the left value axis show_grid=SHOW_GRID.Y, ).show() ``` ### Legend Overlaid figures need a legend more than any single chart does, because nothing else says which mark is which. `show_legend` merges the entries of every figure, labeled by the `subtitle` of its chart, and when the panel has two value axes an `(L)` or `(R)` suffix says which axis an entry is read against. A figure that should be labeled differently in the panel than on its own is wrapped in a dictionary with a `"legend_label"`, which overrides its subtitle. `legend` then says where and how, with a `title`, a `location` from [LEGEND_LOCATION](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), the number of columns `ncols` and the `alignment` of the entries ([LegendSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendSettingAttrs)); the axis labels already carry the units, so the legend labels can drop them. ``` from datachart.constants import LEGEND_LOCATION Panel( [ # override the subtitle of each chart in the legend {"figure": precipitation, "legend_label": "Precipitation"}, {"figure": temperature, "legend_label": "Mean temperature"}, ], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, # show the legend, in one row above the axes show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 2}, ).show() ``` ### Axis assignment Two quantities in different units cannot share a scale without one of them flattening out, which is what the second value axis is for. Each figure takes its axis from the per-figure `"y_axis"` option: `"left"`, `"right"`, or `"auto"`, the default, where the panel compares the span of each figure's values and separates figures whose spans differ by more than the `auto_secondary_axis` ratio (default `3.0`), the larger group on the left. The temperature spans about 20 units and the precipitation about 80, a ratio of about 4, which is why the [basic example](#basics) already has two axes. An explicit `"y_axis"` is the robust choice whenever you know which axis a figure belongs to: the data can change, the assignment should not. ``` Panel( [ # assign each figure to its value axis explicitly {"figure": precipitation, "y_axis": "left"}, {"figure": temperature, "y_axis": "right"}, ], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` `auto_secondary_axis` keeps the automatic assignment but makes it more or less eager. A ratio above the 4 of this dataset keeps both figures on the left axis, where the temperature is squeezed against the bottom, which is the reason the panel splits the axes in the first place: ``` Panel( [precipitation, temperature], # only split the axes when the spans differ more than tenfold auto_secondary_axis=10.0, title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm) / Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Drawing order A line read against bars must not disappear behind them. The figures are drawn in the order given, later ones on top, and when no order is given each chart type takes the default of its kind from the [panel configuration](#panel-configuration): bars and histograms sit behind lines and scatter points. The per-figure `"z_order"` option overrides that, higher values on top, whatever the position in the list. The example reverses the default and draws the bars over the line, which is how a bar chart hides a line and why the default is the other way round. ``` Panel( [ # draw the bars on top of the line {"figure": precipitation, "y_axis": "left", "z_order": 2}, {"figure": temperature, "y_axis": "right", "z_order": 1}, ], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Emphasis In most overlays one figure is the message and the others are context, and emphasis says which is which. The per-figure `"emphasis"` option applies one role to every layer of a figure: `"background"` mutes it (the muted color of the active theme, pushed behind the others, dropped from the legend), `"highlight"` bolds it and brings it to the front of the data layers, and `None` leaves it as it is. The roles are also available as the [EMPHASIS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) constants, and the [Highlighting](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md) guide covers emphasis across the package. Here the precipitation is context and the temperature the message: ``` from datachart.constants import EMPHASIS Panel( [ # mute the bars, bold the line {"figure": precipitation, "y_axis": "left", "emphasis": EMPHASIS.BACKGROUND}, {"figure": temperature, "y_axis": "right", "emphasis": EMPHASIS.HIGHLIGHT}, ], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Axis limits Two value axes tempt the eye to compare heights across them, and the honest way to allow that is to start both at zero. `ymin` and `ymax` set the left axis, `ymin_right` and `ymax_right` the right one, and `xmin` and `xmax` the shared axis; limits set on the individual charts are not carried over, the panel owns its axes. With labeled bars the x positions are the indices of the labels, so half-integer limits cut between two months. The example zooms in on April to September and starts both value axes at zero, which also leaves room for the legend: ``` Panel( [ {"figure": precipitation, "y_axis": "left"}, {"figure": temperature, "y_axis": "right"}, ], # zoom in on April to September xmin=2.5, xmax=8.5, # start both value axes at zero ymin=0, ymax=200, ymin_right=0, ymax_right=30, title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Axis scales A quantity that spans orders of magnitude next to one that does not is the case for two axes with two scales. `scalex`, `scaley` and `scaley_right` take a [SCALE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) member; `scaley` applies to the left value axis and `scaley_right` to the right one, so the two scale independently. Unlike the limits, a scale set on an individual chart is carried over: a chart drawn with `scaley=SCALE.LOG` stays log in the panel, on whichever axis it lands, and the panel attributes override it per axis. A chart that set no scale of its own takes the scale of the axis it lands on. When two charts sharing one axis were each built with a different scale, the first one wins and the panel warns; the `overlay_warn_scale_conflict` setting of the [panel configuration](#panel-configuration) switches the warning off. `pollen` holds an illustrative monthly mean pollen concentration (in grains/m³), which spans three orders of magnitude between winter and spring. Against the precipitation bars on a linear left axis, a log right axis is what keeps the winter months readable: ``` from datachart.constants import SCALE # an illustrative monthly mean pollen concentration in grains/m³ POLLEN = [4, 12, 180, 1400, 2100, 650, 210, 90, 40, 15, 6, 3] pollen = LineChart( data=[{"x": i, "y": value} for i, value in enumerate(POLLEN)], subtitle="Pollen (grains/m³)", ) Panel( [ {"figure": precipitation, "y_axis": "left"}, {"figure": pollen, "y_axis": "right"}, ], # a log scale on the right value axis only; the left one stays linear scaley_right=SCALE.LOG, title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Pollen (grains/m³)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Bar mode Several bar charts in one panel have to share each category somehow, and `bar_mode` says how ([BAR_MODE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE)): `"group"` draws the bars side by side (the default), `"stack"` stacks them so each stack is the total, `"overlay"` draws them over each other with transparency. Left unset, the panel takes the `bar_mode` of the first chart that was built with one, and falls back on the configuration when no chart set any. The example splits the precipitation into the rain and the snow of each month (the split is illustrative) and stacks them, so the bars still add up to the monthly total while the temperature line stays on its own axis: ``` from datachart.constants import BAR_MODE # an illustrative split of the monthly precipitation into snow (cold months) and rain SNOW_SHARE = [0.5, 0.4, 0.15, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.1, 0.4] snow = BarChart( data=[ {"label": month, "y": round(total * share)} for month, total, share in zip(MONTHS, PRECIPITATION, SNOW_SHARE) ], subtitle="Snow (mm)", ) rain = BarChart( data=[ {"label": month, "y": round(total * (1 - share))} for month, total, share in zip(MONTHS, PRECIPITATION, SNOW_SHARE) ], subtitle="Rain (mm)", ) Panel( [ {"figure": rain, "y_axis": "left"}, {"figure": snow, "y_axis": "left"}, {"figure": temperature, "y_axis": "right"}, ], # stack the bars of the two bar charts bar_mode=BAR_MODE.STACK, # headroom above the tallest stack ymin=0, ymax=175, title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Nesting panels A panel built in one place often needs one more figure in another, and rebuilding it from its parts is a chore. Panel figures nest: `Panel([Panel([f1, f2]), f3])` is equivalent to `Panel([f1, f2, f3])`, to any depth. A nested panel contributes its figures with their per-figure options intact, while the panel-level settings (title, labels, limits) always come from the outermost call; the axis scales and the `bar_mode` travel with the figures, as above. The stacked precipitation panel from the previous section, extended with the temperature: ``` # an existing panel... precipitation_panel = Panel( [ {"figure": rain, "y_axis": "left"}, {"figure": snow, "y_axis": "left"}, ], bar_mode=BAR_MODE.STACK, ) # ...later extended with an additional figure Panel( [precipitation_panel, {"figure": temperature, "y_axis": "right"}], bar_mode=BAR_MODE.STACK, ymin=0, ymax=175, title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Horizontal panels Long category names read best on horizontal bars, and a panel follows the bars. A panel takes its orientation from the figures it holds: it is horizontal when every bar chart (and histogram) in it is horizontal, vertical otherwise, and mixing the two raises a `ValueError`. Line and scatter figures have no orientation of their own and follow the panel: in a horizontal panel their `x` runs along the categories and their `y` along the values, so the same temperature line overlays vertical and horizontal bars. The parameters keep their names but address the axes by role. The *value axis* carries the quantities (x in a horizontal panel) and the *category axis* the labels (y): `ylabel_left`, `ylabel_right`, `ymin`, `ymax`, `ymin_right`, `ymax_right`, `scaley` and `scaley_right` refer to the value axes, `xlabel`, `xmin`, `xmax` and `scalex` to the category axis. The secondary value axis sits at the top, so `"y_axis": "right"` places a figure on the top axis and the legend marks the two with `(B)` and `(T)`. Only `show_grid` keeps its literal meaning; it names the gridlines you see. ``` from datachart.constants import ORIENTATION # horizontal bars make the panel horizontal precipitation_h = BarChart( data=precipitation_data, subtitle="Precipitation (mm)", orientation=ORIENTATION.HORIZONTAL, ) Panel( [ {"figure": precipitation_h, "y_axis": "left"}, # "right" is the top value axis in a horizontal panel {"figure": temperature, "y_axis": "right"}, ], title="Climate of Ljubljana", # the category axis (y) and the two value axes (bottom and top) xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", # start both value axes at zero ymin=0, ymin_right=0, figsize=FIG_SIZE.FULL_MEDIUM, # gridlines keep their literal spelling: vertical lines along the values show_grid=SHOW_GRID.X, show_legend=True, ).show() ``` ## Panel Configuration The defaults the panel falls back on (the automatic axis threshold, the transparency of overlaid bars and histograms, the default drawing order of each chart type, the bar mode) are part of the global configuration, under the keys that start with `overlay_`. They are changed like any other setting, through [datachart.config.config.update_config](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.update_config); see the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide for the configuration system as a whole. The current keys and their values in the active theme are: ``` from datachart.config import config {key: value for key, value in config.config.items() if key.startswith("overlay_")} ``` A setting given to `Panel` directly, such as `auto_secondary_axis` or `bar_mode`, always wins over the configuration. The configuration is the place for a default that should hold for every panel of a document: ``` config.update_config( { # split the value axes sooner "overlay_auto_threshold": 2.0, # draw overlaid bars more transparent "overlay_bar_alpha": 0.5, } ) Panel( [precipitation, temperature], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() # restore the defaults for the rest of the guide config.reset_config() ``` ## Real-World Examples The examples below put the features above to work on real or realistic data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. The chart functions they overlay are imported as needed; any chart from the [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) guides can take part in a panel. ### Example 1: Does the Model Fit? (Measurements and a Fitted Curve on One Axis) `observed` holds the illustrative reaction velocity of an enzyme at 15 substrate concentrations, drawn from the Michaelis–Menten equation with seeded measurement noise, and `model` the noise-free curve at 100 concentrations. The question every fit raises is how well the model explains the data, and the answer is visible only when the two are overlaid: the measurements as a scatter chart, the model as a line. They share the same units, so they share one value axis and the default `"auto"` assignment leaves it at that; the fitted parameters go in a note, which travels with the line. ``` from datachart.charts import ScatterChart Panel( [ ScatterChart(data=observed, subtitle="Observed"), LineChart( data=model, subtitle="Michaelis–Menten fit", # the fitted parameters, pointing at the half-saturation point texts={ "text": f"Vmax = {V_MAX} µmol/min\nKm = {K_M} µM", "x": 0.55, "y": 0.35, "coords": "axes", "target": (K_M, V_MAX / 2), }, ), ], title="Enzyme kinetics", xlabel="Substrate concentration (µM)", ylabel_left="Reaction velocity (µmol/min)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.BOTH, show_legend=True, ).show() ``` ### Example 2: Is the Sample Normal? (A Histogram and a Fitted Density) `diameters` holds the illustrative diameter (in µm) of 250 cells measured under a microscope, drawn from a seeded normal generator, and `normal_fit` the normal density with the same mean and standard deviation, scaled to the bin width and the sample size so it is comparable with the histogram counts. Whether a sample is normally distributed is a question the histogram alone cannot settle; the fitted curve over it can. The curve is drawn on top of the histogram by default (histograms take the background drawing order) and reads against it on the same value axis. ``` from datachart.charts import Histogram Panel( [ Histogram(data=diameters, num_bins=N_BINS, subtitle="Measured diameters"), LineChart(data=normal_fit, subtitle="Normal fit"), ], title="Cell size distribution", xlabel="Cell diameter (µm)", ylabel_left="Number of cells", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` ### Example 3: Where Do the Returns Come From? (A Pareto Chart on Two Axes) `returns` holds the illustrative reasons customers gave for returning orders in one quarter, with the number of returns per reason, and `cumulative` the running share of all returns once the reasons are ordered from most to least frequent. The question is which few reasons account for most of the returns, and the Pareto chart answers it: counts as bars on the left axis and the cumulative share as a line on the right one, which crosses the 80 % mark at the third reason. The bars are pre-sorted so the line's positions match them, the right axis is pinned to 0–100 % so the share reads like a percentage, and a note declared on the line chart names the answer. ``` from datachart.constants import LINE_MARKER counts = BarChart(data=returns, subtitle="Returns") share = LineChart( data=cumulative, subtitle="Cumulative share", style={"plot_line_color": "#c1121f", "plot_line_marker": LINE_MARKER.CIRCLE}, # the answer, pointing at the reason where the share passes 80 % texts={ "text": f"{VITAL_FEW} reasons account for\n{cumulative[VITAL_FEW - 1]['y']:.0f} % of all returns", "x": 0.62, "y": 0.4, "coords": "axes", "target": (VITAL_FEW - 1, cumulative[VITAL_FEW - 1]["y"]), }, ) Panel( [ {"figure": counts, "y_axis": "left"}, {"figure": share, "y_axis": "right"}, ], title="Why orders were returned", xlabel="Reason", ylabel_left="Returns", ylabel_right="Cumulative share (%)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 2}, ymin=0, ymin_right=0, ymax_right=100, ).show() ``` ### Example 4: What Stands Out From the Background? (Emphasis and One Scale for Everything) `baseline` holds 200 minutes of illustrative ground acceleration (in g) of a seismometer at rest, drawn from a seeded generator, and `tremor` and `earthquake` two events, a minor tremor and a larger earthquake, as bell-shaped bursts over the same baseline. The baseline is context: the `"background"` emphasis mutes it and drops it from the legend, so the two events, each in its own chart and color, are what the reader sees. The events dwarf the baseline (a span ratio of about 12), so the default axis assignment would move the baseline to its own axis and blow it up; raising `auto_secondary_axis` above that ratio keeps every figure on one scale, the whole point being that the events stand out against the baseline. ``` Panel( [ # the baseline is context: mute it {"figure": LineChart(data=baseline, subtitle="Background"), "emphasis": EMPHASIS.BACKGROUND}, LineChart(data=tremor, subtitle="Tremor (M 3.2)", style={"plot_line_color": "#f39c12"}), LineChart(data=earthquake, subtitle="Earthquake (M 4.8)", style={"plot_line_color": "#e74c3c"}), ], # one scale for everything, so the events stand out against the baseline auto_secondary_axis=20.0, title="Seismic monitoring", xlabel="Time (minutes)", ylabel_left="Ground acceleration (g)", figsize=FIG_SIZE.FULL_SHORT, show_grid=SHOW_GRID.Y, show_legend=True, ).show() ``` # Grid Some questions need several charts side by side: one place against others, one quantity next to another, a headline with its details. A grid takes figures already drawn by the chart functions of the [datachart.charts](https://eriknovak.github.io/datachart/0.10.2/references/charts/index.md) module (any chart from the [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) guides) and redraws each one into its own cell of one combined figure, so the reader compares them at a glance. Where the [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) overlays figures in one coordinate space, the grid keeps every figure in a coordinate space of its own: reach for a panel to read series against each other, and for a grid to compare them side by side. This guide shows how to build grids with the [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) function, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-grid), which maps common tasks to the parameter or layout option that does the job. ``` from datachart.charts import BarChart, LineChart from datachart.utils import Grid ``` ## Basics The examples in this guide share one dataset: the climate of Ljubljana, set against eight other European cities. For Ljubljana, `temperature_data` holds the mean temperature (in °C) and `precipitation_data` the total precipitation (in mm) of each month, rounded from the published 1991–2020 normals of its weather station (the same values as in the [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) guide). `city_data` holds the mean monthly temperature of nine cities, Ljubljana included, rounded to the nearest degree from the published climate normals of each city. One chart rarely says whether a climate is mild or harsh; a grid of them does. The data lives in a hidden cell. Each temperature data point is a dictionary with the month index as `x` and the value as `y`: ``` city_data["Ljubljana"][:3] ``` A grid arranges figures, so the first step is to draw each chart on its own. The `title` of a chart becomes the heading of its cell (with the `subtitle` as the fallback), so each part is named where it is drawn. The charts are drawn at half the page width, `HALF_SHORT` from [datachart.constants.FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), because a grid sizes itself from its first figure (see [Figure size](#figure-size)). `city_chart` draws the temperature of one city the same way every time, with a tick every quarter so that small cells stay readable: ``` from datachart.constants import FIG_SIZE # a tick every quarter keeps small cells readable QUARTERS = {"xticks": [0, 3, 6, 9], "xticklabels": ["Jan", "Apr", "Jul", "Oct"]} temperature = LineChart( data=temperature_data, title="Temperature (°C)", figsize=FIG_SIZE.HALF_SHORT, **QUARTERS ) precipitation = BarChart( data=precipitation_data, title="Precipitation (mm)", figsize=FIG_SIZE.HALF_SHORT, xtickrotate=90 ) def city_chart(city, **kwargs): return LineChart( data=city_data[city], title=city, figsize=FIG_SIZE.HALF_SHORT, **QUARTERS, **kwargs ) ``` **Basic example.** Only the list of figures is required. A flat list is arranged automatically into rows of up to four cells, so two figures make one row of two, and each cell keeps its own axes and scales: ``` Grid( # add the figures to the grid [temperature, precipitation] ).show() ``` ## Customizing the Grid Every customization is either a keyword argument of `Grid` or the shape of the list it is given: nested rows, or a flat list with layout options. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------- | | add a title over the whole grid | `title` | [Title and axis labels](#title-and-axis-labels) | | label the axes once for every cell | `xlabel`, `ylabel` | [Title and axis labels](#title-and-axis-labels) | | resize the figure | `figsize` | [Figure size](#figure-size) | | let the grid lay the figures out | a flat list, `max_cols` | [Automatic layout](#automatic-layout) | | set the rows myself | nested rows | [Nested rows](#nested-rows) | | leave a cell blank | `None` in a row | [Blank cells](#blank-cells) | | compare the cells on one scale | `sharex`, `sharey` | [Sharing axes](#sharing-axes) | | span a figure across rows or columns | per-figure `"layout_spec"` | [Irregular layouts](#irregular-layouts) | | use a grid or a panel as one cell | nest `Grid` and `Panel` figures | [Nesting grids and panels](#nesting-grids-and-panels) | | put a chart with subplots in one cell | a chart drawn with `subplots=True` | [Subplot figures](#subplot-figures) | | annotate a chart in the grid | the charts' `texts`, `Annotate` | [Annotations](#annotations) | | save the grid to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The full list of parameters and layout options is in the [datachart.utils.Grid](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Grid) reference. The look of each figure (colors, line widths, markers, labels) is set on the chart itself through its attributes and `style`; see the guide of each chart in the [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) section. ### Title and axis labels A grid is one figure, and it needs one name that says what the cells have in common. `title` names the whole grid, while each cell keeps the heading of its own chart. When every cell measures the same quantity, labeling each one repeats the same words: `xlabel` and `ylabel` are drawn once for the whole grid, below the bottom row and to the left of the leftmost column. Here two cities share one quantity, so one pair of labels serves both: ``` Grid( [city_chart("Ljubljana"), city_chart("London")], # add the title of the whole grid title="Mean monthly temperature", # one label per axis for the whole grid xlabel="Month", ylabel="Temperature (°C)", ).show() ``` ### Figure size A grid needs room for every cell, and it guesses that room from its first figure: the default size is the first figure's size times the number of columns and of rows. Two half-width charts side by side fill the page width, which is why the charts above are drawn at `HALF_SHORT`; three of them in a row would be 9 inches wide and shrink on the page. `figsize` takes a `(width, height)` tuple in inches or one of the [FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) presets, and it overrides the guess whatever the sizes of the figures inside: ``` Grid( [city_chart("Helsinki"), city_chart("Berlin"), city_chart("Lisbon")], title="Mean monthly temperature (°C)", # three cells in one full-width row figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Automatic layout Most grids are a list of similar charts, and the only question is how many to put in a row. With a flat list the grid answers it: `max_cols` caps the number of columns (four by default), the number of rows follows from the number of figures, and cells left over in the last row stay empty. Four cities with `max_cols=2` make a 2×2 grid, which the default size fits to the page width: ``` Grid( [city_chart(city) for city in ["Reykjavík", "London", "Ljubljana", "Athens"]], # cap the automatic layout at two columns max_cols=2, title="Mean monthly temperature (°C)", ).show() ``` ### Nested rows A dashboard has a headline and its details, and the headline deserves the width. Nested rows set the layout directly: every inner list is one row of the grid, in the order given. Rows need not be equally long; the cells of a shorter row stretch to fill the width, so a single-figure row becomes a full-width headline. Here Ljubljana's precipitation heads the grid, with the temperature of three cities below it: ``` Grid( [ # the first row: one figure stretched across the full width [precipitation], # the second row: three figures side by side [city_chart("Ljubljana"), city_chart("Berlin"), city_chart("Athens")], ], title="Climate of Ljubljana", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Blank cells A stretched row suits a headline, but a short row in a table of equals should keep the column width of the rows above it, so that its axes line up with theirs. `None` holds the place of a missing cell. Here each row is a group of cities, the northern ones above and the southern ones below; the southern group has one city fewer, and `None` keeps its two cells the size of the ones above: ``` Grid( [ [city_chart("Reykjavík"), city_chart("Helsinki"), city_chart("Moscow")], # None keeps the last cell of the row blank [city_chart("Lisbon"), city_chart("Madrid"), None], ], title="Mean monthly temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Sharing axes Each cell scales its axes to its own data. That serves unrelated quantities, but misleads when the cells hold the same one: drawn with free y-axes, the curves of Reykjavík, Ljubljana and Athens all fill their cells and look alike, although Reykjavík's warmest month is barely warmer than Athens' coldest. ``` extremes = [city_chart("Reykjavík"), city_chart("Ljubljana"), city_chart("Athens")] Grid( extremes, title="Mean monthly temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` `sharex` and `sharey` put every cell on one x or y scale. Sharing is honest when the cells hold the same quantity in the same units, as here; sharing a scale between a temperature and a precipitation would squeeze one of them flat and invite a comparison that means nothing. In an automatic (flat-list) grid, shared axes are also labeled only once per row or column, which declutters the cells. With a shared y-axis the three climates separate at a glance: ``` Grid( extremes, # read every cell against the same y-axis sharey=True, title="Mean monthly temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Irregular layouts Some layouts are not rows at all: a tall main chart with smaller ones stacked beside it. For those, wrap the figures of a flat list in dictionaries and add the per-figure `"layout_spec"` option, a dictionary with the `"row"` and `"col"` of the top-left cell and the `"rowspan"` and `"colspan"` the figure covers. Nested rows and `"layout_spec"` cannot be mixed in one call. Here Ljubljana's temperature takes the left column top to bottom, with the two extremes stacked to its right: ``` Grid( [ # Ljubljana spans both rows of the left column {"figure": temperature, "layout_spec": {"row": 0, "col": 0, "rowspan": 2, "colspan": 1}}, {"figure": city_chart("Reykjavík"), "layout_spec": {"row": 0, "col": 1, "rowspan": 1, "colspan": 1}}, {"figure": city_chart("Athens"), "layout_spec": {"row": 1, "col": 1, "rowspan": 1, "colspan": 1}}, ], title="Ljubljana between the extremes", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Nesting grids and panels A dashboard is often built from parts that are compositions themselves. Grid figures nest: a grid placed in a cell occupies exactly that cell and rebuilds its layout inside it, to any depth. The nested grid keeps its own title (a heading over its cells) and its own `sharex` and `sharey` among its own cells, while the outer grid's settings apply only to its top-level cells. [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) figures nest the same way, so an overlay can take one cell; the reverse, a grid inside a panel, raises a `ValueError`. Here the climograph of the Panel guide sits beside a nested grid that shares its y-axis: ``` from datachart.constants import LEGEND_LOCATION from datachart.utils import Panel # a panel as one cell: precipitation bars and the temperature line climograph = Panel( [ {"figure": precipitation, "y_axis": "left", "legend_label": "Precipitation (mm)"}, {"figure": temperature, "y_axis": "right", "legend_label": "Temperature (°C)"}, ], title="Ljubljana (mm, °C)", show_legend=True, # the legend between the title and the axes, clear of the bars legend={"location": LEGEND_LOCATION.OUTSIDE_TOP}, ) # a grid as another cell, with its own title and shared y-axis extremes_grid = Grid( [[city_chart("Reykjavík")], [city_chart("Athens")]], title="The extremes (°C)", sharey=True, ) Grid( [[climograph, extremes_grid]], title="Climate of Ljubljana", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Subplot figures A chart drawn with `subplots=True` is already a small grid, and it takes one cell as it is. The cell rebuilds the chart's subplots inside it, each headed by its `subtitle` and scaled on its own. Here the temperatures of Ljubljana and Moscow, drawn as one chart with two subplots, share a grid with Ljubljana's precipitation: ``` pair = LineChart( data=[city_data["Ljubljana"], city_data["Moscow"]], # one subplot per city, headed by its subtitle subtitle=["Ljubljana (°C)", "Moscow (°C)"], subplots=True, **QUARTERS, ) Grid( # the subplot figure takes the whole first row [[pair], [precipitation]], title="Ljubljana and Moscow", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Annotations A note belongs to the chart it explains, and it should not get lost when the chart moves into a grid. Notes travel with their charts in both forms: the `texts` argument of a chart function, and [datachart.utils.Annotate](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Annotate), which adds notes to a figure that is already drawn. The [Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide covers both. `Annotate` does not accept a grid figure, so annotate the charts before composing them: ``` from datachart.utils import Annotate # a note declared with the chart warmest = LineChart( data=temperature_data, title="Temperature (°C)", figsize=FIG_SIZE.HALF_SHORT, texts={"text": "warmest: July", "x": 0.5, "y": 18, "target": (6, 22.0)}, **QUARTERS, ) # a note added to a chart that is already drawn wettest = Annotate( precipitation, texts={"text": "wettest: Sep–Oct", "x": 0, "y": 135, "target": (8, 147)}, ) Grid([warmest, wettest], title="Climate of Ljubljana").show() ``` ## Real-World Examples The examples below put the features above to work on the climate data of the [Basics](#basics), each one answering a question. Any data they derive lives in hidden cells. The chart functions they arrange are imported as needed; any chart from the [Charts](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) guides can take a cell of a grid. ### Example 1: Which Cities Share Ljubljana's Climate? (Small Multiples With Shared Axes) `city_data` holds the mean monthly temperature of the nine cities. Small multiples, one small cell per city with identical axes everywhere, let the eye sweep across many groups and compare shapes rather than read single values; `sharex` and `sharey` are what make the cells comparable. Each cell draws its city over Ljubljana, muted by the `"background"` [emphasis](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md), so every cell answers the question on its own. The cities are ordered from the coldest year to the warmest: the maritime cities (Reykjavík, London, Lisbon) draw flat curves, the continental ones (Helsinki, Moscow) wide swings, and Berlin follows Ljubljana almost exactly. ``` from datachart.constants import EMPHASIS Grid( [ LineChart( # the city over Ljubljana, muted as context data=[city_data["Ljubljana"], city_data[city]], emphasis=[EMPHASIS.BACKGROUND, None], title=city, figsize=FIG_SIZE.HALF_SHORT, **QUARTERS, ) for city in CITIES_BY_WARMTH ], max_cols=3, # identical axes make the nine cells comparable sharex=True, sharey=True, title="Mean monthly temperature, against Ljubljana (grey)", xlabel="Month", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_TALL, ).show() ``` ### Example 2: Which Climates Swing the Most? (An Irregular Layout) `swing` holds, for each city, the difference between its warmest and its coldest month (in °C), derived from `city_data`. The ranking is the answer, so a sorted horizontal bar chart of it spans two rows and two columns of a `"layout_spec"` grid, with Ljubljana highlighted and the other cities muted; the two ends of the ranking, Moscow and Reykjavík, are stacked to its right as evidence. `sharey` would also tie the bar chart to the temperature scale, so the two line charts fix the same range themselves with `ymin` and `ymax` instead. ``` from datachart.constants import ORIENTATION, SORT ranking = BarChart( data=swing, title="Warmest minus coldest month (°C)", orientation=ORIENTATION.HORIZONTAL, # ascending from the bottom: the largest swing on top sort=SORT.ASCENDING, show_values=True, ) # the same range on both line charts, instead of sharey same_range = {"ymin": -10, "ymax": 25} Grid( [ # the ranking spans two rows and two columns {"figure": ranking, "layout_spec": {"row": 0, "col": 0, "rowspan": 2, "colspan": 2}}, {"figure": city_chart("Moscow", **same_range), "layout_spec": {"row": 0, "col": 2, "rowspan": 1, "colspan": 1}}, {"figure": city_chart("Reykjavík", **same_range), "layout_spec": {"row": 1, "col": 2, "rowspan": 1, "colspan": 1}}, ], title="How much does the temperature swing over a year?", figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 3: What Is a Year in Ljubljana Like? (A Dashboard of a Panel, Annotations, and a Nested Grid) `temperature_data` and `precipitation_data` hold Ljubljana's monthly normals, and `city_data` the temperatures of two cities from Example 1, Berlin and Madrid. The dashboard answers the question in two rows. The headline is the climograph, a panel of the precipitation bars and the temperature line, with notes on the warmest and the wettest months; the notes are declared on the charts, so they travel through the panel into the grid. Below it, a nested grid with its own title and shared y-axis sets Ljubljana against a slightly colder city and a warmer one, so the reader sees where its year sits among its neighbours. ``` year_temperature = LineChart( data=temperature_data, texts={"text": "warmest: July, 22 °C", "x": 0.5, "y": 25, "target": (6, 22.0)}, ) year_precipitation = BarChart( data=precipitation_data, texts={"text": "wettest: Sep–Oct", "x": 9.6, "y": 185, "target": (8.5, 147)}, ) climograph = Panel( [ {"figure": year_precipitation, "y_axis": "left", "legend_label": "Precipitation (mm)"}, {"figure": year_temperature, "y_axis": "right", "legend_label": "Temperature (°C)"}, ], # both value axes start at zero, with room for the notes ymin=0, ymax=200, ymin_right=0, ymax_right=30, show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 2}, ) neighbours = Grid( [ LineChart( data=[city_data["Ljubljana"], city_data[city]], emphasis=[EMPHASIS.BACKGROUND, None], title=city, **QUARTERS, ) for city in ["Berlin", "Madrid"] ], # the nested grid shares its own y-axis sharey=True, title="Against its neighbours, Ljubljana in grey (°C)", ) Grid( [[climograph], [neighbours]], title="A year in Ljubljana", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Text Annotations An annotation turns a chart into an argument: it names what the reader should see, such as a record, a turning point, or its cause. Every chart function takes a `texts` parameter that writes notes onto the chart, with an optional connector to the data point a note is about, and the [datachart.utils.Annotate](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Annotate) function adds notes to a figure that is already drawn. This guide shows both, starting with the basics and building up to worked examples on real data. Looking for a specific customization? Jump straight to the [quick reference](#customizing-the-annotations), which maps common tasks to the attribute or style key that does the job. ``` from datachart.charts import BarChart, LineChart from datachart.utils import Annotate, Grid, Panel ``` ## Basics The examples in this guide share one dataset: the monthly climate normals of Ljubljana's weather station, rounded from the published 1991–2020 values. `TEMPERATURE` holds the mean temperature of each month (in °C) and `PRECIPITATION` its total precipitation (in mm), and `STATION_TEMPERATURES` adds the mean monthly temperature of two contrasting stations, coastal Portorož and Kredarica, high in the Julian Alps. The data lives in a hidden cell. The numbers have stories a chart alone does not tell: the warmest month, the wettest season, and a mountain that stays below freezing for half the year. The temperature is a line chart with one data point per month, the month index as `x`: ``` temperature_data[:3] ``` An annotation is a dictionary, and `texts` takes one of them or a list. Three keys are required: `text` is what the note says (a `\n` breaks the line), and `x` and `y` are where it sits, in the chart's data coordinates unless the note says otherwise. The optional `target` is the data point the note is about, as an `(x, y)` tuple: giving one draws a connector from the note to that point. Two more optional keys, `coords` and `style`, are covered in the sections below; the full definition is the [datachart.typings.TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs) type. **Basic example.** A note that names the warmest month and points at it. The note sits at month 1 and 26 °C, in the empty space above the curve (the y-axis is extended to 30 °C to make room), and the connector runs to July (month 6, 22.0 °C): ``` from datachart.constants import FIG_SIZE LineChart( data=temperature_data, title="Climate of Ljubljana", xlabel="Month", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=list(range(12)), xticklabels=MONTHS, ymax=30, # one note, pointing at July texts={ "text": "July is the warmest month", "x": 1, "y": 26, "target": (6, 22.0), }, ).show() ``` The annotation is part of the chart declaration, not something drawn onto the finished image: it follows the active theme and is redrawn with the chart when the figure is composed with [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) or [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md). ## Customizing the Annotations Every customization is either a key of the annotation dictionary, a `plot_text_*` key of its `style` dictionary, or the way the annotation reaches the figure. The table maps common tasks to the one you need and links to the subsection that shows it. | I want to… | Use | See | | ------------------------------------------------ | ------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | write a note on a chart | `texts` with `text`, `x`, `y` | [Basics](#basics) | | place a note next to the data it describes | `x`, `y` in data coordinates (the default) | [Placement and coordinates](#placement-and-coordinates) | | pin a note to a corner, whatever the axis limits | `"coords": "axes"` | [Placement and coordinates](#placement-and-coordinates) | | point a note at a data point | `target` | [Targets and connectors](#targets-and-connectors) | | change the connector look | `"style": {"plot_text_arrow_style": ...}` | [Connector looks](#connector-looks) | | bend, recolor, or thicken the connector | `plot_text_arrow_curve`, `plot_text_arrow_color`, `plot_text_arrow_width` | [Connector looks](#connector-looks) | | restyle the text or its box | the `plot_text_*` keys in `style` | [Text and box style](#text-and-box-style) | | write a quiet, label-like note | `"style": {"plot_text_box_visible": False}` | [Text and box style](#text-and-box-style) | | annotate each subplot of a chart | `texts` as a list of lists | [Annotating subplots](#annotating-subplots) | | annotate a figure that is already drawn | `Annotate(figure, texts)` | [Annotating Finished Figures](#annotating-finished-figures) | | annotate a panel | `Annotate` on the `Panel` figure | [Annotating a panel](#annotating-a-panel) | | annotate one subplot of a finished figure | `Annotate` with the `subplot` index | [Annotating finished subplots](#annotating-finished-subplots) | | annotate a figure that goes into a grid | `Annotate` before `Grid` | [Annotations in a grid](#annotations-in-a-grid) | | change the defaults of every annotation | `config.update_config` with the `plot_text_*` keys | [Text Configuration](#text-configuration) | | save the chart to a file | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) guide | The parameter that accepts a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values: | Parameter | Constant | | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | | `style={"plot_text_arrow_style": ...}` | [`ARROW_STYLE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ARROW_STYLE) | The annotation keys are listed in the [datachart.typings.TextSettingAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextSettingAttrs) type and the style keys in [datachart.typings.TextStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs); the `Annotate` parameters are in the [datachart.utils.Annotate](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Annotate) reference. ### Placement and coordinates A note either belongs to a place in the data or to the chart as a whole, and the coordinates say which. By default `x` and `y` are data coordinates: the note sits among the data it describes and moves with it when the axis limits change. That is the right choice for a note about a month, a peak, or an event. With `"coords": "axes"` the position is a fraction of the axes instead, `(0, 0)` the bottom-left corner and `(1, 1)` the top-right, so the note stays in its place whatever the limits. That is the right choice for a note about the whole chart (the data source, the period, a caveat), and for a note that must not land on the data when the data changes. The example places one note of each kind; the y-axis is extended to 30 °C to leave room above the curve, and the axes note stays in the top-left corner regardless. ``` LineChart( data=temperature_data, title="Climate of Ljubljana", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=list(range(12)), xticklabels=MONTHS, ymax=30, texts=[ # about the whole chart: pinned to the top-left corner of the axes {"text": "normals 1991–2020", "x": 0.02, "y": 0.92, "coords": "axes"}, # about the summer: placed in data coordinates, above July and August {"text": "summer plateau above 20 °C", "x": 4.6, "y": 26}, ], ).show() ``` ### Targets and connectors A note next to a curve is only clear when it is obvious which point it means; a connector removes the doubt. `target` is the `(x, y)` data point the note is about, and it is **always** in data coordinates, even when the note itself is placed on the axes, so a note pinned to a corner still points at its data point. One annotation has one target; two points need two annotations. Place the note in open space some distance from its target, so the connector has room to be drawn. A connector with little room straightens out instead of curving, and one with no room left — a target the note's own box already covers — is left out. The two coldest months of the year are January and December; each note below points at one of them, and both notes are placed as axes fractions in open space. ``` LineChart( data=temperature_data, title="Climate of Ljubljana", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=list(range(12)), xticklabels=MONTHS, ymin=-2, ymax=26, texts=[ # placed on the axes, pointing at a data point { "text": "coldest month: 0.8 °C", "x": 0.1, "y": 0.85, "coords": "axes", "target": (0, TEMPERATURE[0]), }, # one target per annotation { "text": "December: 1.3 °C", "x": 0.8, "y": 0.7, "coords": "axes", "target": (11, TEMPERATURE[11]), }, ], ).show() ``` ### Connector looks A connector can be a quiet hint or a pointer that demands attention, and the look says which. The look is set by the `plot_text_arrow_style` key of the annotation's `style`, one of the [ARROW_STYLE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ARROW_STYLE) constants. Each is a complete look (the line shape, its curvature, and the gap on the text side): `CURVE`, the default, is a plain curved line; `CURVE_ARROW` adds an arrowhead; `STRAIGHT` is a plain straight line; `TOUCHING` is a straight line that starts flush at the box border; and `ARROW` is a straight line with an arrowhead. A raw matplotlib arrow style string (such as `"-|>"`) is accepted too. The chart below shows the five looks side by side: ``` from datachart.constants import ARROW_STYLE # (look, note position as axes fractions, target month) looks = [ (ARROW_STYLE.CURVE, 0.03, 0.62, 1), (ARROW_STYLE.CURVE_ARROW, 0.28, 0.9, 4), (ARROW_STYLE.TOUCHING, 0.7, 0.88, 7), (ARROW_STYLE.ARROW, 0.6, 0.28, 9), (ARROW_STYLE.STRAIGHT, 0.86, 0.45, 11), ] LineChart( data=temperature_data, title="The connector looks", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_MEDIUM, xticks=list(range(12)), xticklabels=MONTHS, texts=[ { "text": look, "x": x, "y": y, "coords": "axes", "target": (month, TEMPERATURE[month]), # the connector look of this note "style": {"plot_text_arrow_style": look}, } for look, x, y, month in looks ], ).show() ``` A curved connector places itself: it leaves the box from the side facing the target and bows toward the side with the most open space, away from the chart's data. When that choice is not the one you want, the `plot_text_arrow_*` keys override single properties of the look. `plot_text_arrow_curve` pins the bow: a signed number, where positive and negative values bow to opposite sides, larger values bow deeper, and `0` draws a straight line. `plot_text_arrow_color` and `plot_text_arrow_width` restyle the stroke. The example keeps the arrowhead look and pins a deep bow, in the color of the note's message: ``` LineChart( data=temperature_data, title="Climate of Ljubljana", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=list(range(12)), xticklabels=MONTHS, ymax=30, texts={ "text": "the temperature climbs 15 °C\nfrom March to July", "x": 0.06, "y": 0.75, "coords": "axes", "target": (6, TEMPERATURE[6]), "style": { "plot_text_arrow_style": ARROW_STYLE.CURVE_ARROW, # a pinned bow, a red and thicker stroke "plot_text_arrow_curve": -0.4, "plot_text_arrow_color": "#c1121f", "plot_text_arrow_width": 1.8, }, }, ).show() ``` ### Text and box style A chart can carry notes of different weight: a main message that should stand out, and a quiet label that should not compete with the data. The `plot_text_*` keys of the annotation's `style` set the text (color, size, weight, alignment, alpha) and its background box (visibility, face and edge color, edge width, alpha); they are the same keys every theme sets, so a per-note override changes exactly one annotation. Hiding the box with `plot_text_box_visible` turns a note into a label that sits directly on the chart, the right look for naming a line or a region. ``` LineChart( data=temperature_data, title="Climate of Ljubljana", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=list(range(12)), xticklabels=MONTHS, ymax=30, texts=[ # a quiet, boxless label { "text": "mean monthly temperature", "x": 0.02, "y": 0.92, "coords": "axes", "style": {"plot_text_box_visible": False, "plot_text_color": "#7F8C8D"}, }, # the main message: bold text, a colored box and connector { "text": "summer plateau", "x": 0.62, "y": 0.9, "coords": "axes", "target": (7, TEMPERATURE[7]), "style": { "plot_text_weight": "bold", "plot_text_size": 11, "plot_text_box_facecolor": "#FFF6E0", "plot_text_box_edgecolor": "#F28E2B", "plot_text_arrow_color": "#F28E2B", }, }, ], ).show() ``` ### Annotating subplots A chart drawn with `subplots=True` has one coordinate space per subplot, and a note usually belongs to one of them. As with `vlines`, `hlines`, `vspans` and `hspans`, `texts` then takes a list of lists: one list of annotations per chart, in the order of the data, with an empty list (or `None`) for a subplot without notes. A single dictionary is drawn in every subplot. Each station below gets the note that describes it; Ljubljana, the reference, gets none. The three subplots share one fixed temperature range, so the stations compare at a glance. ``` stations = [ [{"x": i, "y": value} for i, value in enumerate(values)] for values in STATION_TEMPERATURES.values() ] LineChart( data=stations, subtitle=list(STATION_TEMPERATURES), subplots=True, sharey=True, ymin=-10, ymax=25, title="Mean monthly temperature", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=[0, 3, 6, 9], xticklabels=["Jan", "Apr", "Jul", "Oct"], # one list of notes per subplot texts=[ [], [{"text": "never below 5 °C", "x": 0.3, "y": 0.15, "coords": "axes", "target": (0, 5.2)}], [{"text": "below 0 °C\nNov to Apr", "x": 0.05, "y": 0.9, "coords": "axes", "target": (3, -2.0)}], ], ).show() ``` ## Annotating Finished Figures Notes are often added last: the figure comes from someone else's code, or the message is only clear once the chart is drawn. The [datachart.utils.Annotate](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.Annotate) function takes a figure drawn by a chart function and the annotations to add, in the same form as `texts`. It returns a **new** figure and leaves the source figure untouched; the notes become part of the new figure's chart declaration and are styled by the theme active when `Annotate` is called. ``` precipitation = BarChart( data=precipitation_data, title="Precipitation in Ljubljana", ylabel="Precipitation (mm)", figsize=FIG_SIZE.FULL_SHORT, ymax=200, ) # a new figure; `precipitation` stays as it was Annotate( precipitation, texts={ "text": "the driest months are\nin late winter", "x": 0.25, "y": 0.85, "coords": "axes", "target": (0.5, 72), }, ).show() ``` ### Annotating a panel A panel is the typical figure that is finished before its message is: the overlay of precipitation and temperature (a *climograph*) shows a pattern that no single chart shows. `Annotate` accepts [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) figures; the note below is placed on the axes and points at the September precipitation bar. ``` from datachart.constants import LEGEND_LOCATION climograph = Panel( [ BarChart(data=precipitation_data, subtitle="Precipitation (mm)"), LineChart(data=temperature_data, subtitle="Temperature (°C)"), ], title="Climate of Ljubljana", xlabel="Month", ylabel_left="Precipitation (mm)", ylabel_right="Temperature (°C)", # headroom above the bars and the line for the notes ymin=0, ymax=250, ymin_right=0, ymax_right=30, figsize=FIG_SIZE.FULL_SHORT, show_legend=True, legend={"location": LEGEND_LOCATION.OUTSIDE_TOP, "ncols": 2}, ) Annotate( climograph, texts={ "text": "autumn rains peak", "x": 0.72, "y": 0.93, "coords": "axes", "target": (8, 147), }, ).show() ``` ### Annotating finished subplots A finished figure drawn with `subplots=True` has one coordinate space per subplot, so each note names the subplot it lands in with a `subplot` index: 0-based, in the order the subplots are drawn. `Annotate` raises a `ValueError` when a note on such a figure has no `subplot` or one out of range, and when a note names a `subplot` on a single-panel figure. The annotated figure keeps the subplot layout, with each subplot scaled on its own, and composes onward with [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md); in a Panel, where the subplots collapse into one coordinate space, the subplot notes are not drawn. Here the mountain station, the third subplot, gets the note: ``` station_figure = LineChart( data=stations, subtitle=list(STATION_TEMPERATURES), subplots=True, title="Mean monthly temperature", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=[0, 3, 6, 9], xticklabels=["Jan", "Apr", "Jul", "Oct"], ) Annotate( station_figure, texts={ "text": "a summer\nbelow 10 °C", "x": 0.35, "y": 0.12, "coords": "axes", "target": (7, 9.0), # the third subplot "subplot": 2, }, ).show() ``` ### Annotations in a grid A [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) redraws each figure in a cell of its own, and the notes of each figure travel with it. A Grid figure itself cannot be annotated: `Annotate` raises a `ValueError` for it, because a grid has no single coordinate space to place a note in. Annotate the figures first, then compose them, as the [last example](#example-3-how-does-ljubljanas-climate-compare-an-annotated-panel-and-subplots-in-a-grid) of this guide does: ``` try: Annotate(Grid([climograph]), texts={"text": "too late", "x": 0.5, "y": 0.5}) except ValueError as error: print(error) ``` ## Text Configuration The defaults every annotation falls back on (the font, the box face and edge, the connector look and color) are part of the global configuration, under the keys that start with `plot_text_`. Every predefined theme sets them to match its own look, and they are changed like any other setting, through [datachart.config.config.update_config](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.update_config); see the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide for the configuration system as a whole. The current keys and their values in the active theme are: ``` from datachart.config import config {key: value for key, value in config.config.items() if key.startswith("plot_text_")} ``` A `plot_text_*` key in an annotation's `style` always wins over the configuration. The configuration is the place for a default that should hold for every annotation of a document, such as arrowheads on every connector and notes without a box: ``` config.update_config( { # arrowheads and boxless notes for every annotation "plot_text_arrow_style": ARROW_STYLE.ARROW, "plot_text_box_visible": False, } ) LineChart( data=temperature_data, title="Climate of Ljubljana", ylabel="Temperature (°C)", figsize=FIG_SIZE.FULL_SHORT, xticks=list(range(12)), xticklabels=MONTHS, ymax=30, texts={"text": "July is the warmest month", "x": 1, "y": 26, "target": (6, 22.0)}, ).show() # restore the defaults for the rest of the guide config.reset_config() ``` ## Real-World Examples The examples below put annotations to work on real or illustrative data, each one answering a question. The data lives in hidden cells; each example says what its data is and where it comes from. ### Example 1: What Made 2024 the Warmest Year? (A Highlighted Record and Quiet Context Notes) `warming` holds the global mean surface temperature anomaly from 1970 to 2024, the difference of each year's global average from the 1951–1980 mean in °C (source: NASA GISS Surface Temperature Analysis, GISTEMP v4, rounded to two decimals). A line of fifty years says *warming*; the notes say why single years stand out. The record gets the loud treatment, a bold note with an arrowhead. The context gets quiet, boxless notes: the dip after the eruption of Mount Pinatubo in 1991, the spike of the strong 1997–98 El Niño, and the previous record of 2016, marked with a dashed reference line so the reader can see by how much 2024 beat it. ``` from datachart.constants import LINE_STYLE, SHOW_GRID quiet = {"plot_text_box_visible": False, "plot_text_color": "#6c757d", "plot_text_size": 8.5} LineChart( data=warming, title="Global surface temperature, 1970–2024", xlabel="Year", ylabel="Anomaly vs 1951–1980 (°C)", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, ymin=-0.3, ymax=1.5, # the previous record, for comparison hlines={"y": BY_YEAR[2016], "style": {"plot_hline_style": LINE_STYLE.DASHED}}, texts=[ # the record: bold, boxed, with an arrowhead { "text": f"2024: {BY_YEAR[2024]:+.2f} °C, the warmest year\nof the record, after the 2023–24 El Niño", "x": 0.05, "y": 0.9, "coords": "axes", "target": (2024, BY_YEAR[2024]), "style": {"plot_text_weight": "bold", "plot_text_arrow_style": ARROW_STYLE.CURVE_ARROW}, }, # the context: quiet, boxless notes { "text": "previous record, 2016", "x": 2003, "y": BY_YEAR[2016] + 0.06, "style": quiet, }, { "text": "Pinatubo eruption\ncools 1992", "x": 0.3, "y": 0.12, "coords": "axes", "target": (1992, BY_YEAR[1992]), "style": quiet, }, { "text": "1997–98 El Niño", "x": 0.44, "y": 0.62, "coords": "axes", "target": (1998, BY_YEAR[1998]), "style": quiet, }, ], ).show() ``` ### Example 2: What Moved the Traffic? (Event Notes on a Date Axis) `visits` holds the illustrative daily visits of a product website from March to May 2024, generated with a seeded weekly cycle and noise, and `EVENTS` the three days that moved it: a product launch, an outage, and a mention in a large newsletter. A traffic chart without notes shows jumps; with notes it shows their causes. Each event gets its own note with an arrowhead, placed above or below the line where there is room. The chart's x-axis holds dates, and a note position on a date axis is a number: [matplotlib.dates.date2num](https://matplotlib.org/stable/api/dates_api.html#matplotlib.dates.date2num) converts each date. ``` from matplotlib.dates import date2num def event_note(text, day, x, y): # a note at (x, y) axes fractions, pointing at the visits of `day` return { "text": text, "x": x, "y": y, "coords": "axes", "target": (date2num(day), VISITS[day]), "style": {"plot_text_arrow_style": ARROW_STYLE.ARROW}, } LineChart( data=visits, title="Daily website visits, spring 2024", ylabel="Visits", figsize=FIG_SIZE.FULL_MEDIUM, show_grid=SHOW_GRID.Y, xticks_format="%b %d", ymin=0, ymax=6000, texts=[ event_note("product launch:\na new, higher level", EVENTS["launch"], 0.08, 0.75), event_note("four-hour outage", EVENTS["outage"], 0.2, 0.12), event_note("newsletter mention:\na two-day spike", EVENTS["newsletter"], 0.5, 0.9), ], ).show() ``` ### Example 3: How Does Ljubljana's Climate Compare? (An Annotated Panel and Subplots in a Grid) The last example uses the Basics dataset to build one figure for a report: the Ljubljana climograph on top, with its notes added by `Annotate`, and the three stations below, with their notes declared per subplot on the chart's own `texts`. Both figures carry their notes into the [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md), which stacks them in two rows; the station subtitles carry the unit, since each station is drawn in a cell of its own. The notes are added before the grid is built, since a Grid figure cannot be annotated. ``` top = Annotate( climograph, texts=[ {"text": "driest in late winter", "x": 0.04, "y": 0.9, "coords": "axes", "target": (1, 70)}, {"text": "wettest in autumn", "x": 0.72, "y": 0.93, "coords": "axes", "target": (9, 147)}, ], ) bottom = LineChart( data=stations, # the unit rides the subtitles subtitle=[f"{name}, °C" for name in STATION_TEMPERATURES], subplots=True, xticks=[0, 3, 6, 9], xticklabels=["Jan", "Apr", "Jul", "Oct"], # one list of notes per subplot texts=[ [], [{"text": "mild winters\nby the sea", "x": 0.25, "y": 0.2, "coords": "axes", "target": (0, 5.2)}], [{"text": "below 0 °C\nhalf the year", "x": 0.35, "y": 0.12, "coords": "axes", "target": (3, -2.0)}], ], ) Grid( [[top], [bottom]], title="Three climates of Slovenia", figsize=FIG_SIZE.FULL_TALL, ).show() ``` # Styling # Styling The styling guides cover everything that controls how charts look: the predefined themes and how to apply, build, and share your own through the [config](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md) singleton, and the available colormaps. Each card names a guide, says what it covers, and links to it. - [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) Applying the predefined themes, what a theme controls, and building and sharing your own, shown on a neon noir theme. - [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) Every predefined theme as a card of color swatches with hex codes and six signature charts, grouped by use, to pick one by eye. - [Colormaps](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/colormaps/index.md) Every `COLORS` palette by kind, with the predefined themes that use it, and which kind fits which theme attribute. # Themes A theme is the complete set of style attributes the charts read when they are built: the palettes, the fonts, the axes furniture, and the per-chart defaults, one value per key of [StyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.StyleAttrs). The package ships eight predefined themes, each named for its visual trait; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows their color swatches and signature charts, grouped by use. Themes are applied and built through the global [config](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md) instance: | Task | Method | Section | | ---------------------------------------- | ------------------------------------------ | --------------------------------------------------- | | Switch the look of every chart | `set_theme`, `list_themes` | [Applying a Theme](#applying-a-theme) | | Switch it for one block only | `using_theme` | [Applying a Theme](#applying-a-theme) | | Read and change single attributes | `config[...]`, `update_config`, `override` | [What a Theme Controls](#what-a-theme-controls) | | Make your own theme switchable by name | `register_theme` | [Building Your Own Theme](#building-your-own-theme) | | Share a theme as a file and load it back | `save_theme`, `load_theme` | [Sharing a Theme](#sharing-a-theme) | | Return to the default theme | `reset_config` | [Applying a Theme](#applying-a-theme) | ``` from datachart.config import config from datachart.constants import FONT_WEIGHT, THEME ``` The examples render one figure throughout, a grouped bar chart beside a line chart, so the theme is the only thing that changes between renders. The chart code is left out of this page; see the [chart guides](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) for it. ## Applying a Theme `list_themes` returns every name `set_theme` accepts: the predefined themes from [THEME](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) plus any theme registered in this process. ``` config.list_themes() ``` Applying a theme replaces the whole configuration. Charts read the configuration when they are built, so set the theme before building the charts it should style; the active theme's name is in `config.theme`. ``` config.set_theme(THEME.MINIMAL) demo().show() ``` `using_theme` applies a theme for one `with` block and restores the configuration that entered the block when it ends, also when the block raises. The scopes are plain save-and-restore on the global configuration, so they are neither thread-safe nor async-safe. ``` with config.using_theme(THEME.INK): demo().show() config.theme ``` `reset_config` returns to the default theme, discarding every change made since: ``` config.reset_config() config.theme ``` Beyond style, a theme carries defaults for chart settings ([ThemeDefaultAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ThemeDefaultAttrs)): `chart_default_show_grid` supplies the grid when a chart call leaves `show_grid` unset (every predefined theme but [`SKETCH`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/#sketch) and [`QUILL`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/#quill) ships a muted `"y"` grid), `chart_default_show_values` does the same for value labels (no predefined theme turns them on), and the `plot_hatch_cycle`, `plot_linestyle_cycle`, and `plot_marker_cycle` attributes tell series apart by pattern where a theme ships them ([`HATCH`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/#hatch) and [`QUILL`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/#quill)). A setting given in the chart call always wins over the theme default. ## What a Theme Controls The attribute names are the keys of the live configuration, grouped by prefix; the [typings reference](https://eriknovak.github.io/datachart/0.10.2/references/typings/index.md) documents each one. | Prefix | Controls | Reference | | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `color_*`, `muted_*` | The palettes: `multiple` for series sharing one axes and for subplots, `singular` where one color is needed; the muted color of de-emphasized series | [ColorStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ColorStyleAttrs) | | `font_*` | The font family and its stacks, and the size, color, style, and weight of each text role: general, title, subtitle, axis labels | [FontStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.FontStyleAttrs) | | `axes_*`, `figure_*` | The spines, the ticks, and the face colors of the figure and the axes | [AxesStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AxesStyleAttrs) | | `plot_grid_*`, `plot_legend_*`, `plot_text_*`, `plot_value_*` | The furniture every chart shares: grid lines, legend, annotations, value labels | [GridStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.GridStyleAttrs), [LegendStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.LegendStyleAttrs), [TextStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs), [ValueLabelStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs) | | `plot__*` | One group per chart type: `plot_line_*`, `plot_bar_*`, `plot_heatmap_*`, and so on | The chart's guide, under "Customize" | | `overlay_*` | How a [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) combines charts: the twin-axis threshold, drawing order, bar mode | [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/#panel-configuration) | | `chart_default_*`, `plot_*_cycle` | The chart-setting defaults above | [ThemeDefaultAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ThemeDefaultAttrs) | Two themes add groups of their own: [`SKETCH`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/#sketch) the path wobble and halo of its hand-drawn look ([SketchStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.SketchStyleAttrs)), [`QUILL`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/#quill) the pen strokes and etched fills of its ink look ([InkStyleAttrs](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.InkStyleAttrs)). Read an attribute by indexing `config` or with `config.get`; the live dictionary is `config.config`, so a prefix lists a whole group: ``` config["color_general_multiple"], config.get("font_general_family") ``` ``` {key: value for key, value in config.config.items() if key.startswith("axes_")} ``` `update_config` changes attributes on top of the active theme; the change persists until the next `set_theme` or `reset_config`, and unknown attribute names are skipped with a warning. `override` does the same for one `with` block, taking a dictionary or keyword arguments. Palette attributes also accept a single color, used for every series that asks for one. ``` config.update_config({"font_general_family": "serif", "plot_line_width": 3}) demo().show() ``` ``` with config.override(color_general_multiple=["#0B3954", "#FF6663", "#E0FF4F"]): demo().show() config.reset_config() ``` ## Building Your Own Theme A custom theme is a dictionary of the attributes that differ from the default theme. `register_theme` fills the rest from the default theme and rejects unknown names, so a theme can be as short as one palette. To build on another predefined theme instead, spread it first: `{**MINIMAL_THEME, ...}` with the dictionaries of the [themes](https://eriknovak.github.io/datachart/0.10.2/references/themes/index.md) module. The theme built here is a neon noir look: a near-black ground, a cyan, magenta, and amber palette, monospaced type, and the furniture dimmed so the series carry the light. It starts with the palette: ``` NEON_COLORS = ["#00E5FF", "#FF2D95", "#FFB000", "#7DFF5A", "#B26BFF"] swatches(NEON_COLORS) ``` The rest of the dictionary sets the ground, the type, and the furniture. `font_general_family` takes `serif` or `sans-serif` to use the theme's font stacks, or any family matplotlib resolves, here the generic `monospace`; a `None` in a color attribute keeps matplotlib's own color, so every color a dark ground needs is set explicitly: ``` NEON = { # ground "figure_facecolor": "#0B0F19", "axes_facecolor": "#0B0F19", # palettes: the series colors, and a two-stop ramp for value scales "color_general_multiple": NEON_COLORS, "color_general_singular": ["#1B2A4A", "#00E5FF"], "muted_color": "#3A4656", # type "font_general_family": "monospace", "font_general_color": "#E6EDF3", "font_title_color": "#00E5FF", "font_title_weight": FONT_WEIGHT.BOLD, "font_subtitle_color": "#9AA5B1", "font_xlabel_color": "#9AA5B1", "font_ylabel_color": "#9AA5B1", # furniture: open top and right, dim spines, dotted grid, dark legend "axes_spines_top_visible": False, "axes_spines_right_visible": False, "axes_spines_color": "#2A3548", "axes_ticks_color": "#9AA5B1", "plot_grid_color": "#222D40", "plot_grid_alpha": 1.0, "plot_grid_linestyle": ":", "plot_legend_face_color": "#111827", "plot_legend_edge_color": "#2A3548", "plot_legend_label_color": "#E6EDF3", "plot_value_color": "#E6EDF3", # marks: heavier strokes, no bar outlines "plot_line_width": 2.2, "plot_bar_edge_width": 0, } ``` Try it before registering: `override` renders the figure under the dictionary and leaves the configuration untouched. ``` with config.override(NEON): demo().show() config.theme ``` Register the dictionary under a name and it behaves like a predefined theme: it appears in `list_themes`, `set_theme` and `using_theme` apply it, and `update_config` tweaks on top of it, here lifting the axes off the ground with a lighter face: ``` config.register_theme("neon", NEON) config.set_theme("neon") config.list_themes() ``` ``` config.update_config({"axes_facecolor": "#131A2A"}) demo().show() ``` Adding the theme to the `datachart` package If you think the theme would be useful to others, open a pull request that adds it to the `datachart.themes` module. ## Sharing a Theme `save_theme` writes a theme file: a JSON document carrying a name, a format version, and only the attributes that differ from the default theme, so the file stays short and reviewable. A registered theme is saved by name; with no name the live configuration is saved, so a look assembled with `update_config` leaves the process too. ``` import tempfile from pathlib import Path folder = Path(tempfile.mkdtemp()) config.save_theme(folder / "neon.json", name="neon") print((folder / "neon.json").read_text()) ``` `load_theme` registers the theme in a file and returns the name it registered under: the `name` argument, else the name in the file, else the file's stem. Loading only registers; apply the theme with `set_theme` or `using_theme`. The file is validated the way `register_theme` validates a dictionary, so a hand-edited file cannot register a broken theme. ``` name = config.load_theme(folder / "neon.json", name="neon-shared") config.set_theme(name) config.theme ``` A companion package ships its themes the same way: it registers or loads them on import, and its users apply them with `config.set_theme("")`. Finally, reset the configuration back to the default theme: ``` config.reset_config() ``` # Theme Gallery Each predefined theme has a card here: a strip of its color swatches with hex codes, the sequential colormap, and the font it sets in, its colour-blindness scores, and the same six signature charts rendered under that theme — grouped bars, lines, a fitted scatter, a box plot, a heatmap, and a twin-axis [`Panel`](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md). The six charts cover every trait a theme can differ in: palette, edges and fills, line strokes and markers, bodies, value scales, and the furniture around them (spines, grid, ticks, legend). The themes are grouped by where they work best: | Theme | Character | | ------------------------------------------------------- | -------------------------------------------------------------------------- | | [Screen and presentations](#screen-and-presentations) | | | [`THEME.DEFAULT`](#default) | Softened Okabe–Ito palette, colour-blind safe, open spines, soft grid. | | [`THEME.MATERIAL`](#material) | Google palette, bottom spine only, light grid. | | [`THEME.MINIMAL`](#minimal) | Accent violet with deep grays, no spines, flat bars. | | [`THEME.HARBOR`](#harbor) | Navy and amber in lightness steps, colour-blind safe. | | [Print and black-and-white](#print-and-black-and-white) | | | [`THEME.GREYSCALE`](#greyscale) | Monochrome, print-friendly. | | [`THEME.INK`](#ink) | Diversified YlGnBu palette with navy ink accents. | | [`THEME.HATCH`](#hatch) | Hatch cycle, black edges, dotted grid. | | [`THEME.MUTED`](#muted) | Tol's muted colours, dash and marker cycles, colour-blind safe. | | [`THEME.CONTRAST`](#contrast) | Lightness-stepped colours plus hatches, print-safe. | | [Illustrative](#illustrative) | | | [`THEME.SKETCH`](#sketch) | Hand-drawn wobble and halo, Comic Neue font, no grid. | | [`THEME.QUILL`](#quill) | Black ink on white paper: pen strokes, etched fills, IM Fell English font. | Themes also carry *defaults for chart settings*: every theme but `SKETCH` and `QUILL` shows a muted y-grid unless a chart call sets `show_grid` itself, and `HATCH` hatches bar series via its hatch cycle, which is why the very same chart code below renders with grids and hatches that differ per theme. An explicit setting always wins. The sample data and the two helpers behind every card are defined in a hidden cell. `show_swatches()` reads the palette, sequential colormap and font straight from the active [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md), so the strip always matches the theme as shipped. It closes with the theme's colour-blindness scores, computed by `cvd_scores()` as explained in the next section; `cvd_table()` lists them for every theme. `signature(pair)` builds the six charts with the very same code for every theme; `pair` supplies the two colors the twin-axis panel styles explicitly (one per axis), picked from the theme's own swatches. Each card opens with the one call that selects the theme. ## Colour-blindness suitability A reader compares any two series on a chart, so every theme is scored on its worst pair of palette colours, not only on neighbouring ones. Each colour is passed through the Machado, Oliveira and Fernandes (2009) simulation of deuteranopia, protanopia and tritanopia at full severity, and the distance between the two closest colours is measured in the OKLab colour space (ΔE, ×100). *Normal* is the same distance without simulation, and *greyscale gap* is the smallest lightness step between two colours once the chart is printed without colour. A theme **passes** when the worst pair stays at ΔE 8 or more for both deutan and protan readers and at 15 or more for everyone; between 6 and 8 it is **weak**, acceptable only where a second cue (dashes, markers, hatches) tells the series apart; below that it **fails**. Tritan scores are reported but not gated, since that deficiency is rare. `GREYSCALE` and `QUILL` separate series by lightness and pattern rather than hue, so their scores read the colour axis only. `DEFAULT`, `HARBOR`, `MUTED` and `CONTRAST` were built to pass this check. ``` cvd_table() ``` ## Screen and presentations Colorful categorical palettes on light furniture, for notebooks, dashboards and slides. ### Default The modernized default: a softened Okabe–Ito palette closed with charcoal, so every pair of series stays apart for colour-blind readers; white bar edges, open spines, soft y-grid from the theme default. Selected with [`THEME.DEFAULT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`DEFAULT_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.DEFAULT_THEME). ``` config.set_theme(THEME.DEFAULT) show_swatches() signature(pair=("#3B76B0", "#C24E2A")).show() ``` ### Material The Google palette with a bottom spine only and a light solid grid. Selected with [`THEME.MATERIAL`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`MATERIAL_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.MATERIAL_THEME). ``` config.set_theme(THEME.MATERIAL) show_swatches() signature(pair=("#1A73E8", "#D93025")).show() ``` ### Minimal Accent violet with deep grays, no spines or tick marks, flat bars. Selected with [`THEME.MINIMAL`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`MINIMAL_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.MINIMAL_THEME). ``` config.set_theme(THEME.MINIMAL) show_swatches() signature(pair=("#7048E8", "#1F2933")).show() ``` ### Harbor Navy to sky and amber to sand, stepped in lightness, with taupe and near-black closing the set. Two hue families keep the chart reading as one palette, and every pair of series stays apart for deutan, protan and tritan readers. Selected with [`THEME.HARBOR`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`HARBOR_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.HARBOR_THEME). ``` config.set_theme(THEME.HARBOR) show_swatches() signature(pair=("#1F4E79", "#D08C3A")).show() ``` ## Print and black-and-white Themes that survive a greyscale printer or photocopier: one that is monochrome by design, one whose palette stays distinct on paper, one that tells series apart by pattern, and two whose colours stay apart for colour-blind readers and carry dashes, markers or hatches for the greyscale print. ### Greyscale Monochrome and print-friendly, with the same open spines and muted grid treatment. Selected with [`THEME.GREYSCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`GREYSCALE_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.GREYSCALE_THEME). ``` config.set_theme(THEME.GREYSCALE) show_swatches() signature(pair=("#252525", "#969696")).show() ``` ### Ink The diversified YlGnBu palette (`COLORS.PaperYlGnBu`) with navy ink edges, print-ready. Selected with [`THEME.INK`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`INK_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.INK_THEME). ``` config.set_theme(THEME.INK) show_swatches() signature(pair=("#0C2C84", "#41B6C4")).show() ``` ### Hatch Black edges, dotted grid, and the hatch cycle (`""`, `"//"`, `".."`) applied per bar series, so grouped bars stay distinguishable in black-and-white print. Selected with [`THEME.HATCH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`HATCH_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.HATCH_THEME). ``` config.set_theme(THEME.HATCH) show_swatches() signature(pair=("#B5563A", "#4F6D8F")).show() ``` ### Muted Indigo, cyan, sand, rose and wine from Paul Tol's muted scheme, every pair distinct for deutan, protan and tritan readers. Lines also differ by dash and scatter points by marker, bars carry black edges and the grid is dotted, so a figure survives a greyscale print. Selected with [`THEME.MUTED`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`MUTED_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.MUTED_THEME). ``` config.set_theme(THEME.MUTED) show_swatches() signature(pair=("#332288", "#CC6677")).show() ``` ### Contrast Navy, straw, dusty rose, charcoal and grey, each a clear lightness step from the next, so a photocopy still tells the series apart; bars take a hatch cycle and black edges, lines a dash cycle, scatter points a marker cycle. Selected with [`THEME.CONTRAST`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`CONTRAST_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.CONTRAST_THEME). ``` config.set_theme(THEME.CONTRAST) show_swatches() signature(pair=("#1F4E79", "#B45C6A")).show() ``` ## Illustrative Hand-drawn looks for explainers, blog posts and talks, where a chart should read as a drawing rather than a measurement. ### Sketch Hand-drawn: wobbled paths, series lines cut out by a white halo, thick spines and lines, no grid, and the bundled Comic Neue font, so the look is the same on every machine. The wobble and halo are theme attributes applied at render time, so nothing changes in matplotlib's global settings; the halo sits under line, radial and regression lines only, and a chart's `style` can set `plot_sketch_halo_width` to `0` where many lines overlap. Selected with [`THEME.SKETCH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`SKETCH_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.SKETCH_THEME). ``` config.set_theme(THEME.SKETCH) show_swatches() signature(pair=("#2E86AB", "#E4572E")).show() ``` ### Quill Black ink on white paper, as a quill and an etching needle would draw it. There is one ink only, so series differ by line style, marker and etching, never by color. Series lines are broad-nib pen strokes whose width follows the pen's direction. Bars, areas and bodies are etched by hand instead of tiled with a hatch, with a faint ink wash under the bars and bodies. A value scale (heatmap, calendar, hexbin, filled contour) reads as steps of etch density; where a chart asks for a colorbar, a legend of the steps takes its place. Text is set in the bundled IM Fell English font, titles in its italic. Like the sketch look, every effect is a theme attribute applied at render time, so nothing changes in matplotlib's global settings. Selected with [`THEME.QUILL`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME); the full attribute set is [`QUILL_THEME`](https://eriknovak.github.io/datachart/0.10.2/references/themes/#datachart.themes.QUILL_THEME). ``` config.set_theme(THEME.QUILL) show_swatches() signature(pair=("#1A120A", "#1A120A")).show() ``` ______________________________________________________________________ Applying a theme replaces the whole global configuration, so remember to call `config.set_theme(...)` (or `config.reset_config()`) before building the charts it should style. See the [themes how-to](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) for customizing themes attribute by attribute. ``` config.reset_config() ``` # Colormaps A palette is named wherever a theme asks for colors: the two general palettes, the heatmap colormap, and the parallel-coordinates hues. The name is resolved through [pypalettes](https://y-sunflower.github.io/pypalettes/), which gives access to over 2500 palettes; the [COLORS](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORS) constant is a curated selection of them, rendered on this page. Which kind of palette a role wants: | Role | Attribute | Kind | | ------------------------------------------------- | ----------------------------------------------------- | ----------------------- | | Series sharing one axes | `color_general_multiple` | Categorical | | Single-color roles (network nodes, parallel ramp) | `color_general_singular` | Sequential | | Heatmap cells | `plot_heatmap_cmap` | Sequential or diverging | | Parallel-coordinates hue | `color_parallel_hue`, `color_parallel_hue_continuous` | Categorical, sequential | A palette asked for one color gives its last one, so a sequential palette yields one strong color and a graded set when several are asked for. The calendar heatmap, hexbin, and contour colormaps follow the heatmap's unless set. See the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide for setting these attributes and building a theme around them. Each palette below is shown as a continuous strip and as the six colors a chart with six series receives; hover a swatch for its hex code. A palette is passed as its `COLORS` value, or as the plain name string. A chip names a predefined theme that uses the palette and the role it plays there, and links to the theme's card in the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md). ``` from datachart.constants import COLORS ``` ## Sequential, Single Hue One hue from light to dark, ordered by magnitude. The natural `color_general_singular` palette: a role that needs one color takes the dark end. Also the safest heatmap colormap. ## Sequential, Multiple Hues Light to dark through two or more hues, which separates neighbouring values better than a single hue. Suited to heatmaps and value scales; the last color still serves a single series. ## Diverging Two hues meeting at a neutral center, for values with a meaningful midpoint: differences from a baseline, correlations, gains and losses. Use with a heatmap `norm` centred on that value. ## Categorical Distinct hues of similar weight, for series that are different in kind rather than in amount: the `color_general_multiple` palette. The six colors shown are the ones six series receive. ## Perceptually Uniform and Color-Blind Safe Equal steps in value read as equal steps in color, and the palettes stay distinguishable under the common forms of color-vision deficiency. The first six are sequential, the Okabe-Ito pair categorical. ## Greyscale For print and black-and-white reproduction; the greyscale themes pair it with hatching or markers to keep series apart. ## Datachart's Own Two palettes registered by datachart rather than pypalettes, made for the ink theme's publication look. They cycle through their exact colors instead of interpolating, so the series colors are always the ones listed. ## Beyond the Constant Any pypalettes name works where a `COLORS` value does. So does a single matplotlib color, which is a palette of one and repeats for every series, and a list of hex colors, which cycles through its exact colors like datachart's own palettes. The list is how the predefined themes define most of their series palettes, and how a custom theme states its own: ``` palettes("Antique", "#B5651D", ["#0B3954", "#FF6663", "#E0FF4F"]) ``` ## Using a Palette in a Theme A palette is set through the attributes in the table above, here for one figure with `override`; a theme sets the same attributes once. The heatmap takes its own colormap, the bars the general series palette: ``` from datachart.charts import BarChart, Heatmap from datachart.config import config from datachart.utils import Grid CELLS = {"z": [[r * c for c in range(1, 8)] for r in range(1, 7)]} BARS = [[{"label": f"Q{q}", "y": 30 + 12 * s + 7 * q} for q in range(1, 5)] for s in range(4)] with config.override(plot_heatmap_cmap=COLORS.Cividis, color_general_multiple=COLORS.OkabeIto): Grid( [[ Heatmap(data=CELLS, title="Cividis cells", show_heatmap_values=True), BarChart(data=BARS, title="Okabe-Ito series", subtitle=["A", "B", "C", "D"], show_legend=True), ]], figsize=(9, 3.2), ).show() ``` # Utility # Utility The utilities of the [datachart.utils](https://eriknovak.github.io/datachart/0.10.2/references/utils/index.md) module around a figure: the numbers behind the charts, getting a figure out to where it is read, and inspecting it while it is still on screen. Each card names a guide, says what it is for, and links to it. - [Statistics](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/stats/index.md) The numbers behind the charts, in [datachart.utils.stats](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/index.md): centers, spreads, correlations, fits, intervals, smoothers and densities, each shown feeding back into a chart's title, error bars, or an overlaid series. - [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) Writes a figure to disk through [datachart.utils.save_figure](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.save_figure): the format, resolution and background for a manuscript, a slide, and a web page, in one call or several at once. - [Interactive Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/interactive/index.md) Shows a figure with zoom, pan, and hover over its marks through the `interactive` flag of every figure's `show()` method, to read the point behind an outlier or check a value without labelling it. # Statistics A chart shows a shape; a statistic names it. The [datachart.utils.stats](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/index.md) module holds the numbers behind the charts (means, quantiles, correlations, fits, densities, smoothers) as plain functions on plain lists, so the same values that go into a chart can go into its title, its error bars, or a second series laid over it. This guide shows what each function answers and how its result feeds back into a chart, starting with the basics and building up to worked examples on real data. Looking for a specific statistic? Jump straight to the [quick reference](#the-functions), which maps common questions to the function that answers them. ``` from datachart.utils import stats ``` ## Basics The examples in this guide share one dataset: the flipper length (in mm) and body mass (in g) of the 342 penguins measured on three islands of the Palmer Archipelago, Antarctica (source: the [Palmer penguins](https://allisonhorst.github.io/palmerpenguins/) dataset, Gorman, Williams and Fraser 2014, released under CC0). The data lives in a hidden cell: `flipper` and `mass` are the pooled values, and `flipper_by_species` and `mass_by_species` split them by species (Adelie, Chinstrap and Gentoo, in the order of `SPECIES`). Every function below takes such a list of numbers, and most take nothing else: ``` flipper[:5], mass[:5] ``` **Basic example.** The first question about any measurement is where its values sit and how far they spread. `mean` and `stdev` answer it in the units of the data; `count` says how many values the answer rests on: ``` stats.count(flipper), stats.mean(flipper), stats.stdev(flipper) ``` The results are plain floats, so they go straight into an f-string. The most common place for one is the title of the chart it describes, which is how the rest of this guide uses them. ## The Functions The module is organised around the questions its functions answer. The table maps each question to its function and to the section that shows it on a chart; every function is documented in the [reference](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/index.md). | I want to… | Use | See | | ------------------------------------------------------ | ------------------------------------------------------------ | --------------------------------------------- | | know where the values sit | `mean`, `median`, `mode` | [Center](#center) | | know how far they spread | `stdev`, `variance`, `quantile`, `iqr`, `minimum`, `maximum` | [Spread](#spread) | | tell a skewed or heavy-tailed sample from a normal one | `skewness`, `kurtosis` | [Shape](#shape) | | say how much two variables move together | `correlation`, `spearman` | [Association](#association) | | draw a trend line | `linear_fit` | [Trend line](#trend-line) | | put an error bar on a mean | `bootstrap_ci` | [Confidence intervals](#confidence-intervals) | | pick the bins of a histogram from the data | `histogram` | [Binning](#binning) | | smooth a noisy series | `rolling_mean`, `ewma`, `loess` | [Smoothing](#smoothing) | | draw a distribution as a curve or a contour | `kde1d`, `kde2d` | [Density estimates](#density-estimates) | The functions accept any sequence of numbers, a list or a NumPy array, and return Python floats and lists, so their results drop into the `data` of any chart. `minimum` and `maximum` also accept dates and strings; `correlation`, `spearman`, `linear_fit`, `loess` and `kde2d` accept dates for `x`, which the [Smoothing](#smoothing) section uses. ### Center *What is a typical value?* has three answers, and they disagree exactly when the answer matters. `mean` is the balance point and moves with every value, including outliers; `median` is the middle value and ignores them; `mode` is the most frequent value and is meant for discrete data, where values repeat. The Gentoo penguins are far heavier than the other two species, so the pooled body mass has a long right tail that pulls the mean above the median: ``` stats.mean(mass), stats.median(mass) ``` Flipper lengths were measured to the millimeter, so they repeat and have a mode. The three centers on a histogram show the disagreement: a [Histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) of the mass with the mean and median as `vlines` puts the gap between them on the tail that causes it. ``` from datachart.charts import Histogram from datachart.constants import FIG_SIZE, SHOW_GRID Histogram( data=[{"x": m} for m in mass], num_bins=25, vlines=[ {"x": stats.mean(mass), "label": f"mean {stats.mean(mass):.0f} g"}, {"x": stats.median(mass), "label": f"median {stats.median(mass):.0f} g", "style": {"plot_vline_style": "--"}}, ], title="Penguin body mass: the Gentoo tail pulls the mean past the median", xlabel="Body mass (g)", ylabel="Penguins", show_legend=True, show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Spread *How different are the values from each other?* `stdev` is the typical distance from the mean, in the units of the data, and `variance` is its square (the unit a sum of independent errors adds up in). Both are pulled by outliers, so for a skewed sample the quantiles describe the spread more honestly: `quantile(values, q)` is the value below which `q` percent of the sample lies, `iqr` is the distance between the 25th and 75th quantiles (the middle half of the data), and `minimum` and `maximum` are the range. On the pooled mass: ``` { "stdev": round(stats.stdev(mass)), "variance": round(stats.variance(mass)), "q25": stats.quantile(mass, 25), "q75": stats.quantile(mass, 75), "iqr": stats.iqr(mass), "range": (stats.minimum(mass), stats.maximum(mass)), } ``` The five numbers a [Box Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/boxplot/index.md) draws (minimum, first quartile, median, third quartile, maximum) are these same functions, and computing them by hand is how a spread gets into a table or a title. A [Dumbbell Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/dumbbellchart/index.md) of the 25th and 75th quantiles per species shows where the middle half of each species sits: the Gentoo range does not even touch the other two. ``` from datachart.charts import DumbbellChart DumbbellChart( data=[ { "label": species, "start": stats.quantile(values, 25), "end": stats.quantile(values, 75), } for species, values in mass_by_species.items() ], start_name="25th quantile", end_name="75th quantile", title="The middle half of each species' body mass", xlabel="Body mass (g)", show_legend=True, show_grid=SHOW_GRID.X, figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Shape *Is the sample normal, or does it lean or have heavy tails?* `skewness` measures the asymmetry: positive when the tail extends to the right of the bulk, negative when it extends to the left, zero for a symmetric distribution. `kurtosis` measures the tails against a normal distribution, which scores zero: positive for heavier tails and a sharper peak, negative for lighter tails and a flatter, boxier shape. The two numbers say whether a mean and standard deviation are a fair summary (they are for a normal sample) or hide a tail. The pooled mass leans right; each species on its own is close to symmetric: ``` { "all": (round(stats.skewness(mass), 2), round(stats.kurtosis(mass), 2)), **{ species: (round(stats.skewness(values), 2), round(stats.kurtosis(values), 2)) for species, values in mass_by_species.items() }, } ``` The shape statistics are easiest to read against the distribution they describe. Per-species subplots of the body mass, each titled with its skewness, show that the right lean of the pooled sample is a mixture effect, not a property of any species: ``` Histogram( data=[[{"x": m} for m in values] for values in mass_by_species.values()], subtitle=[ f"{species} (skewness {stats.skewness(values):.2f})" for species, values in mass_by_species.items() ], num_bins=15, xlabel="Body mass (g)", ylabel="Penguins", subplots=True, sharex=True, xmin=2_500, xmax=6_500, xticks=[3_000, 4_000, 5_000, 6_000], show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Association *When one variable goes up, does the other?* `correlation` is Pearson's coefficient: the strength of a *straight-line* relationship, from -1 through 0 (none) to 1. `spearman` is the same coefficient computed on the ranks, so it measures any relationship that only goes one way (monotone), whether or not it is a straight line, and one outlier cannot move it much. When the two agree, the relationship is linear; when Spearman is clearly larger, the relationship bends or an outlier is dragging Pearson down. Flipper length and body mass: ``` stats.correlation(flipper, mass), stats.spearman(flipper, mass) ``` A correlation belongs next to the scatter it summarises. The [Scatter Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md) can print it by itself with `show_correlation`; computing it here instead lets the title carry both coefficients, and the per-species correlations show that the relationship holds *within* each species too, not only because the species differ in size: ``` from datachart.charts import ScatterChart ScatterChart( data=[ [{"x": f, "y": m} for f, m in PENGUINS[species]] for species in SPECIES ], subtitle=[ f"{species} (r = {stats.correlation(flipper_by_species[species], mass_by_species[species]):.2f})" for species in SPECIES ], title=( f"Flipper length against body mass: Pearson {stats.correlation(flipper, mass):.2f}, " f"Spearman {stats.spearman(flipper, mass):.2f}" ), xlabel="Flipper length (mm)", ylabel="Body mass (g)", show_legend=True, show_grid=SHOW_GRID.BOTH, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Trend line *How much does y change per unit of x?* A correlation says how tight a relationship is, not how steep. `linear_fit(x, y)` fits the straight line `y = slope * x + intercept` and returns `(slope, intercept, r2)`, where the slope is the rate in the units of the data and `r2` is the share of the variation in `y` the line explains (1 is a perfect fit). Across all penguins, each extra millimeter of flipper goes with about 50 g of body mass: ``` slope, intercept, r2 = stats.linear_fit(flipper, mass) slope, intercept, r2 ``` The fit is a trend line waiting to be drawn: evaluate `slope * x + intercept` at the two ends of the x range and lay the resulting [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) over the scatter with a [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md). The `show_regression` option of the scatter chart draws the same line; computing the fit here puts the slope in the legend, where it answers the question in words. ``` from datachart.charts import LineChart from datachart.utils import Panel ends = [stats.minimum(flipper), stats.maximum(flipper)] Panel( [ ScatterChart(data=[{"x": f, "y": m} for f, m in zip(flipper, mass)], subtitle="penguins"), LineChart( data=[{"x": x, "y": slope * x + intercept} for x in ends], subtitle=f"fit: {slope:.0f} g per mm (r² = {r2:.2f})", ), ], title="Body mass grows about 50 g per millimeter of flipper", xlabel="Flipper length (mm)", ylabel_left="Body mass (g)", show_legend=True, show_grid=SHOW_GRID.BOTH, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Confidence intervals *How sure is this mean?* A sample mean is an estimate, and a different sample would give a different one. `bootstrap_ci` puts a range on it without assuming a distribution: it resamples the values with replacement `n_resamples` times (1000 by default), computes the `statistic` (the `mean` by default) on each resample, and returns the central `level` share of the results (95% by default). Pass a `seed` so the interval is the same on every run. Only 68 Chinstrap penguins were measured against 151 Adelie, so the Chinstrap interval is wider: ``` { species: tuple(round(v) for v in stats.bootstrap_ci(values, seed=42)) for species, values in mass_by_species.items() } ``` The half-width of the interval is a ready-made error bar. A [Bar Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) of the species means with `yerr` set to it, and `show_yerr` on, says at a glance that the Adelie and Chinstrap means are indistinguishable while the Gentoo mean is far off: ``` from datachart.charts import BarChart bars = [] for species, values in mass_by_species.items(): low, high = stats.bootstrap_ci(values, seed=42) bars.append({"label": species, "y": stats.mean(values), "yerr": (high - low) / 2}) BarChart( data=bars, title="Mean body mass per species with 95% bootstrap intervals", xlabel="Species", ylabel="Body mass (g)", show_yerr=True, show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_SHORT, ).show() ``` Any statistic works: pass `statistic=stats.median` for a robust center, or `stats.iqr` for the uncertainty of a spread. The statistic is a function of a list, so a `lambda` fits too. ``` stats.bootstrap_ci(mass_by_species["Gentoo"], statistic=stats.median, seed=42) ``` ### Binning *How many bins should a histogram have?* Too few hide the shape, too many show noise. `histogram(values, bins)` returns the `(counts, edges)` of a histogram, with one more edge than counts, and its `bins` argument accepts the rules of `numpy.histogram_bin_edges`: `"auto"` (the default), `"fd"` (Freedman–Diaconis, robust to outliers), `"sturges"` (few bins, for small normal samples), `"rice"`, or an integer or an explicit list of edges. On the pooled flipper length the Freedman–Diaconis rule picks the bins from the spread and the sample size: ``` counts, edges = stats.histogram(flipper, bins="fd") len(counts), [round(e) for e in edges[:4]] ``` The [Histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) front takes a fixed `num_bins`, so the rule is how to let the data choose it. The counts and edges also draw directly as a [Bar Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md), which is the way to plot bins that were computed elsewhere or to label them by hand: ``` Histogram( data=[{"x": f} for f in flipper], # the number of bins the Freedman-Diaconis rule chose num_bins=len(counts), title=f"Flipper length in {len(counts)} bins (Freedman–Diaconis rule)", xlabel="Flipper length (mm)", ylabel="Penguins", show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_SHORT, ).show() ``` ### Smoothing *What is the trend under the noise?* A series measured over time jitters from one reading to the next, and the eye reads the jitter as signal. The three smoothers each return a series that lines up with the input, so they draw as extra lines over the raw one, and each answers the question differently: - `rolling_mean(values, window)` replaces each value by the mean of the `window` values ending there. It is easy to explain and to reproduce, but the first `window - 1` outputs are `nan`, and it lags the data by half a window. - `ewma(values, alpha)` blends each value with the previous output, `alpha * value + (1 - alpha) * previous`, so it starts at once and weights recent values most. A larger `alpha` follows the data more closely; a smaller one smooths harder. - `loess(x, y, frac)` fits a local straight line through the nearest `frac` share of the points at every `x`, weighted so closer points count more. It follows curves, has no lag and no warm-up, and is the smoother to show when the shape of the trend is the message. The smoothers need a time series, so this section switches to a second dataset: the global mean surface temperature anomaly of each year from 1880 to 2024, in °C relative to the 1951–1980 mean (source: NASA GISTEMP v4, rounded), in a hidden cell as `YEARS` and `ANOMALY`. The raw series is noisy enough that the year-to-year swings hide the acceleration after 1970: ``` smoothed = { "10-year rolling mean": stats.rolling_mean(ANOMALY, window=10), "EWMA (alpha 0.1)": stats.ewma(ANOMALY, alpha=0.1), "LOESS (frac 0.2)": [p["y"] for p in stats.loess(YEARS, ANOMALY, frac=0.2)], } {name: [round(v, 2) for v in values[-3:]] for name, values in smoothed.items()} ``` Drawn together on one [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md), with the raw series in the background through [emphasis](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/highlighting/index.md), the three smoothers show their characters: the rolling mean starts ten years late and runs behind every turn, the EWMA starts at once but still trails, and the LOESS curve sits in the middle of the data throughout. ``` from datachart.constants import EMPHASIS LineChart( data=[ [{"x": y, "y": a} for y, a in zip(YEARS, ANOMALY)], *[[{"x": y, "y": v} for y, v in zip(YEARS, values)] for values in smoothed.values()], ], subtitle=["yearly anomaly", *smoothed], # the raw series is context, so it is muted and left out of the legend emphasis=[EMPHASIS.BACKGROUND, None, None, None], title="Global temperature anomaly under three smoothers", xlabel="Year", ylabel="Anomaly (°C)", show_legend=True, show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `loess` returns `{x, y}` points sorted by `x`, so its result is already the `data` of a line chart, and its `x` may be dates or datetimes: the fit runs on the dates directly and the curve keeps them, so it lands on a date axis. `linear_fit` (its slope is then per day), `correlation`, `spearman` and `kde2d` accept dates the same way. The [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) guide shows a date axis in use. ### Density estimates *What does the distribution look like, without the bins?* A histogram's shape changes with its bin edges; a kernel density estimate smooths the same values into a curve that does not. `kde1d(values)` returns the curve as `{x, y}` points ready for a [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md); it integrates to 1, so it overlays a density [Histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) of the same values on the same axis. Its `bandwidth` sets how smooth the curve is: a rule of [BANDWIDTH](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) (Scott's by default) or a number, where smaller values follow the data more closely. `gridsize` is the number of points, and `cut` how many bandwidths the curve extends past the extremes (or `xlim` fixes the range, so several curves share one grid). One curve per species, with `show_area`, is the cleanest picture of the three flipper distributions: ``` LineChart( data=[stats.kde1d(values, xlim=(165, 240)) for values in flipper_by_species.values()], subtitle=list(SPECIES), title="Flipper length per species as density curves", xlabel="Flipper length (mm)", ylabel="Density", show_area=True, show_legend=True, show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` `kde2d(x, y)` does the same for pairs of values and returns the `{x, y, z}` surface a [Contour Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/contourchart/index.md) draws: the density contours of a scatter, which show where the points crowd when they overplot. The `gridsize` can be one number or an `(x, y)` pair of column and row counts, and `xlim` and `ylim` fix the grid so several surfaces share it. Flipper length against body mass forms two clusters, the Gentoo one apart from the rest: ``` from datachart.charts import ContourChart ContourChart( data=stats.kde2d(flipper, mass), title="Where the penguins crowd: density of flipper length against body mass", xlabel="Flipper length (mm)", ylabel="Body mass (g)", filled=True, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ## Real-World Examples The examples below put the functions above to work, each one answering a question with a number and the chart that shows it. The first two stay with the penguins and the temperature record introduced above; the third brings its own data in a hidden cell and says where it comes from. ### Example 1: Is the Warming Speeding Up? (Linear Fits over Two Periods, a LOESS Curve, and a Note) The question about the temperature record is not whether it rises but whether it rises faster than it used to. Two `linear_fit` calls answer it in one number each: the slope over the whole record since 1880 and the slope since 1975, both in °C per decade. A LOESS curve over the muted yearly values shows the bend the two lines summarise, and a note carries the two rates. The lines are drawn only over their own period, so the eye compares slopes, not lengths. ``` from datachart.constants import LEGEND_LOCATION def trend(start): years = [y for y in YEARS if y >= start] values = ANOMALY[YEARS.index(start):] slope, intercept, _ = stats.linear_fit(years, values) line = [{"x": y, "y": slope * y + intercept} for y in (years[0], years[-1])] return 10 * slope, line rate_all, line_all = trend(1880) rate_recent, line_recent = trend(1975) Panel( [ LineChart(data=[{"x": y, "y": a} for y, a in zip(YEARS, ANOMALY)], emphasis=EMPHASIS.BACKGROUND), LineChart(data=stats.loess(YEARS, ANOMALY, frac=0.2), subtitle="LOESS trend"), LineChart(data=line_all, subtitle=f"since 1880: {rate_all:.2f} °C per decade"), LineChart( data=line_recent, subtitle=f"since 1975: {rate_recent:.2f} °C per decade", texts={ "text": f"{rate_recent / rate_all:.1f}× the long-run rate", "x": 1930, "y": 0.8, "target": (2000, line_recent[0]["y"] + rate_recent * 2.5), }, ), ], title="Global temperature anomaly, 1880–2024 (NASA GISTEMP)", xlabel="Year", ylabel_left="Anomaly (°C)", show_legend=True, # the lines end in the top right corner, where the legend would sit legend={"location": LEGEND_LOCATION.UPPER_LEFT}, show_grid=SHOW_GRID.Y, figsize=FIG_SIZE.FULL_MEDIUM, ).show() ``` ### Example 2: Are the Three Species Really Different? (Bootstrap Intervals, Density Curves, and a Grid) A single chart of species means invites the objection that the samples are small. The answer is to show the uncertainty next to the distribution: a bar chart of the mean body mass per species with `bootstrap_ci` error bars, and the `kde1d` curves of the same values beside it. The two charts share their data, so they go in a [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md), and each title carries the number its chart supports. ``` from datachart.utils import Grid intervals = {species: stats.bootstrap_ci(values, seed=42) for species, values in mass_by_species.items()} gap = stats.mean(mass_by_species["Gentoo"]) - stats.mean(mass_by_species["Adelie"]) means = BarChart( data=[ {"label": species, "y": stats.mean(values), "yerr": (intervals[species][1] - intervals[species][0]) / 2} for species, values in mass_by_species.items() ], title=f"Gentoo: {gap:.0f} g heavier on average", xlabel="Species", ylabel="Mean body mass (g)", show_yerr=True, show_grid=SHOW_GRID.Y, ) curves = LineChart( data=[stats.kde1d(values, xlim=(2_500, 6_500)) for values in mass_by_species.values()], subtitle=list(SPECIES), title="Adelie and Chinstrap overlap", xlabel="Body mass (g)", ylabel="Density", show_area=True, show_legend=True, show_grid=SHOW_GRID.Y, ) Grid( [means, curves], title="Body mass of three penguin species (Palmer penguins)", figsize=(6.3, 3.0), ).show() ``` ### Example 3: Did the Change Help? (Comparing Two Groups with Their Centers, Spreads, and Intervals) An A/B test ends with two lists of numbers and the question whether they differ. `page_load` holds the illustrative page load time (in ms) of 400 visits before and 400 after a caching change, drawn from seeded log-normal generators, because load times are right-skewed: a few slow requests stretch the tail. That skew is why the mean and the median disagree, and why the comparison should be made on both, with a bootstrap interval on each. The medians go in a dumbbell (the shift), the intervals in the title, and the distributions in a pair of density curves that share one grid through `xlim`. ``` summary = { group: { "mean": stats.mean(values), "median": stats.median(values), "median_ci": stats.bootstrap_ci(values, statistic=stats.median, seed=42), "skewness": stats.skewness(values), } for group, values in page_load.items() } {group: {k: (round(v) if isinstance(v, float) else tuple(round(x) for x in v)) for k, v in s.items() if k != "skewness"} for group, s in summary.items()} ``` ``` before, after = summary["before"], summary["after"] shift = DumbbellChart( data=[ {"label": "median", "start": before["median"], "end": after["median"]}, {"label": "mean", "start": before["mean"], "end": after["mean"]}, ], start_name="before", end_name="after", title=( f"Median {before['median']:.0f} → {after['median']:.0f} ms " f"(95% CI {after['median_ci'][0]:.0f}–{after['median_ci'][1]:.0f})" ), xlabel="Page load time (ms)", show_legend=True, show_grid=SHOW_GRID.X, ) shapes = LineChart( data=[stats.kde1d(values, xlim=(0, 2_000)) for values in page_load.values()], subtitle=[f"{group} (skewness {s['skewness']:.1f})" for group, s in summary.items()], title="The change also trims the slow tail", xlabel="Page load time (ms)", ylabel="Density", show_area=True, show_legend=True, show_grid=SHOW_GRID.Y, ) Grid( [[shift], [shapes]], title="Page load time before and after the caching change", figsize=(6.3, 5.4), ).show() ``` # Saving Figures A chart that stays in a notebook reaches one reader. The [datachart.utils.save_figure](https://eriknovak.github.io/datachart/0.10.2/references/utils/#datachart.utils.save_figure) function writes the figure a chart function returns to disk, in one format or several, at the resolution the destination needs. This guide shows how to choose a format for where the figure is going, starting with the basics and building up to worked examples: a figure set for a manuscript, a slide, and a chart on a web page. Looking for a specific task? Jump straight to the [quick reference](#choosing-the-output), which maps common destinations to the arguments that fit them. ## Basics Every chart function returns a matplotlib figure. Pass it to `save_figure` with a path, and the extension picks the format. The theme is baked into the figure when it is created, so saving never consults the global `config`, and a figure saved later looks the way it did when it was drawn. ``` from datachart.charts import LineChart from datachart.utils import save_figure figure = LineChart( data=[{"x": i, "y": i**2} for i in range(10)], subtitle="growth", xlabel="Step", ylabel="Value", ) save_figure(figure, "chart.png") ``` The function returns the list of paths it wrote, here `["chart.png"]`. The parent directory must exist, and a file that already exists is overwritten. ## Choosing the Output The format, resolution and background are the three choices, and the destination decides each of them. The table maps the common destinations to the arguments that fit; the [FIG_FORMAT](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_FORMAT) constant lists every supported format. | I want to… | Use | See | | --------------------------------------------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | put the figure in a paper or a printed report | `format=FIG_FORMAT.PDF` or `.svg` | [Vector or raster](#vector-or-raster) | | upload it where only images are accepted | `format=FIG_FORMAT.PNG`, `dpi=300` or higher | [Vector or raster](#vector-or-raster) | | lay it on a colored slide or page | `transparent=True` | [Transparent background](#transparent-background) | | write a PDF and a PNG of the same figure | `format=[FIG_FORMAT.PDF, FIG_FORMAT.PNG]` | [Several formats at once](#several-formats-at-once) | | show it on a web page | SVG in an `` tag, or inline | [Embedding in web pages](#embedding-in-web-pages) | | size it for the destination before saving | `figsize` on the chart function | [FIG_SIZE](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | ### Vector or raster *Will the figure be scaled?* A **vector** format (PDF, SVG, EPS) stores the marks as shapes, so the figure stays crisp at any size and the file stays small for charts made of lines, bars and text. Use it for print, papers and web pages, and whenever the reader may zoom. A **raster** format (PNG, JPG, WEBP, TIFF) stores pixels at the resolution set by `dpi`, so it is the format for submission portals, chat, and slides that accept only images. The default `dpi` is 300, which prints cleanly; go higher for a figure that will be enlarged, and never below 150 for anything printed. Vector formats ignore `dpi`. ``` from datachart.constants import FIG_FORMAT save_figure(figure, "chart.pdf") # vector, from the extension save_figure(figure, "chart", format=FIG_FORMAT.PNG, dpi=600) # raster, at 600 dots per inch ``` ### Transparent background *What is behind the figure?* By default the figure has the theme's background color, which is right on a white page and wrong on a colored slide, a dark web page, or a poster: the chart arrives in a white rectangle. `transparent=True` drops the figure background, so the chart sits on whatever is behind it. ``` save_figure(figure, "chart.png", transparent=True) ``` Text and marks keep their own colors, so a transparent figure meant for a dark background needs a theme with light text; the [Themes](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) guide shows how to set one before the chart is drawn. ### Several formats at once *Who else needs this figure?* A figure is often needed more than once: a PDF for the manuscript, a PNG for the preview, an SVG for the slides. Pass a list of formats and the same figure is written once per format. ``` paths = save_figure(figure, "figures/growth", format=[FIG_FORMAT.PDF, FIG_FORMAT.PNG]) # ["figures/growth.pdf", "figures/growth.png"] ``` With a list, the path is a stem rather than a file name, and each format is appended to it. An extension on the stem is dropped when it names a supported format, so `figures/growth.png` and `figures/growth` behave the same; a dotted name such as `figures/growth.v2` keeps every part of itself and yields `figures/growth.v2.pdf`. `save_figure` returns the paths it wrote, in the order the formats were given. It returns a list for a single format too, so the return value never changes shape. `dpi` and `transparent` apply to every file in the call; vector formats ignore `dpi`, so one call can carry a raster resolution alongside them. For different settings per format, make separate calls. ### Embedding in web pages *Which screens will show it?* Export the figure as SVG with a transparent background: ``` save_figure(figure, "chart.svg", transparent=True) ``` SVG is the right format for the web: it renders sharp on every screen density, it scales with the layout instead of being resized as an image, and for charts drawn from lines, bars and text the file is usually smaller than a PNG of the same size. The transparent background lets the chart match the page in both light and dark color schemes. There are two ways to put the SVG on a page: - **As an image.** Reference the file from an `` tag, the same as any picture. The chart is cached and reused across pages, its markup stays out of the HTML, and the page's CSS cannot reach into it. Prefer this when the chart is a static illustration. ``` Value against step, growing quadratically ``` - **Inline.** Paste the file's contents into the HTML directly. The chart becomes part of the document, so the page's CSS can restyle its strokes and fills, and text inside it is selectable and searchable. Prefer this when the chart should follow the page's styling or when there is a single chart per page. The markup is repeated on every page that shows it, so keep the number of inlined charts small. ```
...
Value against step.
``` When the page or platform cannot use SVG, save a PNG at `dpi=300` or higher and embed it the same way as the image above. Raster images blur when scaled up, so export at the largest size the page will display. The exported file is a static picture. `datachart` does not render figures interactively in the browser: there is no HTML export, no embed snippet, and no zoom or hover on a web page. Interactivity is available in notebooks and GUI windows through `show(interactive=True)`, as described in the [Interactive Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/interactive/index.md) guide. For charts that zoom and hover inside a web page, use a browser-native library such as [d3](https://d3js.org/), [Plotly](https://plotly.com/javascript/), [Vega](https://vega.github.io/vega/), or [Bokeh](https://bokeh.org/). ## Real-World Examples The examples below put the arguments above to work on the three destinations a figure most often has. Each one names its destination, the constraint it imposes, and the call that meets it. ### Example 1: The Figure Set of a Manuscript (A Stem per Figure, Two Formats, One Size) A journal wants every figure twice: as a PDF the typesetter places in the article, and as a PNG the submission portal shows to reviewers. The figures must share one width so their fonts print at the same size, and their files must be named after the figure they are. One loop over a dictionary of figures handles all of it: the figure size is fixed once, the stem is the figure's name, and the list of formats writes both files per figure. The returned paths make the file list for the cover letter. ``` from pathlib import Path from datachart.charts import BarChart, LineChart, ScatterChart from datachart.constants import FIG_FORMAT, FIG_SIZE from datachart.utils import save_figure # one figure per panel of the paper, at the journal's single-column width figures = { "fig1_growth": LineChart(data=growth, xlabel="Day", ylabel="Cells (×10⁶)", figsize=FIG_SIZE.HALF_MEDIUM), "fig2_dose": ScatterChart(data=dose_response, xlabel="Dose (mg)", ylabel="Response", figsize=FIG_SIZE.HALF_MEDIUM), "fig3_groups": BarChart(data=group_means, xlabel="Group", ylabel="Mean", show_yerr=True, figsize=FIG_SIZE.HALF_MEDIUM), } out = Path("manuscript/figures") out.mkdir(parents=True, exist_ok=True) written = [] for name, figure in figures.items(): written += save_figure(figure, str(out / name), format=[FIG_FORMAT.PDF, FIG_FORMAT.PNG], dpi=600) written # ["manuscript/figures/fig1_growth.pdf", "manuscript/figures/fig1_growth.png", ...] ``` The `dpi` applies to the PNG alone, so the vector PDF costs nothing extra, and 600 dots per inch keeps the PNG sharp when a reviewer zooms into it. ### Example 2: A Chart on a Slide (A Slide-Sized Figure with a Transparent Background) A slide deck has a colored background and a wide aspect ratio, and a figure that ignores either looks pasted on: a white box, or a chart that fills half the slide with a font too small to read from the back of the room. Draw the figure at the slide's own size with [FIG_SIZE.SLIDE_16_9](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE), so its fonts are sized for a slide, and save it with a transparent background so the slide's color shows through. PNG is the safe format for presentation software; `dpi=200` is plenty for a projector. ``` from datachart.charts import BarChart from datachart.constants import FIG_SIZE from datachart.utils import save_figure figure = BarChart( data=quarterly_revenue, title="Revenue by quarter", ylabel="Revenue (M€)", show_values=True, figsize=FIG_SIZE.SLIDE_16_9, ) save_figure(figure, "deck/revenue.png", dpi=200, transparent=True) ``` Insert the PNG at full slide width and it lands pixel for pixel, with no resizing and no white frame. For a deck that will also be printed as a handout, add `FIG_FORMAT.PDF` to a `format` list in the same call. ### Example 3: A Chart on a Documentation Page (SVG, Transparent, in an Image Tag) A documentation site is read on phones and high-density laptop screens, in light and dark mode. The one export that serves all of them is a transparent SVG: it stays sharp at any density, scales with the page layout, and shows the page's own background in either color scheme. Draw the figure at the page's content width, save it next to the page, and reference it from an image tag with an `alt` text that says what the chart shows, so the page reads without it too. ``` from datachart.charts import LineChart from datachart.constants import FIG_SIZE from datachart.utils import save_figure figure = LineChart( data=latency_by_release, subtitle=["p50", "p99"], xlabel="Release", ylabel="Latency (ms)", show_legend=True, figsize=FIG_SIZE.FULL_SHORT, ) save_figure(figure, "docs/assets/latency.svg", transparent=True) ``` ``` Median and tail latency per release; the tail halves after release 2.3 ``` If the site's dark mode turns the page black, the theme's dark text disappears with it; either pick a theme with a mid-grey text color for the docs figures or export a second SVG under a dark theme and switch between the two with a `` element and a `prefers-color-scheme` media query. # Interactive Figures A finished chart answers the question it was drawn for; the next question is usually about one mark on it. *Which point is that outlier? What was the value in that quarter? Is that the p99 line or the p50?* Labelling every mark would bury the chart, so the answer is to ask the figure while it is on screen. Every figure `datachart` returns is static until shown: `figure.show()` renders it inline in a notebook and opens a GUI window in a script. Pass `interactive=True` to `show()` to zoom, pan, and hover over the marks instead. The flag is the only switch: the chart functions, `Panel`, `Grid`, and the `config` know nothing about it, and a figure shown the default way is byte-for-byte unchanged. This guide shows what the interactive view offers, starting with the basics and building up to worked examples. Looking for a specific task? Jump straight to the [quick reference](#the-interactive-view), which maps common questions to the gesture that answers them. ## Basics The same figure, shown twice: once as the static image the notebook always shows, once on an interactive canvas. Nothing about the chart changes between the two calls. ``` from datachart.charts import LineChart figure = LineChart( data=[{"x": i, "y": i**2} for i in range(10)], subtitle="growth", xlabel="Step", ylabel="Value", ) figure.show() # the static image figure.show(interactive=True) # zoom, pan, and hover ``` The figure above, shown in a notebook, hovered and zoomed: The recording is static because the documentation site has no Python kernel behind it; run the snippet in a notebook to get the live widget. ### Installing the extra Interactivity needs two optional packages, [ipympl](https://matplotlib.org/ipympl/) for the notebook widget canvas and [mplcursors](https://mplcursors.readthedocs.io/) for the hover annotations. Install them with the `interactive` extra: ``` pip install "datachart[interactive]" # or: uv add "datachart[interactive]" ``` Without them `show(interactive=True)` raises an `ImportError` naming the missing package and the extra. It never falls back to a static figure. ## The Interactive View The view offers three gestures, and each answers a different question about the chart. | I want to… | Do | See | | ----------------------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------- | | look closely at a crowded region | zoom to a rectangle, pan, step back | [Zoom and pan](#zoom-and-pan) | | read the value behind one mark | hover it | [Hover to inspect](#hover-to-inspect) | | know what a chart's hover annotation shows | the per-chart table | [What each chart reports](#what-each-chart-reports) | | inspect a panel's secondary axis or a grid cell | hover as on a single chart | [Hover follows composition](#hover-follows-composition) | | keep the figure as an image after inspecting it | `save_figure` | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) | ### Zoom and pan *What is going on in that corner?* Points overplot, lines cross, and the interesting part of a chart is often a tenth of its width. Zooming to a rectangle magnifies it without redrawing the chart with new limits, and stepping back restores the whole view. - **Notebooks.** The figure is displayed on an `ipympl` widget canvas with the matplotlib toolbar: zoom to a rectangle, pan, step back through views, and save. The widget takes the place of the static image, so nothing is displayed twice. - **Scripts.** The figure opens in the GUI window it always did; the window's own toolbar provides the zoom and pan. ### Hover to inspect *What is this mark, exactly?* Hovering a mark shows an annotation with the series' legend label on the first line (its `subtitle`, or the `legend_label` a `Panel` assigned) and one `name: value` line per field of the mark. A mark that stands for a data point reports its axis coordinates: the names are the axis labels of the chart when set (`xlabel`, `ylabel`, and a `Panel`'s `ylabel_left` / `ylabel_right`), and `x` / `y` otherwise; a series on a `Panel`'s secondary value axis reports the secondary label. Values are formatted the way the axis formats its coordinates, so a point on a category axis reports the category name and a point on a date axis reports the date. A mark that stands for an aggregate reports its summary under plain names: a box its `median` and quartiles, a histogram bin its range and count, a sankey link its endpoints and `flow`. The annotation wears the theme's text annotation style (the `plot_text_*` font, box, and connector), so it matches the figure it sits on. Every chart type has hover support. Filled marks (bars, bands, boxes, bodies, cells, hexagons, tiles, nodes, ribbons) pick anywhere inside; lines, outlines, and network edges pick within a few points of their stroke, as do the outline of a `step` histogram and the edges of a filled contour band. Text annotations and reference lines decorate the chart and carry no hover. ### Hover follows composition *Does it still work once charts are combined?* Hover follows the marks through composition: it works on every series of a [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) overlay, including those on the secondary axis, which report the panel's right axis label, and in every cell of a [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md), where each cell's marks report that cell's labels. A figure composed from figures keeps the hover of each. ### What each chart reports The table lists, per chart, the mark that is picked and what its annotation shows; the gallery below it shows the annotation on each chart. | Chart | Mark | Annotation | | ----------------------------------------------------------------------------------------------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) | line point | legend label, `x`, `y` | | [Stacked Area Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/stackedareachart/index.md) | band, at the nearest point | legend label, `x`, the series' own `y` (never the stack total) | | [Bump Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/bumpchart/index.md) | line, at the nearest period | legend label, `x`, the rank as `y`, the original `value` | | [Bar Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) | bar | legend label, category, the bar's own value (never the stack total) | | [Pyramid Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/pyramidchart/index.md) | bar | legend label, category, the value as passed, positive | | [Gantt Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ganttchart/index.md) | task bar | legend label, `task`, `start`, `end`, `duration` in days, and `progress` when set | | [Dumbbell Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/dumbbellchart/index.md) | start or end dot | legend label (the endpoint name), category, value | | [Radial Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/radialchart/index.md) | point, bar, or bin | legend label, `angle` (the category, or a bin's degree range), `radius` | | [Histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) | bin | legend label, the bin's range, its count (or density) | | [Box Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/boxplot/index.md) | box | legend label, category, `median`, `q1`, `q3`, `min`, `max` | | [Violin Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/violinplot/index.md) | body | legend label (or the split value), category, `median`, `q1`, `q3`, `min`, `max` | | [Swarm Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/swarmplot/index.md) | point | legend label, category, value | | [Raincloud Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/raincloudplot/index.md) | box, body, or rain point | as the box, violin, and swarm marks | | [Ridgeline Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ridgelineplot/index.md) | ridge | legend label, category, `median`, `q1`, `q3`, `min`, `max` | | [Scatter Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md) | point | legend label (or the `hue` group), `x`, `y` | | [Heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/heatmap/index.md) | cell | legend label, `x`, `y`, `value` | | [Calendar Heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/calendarheatmap/index.md) | day cell | legend label, `date`, `value` | | [Contour Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/contourchart/index.md) | level line or filled band | legend label, `level` (a band's two levels) | | [Hexbin Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/hexbinchart/index.md) | hexagon | legend label, `x`, `y` (the cell center), `count` (or the reduced `c` under its reducer's name) | | [Parallel Coordinates](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/parallelcoords/index.md) | row line, at the nearest axis | the `hue` value (or the legend label), the axis name and the row's value there | | [Network Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/networkchart/index.md) | node or edge | node: its label, `degree` (`in` / `out` when directed, the weight sum when weighted), its `group` and `size` when given; edge: `source`, `target`, `weight` | | [Scatter Matrix](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scattermatrix/index.md) | point, bin, or density curve | as the scatter chart and histogram marks; a density curve reports the legend label (the `hue` group), `x`, and the density as `y` | | [Sankey Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/sankeychart/index.md) | node or link | node: its name, `flow`; link: `source`, `target`, `flow` | | [Treemap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/treemap/index.md) | tile or group band | its label, `value` (a band's group total) | The annotation on each chart, from the figure shown with `show(interactive=True)` and a mark hovered: | **Line Chart** — a hovered point | **Stacked Area Chart** — a hovered band | | -------------------------------- | --------------------------------------- | | | | | **Bump Chart** — a hovered period | **Bar Chart** — a hovered bar | | --------------------------------- | ----------------------------- | | | | | **Pyramid Chart** — a hovered bar | **Gantt Chart** — a hovered task bar | | --------------------------------- | ------------------------------------ | | | | | **Dumbbell Chart** — a hovered start dot | **Radial Chart** — a hovered bar | | ---------------------------------------- | -------------------------------- | | | | | **Histogram** — a hovered bin | **Box Plot** — a hovered box | | ----------------------------- | ---------------------------- | | | | | **Violin Plot** — a hovered body | **Swarm Plot** — a hovered point | | -------------------------------- | -------------------------------- | | | | | **Raincloud Plot** — a hovered box | **Ridgeline Plot** — a hovered ridge | | ---------------------------------- | ------------------------------------ | | | | | **Scatter Chart** — a hovered point | **Heatmap** — a hovered cell | | ----------------------------------- | ---------------------------- | | | | | **Calendar Heatmap** — a hovered day | **Contour Chart** — a hovered level line | | ------------------------------------ | ---------------------------------------- | | | | | **Hexbin Chart** — a hovered hexagon | **Parallel Coordinates** — a hovered row | | ------------------------------------ | ---------------------------------------- | | | | | **Network Chart** — a hovered node | **Scatter Matrix** — a hovered point | | ---------------------------------- | ------------------------------------ | | | | | **Sankey Chart** — a hovered link | **Treemap** — a hovered tile | | --------------------------------- | ---------------------------- | | | | ## Real-World Examples The examples below are the three questions the interactive view is opened for most often. Each is a snippet to run in a notebook with the `interactive` extra installed; the site cannot run them, so they are shown as code. ### Example 1: Which Country Is the Outlier? (Hover on a Grouped Scatter) A scatter of GDP per capita against life expectancy has one point far below the trend, and the chart is not the place to name every country. Group the points with `hue` so the annotation's first line is the region, keep the axis labels descriptive so the coordinates come back under readable names, and hover the stray point: it reports its region, its GDP and its life expectancy, which is enough to find it in the data. ``` from datachart.charts import ScatterChart figure = ScatterChart( data=countries, # {"x": gdp, "y": life_expectancy, "region": ...} per country hue="region", xlabel="GDP per capita (USD)", ylabel="Life expectancy (years)", scalex="log", show_legend=True, ) figure.show(interactive=True) # hovering the low point shows, for example: # Africa # GDP per capita (USD): 3,200 # Life expectancy (years): 63.1 ``` For an outlier that should stay named on the static chart too, add a `texts` note once it is identified; the [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) guide shows how. ### Example 2: Which Axis Is That Line On? (Hover in a Two-Axis Panel) A [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) that overlays a count on a rate puts one series on each value axis, and a reader who zooms in loses track of which is which. Hovering settles it: a series on the secondary axis reports its value under the panel's `ylabel_right`, one on the primary axis under `ylabel_left`. The legend label on the first line names the series. ``` from datachart.charts import BarChart, LineChart from datachart.utils import Panel figure = Panel( [ BarChart(data=monthly_orders, subtitle="Orders"), LineChart(data=monthly_return_rate, subtitle="Return rate"), ], xlabel="Month", ylabel_left="Orders", ylabel_right="Return rate (%)", show_legend=True, ) figure.show(interactive=True) # hovering the line shows: hovering a bar shows: # Return rate Orders # Month: Mar Month: Mar # Return rate (%): 4.2 Orders: 1,180 ``` ### Example 3: Is Every Cell Right? (Zoom and Hover across a Grid) A [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) of a dozen small multiples is drawn to be scanned, not read, and a cell that looks off needs checking before the figure is shared. The interactive view is the check: zoom to the cell to see it at full size, hover its marks to read the values under that cell's own labels, then step back to the whole grid. Nothing has to be redrawn, and the figure saved afterwards with `save_figure` is the unchanged static one. ``` from datachart.charts import LineChart from datachart.utils import Grid, save_figure cells = [ LineChart(data=series, title=station, xlabel="Year", ylabel="Rainfall (mm)") for station, series in rainfall_by_station.items() ] figure = Grid(cells, max_cols=4, sharey=True) figure.show(interactive=True) # zoom into a cell, hover its points save_figure(figure, "rainfall_grid.pdf") # the static figure, unchanged by the inspection ``` # API Reference # Datachart Module ## datachart `Datachart` is a data visualization package. The `datachart` package provides utilities for easier data visualization: chart functions that return a matplotlib figure, the composition of finished figures, a global style configuration with predefined themes, and the statistics behind the charts. Each module has its own reference page. ## Where to Look | I want to… | Page | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | draw a chart and see what each parameter accepts | [charts](https://eriknovak.github.io/datachart/0.10.2/references/charts/index.md), one page per chart: the function, its data record, its `style` keys, and its constants | | know the settings any chart takes, or the keys a theme holds | [typings](https://eriknovak.github.io/datachart/0.10.2/references/typings/index.md): reference lines and bands, texts, legend, emphasis rule, colorbar, and the theme style | | find the values a parameter accepts | [constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md), with a table of constants by chart | | change the look of every chart | [config](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md) to apply and adjust a theme, [themes](https://eriknovak.github.io/datachart/0.10.2/references/themes/index.md) for the predefined looks | | overlay charts, arrange them in a grid, annotate, or save | [utils](https://eriknovak.github.io/datachart/0.10.2/references/utils/index.md): `Panel`, `Grid`, `Annotate`, and `save_figure` | | compute the number a chart shows | [utils.stats](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/index.md): centers, spreads, correlations, fits, intervals, smoothers, densities | The [how-to guides](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/index.md) show the same API on real data, feature by feature. # Charts Module ## datachart.charts Module containing the `charts`. The `charts` module contains the functions that create the figures, one per chart type, grouped by the question they answer. Every function takes the data as a list of records, keyword settings, and an optional `style` dictionary, and returns a matplotlib figure. ## Charts by Family One page per chart: the function and its parameters, the shape of its data, the keys `style` takes, and the constant each parameter accepts. Pick the chart by the question it answers; the [chart guides](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/index.md) show each one on real data. ### Trends and Comparisons | Chart | Shows | Data | Style | Guide | | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | | [LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/index.md) | A value along an ordered axis, one line per series. | [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs) | [`LineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs) | [Line Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) | | [StackedAreaChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/index.md) | Parts of a total along an ordered axis, filled on top of each other. | [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs) | [`StackedAreaStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.typings.StackedAreaStyleAttrs) | [Stacked Area Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/stackedareachart/index.md) | | [BumpChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/index.md) | Rank over time, one line per series. | [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs) | [`BumpStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.typings.BumpStyleAttrs) | [Bump Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/bumpchart/index.md) | | [BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/index.md) | A value per category as bars; series grouped, stacked, or overlaid. | [`BarDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarDataPointAttrs) | [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs) | [Bar Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) | | [PyramidChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/pyramidchart/index.md) | Two series as horizontal bars mirrored around a shared category axis. | [`BarDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarDataPointAttrs) | [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs) | [Pyramid Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/pyramidchart/index.md) | | [RadialChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/index.md) | Series on polar axes, as a radar line, an area, bars, or a histogram. | [`RadialDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/#datachart.typings.RadialDataPointAttrs) | [`LineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs), [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs), [`HistStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs), [`ScatterStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs) | [Radial Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/radialchart/index.md) | | [CalendarHeatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/index.md) | One colored cell per day, weeks as columns and weekdays as rows. | [`CalendarHeatmapDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.typings.CalendarHeatmapDataAttrs) | [`CalendarHeatmapStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.typings.CalendarHeatmapStyleAttrs) | [Calendar Heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/calendarheatmap/index.md) | | [GanttChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/index.md) | A schedule: one bar per task from its start to its end over a date axis. | [`GanttTaskAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttTaskAttrs) | [`GanttStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttStyleAttrs) | [Gantt Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ganttchart/index.md) | | [DumbbellChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/index.md) | Two values per category, a dot at each and a connector between them. | [`DumbbellRecordAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.typings.DumbbellRecordAttrs) | [`DumbbellStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.typings.DumbbellStyleAttrs) | [Dumbbell Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/dumbbellchart/index.md) | ### Distributions | Chart | Shows | Data | Style | Guide | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | [Histogram](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/index.md) | The distribution of one numeric variable, binned. | [`HistDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistDataPointAttrs) | [`HistStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs) | [Histogram](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) | | [BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/index.md) | Median, quartiles, whiskers, and outliers per group. | [`BoxDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxDataPointAttrs) | [`BoxStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxStyleAttrs) | [Box Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/boxplot/index.md) | | [ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/index.md) | The density profile of each group's distribution. | [`ViolinDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinDataPointAttrs) | [`ViolinStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinStyleAttrs) | [Violin Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/violinplot/index.md) | | [SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/index.md) | Every observation as a point, spread within its group. | [`SwarmDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmDataPointAttrs) | [`SwarmStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmStyleAttrs) | [Swarm Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/swarmplot/index.md) | | [RaincloudPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/index.md) | A half violin, the raw points, and a box per group. | [`RaincloudDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.typings.RaincloudDataPointAttrs) | [`RaincloudStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.typings.RaincloudStyleAttrs) | [Raincloud Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/raincloudplot/index.md) | | [RidgelinePlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/index.md) | One density ridge per group, stacked and partly overlapping. | [`RidgelineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.typings.RidgelineDataPointAttrs) | [`RidgelineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.typings.RidgelineStyleAttrs) | [Ridgeline Plot](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ridgelineplot/index.md) | ### Relationships | Chart | Shows | Data | Style | Guide | | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | [ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/index.md) | One point per observation, placed by two numeric variables. | [`ScatterDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterDataPointAttrs) | [`ScatterStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs) | [Scatter Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md) | | [Heatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/index.md) | A two-dimensional matrix as colored cells. | [`HeatmapDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.typings.HeatmapDataAttrs) | [`HeatmapStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.typings.HeatmapStyleAttrs) | [Heatmap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/heatmap/index.md) | | [ContourChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/index.md) | A surface sampled on a grid, as iso-lines or filled bands. | [`ContourDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.typings.ContourDataAttrs) | [`ContourStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.typings.ContourStyleAttrs) | [Contour Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/contourchart/index.md) | | [HexbinChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/index.md) | Point density on the plane, as colored hexagons. | [`HexbinDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.typings.HexbinDataAttrs) | [`HexbinStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.typings.HexbinStyleAttrs) | [Hexbin Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/hexbinchart/index.md) | | [ParallelCoords](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/index.md) | Each record as a polyline across one axis per dimension. | [`ParallelCoordsDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.typings.ParallelCoordsDataPointAttrs) | [`ParallelCoordsStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.typings.ParallelCoordsStyleAttrs) | [Parallel Coordinates](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/parallelcoords/index.md) | | [NetworkChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/index.md) | Nodes joined by edges, placed by a layout. | [`NetworkSingleChartAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.typings.NetworkSingleChartAttrs) | [`NetworkStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.typings.NetworkStyleAttrs) | [Network Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/networkchart/index.md) | | [ScatterMatrix](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/index.md) | A scatter chart for every pair of dimensions, distributions on the diagonal. | [`ScatterMatrixDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/#datachart.typings.ScatterMatrixDataPointAttrs) | [`StyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.StyleAttrs) | [Scatter Matrix](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scattermatrix/index.md) | ### Flows | Chart | Shows | Data | Style | Guide | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | | [SankeyChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/index.md) | Weighted flows between categories, as ribbons between node columns. | [`SankeySingleChartAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.typings.SankeySingleChartAttrs) | [`SankeyStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.typings.SankeyStyleAttrs) | [Sankey Chart](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/sankeychart/index.md) | ### Part of a Whole | Chart | Shows | Data | Style | Guide | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | [Treemap](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/index.md) | Part-of-whole data as nested rectangles sized by value. | [`TreemapSingleChartAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.typings.TreemapSingleChartAttrs) | [`TreemapStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.typings.TreemapStyleAttrs) | [Treemap](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/treemap/index.md) | # LineChart A value along an ordered axis, one line per series. The [Line Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/linechart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.LineChart ``` LineChart( data: ( list[LineDataPointAttrs] | list[list[LineDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | datetime | None = None, xmax: int | float | datetime | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_yerr: bool | None = None, show_area: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, value_step: int | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( LineStyleAttrs | list[LineStyleAttrs | None] | None ) = None, xticks: ( list[int | float | datetime] | list[list[int | float | datetime]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, x: str | list[str | None] | None = None, y: str | list[str | None] | None = None, yerr: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the line chart. Lines connect ordered (x, y) points to show how a value changes along a continuous axis, typically time. Use it for trends, growth, and comparing the trajectories of several series on the same scale. For unordered categories use BarChart; for unconnected samples use ScatterChart. Examples: ``` >>> from datachart.charts import LineChart >>> figure = LineChart( ... data=[ ... {"x": 1, "y": 5}, ... {"x": 2, "y": 10}, ... {"x": 3, "y": 15}, ... {"x": 4, "y": 20}, ... {"x": 5, "y": 25} ... ], ... title="Basic Line Chart", ... xlabel="X", ... ylabel="Y" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the line chart(s). Can be a single list of data points for one chart, or a list of lists for multiple charts/subplots. **TYPE:** \`list[LineDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual charts, aligned like style: "background" mutes a chart (theme muted color, lowered alpha, thinner line, behind the others, no legend entry), "highlight" bolds it and brings it to the front, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the lines matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each line's own y values, chosen by by: "mean" (default), "median", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every line. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_yerr` | Whether to show y-axis error bars. **TYPE:** \`bool | | `show_area` | Whether to show the area under the line. **TYPE:** \`bool | | `show_values` | Whether to print each point's value above or below it. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `value_step` | Label every Nth point (1 labels all of them). Defaults to the smallest step that keeps neighbouring labels apart. **TYPE:** \`int | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `scalex` | The x-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the line(s). **TYPE:** \`LineStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `x` | The key name in data for x-axis values (default: "x"). **TYPE:** \`str | | `y` | The key name in data for y-axis values (default: "y"). **TYPE:** \`str | | `yerr` | The key name in data for y-axis error values (default: "yerr"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ------------------------------------- | | `plt.Figure` | The figure containing the line chart. | ## Data Each record in `data` is a [`LineDataPointAttrs`](#datachart.typings.LineDataPointAttrs); the `x`, `y` and `yerr` parameters rename its keys. ### datachart.typings.LineDataPointAttrs Bases: `TypedDict` The data point attributes for the line chart. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------------- | | `x` | The x-axis value. **TYPE:** \`int | | `y` | The y-axis value. **TYPE:** \`int | | `yerr` | The y-axis error value. **TYPE:** \`int | ## Style `style` takes the keys of [`LineStyleAttrs`](#datachart.typings.LineStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), the area fill ([`AreaStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AreaStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.LineStyleAttrs Bases: `TypedDict` The typing for the line chart style. | ATTRIBUTE | DESCRIPTION | | -------------------------- | ------------------------------------------------------------------- | | `plot_line_color` | The line color. **TYPE:** \`str | | `plot_line_alpha` | The alpha value of the line. **TYPE:** \`float | | `plot_line_style` | The line style. **TYPE:** \`LINE_STYLE | | `plot_line_marker` | The line marker. **TYPE:** \`LINE_MARKER | | `plot_line_width` | The line width. **TYPE:** \`int | | `plot_line_drawstyle` | The line draw style. **TYPE:** \`LINE_DRAW_STYLE | | `plot_line_zorder` | The zorder of the line. **TYPE:** \`int | | `plot_xticks_label_rotate` | The label rotation of the xticks in the line chart. **TYPE:** \`int | | `plot_yticks_label_rotate` | The label rotation of the yticks in the line chart. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # StackedAreaChart Parts of a total along an ordered axis, filled on top of each other. The [Stacked Area Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/stackedareachart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.StackedAreaChart ``` StackedAreaChart( data: ( list[LineDataPointAttrs] | list[list[LineDataPointAttrs]] ), *, baseline: STACKED_AREA_BASELINE | str | None = None, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | datetime | None = None, xmax: int | float | datetime | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, value_step: int | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( StackedAreaStyleAttrs | list[StackedAreaStyleAttrs | None] | None ) = None, xticks: ( list[int | float | datetime] | list[list[int | float | datetime]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, x: str | list[str | None] | None = None, y: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the stacked area chart. Stacked areas fill each series on top of the previous one along an ordered axis, so the top edge traces the total and the bands show how it splits into parts — class proportions over time, traffic by channel per year. Every series must share the same `x` values. Use it for composition that changes along an axis; for the trajectories themselves use LineChart, and for composition at a few discrete categories use BarChart with `bar_mode="stack"`. Examples: ``` >>> from datachart.charts import StackedAreaChart >>> figure = StackedAreaChart( ... data=[ ... [{"x": 1, "y": 3}, {"x": 2, "y": 4}, {"x": 3, "y": 5}], ... [{"x": 1, "y": 2}, {"x": 2, "y": 3}, {"x": 3, "y": 1}], ... ], ... subtitle=["Mobile", "Desktop"], ... title="Traffic by Device", ... xlabel="Year", ... ylabel="Visits", ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the stacked series. A single list of points draws one band; a list of lists draws one band per series, the first at the bottom. Every series must hold the same x values in the same order. **TYPE:** \`list[LineDataPointAttrs] | | `baseline` | Where the first series starts: "zero" (default), "percent" (each x normalised to 100), "sym" (centred on zero), "wiggle" or "weighted_wiggle" (streamgraph baselines). See STACKED_AREA_BASELINE. **TYPE:** \`STACKED_AREA_BASELINE | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual series. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual series, aligned like style: "background" mutes a band (theme muted color, lowered alpha, no legend entry), "highlight" brings it to the front, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the series matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each series's own y values, chosen by by: "mean" (default), "median", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every series. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_values` | Whether to print each value at the midpoint of its band. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `value_step` | Label every Nth x position (1 labels all of them). Defaults to the smallest step that keeps neighbouring labels apart. **TYPE:** \`int | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `scalex` | The x-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to draw each series unstacked in its own subplot. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the band(s). **TYPE:** \`StackedAreaStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `x` | The key name in data for x-axis values (default: "x"). **TYPE:** \`str | | `y` | The key name in data for y-axis values (default: "y"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------------- | | `plt.Figure` | The figure containing the stacked area chart. | | RAISES | DESCRIPTION | | ------------ | ----------------------------------------------------------------------------------------------- | | `ValueError` | If the series do not share the same x values, or baseline is not a STACKED_AREA_BASELINE value. | ## Data Each record in `data` is a [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs); the `x` and `y` parameters rename its keys. ## Style `style` takes the keys of [`StackedAreaStyleAttrs`](#datachart.typings.StackedAreaStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.StackedAreaStyleAttrs Bases: `TypedDict` The typing for the stacked area chart style. The fill takes the `plot_area_*` keys (color, hatch, zorder) and the outline the `plot_line_*` keys; these keys switch what is specific to a stack. | ATTRIBUTE | DESCRIPTION | | ----------------------------- | ---------------------------------------------------------------- | | `plot_stackedarea_alpha` | The alpha value of the stacked bands. **TYPE:** \`float | | `plot_stackedarea_outline` | Whether each band draws its top edge as a line. **TYPE:** \`bool | | `plot_stackedarea_edge_color` | The stroke color between the bands. **TYPE:** \`str | | `plot_stackedarea_edge_width` | The stroke width between the bands. **TYPE:** \`float | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `baseline` | [`STACKED_AREA_BASELINE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.STACKED_AREA_BASELINE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # BumpChart Rank over time, one line per series. The [Bump Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/bumpchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.BumpChart ``` BumpChart( data: ( list[LineDataPointAttrs] | list[list[LineDataPointAttrs]] ), *, rank_by: BUMP_RANK | str | None = None, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | datetime | None = None, xmax: int | float | datetime | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_labels: bool | None = None, label_position: BUMP_LABEL_POSITION | str | None = None, show_markers: bool | None = None, line_curve: float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, value_step: int | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, scalex: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( BumpStyleAttrs | list[BumpStyleAttrs | None] | None ) = None, xticks: ( list[int | float | datetime] | list[list[int | float | datetime]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, x: str | list[str | None] | None = None, y: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the bump chart. A bump chart shows rank over time: one line per series, rank 1 at the top, a marker at every period, and the series named at the line's end in place of a y-axis. Use it for league tables, popularity, or market-share rankings, where the order matters more than the gaps between values. Periods may be numbers, dates, or strings; strings draw as categories in first-seen order. For the values themselves use LineChart; for a single period's order use BarChart with `sort`. Examples: ``` >>> from datachart.charts import BumpChart >>> figure = BumpChart( ... data=[ ... [{"x": 2022, "y": 71}, {"x": 2023, "y": 64}, {"x": 2024, "y": 80}], ... [{"x": 2022, "y": 68}, {"x": 2023, "y": 75}, {"x": 2024, "y": 77}], ... [{"x": 2022, "y": 59}, {"x": 2023, "y": 70}, {"x": 2024, "y": 62}], ... ], ... subtitle=["Ljubljana", "Maribor", "Celje"], ... title="League Table", ... xlabel="Season", ... ylabel="Rank", ... ) ``` | PARAMETER | DESCRIPTION | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `data` | The data points of the series. Can be a single list of data points for one series, or a list of lists for several. **TYPE:** \`list[LineDataPointAttrs] | | `rank_by` | How y becomes a rank, a BUMP_RANK member: VALUE_DESCENDING (default) ranks the highest value first at each period, VALUE_ASCENDING the lowest, and GIVEN reads y as the rank (a positive integer). Ranking reads the series present at a period; a series without a point there leaves a gap. Ties keep input order. **TYPE:** \`BUMP_RANK | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) of the series. Used as end labels and legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual series, aligned like style: "background" mutes a series, "highlight" bolds it and brings it to the front, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the series matching it and mutes the rest, read against a summary of each series' ranks, chosen by by ("mean" by default): {"top": n} picks the n best-ranked series, {"bottom": n} the worst, and {"above": v}, {"below": v}, {"between": (lo, hi)} compare the rank number itself. An explicit emphasis role wins. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum rank shown (the top of the axis). **TYPE:** \`int | | `ymax` | The maximum rank shown (the bottom of the axis). **TYPE:** \`int | | `show_labels` | Whether to print each series' subtitle beside its line end, in the series color. Defaults to True. **TYPE:** \`bool | | `label_position` | Which line end carries the label, a BUMP_LABEL_POSITION member: START, END (default), or BOTH. **TYPE:** \`BUMP_LABEL_POSITION | | `show_markers` | Whether to draw a marker at every period. Defaults to True. **TYPE:** \`bool | | `line_curve` | How far each segment eases between two periods, in \[0, 1\]: 0 (default) draws straight segments, 1 a full sigmoid. The points never move. **TYPE:** \`float | | `show_legend` | Whether to show the legend. Defaults to on only when show_labels is off. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. A bump chart draws none unless asked: the ranks read from the lines. **TYPE:** \`SHOW_GRID | | `show_values` | Whether to print each point's original y value beside it. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `value_step` | Label every Nth point (1 labels all of them). Defaults to the smallest step that keeps neighbouring labels apart. **TYPE:** \`int | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `scalex` | The x-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each series; the ranks still read every series. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the series. See BumpStyleAttrs. **TYPE:** \`BumpStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot, at rank positions. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two ranks. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `x` | The key name in data for x-axis values (default: "x"). **TYPE:** \`str | | `y` | The key name in data for the ranked values (default: "y"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ------------------------------------- | | `plt.Figure` | The figure containing the bump chart. | ## Data Each record in `data` is a [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs); the `x` and `y` parameters rename its keys. ## Style `style` takes the keys of [`BumpStyleAttrs`](#datachart.typings.BumpStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.BumpStyleAttrs Bases: `TypedDict` The typing for the bump chart style. The line takes the `plot_line_*` keys (color, alpha, style, zorder); these keys set what is specific to a bump chart. | ATTRIBUTE | DESCRIPTION | | ------------------------- | ------------------------------------------------------------------------ | | `plot_bump_line_width` | The line width. **TYPE:** \`int | | `plot_bump_marker` | The marker at every period. **TYPE:** \`LINE_MARKER | | `plot_bump_marker_size` | The marker size. **TYPE:** \`int | | `plot_bump_label_padding` | The gap between a line end and its end label, in points. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `rank_by` | [`BUMP_RANK`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BUMP_RANK) | | `label_position` | [`BUMP_LABEL_POSITION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BUMP_LABEL_POSITION) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # BarChart A value per category as bars; series grouped, stacked, or overlaid. The [Bar Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/barchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.BarChart ``` BarChart( data: ( list[BarDataPointAttrs] | list[list[BarDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_yerr: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.VERTICAL, bar_mode: BAR_MODE | str | None = None, sort: SORT | str | None = None, sort_by: str | None = None, emphasis_rule: EmphasisRuleAttrs | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( BarStyleAttrs | list[BarStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, y: str | list[str | None] | None = None, yerr: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the bar chart. Bars compare a numeric value across discrete categories: each label gets a bar whose length encodes its value. Use it when the categories are few and unordered (or ordinal) and the question is "which is bigger, and by how much"; several series can be grouped, stacked, or overlaid via `bar_mode`. For a continuous x-axis reach for LineChart, for distributions for Histogram. Examples: ``` >>> from datachart.charts import BarChart >>> figure = BarChart( ... data=[ ... {"label": "cat1", "y": 5}, ... {"label": "cat2", "y": 10}, ... {"label": "cat3", "y": 15}, ... {"label": "cat4", "y": 20}, ... {"label": "cat5", "y": 25} ... ], ... title="Basic Bar Chart", ... xlabel="LABEL", ... ylabel="Y" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the bar chart(s). Can be a single list of data points for one chart, or a list of lists for multiple charts/subplots. **TYPE:** \`list[BarDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual charts, aligned like style: "background" mutes a chart (theme muted color, lowered alpha, behind the others, no legend entry), "highlight" bolds its edges and brings it to the front, None leaves it unchanged. See EMPHASIS. **TYPE:** \`EMPHASIS | | `figsize` | The size of the figure as (width, height) in inches. See FIG_SIZE. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show ("both", "x", "y"); False draws none. See SHOW_GRID. **TYPE:** \`SHOW_GRID | | `show_yerr` | Whether to show y-axis error bars. **TYPE:** \`bool | | `show_values` | Whether to show bar value labels at the edge of each bar. **TYPE:** \`bool | | `value_format` | Format string for bar value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `bar_mode` | How multiple bar series share the axis: "group" (side-by-side), "stack" (stacked), or "overlay" (overlapping). See BAR_MODE. **TYPE:** \`BAR_MODE | | `sort` | The order the categories are drawn in: None (input order), "ascending", or "descending" by value. One order serves every series, keyed by the total across them; ties keep input order. See SORT. **TYPE:** \`SORT | | `sort_by` | The subtitle of the one series whose values key the sort instead of the total. A category that series lacks sorts last. **TYPE:** \`str | | `emphasis_rule` | A one-key dict that highlights the bars matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}. Reads each bar's own value; a record's own emphasis key wins over the rule. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `orientation` | The orientation of the bars ("vertical" or "horizontal"). See ORIENTATION. **TYPE:** \`ORIENTATION | | `scalex` | The x-axis scale ("linear", "log", "symlog", "asinh"). Useful for horizontal bars. See SCALE. **TYPE:** \`SCALE | | `scaley` | The y-axis scale ("linear", "log", "symlog", "asinh"). Useful for vertical bars. See SCALE. **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the bar(s). **TYPE:** \`BarStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label values (default: "label"). **TYPE:** \`str | | `y` | The key name in data for y-axis values (default: "y"). **TYPE:** \`str | | `yerr` | The key name in data for y-axis error values (default: "yerr"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ------------------------------------ | | `plt.Figure` | The figure containing the bar chart. | ## Data Each record in `data` is a [`BarDataPointAttrs`](#datachart.typings.BarDataPointAttrs); the `emphasis`, `label`, `y` and `yerr` parameters rename its keys. ### datachart.typings.BarDataPointAttrs Bases: `TypedDict` The data point attributes for the bar chart. | ATTRIBUTE | DESCRIPTION | | ---------- | -------------------------------------------------------------------------------------------------------------------- | | `label` | The label. **TYPE:** `str` | | `y` | The y-axis value. **TYPE:** \`int | | `yerr` | The y-axis error value. **TYPE:** \`int | | `emphasis` | The bar's own emphasis role ("background" or "highlight"); wins over the chart's emphasis_rule. **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`BarStyleAttrs`](#datachart.typings.BarStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.BarStyleAttrs Bases: `TypedDict` The typing for the bar chart style. | ATTRIBUTE | DESCRIPTION | | -------------------------- | ------------------------------------------------------------------ | | `plot_bar_color` | The bar color. **TYPE:** \`str | | `plot_bar_alpha` | The alpha value of the bar. **TYPE:** \`float | | `plot_bar_width` | The width of the bar. **TYPE:** \`int | | `plot_bar_zorder` | The zorder of the bar. **TYPE:** \`int | | `plot_bar_hatch` | The hatch style of the bar. **TYPE:** \`HATCH_STYLE | | `plot_bar_edge_width` | The edge width of the bar. **TYPE:** \`int | | `plot_bar_edge_color` | The edge color of the bar. **TYPE:** \`str | | `plot_bar_error_color` | The color of the error line of the bar. **TYPE:** \`str | | `plot_bar_value_fontsize` | Alias of plot_value_fontsize. **TYPE:** \`int | | `plot_bar_value_color` | Alias of plot_value_color. **TYPE:** \`str | | `plot_bar_value_padding` | Alias of plot_value_padding. **TYPE:** \`int | | `plot_xticks_label_rotate` | The label rotation of the xticks in the bar chart. **TYPE:** \`int | | `plot_yticks_label_rotate` | The label rotation of the yticks in the bar chart. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `bar_mode` | [`BAR_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # PyramidChart Two series as horizontal bars mirrored around a shared category axis. The [Pyramid Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/pyramidchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.PyramidChart ``` PyramidChart( data: list[list[BarDataPointAttrs]], *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_yerr: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, sort: SORT | str | None = None, sort_by: str | None = None, emphasis_rule: EmphasisRuleAttrs | None = None, style: ( BarStyleAttrs | list[BarStyleAttrs | None] | None ) = None, xticks: list[int | float] | None = None, xticklabels: list[str] | None = None, xtickrotate: int | None = None, yticks: list[int | float] | None = None, yticklabels: list[str] | None = None, ytickrotate: int | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | None ) = None, label: str | list[str | None] | None = None, y: str | list[str | None] | None = None, yerr: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the pyramid chart. A pyramid chart draws exactly two series as horizontal bars mirrored around a shared category axis, the first series to the left and the second to the right: the classic age-sex population pyramid. Use it to compare the distribution of two groups over the same ordered categories, such as age bands, where the symmetry (or lack of it) is the message. Both series are supplied as positive values; value ticks and labels show absolute values. Unlike the other chart fronts, the axis parameters are spatial: `xlabel`, `xticks`, and `xmax` address the horizontal value axis, and `ylabel` the vertical category axis. Examples: ``` >>> from datachart.charts import PyramidChart >>> figure = PyramidChart( ... data=[ ... [ ... {"label": "0-14", "y": 12}, ... {"label": "15-29", "y": 18}, ... {"label": "30-44", "y": 22}, ... ], ... [ ... {"label": "0-14", "y": 11}, ... {"label": "15-29", "y": 19}, ... {"label": "30-44", "y": 24}, ... ], ... ], ... subtitle=["Group A", "Group B"], ... title="Basic Pyramid Chart", ... show_legend=True, ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | Exactly two lists of data points — the first is the left side, the second the right. Values are positive for both sides; the chart mirrors the left side itself. **TYPE:** `list[list[BarDataPointAttrs]]` | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The label of the horizontal value axis. **TYPE:** \`str | | `ylabel` | The label of the vertical category axis. **TYPE:** \`str | | `subtitle` | The names of the two sides. Used as legend labels. **TYPE:** \`str | | `figsize` | The size of the figure as (width, height) in inches. See FIG_SIZE. **TYPE:** \`FIG_SIZE | | `xmin` | Not supported; the value axis is always symmetric around zero. Raises when passed. **TYPE:** \`int | | `xmax` | The maximum per-side value; the value axis spans (-xmax, xmax). **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show ("both", "x", "y"); False draws none. See SHOW_GRID. **TYPE:** \`SHOW_GRID | | `show_yerr` | Whether to show error bars on the bars. **TYPE:** \`bool | | `show_values` | Whether to show bar value labels at the edge of each bar. **TYPE:** \`bool | | `value_format` | Format string for bar value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `sort` | The order the categories are drawn in: None (input order), "ascending", or "descending" by value. One order serves both sides, keyed by the total of the two; ties keep input order. See SORT. **TYPE:** \`SORT | | `sort_by` | The subtitle of the one side whose values key the sort instead of the total. A category that side lacks sorts last. **TYPE:** \`str | | `emphasis_rule` | A one-key dict that highlights the bars matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}. Reads each bar's positive value; a record's own emphasis key wins over the rule. **TYPE:** \`EmphasisRuleAttrs | | `style` | Style configuration(s) for the bars, per side. **TYPE:** \`BarStyleAttrs | | `xticks` | Custom value-axis tick positions, as positive values; each is mirrored to both halves. **TYPE:** \`list\[int | | `xticklabels` | Custom value-axis tick labels (same length as xticks), applied to both mirrored halves. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for value-axis tick labels. **TYPE:** \`int | | `yticks` | Custom category-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom category-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for category-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label values (default: "label"). **TYPE:** \`str | | `y` | The key name in data for the bar values (default: "y"). **TYPE:** \`str | | `yerr` | The key name in data for the bar error values (default: "yerr"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------------- | | `plt.Figure` | The figure containing the pyramid chart. | ## Data Each record in `data` is a [`BarDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarDataPointAttrs); the `label`, `y` and `yerr` parameters rename its keys. ## Style `style` takes the keys of [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # RadialChart Series on polar axes, as a radar line, an area, bars, or a histogram. The [Radial Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/radialchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.RadialChart ``` RadialChart( data: ( list[RadialDataPointAttrs] | list[list[RadialDataPointAttrs]] ), *, type: RADIAL_TYPE | str | None = None, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, figsize: FIG_SIZE | tuple[float, float] | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_yerr: bool | None = None, show_area: bool | None = None, show_values: bool | None = None, show_tip_labels: bool | None = None, show_border: bool | None = None, value_format: str | None = None, bar_mode: BAR_MODE | str | None = None, sort: SORT | str | None = None, sort_by: str | None = None, emphasis_rule: EmphasisRuleAttrs | None = None, num_bins: int | None = None, startangle: str | int | float | None = None, direction: RADIAL_DIRECTION | str | None = None, innerradius: float | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( _RadialStyleAttrs | list[_RadialStyleAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, vlines: dict | None = None, hlines: dict | None = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, x: str | list[str | None] | None = None, y: str | list[str | None] | None = None, yerr: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the radial chart. A radial chart plots series on polar axes: as a line (radar) profile, an area, bars, or a histogram, chosen with `type`. Use the radar form to compare a few entities across several metrics on a shared scale, and the bar and histogram forms for cyclic categories such as hours, weekdays, or compass directions. Examples: ``` >>> from datachart.charts import RadialChart >>> figure = RadialChart( ... data=[ ... {"label": "N", "y": 5}, ... {"label": "E", "y": 10}, ... {"label": "S", "y": 15}, ... {"label": "W", "y": 20} ... ], ... title="Basic Radial Chart" ... ) ``` | PARAMETER | DESCRIPTION | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the radial chart(s). Can be a single list of data points for one chart, or a list of lists for multiple charts/subplots. The line, bar, and scatter visuals take label/y points whose labels are placed evenly around the circle; the histogram visual takes numeric x observations in degrees, binned over \[0, 360). **TYPE:** \`list[RadialDataPointAttrs] | | `type` | The visual the whole figure draws: "line" (default), "bar", "scatter", or "histogram". See RADIAL_TYPE. **TYPE:** \`RADIAL_TYPE | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The angular-axis label. **TYPE:** \`str | | `ylabel` | The radial-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual charts, aligned like style: "background" mutes a chart, "highlight" bolds it, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `ymin` | The minimum radial-axis value. **TYPE:** \`int | | `ymax` | The maximum radial-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to draw: "x" the spokes only, "y" the rings only, "both" both, False neither. Left unset, spokes and rings are both drawn and the theme's own choice is the one drawn in the faint grid color; the other stays a darker grey. **TYPE:** \`SHOW_GRID | | `show_yerr` | Whether to show the radial error band (line visual). **TYPE:** \`bool | | `show_area` | Whether to fill the area inside the line (line visual). **TYPE:** \`bool | | `show_values` | Whether to write each mark's value at its tip, rotated along the spoke. **TYPE:** \`bool | | `show_tip_labels` | Whether to write the category labels at the mark tips, rotated along their spokes, instead of around the circle. **TYPE:** \`bool | | `show_border` | Whether to draw the outer border circle. Defaults to the theme's spine visibility; False hides it. **TYPE:** \`bool | | `value_format` | Format for the values written by show_values — a printf format (e.g. "%.1f") or a {x}-style string. See VALUE_FORMAT. **TYPE:** \`str | | `bar_mode` | How multiple bar series share the circle: "group", "stack", or "overlay" (bar visual). See BAR_MODE. **TYPE:** \`BAR_MODE | | `sort` | The order the categories are drawn in around the circle: None (input order), "ascending", or "descending" by value (bar visual). One order serves every series, keyed by the total across them; ties keep input order. See SORT. **TYPE:** \`SORT | | `sort_by` | The subtitle of the one series whose values key the sort instead of the total (bar visual). A category that series lacks sorts last. **TYPE:** \`str | | `emphasis_rule` | A one-key dict that highlights the bars matching it and mutes the rest (bar visual): {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}. Reads each bar's own value; a record's own emphasis key wins over the rule. **TYPE:** \`EmphasisRuleAttrs | | `num_bins` | The number of angular bins over \[0, 360) (histogram visual). **TYPE:** \`int | | `startangle` | Where the first point sits: a compass location ("N", "NE", "E", "SE", "S", "SW", "W", "NW") or a numeric compass bearing in degrees clockwise from north. Defaults to "N". **TYPE:** \`str | | `direction` | Which way the angles increase: "clockwise" (default) or "counterclockwise". See RADIAL_DIRECTION. **TYPE:** \`RADIAL_DIRECTION | | `innerradius` | The donut hole, as a fraction (0 \<= f < 1) of the radial extent. Defaults to 0. **TYPE:** \`float | | `scalex` | Not supported; the angular axis has no scale. Raises when passed. **TYPE:** \`SCALE | | `scaley` | The radial-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate polar subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the angular axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the radial axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the chart(s); radial visuals obey the matching cartesian style family (plot_line\_\*, plot_bar\_\*, plot_hist\_\*, plot_scatter\_\*). **TYPE:** \`\_RadialStyleAttrs | | `texts` | Text annotation(s) to draw. On the polar axes, data coordinates are (angle in radians, radius); axes-fraction coordinates ("coords": "axes") are often easier. **TYPE:** \`TextSettingAttrs | | `vlines` | Not supported on a polar axes. Raises when passed. **TYPE:** \`dict | | `hlines` | Not supported on a polar axes. Raises when passed. **TYPE:** \`dict | | `vspans` | Angular wedge(s) to shade over the full radius; xmin and xmax are angles in degrees from the start angle, an omitted bound running to 0 or 360. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Annulus (annuli) to shade over the full circle; ymin and ymax are radial values, an omitted bound running to the radial limit. **TYPE:** \`HSpanSettingAttrs | | `label` | The key name in data for the category labels (default: "label"). **TYPE:** \`str | | `x` | The key name in data for the histogram observations (default: "x"). **TYPE:** \`str | | `y` | The key name in data for radial values (default: "y"). **TYPE:** \`str | | `yerr` | The key name in data for radial error values (default: "yerr"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------- | | `plt.Figure` | The figure containing the radial chart. | ## Data Each record in `data` is a [`RadialDataPointAttrs`](#datachart.typings.RadialDataPointAttrs); the `label`, `x`, `y` and `yerr` parameters rename its keys. ### datachart.typings.RadialDataPointAttrs Bases: `TypedDict` The data point attributes for the radial chart. The line, bar, and scatter visuals take `label`/`y` points whose labels are placed evenly around the circle; the histogram visual takes numeric `x` observations in degrees. | ATTRIBUTE | DESCRIPTION | | ---------- | --------------------------------------------------------------------------------------------------------------------------------- | | `label` | The category label (line, bar, and scatter visuals). **TYPE:** `str` | | `y` | The radial value (line, bar, and scatter visuals). **TYPE:** \`int | | `yerr` | The radial error value. **TYPE:** \`int | | `x` | The angular observation in degrees (histogram visual). **TYPE:** \`int | | `emphasis` | The bar's own emphasis role ("background" or "highlight"); wins over the chart's emphasis_rule (bar visual). **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`LineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs), [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs), [`HistStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs) and [`ScatterStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), the area fill ([`AreaStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.AreaStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | [`RADIAL_TYPE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RADIAL_TYPE) | | `direction` | [`RADIAL_DIRECTION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RADIAL_DIRECTION) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `bar_mode` | [`BAR_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | # CalendarHeatmap One colored cell per day, weeks as columns and weekdays as rows. The [Calendar Heatmap guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/calendarheatmap/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.CalendarHeatmap ``` CalendarHeatmap( data: ( CalendarHeatmapDataAttrs | list[CalendarHeatmapDataAttrs] ), *, title: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: None = None, year: int | None = None, week_start: CALENDAR_WEEKDAY | str | None = None, show_month_labels: bool | None = None, show_weekday_labels: bool | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, max_cols: int | None = None, show_colorbars: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, style: ( CalendarHeatmapStyleAttrs | list[CalendarHeatmapStyleAttrs | None] | None ) = None, norm: str | list[str | None] | None = None, vmin: float | list[float | None] | None = None, vmax: float | list[float | None] | None = None, colorbar: ( ColorbarSettingAttrs | list[ColorbarSettingAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the calendar heatmap. A calendar heatmap draws one cell per day, the weeks as columns and the weekdays as rows, colored by the day's value, with the months separated and labelled. Use it when a daily series has a weekly or seasonal rhythm worth seeing at a glance: commits, sales, steps, rainfall. Data spanning several years draws one calendar per year; for a matrix that is not a calendar, use Heatmap. Examples: ``` >>> from datetime import date, timedelta >>> from datachart.charts import CalendarHeatmap >>> days = [date(2024, 1, 1) + timedelta(days=i) for i in range(366)] >>> figure = CalendarHeatmap( ... data={"date": days, "value": [i % 7 for i in range(366)]}, ... title="Daily values, 2024", ... show_colorbars=True, ... ) ``` | PARAMETER | DESCRIPTION | | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The dated values: a {"date": [...], "value": [...]} dict, or a list of such dicts drawing one calendar per dataset. Dates are temporal objects (date, datetime, numpy.datetime64, or pandas Timestamp), each appearing once; date strings are never parsed. A calendar spans the months that hold data, whole months at a time; a day absent from the list, or valued None, is drawn blank. **TYPE:** \`CalendarHeatmapDataAttrs | | `title` | The title of the chart. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual datasets. A dataset that spans several years names each calendar by its year, after the subtitle when one is given. **TYPE:** \`str | | `emphasis` | Not supported: a calendar is a single raster layer with no series to mute or highlight. Passing a value raises ValueError. **TYPE:** `None` **DEFAULT:** `None` | | `year` | The one year to draw, colored over its own values. Without it every year the dates span is drawn, one calendar per year in year order, sharing one value range so the colors compare across years. **TYPE:** \`int | | `week_start` | The weekday of the top row: CALENDAR_WEEKDAY.MONDAY or CALENDAR_WEEKDAY.SUNDAY. Defaults to the theme's plot_calendar_heatmap_week_start. **TYPE:** \`CALENDAR_WEEKDAY | | `show_month_labels` | Whether to label the months along the bottom axis, each over the middle of its weeks. Defaults to True. **TYPE:** \`bool | | `show_weekday_labels` | Whether to label every other weekday along the left axis. Defaults to True. **TYPE:** \`bool | | `figsize` | The size of the figure. Defaults to the default width at a short height per row of calendars, the shape a calendar fills. **TYPE:** \`FIG_SIZE | | `aspect_ratio` | The aspect ratio of the cells: ASPECT_RATIO.EQUAL (the default) keeps them square, ASPECT_RATIO.AUTO stretches them to the figure. See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `max_cols` | Maximum number of calendars per row when several are drawn. Defaults to 1, one calendar per row. **TYPE:** \`int | | `show_colorbars` | Whether to show the colorbar(s). **TYPE:** \`bool | | `show_values` | Whether to write each day's value into its cell. **TYPE:** \`bool | | `value_format` | The format of the cell values: a VALUE_FORMAT constant (default VALUE_FORMAT.DEFAULT) or any "{x:.1f}", "{:.1f}", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `style` | Style configuration(s) for the calendar(s). **TYPE:** \`CalendarHeatmapStyleAttrs | | `norm` | Value normalization method(s). **TYPE:** \`str | | `vmin` | Minimum value(s) for normalization. **TYPE:** \`float | | `vmax` | Maximum value(s) for normalization. **TYPE:** \`float | | `colorbar` | The colorbar setting(s): label, location, tick format, and tick positions. See ColorbarSettingAttrs. **TYPE:** \`ColorbarSettingAttrs | | `texts` | Text annotation(s) to draw, on every calendar of their dataset. The cells sit at integer positions: the week column along x, the weekday row along y, counted from zero at the top-left cell of the drawn range. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------------------- | | `plt.Figure` | The figure containing the calendar heatmap(s). | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If emphasis is given, the data is not a dated-values dict, a date is not a temporal object, a date appears twice, year names a year without data, or week_start is not a CALENDAR_WEEKDAY member. | ## Data Each record in `data` is a [`CalendarHeatmapDataAttrs`](#datachart.typings.CalendarHeatmapDataAttrs). ### datachart.typings.CalendarHeatmapDataAttrs Bases: `TypedDict` The data attributes for the calendar heatmap. | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `date` | One temporal object per day: a date, datetime, numpy.datetime64, or pandas Timestamp. Date strings are never parsed, and every date appears once. **TYPE:** \`list\[date | | `value` | The value of each day, one per date; None leaves the day blank. **TYPE:** \`list\[int | ## Style `style` takes the keys of [`CalendarHeatmapStyleAttrs`](#datachart.typings.CalendarHeatmapStyleAttrs). The chart also reads the shared groups it draws: text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.CalendarHeatmapStyleAttrs Bases: `TypedDict` The typing for the calendar heatmap style. | ATTRIBUTE | DESCRIPTION | | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_calendar_heatmap_cmap` | The colormap of the day cells (palette name, single color, list of hex colors, or colormap); None takes the heatmap colormap. **TYPE:** \`str | | `plot_calendar_heatmap_alpha` | The alpha value of the day cells. **TYPE:** \`float | | `plot_calendar_heatmap_font_size` | The font size of the cell values. **TYPE:** \`int | | `plot_calendar_heatmap_font_color` | The font color of the cell values. **TYPE:** \`str | | `plot_calendar_heatmap_font_style` | The font style of the cell values. **TYPE:** \`FONT_STYLE | | `plot_calendar_heatmap_font_weight` | The font weight of the cell values. **TYPE:** \`FONT_WEIGHT | | `plot_calendar_heatmap_edge_width` | The width of the borders drawn between the day cells (0 draws none). **TYPE:** \`int | | `plot_calendar_heatmap_edge_color` | The color of the borders drawn between the day cells. **TYPE:** \`str | | `plot_calendar_heatmap_month_line_width` | The width of the separators drawn between months (0 draws none). **TYPE:** \`int | | `plot_calendar_heatmap_month_line_color` | The color of the separators drawn between months; None takes the heatmap frame color. **TYPE:** \`str | | `plot_calendar_heatmap_week_start` | The weekday in the top row of every week, the default of week_start. **TYPE:** \`CALENDAR_WEEKDAY | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `week_start` | [`CALENDAR_WEEKDAY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.CALENDAR_WEEKDAY) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | # GanttChart A schedule: one bar per task from its start to its end over a date axis. The [Gantt Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ganttchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.GanttChart ``` GanttChart( data: list[GanttTaskAttrs] | list[list[GanttTaskAttrs]], *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: date | datetime | None = None, xmax: date | datetime | None = None, max_cols: int | None = None, period: GANTT_DATE_PERIOD | str | None = None, show_group_headers: bool | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_values: GANTT_VALUE | str | None = None, value_format: VALUE_FORMAT | str | None = None, show_dependencies: bool | None = None, show_today: bool | None = None, today: date | datetime | None = None, today_label: str | None = None, sort: SORT | str | None = None, sort_by: GANTT_SORT_KEY | str | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, style: ( GanttStyleAttrs | list[GanttStyleAttrs | None] | None ) = None, xtickrotate: int | None = None, ytickrotate: int | None = None, xticks_format: DATE_FORMAT | str | None = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | None ) = None ) -> plt.Figure ``` Creates the gantt chart. A gantt chart shows a schedule: one horizontal bar per task from its start to its end over a date axis, one row per task, the first task at the top. Use it for project plans, release roadmaps, or any set of activities where when and how long matter more than a single value. Task groups share a colour or get header rows with summary bars, an optional progress fraction fills part of each bar, a task ending when it starts is a milestone marker, dependency arrows link tasks, the date axis can be divided into calendar periods, and a today line marks the present. The chart is always horizontal, so the axis parameters are spatial: `xlabel`, `xmin`, `xmax`, and `xticks_format` address the horizontal date axis, and `ylabel` the vertical task axis. It composes in `Grid`, but not in `Panel`. Examples: ``` >>> from datetime import date >>> from datachart.charts import GanttChart >>> figure = GanttChart( ... data=[ ... {"task": "Design", "start": date(2024, 1, 1), "end": date(2024, 1, 12), ... "group": "Plan", "progress": 1.0}, ... {"task": "Build", "start": date(2024, 1, 10), "end": date(2024, 2, 9), ... "group": "Make", "progress": 0.4, "depends_on": ["Design"]}, ... {"task": "Test", "start": date(2024, 2, 5), "end": date(2024, 2, 23), ... "group": "Make", "depends_on": ["Build"]}, ... ], ... title="Release Plan", ... show_dependencies=True, ... ) ``` | PARAMETER | DESCRIPTION | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `data` | The task records of the schedule: a list of {task, start, end} dicts with optional group, progress, depends_on, and emphasis keys. start and end are date, datetime, numpy.datetime64, or pandas Timestamp objects; date strings are never parsed. A list of such lists draws one schedule per subplot. See GanttTaskAttrs. **TYPE:** \`list[GanttTaskAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The label of the horizontal date axis. **TYPE:** \`str | | `ylabel` | The label of the vertical task axis. **TYPE:** \`str | | `subtitle` | The subtitle of each schedule. **TYPE:** \`str | | `figsize` | The size of the figure as (width, height) in inches. See FIG_SIZE. **TYPE:** \`FIG_SIZE | | `xmin` | The start of the date window, as a temporal object. **TYPE:** \`date | | `xmax` | The end of the date window, as a temporal object. **TYPE:** \`date | | `max_cols` | The maximum number of subplot columns for several schedules. **TYPE:** \`int | | `period` | The calendar period the date axis is divided into: None (concise date ticks), "day", "week", "month", "quarter", "year", or "project_month" (M1, M2, … from xmin or the earliest start). Lines mark the period edges, each period is labelled at its centre, and a row beneath names the enclosing month or year. xticks_format sets the period labels. See GANTT_DATE_PERIOD. **TYPE:** \`GANTT_DATE_PERIOD | | `show_group_headers` | Whether to give each task group a header row with a summary bar from its first start to its last end, the group's rows clustered beneath it and a gap before the next group. Raises when no task carries a group. **TYPE:** \`bool | | `show_legend` | Whether to show the legend of the task groups. Defaults to on when any task carries a group and the group headers are off. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show ("both", "x", "y"); False draws none. See SHOW_GRID. **TYPE:** \`SHOW_GRID | | `show_values` | The label printed past each bar end: None (none), "duration" (the duration in days), or "progress" (the progress as a percentage). A milestone prints its date instead, in the xticks_format or as day and month. See GANTT_VALUE. **TYPE:** \`GANTT_VALUE | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. It formats the duration in days, or the progress fraction. **TYPE:** \`VALUE_FORMAT | | `show_dependencies` | Whether to draw an arrow from the end of each task named in depends_on to the start of the task depending on it. **TYPE:** \`bool | | `show_today` | Whether to draw the today line. **TYPE:** \`bool | | `today` | The date of the today line; the current date when not given. **TYPE:** \`date | | `today_label` | The text printed at the foot of the today line; none when not given. **TYPE:** \`str | | `sort` | The order of the task rows: None (input order), "ascending", or "descending" by the key sort_by names. Ties keep input order. See SORT. **TYPE:** \`SORT | | `sort_by` | The key sort orders by: "start" (default) orders every row by its start; "group" clusters the rows by group, the groups ordered by their earliest start and the tasks within a group by start. Requires sort. See GANTT_SORT_KEY. **TYPE:** \`GANTT_SORT_KEY | | `emphasis` | The emphasis role of the whole schedule ("background" or "highlight"), or one role per schedule. See EMPHASIS. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A one-key dict that highlights the tasks matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}. Reads each task's duration in days; a task's own emphasis key wins over the rule. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `style` | Style configuration(s) for the schedule. See GanttStyleAttrs. **TYPE:** \`GanttStyleAttrs | | `xtickrotate` | Rotation angle for the date-axis tick labels. **TYPE:** \`int | | `ytickrotate` | Rotation angle for the task-axis tick labels. **TYPE:** \`int | | `xticks_format` | The date-axis tick label format: a DATE_FORMAT member or strftime pattern. **TYPE:** \`DATE_FORMAT | | `vlines` | Vertical line(s) to plot, at temporal x positions. **TYPE:** \`VLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two temporal positions. **TYPE:** \`VSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | -------------------------------------- | | `plt.Figure` | The figure containing the gantt chart. | ## Data Each record in `data` is a [`GanttTaskAttrs`](#datachart.typings.GanttTaskAttrs); the `emphasis` parameter renames its keys. ### datachart.typings.GanttTaskAttrs Bases: `TypedDict` The task record attributes for the gantt chart. | ATTRIBUTE | DESCRIPTION | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | `task` | The task name, unique within the chart; the label of its row. **TYPE:** `str` | | `start` | When the task starts: a date, datetime, numpy.datetime64, or pandas Timestamp. Date strings are never parsed. **TYPE:** \`date | | `end` | When the task ends, of the same temporal types; never before start. A task ending when it starts is a milestone, drawn as a marker. **TYPE:** \`date | | `group` | The task group; tasks of one group share a color and a legend entry. **TYPE:** \`str | | `progress` | The fraction of the task done, in [0, 1]; drawn as an inner bar. **TYPE:** \`int | | `depends_on` | The names of the tasks this task depends on. **TYPE:** \`list[str] | | `emphasis` | The task's own emphasis role ("background" or "highlight"); wins over the chart's emphasis_rule. **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`GanttStyleAttrs`](#datachart.typings.GanttStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.GanttStyleAttrs Bases: `TypedDict` The typing for the gantt chart style. The range bars take the `plot_bar_*` keys (color, alpha, edge, hatch, zorder); these keys set what is specific to a gantt chart. | ATTRIBUTE | DESCRIPTION | | ------------------------------ | --------------------------------------------------------------------------------------------------------- | | `plot_gantt_bar_height` | The height of a task bar, as a fraction of its row. **TYPE:** \`int | | `plot_gantt_progress_color` | The color of the progress bar; None darkens the task bar's color. **TYPE:** \`str | | `plot_gantt_progress_alpha` | The alpha value of the progress bar. **TYPE:** \`float | | `plot_gantt_progress_height` | The height of the progress bar, as a fraction of the task bar. **TYPE:** \`int | | `plot_gantt_dependency_color` | The color of the dependency arrows. **TYPE:** \`str | | `plot_gantt_dependency_width` | The line width of the dependency arrows. **TYPE:** \`int | | `plot_gantt_dependency_style` | The arrow head of the dependency arrows, as a matplotlib arrow style. **TYPE:** \`str | | `plot_gantt_dependency_zorder` | The zorder of the dependency arrows. **TYPE:** \`int | | `plot_gantt_dependency_entry` | The side of the dependent task a dependency arrow enters ("top" or "left"). **TYPE:** \`GANTT_ARROW_ENTRY | | `plot_gantt_summary_height` | The height of a group's summary bar under show_group_headers, as a fraction of its row. **TYPE:** \`int | | `plot_gantt_summary_color` | The color of the summary bars; None takes each group's color. **TYPE:** \`str | | `plot_gantt_group_gap` | The empty space before each group header, in rows. **TYPE:** \`int | | `plot_gantt_milestone_marker` | The marker of a milestone, a task whose start equals its end. **TYPE:** \`LINE_MARKER | | `plot_gantt_milestone_size` | The size of the milestone marker, in points. **TYPE:** \`int | | `plot_gantt_today_color` | The color of the today line. **TYPE:** \`str | | `plot_gantt_today_style` | The line style of the today line. **TYPE:** \`LINE_STYLE | | `plot_gantt_today_width` | The line width of the today line. **TYPE:** \`int | | `plot_gantt_today_alpha` | The alpha value of the today line. **TYPE:** \`float | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `period` | [`GANTT_DATE_PERIOD`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_DATE_PERIOD) | | `show_values` | [`GANTT_VALUE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_VALUE) | | `sort_by` | [`GANTT_SORT_KEY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_SORT_KEY) | | `style={"plot_gantt_dependency_entry": ...}` | [`GANTT_ARROW_ENTRY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.GANTT_ARROW_ENTRY) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `xticks_format` | [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # DumbbellChart Two values per category, a dot at each and a connector between them. The [Dumbbell Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/dumbbellchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.DumbbellChart ``` DumbbellChart( data: ( list[DumbbellRecordAttrs] | list[list[DumbbellRecordAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, start_name: str | None = None, end_name: str | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.HORIZONTAL, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_values: DUMBBELL_VALUE | str | None = None, show_direction: bool | None = None, value_format: VALUE_FORMAT | str | None = None, sort: SORT | str | None = None, sort_by: DUMBBELL_SORT_KEY | str | None = None, marker: ( tuple[LINE_MARKER | str, LINE_MARKER | str] | None ) = None, connector_style: LINE_STYLE | str | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, style: ( DumbbellStyleAttrs | list[DumbbellStyleAttrs | None] | None ) = None, xtickrotate: int | list[int | None] | None = None, ytickrotate: int | list[int | None] | None = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | None ) = None ) -> plt.Figure ``` Creates the dumbbell chart. A dumbbell chart shows two values per category: a dot at `start`, a dot at `end`, and a connector between them. Use it for a change between two states (before and after, one year and the next) or for a range (a minimum and a maximum), when the gap matters as much as either value. Rows read top to bottom in input order, or sorted by start, end, or the delta `end - start`; the endpoints print their values, or the delta prints at the connector midpoint, and an optional thin arrow shows which way each value moved. The rows sit on the category index the box, violin and swarm plots share, so the chart composes with them and with other dumbbell charts in `Panel`, and in `Grid`. Several data lists overlay in distinct colors, each start dot a lighter shade of its end dot. Examples: ``` >>> from datachart.charts import DumbbellChart >>> figure = DumbbellChart( ... data=[ ... {"label": "Norway", "start": 79.8, "end": 83.2}, ... {"label": "Chile", "start": 77.1, "end": 81.2}, ... {"label": "India", "start": 62.5, "end": 70.9}, ... ], ... title="Life Expectancy", ... start_name="2000", ... end_name="2019", ... show_values="delta", ... ) ``` | PARAMETER | DESCRIPTION | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The records of the chart: a list of {label, start, end} dicts with an optional emphasis key. label names the category and is unique within the list; start and end are finite numbers. A list of such lists overlays several charts, or draws one per subplot with subplots. See DumbbellRecordAttrs. **TYPE:** \`list[DumbbellRecordAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The label of the horizontal axis. **TYPE:** \`str | | `ylabel` | The label of the vertical axis. **TYPE:** \`str | | `subtitle` | The subtitle of each chart; the legend label of an overlaid chart. **TYPE:** \`str | | `start_name` | The name of the start endpoint, shown in the legend. **TYPE:** \`str | | `end_name` | The name of the end endpoint, shown in the legend. **TYPE:** \`str | | `figsize` | The size of the figure as (width, height) in inches. See FIG_SIZE. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum value of the x-axis. **TYPE:** \`int | | `xmax` | The maximum value of the x-axis. **TYPE:** \`int | | `ymin` | The minimum value of the y-axis. **TYPE:** \`int | | `ymax` | The maximum value of the y-axis. **TYPE:** \`int | | `orientation` | Which axis the values run along: "horizontal" (default, one row per category, the first at the top) or "vertical" (one column per category). See ORIENTATION. **TYPE:** \`ORIENTATION | | `scaley` | The scale of the value axis ("linear", "log", "symlog", "logit"), whichever way it runs, as on the box plot. See SCALE. **TYPE:** \`SCALE | | `subplots` | Whether to draw each data list in its own subplot. **TYPE:** \`bool | | `max_cols` | The maximum number of subplot columns. **TYPE:** \`int | | `sharex` | Whether the subplots share the x-axis. **TYPE:** \`bool | | `sharey` | Whether the subplots share the y-axis. **TYPE:** \`bool | | `show_legend` | Whether to show the legend. Defaults to on when start_name or end_name is given. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show ("both", "x", "y"); False draws none. Unset, the theme's grid runs along the value axis, whichever way it points. See SHOW_GRID. **TYPE:** \`SHOW_GRID | | `show_values` | The value labels: None (none), "endpoints" (each endpoint's value past its dot, away from the connector), or "delta" (end - start at the connector midpoint). See DUMBBELL_VALUE. **TYPE:** \`DUMBBELL_VALUE | | `show_direction` | Whether to draw a thin arrow beside each connector, pointing from start to end: above a horizontal dumbbell, right of a vertical one. Records whose endpoints coincide draw none. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:+.1f}", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `sort` | The order of the categories: None (input order), "ascending", or "descending" by the key sort_by names. Ties keep input order. See SORT. **TYPE:** \`SORT | | `sort_by` | The key sort orders by: "start" (default), "end", or "delta". Requires sort. See DUMBBELL_SORT_KEY. **TYPE:** \`DUMBBELL_SORT_KEY | | `marker` | The (start, end) marker pair of the dots; a chart style sets them per chart. See LINE_MARKER. **TYPE:** \`tuple\[LINE_MARKER | | `connector_style` | The line style of the connectors; a chart style sets it per chart. See LINE_STYLE. **TYPE:** \`LINE_STYLE | | `emphasis` | The emphasis role of each chart ("background" or "highlight"), or one role per chart. See EMPHASIS. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A one-key dict that highlights the records matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}. Reads each record's delta; a record's own emphasis key wins over the rule. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `style` | Style configuration(s) for each chart. See DumbbellStyleAttrs. **TYPE:** \`DumbbellStyleAttrs | | `xtickrotate` | Rotation angle for the x-axis tick labels. **TYPE:** \`int | | `ytickrotate` | Rotation angle for the y-axis tick labels. **TYPE:** \`int | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ----------------------------------------- | | `plt.Figure` | The figure containing the dumbbell chart. | ## Data Each record in `data` is a [`DumbbellRecordAttrs`](#datachart.typings.DumbbellRecordAttrs); the `emphasis` parameter renames its keys. ### datachart.typings.DumbbellRecordAttrs Bases: `TypedDict` The record attributes for the dumbbell chart. | ATTRIBUTE | DESCRIPTION | | ---------- | ----------------------------------------------------------------------------------------------------------------------- | | `label` | The category, unique within the chart; the label of its row (or column). **TYPE:** `str` | | `start` | The value of the start endpoint. **TYPE:** \`int | | `end` | The value of the end endpoint. **TYPE:** \`int | | `emphasis` | The record's own emphasis role ("background" or "highlight"); wins over the chart's emphasis_rule. **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`DumbbellStyleAttrs`](#datachart.typings.DumbbellStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.DumbbellStyleAttrs Bases: `TypedDict` The typing for the dumbbell chart style. The value labels take the shared `plot_value_*` keys. | ATTRIBUTE | DESCRIPTION | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_dumbbell_start_color` | The color of the start dots; None takes the first color of the PaperAccent pair. **TYPE:** \`str | | `plot_dumbbell_end_color` | The color of the end dots; None takes the second color of the PaperAccent pair. **TYPE:** \`str | | `plot_dumbbell_alpha` | The alpha value of the dots. **TYPE:** \`float | | `plot_dumbbell_size` | The size of the dots, in points squared. **TYPE:** \`int | | `plot_dumbbell_start_marker` | The marker of the start dots. **TYPE:** \`LINE_MARKER | | `plot_dumbbell_end_marker` | The marker of the end dots. **TYPE:** \`LINE_MARKER | | `plot_dumbbell_edge_width` | The edge width of the dots. **TYPE:** \`int | | `plot_dumbbell_edge_color` | The edge color of the dots. **TYPE:** \`str | | `plot_dumbbell_zorder` | The zorder of the dots. **TYPE:** \`int | | `plot_dumbbell_connector_color` | The color of the connectors. **TYPE:** \`str | | `plot_dumbbell_connector_width` | The line width of the connectors. **TYPE:** \`int | | `plot_dumbbell_connector_style` | The line style of the connectors. **TYPE:** \`LINE_STYLE | | `plot_dumbbell_connector_zorder` | The zorder of the connectors; below the dots by default. **TYPE:** \`int | | `plot_dumbbell_arrow_color` | The color of the direction arrows under show_direction. **TYPE:** \`str | | `plot_dumbbell_arrow_width` | The line width of the direction arrows. **TYPE:** \`int | | `plot_dumbbell_arrow_style` | The direction arrow head, as a matplotlib arrow style. **TYPE:** \`str | | `plot_dumbbell_arrow_gap` | The space between a dot's edge and its direction arrow, in points. **TYPE:** \`int | | `plot_dumbbell_grid_minor` | The parts each step between labelled values splits into with fainter gridlines, on a gridded linear value axis; 0 or None draws none. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `show_values` | [`DUMBBELL_VALUE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DUMBBELL_VALUE) | | `sort_by` | [`DUMBBELL_SORT_KEY`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DUMBBELL_SORT_KEY) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | | `marker` | [`LINE_MARKER`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_MARKER) | | `connector_style` | [`LINE_STYLE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LINE_STYLE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | # Histogram The distribution of one numeric variable, binned. The [Histogram guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/histogram/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.Histogram ``` Histogram( data: ( list[HistDataPointAttrs] | list[list[HistDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_density: bool | None = None, show_cumulative: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.VERTICAL, bar_mode: BAR_MODE | str | None = None, num_bins: int | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( HistStyleAttrs | list[HistStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, x: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the histogram. A histogram bins a single numeric variable and draws the count (or density) per bin, revealing the shape of its distribution: center, spread, skew, modes, and outliers. Use it to inspect one variable or compare a few overlaid distributions. For side-by-side group summaries use BoxPlot or ViolinPlot. Examples: ``` >>> from datachart.charts import Histogram >>> figure = Histogram( ... data=[ ... {"x": 1}, ... {"x": 2}, ... {"x": 3}, ... {"x": 4}, ... {"x": 5} ... ], ... title="Basic Histogram", ... xlabel="X", ... ylabel="Y" ... ) ``` | PARAMETER | DESCRIPTION | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the histogram(s). Can be a single list of data points for one chart, or a list of lists for multiple charts/subplots. **TYPE:** \`list[HistDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual charts, aligned like style: "background" mutes a chart (theme muted color, lowered alpha, behind the others, no legend entry), "highlight" bolds it and brings it to the front, None leaves it unchanged. When any chart carries a role, the histograms draw individually overlaid instead of stacked. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the histograms matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each histogram's own x values, chosen by by: "mean" (default), "median", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every histogram. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_density` | Whether to plot the density histogram. **TYPE:** \`bool | | `show_cumulative` | Whether to plot the cumulative histogram. **TYPE:** \`bool | | `show_values` | Whether to print each bin's height at its top; empty bins stay bare. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `orientation` | The orientation of the histogram (vertical or horizontal). **TYPE:** \`ORIENTATION | | `bar_mode` | How multiple histogram series share the axis: "stack" (stacked on shared bins, the default) or "overlay" (each series drawn individually over the others). "group" has no histogram meaning and behaves like "overlay". See BAR_MODE. **TYPE:** \`BAR_MODE | | `num_bins` | The number of bins to split the data into. **TYPE:** \`int | | `scalex` | The x-axis scale (e.g., "log", "linear"). Useful for log-distributed data. **TYPE:** \`SCALE | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the histogram(s). **TYPE:** \`HistStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `x` | The key name in data for x-axis values (default: "x"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ------------------------------------ | | `plt.Figure` | The figure containing the histogram. | ## Data Each record in `data` is a [`HistDataPointAttrs`](#datachart.typings.HistDataPointAttrs); the `x` parameter renames its keys. ### datachart.typings.HistDataPointAttrs Bases: `TypedDict` The data point attributes for the histogram chart. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------- | | `x` | The x-axis value. **TYPE:** \`int | ## Style `style` takes the keys of [`HistStyleAttrs`](#datachart.typings.HistStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.HistStyleAttrs Bases: `TypedDict` The typing for the histogram chart style. | ATTRIBUTE | DESCRIPTION | | -------------------------- | ------------------------------------------------------------------------ | | `plot_hist_color` | The color of the histogram. **TYPE:** \`str | | `plot_hist_alpha` | The alpha value of the histogram. **TYPE:** \`float | | `plot_hist_zorder` | The zorder of the histogram. **TYPE:** \`int | | `plot_hist_fill` | The fill of the histogram. **TYPE:** \`str | | `plot_hist_hatch` | The hatch style in the histogram. **TYPE:** \`HATCH_STYLE | | `plot_hist_type` | The type of the histogram. **TYPE:** \`HISTOGRAM_TYPE | | `plot_hist_align` | The alignment of the histogram. **TYPE:** \`str | | `plot_hist_edge_width` | The edge width of the histogram. **TYPE:** \`int | | `plot_hist_edge_color` | The edge color of the histogram. **TYPE:** \`str | | `plot_xticks_label_rotate` | The label rotation of the xticks in the histogram chart. **TYPE:** \`int | | `plot_yticks_label_rotate` | The label rotation of the yticks in the histogram chart. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `style={"plot_hist_type": ...}` | [`HISTOGRAM_TYPE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HISTOGRAM_TYPE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `bar_mode` | [`BAR_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BAR_MODE) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | # BoxPlot Median, quartiles, whiskers, and outliers per group. The [Box Plot guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/boxplot/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.BoxPlot ``` BoxPlot( data: ( list[BoxDataPointAttrs] | list[list[BoxDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_outliers: bool | None = None, show_notch: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.VERTICAL, sort: SORT | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( BoxStyleAttrs | list[BoxStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, value: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the box plot. A box plot summarizes a numeric distribution per group by its median, quartiles, whiskers, and outliers. Use it to compare the level and spread of many groups compactly, or to spot skew and outliers, when the full distribution shape is not needed. For shape use ViolinPlot; for the raw points use SwarmPlot. Examples: ``` >>> from datachart.charts import BoxPlot >>> figure = BoxPlot( ... data=[ ... {"label": "Group A", "value": 10}, ... {"label": "Group A", "value": 15}, ... {"label": "Group A", "value": 12}, ... {"label": "Group B", "value": 20}, ... {"label": "Group B", "value": 25}, ... {"label": "Group B", "value": 22}, ... ], ... title="Basic Box Plot", ... xlabel="Group", ... ylabel="Value" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the box plot(s). Can be a single list of data points for one chart, or a list of lists for subplots (requires subplots=True). Each data point should have a label (category) and value (numeric). **TYPE:** \`list[BoxDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts: the subplot title and the legend label. **TYPE:** \`str | | `emphasis` | The emphasis role(s), aligned with the box labels of one call in input order, whatever the sort (a single value applies to every box): "background" mutes a box and its whiskers, caps, median, and outliers, "highlight" bolds the box edges and median, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the groups matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each group's values, chosen by by: "median" (default), "mean", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every group of every chart. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_outliers` | Whether to show outliers. Defaults to True. **TYPE:** \`bool | | `show_notch` | Whether to show notched boxes for median confidence interval. **TYPE:** \`bool | | `show_values` | Whether to print each group's median beside its median line. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `orientation` | The orientation of the boxes (vertical or horizontal). **TYPE:** \`ORIENTATION | | `sort` | The order the groups are drawn in: None (input order), "ascending", or "descending" by each group's median; ties keep input order. One call draws one box dataset per axes, so there is no second series to key on and no sort_by. See SORT. **TYPE:** \`SORT | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart; required for a list of datasets. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the box(es). **TYPE:** \`BoxStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label/category values (default: "label"). **TYPE:** \`str | | `value` | The key name in data for numeric values (default: "value"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ----------------------------------- | | `plt.Figure` | The figure containing the box plot. | ## Data Each record in `data` is a [`BoxDataPointAttrs`](#datachart.typings.BoxDataPointAttrs); the `label` and `value` parameters rename its keys. ### datachart.typings.BoxDataPointAttrs Bases: `TypedDict` The data point attributes for the box plot. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------- | | `label` | The category label. **TYPE:** `str` | | `value` | The numeric value. **TYPE:** \`int | ## Style `style` takes the keys of [`BoxStyleAttrs`](#datachart.typings.BoxStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.BoxStyleAttrs Bases: `TypedDict` The typing for the box plot style. | ATTRIBUTE | DESCRIPTION | | ----------------------------- | ----------------------------------------------------- | | `plot_box_color` | The box fill color. **TYPE:** \`str | | `plot_box_alpha` | The alpha value of the box. **TYPE:** \`float | | `plot_box_linewidth` | The line width of the box. **TYPE:** \`int | | `plot_box_edgecolor` | The edge color of the box. **TYPE:** \`str | | `plot_box_outlier_marker` | The outlier marker style. **TYPE:** \`LINE_MARKER | | `plot_box_outlier_size` | The outlier marker size. **TYPE:** \`int | | `plot_box_outlier_color` | The outlier marker color. **TYPE:** \`str | | `plot_box_outlier_edge_color` | The outlier marker edge color. **TYPE:** \`str | | `plot_box_median_color` | The median line color. **TYPE:** \`str | | `plot_box_median_linewidth` | The median line width. **TYPE:** \`int | | `plot_box_whisker_color` | The whisker line color. **TYPE:** \`str | | `plot_box_whisker_linewidth` | The whisker line width. **TYPE:** \`int | | `plot_box_cap_color` | The cap line color. **TYPE:** \`str | | `plot_box_cap_linewidth` | The cap line width. **TYPE:** \`int | | `plot_xticks_label_rotate` | The label rotation of the xticks. **TYPE:** \`int | | `plot_yticks_label_rotate` | The label rotation of the yticks. **TYPE:** \`int | | `plot_box_hatch` | The hatch pattern of the box. **TYPE:** \`HATCH_STYLE | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # ViolinPlot The density profile of each group's distribution. The [Violin Plot guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/violinplot/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.ViolinPlot ``` ViolinPlot( data: ( list[ViolinDataPointAttrs] | list[list[ViolinDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.VERTICAL, sort: SORT | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( ViolinStyleAttrs | list[ViolinStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, value: str | list[str | None] | None = None, inner: VIOLIN_INNER | str | None = VIOLIN_INNER.BOX, bandwidth: BANDWIDTH | str | float | None = None, split: str | None = None ) -> plt.Figure ``` Creates the violin plot. A violin plot draws the kernel density estimate of each group's numeric distribution as a mirrored profile, showing shape (multimodality, skew, tails) that a box plot hides. Use it to compare distributions across groups when shape matters and each group has enough samples for a density estimate. Examples: ``` >>> from datachart.charts import ViolinPlot >>> figure = ViolinPlot( ... data=[ ... {"label": "Group A", "value": 10}, ... {"label": "Group A", "value": 15}, ... {"label": "Group A", "value": 12}, ... {"label": "Group B", "value": 20}, ... {"label": "Group B", "value": 25}, ... {"label": "Group B", "value": 22}, ... ], ... title="Basic Violin Plot", ... xlabel="Group", ... ylabel="Value" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the violin plot(s). Can be a single list of data points for one chart, or a list of lists for subplots (requires subplots=True). Each data point should have a label (category) and value (numeric). **TYPE:** \`list[ViolinDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts: the subplot title and the legend label. **TYPE:** \`str | | `emphasis` | The emphasis role(s), aligned with the violin labels of one call in input order, whatever the sort (a single value applies to every violin): "background" mutes a violin body and its inner marks, "highlight" bolds the body edge, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the groups matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each group's values, chosen by by: "median" (default), "mean", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every group of every chart. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_values` | Whether to print each group's median beside its median line. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `orientation` | The orientation of the violins (vertical or horizontal). **TYPE:** \`ORIENTATION | | `sort` | The order the groups are drawn in: None (input order), "ascending", or "descending" by each group's median; ties keep input order. One call draws one violin dataset per axes, so there is no second series to key on and no sort_by. See SORT. **TYPE:** \`SORT | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart; required for a list of datasets. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the violin(s). **TYPE:** \`ViolinStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label/category values (default: "label"). **TYPE:** \`str | | `value` | The key name in data for numeric values (default: "value"). **TYPE:** \`str | | `inner` | The marks drawn inside each body: "box" (quartile bar, 1.5·IQR whisker, median dot), "quartiles" (dashed median, dotted Q1/Q3), "median" (one line), or None (body only). See VIOLIN_INNER. **TYPE:** \`VIOLIN_INNER | | `bandwidth` | The KDE bandwidth: None or "scott" (Scott's rule), "silverman", or a scalar factor. See BANDWIDTH. **TYPE:** \`BANDWIDTH | | `split` | The key name in data whose exactly two distinct values become the left and right halves of each violin, colored from the multiple palette and listed in the legend. **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | -------------------------------------- | | `plt.Figure` | The figure containing the violin plot. | ## Data Each record in `data` is a [`ViolinDataPointAttrs`](#datachart.typings.ViolinDataPointAttrs); the `label` and `value` parameters rename its keys. ### datachart.typings.ViolinDataPointAttrs Bases: `TypedDict` The data point attributes for the violin plot. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------- | | `label` | The category label. **TYPE:** `str` | | `value` | The numeric value. **TYPE:** \`int | ## Style `style` takes the keys of [`ViolinStyleAttrs`](#datachart.typings.ViolinStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.ViolinStyleAttrs Bases: `TypedDict` The typing for the violin plot style. | ATTRIBUTE | DESCRIPTION | | ----------------------------- | ------------------------------------------------------------------------- | | `plot_violin_color` | The violin fill color. **TYPE:** \`str | | `plot_violin_alpha` | The alpha value of the violin body. **TYPE:** \`float | | `plot_violin_linewidth` | The line width of the body edge. **TYPE:** \`int | | `plot_violin_edgecolor` | The edge color of the body; defaults to the fill. **TYPE:** \`str | | `plot_violin_width` | The maximum width of the body. **TYPE:** \`int | | `plot_violin_inner_color` | The color of the inner marks; defaults to the font color. **TYPE:** \`str | | `plot_violin_inner_linewidth` | The line width of the inner marks. **TYPE:** \`int | | `plot_violin_median_color` | The color of the median dot. **TYPE:** \`str | | `plot_violin_median_size` | The size of the median dot. **TYPE:** \`int | | `plot_violin_hatch` | The hatch pattern of the body. **TYPE:** \`HATCH_STYLE | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `inner` | [`VIOLIN_INNER`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VIOLIN_INNER) | | `bandwidth` | [`BANDWIDTH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) | # SwarmPlot Every observation as a point, spread within its group. The [Swarm Plot guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/swarmplot/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.SwarmPlot ``` SwarmPlot( data: ( list[SwarmDataPointAttrs] | list[list[SwarmDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, mode: SWARM_MODE | str = SWARM_MODE.SWARM, jitter: float = 0.4, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.VERTICAL, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( SwarmStyleAttrs | list[SwarmStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, value: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the swarm plot. A swarm plot draws every observation as a point at its group's category position, spread across the category width so the points do not hide each other, making counts and gaps visible. Use it for small-to-medium samples where each observation matters, or overlay it on a BoxPlot with `Panel` (the two share positions). For large samples prefer ViolinPlot. Examples: ``` >>> from datachart.charts import SwarmPlot >>> figure = SwarmPlot( ... data=[ ... {"label": "Group A", "value": 10}, ... {"label": "Group A", "value": 15}, ... {"label": "Group A", "value": 12}, ... {"label": "Group B", "value": 20}, ... {"label": "Group B", "value": 25}, ... {"label": "Group B", "value": 22}, ... ], ... title="Basic Swarm Plot", ... xlabel="Group", ... ylabel="Value" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the swarm plot(s). Can be a single list of data points for one chart, or a list of lists for multiple charts. Each data point should have a label (category) and value (numeric), and may carry its own emphasis role, which wins over its group's. **TYPE:** \`list[SwarmDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s), aligned with the group labels of one call (a single value applies to every group): "background" mutes a group's points, "highlight" bolds their edges, None leaves them unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the groups matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each group's values, chosen by by: "median" (default), "mean", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every group of every chart. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `mode` | How the points spread across the category width. See SWARM_MODE: "swarm" packs the points so none overlap, from the marker size at draw time (axis limits changed afterwards can shift the spacing); "strip" jitters them uniformly. **TYPE:** \`SWARM_MODE | | `jitter` | The strip jitter width, as a fraction of the category width. Only used with mode="strip". **TYPE:** `float` **DEFAULT:** `0.4` | | `show_values` | Whether to print each group's minimum, median, and maximum beside the points nearest them. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `orientation` | The orientation of the swarms (vertical or horizontal). **TYPE:** \`ORIENTATION | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the points. **TYPE:** \`SwarmStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label/category values (default: "label"). **TYPE:** \`str | | `value` | The key name in data for numeric values (default: "value"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ------------------------------------- | | `plt.Figure` | The figure containing the swarm plot. | ## Data Each record in `data` is a [`SwarmDataPointAttrs`](#datachart.typings.SwarmDataPointAttrs); the `label` and `value` parameters rename its keys. ### datachart.typings.SwarmDataPointAttrs Bases: `TypedDict` The data point attributes for the swarm plot. | ATTRIBUTE | DESCRIPTION | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `label` | The category label. **TYPE:** `str` | | `value` | The numeric value. **TYPE:** \`int | | `emphasis` | The point's own emphasis role ("background" or "highlight"); wins over its group's emphasis and the chart's emphasis_rule. **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`SwarmStyleAttrs`](#datachart.typings.SwarmStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.SwarmStyleAttrs Bases: `TypedDict` The typing for the swarm plot style. | ATTRIBUTE | DESCRIPTION | | ----------------------- | ------------------------------------------------ | | `plot_swarm_color` | The point color. **TYPE:** \`str | | `plot_swarm_alpha` | The alpha value of the points. **TYPE:** \`float | | `plot_swarm_size` | The point size. **TYPE:** \`int | | `plot_swarm_marker` | The point marker shape. **TYPE:** \`LINE_MARKER | | `plot_swarm_zorder` | The zorder of the points. **TYPE:** \`int | | `plot_swarm_edge_width` | The edge width of the points. **TYPE:** \`int | | `plot_swarm_edge_color` | The edge color of the points. **TYPE:** \`str | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `mode` | [`SWARM_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SWARM_MODE) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # RaincloudPlot A half violin, the raw points, and a box per group. The [Raincloud Plot guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/raincloudplot/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.RaincloudPlot ``` RaincloudPlot( data: ( list[RaincloudDataPointAttrs] | list[list[RaincloudDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_outliers: bool | None = True, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, mode: SWARM_MODE | str = SWARM_MODE.SWARM, jitter: float = 0.4, bandwidth: BANDWIDTH | str | float | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.VERTICAL, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( RaincloudStyleAttrs | list[RaincloudStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, value: str | list[str | None] | None = None ) -> plt.Figure ``` Creates the raincloud plot. A raincloud plot draws each group as a cloud (a half violin of its density), its rain (the raw observations), and a box (the quartile summary) side by side at one category position, all in the group's palette color. Use it when you want the shape, the summary statistics, and the individual observations in a single view, for example when reporting experimental results per condition. Vertical rainclouds keep the cloud on the right; horizontal ones keep it above. Examples: ``` >>> from datachart.charts import RaincloudPlot >>> figure = RaincloudPlot( ... data=[ ... {"label": "Group A", "value": 10}, ... {"label": "Group A", "value": 15}, ... {"label": "Group A", "value": 12}, ... {"label": "Group B", "value": 20}, ... {"label": "Group B", "value": 25}, ... {"label": "Group B", "value": 22}, ... ], ... title="Basic Raincloud Plot", ... xlabel="Group", ... ylabel="Value" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the raincloud plot(s). Can be a single list of data points for one chart, or a list of lists for multiple charts (drawn as subplots). Each data point should have a label (category) and value (numeric). **TYPE:** \`list[RaincloudDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | The emphasis role(s), aligned with the group labels of one call (a single value applies to every group): "background" mutes a group's cloud, rain, and box, "highlight" bolds their edges, None leaves them unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the groups matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each group's values, chosen by by: "median" (default), "mean", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every group of every chart. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend; one entry per group. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_outliers` | Whether the box shows outliers. **TYPE:** \`bool | | `show_values` | Whether to print each group's median beside its box, and its minimum and maximum beside the rain points holding them. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `mode` | How the rain spreads across its width. See SWARM_MODE: "swarm" packs the points so none overlap; "strip" jitters them uniformly. **TYPE:** \`SWARM_MODE | | `jitter` | The strip jitter width, as a fraction of the category width like SwarmPlot, scaled down to the rain's narrower cell. Only used with mode="strip". **TYPE:** `float` **DEFAULT:** `0.4` | | `bandwidth` | The cloud's KDE bandwidth: None or "scott" (Scott's rule), "silverman" (Silverman's rule), or a scalar factor. See BANDWIDTH. **TYPE:** \`BANDWIDTH | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `orientation` | The orientation of the rainclouds (vertical or horizontal). **TYPE:** \`ORIENTATION | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s); the violin keys style the cloud, the swarm keys the rain, and the box keys the box. **TYPE:** \`RaincloudStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label/category values (default: "label"). **TYPE:** \`str | | `value` | The key name in data for numeric values (default: "value"). **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------ | ----------------------------------------- | | `plt.Figure` | The figure containing the raincloud plot. | ## Data Each record in `data` is a [`RaincloudDataPointAttrs`](#datachart.typings.RaincloudDataPointAttrs); the `label` and `value` parameters rename its keys. ### datachart.typings.RaincloudDataPointAttrs Bases: `TypedDict` The data point attributes for the raincloud plot. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------- | | `label` | The category label. **TYPE:** `str` | | `value` | The numeric value. **TYPE:** \`int | ## Style `style` takes the keys of [`RaincloudStyleAttrs`](#datachart.typings.RaincloudStyleAttrs). RaincloudStyleAttrs is the union of [`ViolinStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinStyleAttrs), [`SwarmStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmStyleAttrs) and [`BoxStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxStyleAttrs), one per part of the chart. The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.RaincloudStyleAttrs Bases: `ViolinStyleAttrs`, `SwarmStyleAttrs`, `BoxStyleAttrs` The typing for the raincloud plot style. The union of the violin (cloud), swarm (rain), and box style keys; each key styles its own part of the raincloud. ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `mode` | [`SWARM_MODE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SWARM_MODE) | | `bandwidth` | [`BANDWIDTH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # RidgelinePlot One density ridge per group, stacked and partly overlapping. The [Ridgeline Plot guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/ridgelineplot/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.RidgelinePlot ``` RidgelinePlot( data: ( list[RidgelineDataPointAttrs] | list[list[RidgelineDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, orientation: ( ORIENTATION | str | None ) = ORIENTATION.HORIZONTAL, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( RidgelineStyleAttrs | list[RidgelineStyleAttrs | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, label: str | list[str | None] | None = None, value: str | list[str | None] | None = None, bandwidth: BANDWIDTH | str | float | None = None, overlap: float | None = None, normalize: ( RIDGELINE_SCALE | str | None ) = RIDGELINE_SCALE.PER_ROW, inner: VIOLIN_INNER | str | None = None, fill: bool = True, show_outline: bool = True, sort: SORT | str | None = SORT.NONE ) -> plt.Figure ``` Creates the ridgeline plot. A ridgeline plot (joy plot) draws the kernel density estimate of each group's numeric distribution as a ridge on its own row, the rows stacked and partly overlapping, first row at the top. Use it to show how one distribution shifts across many groups in the space a grid of histograms would spend on a few. Examples: ``` >>> from datachart.charts import RidgelinePlot >>> figure = RidgelinePlot( ... data=[ ... {"label": "Group A", "value": 10}, ... {"label": "Group A", "value": 15}, ... {"label": "Group A", "value": 12}, ... {"label": "Group B", "value": 20}, ... {"label": "Group B", "value": 25}, ... {"label": "Group B", "value": 22}, ... ], ... title="Basic Ridgeline Plot", ... xlabel="Value", ... ylabel="Group" ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the ridgeline plot(s). Can be a single list of data points for one chart, or a list of lists for multiple charts/subplots. Each data point should have a label (category) and value (numeric). **TYPE:** \`list[RidgelineDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts (subplots). **TYPE:** \`str | | `emphasis` | The emphasis role(s), aligned with the ridge labels of one call in input order (a single value applies to every ridge): "background" mutes a ridge and its inner marks, "highlight" bolds its outline, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the groups matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each group's values, chosen by by: "median" (default), "mean", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every group of every chart. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value; on the value axis it also bounds the density grid. **TYPE:** \`int | | `xmax` | The maximum x-axis value; on the value axis it also bounds the density grid. **TYPE:** \`int | | `ymin` | The minimum y-axis value; bounds the grid when vertical. **TYPE:** \`int | | `ymax` | The maximum y-axis value; bounds the grid when vertical. **TYPE:** \`int | | `show_legend` | Whether to show the legend; the ridges add no entries, their labels sit on the category axis. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `orientation` | "horizontal" (default) runs the value axis along x and stacks the rows along y, first row at the top; "vertical" runs the rows along x, first row at the left, each ridge rising rightward from its tick. See ORIENTATION. **TYPE:** \`ORIENTATION | | `scaley` | The value-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the ridges. **TYPE:** \`RidgelineStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `label` | The key name in data for label/category values (default: "label"). **TYPE:** \`str | | `value` | The key name in data for numeric values (default: "value"). **TYPE:** \`str | | `bandwidth` | The KDE bandwidth: None or "scott" (Scott's rule), "silverman", or a scalar factor. See BANDWIDTH. **TYPE:** \`BANDWIDTH | | `overlap` | How far a ridge's peak rises into the row above, in \[0, 1\]: a ridge rises from its tick and its peak stands 1 + overlap rows above it, so 0 makes rows touch. None takes the theme's plot_ridgeline_overlap. **TYPE:** \`float | | `normalize` | "per_row" scales every ridge to the same peak so shapes compare; "common" keeps one density scale so heights compare. See RIDGELINE_SCALE. **TYPE:** \`RIDGELINE_SCALE | | `inner` | The marks drawn inside each ridge, up to its height: "median" (one line), "quartiles" (dashed median, dotted Q1/Q3), or None. See VIOLIN_INNER; "box" is not supported. **TYPE:** \`VIOLIN_INNER | | `fill` | Whether to fill each ridge. **TYPE:** `bool` **DEFAULT:** `True` | | `show_outline` | Whether to stroke each ridge's density curve. **TYPE:** `bool` **DEFAULT:** `True` | | `sort` | The row order: None keeps input order, "ascending" or "descending" orders the rows by their median; ties keep input order. See SORT. **TYPE:** \`SORT | | RETURNS | DESCRIPTION | | ------------ | ----------------------------------------- | | `plt.Figure` | The figure containing the ridgeline plot. | | RAISES | DESCRIPTION | | ------------ | -------------------------------------------------------------------------------------- | | `ValueError` | If overlap is outside [0, 1], inner is "box", or both fill and show_outline are False. | ## Data Each record in `data` is a [`RidgelineDataPointAttrs`](#datachart.typings.RidgelineDataPointAttrs); the `label` and `value` parameters rename its keys. ### datachart.typings.RidgelineDataPointAttrs Bases: `TypedDict` The data point attributes for the ridgeline plot. | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------- | | `label` | The category label; one ridge per label. **TYPE:** `str` | | `value` | The numeric value. **TYPE:** \`int | ## Style `style` takes the keys of [`RidgelineStyleAttrs`](#datachart.typings.RidgelineStyleAttrs). The chart also reads the shared groups it draws: reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.RidgelineStyleAttrs Bases: `TypedDict` The typing for the ridgeline plot style. | ATTRIBUTE | DESCRIPTION | | -------------------------------- | ------------------------------------------------------------------------- | | `plot_ridgeline_color` | The ridge fill color; defaults to the palette color. **TYPE:** \`str | | `plot_ridgeline_alpha` | The alpha value of the ridge fill. **TYPE:** \`float | | `plot_ridgeline_linewidth` | The line width of the ridge outline. **TYPE:** \`int | | `plot_ridgeline_edgecolor` | The color of the ridge outline; defaults to the fill. **TYPE:** \`str | | `plot_ridgeline_overlap` | How far a peak rises into the row above, in [0, 1]. **TYPE:** \`float | | `plot_ridgeline_inner_color` | The color of the inner marks; defaults to the font color. **TYPE:** \`str | | `plot_ridgeline_inner_linewidth` | The line width of the inner marks. **TYPE:** \`int | | `plot_ridgeline_hatch` | The hatch pattern of the ridge fill. **TYPE:** \`HATCH_STYLE | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `normalize` | [`RIDGELINE_SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.RIDGELINE_SCALE) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `orientation` | [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `bandwidth` | [`BANDWIDTH`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.BANDWIDTH) | | `inner` | [`VIOLIN_INNER`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VIOLIN_INNER) | | `sort` | [`SORT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SORT) | # ScatterChart One point per observation, placed by two numeric variables. The [Scatter Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scatterchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.ScatterChart ``` ScatterChart( data: ( list[ScatterDataPointAttrs] | list[list[ScatterDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | datetime | None = None, xmax: int | float | datetime | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_regression: bool | None = None, show_ci: bool | None = None, ci_level: float | None = None, show_correlation: bool | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, value_step: int | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( ScatterStyleAttrs | list[ScatterStyleAttrs | None] | None ) = None, xticks: ( list[int | float | datetime] | list[list[int | float | datetime]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None, x: str | list[str | None] | None = None, y: str | list[str | None] | None = None, size: str | list[str | None] | None = None, hue: str | list[str | None] | None = None, label: str | list[str | None] | None = None, size_range: tuple[float, float] | None = None ) -> plt.Figure ``` Creates a scatter chart. Each point is one observation placed by two numeric variables, optionally with a third encoded as marker size. Use it to check whether two variables are related, spot clusters and outliers, and quantify the link with the optional regression line and correlation coefficient. For ordered series use LineChart. Examples: ``` >>> from datachart.charts import ScatterChart >>> # Basic scatter plot >>> figure = ScatterChart( ... data=[ ... {"x": 1, "y": 5}, ... {"x": 2, "y": 10}, ... {"x": 3, "y": 15}, ... {"x": 4, "y": 20}, ... {"x": 5, "y": 25} ... ], ... title="Basic Scatter Chart", ... xlabel="X", ... ylabel="Y" ... ) >>> >>> # Scatter with hue grouping >>> figure = ScatterChart( ... data=[ ... {"x": 1, "y": 5, "category": "A"}, ... {"x": 2, "y": 10, "category": "B"}, ... ], ... hue="category", ... show_legend=True ... ) >>> >>> # Bubble chart with size variable >>> figure = ScatterChart( ... data=[ ... {"x": 1, "y": 5, "pop": 100}, ... {"x": 2, "y": 10, "pop": 200} ... ], ... size="pop", ... size_range=(20, 200) ... ) >>> >>> # Scatter with regression line >>> figure = ScatterChart( ... data=[...], ... show_regression=True, ... show_ci=True, ... ci_level=0.95 ... ) >>> >>> # Scatter with correlation annotation >>> figure = ScatterChart( ... data=[...], ... show_correlation=True ... ) >>> >>> # Scatter with a label beside each point >>> figure = ScatterChart( ... data=[ ... {"x": 1, "y": 5, "name": "A"}, ... {"x": 2, "y": 10, "name": "B"} ... ], ... label="name" ... ) ``` | PARAMETER | DESCRIPTION | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the scatter chart(s). Can be a single list of data points for one chart, or a list of lists for multiple charts/subplots. A point may carry its own emphasis role, which wins over its chart's. **TYPE:** \`list[ScatterDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual charts, aligned like style: "background" mutes a chart (theme muted color, lowered alpha, behind the others, no legend entry), "highlight" gives it a contrasting edge and brings it to the front, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the series matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each series's own y values, chosen by by: "mean" (default), "median", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every series. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_regression` | Whether to show the regression line. **TYPE:** \`bool | | `show_ci` | Whether to show the confidence interval around the regression line. **TYPE:** \`bool | | `ci_level` | The confidence interval level (default 0.95). **TYPE:** \`float | | `show_correlation` | Whether to show the Pearson correlation coefficient (r-value) as an annotation. **TYPE:** \`bool | | `show_values` | Whether to print each point's y value beside it. Cannot be combined with label: a point carries its label or its value. **TYPE:** \`bool | | `value_format` | Format string for the value labels: a VALUE_FORMAT constant or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `value_step` | Label every Nth point (1 labels all of them). Defaults to the smallest step that keeps neighbouring labels apart. **TYPE:** \`int | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `scalex` | The x-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the scatter markers. **TYPE:** \`ScatterStyleAttrs | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | `x` | The key name in data for x-axis values (default: "x"). **TYPE:** \`str | | `y` | The key name in data for y-axis values (default: "y"). **TYPE:** \`str | | `size` | The key name in data for marker size values (for bubble charts). **TYPE:** \`str | | `hue` | The key name in data for color grouping (categorical variable). **TYPE:** \`str | | `label` | The key name in data for the point labels (default: "label"), aligned like style for multiple charts; None in the list leaves that chart unlabelled. Each label is drawn beside its marker at the spot with the least overlap against the other markers, labels, and the axes edge; points without the key stay unlabelled. **TYPE:** \`str | | `size_range` | Tuple of (min_size, max_size) for bubble charts (default: (20, 200)). **TYPE:** \`tuple[float, float] | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------------- | | `plt.Figure` | The figure containing the scatter chart. | ## Data Each record in `data` is a [`ScatterDataPointAttrs`](#datachart.typings.ScatterDataPointAttrs); the `x`, `y`, `size`, `hue` and `label` parameters rename its keys. ### datachart.typings.ScatterDataPointAttrs Bases: `TypedDict` The data point attributes for the scatter chart. | ATTRIBUTE | DESCRIPTION | | ---------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `x` | The x-axis value. **TYPE:** \`int | | `y` | The y-axis value. **TYPE:** \`int | | `size` | The marker size (for bubble charts). **TYPE:** \`int | | `hue` | The category for color grouping. **TYPE:** \`str | | `label` | The label drawn beside the point. **TYPE:** \`str | | `emphasis` | The point's own emphasis role ("background" or "highlight"); wins over the chart's emphasis and emphasis_rule. **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`ScatterStyleAttrs`](#datachart.typings.ScatterStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)), the regression line ([`RegressionStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.RegressionStyleAttrs)), reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.ScatterStyleAttrs Bases: `TypedDict` The typing for the scatter chart style. | ATTRIBUTE | DESCRIPTION | | ------------------------- | ------------------------------------------------- | | `plot_scatter_color` | The scatter marker color. **TYPE:** \`str | | `plot_scatter_alpha` | The alpha value of the markers. **TYPE:** \`float | | `plot_scatter_size` | The marker size. **TYPE:** \`int | | `plot_scatter_marker` | The marker shape. **TYPE:** \`LINE_MARKER | | `plot_scatter_zorder` | The zorder of the scatter. **TYPE:** \`int | | `plot_scatter_edge_width` | The edge width of markers. **TYPE:** \`int | | `plot_scatter_edge_color` | The edge color of markers. **TYPE:** \`str | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | # Heatmap A two-dimensional matrix as colored cells. The [Heatmap guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/heatmap/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.Heatmap ``` Heatmap( data: HeatmapDataAttrs | list[HeatmapDataAttrs], *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: None = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | None = None, xmax: int | float | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_colorbars: bool | None = None, show_heatmap_values: bool | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( HeatmapStyleAttrs | list[HeatmapStyleAttrs | None] | None ) = None, norm: str | list[str | None] | None = None, vmin: float | list[float | None] | None = None, vmax: float | list[float | None] | None = None, valfmt: ( VALUE_FORMAT | str | list[str | None] | None ) = None, xticks: ( list[int | float] | list[list[int | float]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, colorbar: ( ColorbarSettingAttrs | list[ColorbarSettingAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the heatmap. A heatmap maps every cell of a 2-D matrix to a color, so structure in a grid of numbers (correlations, confusion matrices, feature-by-time tables) reads at a glance. Use it when both axes are categorical or gridded and the value is what matters; the color scale, colorbar, and cell value labels are all configurable. Examples: ``` >>> from datachart.charts import Heatmap >>> figure = Heatmap( ... data={ ... "x": ["a", "b", "c"], ... "y": ["p", "q", "r"], ... "z": [ ... [1, 2, 3], ... [4, 5, 6], ... [7, 8, 9], ... ], ... }, ... title="Basic Heatmap", ... xlabel="X", ... ylabel="Y" ... ) ``` | PARAMETER | DESCRIPTION | | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The labelled grid(s) for the heatmap(s): one {x, y, z} dict, or a list of them for multiple heatmaps/subplots. z is the 2-D matrix of cell values (rows along y, columns along x; None cells stay blank); x and y are optional tick labels for its columns and rows (any values, the indices by default). An explicit xticks/xticklabels (yticks/yticklabels) overrides them. An optional emphasis grid aligned with z gives a cell its own role: "background" fades it to the theme's muted alpha, "highlight" outlines it, None leaves it unchanged. **TYPE:** \`HeatmapDataAttrs | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | Not supported: a heatmap has no series to mute or highlight; set per-cell roles through the emphasis grid of data. Passing a value raises ValueError. **TYPE:** `None` **DEFAULT:** `None` | | `emphasis_rule` | A rule that highlights the cells matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against each cell's value; a blank cell never matches. A cell's role in the emphasis grid of data wins. The rule takes no by. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend (not typical for heatmaps). **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `show_colorbars` | Whether to show the colorbar(s). **TYPE:** \`bool | | `show_heatmap_values` | Whether to show values on the heatmap cells. **TYPE:** \`bool | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `subplots` | Whether to create separate subplots for each heatmap. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the heatmap(s). **TYPE:** \`HeatmapStyleAttrs | | `norm` | Value normalization method(s). **TYPE:** \`str | | `vmin` | Minimum value(s) for normalization. **TYPE:** \`float | | `vmax` | Maximum value(s) for normalization. **TYPE:** \`float | | `valfmt` | Format string(s) for cell values, with the value named x (e.g., "{x:.1f}"). See VALUE_FORMAT. **TYPE:** \`VALUE_FORMAT | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `colorbar` | The colorbar setting(s): label, location, tick format, and tick positions. See ColorbarSettingAttrs. **TYPE:** \`ColorbarSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------- | | `plt.Figure` | The figure containing the heatmap. | ## Data Each record in `data` is a [`HeatmapDataAttrs`](#datachart.typings.HeatmapDataAttrs); the `emphasis` parameter renames its keys. ### datachart.typings.HeatmapDataAttrs Bases: `TypedDict` The data attributes for the heatmap chart. | ATTRIBUTE | DESCRIPTION | | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `x` | The column labels, one per column of z. Defaults to the column indices. **TYPE:** \`list\[str | | `y` | The row labels, one per row of z. Defaults to the row indices. **TYPE:** \`list\[str | | `z` | The 2-D grid of cell values, one row per y and one column per x. **TYPE:** \`list\[list\[int | | `emphasis` | The per-cell emphasis roles, aligned with z ("background" or "highlight"); wins over the chart's emphasis_rule. **TYPE:** \`list\[list\[EMPHASIS | ## Style `style` takes the keys of [`HeatmapStyleAttrs`](#datachart.typings.HeatmapStyleAttrs). The chart also reads the shared groups it draws: text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.HeatmapStyleAttrs Bases: `TypedDict` The typing for the heatmap chart style. | ATTRIBUTE | DESCRIPTION | | -------------------------- | ----------------------------------------------------------------------------------------------------------- | | `plot_heatmap_cmap` | The color map of the heatmap (palette name, single color, list of hex colors, or colormap). **TYPE:** \`str | | `plot_heatmap_alpha` | The alpha value of the heatmap. **TYPE:** \`float | | `plot_heatmap_font_size` | The font size of the heatmap. **TYPE:** \`int | | `plot_heatmap_font_color` | The font color of the heatmap. **TYPE:** \`str | | `plot_heatmap_font_style` | The font style of the heatmap. **TYPE:** \`FONT_STYLE | | `plot_heatmap_font_weight` | The font weight of the heatmap. **TYPE:** \`FONT_WEIGHT | | `plot_heatmap_frame_color` | The color of the frame always drawn around heatmap axes. **TYPE:** \`str | | `plot_heatmap_edge_width` | The width of the borders drawn between the cells (0 draws none). **TYPE:** \`int | | `plot_heatmap_edge_color` | The color of the borders drawn between the cells. **TYPE:** \`str | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `valfmt` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | # ContourChart A surface sampled on a grid, as iso-lines or filled bands. The [Contour Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/contourchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.ContourChart ``` ContourChart( data: ContourDataAttrs | list[ContourDataAttrs], *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | datetime | None = None, xmax: int | float | datetime | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, filled: bool | None = None, levels: ( CONTOUR_LEVELS | str | int | list[float] | None ) = None, show_labels: bool | None = None, show_colorbars: bool | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( ContourStyleAttrs | list[ContourStyleAttrs | None] | None ) = None, norm: str | list[str | None] | None = None, vmin: float | list[float | None] | None = None, vmax: float | list[float | None] | None = None, valfmt: ( VALUE_FORMAT | str | list[str | None] | None ) = None, xticks: ( list[int | float | datetime] | list[list[int | float | datetime]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, colorbar: ( ColorbarSettingAttrs | list[ColorbarSettingAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the contour chart. A contour chart draws a surface sampled on a grid — a loss landscape, a 2-D density, a terrain — as iso-lines of equal value, or as filled bands between them. Use it to read the shape of a function of two variables: where its minima and ridges sit and how steeply it changes. Lines overlay on other charts and on each other; fills stand alone, with an optional colorbar. For a per-cell view of a matrix use Heatmap; for the raw points behind a density use ScatterChart. Examples: ``` >>> from datachart.charts import ContourChart >>> figure = ContourChart( ... data={ ... "x": [0, 1, 2], ... "y": [0, 1, 2], ... "z": [ ... [0, 1, 4], ... [1, 2, 5], ... [4, 5, 8], ... ], ... }, ... title="Basic Contour Chart", ... xlabel="X", ... ylabel="Y" ... ) ``` | PARAMETER | DESCRIPTION | | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The gridded surface(s): a dictionary with the 2-D z grid and the optional x and y axis values (one per column and per row of z, the indices by default), or a list of them for multiple charts/subplots. **TYPE:** \`ContourDataAttrs | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. Used as legend labels. **TYPE:** \`str | | `emphasis` | The emphasis role(s) for individual line contours, aligned like style: "background" mutes a chart (theme muted color, lowered alpha, behind the others, no legend entry), "highlight" bolds it and brings it to the front, None leaves it unchanged. Not supported for filled contours: passing a value with filled=True raises ValueError. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the line contours matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against a summary of each contour's own z values, chosen by by: "mean" (default), "median", "min", "max", or "sum". An explicit emphasis role wins, and a count ranks across every contour. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. Off by default for filled contours. **TYPE:** \`SHOW_GRID | | `filled` | Whether to fill the bands between the levels (colored by the colormap) instead of drawing iso-lines (in the chart's color). **TYPE:** \`bool | | `levels` | Which levels cut the surface: a rule of CONTOUR_LEVELS ("auto", the default, leaves the choice to matplotlib), a target level count, or an explicit list of level values. Filled values beyond a list's ends take the end colors, and the colorbar marks the overflow. **TYPE:** \`CONTOUR_LEVELS | | `show_labels` | Whether to write the level values along the iso-lines. **TYPE:** \`bool | | `show_colorbars` | Whether to show the colorbar(s) of filled contours. **TYPE:** \`bool | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `scalex` | The x-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the contour chart(s). **TYPE:** \`ContourStyleAttrs | | `norm` | Value normalization method(s) of the colormap. **TYPE:** \`str | | `vmin` | Minimum value(s) for normalization. **TYPE:** \`float | | `vmax` | Maximum value(s) for normalization. **TYPE:** \`float | | `valfmt` | Format string(s) for the inline level labels, with the value named x (e.g., "{x:.1f}"). See VALUE_FORMAT. **TYPE:** \`VALUE_FORMAT | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `colorbar` | The colorbar setting(s): label, location, tick format, and tick positions. See ColorbarSettingAttrs. **TYPE:** \`ColorbarSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------------- | | `plt.Figure` | The figure containing the contour chart. | ## Data Each record in `data` is a [`ContourDataAttrs`](#datachart.typings.ContourDataAttrs). ### datachart.typings.ContourDataAttrs Bases: `TypedDict` The data attributes for the contour chart. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------------------------------------- | | `x` | The x-axis values, one per column of z. Defaults to the column indices. **TYPE:** \`list\[int | | `y` | The y-axis values, one per row of z. Defaults to the row indices. **TYPE:** \`list\[int | | `z` | The 2-D grid of surface values, one row per y and one column per x. **TYPE:** \`list\[list\[int | ## Style `style` takes the keys of [`ContourStyleAttrs`](#datachart.typings.ContourStyleAttrs). The chart also reads the shared groups it draws: reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.ContourStyleAttrs Bases: `TypedDict` The typing for the contour chart style. | ATTRIBUTE | DESCRIPTION | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_contour_color` | The color of the iso-lines; None takes the panel's color cycle. **TYPE:** \`str | | `plot_contour_cmap` | The colormap of the filled bands (palette name, single color, list of hex colors, or colormap); None takes the heatmap colormap. Iso-lines use it only when set. **TYPE:** \`str | | `plot_contour_line_width` | The width of the iso-lines; None takes the line chart width. **TYPE:** \`int | | `plot_contour_line_style` | The style of the iso-lines. **TYPE:** \`LINE_STYLE | | `plot_contour_alpha` | The alpha value of the contour. **TYPE:** \`float | | `plot_contour_zorder` | The z-order of the contour. **TYPE:** \`int | | `plot_contour_label_font_size` | The font size of the inline level labels; None takes the general font size minus two. **TYPE:** \`int | | `plot_contour_label_font_color` | The color of the inline level labels; None takes the line color. **TYPE:** \`str | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `levels` | [`CONTOUR_LEVELS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.CONTOUR_LEVELS) | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `valfmt` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | # HexbinChart Point density on the plane, as colored hexagons. The [Hexbin Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/hexbinchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.HexbinChart ``` HexbinChart( data: HexbinDataAttrs | list[HexbinDataAttrs], *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: None = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, xmin: int | float | datetime | None = None, xmax: int | float | datetime | None = None, ymin: int | float | None = None, ymax: int | float | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, show_colorbars: bool = True, aspect_ratio: ASPECT_RATIO | str | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, subplots: bool | None = None, max_cols: int | None = None, sharex: bool | None = None, sharey: bool | None = None, style: ( HexbinStyleAttrs | list[HexbinStyleAttrs | None] | None ) = None, gridsize: int | list[int | None] | None = None, reduce: ( HEXBIN_REDUCE | str | list[str | None] | None ) = None, mincnt: int | list[int | None] | None = None, norm: str | list[str | None] | None = None, vmin: float | list[float | None] | None = None, vmax: float | list[float | None] | None = None, valfmt: ( VALUE_FORMAT | str | list[str | None] | None ) = None, xticks: ( list[int | float | datetime] | list[list[int | float | datetime]] | None ) = None, xticklabels: list[str] | list[list[str]] | None = None, xtickrotate: int | list[int | None] | None = None, yticks: ( list[int | float] | list[list[int | float]] | None ) = None, yticklabels: list[str] | list[list[str]] | None = None, ytickrotate: int | list[int | None] | None = None, xticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, yticks_format: ( VALUE_FORMAT | DATE_FORMAT | str | None ) = None, vlines: ( VLineSettingAttrs | list[VLineSettingAttrs] | list[ VLineSettingAttrs | list[VLineSettingAttrs] | None ] | None ) = None, hlines: ( HLineSettingAttrs | list[HLineSettingAttrs] | list[ HLineSettingAttrs | list[HLineSettingAttrs] | None ] | None ) = None, vspans: ( VSpanSettingAttrs | list[VSpanSettingAttrs] | list[ VSpanSettingAttrs | list[VSpanSettingAttrs] | None ] | None ) = None, hspans: ( HSpanSettingAttrs | list[HSpanSettingAttrs] | list[ HSpanSettingAttrs | list[HSpanSettingAttrs] | None ] | None ) = None, colorbar: ( ColorbarSettingAttrs | list[ColorbarSettingAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the hexbin chart. A hexbin chart tiles the plane with hexagons and colors each by the number of points falling in it — or, with a per-point `c`, by an aggregate of those values. Use it where a scatter chart turns into an opaque blob: thousands of points, overlapping clusters, or a value that varies across the plane. For the points themselves use ScatterChart; for a smooth density estimate use ContourChart on stats.kde2d. Examples: ``` >>> from datachart.charts import HexbinChart >>> figure = HexbinChart( ... data={ ... "x": [0.1, 0.4, 0.5, 1.2, 1.3, 2.0], ... "y": [0.2, 0.3, 0.6, 1.1, 1.4, 2.1], ... }, ... title="Basic Hexbin Chart", ... xlabel="X", ... ylabel="Y" ... ) ``` | PARAMETER | DESCRIPTION | | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The points to bin: a dictionary with the x and y columns and an optional c column of per-point values, or a list of them for multiple charts/subplots. **TYPE:** \`HexbinDataAttrs | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | Not supported: a hexbin chart is a single colormapped layer with no series to mute or highlight. Passing a value raises ValueError. **TYPE:** `None` **DEFAULT:** `None` | | `emphasis_rule` | A rule that highlights the bins matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against each bin's aggregated value (its count, or c reduced by reduce). Bins exist only once drawn, so a count ranks the bins of each chart on its own, and an empty bin never matches. The rule takes no by. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `xmin` | The minimum x-axis value. **TYPE:** \`int | | `xmax` | The maximum x-axis value. **TYPE:** \`int | | `ymin` | The minimum y-axis value. **TYPE:** \`int | | `ymax` | The maximum y-axis value. **TYPE:** \`int | | `show_legend` | Whether to show the legend; it lists the labelled reference lines and bands. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. Off by default: the hexagons cover it. **TYPE:** \`SHOW_GRID | | `show_colorbars` | Whether to show the colorbar(s). **TYPE:** `bool` **DEFAULT:** `True` | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `scalex` | The x-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `scaley` | The y-axis scale (e.g., "log", "linear"). **TYPE:** \`SCALE | | `subplots` | Whether to create separate subplots for each chart. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots (when subplots=True). **TYPE:** \`int | | `sharex` | Whether to share the x-axis in subplots. **TYPE:** \`bool | | `sharey` | Whether to share the y-axis in subplots. **TYPE:** \`bool | | `style` | Style configuration(s) for the hexbin chart(s). **TYPE:** \`HexbinStyleAttrs | | `gridsize` | The number of hexagons across the x-axis; the plot_hexbin_gridsize config value by default. **TYPE:** \`int | | `reduce` | How the c values in a hexagon collapse into its color, one of HEXBIN_REDUCE (the mean by default). Ignored without c, where every hexagon shows its point count. **TYPE:** \`HEXBIN_REDUCE | | `mincnt` | The point count below which a hexagon stays blank; every hexagon is drawn by default. **TYPE:** \`int | | `norm` | Value normalization method(s) of the colormap; "log" spreads heavy-tailed counts. **TYPE:** \`str | | `vmin` | Minimum value(s) for normalization. **TYPE:** \`float | | `vmax` | Maximum value(s) for normalization. **TYPE:** \`float | | `valfmt` | Format string(s) for the colorbar tick labels, with the value named x (e.g., "{x:.0f}"). See VALUE_FORMAT. The format field of the colorbar setting wins when set. **TYPE:** \`VALUE_FORMAT | | `xticks` | Custom x-axis tick positions. **TYPE:** \`list\[int | | `xticklabels` | Custom x-axis tick labels. **TYPE:** \`list[str] | | `xtickrotate` | Rotation angle for x-axis tick labels. **TYPE:** \`int | | `yticks` | Custom y-axis tick positions. **TYPE:** \`list\[int | | `yticklabels` | Custom y-axis tick labels. **TYPE:** \`list[str] | | `ytickrotate` | Rotation angle for y-axis tick labels. **TYPE:** \`int | | `xticks_format` | The x-axis tick label format: a DATE_FORMAT member or strftime pattern on a datetime axis, else a VALUE_FORMAT member or "{x:.1f}" style string. **TYPE:** \`VALUE_FORMAT | | `yticks_format` | The y-axis tick label format, as xticks_format. **TYPE:** \`VALUE_FORMAT | | `vlines` | Vertical line(s) to plot. **TYPE:** \`VLineSettingAttrs | | `hlines` | Horizontal line(s) to plot. **TYPE:** \`HLineSettingAttrs | | `vspans` | Vertical reference band(s) to shade, between two x positions. **TYPE:** \`VSpanSettingAttrs | | `hspans` | Horizontal reference band(s) to shade, between two y positions. **TYPE:** \`HSpanSettingAttrs | | `colorbar` | The colorbar setting(s): label, location, tick format, and tick positions. See ColorbarSettingAttrs. **TYPE:** \`ColorbarSettingAttrs | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------- | | `plt.Figure` | The figure containing the hexbin chart. | ## Data Each record in `data` is a [`HexbinDataAttrs`](#datachart.typings.HexbinDataAttrs). ### datachart.typings.HexbinDataAttrs Bases: `TypedDict` The data attributes for the hexbin chart. | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | `x` | The x values of the points. **TYPE:** \`list\[int | | `y` | The y values of the points, one per x. **TYPE:** \`list\[int | | `c` | The value of each point, one per x; when given, every hexagon shows their reduce aggregate instead of its point count. **TYPE:** \`list\[int | ## Style `style` takes the keys of [`HexbinStyleAttrs`](#datachart.typings.HexbinStyleAttrs). The chart also reads the shared groups it draws: reference lines ([`VLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VLineStyleAttrs) and [`HLineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HLineStyleAttrs)), reference bands ([`VSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.VSpanStyleAttrs) and [`HSpanStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.HSpanStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.HexbinStyleAttrs Bases: `TypedDict` The typing for the hexbin chart style. | ATTRIBUTE | DESCRIPTION | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_hexbin_cmap` | The colormap of the hexagons (palette name, single color, list of hex colors, or colormap); None takes the heatmap colormap. **TYPE:** \`str | | `plot_hexbin_alpha` | The alpha value of the hexagons. **TYPE:** \`float | | `plot_hexbin_edge_width` | The width of the hexagon edges; 0 draws none. **TYPE:** \`int | | `plot_hexbin_edge_color` | The color of the hexagon edges. **TYPE:** \`str | | `plot_hexbin_gridsize` | The number of hexagons across the x-axis when the chart sets no gridsize. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `reduce` | [`HEXBIN_REDUCE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.HEXBIN_REDUCE) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | | `scalex` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `scaley` | [`SCALE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCALE) | | `norm` | [`NORMALIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NORMALIZE) | | `valfmt` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `xticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `yticks_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.DATE_FORMAT) | | `colorbar={"location": ..., "format": ..., "orientation": ...}` | [`COLORBAR_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.COLORBAR_LOCATION), [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT), [`ORIENTATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ORIENTATION) | # ParallelCoords Each record as a polyline across one axis per dimension. The [Parallel Coordinates guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/parallelcoords/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.ParallelCoords ``` ParallelCoords( data: ( list[ParallelCoordsDataPointAttrs] | list[list[ParallelCoordsDataPointAttrs]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: ( EMPHASIS | str | list[str | None] | None ) = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, aspect_ratio: ASPECT_RATIO | str | None = None, style: ( ParallelCoordsStyleAttrs | list[ParallelCoordsStyleAttrs | None] | None ) = None, dimensions: list[str] | list[list[str]] | None = None, hue: str | list[str | None] | None = None, category_orders: dict[str, list[str]] | None = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the parallel coordinates chart. Parallel coordinates draw each record as a polyline across one vertical axis per dimension. Use it to explore multivariate data: clusters show as bundles of similar lines, and correlations between neighboring dimensions show as parallel or crossing segments. Works best with a handful of dimensions; color the records by group with `hue` to compare groups. Examples: ``` >>> from datachart.charts import ParallelCoords >>> figure = ParallelCoords( ... data=[ ... {"sepal_length": 5.1, "sepal_width": 3.5, "petal_length": 1.4, "petal_width": 0.2, "species": "setosa"}, ... {"sepal_length": 4.9, "sepal_width": 3.0, "petal_length": 1.4, "petal_width": 0.2, "species": "setosa"}, ... {"sepal_length": 7.0, "sepal_width": 3.2, "petal_length": 4.7, "petal_width": 1.4, "species": "versicolor"}, ... ], ... title="Iris Dataset", ... hue="species", ... dimensions=["sepal_length", "sepal_width", "petal_length", "petal_width"], ... show_legend=True ... ) ``` | PARAMETER | DESCRIPTION | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The data points for the chart. Each data point is a dictionary where keys are dimension names and values are numeric or string values. Can optionally include a hue key for categorical coloring. **TYPE:** \`list[ParallelCoordsDataPointAttrs] | | `title` | The title of the chart. **TYPE:** \`str | | `xlabel` | The x-axis label. **TYPE:** \`str | | `ylabel` | The y-axis label. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | The emphasis role(s), aligned with the data rows (a single value applies to every row): "background" mutes a row (theme muted color, lowered alpha, thinner line, behind the others, no hue legend entry), "highlight" bolds it and brings it to the front among the data rows, None leaves it unchanged. **TYPE:** \`EMPHASIS | | `emphasis_rule` | A rule that highlights the data rows matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against each row's numeric hue value; no hue, or a non-numeric one, raises. An explicit emphasis role wins, and a count ranks across the rows of every chart. The rule takes no by. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `show_legend` | Whether to show the legend (for hue categories). **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `aspect_ratio` | The aspect ratio of the axes ("auto" or "equal"). See ASPECT_RATIO. **TYPE:** \`ASPECT_RATIO | | `style` | Style configuration(s) for the lines. **TYPE:** \`ParallelCoordsStyleAttrs | | `dimensions` | List of dimension names to include and their order. If None, all columns (except hue) are auto-detected. With several data sets, a flat list applies to every set and a list of lists gives one list per set; every set shares one axis, so the lists must be equal. **TYPE:** \`list[str] | | `hue` | The key name in data for line coloring. String values color categorically: data points with the same hue value get the same color from color_parallel_hue. Numeric values color continuously along the theme's color_parallel_hue_continuous ramp, which spans every row, muted ones included. **TYPE:** \`str | | `category_orders` | Dictionary mapping dimension names to lists of category values in the desired order. Example: {"rating": ["Low", "Medium", "High"]}. Categories not in the list will be appended at the end (sorted). **TYPE:** \`dict\[str, list[str]\] | | `texts` | Text annotation(s) to draw. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ----------------------------------------------------- | | `plt.Figure` | The figure containing the parallel coordinates chart. | ## Data Each record in `data` is a [`ParallelCoordsDataPointAttrs`](#datachart.typings.ParallelCoordsDataPointAttrs); the `hue` parameter renames its keys. ### datachart.typings.ParallelCoordsDataPointAttrs Bases: `TypedDict` The data point attributes for the parallel coordinates chart. A dictionary where keys are dimension names and values are numeric values. Can optionally include a 'hue' key for categorical coloring. | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------ | | `hue` | The category for color grouping. **TYPE:** \`str | ## Style `style` takes the keys of [`ParallelCoordsStyleAttrs`](#datachart.typings.ParallelCoordsStyleAttrs). The chart also reads the shared groups it draws: text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.ParallelCoordsStyleAttrs Bases: `TypedDict` The typing for the parallel coordinates chart style. | ATTRIBUTE | DESCRIPTION | | ----------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | `plot_parallel_color` | The line color. **TYPE:** \`str | | `plot_parallel_alpha` | The alpha value of the lines. **TYPE:** \`float | | `plot_parallel_width` | The line width. **TYPE:** \`int | | `plot_parallel_style` | The line style. **TYPE:** \`LINE_STYLE | | `plot_parallel_marker` | The marker style for data points. **TYPE:** \`LINE_MARKER | | `plot_parallel_zorder` | The draw order of data lines. **TYPE:** \`int | | `plot_parallel_axis_color` | The vertical axis line color. **TYPE:** \`str | | `plot_parallel_axis_width` | The vertical axis line width. **TYPE:** \`int | | `plot_parallel_axis_zorder` | The vertical axis line draw order. **TYPE:** \`int | | `plot_parallel_tick_color` | The tick mark color. **TYPE:** \`str | | `plot_parallel_tick_width` | The tick mark line width. **TYPE:** \`int | | `plot_parallel_tick_length` | The tick mark length. **TYPE:** \`float | | `plot_parallel_tick_label_size` | The tick label font size. **TYPE:** \`int | | `plot_parallel_tick_label_color` | The tick label font color. **TYPE:** \`str | | `plot_parallel_tick_label_bg_color` | The tick label background color; None draws no box and strokes the label with the value halo instead. **TYPE:** \`str | | `plot_parallel_tick_label_bg_alpha` | The tick label background alpha. **TYPE:** \`float | | `plot_parallel_dim_label_size` | The dimension label font size. **TYPE:** \`int | | `plot_parallel_dim_label_color` | The dimension label font color. **TYPE:** \`str | | `plot_parallel_dim_label_rotation` | The dimension label rotation. **TYPE:** \`int | | `plot_parallel_dim_label_pad` | The dimension label padding from axis. **TYPE:** \`int | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `emphasis` | [`EMPHASIS`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.EMPHASIS) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | | `aspect_ratio` | [`ASPECT_RATIO`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.ASPECT_RATIO) | # NetworkChart Nodes joined by edges, placed by a layout. The [Network Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/networkchart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.NetworkChart ``` NetworkChart( data: ( NetworkSingleChartAttrs | list[NetworkSingleChartAttrs] ), *, layout: NETWORK_LAYOUT | str | None = None, directed: bool | None = None, seed: int | None = None, label_position: ( NETWORK_LABEL_POSITION | str | None ) = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, title: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: None = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, subplots: bool | None = None, max_cols: int | None = None, style: ( NetworkStyleAttrs | list[NetworkStyleAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the network chart. A network chart draws relational data as a node-link diagram — module dependencies, who works with whom, co-occurring terms, flows between peers. Nodes are placed by a layout and joined by edges; an edge's weight sets its width, a node's size its marker area, its group its color. Use it when the question is what is connected to what; for weighted flows through ordered stages use SankeyChart. Every edge is its own patch and the spring layout weighs every pair of nodes, so the chart is meant for networks that can be read, not for whole graphs. Without a problem: up to about 1,000 nodes and 3,000 edges under the spring layout (a few seconds), up to about 5,000 nodes and 15,000 edges under the circular or fixed layout (under a minute). Beyond that the spring layout grows with the square of the node count — 2,000 nodes take half a minute, 5,000 several minutes and gigabytes of memory — and every layout pays a few milliseconds per edge to draw and again to save. Aggregate or filter a larger graph first. Examples: ``` >>> from datachart.charts import NetworkChart >>> figure = NetworkChart( ... data={ ... "edges": [ ... {"source": "core", "target": "utils"}, ... {"source": "cli", "target": "core"}, ... {"source": "api", "target": "core"}, ... {"source": "web", "target": "api"}, ... ] ... }, ... directed=True, ... title="Module dependencies", ... ) ``` | PARAMETER | DESCRIPTION | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The chart data: a {"nodes": [...], "edges": [...]} dict, or a list of such dicts drawing one network per subplot. An edge is a {"source", "target"} record naming node ids, with an optional weight above zero. A node is an {"id"} record with an optional label (defaults to the id; an empty string draws nothing), size above zero, group (a node without one draws in the edge color beside grouped nodes), and emphasis role: "background" mutes the node, its label, and its edges, "highlight" strokes its border. nodes may be omitted; the node set is then read from the edges in first-seen order. **TYPE:** \`NetworkSingleChartAttrs | | `layout` | How the nodes are placed: a NETWORK_LAYOUT constant (default NETWORK_LAYOUT.SPRING); on a disconnected network, SPRING and WEIGHTED place each connected part on its own, side by side, with the unlinked nodes on a ring around them. WEIGHTED lets each edge's weight set how hard it pulls its nodes together; GROUPED clusters the nodes by group, arranges the clusters by the summed weight of the edges between them, and marks each with a disc in the group color (plot_network_group_alpha). FIXED reads each node's x/y, each between 0 and 1, and draws that space inside the margin the other layouts keep. The three spring layouts cost the square of the node count; past about 1,000 nodes prefer CIRCULAR or FIXED. **TYPE:** \`NETWORK_LAYOUT | | `directed` | Whether the edges end in an arrowhead at the target. When False (the default), an edge and its reverse draw as one line. **TYPE:** \`bool | | `seed` | The seed of the spring layouts (default 0); another seed gives another arrangement of the same data. **TYPE:** \`int | | `label_position` | Where the node names print: a NETWORK_LABEL_POSITION constant (default NETWORK_LABEL_POSITION.CENTER, or the theme's chart_default_node_label_position). **TYPE:** \`NETWORK_LABEL_POSITION | | `show_values` | Whether to write each edge's weight at its midpoint. **TYPE:** \`bool | | `value_format` | The format of the edge values: a VALUE_FORMAT constant (default VALUE_FORMAT.DEFAULT) or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `show_legend` | Whether to list the node groups in a legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `title` | The title of the chart. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | Not supported: emphasis is set per node through its emphasis key. Passing a value raises ValueError. **TYPE:** `None` **DEFAULT:** `None` | | `emphasis_rule` | A rule that highlights the nodes matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against each node's size; a node without one raises. A node's own emphasis key wins. The rule takes no by. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `subplots` | Whether to show each chart in its own subplot; several charts always split into subplots. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots. **TYPE:** \`int | | `style` | Style configuration(s) for the chart(s). The edge geometry, plot_network_edge_style, takes ARROW_STYLE.CURVE (default) or ARROW_STYLE.STRAIGHT; the arrowhead comes from directed. **TYPE:** \`NetworkStyleAttrs | | `texts` | Text annotation(s) to draw. Data coordinates are the 0–1 layout space, so under FIXED a text at a node's x/y lands on that node. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------------- | | `plt.Figure` | The figure containing the network chart. | | RAISES | DESCRIPTION | | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If emphasis is given, layout is unknown, the records are malformed (a node without an id or a repeated id, an edge naming an unknown node or joining a node to itself, a size or weight not above zero, a node without x/y, or with one outside 0–1, under the fixed layout, an emphasis that is not a role), or plot_network_edge_style is a headed connector look. | ## Data `data` is one [`NetworkSingleChartAttrs`](#datachart.typings.NetworkSingleChartAttrs), or a list of them for subplots, with [`NetworkNodeAttrs`](#datachart.typings.NetworkNodeAttrs) and [`NetworkEdgeAttrs`](#datachart.typings.NetworkEdgeAttrs) inside. ### datachart.typings.NetworkSingleChartAttrs Bases: `TypedDict` The single chart attributes for the network chart. | ATTRIBUTE | DESCRIPTION | | ---------- | ----------------------------------------------------------------------------------- | | `nodes` | The nodes; inferred from the edges when omitted. **TYPE:** \`list[NetworkNodeAttrs] | | `edges` | The edges. **TYPE:** `list[NetworkEdgeAttrs]` | | `subtitle` | The subtitle of the chart. **TYPE:** \`str | | `style` | The style of the chart. **TYPE:** \`NetworkStyleAttrs | | `texts` | The text annotations to be drawn. **TYPE:** \`TextSettingAttrs | ### datachart.typings.NetworkNodeAttrs Bases: `TypedDict` The node record attributes for the network chart. | ATTRIBUTE | DESCRIPTION | | ---------- | ---------------------------------------------------------------------------------------------------- | | `id` | The node identifier the edges refer to; unique within a chart. **TYPE:** `str` | | `label` | The drawn label; defaults to id. An empty string draws nothing. **TYPE:** \`str | | `size` | The node size, mapped by square root to marker area; must be greater than 0. **TYPE:** \`int | | `group` | The group the node is colored by. **TYPE:** \`str | | `emphasis` | The emphasis role of the node. **TYPE:** \`EMPHASIS | | `x` | The node's horizontal position in the 0–1 layout space; NETWORK_LAYOUT.FIXED only. **TYPE:** \`float | | `y` | The node's vertical position in the 0–1 layout space; NETWORK_LAYOUT.FIXED only. **TYPE:** \`float | ### datachart.typings.NetworkEdgeAttrs Bases: `TypedDict` The edge record attributes for the network chart. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------------------------------------------------------------- | | `source` | The id of the node the edge leaves. **TYPE:** `str` | | `target` | The id of the node the edge enters. **TYPE:** `str` | | `weight` | The edge weight, mapped to its width and, under the weighted layouts, its pull; must be greater than 0. **TYPE:** \`int | ## Style `style` takes the keys of [`NetworkStyleAttrs`](#datachart.typings.NetworkStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.NetworkStyleAttrs Bases: `TypedDict` The typing for the network chart style. | ATTRIBUTE | DESCRIPTION | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_network_node_color` | The node marker color; overrides the color cycle. **TYPE:** \`str | | `plot_network_node_alpha` | The alpha value of the node markers. **TYPE:** \`float | | `plot_network_node_marker` | The node marker shape. **TYPE:** \`LINE_MARKER | | `plot_network_node_size` | The marker area of a node without size. **TYPE:** \`int | | `plot_network_node_size_min` | The marker area of the smallest sized node. **TYPE:** \`int | | `plot_network_node_size_max` | The marker area of the largest sized node. **TYPE:** \`int | | `plot_network_node_edge_color` | The node stroke color. **TYPE:** \`str | | `plot_network_node_edge_width` | The node stroke width. **TYPE:** \`int | | `plot_network_edge_style` | The edge geometry: ARROW_STYLE.CURVE or ARROW_STYLE.STRAIGHT. **TYPE:** \`ARROW_STYLE | | `plot_network_edge_curve` | The bow of a curved edge; the sign picks the side. **TYPE:** \`float | | `plot_network_edge_color` | The edge color. **TYPE:** \`str | | `plot_network_edge_alpha` | The edge alpha. **TYPE:** \`float | | `plot_network_edge_width_min` | The width of the lightest edge, and of an edge without weight. **TYPE:** \`int | | `plot_network_edge_width_max` | The width of the heaviest edge. **TYPE:** \`int | | `plot_network_highlight_edge_width` | The stroke width of a highlighted node. **TYPE:** \`float | | `plot_network_label_halo_width` | The width of the halo, in the axes face color, behind labels; 0 disables it. **TYPE:** \`float | | `plot_network_group_alpha` | The alpha of the disc in the group color behind each cluster of the grouped layout; 0 disables it. **TYPE:** \`float | | `plot_network_group_linestyle` | Draws each cluster's mark as a ring in this line style and the edge color instead of a disc. None draws the disc. **TYPE:** \`LINE_STYLE | | `plot_network_edge_ink_stroke` | The pen the edges are drawn with, as for plot_ink_stroke, plus swell (the pressure swell amplitude) and noise (the grain); a directed edge draws as a stroked shaft with a small head. None draws plain edges. **TYPE:** \`dict[str, float] | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `layout` | [`NETWORK_LAYOUT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LAYOUT) | | `label_position` | [`NETWORK_LABEL_POSITION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.NETWORK_LABEL_POSITION) | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | # ScatterMatrix A scatter chart for every pair of dimensions, distributions on the diagonal. The [Scatter Matrix guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/scattermatrix/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.ScatterMatrix ``` ScatterMatrix( data: ( dict[str, list[Any]] | list[ScatterMatrixDataPointAttrs] ), *, dimensions: list[str] | None = None, hue: str | None = None, diagonal: SCATTER_MATRIX_DIAGONAL | str | None = None, lower_only: bool | None = None, show_regression: bool | None = None, show_correlation: bool | None = None, sharex: bool | None = None, sharey: bool | None = None, title: str | None = None, figsize: tuple[float, float] | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, style: StyleAttrs | None = None ) -> plt.Figure ``` Creates a scatter matrix. Every pair of numeric dimensions gets a scatter chart, and each dimension's own distribution sits on the diagonal. Use it to scan many variables for relationships, clusters and outliers at once, optionally split by a categorical `hue`. For two variables use ScatterChart; for many dimensions per observation read as lines, use ParallelCoords. The figure is a grid: it nests inside Grid and cannot be overlaid with Panel. Examples: ``` >>> from datachart.charts import ScatterMatrix >>> figure = ScatterMatrix( ... data={ ... "length": [5.1, 4.9, 6.3, 5.8, 7.1, 6.5], ... "width": [3.5, 3.0, 3.3, 2.7, 3.0, 3.2], ... "petal": [1.4, 1.4, 6.0, 5.1, 5.9, 5.1], ... "species": ["a", "a", "b", "b", "b", "b"], ... }, ... hue="species", ... ) >>> >>> # records work too; correlations above the diagonal >>> records = [ ... {"length": 5.1, "width": 3.5, "petal": 1.4}, ... {"length": 6.3, "width": 3.3, "petal": 6.0}, ... {"length": 5.8, "width": 2.7, "petal": 5.1}, ... ] >>> figure = ScatterMatrix( ... data=records, ... diagonal="kde", ... show_correlation=True, ... show_regression=True, ... ) ``` | PARAMETER | DESCRIPTION | | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The observations: a dict of equal-length columns, or a list of records. Missing values (None) are left out pair by pair. **TYPE:** \`dict\[str, list[Any]\] | | `dimensions` | The numeric columns to plot, in order. Defaults to every numeric column except the hue, in input order. **TYPE:** \`list[str] | | `hue` | The column whose categories colour the points, one colour per category and one legend for the whole figure. A numeric column raises. **TYPE:** \`str | | `diagonal` | What each dimension's own cell shows: a histogram ("hist", default), a density curve ("kde"), or nothing ("none"). See SCATTER_MATRIX_DIAGONAL. **TYPE:** \`SCATTER_MATRIX_DIAGONAL | | `lower_only` | Whether to leave the cells above the diagonal empty. Wins over show_correlation. **TYPE:** \`bool | | `show_regression` | Whether to draw a least-squares line per hue group in every scatter cell. **TYPE:** \`bool | | `show_correlation` | Whether to replace the scatters above the diagonal with the Pearson correlation of each hue group. **TYPE:** \`bool | | `sharex` | Whether the cells of a column share one x-axis and only the bottom row labels its ticks (default True). **TYPE:** \`bool | | `sharey` | Whether the cells of a row share one y-axis and only the left column labels its ticks (default True). A diagonal cell's axis shows its row's scale too; its histogram or density curve keeps its own, unlabelled height. **TYPE:** \`bool | | `title` | The title of the figure. **TYPE:** \`str | | `figsize` | The size of the figure; the cells stay square inside it. Defaults to 2.2 inches per cell, shrunk so the figure is at most 6.3 inches (a full page width) wide. **TYPE:** \`tuple[float, float] | | `show_legend` | Whether to show the legend of the hue groups (default True when hue is set). **TYPE:** \`bool | | `legend` | The legend setting: title, column count, alignment and location. The title defaults to the hue column's name; an empty string hides it. The location is one of the four LEGEND_LOCATION.OUTSIDE\_\* edges (default right); a legend above or below the matrix lays its entries out in one row. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show in the cells (e.g., "both", "x", "y"); False draws none. **TYPE:** \`SHOW_GRID | | `style` | Style attributes for every cell: the scatter, histogram, plot text and plot_scatter_matrix\_\* keys. See ScatterMatrixStyleAttrs. **TYPE:** \`StyleAttrs | | RETURNS | DESCRIPTION | | ------------ | ----------------------------------------- | | `plt.Figure` | The figure containing the scatter matrix. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `ValueError` | If data is malformed or has no numeric column, a dimension is missing or not numeric, or hue is missing, has missing values, or is numeric, or the legend location is not an outside edge. | ## Data Each record in `data` is a [`ScatterMatrixDataPointAttrs`](#datachart.typings.ScatterMatrixDataPointAttrs); the `hue` parameter renames its keys. ### datachart.typings.ScatterMatrixDataPointAttrs Bases: `TypedDict` The record attributes for the scatter matrix. A dictionary where keys are column names: numeric columns become dimensions, and one categorical column may be named as the `hue`. The same columns can be passed as one dictionary of lists instead. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------------------------------------------------------------------- | | `hue` | The category for color grouping, under the column name the hue setting names. **TYPE:** \`str | ## Style `style` takes the keys of [`StyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.StyleAttrs). Its own keys are those of [`ScatterMatrixStyleAttrs`](#datachart.typings.ScatterMatrixStyleAttrs). The chart also reads the shared groups it draws: the regression line ([`RegressionStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.RegressionStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.ScatterMatrixStyleAttrs Bases: `TypedDict` The typing for the scatter matrix style. The cells take the scatter, histogram and plot text keys; these keys style what the matrix adds on top of them. | ATTRIBUTE | DESCRIPTION | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `plot_scatter_matrix_regression_color` | The color of the regression lines under show_regression; None takes each hue group's color. **TYPE:** \`str | | `plot_scatter_matrix_regression_width` | The line width of the regression lines. **TYPE:** \`int | | `plot_scatter_matrix_regression_style` | The line style of the regression lines. **TYPE:** \`LINE_STYLE | | `plot_scatter_matrix_correlation_size` | The font size of the correlation text under show_correlation. **TYPE:** \`int | | `plot_scatter_matrix_correlation_weight` | The font weight of the correlation text. **TYPE:** \`FONT_WEIGHT | | `plot_scatter_matrix_kde_width` | The line width of the diagonal density curves. **TYPE:** \`int | | `plot_scatter_matrix_kde_alpha` | The alpha value of the fill under the diagonal density curves; 0 draws no fill. **TYPE:** \`float | | `plot_scatter_matrix_diagonal_alpha` | The alpha value of the diagonal histograms, overlaid per hue group. **TYPE:** \`float | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `diagonal` | [`SCATTER_MATRIX_DIAGONAL`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SCATTER_MATRIX_DIAGONAL) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `show_grid` | [`SHOW_GRID`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.SHOW_GRID) | # SankeyChart Weighted flows between categories, as ribbons between node columns. The [Sankey Chart guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/sankeychart/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.SankeyChart ``` SankeyChart( data: ( SankeySingleChartAttrs | list[SankeySingleChartAttrs] ), *, nodes: list[list[str]] | None = None, column_labels: list[str] | None = None, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, title: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, subplots: bool | None = None, max_cols: int | None = None, style: ( SankeyStyleAttrs | list[SankeyStyleAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the Sankey chart. A Sankey diagram draws weighted flows between categories: nodes are bars laid out in columns and each flow is a ribbon whose height carries its value — label transitions between annotators, attrition through a signup funnel, energy from source to use. Use it when the question is where a quantity goes; for the totals per category alone use BarChart. Examples: ``` >>> from datachart.charts import SankeyChart >>> figure = SankeyChart( ... data={ ... "links": [ ... {"source": "Visited", "target": "Signed up", "value": 300}, ... {"source": "Visited", "target": "Bounced", "value": 700}, ... {"source": "Signed up", "target": "Paid", "value": 90}, ... {"source": "Signed up", "target": "Churned", "value": 210}, ... ] ... }, ... title="Signup funnel", ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The chart data: a {"links": [...]} dict whose links are {"source", "target", "value"} records, or a list of such dicts drawing one Sankey per subplot. A node is the string that names it, which is also its drawn label. **TYPE:** \`SankeySingleChartAttrs | | `nodes` | The node columns, left to right, each a list of node names top to bottom. Must name every node in the links exactly once. When omitted, a node's column is its longest path from any source and nodes keep their first-seen order within a column. **TYPE:** \`list\[list[str]\] | | `column_labels` | One heading per column, drawn above it; must match the number of columns. **TYPE:** \`list[str] | | `show_values` | Whether to write each flow's value on its ribbon. **TYPE:** \`bool | | `value_format` | The format of the ribbon values: a VALUE_FORMAT constant (default VALUE_FORMAT.DEFAULT) or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `title` | The title of the chart. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | Not supported: a Sankey has no series to mute or highlight. Passing a value raises ValueError. **TYPE:** `None` **DEFAULT:** `None` | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `subplots` | Whether to show each chart in its own subplot; several charts always split into subplots. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots. **TYPE:** \`int | | `style` | Style configuration(s) for the chart(s). **TYPE:** \`SankeyStyleAttrs | | `texts` | Text annotation(s) to draw. The columns span 0–1 horizontally and the tallest column 0–1 vertically. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------- | | `plt.Figure` | The figure containing the Sankey chart. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If emphasis is given, the links are malformed (missing keys, a value not above zero, a self-link), the links form a cycle, nodes does not name exactly the linked nodes, or column_labels does not match the number of columns. | ## Data `data` is one [`SankeySingleChartAttrs`](#datachart.typings.SankeySingleChartAttrs), or a list of them for subplots, with [`SankeyLinkAttrs`](#datachart.typings.SankeyLinkAttrs) inside. ### datachart.typings.SankeySingleChartAttrs Bases: `TypedDict` The single chart attributes for the Sankey chart. | ATTRIBUTE | DESCRIPTION | | ---------- | -------------------------------------------------------------------------------- | | `links` | The flows; a node is the string that names it. **TYPE:** `list[SankeyLinkAttrs]` | | `subtitle` | The subtitle of the chart. **TYPE:** \`str | | `style` | The style of the chart. **TYPE:** \`SankeyStyleAttrs | | `texts` | The text annotations to be drawn. **TYPE:** \`TextSettingAttrs | ### datachart.typings.SankeyLinkAttrs Bases: `TypedDict` The link record attributes for the Sankey chart. | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------- | | `source` | The node the flow leaves. **TYPE:** `str` | | `target` | The node the flow enters. **TYPE:** `str` | | `value` | The size of the flow; must be greater than 0. **TYPE:** \`int | ## Style `style` takes the keys of [`SankeyStyleAttrs`](#datachart.typings.SankeyStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.SankeyStyleAttrs Bases: `TypedDict` The typing for the Sankey chart style. | ATTRIBUTE | DESCRIPTION | | ------------------------------ | ---------------------------------------------------------------------------------------------- | | `plot_sankey_node_width` | The node bar width as a fraction of the horizontal span. **TYPE:** \`float | | `plot_sankey_node_pad` | The vertical span shared by the gaps of the tallest column. **TYPE:** \`float | | `plot_sankey_node_edge_color` | The node stroke color. **TYPE:** \`str | | `plot_sankey_node_edge_width` | The node stroke width. **TYPE:** \`float | | `plot_sankey_link_color` | Which node colors a ribbon: "source", "target", or "grey". **TYPE:** \`str | | `plot_sankey_link_alpha` | The ribbon alpha. **TYPE:** \`float | | `plot_sankey_label_halo_width` | The width of the halo, in the axes face color, behind labels; 0 disables it. **TYPE:** \`float | | `plot_sankey_node_fill` | Whether the node bars are filled; False draws them as outlines. **TYPE:** \`bool | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------- | --------------------------------------------------------------------------------------------------------------------- | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | # Treemap Part-of-whole data as nested rectangles sized by value. The [Treemap guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/charts/treemap/index.md) shows every feature on real data; this page is the contract: the function, the shape of its data, the keys `style` takes, and the constant each parameter accepts. ## Function ### datachart.charts.Treemap ``` Treemap( data: ( TreemapSingleChartAttrs | list[TreemapSingleChartAttrs] ), *, show_values: bool | None = None, value_format: VALUE_FORMAT | str | None = None, show_legend: bool | None = None, legend: LegendSettingAttrs | None = None, title: str | None = None, subtitle: str | list[str | None] | None = None, emphasis: None = None, emphasis_rule: EmphasisRuleAttrs | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, subplots: bool | None = None, max_cols: int | None = None, style: ( TreemapStyleAttrs | list[TreemapStyleAttrs | None] | None ) = None, texts: ( TextSettingAttrs | list[TextSettingAttrs] | list[ TextSettingAttrs | list[TextSettingAttrs] | None ] | None ) = None ) -> plt.Figure ``` Creates the treemap. A treemap tiles part-of-whole data as rectangles whose area is the value — disk usage by folder, a budget by line, population by continent and country. A record's `children` group it, up to four levels deep: a group is a box in its color with a header band, its children inset in a lighter tint. Every level is sorted largest first and tiled so the rectangles stay near square. Use it when the question is how a whole splits; for the values alone, or for more than a handful of small parts, use BarChart. Examples: ``` >>> from datachart.charts import Treemap >>> figure = Treemap( ... data={ ... "data": [ ... {"label": "Asia", "children": [ ... {"label": "India", "value": 1429}, ... {"label": "China", "value": 1426}, ... ]}, ... {"label": "Africa", "value": 1460}, ... {"label": "Europe", "value": 742}, ... ] ... }, ... title="Population, millions", ... ) ``` | PARAMETER | DESCRIPTION | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `data` | The chart data: a {"data": [...]} dict whose records are {"label", "value"} dicts, or a list of such dicts drawing one treemap per subplot. A record may carry children, a list of records of the same shape, nesting up to four levels deep; a group then omits value or carries its children's sum. Any record may carry an emphasis role: "background" mutes the tile or group, "highlight" strokes its border; the role applies to the whole subtree, and a descendant's own role overrides it. **TYPE:** \`TreemapSingleChartAttrs | | `show_values` | Whether to write each tile's value under its label. A value that does not fit is dropped before the label. **TYPE:** \`bool | | `value_format` | The format of the tile values: a VALUE_FORMAT constant (default VALUE_FORMAT.DEFAULT) or any "{x:.1f}", "{:.1f}%", or "%g" style string. **TYPE:** \`VALUE_FORMAT | | `show_legend` | Whether to list the top-level records in a legend; it names the groups too short for a header band. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `title` | The title of the chart. **TYPE:** \`str | | `subtitle` | The subtitle(s) for individual charts. **TYPE:** \`str | | `emphasis` | Not supported: emphasis is set per record through its emphasis key. Passing a value raises ValueError. **TYPE:** `None` **DEFAULT:** `None` | | `emphasis_rule` | A rule that highlights the leaf records matching it and mutes the rest: {"above": v} or {"below": v} (strict), {"between": (lo, hi)} (inclusive), {"top": n} or {"bottom": n}, read against each leaf's value. A record's own emphasis key wins, and so does a group's, over its whole subtree. The rule takes no by. See EmphasisRuleAttrs. **TYPE:** \`EmphasisRuleAttrs | | `figsize` | The size of the figure. **TYPE:** \`FIG_SIZE | | `subplots` | Whether to show each chart in its own subplot; several charts always split into subplots. **TYPE:** \`bool | | `max_cols` | Maximum number of columns in subplots. **TYPE:** \`int | | `style` | Style configuration(s) for the chart(s). **TYPE:** \`TreemapStyleAttrs | | `texts` | Text annotation(s) to draw. The tiling spans 0–1 in both directions. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | ---------------------------------- | | `plt.Figure` | The figure containing the treemap. | | RAISES | DESCRIPTION | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If emphasis is given, the records are malformed (a missing label, a value not above zero, a record nested past four levels, a group value that is not its children's sum), or a record's emphasis is not a role. | ## Data `data` is one [`TreemapSingleChartAttrs`](#datachart.typings.TreemapSingleChartAttrs), or a list of them for subplots, with [`TreemapRecordAttrs`](#datachart.typings.TreemapRecordAttrs) inside. ### datachart.typings.TreemapSingleChartAttrs Bases: `TypedDict` The single chart attributes for the treemap. | ATTRIBUTE | DESCRIPTION | | ---------- | -------------------------------------------------------------- | | `data` | The records to tile. **TYPE:** `list[TreemapRecordAttrs]` | | `subtitle` | The subtitle of the chart. **TYPE:** \`str | | `style` | The style of the chart. **TYPE:** \`TreemapStyleAttrs | | `texts` | The text annotations to be drawn. **TYPE:** \`TextSettingAttrs | ### datachart.typings.TreemapRecordAttrs Bases: `TypedDict` The record attributes for the treemap. | ATTRIBUTE | DESCRIPTION | | ---------- | ------------------------------------------------------------------------------------------------------------- | | `label` | The drawn label of the tile or group. **TYPE:** `str` | | `value` | The size of the tile; must be greater than 0. A group omits it or carries its children's sum. **TYPE:** \`int | | `children` | The records of a group, nesting up to four levels deep. **TYPE:** \`list[TreemapRecordAttrs] | | `emphasis` | The emphasis role of the record and its subtree; a descendant's own role overrides it. **TYPE:** \`EMPHASIS | ## Style `style` takes the keys of [`TreemapStyleAttrs`](#datachart.typings.TreemapStyleAttrs). The chart also reads the shared groups it draws: value labels ([`ValueLabelStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.ValueLabelStyleAttrs)) and text annotations ([`TextStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.TextStyleAttrs)). Every key falls back to the theme, so the same keys set the default look through [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md). ### datachart.typings.TreemapStyleAttrs Bases: `TypedDict` The typing for the treemap style. | ATTRIBUTE | DESCRIPTION | | ----------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_treemap_edge_color` | The stroke color of leaf tiles and group borders. **TYPE:** \`str | | `plot_treemap_edge_width` | The leaf tile stroke width. **TYPE:** \`float | | `plot_treemap_group_edge_width` | The width of the border around a group. **TYPE:** \`float | | `plot_treemap_group_pad` | The gap between top-level records and, at every level, the gutter between a group's border and its children, as a fraction of the span. **TYPE:** \`float | | `plot_treemap_level_shade` | How much lighter than its parent each level is, 0 to 1, applied once more per level; 0 keeps the group color. **TYPE:** \`float | | `plot_treemap_level_font_scale` | The label font scale applied once more per nesting level. **TYPE:** \`float | | `plot_treemap_min_fontsize` | The smallest font size a label shrinks to before it is dropped. **TYPE:** \`float | | `plot_treemap_highlight_edge_width` | The border width of a highlighted record. **TYPE:** \`float | | `plot_treemap_label_halo_width` | The width of the halo, in the axes face color, behind labels; 0 disables it. **TYPE:** \`float | | `plot_treemap_etch_density` | With plot_etch and a hatch cycle, how many times each nesting level repeats its top-level group's pattern, outermost first (a level past the list is blank); every box fills with the axes face so outer etching never shows through. None keeps the colored tiles. **TYPE:** \`list[int] | ## Constants The parameters that accept a constant, with the class in [datachart.constants](https://eriknovak.github.io/datachart/0.10.2/references/constants/index.md) that lists its values. | Parameter | Constant | | -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `value_format` | [`VALUE_FORMAT`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.VALUE_FORMAT) | | `legend={"location": ..., "alignment": ...}` | [`LEGEND_LOCATION`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_LOCATION), [`LEGEND_ALIGN`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.LEGEND_ALIGN) | | `figsize` | [`FIG_SIZE`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.FIG_SIZE) | # Utils Module ## datachart.utils The module containing the `utils`. The `utils` module provides a set of public utilities for the package: the composition of finished figures (`Panel`, `Grid`, `Annotate`), saving them (`save_figure`), and the statistics behind the charts (`stats`). This module exports only the public API intended for end users. Internal implementation details are located in the `_internal` submodule and should not be imported directly by external code. ## Choosing a Utility Everything here takes or returns the figure a chart function returns: three ways to compose finished figures, one to save them, and the statistics behind them. | I want to… | Use | Guide | | --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | draw several charts in one coordinate space, with a second value axis | [`Panel`](#datachart.utils.Panel) | [Panel](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/panel/index.md) | | lay charts out side by side or in rows | [`Grid`](#datachart.utils.Grid) | [Grid](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/grid/index.md) | | add notes to a figure that is already drawn | [`Annotate`](#datachart.utils.Annotate) | [Text Annotations](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/annotations/index.md) | | write a figure to disk, in one format or several | [`save_figure`](#datachart.utils.save_figure) | [Saving Figures](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/saving/index.md) | | compute the number a chart shows | [`stats`](https://eriknovak.github.io/datachart/0.10.2/references/utils/stats/index.md) | [Statistics](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/stats/index.md) | ## Composition ### datachart.utils.Panel ``` Panel( charts: list[plt.Figure | dict[str, Any]], *, title: str | None = None, xlabel: str | None = None, ylabel_left: str | None = None, ylabel_right: str | None = None, figsize: FIG_SIZE | tuple[float, float] | None = None, show_legend: bool | None = False, legend: LegendSettingAttrs | None = None, show_grid: SHOW_GRID | str | bool | None = None, auto_secondary_axis: float | None = None, xmin: float | None = None, xmax: float | None = None, ymin: float | None = None, ymax: float | None = None, ymin_right: float | None = None, ymax_right: float | None = None, scalex: SCALE | str | None = None, scaley: SCALE | str | None = None, scaley_right: SCALE | str | None = None, bar_mode: BAR_MODE | str | None = None ) -> plt.Figure ``` Overlay rendered chart figures in one coordinate space. Combines different chart types (LineChart, BarChart, ScatterChart, Histogram, BoxPlot, SwarmPlot) on a single plot, drawn in the order provided. Two value axes (primary and secondary) are supported for handling different scales. A panel has an orientation, inferred from its figures: it is horizontal when every bar chart and histogram in it is horizontal, vertical otherwise. Mixing the two orientations raises `ValueError`. The *value axis* carries the quantities — y in a vertical panel, x in a horizontal one — and the *category axis* is the other. The parameters keep their spelling but address the axis by role: `ylabel_left`/`ylabel_right`, `ymin`/`ymax`, `ymin_right`/`ymax_right` and `scaley`/`scaley_right` set the primary/secondary value axis, `xlabel`, `xmin`/`xmax` and `scalex` the category axis. In a horizontal panel the secondary value axis sits at the top, so `"y_axis": "left"` means the bottom axis and `"right"` the top one, and the legend suffixes become `(B)`/`(T)`. Line and scatter figures follow the panel: in a horizontal panel their `x` runs along the category axis and their `y` along the value axis, so the same `LineChart` overlays vertical and horizontal bars. Each axis keeps the scale its figures were built with: a figure drawn with `scaley="log"` stays log in the panel, on whichever value axis it lands. A figure that set no scale takes the one its axis resolves to. The panel's own `scalex`, `scaley` and `scaley_right` override that per axis; where two figures on one axis each set a different scale, the first one wins and the panel warns (`overlay_warn_scale_conflict` in the config). The two value axes scale independently, so linear bars on the primary axis against a log line on the secondary one is one panel. Panel figures nest: `Panel([Panel([f1, f2]), f3])` is equivalent to `Panel([f1, f2, f3])`, to any depth. A nested panel contributes its figures with their per-figure options, axis scales and `bar_mode` intact, while the other panel-level settings (title, labels, limits, ...) always come from the outermost call. Dict options on a nested panel override its per-figure options only when explicitly given. Examples: ``` >>> from datachart.charts import LineChart, BarChart >>> from datachart.utils import Panel >>> >>> bar_fig = BarChart(data=[{"label": "A", "y": 100}, {"label": "B", "y": 200}]) >>> line_fig = LineChart(data=[{"x": 0, "y": 5}, {"x": 1, "y": 15}]) >>> >>> # Bare figures: automatic axis assignment >>> combined = Panel([bar_fig, line_fig], title="Sales Analysis") >>> >>> # Panels nest: add a figure to an existing panel >>> extended = Panel([combined, line_fig]) >>> >>> # Dicts carry per-figure options >>> combined = Panel( ... [ ... {"figure": bar_fig, "y_axis": "left"}, ... {"figure": line_fig, "y_axis": "right"}, ... ], ... ylabel_left="Count", ... ylabel_right="Average", ... show_legend=True, ... ) >>> >>> # Each value axis scales on its own: linear bars, a log line >>> combined = Panel( ... [ ... {"figure": bar_fig, "y_axis": "left"}, ... {"figure": line_fig, "y_axis": "right"}, ... ], ... scaley_right="log", ... ) >>> >>> # Horizontal bars make a horizontal panel: the line runs along the >>> # categories and "right" is the top value axis >>> hbar_fig = BarChart( ... data=[{"label": "A", "y": 100}, {"label": "B", "y": 200}], ... orientation="horizontal", ... ) >>> combined = Panel( ... [hbar_fig, {"figure": line_fig, "y_axis": "right"}], ... xlabel="Category", ... ylabel_left="Count", ... ylabel_right="Average", ... ) ``` | PARAMETER | DESCRIPTION | | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `charts` | The figures to overlay. Each item is either a bare matplotlib Figure created by a datachart chart function — including another Panel figure, which flattens into this one — or a dict with a "figure" key plus optional per-figure options: - "y_axis": "left", "right", or "auto" (chart figures default to "auto"; a nested panel's figures keep their own assignment). "left"/"right" name the primary/secondary value axis — the bottom/top axis in a horizontal panel - "z_order": Integer for layering control (higher values on top) - "legend_label": Custom legend label (overrides chart subtitle) - "emphasis": "background" or "highlight" role for every layer of this figure. Background layers are muted (theme muted color, lowered alpha, behind the others) and excluded from the legend; highlight layers are bolded and brought to the front among the data layers. A nested panel's figures keep their own roles. **TYPE:** \`list\[plt.Figure | | `title` | Title for the combined chart. **TYPE:** \`str | | `xlabel` | Label for the category axis. **TYPE:** \`str | | `ylabel_left` | Label for the primary value axis. **TYPE:** \`str | | `ylabel_right` | Label for the secondary value axis (if using dual axes). **TYPE:** \`str | | `figsize` | Size of the figure (width, height) in inches. **TYPE:** \`FIG_SIZE | | `show_legend` | Whether to show the legend. **TYPE:** \`bool | | `legend` | The per-figure legend setting: title, location, column count and alignment; each field falls back to the theme. See LegendSettingAttrs. **TYPE:** \`LegendSettingAttrs | | `show_grid` | Which grid lines to show ("x", "y", "both", or None); False draws none. These name the matplotlib axes literally. **TYPE:** \`SHOW_GRID | | `auto_secondary_axis` | Threshold ratio for automatic secondary axis creation. Default is taken from config (overlay_auto_threshold, default 3.0). **TYPE:** \`float | | `xmin` | Minimum value for the category-axis limits. **TYPE:** \`float | | `xmax` | Maximum value for the category-axis limits. **TYPE:** \`float | | `ymin` | Minimum value for the primary value-axis limits. **TYPE:** \`float | | `ymax` | Maximum value for the primary value-axis limits. **TYPE:** \`float | | `ymin_right` | Minimum value for the secondary value-axis limits. **TYPE:** \`float | | `ymax_right` | Maximum value for the secondary value-axis limits. **TYPE:** \`float | | `scalex` | The category-axis scale ("linear", "log", "symlog", "asinh"). Default: the scale of the first figure that was built with one. See SCALE. **TYPE:** \`SCALE | | `scaley` | The primary value-axis scale. Default: the scale of the first figure on that axis that was built with one. **TYPE:** \`SCALE | | `scaley_right` | The secondary value-axis scale. Default: the scale of the first figure on that axis that was built with one. Inert on a polar panel, which has no secondary axis. **TYPE:** \`SCALE | | `bar_mode` | How bar and histogram series share the axis: "group" (side-by-side bars; histograms overlay), "stack" (stacked), or "overlay" (overlapping). Default: the mode of the first figure that was built with one, then the config (overlay_bar_mode, default "group"). See BAR_MODE. **TYPE:** \`BAR_MODE | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------------------- | | `plt.Figure` | A matplotlib Figure containing the overlaid charts. | | RAISES | DESCRIPTION | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If charts is empty, an item is not a figure or a valid dict, a figure cannot be overlaid (missing metadata, Grid figure), or the figures mix horizontal and vertical orientations. | ### datachart.utils.Grid ``` Grid( charts: ( list[plt.Figure | dict[str, Any]] | list[list[plt.Figure | None]] ), *, title: str | None = None, xlabel: str | None = None, ylabel: str | None = None, max_cols: int = 4, figsize: tuple[float, float] | None = None, sharex: bool = False, sharey: bool = False ) -> plt.Figure ``` Arrange rendered chart figures in a grid. Each figure's chart is redrawn into its grid cell. Nested rows define the layout directly: every inner list is one grid row, and a shorter row's cells stretch to fill the width. A flat list uses an automatic uniform grid governed by `max_cols`, with a `layout_spec` escape hatch for irregular grids (rowspans). Grids nest: a Grid figure placed in a cell occupies exactly that cell and rebuilds its internal layout inside it, to any depth. The nested grid keeps its own title (a heading spanning its subgrid) and its own sharex/sharey among its own cells; the outer grid's sharex/sharey applies only to its top-level cells. Panel figures also nest in a cell; the reverse — a Grid figure inside a Panel — stays an error. Examples: ``` >>> from datachart.charts import LineChart, BarChart, ScatterChart >>> from datachart.utils import Grid >>> >>> fig1 = LineChart(data=[{"x": i, "y": i**2} for i in range(10)], title="Line") >>> fig2 = BarChart(data=[{"label": "A", "y": 10}, {"label": "B", "y": 20}], title="Bar") >>> fig3 = ScatterChart(data=[{"x": i, "y": i * 2} for i in range(10)], title="Scatter") >>> >>> # Nested rows are the layout: fig1 spans the full top row >>> combined = Grid([[fig1], [fig2, fig3]], title="Dashboard") >>> >>> # None leaves a blank cell >>> combined = Grid([[fig1, fig2], [fig3, None]]) >>> >>> # Flat list: automatic uniform grid >>> combined = Grid([fig1, fig2, fig3], max_cols=2) >>> >>> # Grids nest: a grid figure occupies one cell of the outer grid >>> inner = Grid([[fig1, fig2], [fig3]], title="Inner") >>> combined = Grid([inner, fig1], title="Outer") >>> >>> # Nested rows can hold grid (and Panel) figures too >>> combined = Grid([[inner, fig1], [fig2]]) >>> >>> # Flat list with the layout_spec escape hatch (rowspans) >>> combined = Grid( ... [ ... {"figure": fig1, "layout_spec": {"row": 0, "col": 0, "rowspan": 2, "colspan": 1}}, ... {"figure": fig2, "layout_spec": {"row": 0, "col": 1, "rowspan": 1, "colspan": 1}}, ... {"figure": fig3, "layout_spec": {"row": 1, "col": 1, "rowspan": 1, "colspan": 1}}, ... ] ... ) ``` | PARAMETER | DESCRIPTION | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `charts` | Either nested rows — each inner list is one grid row of bare matplotlib Figures (or None for a blank cell) — or a flat list whose items are bare figures or dicts with a "figure" key and an optional "layout_spec" dict ('row', 'col', 'rowspan', 'colspan'). Nested rows and layout_spec cannot be mixed. **TYPE:** \`list\[plt.Figure | | `title` | Optional title for the combined figure. **TYPE:** \`str | | `xlabel` | Optional x-axis label for the whole grid, drawn once below every cell. A nested grid keeps its own as a footer of its cell. **TYPE:** \`str | | `ylabel` | Optional y-axis label for the whole grid, drawn once to the left of every cell. A nested grid keeps its own beside its cell. **TYPE:** \`str | | `max_cols` | Maximum number of columns for the flat-list automatic grid. **TYPE:** `int` **DEFAULT:** `4` | | `figsize` | Size of the combined figure (width, height) in inches. If None, calculated from the first figure's size. **TYPE:** \`tuple[float, float] | | `sharex` | Whether to share the x-axis across all subplots. **TYPE:** `bool` **DEFAULT:** `False` | | `sharey` | Whether to share the y-axis across all subplots. **TYPE:** `bool` **DEFAULT:** `False` | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------------------------------- | | `plt.Figure` | A new matplotlib Figure containing all charts in a grid layout. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If charts is empty, rows are mixed with flat items, a cell is invalid, or a figure cannot be composed (missing metadata). | ### datachart.utils.Annotate ``` Annotate( figure: plt.Figure, texts: TextSettingAttrs | list[TextSettingAttrs], ) -> plt.Figure ``` Add text annotations to an already rendered figure. Returns a new figure with the annotations riding the figure's chart metadata, styled by the current theme at call time — so they follow themes and survive `Panel` and `Grid` composition. The source figure and its charts are never modified. Works on chart figures (including polar ones), `Panel` output, and multi-subplot figures (`subplots=True`). On a multi-subplot figure every text names its target with a 0-based `subplot` index in render order; the figure is redrawn with the same subplot layout — each subplot scales on its own, without the source's `sharex`/`sharey`, which a `Grid` cell of the result restores — and the texts ride the per-subplot panels only, so they show in `Grid` cells but not in a `Panel` overlay of the figure. Grid figures are rejected — annotate the sources before composing. Examples: ``` >>> from datachart.charts import LineChart >>> from datachart.utils import Annotate >>> >>> figure = LineChart(data=[{"x": i, "y": i**2} for i in range(10)]) >>> annotated = Annotate( ... figure, ... texts={ ... "text": "growth accelerates", ... "x": 4, ... "y": 60, ... "target": (7, 49), ... }, ... ) >>> >>> # a multi-subplot figure: each text names its subplot >>> series = [[{"x": i, "y": k * i} for i in range(10)] for k in (1, 2, 3)] >>> annotated = Annotate( ... LineChart(data=series, subplots=True), ... texts={"text": "steepest", "x": 2, "y": 20, "subplot": 2}, ... ) ``` | PARAMETER | DESCRIPTION | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `figure` | A figure created by a datachart chart function or Panel. **TYPE:** `plt.Figure` | | `texts` | The text annotation(s) to add. Each annotation places text at (x, y) — data coordinates by default, axes fractions with "coords": "axes" — draws a connector to the optional target data point, and takes a per-text style override. On a multi-subplot figure each one also names its subplot index. **TYPE:** \`TextSettingAttrs | | RETURNS | DESCRIPTION | | ------------ | --------------------------------------------------- | | `plt.Figure` | A new matplotlib Figure with the annotations added. | | RAISES | DESCRIPTION | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ValueError` | If the figure has no chart metadata or is a Grid figure; if a text names a subplot on a single-panel figure; if, on a multi-subplot figure, a text names no subplot or one out of range. | ## Output ### datachart.utils.save_figure ``` save_figure( figure: plt.Figure, path: str, dpi: int = 300, format: FIG_FORMAT | list[FIG_FORMAT] | None = None, transparent: bool = False, ) -> list[str] ``` Save the figure to one or more files. Writes the rendered figure to disk in the format given by `format` or, when omitted, by the file extension. Use a vector format (PDF, SVG) for print and papers, PNG with `dpi` >= 300 for raster deliverables, and `transparent=True` to drop the figure background for slides and web pages. The theme is already baked into the figure, so saving never consults the global config. Pass a list of formats to write the same figure several times in one call. `path` is then a stem: its extension is dropped when it names a supported format, and one file per format is written next to it. `dpi` and `transparent` apply to every file. Examples: ``` >>> # 1. create the figure >>> from datachart.charts import LineChart >>> figure = LineChart({...}) ``` ``` >>> # 2. save the figure >>> from datachart.utils.figure import save_figure >>> from datachart.constants import FIG_FORMAT >>> path = "/path/to/save/chart.png" >>> save_figure(figure, path, dpi=300, format=FIG_FORMAT.PNG, transparent=True) ``` ``` >>> # 3. save the same figure as a PDF and a PNG >>> save_figure(figure, "/path/to/save/chart", format=[FIG_FORMAT.PDF, FIG_FORMAT.PNG]) ['/path/to/save/chart.pdf', '/path/to/save/chart.png'] ``` | PARAMETER | DESCRIPTION | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | | `figure` | The figure to save. **TYPE:** `plt.Figure` | | `path` | The path where the figure is saved. A stem when format is a list. **TYPE:** `str` | | `dpi` | The DPI of the figure. **TYPE:** `int` **DEFAULT:** `300` | | `format` | The format of the figure, or a list of formats to write. If None, the format will be determined from the file extension. **TYPE:** \`FIG_FORMAT | | `transparent` | Whether to make the background transparent. **TYPE:** `bool` **DEFAULT:** `False` | | RETURNS | DESCRIPTION | | ----------- | ------------------------------------------------------- | | `list[str]` | The paths written, in the order the formats were given. | | RAISES | DESCRIPTION | | ------------ | --------------------------- | | `ValueError` | If format is an empty list. | # Stats Module ## datachart.utils.stats The module containing the `stats` methods. The `stats` module provides the statistics behind the charts: centers and spreads, shape, correlation, a linear fit, bootstrap intervals, histogram bins, smoothers, and density estimates. Every function takes plain Python lists. ## Choosing a Function Every function takes plain Python lists and returns a number, a pair, or lists ready to feed back into a chart. The groups below match the [Statistics guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/utility/stats/index.md), which shows each one on a chart. | I want to… | Use | | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | a center, count, or total | [`mean`](#datachart.utils.stats.mean), [`median`](#datachart.utils.stats.median), [`mode`](#datachart.utils.stats.mode), [`count`](#datachart.utils.stats.count), [`sum_values`](#datachart.utils.stats.sum_values) | | how far the values spread | [`stdev`](#datachart.utils.stats.stdev), [`variance`](#datachart.utils.stats.variance), [`quantile`](#datachart.utils.stats.quantile), [`iqr`](#datachart.utils.stats.iqr), [`minimum`](#datachart.utils.stats.minimum), [`maximum`](#datachart.utils.stats.maximum) | | the shape of a distribution | [`skewness`](#datachart.utils.stats.skewness), [`kurtosis`](#datachart.utils.stats.kurtosis) | | how two variables move together | [`correlation`](#datachart.utils.stats.correlation), [`spearman`](#datachart.utils.stats.spearman) | | a trend line | [`linear_fit`](#datachart.utils.stats.linear_fit) | | an interval around a statistic | [`bootstrap_ci`](#datachart.utils.stats.bootstrap_ci) | | bins for a histogram | [`histogram`](#datachart.utils.stats.histogram) | | a smoothed series | [`rolling_mean`](#datachart.utils.stats.rolling_mean), [`ewma`](#datachart.utils.stats.ewma), [`loess`](#datachart.utils.stats.loess) | | a density curve or surface | [`kde1d`](#datachart.utils.stats.kde1d), [`kde2d`](#datachart.utils.stats.kde2d) | ## Center ### datachart.utils.stats.count ``` count(values: list[int | float]) -> int ``` Counts the number of elements in a list. Examples: ``` >>> from datachart.utils.stats import count >>> count([1, 2, 3, 4, 5]) 5 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ----------------------------------- | | `int` | The number of elements in the list. | ### datachart.utils.stats.sum_values ``` sum_values(values: list[int | float]) -> float ``` Calculates the sum of all values. Examples: ``` >>> from datachart.utils.stats import sum_values >>> sum_values([1, 2, 3, 4, 5]) 15.0 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ---------------------- | | `float` | The sum of all values. | ### datachart.utils.stats.mean ``` mean(values: list[int | float]) -> float ``` Calculates the mean of the values. Examples: ``` >>> from datachart.utils.stats import mean >>> mean([1, 2, 3, 4, 5]) 3.0 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ----------------------- | | `float` | The mean of the values. | ### datachart.utils.stats.median ``` median(values: list[int | float]) -> float ``` Calculates the median of the values. Examples: ``` >>> from datachart.utils.stats import median >>> median([1, 2, 3, 4, 5]) 3.0 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ------------------------- | | `float` | The median of the values. | ### datachart.utils.stats.mode ``` mode(values: list[int | float]) -> float ``` Gets the most frequent value. Meant for discrete data, where values repeat; on continuous data every value tends to be unique and the mode is just the smallest one. Ties are broken by taking the smallest of the most frequent values. Examples: ``` >>> from datachart.utils.stats import mode >>> mode([3, 1, 2, 3, 1]) 1.0 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | -------------------------------------------------------- | | `float` | The smallest most frequent value; nan for an empty list. | | RAISES | DESCRIPTION | | ----------- | --------------------------------------- | | `TypeError` | If values is not a list or numpy array. | ## Spread ### datachart.utils.stats.stdev ``` stdev(values: list[int | float]) -> float ``` Calculates the standard deviation of the values. Examples: ``` >>> from datachart.utils.stats import stdev >>> stdev([1, 2, 3, 4, 5]) 1.4142135623730951 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ------------------------------------- | | `float` | The standard deviation of the values. | ### datachart.utils.stats.variance ``` variance(values: list[int | float]) -> float ``` Calculates the variance of the values. Examples: ``` >>> from datachart.utils.stats import variance >>> variance([1, 2, 3, 4, 5]) 2.0 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | --------------------------- | | `float` | The variance of the values. | ### datachart.utils.stats.quantile ``` quantile(values: list[int | float], q: float) -> float ``` Calculates the quantile of the values. Examples: ``` >>> from datachart.utils.stats import quantile >>> quantile([1, 2, 3, 4, 5], 25) 2.0 ``` | PARAMETER | DESCRIPTION | | --------- | ---------------------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | `q` | The quantile to calculate (0-100). **TYPE:** `float` | | RETURNS | DESCRIPTION | | ------- | --------------------------- | | `float` | The quantile of the values. | ### datachart.utils.stats.iqr ``` iqr(values: list[int | float]) -> float ``` Calculates the interquartile range (Q3 - Q1). The interquartile range is the difference between the 75th percentile (Q3) and the 25th percentile (Q1). It is a measure of statistical dispersion and is useful for identifying outliers. Examples: ``` >>> from datachart.utils.stats import iqr >>> iqr([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) 4.5 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | -------------------------------------- | | `float` | The interquartile range of the values. | ### datachart.utils.stats.minimum ``` minimum(values: list[Any]) -> Any ``` Gets the minimum of the values. Numeric values return a float; any other ordered values, such as datetimes, return their minimum unchanged. Examples: ``` >>> from datachart.utils.stats import minimum >>> minimum([1, 2, 3, 4, 5]) 1 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** `list[Any]` | | RETURNS | DESCRIPTION | | ------- | ---------------------------------------------------------------------- | | `Any` | The minimum of the values: a float for numbers, else the value itself. | ### datachart.utils.stats.maximum ``` maximum(values: list[Any]) -> Any ``` Gets the maximum of the values. Numeric values return a float; any other ordered values, such as datetimes, return their maximum unchanged. Examples: ``` >>> from datachart.utils.stats import maximum >>> maximum([1, 2, 3, 4, 5]) 5 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** `list[Any]` | | RETURNS | DESCRIPTION | | ------- | ---------------------------------------------------------------------- | | `Any` | The maximum of the values: a float for numbers, else the value itself. | ## Shape ### datachart.utils.stats.skewness ``` skewness(values: list[int | float]) -> float ``` Calculates the skewness of the values. Skewness measures the asymmetry of the distribution: positive when the tail extends to the right of the bulk, negative when it extends to the left, and zero for a symmetric distribution. Examples: ``` >>> from datachart.utils.stats import skewness >>> skewness([1, 2, 3, 4, 5]) 0.0 >>> round(skewness([1, 1, 1, 2, 10]), 3) 1.457 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | -------------------------------------------------------------- | | `float` | The skewness of the values; nan for fewer than two values or a | | `float` | constant list. | | RAISES | DESCRIPTION | | ----------- | --------------------------------------- | | `TypeError` | If values is not a list or numpy array. | ### datachart.utils.stats.kurtosis ``` kurtosis(values: list[int | float]) -> float ``` Calculates the excess kurtosis of the values. Kurtosis measures how heavy the tails of the distribution are compared to a normal distribution, which scores zero: positive for heavier tails and sharper peaks, negative for lighter tails and flatter shapes. Examples: ``` >>> from datachart.utils.stats import kurtosis >>> kurtosis([1, 2, 3, 4, 5]) -1.3 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ------------------------------------------------------------------- | | `float` | The excess kurtosis of the values; nan for fewer than two values or | | `float` | a constant list. | | RAISES | DESCRIPTION | | ----------- | --------------------------------------- | | `TypeError` | If values is not a list or numpy array. | ## Association ### datachart.utils.stats.correlation ``` correlation( x: list[int | float], y: list[int | float] ) -> float ``` Calculates the Pearson correlation coefficient between two lists. The Pearson correlation coefficient measures the linear relationship between two datasets. It ranges from -1 (perfect negative correlation) to 1 (perfect positive correlation), with 0 indicating no linear correlation. A temporal `x` (dates, datetimes, or `datetime64`) is correlated as matplotlib date numbers. Examples: ``` >>> from datachart.utils.stats import correlation >>> correlation([1, 2, 3, 4, 5], [1, 2, 3, 4, 5]) 1.0 >>> correlation([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]) -1.0 ``` | PARAMETER | DESCRIPTION | | --------- | -------------------------------------------------------------------- | | `x` | The first list of values, numeric or temporal. **TYPE:** \`list\[int | | `y` | The second list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ------------------------------------ | | `float` | The Pearson correlation coefficient. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------- | | `TypeError` | If x or y is not a list or numpy array, or x mixes temporal and numeric values. | | `ValueError` | If x and y have different lengths. | ### datachart.utils.stats.spearman ``` spearman( x: list[int | float], y: list[int | float] ) -> float ``` Calculates the Spearman rank correlation between two lists. The Spearman coefficient is the Pearson correlation of the ranks, so it measures any monotone relationship, not only a linear one, and is robust to outliers. It ranges from -1 to 1 like `correlation`, and likewise accepts a temporal `x`. Examples: ``` >>> from datachart.utils.stats import spearman >>> round(spearman([1, 2, 3, 4, 5], [1, 4, 9, 16, 25]), 6) 1.0 >>> round(spearman([1, 2, 3, 4, 5], [5, 4, 3, 2, 1]), 6) -1.0 ``` | PARAMETER | DESCRIPTION | | --------- | -------------------------------------------------------------------- | | `x` | The first list of values, numeric or temporal. **TYPE:** \`list\[int | | `y` | The second list of values. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ------- | ----------------------------------------------------------------- | | `float` | The Spearman rank correlation; nan for fewer than two points or a | | `float` | constant list. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------- | | `TypeError` | If x or y is not a list or numpy array, or x mixes temporal and numeric values. | | `ValueError` | If x and y have different lengths. | ## Trend Line ### datachart.utils.stats.linear_fit ``` linear_fit( x: list[int | float], y: list[int | float] ) -> tuple[float, float, float] ``` Fits a straight line to the (x, y) points. An ordinary least-squares fit of `y = slope * x + intercept`, with the coefficient of determination `r2` saying how much of the variation in `y` the line explains (1 is a perfect fit). A temporal `x` (dates, datetimes, or `datetime64`) is fitted as matplotlib date numbers, so the slope is per day and the intercept is relative to matplotlib's date epoch. Examples: ``` >>> from datachart.utils.stats import linear_fit >>> slope, intercept, r2 = linear_fit([0, 1, 2, 3], [1, 3, 5, 7]) >>> round(slope, 6), round(intercept, 6), round(r2, 6) (2.0, 1.0, 1.0) ``` | PARAMETER | DESCRIPTION | | --------- | ---------------------------------------------------------------------- | | `x` | The x values of the points, numeric or temporal. **TYPE:** \`list\[int | | `y` | The y values of the points, one per x value. **TYPE:** \`list\[int | | RETURNS | DESCRIPTION | | ---------------------------- | ----------------------------------------------------------------- | | `float` | The (slope, intercept, r2) of the fitted line, the slope per day | | `float` | for a temporal x; all nan for fewer than two points or a constant | | `float` | x, and r2 alone nan for a constant y, which leaves no | | `tuple[float, float, float]` | variation to explain. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------- | | `TypeError` | If x or y is not a list or numpy array, or x mixes temporal and numeric values. | | `ValueError` | If x and y have different lengths. | ## Confidence Intervals ### datachart.utils.stats.bootstrap_ci ``` bootstrap_ci( values: list[int | float], statistic: Callable[ [List[Union[int, float]]], float ] = mean, level: float = 0.95, n_resamples: int = 1000, seed: int | np.random.Generator | None = None, ) -> tuple[float, float] ``` Estimates a confidence interval of a statistic by bootstrapping. The values are resampled with replacement `n_resamples` times, the statistic is computed on each resample, and the interval is the central `level` share of those results (the percentile bootstrap). The half-width of the interval is a ready-made error bar for a `BarChart`. Examples: ``` >>> from datachart.utils.stats import bootstrap_ci, median >>> low, high = bootstrap_ci([1, 2, 3, 4, 5, 6, 7, 8], seed=0) >>> low < 4.5 < high True >>> bootstrap_ci([1, 2, 3, 4, 100], statistic=median, seed=0)[1] <= 100 True ``` | PARAMETER | DESCRIPTION | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | `statistic` | The function of the values to estimate, mean by default. **TYPE:** `Callable[[List[Union[int, float]]], float]` **DEFAULT:** `mean` | | `level` | The confidence level, strictly between 0 and 1. **TYPE:** `float` **DEFAULT:** `0.95` | | `n_resamples` | The number of resamples to draw. **TYPE:** `int` **DEFAULT:** `1000` | | `seed` | An integer or numpy.random.Generator that makes the resamples reproducible. **TYPE:** \`int | | RETURNS | DESCRIPTION | | ------- | --------------------------------------------------------------- | | `float` | The (low, high) bounds of the interval; both nan for fewer than | | `float` | two values. | | RAISES | DESCRIPTION | | ------------ | --------------------------------------------------------------- | | `TypeError` | If values is not a list or numpy array. | | `ValueError` | If level is not between 0 and 1 or n_resamples is not positive. | ## Binning ### datachart.utils.stats.histogram ``` histogram( values: list[int | float], bins: str | int | list[int | float] = "auto", ) -> tuple[list[int], list[float]] ``` Bins the values into histogram counts and edges. The `bins` are passed straight to `numpy.histogram_bin_edges`: a rule name such as `"auto"`, `"fd"`, `"rice"`, or `"sturges"` picks the edges from the data, an integer sets the number of equal-width bins, and a list gives the edges explicitly. These rules are unrelated to the `CONTOUR_LEVELS` rules that share their names. Examples: ``` >>> from datachart.utils.stats import histogram >>> histogram([1, 2, 2, 3, 3, 3, 4], bins=3) ([1, 2, 4], [1.0, 2.0, 3.0, 4.0]) ``` | PARAMETER | DESCRIPTION | | --------- | -------------------------------------------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | `bins` | A bin rule name, a number of bins, or a list of bin edges. **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------------- | --------------------------------------------------------------- | | `list[int]` | The (counts, edges) lists, with one more edge than counts; both | | `list[float]` | empty for an empty list. | | RAISES | DESCRIPTION | | ------------ | --------------------------------------- | | `TypeError` | If values is not a list or numpy array. | | `ValueError` | If the bin rule is unknown. | ## Smoothing ### datachart.utils.stats.rolling_mean ``` rolling_mean( values: list[int | float], window: int ) -> list[float] ``` Smooths the values with a trailing moving average. Each output is the mean of the `window` values ending at that index, so the result lines up with the input and is `nan` until the window fills. Examples: ``` >>> from datachart.utils.stats import rolling_mean >>> rolling_mean([1, 2, 3, 4, 5], 3) [nan, nan, 2.0, 3.0, 4.0] ``` | PARAMETER | DESCRIPTION | | --------- | ---------------------------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | `window` | The number of values averaged, at least 1. **TYPE:** `int` | | RETURNS | DESCRIPTION | | ------------- | ----------------------------------------- | | `list[float]` | The smoothed values, one per input value. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------ | | `TypeError` | If values is not a list or numpy array, or the window is not an integer. | | `ValueError` | If the window is not positive. | ### datachart.utils.stats.ewma ``` ewma( values: list[int | float], alpha: float ) -> list[float] ``` Smooths the values with an exponentially weighted moving average. Each output blends the current value with the previous output, `alpha * value + (1 - alpha) * previous`, starting from the first value. A larger `alpha` follows the data more closely; a smaller one smooths harder. Examples: ``` >>> from datachart.utils.stats import ewma >>> ewma([1, 2, 3], 0.5) [1.0, 1.5, 2.25] ``` | PARAMETER | DESCRIPTION | | --------- | -------------------------------------------------------------- | | `values` | The list of values. **TYPE:** \`list\[int | | `alpha` | The weight of the current value, in (0, 1\]. **TYPE:** `float` | | RETURNS | DESCRIPTION | | ------------- | ----------------------------------------- | | `list[float]` | The smoothed values, one per input value. | | RAISES | DESCRIPTION | | ------------ | --------------------------------------- | | `TypeError` | If values is not a list or numpy array. | | `ValueError` | If alpha is not in (0, 1\]. | ### datachart.utils.stats.loess ``` loess( x: list[int | float], y: list[int | float], frac: float = 0.3, ) -> list[dict[str, float]] ``` Smooths the (x, y) points with a locally weighted linear fit. At each `x` a straight line is fitted to the nearest `frac` share of the points, weighted by a tricube kernel so closer points count more, and the smoothed `y` is that line's value there (LOESS/LOWESS). The result is a list of `{x, y}` points sorted by `x`, ready for `LineChart`, as `kde1d` returns. A smaller `frac` follows the data more closely. A temporal `x` (dates, datetimes, or `datetime64`) is smoothed as date numbers and the curve's `x` values come back as datetimes, in the input's zone. Examples: ``` >>> from datachart.utils.stats import loess >>> curve = loess([5, 1, 3, 2, 4], [11, 3, 7, 5, 9], frac=0.6) >>> [(point["x"], round(point["y"], 6)) for point in curve] [(1.0, 3.0), (2.0, 5.0), (3.0, 7.0), (4.0, 9.0), (5.0, 11.0)] ``` | PARAMETER | DESCRIPTION | | --------- | --------------------------------------------------------------------------------------------- | | `x` | The x values of the points, numeric or temporal. **TYPE:** \`list\[int | | `y` | The y values of the points, one per x value. **TYPE:** \`list\[int | | `frac` | The share of the points each local fit uses, in (0, 1\]. **TYPE:** `float` **DEFAULT:** `0.3` | | RETURNS | DESCRIPTION | | ------------------------ | -------------------------------------------------------------- | | `list[dict[str, float]]` | The {x, y} points of the smoothed curve, sorted by x; the y is | | `list[dict[str, float]]` | nan for fewer than two points. | | RAISES | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------- | | `TypeError` | If x or y is not a list or numpy array, or x mixes temporal and numeric values. | | `ValueError` | If x and y have different lengths or frac is not in (0, 1\]. | ## Density Estimates ### datachart.utils.stats.kde1d ``` kde1d( values: list[int | float], *, bandwidth: BANDWIDTH | str | float | None = None, gridsize: int = 100, cut: float = 3, xlim: tuple[float, float] | None = None ) -> list[dict[str, float]] ``` Estimates the density of the values as a curve. A Gaussian kernel density estimate evaluated on `gridsize` evenly spaced points over the range of the values, extended by `cut` bandwidths on each side so the curve tails off instead of being clipped at the extremes, or over an explicit `xlim` so several curves share one grid. The result is a list of `{x, y}` points ready for `LineChart`; the curve integrates to 1, so it overlays a density `Histogram` of the same values. Examples: ``` >>> from datachart.utils.stats import kde1d >>> curve = kde1d([1, 2, 2, 3, 3, 3, 4, 4, 5], gridsize=5, cut=0) >>> [round(point["x"], 2) for point in curve] [1.0, 2.0, 3.0, 4.0, 5.0] >>> round(sum(point["y"] for point in curve), 2) 0.94 ``` | PARAMETER | DESCRIPTION | | ----------- | --------------------------------------------------------------------------------------------------------------------------- | | `values` | The values to estimate the density of. **TYPE:** \`list\[int | | `bandwidth` | The kernel bandwidth: None or "scott" (Scott's rule), "silverman", or a scalar factor. See BANDWIDTH. **TYPE:** \`BANDWIDTH | | `gridsize` | The number of points the curve is evaluated on. **TYPE:** `int` **DEFAULT:** `100` | | `cut` | How many bandwidths to extend the grid past the extremes. **TYPE:** `float` **DEFAULT:** `3` | | `xlim` | The (min, max) range of the grid; overrides the padded range. **TYPE:** \`tuple[float, float] | | RETURNS | DESCRIPTION | | ------------------------ | --------------------------------------- | | `list[dict[str, float]]` | The {x, y} points of the density curve. | | RAISES | DESCRIPTION | | ------------ | --------------------------------------------------------------------------------------- | | `ValueError` | If the bandwidth is invalid, there are fewer than two values, or a value is not finite. | ### datachart.utils.stats.kde2d ``` kde2d( x: list[int | float], y: list[int | float], *, bandwidth: BANDWIDTH | str | float | None = None, gridsize: int | tuple[int, int] = 100, cut: float = 3, xlim: tuple[Any, Any] | None = None, ylim: tuple[float, float] | None = None ) -> dict[str, list] ``` Estimates the density of the (x, y) points as a gridded surface. A Gaussian kernel density estimate evaluated on a `gridsize` × `gridsize` grid over the range of the points, extended by `cut` bandwidths on each side so the outer contours close instead of being clipped, or over explicit `xlim`/`ylim` so several surfaces share one grid. The result is an `{x, y, z}` chart dict ready for `ContourChart` — the density chart of a scattered dataset is `ContourChart(kde2d(x, y))`. A temporal `x` (dates, datetimes, or `datetime64`) gives a grid of datetime `x` values, in the input's zone, and `xlim` may then be a pair of datetimes. Examples: ``` >>> from datachart.utils.stats import kde2d >>> surface = kde2d([1, 2, 3, 4], [1, 3, 2, 4], gridsize=(3, 2), cut=0) >>> surface["x"], surface["y"] ([1.0, 2.5, 4.0], [1.0, 4.0]) >>> [[round(z, 3) for z in row] for row in surface["z"]] [[0.075, 0.038, 0.001], [0.001, 0.038, 0.075]] ``` | PARAMETER | DESCRIPTION | | ----------- | --------------------------------------------------------------------------------------------------------------------------- | | `x` | The x values of the points, numeric or temporal. **TYPE:** \`list\[int | | `y` | The y values of the points, one per x value. **TYPE:** \`list\[int | | `bandwidth` | The kernel bandwidth: None or "scott" (Scott's rule), "silverman", or a scalar factor. See BANDWIDTH. **TYPE:** \`BANDWIDTH | | `gridsize` | The number of grid columns and rows, as one number or an (x, y) pair. **TYPE:** \`int | | `cut` | How many bandwidths to extend the grid past the extremes. **TYPE:** `float` **DEFAULT:** `3` | | `xlim` | The (min, max) x range of the grid; overrides the padded range. **TYPE:** \`tuple[Any, Any] | | `ylim` | The (min, max) y range of the grid; overrides the padded range. **TYPE:** \`tuple[float, float] | | RETURNS | DESCRIPTION | | ----------------- | ------------------------------------------------ | | `dict[str, list]` | The {x, y, z} chart dict of the density surface. | | RAISES | DESCRIPTION | | ------------ | ----------------------------------------------------------------------------------------------------------------- | | `TypeError` | If x or xlim mixes temporal and numeric values, or xlim is temporal while x is not, or the other way around. | | `ValueError` | If the bandwidth is invalid, x and y differ in length, there are fewer than two points, or a value is not finite. | # Config Module ## datachart.config The module containing the `config`. The `config` module contains the configuration objects, enabling the users to globally customize the chart and plot styles. | ATTRIBUTE | DESCRIPTION | | --------- | ---------------------------------------------- | | `config` | The configuration instance. **TYPE:** `Config` | | CLASS | DESCRIPTION | | -------- | ------------------------ | | `Config` | The configuration class. | ## Choosing a Method One `config` instance holds the style every chart is drawn with. Its methods change that style for the rest of the session, for one block of code, or from a file; the [Themes guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) walks through them on a chart. | I want to… | Call | See | | ------------------------------------------- | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- | | switch the look of every chart | `config.set_theme(THEME.INK)` | [set_theme](#datachart.config.Config.set_theme) | | change a few attributes on top of the theme | `config.update_config({"font_general_size": 12})` | [update_config](#datachart.config.Config.update_config) | | change the look for one block of code | `with config.override(...)`, `with config.using_theme(...)` | [override](#datachart.config.Config.override), [using_theme](#datachart.config.Config.using_theme) | | go back to the default theme | `config.reset_config()` | [reset_config](#datachart.config.Config.reset_config) | | add a theme of my own | `config.register_theme(name, theme)`, then `set_theme(name)` | [register_theme](#datachart.config.Config.register_theme) | | see which names `set_theme` accepts | `config.list_themes()` | [list_themes](#datachart.config.Config.list_themes) | | share a theme as a file | `config.save_theme(path)`, `config.load_theme(path)` | [save_theme](#datachart.config.Config.save_theme), [load_theme](#datachart.config.Config.load_theme) | | read one attribute | `config.get("font_general_size")` | [get](#datachart.config.Config.get) | The attribute names are the keys of [`StyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.StyleAttrs): the theme-level keys on the [typings](https://eriknovak.github.io/datachart/0.10.2/references/typings/#theme-style) page and each chart's own keys on its [reference page](https://eriknovak.github.io/datachart/0.10.2/references/charts/index.md). The theme names are the members of [`THEME`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME). ## Attributes ### datachart.config.config ``` config: Config = Config() ``` The configuration instance that the users should interact with. ## Classes ### datachart.config.Config The class representing the configuration options. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------- | | `config` | The style configuration. **TYPE:** `StyleAttrs` | | `theme` | The name of the active theme. **TYPE:** `str` | | METHOD | DESCRIPTION | | ---------------- | ------------------------------------------------------------- | | `set_theme` | Set the global configuration to match the theme. | | `reset_config` | Resets the global configuration. | | `update_config` | Updates the global configuration. | | `override` | Applies attribute overrides for the duration of a with block. | | `using_theme` | Applies a theme for the duration of a with block. | | `register_theme` | Registers a custom theme for use with set_theme. | | `list_themes` | Lists the names set_theme accepts. | | `save_theme` | Writes a theme file. | | `load_theme` | Registers the theme in a theme file. | | `get` | Gets the associated configuration attribute. | #### set_theme ``` set_theme(theme: THEME) -> None ``` Sets the global configuration to match the theme. Replaces the whole style configuration with a deep copy of the theme: one of the THEME constants or a name registered with `register_theme`. Use it to switch the look of every chart rendered afterwards; call `update_config` on top for per-attribute tweaks. Examples: ``` >>> from datachart.constants import THEME >>> from datachart.config import config >>> config.set_theme(THEME.DEFAULT) >>> config.get("theme") 'default' ``` | PARAMETER | DESCRIPTION | | --------- | -------------------------------------- | | `theme` | The theme to be set. **TYPE:** `THEME` | #### register_theme ``` register_theme(name: str, theme: StyleAttrs) -> None ``` Registers a custom theme so it can be applied with `set_theme`. The theme must define every attribute of the default theme; missing keys are filled from it, unknown keys are rejected. Examples: ``` >>> from datachart.config import config >>> from datachart.themes import DEFAULT_THEME >>> config.register_theme("mine", {**DEFAULT_THEME, "font_general_size": 14}) >>> config.set_theme("mine") >>> config.get("font_general_size") 14 ``` | PARAMETER | DESCRIPTION | | --------- | ---------------------------------------------------------- | | `name` | The theme name, later passed to set_theme. **TYPE:** `str` | | `theme` | The style attributes of the theme. **TYPE:** `StyleAttrs` | #### reset_config ``` reset_config() -> None ``` Resets the global configuration. Restores the default theme, discarding the current theme and every `update_config` override, and resets the active theme name to match. Use it to return to a known state, for example at the start of a notebook section or between tests. Examples: ``` >>> from datachart.config import config >>> config.reset_config() >>> config.get("theme") 'default' ``` #### update_config ``` update_config(config: StyleAttrs) -> None ``` Updates the global configuration. Overrides individual style attributes on top of the current theme; the change persists until the next `set_theme` or `reset_config`. Use it for global tweaks such as font family or default colors; unknown attribute names are skipped with a warning. Examples: ``` >>> from datachart.config import config >>> config.update_config({"font_general_color": "#FFFFFF"}) >>> config.get("font_general_color") '#FFFFFF' ``` | PARAMETER | DESCRIPTION | | --------- | ------------------------------------------------------------------ | | `config` | The configuration attributes to be updated. **TYPE:** `StyleAttrs` | #### \_scope ``` _scope() -> Iterator[None] ``` Restores the style dict and the active theme name on exit. #### override ``` override( config: StyleAttrs | None = None, **attrs: Any ) -> Iterator[None] ``` Applies style overrides for the duration of a `with` block. On entry the attributes are applied the way `update_config` applies them; on exit the configuration that entered the block is restored, also when the block raises. Any `set_theme` or `update_config` performed inside the block is discarded at exit. Use it for a one-off figure that needs a different font or palette without touching the global state. The scope is plain save-and-restore on the global configuration: it is neither thread-safe nor async-safe. Examples: ``` >>> from datachart.config import config >>> with config.override(font_general_size=14): ... config.get("font_general_size") 14 >>> config.get("font_general_size") 10 ``` | PARAMETER | DESCRIPTION | | --------- | ----------------------------------------------------------------------------------- | | `config` | The attributes to override, as a dictionary. **TYPE:** \`StyleAttrs | | `**attrs` | The attributes to override, as keyword arguments. **TYPE:** `Any` **DEFAULT:** `{}` | #### using_theme ``` using_theme(theme: THEME) -> Iterator[None] ``` Applies a theme for the duration of a `with` block. On entry the theme is applied the way `set_theme` applies it; on exit both the configuration and the active theme name that entered the block are restored, also when the block raises. Any `set_theme` or `update_config` performed inside the block is discarded at exit. The scope is plain save-and-restore on the global configuration: it is neither thread-safe nor async-safe. Examples: ``` >>> from datachart.constants import THEME >>> from datachart.config import config >>> with config.using_theme(THEME.INK): ... config.theme 'ink' >>> config.theme 'default' ``` | PARAMETER | DESCRIPTION | | --------- | -------------------------------------------------------------------------------------- | | `theme` | The theme to apply: one of the THEME constants or a registered name. **TYPE:** `THEME` | #### list_themes ``` list_themes() -> list[str] ``` Lists the theme names `set_theme` accepts. Returns the predefined themes in declaration order, followed by every name added with `register_theme` or `load_theme` in registration order. Examples: ``` >>> from datachart.config import config >>> "default" in config.list_themes() True ``` | RETURNS | DESCRIPTION | | ----------- | ---------------- | | `list[str]` | The theme names. | #### save_theme ``` save_theme( path: str | Path, name: str | None = None ) -> None ``` Writes a theme file. With no name the live configuration is saved, so a look assembled with `update_config` can be shared or committed directly; the file is named after its stem. With a name that registered theme is saved instead. The file is JSON and carries only the attributes that differ from the default theme, so it stays short and reviewable; load it back with `load_theme`. The parent directory must exist. Examples: ``` >>> from datachart.config import config >>> config.update_config({"font_general_size": 14}) >>> config.save_theme("house.json") >>> config.save_theme("ink.json", name="ink") ``` | PARAMETER | DESCRIPTION | | --------- | --------------------------------------------------------------------------------- | | `path` | The file to write. **TYPE:** \`str | | `name` | The registered theme to save. Defaults to the live configuration. **TYPE:** \`str | | RAISES | DESCRIPTION | | ------------ | ---------------------------------- | | `ValueError` | If name is not a registered theme. | #### load_theme ``` load_theme( path: str | Path, name: str | None = None ) -> str ``` Registers the theme held in a theme file and returns its name. The file is read as written by `save_theme` and registered through `register_theme`, so missing attributes are filled from the default theme, alias keys resolve to their canonical name, and unknown keys are rejected. The name is, in order of precedence, the `name` argument, the name in the file, or the file's stem; an existing theme of that name is replaced. Loading only registers: apply the theme with `set_theme` or `using_theme`. Examples: ``` >>> from datachart.config import config >>> config.set_theme(config.load_theme("house.json")) >>> config.theme 'house' ``` | PARAMETER | DESCRIPTION | | --------- | ---------------------------------------------------------------------------------------------------------------- | | `path` | The theme file to read. **TYPE:** \`str | | `name` | The name to register the theme under. Defaults to the name in the file, then to the file's stem. **TYPE:** \`str | | RETURNS | DESCRIPTION | | ------- | ---------------------------------------- | | `str` | The name the theme was registered under. | | RAISES | DESCRIPTION | | ------------ | ---------------------------------------------------------------------------------------------- | | `ValueError` | If the file is not a theme file, states another format version, or holds an unknown attribute. | #### __getitem__ ``` __getitem__(attr: str) -> Any ``` Gets the associated configuration attribute. Examples: ``` >>> from datachart.config import config >>> config["font_general_color"] '#FFFFFF' ``` | PARAMETER | DESCRIPTION | | --------- | ------------------------------------------ | | `attr` | The attribute to retrieve. **TYPE:** `str` | | RETURNS | DESCRIPTION | | ------- | ------------------------------------------------ | | `Any` | The attribute value if present. Otherwise, None. | #### get ``` get(attr: str, default: Any = None) -> Any ``` Gets the associated configuration attribute. Reads one style attribute, falling back to `default` when it is not set. Use it to inspect the active configuration or to build style overrides relative to the current theme. Examples: ``` >>> from datachart.config import config >>> config.get("font_general_color") '#FFFFFF' ``` | PARAMETER | DESCRIPTION | | --------- | ------------------------------------------------------------------------------------------------------- | | `attr` | The attribute to retrieve. **TYPE:** `str` | | `default` | The value to return, if the attribute is not present in the config. **TYPE:** `Any` **DEFAULT:** `None` | | RETURNS | DESCRIPTION | | ------- | --------------------------------------------------------------------- | | `Any` | The attribute value if present. Otherwise, returns the default value. | # Themes Module ## datachart.themes The module containing the `themes`. The `themes` module contains the predefined style themes that are used to visualize the plots. Themes are named for their visual trait, never for a use case or audience; each is a complete `StyleAttrs` dictionary that `config.set_theme` applies. ## Choosing a Theme Every theme is a complete [`StyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/typings/#datachart.typings.StyleAttrs) dictionary, named for its visual trait and listed here by where it works best. Apply one with [`config.set_theme`](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.set_theme) and the member of [`THEME`](https://eriknovak.github.io/datachart/0.10.2/references/constants/#datachart.constants.THEME) in the last column; the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md) shows each on six charts, and the [Themes guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) shows how to adjust one or build your own. | Theme | Look | Apply with | | ------------------------------------------------------ | ------------------------------------------------------------------------------- | ----------------- | | **Screen and presentations** | | | | [`DEFAULT_THEME`](#datachart.themes.DEFAULT_THEME) | softened Okabe–Ito palette, colour-blind safe, baseline furniture | `THEME.DEFAULT` | | [`MATERIAL_THEME`](#datachart.themes.MATERIAL_THEME) | the Google palette, light grid | `THEME.MATERIAL` | | [`MINIMAL_THEME`](#datachart.themes.MINIMAL_THEME) | accent violet, no spines, flat bars | `THEME.MINIMAL` | | [`HARBOR_THEME`](#datachart.themes.HARBOR_THEME) | navy and amber in lightness steps, colour-blind safe | `THEME.HARBOR` | | **Print and black-and-white** | | | | [`GREYSCALE_THEME`](#datachart.themes.GREYSCALE_THEME) | greys only, for print without color | `THEME.GREYSCALE` | | [`INK_THEME`](#datachart.themes.INK_THEME) | dark-ink accents, print-ready | `THEME.INK` | | [`HATCH_THEME`](#datachart.themes.HATCH_THEME) | a hatch cycle, black edges, dotted grid | `THEME.HATCH` | | [`MUTED_THEME`](#datachart.themes.MUTED_THEME) | Tol's muted colours, dash and marker cycles, colour-blind safe | `THEME.MUTED` | | [`CONTRAST_THEME`](#datachart.themes.CONTRAST_THEME) | lightness-stepped colours plus hatches, print-safe | `THEME.CONTRAST` | | **Illustrative** | | | | [`SKETCH_THEME`](#datachart.themes.SKETCH_THEME) | hand-drawn: xkcd-style wobble and halo, Comic Neue font | `THEME.SKETCH` | | [`QUILL_THEME`](#datachart.themes.QUILL_THEME) | black ink on white paper, pen-stroked lines, etched fills, IM Fell English font | `THEME.QUILL` | ## Themes ### datachart.themes.DEFAULT_THEME ``` DEFAULT_THEME: StyleAttrs = make_theme({}) ``` The default theme: the package's baseline palette and furniture. The palette is a softened Okabe–Ito set closed with charcoal, so every pair of series stays apart for deutan, protan and tritan readers. ### datachart.themes.MATERIAL_THEME ``` MATERIAL_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Blues, "color_general_multiple": [ "#1A73E8", "#D93025", "#F9AB00", "#1E8E3E", "#12B5CB", "#9334E6", ], "color_parallel_hue_continuous": [ "#C6DAFC", "#7BAAF7", "#1A73E8", "#174EA6", ], "font_general_sansserif": [ "Roboto", "Arial", "Helvetica", "Liberation Sans", ], "axes_spines_top_visible": False, "axes_spines_right_visible": False, "axes_spines_left_visible": False, "plot_grid_color": "#E0E0E0", "plot_grid_alpha": 1.0, "plot_grid_linewidth": 0.8, "plot_bar_alpha": 1.0, "plot_bar_edge_width": 0, "plot_stackedarea_edge_width": 0, "plot_hist_edge_width": 0, "plot_line_width": 2.0, "plot_text_box_edgecolor": "#757575", "plot_text_arrow_color": "#757575", "plot_dumbbell_start_color": "#F9AB00", "plot_dumbbell_end_color": "#1A73E8", "plot_dumbbell_edge_width": 0, "plot_heatmap_cmap": COLORS.Blues, "plot_heatmap_frame_color": "#000000", } ) ``` The material theme: Google palette, light grid. ### datachart.themes.MINIMAL_THEME ``` MINIMAL_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Purples, "color_general_multiple": [ "#7048E8", "#1F2933", "#8A97A3", "#C5CDD4", ], "color_parallel_hue_continuous": [ "#DCD3F7", "#A796EE", "#7048E8", "#3B1E9E", ], "font_general_color": "#1F1F1F", "font_title_color": "#1F1F1F", "axes_spines_top_visible": False, "axes_spines_right_visible": False, "axes_spines_left_visible": False, "axes_spines_bottom_visible": False, "axes_ticks_length": 0, "plot_grid_color": "#EFEFEF", "plot_grid_alpha": 1.0, "plot_bar_alpha": 1.0, "plot_bar_edge_width": 0, "plot_stackedarea_edge_width": 0, "plot_value_fontsize": 9, "plot_value_color": "#1F1F1F", "plot_hist_edge_width": 0, "plot_line_width": 2.0, "plot_scatter_edge_color": "#FFFFFF", "plot_swarm_edge_color": "#FFFFFF", "plot_dumbbell_start_color": "#C5CDD4", "plot_dumbbell_end_color": "#7048E8", "plot_dumbbell_connector_color": "#DDE3E8", "plot_text_box_edgecolor": "#CFD8DC", "plot_text_arrow_color": "#9AA4AE", "plot_heatmap_cmap": COLORS.Purples, "plot_heatmap_frame_color": "#9AA4AE", } ) ``` The minimal theme: accent violet, no spines, flat bars. ### datachart.themes.HARBOR_THEME ``` HARBOR_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Cividis, "color_general_multiple": [ "#1F4E79", "#D08C3A", "#6FA3D3", "#EFC98C", "#8C6D5A", "#1A1A1A", ], "color_parallel_hue_continuous": [ "#BCAE6C", "#7D7C78", "#434E6C", "#00224E", ], "plot_line_width": 2.0, "plot_bar_alpha": 1.0, "plot_bar_edge_width": 0, "plot_stackedarea_edge_width": 0, "plot_hist_edge_width": 0, "plot_dumbbell_start_color": "#EFC98C", "plot_dumbbell_end_color": "#1F4E79", "plot_heatmap_cmap": COLORS.Cividis, } ) ``` The harbor theme: navy and amber in lightness steps, colour-blind safe. Two hue families, navy to sky and amber to sand, with taupe and near-black closing the set; lightness does the separating, so every pair of series stays apart for deutan, protan and tritan readers. Flat bars, 2 pt lines and the Cividis value scale. ### datachart.themes.GREYSCALE_THEME ``` GREYSCALE_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Greys, "color_general_multiple": GREYS, "color_parallel_hue": GREYS, "color_parallel_hue_continuous": [ "#D9D9D9", "#969696", "#525252", "#000000", ], "plot_bar_edge_width": 0.8, "plot_bar_edge_color": "#000000", "plot_stackedarea_edge_color": "#000000", "plot_sankey_node_edge_color": "#000000", "plot_treemap_edge_color": "#000000", "plot_network_node_edge_color": "#000000", "plot_hist_edge_color": "#000000", "plot_vline_color": "#5D6D7E", "plot_vline_style": LINE_STYLE.DASHED, "plot_hline_color": "#5D6D7E", "plot_hline_style": LINE_STYLE.DASHED, "plot_vspan_color": "#5D6D7E", "plot_hspan_color": "#5D6D7E", "plot_text_box_edgecolor": "#B0B0B0", "plot_text_arrow_color": "#5D6D7E", "plot_gantt_dependency_color": "#2C3E50", "plot_gantt_today_color": "#2C3E50", "plot_dumbbell_start_color": "#ABB2B9", "plot_dumbbell_end_color": "#2C3E50", "plot_dumbbell_edge_color": "#000000", "plot_dumbbell_connector_color": "#85929E", "plot_dumbbell_arrow_color": "#5D6D7E", "plot_heatmap_cmap": COLORS.Greys, "plot_heatmap_frame_color": "#000000", "plot_regression_color": "#34495E", "plot_box_median_color": "#000000", "plot_violin_edgecolor": "#000000", "plot_violin_inner_color": "#000000", "plot_ridgeline_edgecolor": "#000000", "plot_ridgeline_inner_color": "#000000", } ) ``` The greyscale theme: shades of grey for print or colorblind-safe output. ### datachart.themes.INK_THEME ``` INK_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Blues, "color_general_multiple": COLORS.PaperYlGnBu, "color_parallel_hue": COLORS.PaperYlGnBu, "color_parallel_hue_continuous": [ "#C7E9B4", "#7FCDBB", "#41B6C4", "#225EA8", ], "font_general_sansserif": [ "Helvetica", "Arial", "Liberation Sans", "DejaVu Sans", ], "plot_grid_color": "#DDE3E8", "plot_bar_edge_width": 1.0, "plot_bar_edge_color": "#0B1F44", "plot_stackedarea_edge_color": "#0B1F44", "plot_sankey_node_edge_color": "#0B1F44", "plot_treemap_edge_color": "#0B1F44", "plot_network_node_edge_color": "#0B1F44", "plot_hist_edge_color": "#0B1F44", "plot_vline_color": "#7F8C8D", "plot_vline_style": LINE_STYLE.DASHED, "plot_hline_color": "#7F8C8D", "plot_hline_style": LINE_STYLE.DASHED, "plot_vspan_color": "#7F8C8D", "plot_hspan_color": "#7F8C8D", "plot_text_box_edgecolor": "#000000", "plot_text_arrow_color": "#000000", "plot_gantt_dependency_color": "#0B1F44", "plot_gantt_today_color": "#0B1F44", "plot_dumbbell_start_color": "#7FCDBB", "plot_dumbbell_end_color": "#225EA8", "plot_dumbbell_edge_color": "#0B1F44", "plot_dumbbell_connector_color": "#7F8C8D", "plot_dumbbell_arrow_color": "#34495E", "plot_heatmap_cmap": COLORS.YlGnBu, "plot_heatmap_frame_color": "#0B1F44", "plot_scatter_edge_width": 0.6, "plot_scatter_edge_color": "#0B1F44", "plot_swarm_edge_width": 0.6, "plot_swarm_edge_color": "#0B1F44", "plot_regression_color": "#34495E", "plot_parallel_axis_color": "#34495E", "plot_parallel_tick_color": "#34495E", "plot_parallel_tick_label_color": "#34495E", "plot_parallel_dim_label_color": "#34495E", "plot_box_edgecolor": "#34495E", "plot_box_median_color": "#34495E", "plot_violin_edgecolor": "#34495E", "plot_ridgeline_edgecolor": "#34495E", } ) ``` The ink theme: dark-ink accents, print-ready. ### datachart.themes.HATCH_THEME ``` HATCH_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.YlOrBr, "color_general_multiple": [ "#B5563A", "#4F6D8F", "#D4A64A", "#4E7A5A", "#A6A6A6", "#7B5EA7", ], "color_parallel_hue_continuous": [ "#F3E0C3", "#E0AE6A", "#B5563A", "#6B2E1A", ], "font_general_sansserif": [ "Helvetica", "Arial", "Liberation Sans", "DejaVu Sans", ], "plot_hatch_cycle": ["", "//", ".."], "plot_grid_color": "#D0D0D0", "plot_grid_linestyle": LINE_STYLE.DOTTED, "plot_grid_alpha": 0.8, "plot_bar_edge_color": "#000000", "plot_stackedarea_edge_color": "#000000", "plot_sankey_node_edge_color": "#000000", "plot_treemap_edge_color": "#000000", "plot_network_node_edge_color": "#000000", "plot_bar_edge_width": 0.8, "plot_bar_alpha": 1.0, "plot_hist_edge_color": "#000000", "plot_scatter_edge_color": "#000000", "plot_scatter_edge_width": 0.6, "plot_swarm_edge_color": "#000000", "plot_swarm_edge_width": 0.6, "plot_text_box_edgecolor": "#000000", "plot_text_arrow_color": "#000000", "plot_gantt_dependency_color": "#000000", "plot_gantt_today_color": "#000000", "plot_dumbbell_start_color": "#4F6D8F", "plot_dumbbell_end_color": "#B5563A", "plot_dumbbell_edge_color": "#000000", "plot_dumbbell_connector_color": "#8C8C8C", "plot_dumbbell_arrow_color": "#000000", "plot_heatmap_cmap": COLORS.YlOrBr, "plot_heatmap_frame_color": "#000000", "plot_violin_edgecolor": "#000000", "plot_ridgeline_edgecolor": "#000000", } ) ``` The hatch theme: hatch cycle, black edges, dotted grid. ### datachart.themes.MUTED_THEME ``` MUTED_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.YlOrBr, "color_general_multiple": [ "#332288", "#88CCEE", "#DDCC77", "#CC6677", "#882255", ], "color_parallel_hue_continuous": [ "#FEE391", "#FE9929", "#CC4C02", "#662506", ], "plot_linestyle_cycle": ["-", "--", "-.", ":", "-"], "plot_marker_cycle": ["o", "s", "^", "D", "v"], "plot_line_width": 1.2, "plot_bar_alpha": 1.0, "plot_bar_edge_color": "#000000", "plot_bar_edge_width": 0.6, "plot_hist_edge_color": "#000000", "plot_grid_linestyle": LINE_STYLE.DOTTED, "plot_grid_color": "#C8C8C8", "plot_dumbbell_start_color": "#DDCC77", "plot_dumbbell_end_color": "#332288", "plot_heatmap_cmap": COLORS.YlOrBr, } ) ``` The muted theme: Tol's muted colours, dashes and markers, colour-blind safe. Indigo, cyan, sand, rose and wine from Paul Tol's muted scheme, every pair distinct for deutan, protan and tritan readers. Lines also differ by dash and scatter points by marker, bars carry black edges, and the grid is dotted, so a figure survives a greyscale print. The value scale is YlOrBr. ### datachart.themes.CONTRAST_THEME ``` CONTRAST_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Cividis, "color_general_multiple": [ "#1F4E79", "#D4B24C", "#B45C6A", "#2B2B2B", "#9A9A9A", ], "color_parallel_hue_continuous": [ "#BCAE6C", "#7D7C78", "#434E6C", "#00224E", ], "plot_linestyle_cycle": ["-", "--", "-.", ":", "-"], "plot_marker_cycle": ["o", "s", "^", "D", "v"], "plot_hatch_cycle": ["", "//", "..", "xx", "\\"], "plot_line_width": 1.4, "plot_bar_alpha": 1.0, "plot_bar_edge_color": "#000000", "plot_bar_edge_width": 0.8, "plot_hist_edge_color": "#000000", "plot_grid_color": "#C8C8C8", "plot_dumbbell_start_color": "#D4B24C", "plot_dumbbell_end_color": "#1F4E79", "plot_heatmap_cmap": COLORS.Cividis, } ) ``` The contrast theme: lightness-stepped colours plus hatches, print-safe. Navy, straw, dusty rose, charcoal and grey, each a clear lightness step from the next, so a greyscale print or photocopy still tells the series apart, and every pair stays distinct for deutan, protan and tritan readers. Bars take a hatch cycle and black edges, lines a dash cycle, scatter points a marker cycle. The value scale is Cividis. ### datachart.themes.SKETCH_THEME ``` SKETCH_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.YlOrRd, "color_general_multiple": [ "#E4572E", "#2E86AB", "#F5B700", "#7E5AAB", "#76B041", ], "color_parallel_hue_continuous": [ "#FDD49E", "#FC8D59", "#E4572E", "#99000D", ], "font_general_family": "sans-serif", "font_general_sansserif": [ "Comic Neue", "Humor Sans", "Comic Sans MS", ], "font_general_size": 11, "font_general_color": "#222222", "font_title_size": 15, "font_title_color": "#222222", "font_title_weight": FONT_WEIGHT.BOLD, "axes_spines_width": 1.6, "axes_ticks_length": 5, "chart_default_show_grid": None, "plot_line_width": 2.5, "plot_bar_alpha": 1.0, "plot_bar_edge_width": 1.0, "plot_hist_edge_width": 1.0, "plot_scatter_edge_color": "#222222", "plot_dumbbell_start_color": "#2E86AB", "plot_dumbbell_end_color": "#E4572E", "plot_dumbbell_edge_color": "#222222", "plot_dumbbell_connector_color": "#8A8A8A", "plot_heatmap_cmap": COLORS.YlOrRd, "plot_treemap_group_edge_width": 0, "plot_sketch_params": [0.5, 100, 2], "plot_sketch_halo_width": 1.5, } ) ``` The sketch theme: hand-drawn, xkcd-style wobble and halo, Comic Neue font. Paths wobble, lines carry a white halo, spines and lines are thick, the grid is off, and text is set in Comic Neue, which ships with the package; Humor Sans and Comic Sans MS are the fallbacks should the bundled face fail to register. ### datachart.themes.QUILL_THEME ``` QUILL_THEME: StyleAttrs = make_theme( { "color_general_singular": COLORS.Greys, "color_general_multiple": [INK], "color_parallel_hue": [INK], "color_parallel_hue_continuous": [ "#D9CCAA", "#8C7E5E", "#4A3C2A", INK, ], "muted_color": "#C9B892", "font_general_family": "serif", "font_general_serif": [ "IM FELL English", "IM FELL English SC", "Georgia", ], "font_general_size": 11, "font_general_color": INK, "font_title_size": 16, "font_title_color": INK, "font_title_style": FONT_STYLE.ITALIC, "font_title_weight": FONT_WEIGHT.NORMAL, "font_subtitle_color": INK, "font_xlabel_color": INK, "font_ylabel_color": INK, "axes_spines_color": INK, "axes_ticks_color": INK, "axes_spines_width": 1.4, "axes_ticks_length": 5, "axes_ticks_label_size": 9, "chart_default_show_grid": None, "chart_default_node_label_position": NETWORK_LABEL_POSITION.ABOVE, "plot_hatch_cycle": ["/", ".", "\\", "x", "-", "|"], "plot_linestyle_cycle": [ "-", "--", ":", "-.", [0, [7, 2, 1, 2, 1, 2]], [0, [2, 2]], ], "plot_marker_cycle": [ "o", {"marker": "s", "hollow": True}, "^", {"marker": "D", "hollow": True}, "v", {"marker": "P", "hollow": True}, ], "plot_sketch_params": [0.7, 90, 2], "plot_ink_stroke": { "width_scale": 1.6, "nib_angle": 32, "nib_floor": 0.35, "wobble": 0.22, "taper": 7, }, "plot_etch": { "spacing": 4.2, "jitter": 0.3, "angle_jitter": 2.5, "line_width": 0.6, "wash": 0.1, "color": INK, }, "plot_value_etch": { "washes": [PAPER] * 5, "hatches": ["", ".", "..", "//", "xx"], }, "plot_legend_label_color": INK, "plot_legend_font_size": 9, "plot_legend_title_size": 10, "plot_legend_edge_color": INK, "plot_line_width": 2.2, "plot_area_alpha": 1.0, "plot_area_linewidth": 0.8, "plot_stackedarea_alpha": 1.0, "plot_stackedarea_outline": True, "plot_stackedarea_edge_color": INK, "plot_stackedarea_edge_width": 0.9, "plot_bar_alpha": 1.0, "plot_bar_edge_width": 1.0, "plot_bar_edge_color": INK, "plot_bar_error_color": INK, "plot_value_color": INK, "plot_value_halo_width": 5, "plot_hist_alpha": 1.0, "plot_hist_edge_width": 1.0, "plot_hist_edge_color": INK, "plot_vline_color": INK, "plot_vline_style": LINE_STYLE.DASHED, "plot_hline_color": INK, "plot_hline_style": LINE_STYLE.DASHED, "plot_vspan_color": INK, "plot_hspan_color": INK, "plot_vspan_alpha": 1.0, "plot_hspan_alpha": 1.0, "plot_vspan_hatch": ".", "plot_hspan_hatch": ".", "plot_vspan_edge_color": INK, "plot_hspan_edge_color": INK, "plot_text_color": INK, "plot_text_box_facecolor": PAPER, "plot_text_box_edgecolor": INK, "plot_text_box_style": "round,pad=0.4,rounding_size=0.2", "plot_text_arrow_color": INK, "plot_gantt_dependency_color": INK, "plot_gantt_today_color": INK, "plot_dumbbell_start_color": PAPER, "plot_dumbbell_end_color": INK, "plot_dumbbell_edge_color": INK, "plot_dumbbell_edge_width": 0.8, "plot_dumbbell_connector_color": INK, "plot_dumbbell_arrow_color": INK, "plot_dumbbell_connector_width": 1.2, "plot_heatmap_cmap": COLORS.Greys, "plot_heatmap_frame_color": INK, "plot_heatmap_font_color": INK, "plot_heatmap_font_size": 10, "plot_calendar_heatmap_font_size": 8, "plot_heatmap_edge_width": 0.6, "plot_heatmap_edge_color": INK, "plot_calendar_heatmap_edge_color": PAPER, "plot_calendar_heatmap_month_line_color": INK, "plot_contour_color": INK, "plot_contour_line_width": 1.8, "plot_hexbin_edge_width": 0.3, "plot_hexbin_edge_color": INK, "plot_scatter_edge_width": 0.8, "plot_scatter_edge_color": INK, "plot_scatter_alpha": 0.8, "plot_swarm_edge_width": 0.6, "plot_swarm_edge_color": INK, "plot_swarm_alpha": 0.9, "plot_regression_color": INK, "plot_regression_ci_alpha": 0.12, "plot_parallel_color": INK, "plot_parallel_alpha": 0.35, "plot_parallel_axis_color": INK, "plot_parallel_tick_color": INK, "plot_parallel_tick_label_color": INK, "plot_parallel_tick_label_size": 9, "plot_parallel_tick_label_bg_color": None, "plot_parallel_dim_label_color": INK, "plot_box_edgecolor": INK, "plot_box_linewidth": 1.2, "plot_box_hatch": "/", "plot_box_median_color": INK, "plot_box_median_linewidth": 2.5, "plot_box_whisker_color": INK, "plot_box_whisker_linewidth": 1.2, "plot_box_cap_color": INK, "plot_box_cap_linewidth": 1.2, "plot_box_outlier_color": PAPER, "plot_box_outlier_edge_color": INK, "plot_box_alpha": 1.0, "plot_violin_edgecolor": INK, "plot_violin_linewidth": 1.2, "plot_violin_alpha": 1.0, "plot_violin_hatch": "\\", "plot_violin_inner_color": INK, "plot_violin_median_color": PAPER, "plot_ridgeline_edgecolor": INK, "plot_ridgeline_linewidth": 1.2, "plot_ridgeline_alpha": 1.0, "plot_ridgeline_hatch": "/", "plot_ridgeline_inner_color": INK, "plot_sankey_link_alpha": 0.25, "plot_sankey_node_edge_color": INK, "plot_sankey_node_edge_width": 1.0, "plot_sankey_node_fill": False, "plot_treemap_edge_color": INK, "plot_treemap_edge_width": 1.0, "plot_treemap_level_shade": 0.55, "plot_treemap_etch_density": [3, 1, 0], "plot_network_node_edge_color": INK, "plot_network_node_edge_width": 1.6, "plot_network_node_alpha": 1.0, "plot_network_edge_color": INK, "plot_network_edge_alpha": 0.9, "plot_network_edge_style": ARROW_STYLE.STRAIGHT, "plot_network_edge_curve": 0.0, "plot_network_edge_width_min": 2.0, "plot_network_edge_width_max": 2.8, "plot_network_edge_ink_stroke": { "width_scale": 1.4, "nib_floor": 1.0, "wobble": 0.0, "swell": 0.5, "noise": 0.12, }, "plot_network_group_linestyle": LINE_STYLE.DOTTED, } ) ``` The quill theme: black ink on white paper, as a quill and an etching needle draw. One ink only: series differ by line style, marker and etching, never by color. Series lines are broad-nib pen strokes whose width varies along the line, bars, areas and bodies are etched by hand instead of hatched, the furniture wobbles, and text is set in IM Fell English, which ships with the package, titles in its italic. # Constants Module ## datachart.constants Module containing the `constants`. The `constants` module provides a set of predefined constants used in the package. These include figure size, format, style, and other figure manipulation values. **Figure Constants** | CLASS | DESCRIPTION | | ------------ | ----------------------------- | | `FIG_SIZE` | The predefined figure sizes. | | `FIG_FORMAT` | The supported figure formats. | **Font Constants** | CLASS | DESCRIPTION | | ------------- | --------------------------- | | `FONT_STYLE` | The supported font styles. | | `FONT_WEIGHT` | The supported font weights. | **Line Constants** | CLASS | DESCRIPTION | | ----------------- | ---------------------------------------------- | | `LINE_MARKER` | The supported line markers. | | `LINE_STYLE` | The supported line styles. | | `LINE_DRAW_STYLE` | The supported line draw styles. | | `ARROW_STYLE` | The supported text annotation connector looks. | **Style Constants** | CLASS | DESCRIPTION | | ------------- | ----------------------------- | | `HATCH_STYLE` | The supported hatch styles. | | `COLORS` | The predefined colors. | | `THEME` | The predefined themes. | | `EMPHASIS` | The supported emphasis roles. | **Legend Constants** | CLASS | DESCRIPTION | | ----------------- | -------------------------------- | | `LEGEND_ALIGN` | The supported legend alignments. | | `LEGEND_LOCATION` | The supported legend locations. | **Chart Constants** | CLASS | DESCRIPTION | | ------------------- | --------------------------------------------- | | `BAR_MODE` | The supported bar modes. | | `SORT` | The supported category sort orders. | | `NORMALIZE` | The supported normalization options. | | `ORIENTATION` | The supported orientations. | | `VIOLIN_INNER` | The supported violin inner marks. | | `BANDWIDTH` | The supported kernel density bandwidth rules. | | `SWARM_MODE` | The supported swarm plot modes. | | `VALUE_FORMAT` | The predefined value formats. | | `DATE_FORMAT` | The predefined date formats. | | `SHOW_GRID` | The supported show grid options. | | `SCALE` | The supported scale options. | | `ASPECT_RATIO` | The supported aspect ratio options. | | `COLORBAR_LOCATION` | The supported colorbar locations. | **Chart-Specific Constants** | CLASS | DESCRIPTION | | ------------------------- | --------------------------------------------- | | `STACKED_AREA_BASELINE` | The supported stacked area baselines. | | `BUMP_RANK` | The supported bump chart ranking rules. | | `BUMP_LABEL_POSITION` | The supported end label positions. | | `RADIAL_TYPE` | The supported radial chart visuals. | | `RADIAL_DIRECTION` | The supported angular directions. | | `CALENDAR_WEEKDAY` | The supported week start days. | | `GANTT_DATE_PERIOD` | The supported date axis periods. | | `GANTT_VALUE` | The supported gantt chart value labels. | | `GANTT_SORT_KEY` | The supported gantt chart sort keys. | | `GANTT_ARROW_ENTRY` | The supported gantt dependency arrow entries. | | `DUMBBELL_VALUE` | The supported dumbbell chart value labels. | | `DUMBBELL_SORT_KEY` | The supported dumbbell chart sort keys. | | `HISTOGRAM_TYPE` | The supported histogram types. | | `RIDGELINE_SCALE` | The supported ridgeline density scales. | | `CONTOUR_LEVELS` | The supported contour level rules. | | `HEXBIN_REDUCE` | The supported hexbin aggregations. | | `NETWORK_LAYOUT` | The supported network chart layouts. | | `NETWORK_LABEL_POSITION` | The supported network node label positions. | | `SCATTER_MATRIX_DIAGONAL` | The supported scatter matrix diagonal cells. | ## Constants by Chart Which constants the parameters of each chart accept, by chart family. A constant used by one chart carries that chart's prefix; one shared across charts carries none. Style attributes take the constants named in their [typings](https://eriknovak.github.io/datachart/0.10.2/references/typings/index.md). ### Trends and Comparisons | Chart | Chart-specific | Shared | | -------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.charts.LineChart) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [StackedAreaChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.charts.StackedAreaChart) | [`STACKED_AREA_BASELINE`](#datachart.constants.STACKED_AREA_BASELINE) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [BumpChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.charts.BumpChart) | [`BUMP_RANK`](#datachart.constants.BUMP_RANK), [`BUMP_LABEL_POSITION`](#datachart.constants.BUMP_LABEL_POSITION) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.charts.BarChart) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`BAR_MODE`](#datachart.constants.BAR_MODE), [`SORT`](#datachart.constants.SORT), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [PyramidChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/pyramidchart/#datachart.charts.PyramidChart) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`SORT`](#datachart.constants.SORT), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID) | | [RadialChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/#datachart.charts.RadialChart) | [`RADIAL_TYPE`](#datachart.constants.RADIAL_TYPE), [`RADIAL_DIRECTION`](#datachart.constants.RADIAL_DIRECTION) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`BAR_MODE`](#datachart.constants.BAR_MODE), [`SORT`](#datachart.constants.SORT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE) | | [CalendarHeatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.charts.CalendarHeatmap) | [`CALENDAR_WEEKDAY`](#datachart.constants.CALENDAR_WEEKDAY) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`NORMALIZE`](#datachart.constants.NORMALIZE), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO), [`COLORBAR_LOCATION`](#datachart.constants.COLORBAR_LOCATION) | | [GanttChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.charts.GanttChart) | [`GANTT_DATE_PERIOD`](#datachart.constants.GANTT_DATE_PERIOD), [`GANTT_VALUE`](#datachart.constants.GANTT_VALUE), [`GANTT_SORT_KEY`](#datachart.constants.GANTT_SORT_KEY), [`GANTT_ARROW_ENTRY`](#datachart.constants.GANTT_ARROW_ENTRY) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`SORT`](#datachart.constants.SORT), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID) | | [DumbbellChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.charts.DumbbellChart) | [`DUMBBELL_VALUE`](#datachart.constants.DUMBBELL_VALUE), [`DUMBBELL_SORT_KEY`](#datachart.constants.DUMBBELL_SORT_KEY) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`LINE_MARKER`](#datachart.constants.LINE_MARKER), [`LINE_STYLE`](#datachart.constants.LINE_STYLE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`SORT`](#datachart.constants.SORT), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE) | ### Distributions | Chart | Chart-specific | Shared | | ----------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Histogram](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.charts.Histogram) | [`HISTOGRAM_TYPE`](#datachart.constants.HISTOGRAM_TYPE) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`BAR_MODE`](#datachart.constants.BAR_MODE), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.charts.BoxPlot) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.charts.ViolinPlot) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VIOLIN_INNER`](#datachart.constants.VIOLIN_INNER), [`BANDWIDTH`](#datachart.constants.BANDWIDTH), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.charts.SwarmPlot) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`SWARM_MODE`](#datachart.constants.SWARM_MODE), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [RaincloudPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.charts.RaincloudPlot) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`BANDWIDTH`](#datachart.constants.BANDWIDTH), [`SWARM_MODE`](#datachart.constants.SWARM_MODE), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [RidgelinePlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.charts.RidgelinePlot) | [`RIDGELINE_SCALE`](#datachart.constants.RIDGELINE_SCALE) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`SORT`](#datachart.constants.SORT), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VIOLIN_INNER`](#datachart.constants.VIOLIN_INNER), [`BANDWIDTH`](#datachart.constants.BANDWIDTH), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | ### Relationships | Chart | Chart-specific | Shared | | -------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.charts.ScatterChart) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [Heatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.charts.Heatmap) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`NORMALIZE`](#datachart.constants.NORMALIZE), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO), [`COLORBAR_LOCATION`](#datachart.constants.COLORBAR_LOCATION) | | [ContourChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.charts.ContourChart) | [`CONTOUR_LEVELS`](#datachart.constants.CONTOUR_LEVELS) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`NORMALIZE`](#datachart.constants.NORMALIZE), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO), [`COLORBAR_LOCATION`](#datachart.constants.COLORBAR_LOCATION) | | [HexbinChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.charts.HexbinChart) | [`HEXBIN_REDUCE`](#datachart.constants.HEXBIN_REDUCE) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`NORMALIZE`](#datachart.constants.NORMALIZE), [`ORIENTATION`](#datachart.constants.ORIENTATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT), [`DATE_FORMAT`](#datachart.constants.DATE_FORMAT), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`SCALE`](#datachart.constants.SCALE), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO), [`COLORBAR_LOCATION`](#datachart.constants.COLORBAR_LOCATION) | | [ParallelCoords](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.charts.ParallelCoords) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`EMPHASIS`](#datachart.constants.EMPHASIS), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`SHOW_GRID`](#datachart.constants.SHOW_GRID), [`ASPECT_RATIO`](#datachart.constants.ASPECT_RATIO) | | [NetworkChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.charts.NetworkChart) | [`NETWORK_LAYOUT`](#datachart.constants.NETWORK_LAYOUT), [`NETWORK_LABEL_POSITION`](#datachart.constants.NETWORK_LABEL_POSITION) | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT) | | [ScatterMatrix](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/#datachart.charts.ScatterMatrix) | [`SCATTER_MATRIX_DIAGONAL`](#datachart.constants.SCATTER_MATRIX_DIAGONAL) | [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`SHOW_GRID`](#datachart.constants.SHOW_GRID) | ### Flows | Chart | Chart-specific | Shared | | ----------------------------------------------------------------------------------------------------------------------- | -------------- | ------------------------------------------------------------------------------------------------ | | [SankeyChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.charts.SankeyChart) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT) | ### Part of a Whole | Chart | Chart-specific | Shared | | ----------------------------------------------------------------------------------------------------------- | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Treemap](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.charts.Treemap) | — | [`FIG_SIZE`](#datachart.constants.FIG_SIZE), [`LEGEND_ALIGN`](#datachart.constants.LEGEND_ALIGN), [`LEGEND_LOCATION`](#datachart.constants.LEGEND_LOCATION), [`VALUE_FORMAT`](#datachart.constants.VALUE_FORMAT) | ## Figure Constants ### datachart.constants.FIG_SIZE The predefined figure sizes. All values are `(width, height)` in inches, matplotlib's `figsize` unit. Paper figures are anchored to the printable area of an A4 page with standard 2.5 cm margins — a 6.3 x 9.7 in (16.0 x 24.6 cm) text block. `FULL` spans the text-block width; `HALF` spans one of two columns separated by a 0.3 in (0.8 cm) gap (3.0 in / 7.6 cm each). Widths cross with a height — `SHORT` (2.4 in / 6.1 cm), `MEDIUM` (4.8 in / 12.2 cm), or `TALL` (7.2 in / 18.3 cm). Passed as the `figsize` chart setting. Examples: ``` >>> from datachart.constants import FIG_SIZE >>> FIG_SIZE.DEFAULT (6.4, 4.8) ``` | ATTRIBUTE | DESCRIPTION | | -------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default figure size. Equals to (6.4, 4.8) in (16.3 x 12.2 cm). **TYPE:** `Tuple[float, float]` | | `FULL_SHORT` | The short, full-width figure size. Equals to (6.3, 2.4) in (16.0 x 6.1 cm). **TYPE:** `Tuple[float, float]` | | `FULL_MEDIUM` | The medium, full-width figure size. Equals to (6.3, 4.8) in (16.0 x 12.2 cm). **TYPE:** `Tuple[float, float]` | | `FULL_TALL` | The tall, full-width figure size. Equals to (6.3, 7.2) in (16.0 x 18.3 cm). **TYPE:** `Tuple[float, float]` | | `HALF_SHORT` | The short, half-width figure size. Equals to (3.0, 2.4) in (7.6 x 6.1 cm). **TYPE:** `Tuple[float, float]` | | `HALF_MEDIUM` | The medium, half-width figure size. Equals to (3.0, 4.8) in (7.6 x 12.2 cm). **TYPE:** `Tuple[float, float]` | | `HALF_TALL` | The tall, half-width figure size. Equals to (3.0, 7.2) in (7.6 x 18.3 cm). **TYPE:** `Tuple[float, float]` | | `HALF_SQUARE` | The square, half-width figure size. Equals to (3.0, 3.0) in (7.6 x 7.6 cm). **TYPE:** `Tuple[float, float]` | | `A4_PORTRAIT` | The A4 portrait printable-area figure size. Equals to (6.3, 9.7) in (16.0 x 24.6 cm). **TYPE:** `Tuple[float, float]` | | `A4_LANDSCAPE` | The A4 landscape printable-area figure size. Equals to (9.7, 6.3) in (24.6 x 16.0 cm). **TYPE:** `Tuple[float, float]` | | `SQUARE` | The square figure size. Equals to (4.8, 4.8) in (12.2 x 12.2 cm). **TYPE:** `Tuple[float, float]` | | `SLIDE_16_9` | The 16:9 slide figure size (PowerPoint/Google Slides). Equals to (13.33, 7.5) in (33.9 x 19.1 cm). **TYPE:** `Tuple[float, float]` | | `SLIDE_4_3` | The 4:3 slide figure size (PowerPoint/Google Slides). Equals to (10.0, 7.5) in (25.4 x 19.1 cm). **TYPE:** `Tuple[float, float]` | | `BEAMER_16_9` | The 16:9 beamer frame figure size. Equals to (6.3, 3.54) in (16.0 x 9.0 cm). **TYPE:** `Tuple[float, float]` | | `BEAMER_4_3` | The 4:3 beamer frame figure size. Equals to (5.04, 3.78) in (12.8 x 9.6 cm). **TYPE:** `Tuple[float, float]` | ### datachart.constants.FIG_FORMAT The supported figure formats. Passed as the `format` argument of save_figure. Examples: ``` >>> from datachart.constants import FIG_FORMAT >>> FIG_FORMAT.DEFAULT "png" ``` | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------------------------- | | `DEFAULT` | The default format. Same as FIG_FORMAT.PNG. **TYPE:** `str` | | `SVG` | The svg format. Equals to "svg". **TYPE:** `str` | | `PDF` | The pdf format. Equals to "pdf". **TYPE:** `str` | | `PNG` | The png format. Equals to "png". **TYPE:** `str` | | `WEBP` | The webp format. Equals to "webp". **TYPE:** `str` | | `EPS` | The eps format (Encapsulated PostScript). Equals to "eps". **TYPE:** `str` | | `JPG` | The jpg format. Equals to "jpg". **TYPE:** `str` | | `TIFF` | The tiff format. Equals to "tiff". **TYPE:** `str` | ## Font Constants ### datachart.constants.FONT_STYLE The supported font styles. Examples: ``` >>> from datachart.constants import FONT_STYLE >>> FONT_STYLE.DEFAULT "normal" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------------ | | `DEFAULT` | The default font style. Same as FONT_STYLE.NORMAL. **TYPE:** `str` | | `NORMAL` | The normal font style. Equals to "normal". **TYPE:** `str` | | `ITALIC` | The italic font style. Equals to "italic". **TYPE:** `str` | | `OBLIQUE` | The oblique font style. Equals to "oblique". **TYPE:** `str` | ### datachart.constants.FONT_WEIGHT The supported font weights. Used by the `font_*_weight` style attributes (general, title, subtitle, axis labels). Examples: ``` >>> from datachart.constants import FONT_WEIGHT >>> FONT_WEIGHT.DEFAULT "normal" ``` | ATTRIBUTE | DESCRIPTION | | ------------- | -------------------------------------------------------------------- | | `DEFAULT` | The default font weight. Same as FONT_WEIGHT.NORMAL. **TYPE:** `str` | | `ULTRA_LIGHT` | The ultra light font weight. Equals to "ultralight". **TYPE:** `str` | | `LIGHT` | The light font weight. Equals to "light". **TYPE:** `str` | | `NORMAL` | The normal font weight. Equals to "normal". **TYPE:** `str` | | `MEDIUM` | The medium font weight. Equals to "medium". **TYPE:** `str` | | `SEMIBOLD` | The semibold font weight. Equals to "semibold". **TYPE:** `str` | | `BOLD` | The bold font weight. Equals to "bold". **TYPE:** `str` | | `EXTRA_BOLD` | The extra bold font weight. Equals to "extra bold". **TYPE:** `str` | | `HEAVY` | The heavy font weight. Equals to "heavy". **TYPE:** `str` | | `BLACK` | The black font weight. Equals to "black". **TYPE:** `str` | ## Line Constants ### datachart.constants.LINE_MARKER The supported line markers. Used by the `plot_line_marker` (line charts) and `plot_scatter_marker` (scatter charts) style attributes. Examples: ``` >>> from datachart.constants import LINE_MARKER >>> LINE_MARKER.PIXEL "," ``` | ATTRIBUTE | DESCRIPTION | | ---------------- | -------------------------------------------------------------- | | `NONE` | No marker. Equals to "". **TYPE:** `str` | | `PIXEL` | The pixel line marker. Equals to ",". **TYPE:** `str` | | `POINT` | The point line marker. Equals to ".". **TYPE:** `str` | | `CIRCLE` | The circle line marker. Equals to "o". **TYPE:** `str` | | `DIAMOND` | The diamond line marker. Equals to "D". **TYPE:** `str` | | `THIN_DIAMOND` | The thin diamond line marker. Equals to "d". **TYPE:** `str` | | `TRIANGLE` | The triangle (up) line marker. Equals to "^". **TYPE:** `str` | | `TRIANGLE_DOWN` | The triangle down line marker. Equals to "v". **TYPE:** `str` | | `TRIANGLE_LEFT` | The triangle left line marker. Equals to "\<". **TYPE:** `str` | | `TRIANGLE_RIGHT` | The triangle right line marker. Equals to ">". **TYPE:** `str` | | `SQUARE` | The square line marker. Equals to "s". **TYPE:** `str` | | `PENTAGON` | The pentagon line marker. Equals to "p". **TYPE:** `str` | | `HEXAGON` | The hexagon line marker. Equals to "h". **TYPE:** `str` | | `STAR` | The star line marker. Equals to "\*". **TYPE:** `str` | | `CROSS` | The cross line marker. Equals to "x". **TYPE:** `str` | | `PLUS` | The plus line marker. Equals to "+". **TYPE:** `str` | | `VLINE` | The vertical line marker. Equals to " | | `HLINE` | The horizontal line marker. Equals to "\_". **TYPE:** `str` | ### datachart.constants.LINE_STYLE The supported line styles. Used by the `plot_line_style` style attribute of line charts. Examples: ``` >>> from datachart.constants import LINE_STYLE >>> LINE_STYLE.SOLID "-" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------- | | `NONE` | No line style. Equals to "". **TYPE:** `str` | | `SOLID` | The solid line style. Equals to "-". **TYPE:** `str` | | `DASHED` | The dashed line style. Equals to "--". **TYPE:** `str` | | `DASHDOT` | The dashdot line style. Equals to "-.". **TYPE:** `str` | | `DOTTED` | The dotted line style. Equals to ":". **TYPE:** `str` | ### datachart.constants.LINE_DRAW_STYLE The supported line draw styles. Used by the `plot_line_drawstyle` style attribute of line charts. Examples: ``` >>> from datachart.constants import LINE_DRAW_STYLE >>> LINE_DRAW_STYLE.DEFAULT "default" ``` | ATTRIBUTE | DESCRIPTION | | ------------ | ----------------------------------------------------------------------- | | `DEFAULT` | The default line draw style. Equals to "default". **TYPE:** `str` | | `STEPS_PRE` | The pre-steps line draw style. Equals to "steps-pre". **TYPE:** `str` | | `STEPS_MID` | The mid-steps line draw style. Equals to "steps-mid". **TYPE:** `str` | | `STEPS_POST` | The post-steps line draw style. Equals to "steps-post". **TYPE:** `str` | ### datachart.constants.ARROW_STYLE The supported connector looks. The one constant for every drawn connector: the `plot_text_arrow_style` style attribute of text annotations and the `plot_network_edge_style` style attribute of network charts. Each value names a complete connector look — the line shape, curvature, and the gap on the text side. For an annotation, a curved look bows toward the side with the most open space around the chart's data; `plot_text_arrow_curve` pins the bow exactly, and the other `plot_text_arrow_*` style attributes override single properties of the chosen look. A raw matplotlib arrow style string (e.g. `"-|>"`) is also accepted. A network edge takes only the two headless looks, `CURVE` and `STRAIGHT`, bowed by `plot_network_edge_curve`; its arrowhead comes from the chart's `directed` argument. Examples: ``` >>> from datachart.constants import ARROW_STYLE >>> ARROW_STYLE.CURVE "curve" ``` | ATTRIBUTE | DESCRIPTION | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `CURVE` | A curved plain line with a small text-side gap. The default. Text annotations and network edges. Equals to "curve". **TYPE:** `str` | | `CURVE_ARROW` | The same curve with an arrowhead at the target. Text annotations only. Equals to "curve-arrow". **TYPE:** `str` | | `STRAIGHT` | A straight plain line with a small text-side gap. Text annotations and network edges. Equals to "straight". **TYPE:** `str` | | `TOUCHING` | A straight plain line starting flush at the text box border. Text annotations only. Equals to "touching". **TYPE:** `str` | | `ARROW` | A straight line with an arrowhead at the target. Text annotations only. Equals to "arrow". **TYPE:** `str` | ## Style Constants ### datachart.constants.HATCH_STYLE The supported hatch styles. Used by the `plot_bar_hatch` and `plot_hist_hatch` style attributes, and by the `HATCH` theme's hatch cycle. Examples: ``` >>> from datachart.constants import HATCH_STYLE >>> HATCH_STYLE.DEFAULT None ``` | ATTRIBUTE | DESCRIPTION | | ------------------ | ---------------------------------------------------------------- | | `DEFAULT` | The default hatch style. Equals to None. **TYPE:** `str` | | `DIAGONAL` | The diagonal hatch style. Equals to "/". **TYPE:** `str` | | `BACK_DIAGONAL` | The back diagonal hatch style. Equals to "\\". **TYPE:** `str` | | `VERTICAL` | The vertical hatch style. Equals to " | | `HORIZONTAL` | The horizontal hatch style. Equals to "-". **TYPE:** `str` | | `CROSSED` | The crossed hatch style. Equals to "+". **TYPE:** `str` | | `CROSSED_DIAGONAL` | The crossed diagonal hatch style. Equals to "x". **TYPE:** `str` | | `DOTS` | The dots hatch style. Equals to ".". **TYPE:** `str` | | `CIRCLES` | The circles hatch style. Equals to "o". **TYPE:** `str` | | `STARS` | The stars hatch style. Equals to "\*". **TYPE:** `str` | ### datachart.constants.COLORS The predefined colors using [pypalettes](https://y-sunflower.github.io/pypalettes/). All palette names are valid pypalettes identifiers. You can use any of the 2500+ palettes available in pypalettes by passing the palette name as a string. Accepted anywhere a palette is: the `color_general_singular` and `color_general_multiple` config attributes, and the heatmap and parallel coords color settings. All predefined palettes are rendered in the [Colormaps guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/colormaps/index.md). A single matplotlib color (`"#B5651D"`, `"tab:blue"`, `"rebeccapurple"`) is accepted in the same places and is used as a palette of one, repeated for every series that asks for a color. A name that is both a palette and a color, such as `"Red"` or `"pink"`, is read as the palette. Examples: ``` >>> from datachart.constants import COLORS >>> COLORS.Blues 'Blues' ``` | ATTRIBUTE | DESCRIPTION | | ---------------- | ------------------------------------------------------------------------------------------------- | | `Blues` | Sequential blue palette. Equals to "Blues". **TYPE:** `str` | | `Greens` | Sequential green palette. Equals to "Greens". **TYPE:** `str` | | `Oranges` | Sequential orange palette. Equals to "Oranges". **TYPE:** `str` | | `Purples` | Sequential purple palette. Equals to "Purples". **TYPE:** `str` | | `Reds` | Sequential red palette. Equals to "Reds". **TYPE:** `str` | | `Sunset2` | Multi-hue sunset palette. Equals to "Sunset2". **TYPE:** `str` | | `YlGnBu` | Multi-hue yellow-green-blue palette. Equals to "YlGnBu". **TYPE:** `str` | | `YlOrRd` | Multi-hue yellow-orange-red palette. Equals to "YlOrRd". **TYPE:** `str` | | `YlOrBr` | Multi-hue yellow-orange-brown palette. Equals to "YlOrBr". **TYPE:** `str` | | `PuBuGn` | Multi-hue purple-blue-green palette. Equals to "PuBuGn". **TYPE:** `str` | | `GnBu` | Multi-hue green-blue palette. Equals to "GnBu". **TYPE:** `str` | | `Egypt` | Multi-hue Egypt palette. Equals to "Egypt". **TYPE:** `str` | | `Hiroshige` | Multi-hue Hiroshige palette. Equals to "Hiroshige". **TYPE:** `str` | | `Lake` | Multi-hue lake palette. Equals to "Lake". **TYPE:** `str` | | `Neon` | Multi-hue neon palette. Equals to "Neon". **TYPE:** `str` | | `RdBu` | Diverging red-blue palette. Equals to "RdBu". **TYPE:** `str` | | `BrBG` | Diverging brown-blue-green palette. Equals to "BrBG". **TYPE:** `str` | | `PuOr` | Diverging purple-orange palette. Equals to "PuOr". **TYPE:** `str` | | `Spectral` | Diverging spectral palette. Equals to "Spectral". **TYPE:** `str` | | `RdYlBu` | Diverging red-yellow-blue palette. Equals to "RdYlBu". **TYPE:** `str` | | `RdYlGn` | Diverging red-yellow-green palette. Equals to "RdYlGn". **TYPE:** `str` | | `Pastel` | Soft pastel categorical palette. Equals to "Pastel". **TYPE:** `str` | | `Set2` | ColorBrewer Set2 categorical palette. Equals to "Set2". **TYPE:** `str` | | `Accent` | ColorBrewer Accent categorical palette. Equals to "Accent". **TYPE:** `str` | | `Dark2` | ColorBrewer Dark2 categorical palette. Equals to "Dark2". **TYPE:** `str` | | `Paired` | ColorBrewer Paired categorical palette (high contrast). Equals to "Paired". **TYPE:** `str` | | `Set1` | ColorBrewer Set1 categorical palette (high contrast). Equals to "Set1". **TYPE:** `str` | | `Greys` | Grayscale palette for monochrome visualizations. Equals to "Greys". **TYPE:** `str` | | `Viridis` | Perceptually uniform, color-blind friendly. Equals to "Viridis". **TYPE:** `str` | | `Cividis` | Color-blind friendly (optimized for CVD). Equals to "Cividis". **TYPE:** `str` | | `Inferno` | Perceptually uniform, color-blind friendly. Equals to "Inferno". **TYPE:** `str` | | `Plasma` | Perceptually uniform, color-blind friendly. Equals to "Plasma". **TYPE:** `str` | | `Magma` | Perceptually uniform, color-blind friendly. Equals to "magma". **TYPE:** `str` | | `Turbo` | Rainbow-like but perceptually better. Equals to "turbo". **TYPE:** `str` | | `OkabeIto` | Okabe-Ito categorical palette, color-blind safe. Equals to "OkabeIto". **TYPE:** `str` | | `OkabeIto_Black` | Okabe-Ito palette including black. Equals to "OkabeIto_black". **TYPE:** `str` | | `Coolwarm` | Diverging cool-warm palette. Equals to "coolwarm". **TYPE:** `str` | | `Tab10` | Tableau 10-color categorical palette. Equals to "tab10". **TYPE:** `str` | | `Tab20` | Tableau 20-color categorical palette. Equals to "tab20". **TYPE:** `str` | | `PaperYlGnBu` | Diversified YlGnBu categorical palette for publications. Equals to "PaperYlGnBu". **TYPE:** `str` | | `PaperAccent` | Two-color blue/red accent pair for publications. Equals to "PaperAccent". **TYPE:** `str` | ### datachart.constants.THEME The predefined themes. Applied with config.set_theme. Every theme applied to the same set of charts is shown in the [Theme Gallery](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/theme-gallery/index.md). Examples: ``` >>> from datachart.constants import THEME >>> THEME.DEFAULT "default" ``` | ATTRIBUTE | DESCRIPTION | | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default theme. Equals to "default". **TYPE:** `str` | | `GREYSCALE` | The greyscale theme. Equals to "greyscale". **TYPE:** `str` | | `INK` | The ink theme (dark-ink accents, print-ready). Equals to "ink". **TYPE:** `str` | | `HATCH` | The hatch theme (hatch cycle, value labels, dotted grid). Equals to "hatch". **TYPE:** `str` | | `MINIMAL` | The minimal theme (accent violet, no spines, flat bars). Equals to "minimal". **TYPE:** `str` | | `MATERIAL` | The material theme (Google palette, light grid). Equals to "material". **TYPE:** `str` | | `SKETCH` | The sketch theme (hand-drawn, xkcd-style wobble and halo, Comic Neue font). Equals to "sketch". **TYPE:** `str` | | `QUILL` | The quill theme (black ink on white paper: pen-stroked lines, etched fills, IM Fell English font). Equals to "quill". **TYPE:** `str` | | `HARBOR` | The harbor theme (navy and amber in lightness steps, colour-blind safe). Equals to "harbor". **TYPE:** `str` | | `MUTED` | The muted theme (Tol's muted colours, dash and marker cycles, colour-blind safe). Equals to "muted". **TYPE:** `str` | | `CONTRAST` | The contrast theme (lightness-stepped colours plus hatches, print-safe). Equals to "contrast". **TYPE:** `str` | ### datachart.constants.EMPHASIS The supported emphasis roles. Set per chart via the `emphasis` key in a charts list, or per figure via the `emphasis` argument of Panel. Examples: ``` >>> from datachart.constants import EMPHASIS >>> EMPHASIS.BACKGROUND "background" ``` | ATTRIBUTE | DESCRIPTION | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `BACKGROUND` | Mute a series into context: theme muted color, lowered alpha, thinner strokes, behind the others, no legend entry. Equals to "background". **TYPE:** `str` | | `HIGHLIGHT` | Bold a series and bring it to the front of the data layers; it keeps its color and legend entry. Equals to "highlight". **TYPE:** `str` | ## Legend Constants ### datachart.constants.LEGEND_ALIGN The supported legend alignments. Used by the `plot_legend_alignment` style attribute; aligns the legend's title and entries against each other. Examples: ``` >>> from datachart.constants import LEGEND_ALIGN >>> LEGEND_ALIGN.DEFAULT "left" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------------------ | | `DEFAULT` | The default legend alignment. Same as LEGEND_ALIGN.LEFT. **TYPE:** `str` | | `CENTER` | The center legend alignment. Equals to "center". **TYPE:** `str` | | `RIGHT` | The right legend alignment. Equals to "right". **TYPE:** `str` | | `LEFT` | The left legend alignment. Equals to "left". **TYPE:** `str` | ### datachart.constants.LEGEND_LOCATION The supported legend locations. Used by the `plot_legend_location` style attribute and the `location` field of a chart's `legend` setting. The in-axes members place the legend within the chart; the `OUTSIDE_*` members place it beside the axes, on the named edge, with nothing clipped. Examples: ``` >>> from datachart.constants import LEGEND_LOCATION >>> LEGEND_LOCATION.BEST "best" ``` | ATTRIBUTE | DESCRIPTION | | ---------------- | ------------------------------------------------------------------------------ | | `BEST` | Automatic best location. Equals to "best". **TYPE:** `str` | | `UPPER_RIGHT` | Upper right corner. Equals to "upper right". **TYPE:** `str` | | `UPPER_LEFT` | Upper left corner. Equals to "upper left". **TYPE:** `str` | | `LOWER_LEFT` | Lower left corner. Equals to "lower left". **TYPE:** `str` | | `LOWER_RIGHT` | Lower right corner. Equals to "lower right". **TYPE:** `str` | | `RIGHT` | Center right. Equals to "right". **TYPE:** `str` | | `CENTER_LEFT` | Center left. Equals to "center left". **TYPE:** `str` | | `CENTER_RIGHT` | Center right. Equals to "center right". **TYPE:** `str` | | `LOWER_CENTER` | Lower center. Equals to "lower center". **TYPE:** `str` | | `UPPER_CENTER` | Upper center. Equals to "upper center". **TYPE:** `str` | | `CENTER` | Center. Equals to "center". **TYPE:** `str` | | `OUTSIDE_RIGHT` | Beside the right edge, top-aligned. Equals to "outside right". **TYPE:** `str` | | `OUTSIDE_LEFT` | Beside the left edge, top-aligned. Equals to "outside left". **TYPE:** `str` | | `OUTSIDE_TOP` | Above the axes, centered. Equals to "outside top". **TYPE:** `str` | | `OUTSIDE_BOTTOM` | Below the axes, centered. Equals to "outside bottom". **TYPE:** `str` | ## Chart Constants Constants several charts share. ### datachart.constants.BAR_MODE The supported bar modes. Passed as the `bar_mode` setting of bar charts, histograms, and Panel: how multiple series share the axis. Bar charts and panels default to `GROUP`; histograms default to `STACK`, and treat `GROUP` (which has no histogram meaning) as `OVERLAY`. Examples: ``` >>> from datachart.constants import BAR_MODE >>> BAR_MODE.DEFAULT "group" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------------------------------------- | | `DEFAULT` | The default bar mode. Same as BAR_MODE.GROUP. **TYPE:** `str` | | `GROUP` | The series are drawn side by side. Equals to "group". **TYPE:** `str` | | `STACK` | The series are stacked on top of each other. Equals to "stack". **TYPE:** `str` | | `OVERLAY` | The series are drawn over each other at the same position. Equals to "overlay". **TYPE:** `str` | ### datachart.constants.SORT The supported category sort orders. Passed as the `sort` setting of the bar-type fronts (`BarChart`, `PyramidChart`, and the `RadialChart` bar visual): the order the categories are drawn in, by value. One order serves every series in the chart, keyed by the total across them or by the series `sort_by` names; ties keep input order. Examples: ``` >>> from datachart.constants import SORT >>> SORT.DEFAULT None ``` | ATTRIBUTE | DESCRIPTION | | ------------ | ------------------------------------------------------------ | | `DEFAULT` | The default sort. Same as SORT.NONE. **TYPE:** `None` | | `NONE` | Input order. Equals to None. **TYPE:** `None` | | `ASCENDING` | Smallest value first. Equals to "ascending". **TYPE:** `str` | | `DESCENDING` | Largest value first. Equals to "descending". **TYPE:** `str` | ### datachart.constants.NORMALIZE The supported normalization options. Passed as the heatmap's `norm` attribute: normalizes the cell values before they are mapped to colors. Distinct from SCALE, which sets an axis scale. Examples: ``` >>> from datachart.constants import NORMALIZE >>> NORMALIZE.LINEAR "linear" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------- | | `LINEAR` | The linear normalization. Equals to "linear". **TYPE:** `str` | | `LOG` | The logistic normalization. Equals to "log". **TYPE:** `str` | | `SYMLOG` | The symlog normalization. Equals to "symlog". **TYPE:** `str` | | `ASINH` | The asinh normalization. Equals to "asinh". **TYPE:** `str` | | `LOGIT` | The logit normalization. Equals to "logit". **TYPE:** `str` | ### datachart.constants.ORIENTATION The supported orientations. Passed as the `orientation` setting of bar charts, histograms, box plots, and violin plots. Examples: ``` >>> from datachart.constants import ORIENTATION >>> ORIENTATION.HORIZONTAL "horizontal" ``` | ATTRIBUTE | DESCRIPTION | | ------------ | ------------------------------------------------------------------- | | `HORIZONTAL` | The horizontal orientation. Equals to "horizontal". **TYPE:** `str` | | `VERTICAL` | The vertical orientation. Equals to "vertical". **TYPE:** `str` | ### datachart.constants.VIOLIN_INNER The supported violin inner marks. Passed as the `inner` setting of violin plots; `None` draws the body only. Examples: ``` >>> from datachart.constants import VIOLIN_INNER >>> VIOLIN_INNER.BOX "box" ``` | ATTRIBUTE | DESCRIPTION | | ----------- | --------------------------------------------------------------------------------------------------------------------------- | | `BOX` | A thin quartile bar, a 1.5·IQR whisker line, and a median dot. Equals to "box". **TYPE:** `str` | | `QUARTILES` | A dashed median line and dotted first and third quartile lines, clipped to the body. Equals to "quartiles". **TYPE:** `str` | | `MEDIAN` | A single solid median line clipped to the body. Equals to "median". **TYPE:** `str` | ### datachart.constants.BANDWIDTH The supported kernel density bandwidth rules. Passed as the `bandwidth` setting of violin plots: the rule of thumb that sizes the Gaussian kernel. A number is also accepted, as a factor applied to the standard deviation of the values — smaller is sharper, larger is smoother. Examples: ``` >>> from datachart.constants import BANDWIDTH >>> BANDWIDTH.DEFAULT "scott" ``` | ATTRIBUTE | DESCRIPTION | | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default rule. Same as BANDWIDTH.SCOTT. **TYPE:** `str` | | `SCOTT` | Scott's rule of thumb, n \*\* (-1/5) times the standard deviation. Equals to "scott". **TYPE:** `str` | | `SILVERMAN` | Silverman's rule of thumb, (3n/4) \*\* (-1/5) times the standard deviation — about 6% wider than Scott's, so the two look nearly the same. Equals to "silverman". **TYPE:** `str` | ### datachart.constants.SWARM_MODE The supported swarm plot modes. Passed as the `mode` setting of swarm plots: how the points of one group spread across the category width. Examples: ``` >>> from datachart.constants import SWARM_MODE >>> SWARM_MODE.SWARM "swarm" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------------------------------------------------------ | | `SWARM` | The beeswarm mode: non-overlapping offsets computed from the marker size. Equals to "swarm". **TYPE:** `str` | | `STRIP` | The strip mode: seeded uniform jitter. Equals to "strip". **TYPE:** `str` | ### datachart.constants.VALUE_FORMAT The predefined value formats. Passed as the `value_format` attribute of every chart that takes `show_values` (the value labels printed beside its marks) or as the heatmap's `valfmt` attribute (the values drawn in the cells). Examples: ``` >>> from datachart.constants import VALUE_FORMAT >>> VALUE_FORMAT.DEFAULT "{x}" ``` | ATTRIBUTE | DESCRIPTION | | ------------- | ------------------------------------------------------------------------------------ | | `DEFAULT` | The default value format. Equals to "{x}". **TYPE:** `str` | | `INTEGER` | The integer value format (works on floats too). Equals to "{x:.0f}". **TYPE:** `str` | | `DECIMAL` | The decimal value format (1 decimal place). Equals to "{x:.1f}". **TYPE:** `str` | | `DECIMAL_2` | The decimal value format (2 decimal places). Equals to "{x:.2f}". **TYPE:** `str` | | `DECIMAL_3` | The decimal value format (3 decimal places). Equals to "{x:.3f}". **TYPE:** `str` | | `PERCENT` | The percentage value format (1 decimal place). Equals to "{x:.1%}". **TYPE:** `str` | | `PERCENT_INT` | The percentage value format (no decimals). Equals to "{x:.0%}". **TYPE:** `str` | | `SCIENTIFIC` | The scientific notation format. Equals to "{x:.2e}". **TYPE:** `str` | | `THOUSANDS` | The thousands separator format. Equals to "{x:,.0f}". **TYPE:** `str` | ### datachart.constants.DATE_FORMAT The predefined date formats. Passed as the `xticks_format` or `yticks_format` attribute of a chart whose axis holds datetime values, to label its ticks. Every member but `AUTO` is a `strftime` pattern; any other pattern is accepted as well. On a time axis `AUTO` picks concise, non-repeating labels for the visible span; on a category axis with date labels it prints the ISO date, plus the time when any label carries one. Examples: ``` >>> from datachart.constants import DATE_FORMAT >>> DATE_FORMAT.YEAR_MONTH "%Y-%m" ``` | ATTRIBUTE | DESCRIPTION | | ------------ | ------------------------------------------------------------------------------------------------ | | `AUTO` | Pick the labels from the visible span: concise, non-repeating. Equals to "auto". **TYPE:** `str` | | `ISO` | The ISO 8601 date. Equals to "%Y-%m-%d". **TYPE:** `str` | | `YEAR` | The four-digit year. Equals to "%Y". **TYPE:** `str` | | `YEAR_MONTH` | The year and month. Equals to "%Y-%m". **TYPE:** `str` | | `MONTH_DAY` | The month and day. Equals to "%m-%d". **TYPE:** `str` | | `DAY` | The day of the month. Equals to "%d". **TYPE:** `str` | | `TIME` | The hour and minute. Equals to "%H:%M". **TYPE:** `str` | ### datachart.constants.SHOW_GRID The supported show grid options. Passed as the `show_grid` chart setting: which grid lines to draw. When unset (or `NONE`), the theme's `chart_default_show_grid` fills in. The members name a set to draw, so there is no member for "no grid at all": pass `False` for that. Examples: ``` >>> from datachart.constants import SHOW_GRID >>> SHOW_GRID.DEFAULT None ``` | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------------------- | | `DEFAULT` | The default show grid. Same as SHOW_GRID.NONE. **TYPE:** `str` | | `NONE` | No explicit grid; the theme default applies. Equals to None. **TYPE:** `None` | | `X` | Show the x-axis grid. Equals to "x". **TYPE:** `str` | | `Y` | Show the y-axis grid. Equals to "y". **TYPE:** `str` | | `BOTH` | Show both the x- and y-axis grid. Equals to "both". **TYPE:** `str` | ### datachart.constants.SCALE The supported scale options. Passed as the `scalex`/`scaley` chart settings to set an axis scale. Distinct from NORMALIZE, which normalizes heatmap colors. Examples: ``` >>> from datachart.constants import SCALE >>> SCALE.DEFAULT "linear" ``` | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------- | | `DEFAULT` | The default scale. Same as SCALE.LINEAR. **TYPE:** `str` | | `LINEAR` | The linear scale. Equals to "linear". **TYPE:** `str` | | `LOG` | The log scale. Equals to "log". **TYPE:** `str` | | `SYMLOG` | The symlog scale. Equals to "symlog". **TYPE:** `str` | | `ASINH` | The asinh scale. Equals to "asinh". **TYPE:** `str` | ### datachart.constants.ASPECT_RATIO The supported aspect ratio options. Passed as the `aspect_ratio` chart setting: the ratio of the y-unit to the x-unit on screen. Examples: ``` >>> from datachart.constants import ASPECT_RATIO >>> ASPECT_RATIO.DEFAULT "auto" ``` | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------------------- | | `DEFAULT` | The default aspect ratio. Same as ASPECT_RATIO.AUTO. **TYPE:** `str` | | `AUTO` | Automatic aspect ratio. Equals to "auto". **TYPE:** `str` | | `EQUAL` | Equal aspect ratio (1:1). Equals to "equal". **TYPE:** `str` | ### datachart.constants.COLORBAR_LOCATION The supported colorbar locations. Used by the `location` field of a chart's `colorbar` setting (`ColorbarSettingAttrs`): the chart edge the bar sits on. Examples: ``` >>> from datachart.constants import COLORBAR_LOCATION >>> COLORBAR_LOCATION.RIGHT "right" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------- | | `RIGHT` | Right side of the chart. Equals to "right". **TYPE:** `str` | | `LEFT` | Left side of the chart. Equals to "left". **TYPE:** `str` | | `TOP` | Top of the chart. Equals to "top". **TYPE:** `str` | | `BOTTOM` | Bottom of the chart. Equals to "bottom". **TYPE:** `str` | ## Chart-Specific Constants Constants one chart owns, in the order of the [charts reference](https://eriknovak.github.io/datachart/0.10.2/references/charts/index.md). ### datachart.constants.STACKED_AREA_BASELINE The supported stacked area baselines. Passed as the `baseline` attribute of stacked area charts: where the first series starts, and so how the whole stack sits on the y-axis. Examples: ``` >>> from datachart.constants import STACKED_AREA_BASELINE >>> STACKED_AREA_BASELINE.DEFAULT "zero" ``` | ATTRIBUTE | DESCRIPTION | | ----------------- | ------------------------------------------------------------------------------------------------------------ | | `DEFAULT` | The default baseline. Same as STACKED_AREA_BASELINE.ZERO. **TYPE:** `str` | | `ZERO` | The stack starts at zero. Equals to "zero". **TYPE:** `str` | | `PERCENT` | Each x is normalised so the stack spans 0 to 100. Equals to "percent". **TYPE:** `str` | | `SYM` | The stack is centred on zero. Equals to "sym". **TYPE:** `str` | | `WIGGLE` | The baseline minimises the sum of squared slopes. Equals to "wiggle". **TYPE:** `str` | | `WEIGHTED_WIGGLE` | The baseline minimises the size-weighted sum of squared slopes. Equals to "weighted_wiggle". **TYPE:** `str` | ### datachart.constants.BUMP_RANK The supported bump chart ranking rules. Passed as the `rank_by` setting of the bump chart: whether each series' `y` is already a rank or a value ranked per period, over the series present there. Ties keep input order. Examples: ``` >>> from datachart.constants import BUMP_RANK >>> BUMP_RANK.DEFAULT "value_descending" ``` | ATTRIBUTE | DESCRIPTION | | ------------------ | ---------------------------------------------------------------------------- | | `DEFAULT` | The default ranking. Same as BUMP_RANK.VALUE_DESCENDING. **TYPE:** `str` | | `VALUE_DESCENDING` | The highest value ranks first. Equals to "value_descending". **TYPE:** `str` | | `VALUE_ASCENDING` | The lowest value ranks first. Equals to "value_ascending". **TYPE:** `str` | | `GIVEN` | y is the rank, a positive integer. Equals to "given". **TYPE:** `str` | ### datachart.constants.BUMP_LABEL_POSITION The supported end label positions. Passed as the `label_position` setting of the bump chart: beside which end of each line its series label prints. Examples: ``` >>> from datachart.constants import BUMP_LABEL_POSITION >>> BUMP_LABEL_POSITION.DEFAULT "end" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ---------------------------------------------------------------------- | | `DEFAULT` | The default position. Same as BUMP_LABEL_POSITION.END. **TYPE:** `str` | | `START` | Beside the first point. Equals to "start". **TYPE:** `str` | | `END` | Beside the last point. Equals to "end". **TYPE:** `str` | | `BOTH` | Beside the first and the last point. Equals to "both". **TYPE:** `str` | ### datachart.constants.RADIAL_TYPE The supported radial chart visuals. Passed as the `type` setting of radial charts: the mark family the whole figure draws. The area visual is the line visual with `show_area=True`; stacked bars are the bar visual with `bar_mode="stack"`. Examples: ``` >>> from datachart.constants import RADIAL_TYPE >>> RADIAL_TYPE.LINE "line" ``` | ATTRIBUTE | DESCRIPTION | | ----------- | -------------------------------------------------------------------------------- | | `LINE` | The line (radar) visual. Equals to "line". **TYPE:** `str` | | `BAR` | The bar visual, one sector per label. Equals to "bar". **TYPE:** `str` | | `SCATTER` | The scatter visual. Equals to "scatter". **TYPE:** `str` | | `HISTOGRAM` | The angular histogram (wind rose) visual. Equals to "histogram". **TYPE:** `str` | ### datachart.constants.RADIAL_DIRECTION The supported angular directions. Passed as the `direction` setting of radial charts: which way the angles increase around the circle. Examples: ``` >>> from datachart.constants import RADIAL_DIRECTION >>> RADIAL_DIRECTION.CLOCKWISE "clockwise" ``` | ATTRIBUTE | DESCRIPTION | | ------------------ | ----------------------------------------------------------------------------------- | | `CLOCKWISE` | The angles increase clockwise. Equals to "clockwise". **TYPE:** `str` | | `COUNTERCLOCKWISE` | The angles increase counterclockwise. Equals to "counterclockwise". **TYPE:** `str` | ### datachart.constants.CALENDAR_WEEKDAY The supported week start days. Passed as the `week_start` setting of the calendar heatmap: the weekday drawn in the top row of every week column. The theme's `plot_calendar_heatmap_week_start` supplies the default. Examples: ``` >>> from datachart.constants import CALENDAR_WEEKDAY >>> CALENDAR_WEEKDAY.MONDAY "monday" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ---------------------------------------------------------------------- | | `MONDAY` | Weeks run from Monday to Sunday. Equals to "monday". **TYPE:** `str` | | `SUNDAY` | Weeks run from Sunday to Saturday. Equals to "sunday". **TYPE:** `str` | ### datachart.constants.GANTT_DATE_PERIOD The supported date axis periods. Passed as the `period` setting of the gantt chart: the calendar period the date axis is divided into. Lines mark the period edges, each period is labelled at its centre, and a second row names the enclosing period. Examples: ``` >>> from datachart.constants import GANTT_DATE_PERIOD >>> GANTT_DATE_PERIOD.MONTH "month" ``` | ATTRIBUTE | DESCRIPTION | | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `NONE` | Concise date ticks, no period edges. Equals to None. **TYPE:** `None` | | `DAY` | Days, under their month. Equals to "day". **TYPE:** `str` | | `WEEK` | ISO weeks starting on Monday, under their month. Equals to "week". **TYPE:** `str` | | `MONTH` | Months, under their year. Equals to "month". **TYPE:** `str` | | `QUARTER` | Quarters, under their year. Equals to "quarter". **TYPE:** `str` | | `YEAR` | Years. Equals to "year". **TYPE:** `str` | | `PROJECT_MONTH` | Months counted from the project start, M1, M2, …, under their project year, Y1, Y2, …. The start is xmin when given, else the earliest task start. Equals to "project_month". **TYPE:** `str` | ### datachart.constants.GANTT_VALUE The supported gantt chart value labels. Passed as the `show_values` setting of the gantt chart: what each bar prints past its end. None prints nothing. Examples: ``` >>> from datachart.constants import GANTT_VALUE >>> GANTT_VALUE.DURATION "duration" ``` | ATTRIBUTE | DESCRIPTION | | ---------- | -------------------------------------------------------------------------- | | `NONE` | No value labels. Equals to None. **TYPE:** `None` | | `DURATION` | The task's duration in days. Equals to "duration". **TYPE:** `str` | | `PROGRESS` | The task's progress as a percentage. Equals to "progress". **TYPE:** `str` | ### datachart.constants.GANTT_SORT_KEY The supported gantt chart sort keys. Passed as the `sort_by` setting of the gantt chart: what a `sort` other than `SORT.NONE` orders the task rows by. Examples: ``` >>> from datachart.constants import GANTT_SORT_KEY >>> GANTT_SORT_KEY.DEFAULT "start" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default key. Same as GANTT_SORT_KEY.START. **TYPE:** `str` | | `START` | Every row by its start. Equals to "start". **TYPE:** `str` | | `GROUP` | Rows clustered by group, groups by their earliest start and tasks within a group by start. Equals to "group". **TYPE:** `str` | ### datachart.constants.GANTT_ARROW_ENTRY The supported gantt dependency arrow entries. Passed as the `plot_gantt_dependency_entry` style attribute of the gantt chart: which side of the dependent task a dependency arrow enters. Examples: ``` >>> from datachart.constants import GANTT_ARROW_ENTRY >>> GANTT_ARROW_ENTRY.DEFAULT "top" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ---------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default entry. Same as GANTT_ARROW_ENTRY.TOP. **TYPE:** `str` | | `TOP` | Along the dependency's row, then down (or up) onto the dependent bar's start. Equals to "top". **TYPE:** `str` | | `LEFT` | Down (or up) from the dependency's end, then into the dependent bar's start from the left. Equals to "left". **TYPE:** `str` | ### datachart.constants.DUMBBELL_VALUE The supported dumbbell chart value labels. Passed as the `show_values` setting of the dumbbell chart: what each record prints. None prints nothing. Examples: ``` >>> from datachart.constants import DUMBBELL_VALUE >>> DUMBBELL_VALUE.DELTA "delta" ``` | ATTRIBUTE | DESCRIPTION | | ----------- | ---------------------------------------------------------------------------------------------------- | | `NONE` | No value labels. Equals to None. **TYPE:** `None` | | `ENDPOINTS` | Each endpoint's value, past its dot, away from the connector. Equals to "endpoints". **TYPE:** `str` | | `DELTA` | The record's end - start, at the connector midpoint. Equals to "delta". **TYPE:** `str` | ### datachart.constants.DUMBBELL_SORT_KEY The supported dumbbell chart sort keys. Passed as the `sort_by` setting of the dumbbell chart: what a `sort` other than `SORT.NONE` orders the categories by. Examples: ``` >>> from datachart.constants import DUMBBELL_SORT_KEY >>> DUMBBELL_SORT_KEY.DEFAULT "start" ``` | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------------------- | | `DEFAULT` | The default key. Same as DUMBBELL_SORT_KEY.START. **TYPE:** `str` | | `START` | Each category by its start. Equals to "start". **TYPE:** `str` | | `END` | Each category by its end. Equals to "end". **TYPE:** `str` | | `DELTA` | Each category by its end - start. Equals to "delta". **TYPE:** `str` | ### datachart.constants.HISTOGRAM_TYPE The supported histogram types. Passed as the `plot_hist_type` style attribute of histograms: how each series is rendered. How multiple series share the axis is the `bar_mode` setting's job — see `BAR_MODE`. Examples: ``` >>> from datachart.constants import HISTOGRAM_TYPE >>> HISTOGRAM_TYPE.BAR "bar" ``` | ATTRIBUTE | DESCRIPTION | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `BAR` | The bar histogram style. Equals to "bar". **TYPE:** `str` | | `STEP` | The step histogram style: an unfilled outline in the series color. Stacked series draw as STEP_FILLED, since a stack needs area. Equals to "step". **TYPE:** `str` | | `STEP_FILLED` | The filled step histogram style. Equals to "stepfilled". **TYPE:** `str` | ### datachart.constants.RIDGELINE_SCALE The supported ridgeline density scales. Passed as the `normalize` setting of ridgeline plots: whether every ridge is scaled to the same peak height, so their shapes compare, or all ridges share one density scale, so their heights compare. Examples: ``` >>> from datachart.constants import RIDGELINE_SCALE >>> RIDGELINE_SCALE.DEFAULT "per_row" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default scale. Same as RIDGELINE_SCALE.PER_ROW. **TYPE:** `str` | | `PER_ROW` | Every ridge reaches the same peak height. Equals to "per_row". **TYPE:** `str` | | `COMMON` | One density scale: the tallest ridge reaches the peak height and the others stay in proportion. Equals to "common". **TYPE:** `str` | ### datachart.constants.CONTOUR_LEVELS The supported contour level rules. Passed as the `levels` setting of contour charts: the rule that picks how many iso-lines (or filled bands) cut the surface. An integer target count or an explicit list of level values is also accepted. Every rule is evaluated on the per-axis resolution of the grid (the square root of its cell count), so a finer grid draws more levels; the count is clamped to the 4–20 range and snapped to round values. Examples: ``` >>> from datachart.constants import CONTOUR_LEVELS >>> CONTOUR_LEVELS.DEFAULT "auto" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default rule. Same as CONTOUR_LEVELS.AUTO. **TYPE:** `str` | | `AUTO` | Matplotlib's own choice, about eight round values across the surface. Equals to "auto". **TYPE:** `str` | | `RICE` | The Rice rule, 2 * n \*\* (1/3) levels — about ten on a 120×120 grid. Equals to "rice". **TYPE:** `str` | | `FD` | The Freedman–Diaconis rule, the value range over 2 * IQR * n \*\* (-1/3) — about twice as dense as Rice on a 120×120 grid. Equals to "fd". **TYPE:** `str` | ### datachart.constants.HEXBIN_REDUCE The supported hexbin aggregations. Passed as the `reduce` attribute of hexbin charts: how the `c` values of the points in a hexagon collapse into the one value that colors it. Ignored without `c`, where every hexagon shows its point count. Examples: ``` >>> from datachart.constants import HEXBIN_REDUCE >>> HEXBIN_REDUCE.DEFAULT "mean" ``` | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------------------- | | `DEFAULT` | The default aggregation. Same as HEXBIN_REDUCE.MEAN. **TYPE:** `str` | | `MEAN` | The mean of the c values. Equals to "mean". **TYPE:** `str` | | `SUM` | The sum of the c values. Equals to "sum". **TYPE:** `str` | | `MEDIAN` | The median of the c values. Equals to "median". **TYPE:** `str` | | `MIN` | The smallest c value. Equals to "min". **TYPE:** `str` | | `MAX` | The largest c value. Equals to "max". **TYPE:** `str` | ### datachart.constants.NETWORK_LAYOUT The supported network chart layouts. Passed as the `layout` attribute of network charts: the rule that places the nodes in the 0–1 layout space. Layout changes what the picture means, so it is a chart attribute and not a style key. Examples: ``` >>> from datachart.constants import NETWORK_LAYOUT >>> NETWORK_LAYOUT.DEFAULT "spring" ``` Under `WEIGHTED`, and between the groups of `GROUPED`, an edge of weight (w) pulls its nodes together at (s(w)) times the `SPRING` pull: [s(w) = 0.1 + 2.9,\\frac{w - w\_{\\min}}{w\_{\\max} - w\_{\\min}}] The lightest edge pulls at a tenth, the heaviest at three times, the rest linearly between; an edge without a weight pulls as the lightest. With no weights, or all equal, every edge pulls at one and the picture is the `SPRING` picture. | ATTRIBUTE | DESCRIPTION | | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default layout. Same as NETWORK_LAYOUT.SPRING. **TYPE:** `str` | | `SPRING` | A force-directed (Fruchterman–Reingold) layout: linked nodes pull together, every pair pushes apart. Seeded by the chart's seed argument, so the same data renders the same picture. Costs the square of the node count: fine up to about 1,000 nodes, slow and memory-hungry past that. Equals to "spring". **TYPE:** `str` | | `WEIGHTED` | The spring layout with each edge's pull set by its weight, as above: heavy edges draw their nodes close, light ones let them drift. Same cost as SPRING. Equals to "weighted". **TYPE:** `str` | | `GROUPED` | The nodes clustered by their group, a node without one being a group of its own. Each group is laid out by the spring on its own edges; the groups are then laid out as a smaller network by the weighted spring, an edge between two groups weighing the sum of the edges joining them, so strongly linked clusters sit close. A translucent disc in the group color marks each cluster (plot_network_group_alpha; 0 disables it). Costs about what SPRING costs at worst, far less when the groups are many. Equals to "grouped". **TYPE:** `str` | | `CIRCULAR` | The nodes evenly spaced on a circle in input order, starting at the top. Equals to "circular". **TYPE:** `str` | | `FIXED` | Each node at its own x/y, in the 0–1 layout space; a node without them raises. Equals to "fixed". **TYPE:** `str` | ### datachart.constants.NETWORK_LABEL_POSITION The supported node label positions. Passed as the `label_position` setting of the network chart: where each node's name prints against its marker. Examples: ``` >>> from datachart.constants import NETWORK_LABEL_POSITION >>> NETWORK_LABEL_POSITION.DEFAULT "center" ``` | ATTRIBUTE | DESCRIPTION | | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `DEFAULT` | The default position. Same as NETWORK_LABEL_POSITION.CENTER. **TYPE:** `str` | | `CENTER` | On the marker. Equals to "center". **TYPE:** `str` | | `ABOVE` | Above the marker, clear of it, like a place name on a map. Equals to "above". **TYPE:** `str` | | `BEST` | Beside the marker, at the spot with the least overlap with other nodes, edges, and labels, as scatter point labels are placed. Equals to "best". **TYPE:** `str` | ### datachart.constants.SCATTER_MATRIX_DIAGONAL The supported scatter matrix diagonal cells. Passed as the `diagonal` setting of the scatter matrix: what each dimension's own cell shows. Examples: ``` >>> from datachart.constants import SCATTER_MATRIX_DIAGONAL >>> SCATTER_MATRIX_DIAGONAL.DEFAULT "hist" ``` | ATTRIBUTE | DESCRIPTION | | --------- | -------------------------------------------------------------------------------------------- | | `DEFAULT` | The default diagonal. Same as SCATTER_MATRIX_DIAGONAL.HIST. **TYPE:** `str` | | `HIST` | A histogram of the dimension, one per hue group. Equals to "hist". **TYPE:** `str` | | `KDE` | A kernel density curve of the dimension, one per hue group. Equals to "kde". **TYPE:** `str` | | `NONE` | A blank cell. Equals to "none". **TYPE:** `str` | # Typings Module ## datachart.typings Module containing the `typings`. The `typings` module holds the dictionary contracts of the package: the records a chart's `data` takes, the settings passed beside it (reference lines and bands, texts, legend, emphasis rule, colorbar), and the style keys a chart's `style` and the theme accept. A per-chart contract is documented on that chart's reference page; the shared ones on the typings page. ## Typings by Chart The records a chart's `data` takes and the keys its `style` accepts are documented on the chart's own reference page, next to the function that reads them. ### Trends and Comparisons | Chart | Shows | Data | Style | | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [LineChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/index.md) | A value along an ordered axis, one line per series. | [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs) | [`LineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs) | | [StackedAreaChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/index.md) | Parts of a total along an ordered axis, filled on top of each other. | [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs) | [`StackedAreaStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/stackedareachart/#datachart.typings.StackedAreaStyleAttrs) | | [BumpChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/index.md) | Rank over time, one line per series. | [`LineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineDataPointAttrs) | [`BumpStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/bumpchart/#datachart.typings.BumpStyleAttrs) | | [BarChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/index.md) | A value per category as bars; series grouped, stacked, or overlaid. | [`BarDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarDataPointAttrs) | [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs) | | [PyramidChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/pyramidchart/index.md) | Two series as horizontal bars mirrored around a shared category axis. | [`BarDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarDataPointAttrs) | [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs) | | [RadialChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/index.md) | Series on polar axes, as a radar line, an area, bars, or a histogram. | [`RadialDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/radialchart/#datachart.typings.RadialDataPointAttrs) | [`LineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/linechart/#datachart.typings.LineStyleAttrs), [`BarStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/barchart/#datachart.typings.BarStyleAttrs), [`HistStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs), [`ScatterStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs) | | [CalendarHeatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/index.md) | One colored cell per day, weeks as columns and weekdays as rows. | [`CalendarHeatmapDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.typings.CalendarHeatmapDataAttrs) | [`CalendarHeatmapStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/calendarheatmap/#datachart.typings.CalendarHeatmapStyleAttrs) | | [GanttChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/index.md) | A schedule: one bar per task from its start to its end over a date axis. | [`GanttTaskAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttTaskAttrs) | [`GanttStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ganttchart/#datachart.typings.GanttStyleAttrs) | | [DumbbellChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/index.md) | Two values per category, a dot at each and a connector between them. | [`DumbbellRecordAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.typings.DumbbellRecordAttrs) | [`DumbbellStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/dumbbellchart/#datachart.typings.DumbbellStyleAttrs) | ### Distributions | Chart | Shows | Data | Style | | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | [Histogram](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/index.md) | The distribution of one numeric variable, binned. | [`HistDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistDataPointAttrs) | [`HistStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/histogram/#datachart.typings.HistStyleAttrs) | | [BoxPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/index.md) | Median, quartiles, whiskers, and outliers per group. | [`BoxDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxDataPointAttrs) | [`BoxStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/boxplot/#datachart.typings.BoxStyleAttrs) | | [ViolinPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/index.md) | The density profile of each group's distribution. | [`ViolinDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinDataPointAttrs) | [`ViolinStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/violinplot/#datachart.typings.ViolinStyleAttrs) | | [SwarmPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/index.md) | Every observation as a point, spread within its group. | [`SwarmDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmDataPointAttrs) | [`SwarmStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/swarmplot/#datachart.typings.SwarmStyleAttrs) | | [RaincloudPlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/index.md) | A half violin, the raw points, and a box per group. | [`RaincloudDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.typings.RaincloudDataPointAttrs) | [`RaincloudStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/raincloudplot/#datachart.typings.RaincloudStyleAttrs) | | [RidgelinePlot](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/index.md) | One density ridge per group, stacked and partly overlapping. | [`RidgelineDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.typings.RidgelineDataPointAttrs) | [`RidgelineStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/ridgelineplot/#datachart.typings.RidgelineStyleAttrs) | ### Relationships | Chart | Shows | Data | Style | | -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | [ScatterChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/index.md) | One point per observation, placed by two numeric variables. | [`ScatterDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterDataPointAttrs) | [`ScatterStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scatterchart/#datachart.typings.ScatterStyleAttrs) | | [Heatmap](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/index.md) | A two-dimensional matrix as colored cells. | [`HeatmapDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.typings.HeatmapDataAttrs) | [`HeatmapStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/heatmap/#datachart.typings.HeatmapStyleAttrs) | | [ContourChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/index.md) | A surface sampled on a grid, as iso-lines or filled bands. | [`ContourDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.typings.ContourDataAttrs) | [`ContourStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/contourchart/#datachart.typings.ContourStyleAttrs) | | [HexbinChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/index.md) | Point density on the plane, as colored hexagons. | [`HexbinDataAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.typings.HexbinDataAttrs) | [`HexbinStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/hexbinchart/#datachart.typings.HexbinStyleAttrs) | | [ParallelCoords](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/index.md) | Each record as a polyline across one axis per dimension. | [`ParallelCoordsDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.typings.ParallelCoordsDataPointAttrs) | [`ParallelCoordsStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/parallelcoords/#datachart.typings.ParallelCoordsStyleAttrs) | | [NetworkChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/index.md) | Nodes joined by edges, placed by a layout. | [`NetworkSingleChartAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.typings.NetworkSingleChartAttrs) | [`NetworkStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/networkchart/#datachart.typings.NetworkStyleAttrs) | | [ScatterMatrix](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/index.md) | A scatter chart for every pair of dimensions, distributions on the diagonal. | [`ScatterMatrixDataPointAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/scattermatrix/#datachart.typings.ScatterMatrixDataPointAttrs) | [`StyleAttrs`](#datachart.typings.StyleAttrs) | ### Flows | Chart | Shows | Data | Style | | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | | [SankeyChart](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/index.md) | Weighted flows between categories, as ribbons between node columns. | [`SankeySingleChartAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.typings.SankeySingleChartAttrs) | [`SankeyStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/sankeychart/#datachart.typings.SankeyStyleAttrs) | ### Part of a Whole | Chart | Shows | Data | Style | | ------------------------------------------------------------------------------------------ | ------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | [Treemap](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/index.md) | Part-of-whole data as nested rectangles sized by value. | [`TreemapSingleChartAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.typings.TreemapSingleChartAttrs) | [`TreemapStyleAttrs`](https://eriknovak.github.io/datachart/0.10.2/references/charts/treemap/#datachart.typings.TreemapStyleAttrs) | ## Settings The dictionaries a chart takes beside its data: reference lines and bands, text annotations, the legend, the emphasis rule, and the colorbar. Each is a parameter of the chart function, and a field left out or set to `None` falls back to the theme. | I want to… | Pass | As | | --------------------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------ | | mark a value on the x or y axis | `vlines`, `hlines` | [`VLineSettingAttrs`](#datachart.typings.VLineSettingAttrs), [`HLineSettingAttrs`](#datachart.typings.HLineSettingAttrs) | | shade a range of the x or y axis | `vspans`, `hspans` | [`VSpanSettingAttrs`](#datachart.typings.VSpanSettingAttrs), [`HSpanSettingAttrs`](#datachart.typings.HSpanSettingAttrs) | | write a note on the chart | `texts` | [`TextSettingAttrs`](#datachart.typings.TextSettingAttrs) | | title, place, or lay out the legend | `legend` | [`LegendSettingAttrs`](#datachart.typings.LegendSettingAttrs) | | highlight the series or marks matching a rule | `emphasis_rule` | [`EmphasisRuleAttrs`](#datachart.typings.EmphasisRuleAttrs) | | place or format the colorbar | `colorbar` | [`ColorbarSettingAttrs`](#datachart.typings.ColorbarSettingAttrs) | ### datachart.typings.VLineSettingAttrs Bases: `TypedDict` The vertical reference line setting, passed to a chart front as `vlines`. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------------------------------------- | | `x` | The x-axis position of the line. **TYPE:** \`int | | `ymin` | The minimum y-axis position value. **TYPE:** \`int | | `ymax` | The maximum y-axis position value. **TYPE:** \`int | | `style` | The vertical line style attributes. **TYPE:** \`VLineStyleAttrs | | `label` | The label of the vertical line. **TYPE:** \`str | ### datachart.typings.HLineSettingAttrs Bases: `TypedDict` The horizontal reference line setting, passed to a chart front as `hlines`. | ATTRIBUTE | DESCRIPTION | | --------- | ----------------------------------------------------------------- | | `y` | The x-axis position of the line. **TYPE:** \`int | | `xmin` | The minimum y-axis position value. **TYPE:** \`int | | `xmax` | The maximum y-axis position value. **TYPE:** \`int | | `style` | The horizontal line style attributes. **TYPE:** \`HLineStyleAttrs | | `label` | The label of the horizontal line. **TYPE:** \`str | ### datachart.typings.VSpanSettingAttrs Bases: `TypedDict` The vertical reference band setting, passed to a chart front as `vspans`. A vertical band shades the region between two x-axis positions over the full height of the axes. On a radial chart the bounds are angles in degrees and the band is a wedge over the full radius. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------------------------------------------- | | `xmin` | The lower x-axis bound. Defaults to the axis minimum. **TYPE:** \`int | | `xmax` | The upper x-axis bound. Defaults to the axis maximum. **TYPE:** \`int | | `style` | The vertical band style attributes. **TYPE:** \`VSpanStyleAttrs | | `label` | The label of the band (shown in the legend). **TYPE:** \`str | ### datachart.typings.HSpanSettingAttrs Bases: `TypedDict` The horizontal reference band setting, passed to a chart front as `hspans`. A horizontal band shades the region between two y-axis positions over the full width of the axes. On a radial chart the bounds are radii and the band is an annulus over the full circle. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------------------------------------------- | | `ymin` | The lower y-axis bound. Defaults to the axis minimum. **TYPE:** \`int | | `ymax` | The upper y-axis bound. Defaults to the axis maximum. **TYPE:** \`int | | `style` | The horizontal band style attributes. **TYPE:** \`HSpanStyleAttrs | | `label` | The label of the band (shown in the legend). **TYPE:** \`str | ### datachart.typings.TextSettingAttrs Bases: `TypedDict` The text annotation setting, passed to a chart front as `texts`. | ATTRIBUTE | DESCRIPTION | | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `text` | The annotation text. **TYPE:** `str` | | `x` | The x-axis position of the text. **TYPE:** \`int | | `y` | The y-axis position of the text. **TYPE:** \`int | | `coords` | The coordinate system of the text position: "data" (default) or "axes" (axes fraction, 0–1). **TYPE:** \`str | | `target` | The data point the connector points to, always in data coordinates. When present, a connector is drawn from the text to the target. **TYPE:** \`tuple\[int | | `style` | The per-text style attributes. **TYPE:** \`TextStyleAttrs | | `subplot` | The 0-based index, in render order, of the subplot the text lands in. Read only by Annotate on a multi-subplot figure, where every text must name one; chart fronts target subplots with a list of lists instead. **TYPE:** \`int | ### datachart.typings.LegendSettingAttrs Bases: `TypedDict` The per-figure legend setting, passed to a chart front as `legend`. Every field is optional; a `None` field falls back to the theme's `plot_legend_*` attribute of the same name. | ATTRIBUTE | DESCRIPTION | | ----------- | ----------------------------------------------------------------------------------------------------- | | `title` | The legend title; an empty string draws none. **TYPE:** \`str | | `location` | The legend location. An outside member places the legend beside the axes. **TYPE:** \`LEGEND_LOCATION | | `ncols` | The number of legend columns. **TYPE:** \`int | | `alignment` | The legend alignment. **TYPE:** \`LEGEND_ALIGN | ### datachart.typings.EmphasisRuleAttrs Bases: `TypedDict` The emphasis rule setting, passed to a chart front as `emphasis_rule`. Exactly one comparison key: a unit matching it is highlighted and every other unit muted. Each front selects its own unit — a bar, leaf, node, row, cell or bin reads its one value; a group or series reads a summary of its values, chosen by `by`. A unit's explicit `emphasis` role wins. | ATTRIBUTE | DESCRIPTION | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `above` | Highlight values strictly above this. **TYPE:** \`int | | `below` | Highlight values strictly below this. **TYPE:** \`int | | `between` | Highlight values within (lo, hi), both bounds inclusive. **TYPE:** \`tuple\[int | | `top` | Highlight the n largest values; ties keep input order. **TYPE:** `int` | | `bottom` | Highlight the n smallest values; ties keep input order. **TYPE:** `int` | | `by` | The summary a group or series is read by. Groups default to "median", series to "mean"; a front reading one value per unit rejects it. **TYPE:** `Literal['mean', 'median', 'min', 'max', 'sum']` | ### datachart.typings.ColorbarSettingAttrs Bases: `TypedDict` The per-figure colorbar setting, passed to a chart front as `colorbar`. Every field is optional. `location` is the control: it places the bar on any edge of the chart. With no `location`, `orientation` derives the edge: vertical means right, horizontal means top. When both are given `location` wins. | ATTRIBUTE | DESCRIPTION | | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `label` | The caption beside the bar, reading along it; drawn in the font_ylabel\_\* theme font. **TYPE:** \`str | | `location` | The chart edge the bar sits on. **TYPE:** \`COLORBAR_LOCATION | | `format` | The format of the bar's tick labels, with the value named x (e.g. "{x:.0f}"). On a hexbin chart, valfmt still applies when this is unset. **TYPE:** \`VALUE_FORMAT | | `ticks` | Explicit tick positions on the bar; positions outside the mapped value range are not drawn. **TYPE:** \`list\[int | | `orientation` | The orientation; derives the edge when location is unset. **TYPE:** \`ORIENTATION | ## Shared Style Style groups several charts read from their `style` dictionary: the value labels `show_values` prints, the area fill, the regression line, reference lines and bands, and text annotations. A chart's reference page says which of them it draws, and every key is also a theme key. ### datachart.typings.ValueLabelStyleAttrs Bases: `TypedDict` The typing for the value labels: the numbers a chart prints beside its marks when `show_values` is on. One style serves every chart that takes `show_values`; the `plot_bar_value_*` keys of `BarStyleAttrs` are aliases. | ATTRIBUTE | DESCRIPTION | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `plot_value_fontsize` | The font size of the value labels. **TYPE:** \`int | | `plot_value_color` | The color of the value labels. **TYPE:** \`str | | `plot_value_padding` | The gap between a mark and its value label, in points. **TYPE:** \`int | | `plot_value_halo_width` | The width, in points, of the halo, in the axes face color, stroked around the value labels so they stay legible over marks and lines. None or 0 draws no halo. **TYPE:** \`int | ### datachart.typings.AreaStyleAttrs Bases: `TypedDict` The typing for the area style. | ATTRIBUTE | DESCRIPTION | | --------------------- | ---------------------------------------------------- | | `plot_area_alpha` | The alpha value of the area. **TYPE:** \`float | | `plot_area_color` | The color of the area. **TYPE:** \`str | | `plot_area_linewidth` | The line width of the area. **TYPE:** \`int | | `plot_area_hatch` | The hatch style of the area. **TYPE:** \`HATCH_STYLE | | `plot_area_zorder` | The zorder of the area. **TYPE:** \`int | ### datachart.typings.RegressionStyleAttrs Bases: `TypedDict` The typing for regression line style. | ATTRIBUTE | DESCRIPTION | | -------------------------- | --------------------------------------------------- | | `plot_regression_color` | The regression line color. **TYPE:** \`str | | `plot_regression_alpha` | The alpha of the regression line. **TYPE:** \`float | | `plot_regression_width` | The line width. **TYPE:** \`int | | `plot_regression_style` | The line style. **TYPE:** \`LINE_STYLE | | `plot_regression_ci_alpha` | Confidence interval alpha. **TYPE:** \`float | ### datachart.typings.VLineStyleAttrs Bases: `TypedDict` The typing for the vertical line style. | ATTRIBUTE | DESCRIPTION | | ------------------ | ------------------------------------------------------- | | `plot_vline_color` | The color of the vertical line. **TYPE:** \`str | | `plot_vline_style` | The style of the vertical line. **TYPE:** \`LINE_STYLE | | `plot_vline_width` | The width of the vertical line. **TYPE:** \`int | | `plot_vline_alpha` | The alpha value of the vertical line. **TYPE:** \`float | ### datachart.typings.HLineStyleAttrs Bases: `TypedDict` The typing for the horizontal line style. | ATTRIBUTE | DESCRIPTION | | ------------------ | --------------------------------------------------------- | | `plot_hline_color` | The color of the horizontal line. **TYPE:** \`str | | `plot_hline_style` | The style of the horizontal line. **TYPE:** \`LINE_STYLE | | `plot_hline_width` | The width of the horizontal line. **TYPE:** \`int | | `plot_hline_alpha` | The alpha value of the horizontal line. **TYPE:** \`float | ### datachart.typings.VSpanStyleAttrs Bases: `TypedDict` The typing for the vertical reference band style. | ATTRIBUTE | DESCRIPTION | | ----------------------- | ------------------------------------------------------------------------------------------ | | `plot_vspan_color` | The fill color of the band. Defaults to the theme's muted color. **TYPE:** \`str | | `plot_vspan_alpha` | The alpha value of the band. **TYPE:** \`float | | `plot_vspan_hatch` | The hatch pattern of the band. **TYPE:** \`HATCH_STYLE | | `plot_vspan_edge_color` | The edge color of the band; the hatch draws in it. **TYPE:** \`str | | `plot_vspan_edge_width` | The edge line width of the band. **TYPE:** \`int | | `plot_vspan_zorder` | The zorder of the band. Defaults to sit over the grid and under the marks. **TYPE:** \`int | ### datachart.typings.HSpanStyleAttrs Bases: `TypedDict` The typing for the horizontal reference band style. | ATTRIBUTE | DESCRIPTION | | ----------------------- | ------------------------------------------------------------------------------------------ | | `plot_hspan_color` | The fill color of the band. Defaults to the theme's muted color. **TYPE:** \`str | | `plot_hspan_alpha` | The alpha value of the band. **TYPE:** \`float | | `plot_hspan_hatch` | The hatch pattern of the band. **TYPE:** \`HATCH_STYLE | | `plot_hspan_edge_color` | The edge color of the band; the hatch draws in it. **TYPE:** \`str | | `plot_hspan_edge_width` | The edge line width of the band. **TYPE:** \`int | | `plot_hspan_zorder` | The zorder of the band. Defaults to sit over the grid and under the marks. **TYPE:** \`int | ### datachart.typings.TextStyleAttrs Bases: `TypedDict` The typing for the text annotation style. | ATTRIBUTE | DESCRIPTION | | -------------------------- | --------------------------------------------------------------------------------------------- | | `plot_text_color` | The text color; falls back to the general font color. **TYPE:** \`str | | `plot_text_size` | The text font size. **TYPE:** \`int | | `plot_text_weight` | The text font weight. **TYPE:** \`FONT_WEIGHT | | `plot_text_halign` | The horizontal alignment of the text. **TYPE:** \`str | | `plot_text_valign` | The vertical alignment of the text. **TYPE:** \`str | | `plot_text_alpha` | The alpha value of the text. **TYPE:** \`float | | `plot_text_box_visible` | Whether to draw the background box. **TYPE:** \`bool | | `plot_text_box_style` | The matplotlib box style (e.g. "round,pad=0.4"). **TYPE:** \`str | | `plot_text_box_facecolor` | The face color of the box. **TYPE:** \`str | | `plot_text_box_edgecolor` | The edge color of the box. **TYPE:** \`str | | `plot_text_box_edge_width` | The edge width of the box. **TYPE:** \`int | | `plot_text_box_alpha` | The alpha value of the box. **TYPE:** \`float | | `plot_text_arrow_style` | The connector look (see ARROW_STYLE) or a raw matplotlib arrow style. **TYPE:** \`ARROW_STYLE | | `plot_text_arrow_curve` | The connector curvature; overrides the look's own. **TYPE:** \`float | | `plot_text_arrow_color` | The connector color. **TYPE:** \`str | | `plot_text_arrow_width` | The connector line width. **TYPE:** \`int | ## Theme Style The keys a theme defines and [`config`](https://eriknovak.github.io/datachart/0.10.2/references/config/index.md) holds: colors, fonts, axes, legend, grid, the theme-driven defaults, and the sketch and ink looks. `StyleAttrs` is their union together with every chart's own style keys; the [config methods](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config) and [`register_theme`](https://eriknovak.github.io/datachart/0.10.2/references/config/#datachart.config.Config.register_theme) take it, and the [Themes guide](https://eriknovak.github.io/datachart/0.10.2/how-to-guides/styling/themes/index.md) shows how a theme is built from it. ### datachart.typings.StyleAttrs Bases: `ColorStyleAttrs`, `FontStyleAttrs`, `AxesStyleAttrs`, `LegendStyleAttrs`, `AreaStyleAttrs`, `GridStyleAttrs`, `LineStyleAttrs`, `StackedAreaStyleAttrs`, `BumpStyleAttrs`, `SankeyStyleAttrs`, `TreemapStyleAttrs`, `NetworkStyleAttrs`, `BarStyleAttrs`, `ValueLabelStyleAttrs`, `HistStyleAttrs`, `VLineStyleAttrs`, `HLineStyleAttrs`, `VSpanStyleAttrs`, `HSpanStyleAttrs`, `TextStyleAttrs`, `HeatmapStyleAttrs`, `CalendarHeatmapStyleAttrs`, `GanttStyleAttrs`, `DumbbellStyleAttrs`, `ContourStyleAttrs`, `HexbinStyleAttrs`, `ScatterStyleAttrs`, `RegressionStyleAttrs`, `BoxStyleAttrs`, `SwarmStyleAttrs`, `ViolinStyleAttrs`, `RidgelineStyleAttrs`, `ParallelCoordsStyleAttrs`, `ScatterMatrixStyleAttrs`, `ThemeDefaultAttrs`, `SketchStyleAttrs`, `InkStyleAttrs` The style attributes. Combines all style typings. ### datachart.typings.ColorStyleAttrs Bases: `TypedDict` The typing for the general color style. | ATTRIBUTE | DESCRIPTION | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `color_general_singular` | The colors used where a chart needs one color rather than a series palette: the parallel coords numeric hue ramp and the network node base color (palette name, single color, or list of hex colors). **TYPE:** \`COLORS | | `color_general_multiple` | The colors used when the datasets share one coordinate space, which is the default for every chart (palette name, single color, or list of hex colors). **TYPE:** \`COLORS | | `color_parallel_hue` | The color palette for parallel coords hue categories (palette name, single color, or list of hex colors); None takes color_general_multiple. **TYPE:** \`COLORS | | `color_parallel_hue_continuous` | The sequential ramp for parallel coords numeric hue columns (palette name, single color, or list of hex colors). **TYPE:** \`COLORS | | `muted_color` | The color applied to background-emphasis layers. **TYPE:** \`str | | `muted_alpha` | The alpha applied to background-emphasis layers. **TYPE:** \`float | ### datachart.typings.FontStyleAttrs Bases: `TypedDict` The typing for the font style. | ATTRIBUTE | DESCRIPTION | | ------------------------ | ------------------------------------------------------------------------------------ | | `font_general_family` | The general font family. **TYPE:** \`str | | `font_general_sansserif` | The general sans-serif font. **TYPE:** \`list[str] | | `font_general_serif` | The general serif font stack, used when the family is "serif". **TYPE:** \`list[str] | | `font_general_color` | The general font color. **TYPE:** \`str | | `font_general_size` | The general font size. **TYPE:** \`int | | `font_general_style` | The general font style. **TYPE:** \`FONT_STYLE | | `font_general_weight` | The general font weight. **TYPE:** \`FONT_WEIGHT | | `font_title_size` | The title font size. **TYPE:** \`int | | `font_title_color` | The title font color. **TYPE:** \`str | | `font_title_style` | The title font style. **TYPE:** \`FONT_STYLE | | `font_title_weight` | The title font weight. **TYPE:** \`FONT_WEIGHT | | `font_subtitle_size` | The subtitle font size. **TYPE:** \`int | | `font_subtitle_color` | The subtitle font color. **TYPE:** \`str | | `font_subtitle_style` | The subtitle font style. **TYPE:** \`FONT_STYLE | | `font_subtitle_weight` | The subtitle font weight. **TYPE:** \`FONT_WEIGHT | | `font_xlabel_size` | The xlabel font size. **TYPE:** \`int | | `font_xlabel_color` | The xlabel font color. **TYPE:** \`str | | `font_xlabel_style` | The xlabel font style. **TYPE:** \`FONT_STYLE | | `font_xlabel_weight` | The xlabel font weight. **TYPE:** \`FONT_WEIGHT | | `font_ylabel_size` | The ylabel font size. **TYPE:** \`int | | `font_ylabel_color` | The ylabel font color. **TYPE:** \`str | | `font_ylabel_style` | The ylabel font style. **TYPE:** \`FONT_STYLE | | `font_ylabel_weight` | The ylabel font weight. **TYPE:** \`FONT_WEIGHT | ### datachart.typings.AxesStyleAttrs Bases: `TypedDict` The typing for the axes style. | ATTRIBUTE | DESCRIPTION | | ---------------------------- | ----------------------------------------------------------------------------------------------------------- | | `axes_spines_top_visible` | Make the top plot spine visible. **TYPE:** \`bool | | `axes_spines_right_visible` | Make the right plot spine visible. **TYPE:** \`bool | | `axes_spines_bottom_visible` | Make the bottom plot spine visible. **TYPE:** \`bool | | `axes_spines_left_visible` | Make the left plot spine visible. **TYPE:** \`bool | | `axes_spines_width` | The width of the spines. **TYPE:** \`int | | `axes_spines_zorder` | The zorder of the spines. **TYPE:** \`int | | `axes_ticks_length` | The length of the ticks. **TYPE:** \`int | | `axes_ticks_label_size` | The size of the tick labels. **TYPE:** \`int | | `figure_facecolor` | The color of the figure ground. None keeps matplotlib's. **TYPE:** \`str | | `axes_facecolor` | The color of the axes ground; label halos and etch washes take it. None keeps matplotlib's. **TYPE:** \`str | | `axes_spines_color` | The color of the spines. None keeps matplotlib's. **TYPE:** \`str | | `axes_ticks_color` | The color of the tick marks. None keeps matplotlib's. **TYPE:** \`str | ### datachart.typings.LegendStyleAttrs Bases: `TypedDict` The typing for the legend style. | ATTRIBUTE | DESCRIPTION | | ------------------------- | --------------------------------------------------------------------- | | `plot_legend_shadow` | Show the legends shadow. **TYPE:** \`bool | | `plot_legend_frameon` | Show the legends frame. **TYPE:** \`bool | | `plot_legend_alignment` | The legend alignment. **TYPE:** \`LEGEND_ALIGN | | `plot_legend_location` | The legend location. **TYPE:** \`LEGEND_LOCATION | | `plot_legend_font_size` | The font size within the legend. **TYPE:** \`int | | `plot_legend_title_size` | The title size of the legend. **TYPE:** \`int | | `plot_legend_label_color` | The label color of the legend. **TYPE:** \`str | | `plot_legend_title` | The legend title; an empty string draws none. **TYPE:** \`str | | `plot_legend_ncols` | The number of legend columns. **TYPE:** \`int | | `plot_legend_edge_color` | The legend frame color. None keeps matplotlib's. **TYPE:** \`str | | `plot_legend_face_color` | The legend background color. None keeps matplotlib's. **TYPE:** \`str | ### datachart.typings.GridStyleAttrs Bases: `TypedDict` The typing for the grid style. | ATTRIBUTE | DESCRIPTION | | --------------------- | -------------------------------------------------- | | `plot_grid_alpha` | The alpha value of the grid. **TYPE:** \`float | | `plot_grid_color` | The color of the grid. **TYPE:** \`str | | `plot_grid_linewidth` | The line width of the grid. **TYPE:** \`int | | `plot_grid_linestyle` | The line style of the grid. **TYPE:** \`LINE_STYLE | | `plot_grid_zorder` | The zorder of the grid. **TYPE:** \`int | ### datachart.typings.ThemeDefaultAttrs Bases: `TypedDict` The typing for theme-driven defaults and cycles. | ATTRIBUTE | DESCRIPTION | | ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `chart_default_show_grid` | The theme default for show_grid, applied when a chart call leaves it unset. Never applies to heatmaps. None means the theme has no opinion. **TYPE:** \`SHOW_GRID | | `chart_default_show_values` | The theme default for show_values, applied to every chart that takes it when the chart call leaves it unset. None means the theme has no opinion. **TYPE:** \`bool | | `chart_default_node_label_position` | The theme default for the network chart's label_position, applied when the chart call leaves it unset. None means the theme has no opinion. **TYPE:** \`NETWORK_LABEL_POSITION | | `plot_hatch_cycle` | The hatch patterns assigned per bar/histogram series, parallel to the color cycle; with plot_etch on, line area fills and stacked areas take them too. An explicit per-chart hatch style wins. None disables the cycle. **TYPE:** \`list[str] | | `plot_linestyle_cycle` | The line styles assigned per line, bump and radial line series, parallel to the color cycle. An explicit per-chart line style wins. None disables the cycle. **TYPE:** \`list\[LINE_STYLE | | `plot_marker_cycle` | The markers assigned per scatter and radial scatter series, parallel to the color cycle, and per network node group: a marker, or {"marker": ..., "hollow": True} to draw it as an outline. An explicit per-chart marker wins. None disables the cycle. **TYPE:** \`list\[LINE_MARKER | ### datachart.typings.SketchStyleAttrs Bases: `TypedDict` The typing for the sketch attributes: the theme's render-scoped rc-level look (path wobble, halo stroke). The panel snapshots the wobble at build time and applies it inside a scoped matplotlib rc context, so no global rc setting changes; the halo resolves like any style key, so a chart's `style` can override it. Composition keeps the look of the figures it was built from. | ATTRIBUTE | DESCRIPTION | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_sketch_params` | The path wobble as matplotlib sketch parameters [scale, length, randomness]; plt.xkcd() uses [1, 100, 2]. None draws clean paths. **TYPE:** \`list[float] | | `plot_sketch_halo_width` | The extra width, added to the line width, of the halo (in the axes face color) stroked under series lines (line, radial, regression), so crossing lines read as cut-outs; marks, text and patches stay clean. None or 0 draws no halo. **TYPE:** \`float | ### datachart.typings.InkStyleAttrs Bases: `TypedDict` The typing for the ink attributes: marks drawn as a quill and an etching needle would draw them. Every attribute resolves when the chart is built and rides on its artists, so composition keeps the look; `None` turns it off. | ATTRIBUTE | DESCRIPTION | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `plot_ink_stroke` | The broad-nib pen the series lines (line, bump, radial, regression) are drawn with, as a filled ribbon whose width varies along the line. Keys: width_scale (the nib width over the line width), nib_angle (degrees), nib_floor (the hairline width as a share of the nib), wobble (the ink wobble amplitude), taper (the end taper, in pixels). None draws plain lines. **TYPE:** \`dict[str, float] | | `plot_etch` | The etching that replaces the hatch tile of a hatched fill with hand-drawn lines clipped to its outline; the hatch pattern still picks the lines and . stipples. Keys: spacing (points between lines), jitter (the spacing jitter as a share of it), angle_jitter (degrees), line_width (points), wash (the share of the face color laid over the axes face under the lines; fills under lines take none), color (the etch ink). None keeps matplotlib's hatch. **TYPE:** \`dict\[str, float | | `plot_value_etch` | The steps a value scale draws in when plot_etch is on: washes (one fill color per step, lightest first) and hatches (one pattern per step, sparsest first). Heatmap, calendar heatmap and hexbin cells and filled contour bands take the step their value falls in, a filled contour draws its level lines and labels over the bands, and a legend of the steps replaces the colorbar. None keeps the colormap. **TYPE:** \`dict\[str, list[str]\] |