{ "cells": [ { "cell_type": "markdown", "id": "db5c80ad", "metadata": {}, "source": [ "\n", "#### **Title**: Donut Element\n", "\n", "**Dependencies**: Bokeh\n", "\n", "**Backends**: [Bokeh](./Donut.ipynb), [Matplotlib](../matplotlib/Donut.ipynb)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "b6ed344a", "metadata": {}, "outputs": [], "source": [ "import pandas as pd\n", "\n", "import holoviews as hv\n", "\n", "hv.extension('bokeh')" ] }, { "cell_type": "markdown", "id": "9ae271f4", "metadata": {}, "source": [ "\n", "The ``Donut`` Element represents proportional data as wedges of an annular ring. The key dimension provides the category for each slice and the value dimension determines the slice size.\n", "\n", "A ``Donut`` can be created from a list of tuples where each tuple contains a category and its corresponding value.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "4fd19c6d", "metadata": {}, "outputs": [], "source": [ "data = [('Rent', 1200), ('Food', 400), ('Transport', 200), ('Entertainment', 150)]\n", "d = hv.Donut(data)\n", "d" ] }, { "cell_type": "markdown", "id": "dbffe108", "metadata": {}, "source": [ "We can achieve the same plot using a Pandas DataFrame." ] }, { "cell_type": "code", "execution_count": null, "id": "c226ad1e", "metadata": {}, "outputs": [], "source": [ "df = pd.DataFrame(\n", " {\n", " 'Category': ['Rent', 'Food', 'Transport', 'Entertainment'],\n", " 'Amount': [1200, 400, 200, 150],\n", " 'Cumulative': [1200, 1600, 1800, 1950],\n", " }\n", ")\n", "\n", "hv.Donut(df, kdims=['Category'], vdims=['Amount', 'Cumulative'])" ] }, { "cell_type": "markdown", "id": "db2ee585", "metadata": {}, "source": [ "Donut charts also support numeric categories, such as years." ] }, { "cell_type": "code", "execution_count": null, "id": "afcbb8ae", "metadata": {}, "outputs": [], "source": [ "yearly = pd.DataFrame(\n", " {\n", " 'Year': [2021, 2022, 2023, 2024],\n", " 'Amount': [100, 140, 180, 220],\n", " }\n", ")\n", "\n", "hv.Donut(yearly, kdims='Year', vdims='Amount')" ] }, { "cell_type": "markdown", "id": "d3bbcc93", "metadata": {}, "source": [ "Rows with a missing category or missing value are skipped when the donut is rendered." ] }, { "cell_type": "code", "execution_count": null, "id": "88edfcfa", "metadata": {}, "outputs": [], "source": [ "missing = pd.DataFrame(\n", " {\n", " 'Category': ['Rent', 'Food', 'Transport', None, 'Other'],\n", " 'Amount': [1200, 400, 200, 300, pd.NA],\n", " }\n", ")\n", "\n", "hv.Donut(missing, kdims='Category', vdims='Amount')" ] }, { "cell_type": "markdown", "id": "c0993b0c", "metadata": {}, "source": [ "Use ``start_angle`` to rotate the first wedge." ] }, { "cell_type": "code", "execution_count": null, "id": "cbb4d012", "metadata": {}, "outputs": [], "source": [ "d.opts(start_angle=1.2)" ] }, { "cell_type": "markdown", "id": "3b21a0ef", "metadata": {}, "source": [ "Text labels can be placed next to each wedge with ``show_labels=True``." ] }, { "cell_type": "code", "execution_count": null, "id": "0becc196", "metadata": {}, "outputs": [], "source": [ "d = hv.Donut(df, kdims='Category', vdims='Amount')\n", "d.opts(show_labels=True, show_legend=False, inner_radius=0.2)" ] }, { "cell_type": "markdown", "id": "dec2da2b", "metadata": {}, "source": [ "Alternatively, ``show_labels`` can be a template string using dimension names and ``fraction``. Zoom out using `padding` to prevent labels from being clipped." ] }, { "cell_type": "code", "execution_count": null, "id": "d1f88633", "metadata": {}, "outputs": [], "source": [ "(\n", " d.clone().opts(show_labels='{Category}: {fraction:.1%}',).relabel('Category and share')\n", " + d.clone().opts(show_labels='${Amount:,.0f}').relabel('Value only')\n", ").cols(3)" ] }, { "cell_type": "markdown", "id": "5d671ab7", "metadata": {}, "source": [ "Label styling can be controlled with ``label_radius``, ``label_text_align``, ``label_text_font_size``, ``label_text_baseline``, and ``label_text_color``." ] }, { "cell_type": "code", "execution_count": null, "id": "dec6847a", "metadata": {}, "outputs": [], "source": [ "d.opts(\n", " show_labels=\"{Category}\\n{fraction:.1%}\",\n", " label_radius=1.1,\n", " label_text_align='right',\n", " label_text_baseline='middle',\n", " label_text_font_size='8px',\n", " label_text_color='firebrick',\n", ")" ] }, { "cell_type": "markdown", "id": "63da4b14", "metadata": {}, "source": [ "\n", "A center label can display the total or any custom text using ``center_label``.\n", "\n", "Set it to ``'total'`` to compute and format the sum of all values.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "d0b935a0", "metadata": {}, "outputs": [], "source": [ "d = d.opts(center_label='total', center_text_font_size='10pt', inner_radius=0.9)\n", "d" ] }, { "cell_type": "markdown", "id": "a75a9300", "metadata": {}, "source": [ "``center_label`` also accepts template strings. Available placeholders include ``{total}``, ``{total_formatted}``, and the value-dimension name." ] }, { "cell_type": "code", "execution_count": null, "id": "d0d3fb26", "metadata": {}, "outputs": [], "source": [ "(\n", " d.clone().opts(center_label='Budget mix').relabel('Static label')\n", " + d.clone().opts(center_label='The total was\\n{total:,.0f}').relabel('Template')\n", " + d.clone().opts(\n", " center_label=\"Monthly total\\n${total:,.0f}\",\n", " center_text_color='red',\n", " ).relabel('Styled center label')\n", ").cols(3)" ] }, { "cell_type": "markdown", "id": "75606938", "metadata": {}, "source": [ "Datetime categories are also supported and can be formatted in label templates." ] }, { "cell_type": "code", "execution_count": null, "id": "43a11e35", "metadata": {}, "outputs": [], "source": [ "dated = pd.DataFrame(\n", " {\n", " 'date': pd.to_datetime(['2024-01-01', '2024-02-01', '2024-03-01', '2024-04-01']),\n", " 'Amount': [1200, 900, 600, 300],\n", " }\n", ")\n", "\n", "hv.Donut(dated, kdims='date', vdims='Amount').opts(\n", " show_labels=\"{date:%b %Y} {fraction:.1%}\",\n", " show_legend=False,\n", " label_text_font_size='8px',\n", ")" ] }, { "cell_type": "markdown", "id": "200e5703", "metadata": {}, "source": [ "The ``inner_radius`` option controls the size of the hole. Setting it to ``0`` produces a pie chart while a larger ``inner_radius`` creates a thinner ring. ``outer_radius`` can be used to scale the donut itself." ] }, { "cell_type": "code", "execution_count": null, "id": "7347f8a6", "metadata": {}, "outputs": [], "source": [ "d = d.opts(show_legend=False, center_label=None, label_text_color=\"black\")\n", "(\n", " d.clone().opts(inner_radius=0).relabel('Pie') +\n", " d.clone().opts(inner_radius=0.2).relabel('Default')\n", " + d.clone().opts(inner_radius=0.4).relabel('Thin ring')\n", " + d.clone().opts(inner_radius=0.7, outer_radius=1.2).relabel('Thin ring, larger radius')\n", ").cols(4)" ] }, { "cell_type": "markdown", "id": "7e5f7460", "metadata": {}, "source": [ "The color palette can be changed using the ``cmap`` style option." ] }, { "cell_type": "code", "execution_count": null, "id": "437afd83", "metadata": {}, "outputs": [], "source": [ "(\n", " d.clone().opts(cmap='Set2').relabel('Named palette')\n", " + d.clone().opts(cmap=['black', 'brown', 'tan', 'gray']).relabel('Custom list')\n", " + d.clone().opts(\n", " cmap={\n", " 'Rent': '#1d4ed8',\n", " 'Food': '#16a34a',\n", " 'Transport': '#f59e0b',\n", " 'Entertainment': '#ef4444',\n", " }\n", " ).relabel('Category map')\n", ").cols(3)" ] }, { "cell_type": "markdown", "id": "ed6aeaf7", "metadata": {}, "source": [ "\n", "Border styling uses ``wedge_``-prefixed Bokeh line properties.\n", "\n", "To make borders visible, set ``wedge_line_alpha=1`` in addition to ``wedge_line_color`` and ``wedge_line_width``.\n" ] }, { "cell_type": "code", "execution_count": null, "id": "a45bb33c", "metadata": {}, "outputs": [], "source": [ "d.opts(\n", " wedge_line_color='white',\n", " wedge_line_width=4,\n", " wedge_line_alpha=1,\n", ")" ] }, { "cell_type": "markdown", "id": "e73935a9", "metadata": {}, "source": [ "For full documentation and the available style and plot options, use ``hv.help(hv.Donut)``." ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 5 }