Source code for wildewidgets.widgets.charts.altair

from __future__ import annotations

import random
from typing import Any

from django import template

from wildewidgets.views import JSONDataView

from ..base import Widget


[docs]class AltairChart(Widget, JSONDataView): """ A widget for rendering Altair charts in Django applications. This class provides a wrapper around Altair charts, making them easy to integrate into Django templates. It supports both synchronous and asynchronous loading of chart data, and allows for customization of the chart's appearance. The chart content is rendered using a Django template, and the chart data is loaded via a JSON endpoint when in asynchronous mode. Example: .. code-block:: python import altair as alt from wildewidgets.widgets.charts.altair import AltairChart class MyBarChart(AltairChart): def load(self): # Create an Altair chart data = pd.DataFrame({ 'category': ['A', 'B', 'C'], 'value': [10, 20, 30] }) chart = alt.Chart(data).mark_bar().encode( x='category', y='value' ) self.set_data(chart) """ #: The Django template file to render the chart template_file: str = "wildewidgets/altairchart.html" #: The title of the chart, can be set in the options title: str | None = None #: Default width for the chart, can be overridden in options width: str = "100%" #: Default height for the chart, can be overridden in options height: str = "300px" def __init__(self, *args, **kwargs) -> None: # noqa: ARG002 """ Initialize the Altair chart widget. Args: *args: Variable length argument list (not used) **kwargs: Arbitrary keyword arguments width: Override the default chart width height: Override the default chart height title: Set the chart title Note: The chart data is not loaded during initialization. It will be loaded when get_context_data is called. """ self.data = None self.chart_options = { "width": kwargs.get("width", self.width), "height": kwargs.get("height", self.height), "title": kwargs.get("title", self.title), }
[docs] def get_content(self, **kwargs) -> str: # noqa: ARG002 """ Render the chart as HTML content. This method renders the chart using the specified template file. If the chart data has not been loaded yet, it will set the ``async`` flag to ``True``, which tells the template to load the data asynchronously via a JSON endpoint. Args: **kwargs: Arbitrary keyword arguments (not used) Returns: str: The rendered HTML content for the chart """ chart_id = random.randrange(0, 1000) # noqa: S311 template_file = self.template_file context: dict[str, Any] = ( self.get_context_data() if self.data else {"async": True} ) html_template = template.loader.get_template(template_file) context["options"] = self.chart_options context["name"] = f"altair_chart_{chart_id}" context["wildewidgetclass"] = self.__class__.__name__ return html_template.render(context)
def __str__(self) -> str: """ Return the string representation of the chart. This method allows the chart to be used directly in Django templates. Returns: str: The rendered HTML content for the chart """ return self.get_content()
[docs] def get_context_data(self, **kwargs) -> dict[str, Any]: """ Get the context data for rendering the chart. This method loads the chart data if it hasn't been loaded yet and adds it to the context data. Args: **kwargs: Arbitrary keyword arguments passed to the parent method Returns: dict: The context data for rendering the chart """ context = super().get_context_data(**kwargs) self.load() context.update({"data": self.data}) return context
[docs] def set_data(self, spec, set_size: bool = True): """ Set the chart data from an Altair chart specification. This method converts an Altair chart specification into a dictionary representation that can be serialized to JSON. It also optionally sets the chart to use container-based sizing. Args: spec: The Altair chart specification set_size: Whether to set the chart to use container-based sizing When True, the chart will fill its container element. When False, the chart will use its own width and height settings. """ if set_size: self.data = spec.properties(width="container", height="container").to_dict() else: self.data = spec.to_dict()
[docs] def load(self) -> None: """ Load the chart data. This method should be overridden by subclasses to load the chart data. The implementation should create an Altair chart and call set_data() with it. The default implementation does nothing. Example: .. code-block:: python def load(self): # Load data from a source data = pd.DataFrame(...) # Create an Altair chart chart = alt.Chart(data).mark_bar().encode(...) # Set the chart data self.set_data(chart) """