"""
GlyphX high-level ``plot()`` function — the fastest path to a chart.
"""
import numpy as np
from .figure import Figure
from .series import (
LineSeries, BarSeries, ScatterSeries,
PieSeries, DonutSeries, HistogramSeries,
BoxPlotSeries, HeatmapSeries,
)
from .bubble import BubbleSeries
from .stacked_bar import StackedBarSeries
from .bump_chart import BumpChartSeries
from .sparkline import SparklineSeries
from .sunburst import SunburstSeries
from .parallel_coords import ParallelCoordinatesSeries
from .diverging_bar import DivergingBarSeries
# Chart kinds that don't use X/Y axes
_AXISFREE_KINDS = {"pie", "donut", "hist", "box", "heatmap", "sunburst", "parallel", "diverging", "stacked", "bump", "sparkline"}
# Arguments forwarded to Figure rather than the series constructor
_FIGURE_KEYS = {"width", "height", "padding", "title", "theme",
"auto_display", "legend", "xscale", "yscale"}
[docs]
def plot(x=None, y=None, kind="line", data=None, legend="top-right", **kwargs):
"""
Unified high-level plotting function.
This is the quickest way to create a single chart. Specify ``kind``
plus ``x``/``y`` (or ``data`` for distribution charts) and GlyphX
handles scaling, theming, rendering, and display automatically.
Parameters
----------
x : list or None
X-axis values. Not required for ``pie``, ``donut``, ``hist``,
``box``, or ``heatmap``.
y : list or None
Y-axis values or raw data for distribution charts.
kind : str
Chart type. One of ``"line"``, ``"bar"``, ``"scatter"``,
``"pie"``, ``"donut"``, ``"hist"``, ``"box"``, ``"heatmap"``.
data : list or None
Explicit data array for ``hist`` / ``box`` / ``pie`` / ``donut``
(takes priority over ``y``).
legend : str
Legend position (``"top-right"``, ``"top-left"``, etc.) or
``False`` to suppress.
**kwargs
Extra keyword arguments forwarded to the Series constructor
(e.g. ``color``, ``label``, ``bins``, ``linestyle``) **or** to
Figure (e.g. ``width``, ``height``, ``theme``, ``xscale``).
Returns
-------
Figure
The Figure object (auto-displayed unless ``auto_display=False``).
Examples
--------
>>> plot([1, 2, 3], [4, 5, 6], kind="line", title="My Line")
>>> plot(y=[4, 5, 6], kind="bar")
>>> plot(data=[1, 3, 2, 2, 1, 4], kind="hist")
"""
kind = kind.lower()
# Separate Figure-level kwargs from series-level kwargs
figure_kwargs = {k: kwargs.pop(k) for k in list(kwargs) if k in _FIGURE_KEYS}
figure_kwargs.setdefault("legend", legend)
xlabel = kwargs.pop("xlabel", None)
ylabel = kwargs.pop("ylabel", None)
color = kwargs.pop("color", None)
label = kwargs.pop("label", None)
# Validate / coerce inputs
if kind in _AXISFREE_KINDS:
values = data if data is not None else (y if y is not None else x)
if values is None:
raise ValueError(f"[glyphx.plot] No data provided for kind='{kind}'.")
if hasattr(values, "values"): # unwrap pandas Series
values = values.values
# Heatmap requires its 2-D matrix structure — never flatten it.
# Hist and box need a flat 1-D array.
if kind not in {"pie", "donut", "heatmap"}:
values = np.asarray(values, dtype=float).flatten()
if not np.issubdtype(values.dtype, np.number):
raise TypeError(
f"kind='{kind}' requires numeric data; got {values.dtype}."
)
else:
if y is None:
if x is not None:
y = x
x = list(range(len(y)))
else:
raise ValueError(
f"[glyphx.plot] Provide x and/or y for kind='{kind}'."
)
# y was supplied directly — infer x if it was not provided
if x is None:
x = list(range(len(y)))
# Build Figure
fig = Figure(**figure_kwargs)
fig.axes.xlabel = xlabel
fig.axes.ylabel = ylabel
# Build Series
if kind == "line":
series = LineSeries(x, y, color=color, label=label, **kwargs)
elif kind == "bar":
series = BarSeries(x, y, color=color, label=label, **kwargs)
elif kind == "scatter":
series = ScatterSeries(x, y, color=color, label=label, **kwargs)
elif kind == "pie":
series = PieSeries(values=values, **kwargs)
elif kind == "donut":
series = DonutSeries(values=values, **kwargs)
elif kind == "hist":
series = HistogramSeries(values, color=color, label=label, **kwargs)
elif kind == "box":
series = BoxPlotSeries(values, color=color or "#1f77b4", label=label, **kwargs)
elif kind == "heatmap":
series = HeatmapSeries(values, **kwargs)
elif kind == "stacked":
x_data = kwargs.pop("x", x) or []
series_data = kwargs.pop("series", {})
normalize = kwargs.pop("normalize", False)
series = StackedBarSeries(x=x_data, series=series_data, normalize=normalize, **kwargs)
elif kind == "bump":
rankings = kwargs.pop("rankings", {})
series = BumpChartSeries(x=x or [], rankings=rankings, **kwargs)
elif kind == "sparkline":
series = SparklineSeries(data=values, color=color or "#2563eb", **kwargs)
elif kind == "bubble":
size = kwargs.pop("size", 10)
series = BubbleSeries(x, y, size=size, color=color, label=label, **kwargs)
elif kind == "sunburst":
parents = kwargs.pop("parents", [])
series = SunburstSeries(labels=values, parents=parents, values=values, **kwargs)
elif kind in ("parallel", "parallel_coords"):
axes = kwargs.pop("axes", [])
series = ParallelCoordinatesSeries(data=values, axes=axes, **kwargs)
elif kind == "diverging":
categories = kwargs.pop("categories", x or [])
series = DivergingBarSeries(categories=categories, values=values,
**kwargs)
else:
# Fuzzy-match to help users who typo the kind name
import difflib as _dl
_valid = ["line", "bar", "scatter", "pie", "donut", "hist", "box",
"heatmap", "bubble", "sunburst", "parallel", "diverging",
"stacked", "bump", "sparkline"]
_close = _dl.get_close_matches(kind, _valid, n=3, cutoff=0.5)
_hint = f" Did you mean: {_close}?" if _close else ""
raise ValueError(
f"[glyphx.plot] Unsupported kind='{kind}'.{_hint}\n"
f"Valid kinds: {', '.join(_valid)}."
)
fig.add(series)
fig.plot()
return fig