Source code for hail.ggplot.geoms

import abc
from typing import Any, ClassVar, Dict, Optional

import numpy as np
import plotly.graph_objects as go

from .aes import aes
from .stats import StatBin, StatCDF, StatCount, StatFunction, StatIdentity, StatNone
from .utils import bar_position_plotly_to_gg, linetype_plotly_to_gg


[docs]class FigureAttribute(abc.ABC): pass
class Geom(FigureAttribute): def __init__(self, aes): self.aes = aes @abc.abstractmethod def apply_to_fig( self, agg_result, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): """Add this geometry to the figure and indicate if this geometry demands a static figure.""" pass @abc.abstractmethod def get_stat(self): pass def _add_aesthetics_to_trace_args(self, trace_args, df): for aes_name, (plotly_name, default) in self.aes_to_arg.items(): if hasattr(self, aes_name) and getattr(self, aes_name) is not None: trace_args[plotly_name] = getattr(self, aes_name) elif aes_name in df.attrs: trace_args[plotly_name] = df.attrs[aes_name] elif aes_name in df.columns: trace_args[plotly_name] = df[aes_name] elif default is not None: trace_args[plotly_name] = default def _update_legend_trace_args(self, trace_args, legend_cache): if "name" in trace_args: trace_args["legendgroup"] = trace_args["name"] if trace_args["name"] in legend_cache: trace_args["showlegend"] = False else: trace_args["showlegend"] = True legend_cache[trace_args["name"]] = {} class GeomLineBasic(Geom): aes_to_arg: ClassVar = { "color": ("line_color", "black"), "size": ("marker_size", None), "tooltip": ("hovertext", None), "color_legend": ("name", None), } def __init__(self, aes, color): super().__init__(aes) self.color = color def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): def plot_group(df): trace_args = {"x": df.x, "y": df.y, "mode": "lines", "row": facet_row, "col": facet_col} self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_scatter(**trace_args) for group_df in grouped_data: plot_group(group_df) @abc.abstractmethod def get_stat(self): return ... class GeomPoint(Geom): aes_to_plotly: ClassVar = { "color": "marker_color", "size": "marker_size", "tooltip": "hovertext", "alpha": "marker_opacity", "shape": "marker_symbol", } aes_defaults: ClassVar = { "color": "black", "shape": "circle", } aes_legend_groups: ClassVar = { "color", "shape", } def __init__(self, aes, color=None, size=None, alpha=None, shape=None): super().__init__(aes) self.color = color self.size = size self.alpha = alpha self.shape = shape def _map_to_plotly(self, mapping) -> Dict[str, Any]: plotly_kwargs = {self.aes_to_plotly[k]: v for k, v in mapping.items()} if 'tooltip' in mapping: plotly_kwargs['hoverinfo'] = 'text' return plotly_kwargs def _get_aes_value(self, df, aes_name): if getattr(self, aes_name, None) is not None: return getattr(self, aes_name) if df.attrs.get(aes_name) is not None: return df.attrs[aes_name] if df.get(aes_name) is not None: return df[aes_name] return self.aes_defaults.get(aes_name, None) def _get_aes_values(self, df): values = {} for aes_name in self.aes_to_plotly: value = self._get_aes_value(df, aes_name) if value is not None: values[aes_name] = value return values def _add_trace(self, fig_so_far: go.Figure, df, facet_row, facet_col, values, legend: Optional[str] = None): fig_so_far.add_scatter(**{ **{ "x": df.x, "y": df.y, "mode": "markers", "row": facet_row, "col": facet_col, **({"showlegend": False} if legend is None else {"name": legend, "showlegend": True}), }, **self._map_to_plotly(values), }) def _add_legend(self, fig_so_far: go.Figure, aes_name, category, value): fig_so_far.add_scatter(**{ **{ "x": [None], "y": [None], "mode": "markers", "name": category, "showlegend": True, "legendgroup": aes_name, "legendgrouptitle_text": aes_name, }, **self._map_to_plotly({**self.aes_defaults, aes_name: value}), }) def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): traces = [] legends = {} for df in grouped_data: values = self._get_aes_values(df) trace_categories = [] for aes_name in self.aes_legend_groups: category = self._get_aes_value(df, f"{aes_name}_legend") if category is not None: trace_categories.append(category) legends[aes_name] = {**legends.get(aes_name, {}), category: values[aes_name]} traces.append([fig_so_far, df, facet_row, facet_col, values, trace_categories]) non_empty_legend_groups = [ legend_group for legend_group in legends.values() if len(legend_group) > 1 or (len(legend_group) == 1 and next(iter(legend_group.keys())) is not None) ] dummy_legend = is_faceted or len(non_empty_legend_groups) >= 2 if dummy_legend: for trace in traces: self._add_trace(*trace[:-1]) for aes_name, legend_group in legends.items(): prev = legend_cache.get(aes_name, {}) for category, value in legend_group.items(): if category is not None and prev.get(category, None) is None: self._add_legend(fig_so_far, aes_name, category, value) legend_cache[aes_name] = {**prev, **legend_group} else: main_categories = non_empty_legend_groups[0].keys() if len(non_empty_legend_groups) == 1 else None for trace in traces: trace_categories = trace[-1] if main_categories is not None: trace[-1] = next(category for category in trace_categories if category in main_categories) elif len(trace_categories) == 1: trace[-1] = [trace_categories][0] else: trace[-1] = "trace1" for trace in traces: self._add_trace(*trace) def get_stat(self): return StatIdentity()
[docs]def geom_point(mapping=aes(), *, color=None, size=None, alpha=None, shape=None): """Create a scatter plot. Supported aesthetics: ``x``, ``y``, ``color``, ``alpha``, ``tooltip``, ``shape`` Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomPoint(mapping, color=color, size=size, alpha=alpha, shape=shape)
class GeomLine(GeomLineBasic): def __init__(self, aes, color=None): super().__init__(aes, color) self.color = color def apply_to_fig( self, agg_result, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): return super().apply_to_fig(agg_result, fig_so_far, precomputed, facet_row, facet_col, legend_cache, is_faceted) def get_stat(self): return StatIdentity()
[docs]def geom_line(mapping=aes(), *, color=None, size=None, alpha=None): """Create a line plot. Supported aesthetics: ``x``, ``y``, ``color``, ``tooltip`` Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomLine(mapping, color=color)
class GeomText(Geom): aes_to_arg: ClassVar = { "color": ("textfont_color", "black"), "size": ("marker_size", None), "tooltip": ("hovertext", None), "color_legend": ("name", None), "alpha": ("marker_opacity", None), } def __init__(self, aes, color=None, size=None, alpha=None): super().__init__(aes) self.color = color self.size = size self.alpha = alpha def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): def plot_group(df): trace_args = {"x": df.x, "y": df.y, "text": df.label, "mode": "text", "row": facet_row, "col": facet_col} self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_scatter(**trace_args) for group_df in grouped_data: plot_group(group_df) def get_stat(self): return StatIdentity()
[docs]def geom_text(mapping=aes(), *, color=None, size=None, alpha=None): """Create a scatter plot where each point is text from the ``text`` aesthetic. Supported aesthetics: ``x``, ``y``, ``label``, ``color``, ``tooltip`` Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomText(mapping, color=color, size=size, alpha=alpha)
class GeomBar(Geom): aes_to_arg: ClassVar = { "fill": ("marker_color", "black"), "color": ("marker_line_color", None), "tooltip": ("hovertext", None), "fill_legend": ("name", None), "alpha": ("marker_opacity", None), } def __init__(self, aes, fill=None, color=None, alpha=None, position="stack", size=None, stat=None): super().__init__(aes) self.fill = fill self.color = color self.position = position self.size = size self.alpha = alpha if stat is None: stat = StatCount() self.stat = stat def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): def plot_group(df): trace_args = {"x": df.x, "y": df.y, "row": facet_row, "col": facet_col} self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_bar(**trace_args) for group_df in grouped_data: plot_group(group_df) fig_so_far.update_layout(barmode=bar_position_plotly_to_gg(self.position)) def get_stat(self): return self.stat
[docs]def geom_bar(mapping=aes(), *, fill=None, color=None, alpha=None, position="stack", size=None): """Create a bar chart that counts occurrences of the various values of the ``x`` aesthetic. Supported aesthetics: ``x``, ``color``, ``fill``, ``weight`` Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomBar(mapping, fill=fill, color=color, alpha=alpha, position=position, size=size)
[docs]def geom_col(mapping=aes(), *, fill=None, color=None, alpha=None, position="stack", size=None): """Create a bar chart that uses bar heights specified in y aesthetic. Supported aesthetics: ``x``, ``y``, ``color``, ``fill`` Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomBar(mapping, stat=StatIdentity(), fill=fill, color=color, alpha=alpha, position=position, size=size)
class GeomHistogram(Geom): aes_to_arg: ClassVar = { "fill": ("marker_color", "black"), "color": ("marker_line_color", None), "tooltip": ("hovertext", None), "fill_legend": ("name", None), "alpha": ("marker_opacity", None), } def __init__( self, aes, min_val=None, max_val=None, bins=None, fill=None, color=None, alpha=None, position='stack', size=None ): super().__init__(aes) self.min_val = min_val self.max_val = max_val self.bins = bins self.fill = fill self.color = color self.alpha = alpha self.position = position self.size = size def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): min_val = self.min_val if self.min_val is not None else precomputed.min_val max_val = self.max_val if self.max_val is not None else precomputed.max_val # This assumes it doesn't really make sense to use another stat for geom_histogram bins = self.bins if self.bins is not None else self.get_stat().DEFAULT_BINS bin_width = (max_val - min_val) / bins num_groups = len(grouped_data) def plot_group(df, idx): left_xs = df.x if self.position == "dodge": x = left_xs + bin_width * (2 * idx + 1) / (2 * num_groups) bar_width = bin_width / num_groups elif self.position in {"stack", "identity"}: x = left_xs + bin_width / 2 bar_width = bin_width else: raise ValueError(f"Histogram does not support position = {self.position}") right_xs = left_xs + bin_width trace_args = { "x": x, "y": df.y, "row": facet_row, "col": facet_col, "customdata": list(zip(left_xs, right_xs)), "width": bar_width, "hovertemplate": "Range: [%{customdata[0]:.3f}-%{customdata[1]:.3f})<br>" "Count: %{y}<br>" "<extra></extra>", } self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_bar(**trace_args) for idx, group_df in enumerate(grouped_data): plot_group(group_df, idx) fig_so_far.update_layout(barmode=bar_position_plotly_to_gg(self.position)) def get_stat(self): return StatBin(self.min_val, self.max_val, self.bins)
[docs]def geom_histogram( mapping=aes(), *, min_val=None, max_val=None, bins=None, fill=None, color=None, alpha=None, position='stack', size=None, ): """Creates a histogram. Note: this function currently does not support same interface as R's ggplot. Supported aesthetics: ``x``, ``color``, ``fill`` Parameters ---------- mapping: :class:`Aesthetic` Any aesthetics specific to this geom. min_val: `int` or `float` Minimum value to include in histogram max_val: `int` or `float` Maximum value to include in histogram bins: `int` Number of bins to plot. 30 by default. fill: A single fill color for all bars of histogram, overrides ``fill`` aesthetic. color: A single outline color for all bars of histogram, overrides ``color`` aesthetic. alpha: `float` A measure of transparency between 0 and 1. position: :class:`str` Tells how to deal with different groups of data at same point. Options are "stack" and "dodge". Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomHistogram( mapping, min_val=min_val, max_val=max_val, bins=bins, fill=fill, color=color, alpha=alpha, position=position, size=size, )
# Computes the maximum entropy distribution whose cdf is within +- e of the # staircase-shaped cdf encoded by min_x, max_x, x, y. # # x is an array of n x-coordinates between min_x and max_x, and y is an array # of (n+1) y-coordinates between 0 and 1, both sorted. Together they encode a # staircase-shaped cdf. # For example, if min_x = 1, max_x=4, x=[2], y=[.2, .6], then the cdf is the # staircase tracing the points # (1, 0) - (1, .2) - (2, .2) - (2, .6) - (4, .6) - (4, 1) # # Now consider the set of all possible cdfs within +-e of the one above. In # other words, shift the staircase both up and down by e, capping above and # below at 1 and 0, and consider all possible cdfs that lie in between. The # distribution with maximum entropy whose cdf is between the two staircases # is the one whose cdf is the graph constructed as follows: tie a rubber band # to the points (min_x, 0) and (max_x, 1), place the middle between the two # staircases, and let it contract. In other words, it will be the shortest # path between the staircases. # # It's easy to see this path must be piecewise linear, and the points where the # slopes change will be either # * bending up at a point of the form (x[i], y[i]+e), or # * bending down at a point of the form (x[i], y[i+1]-e) # # Returns (new_y, keep). # keep is the array of indices i at which the piecewise linear max-ent cdf # changes slope, as described in the previous paragraph. # new_y is an array the same length as x. For each i in keep, new_y[i] is the # y coordinate of the point on the max-ent cdf. def _max_entropy_cdf(min_x, max_x, x, y, e): def point_on_bound(i, upper): if i == len(x): return max_x, 1 else: yi = y[i] + e if upper else y[i + 1] - e return x[i], yi # Result variables: new_y = np.full_like(x, 0.0, dtype=np.float64) keep = np.full_like(x, False, dtype=np.bool_) # State variables: # (fx, fy) is most recently fixed point on max-ent cdf fx, fy = min_x, 0 li, ui = 0, 0 j = 1 def slope_from_fixed(i, upper): xi, yi = point_on_bound(i, upper) return (yi - fy) / (xi - fx) def fix_point_on_result(i, upper): nonlocal fx, fy, new_y, keep xi, yi = point_on_bound(i, upper) fx, fy = xi, yi new_y[i] = fy keep[i] = True min_slope = slope_from_fixed(li, upper=False) max_slope = slope_from_fixed(ui, upper=True) # Consider a line l from (fx, fy) to (x[j], y?). As we increase y?, l first # bumps into the upper staircase at (x[ui], y[ui] + e), and as we decrease # y?, l first bumps into the lower staircase at (x[li], y[li+1] - e). # We track the min and max slopes l can have while staying between the # staircases, as well as the points li and ui where the line must bend if # forced too high or too low. while True: lower_slope = slope_from_fixed(j, upper=False) upper_slope = slope_from_fixed(j, upper=True) if upper_slope < min_slope: # Line must bend down at x[li]. We know the max-entropy cdf passes # through this point, so record it in new_y, keep. # This becomes the new fixed point, and we must restart the scan # from there. fix_point_on_result(li, upper=False) j = li + 1 if j >= len(x): break li, ui = j, j min_slope = slope_from_fixed(li, upper=False) max_slope = slope_from_fixed(ui, upper=True) j += 1 continue elif lower_slope > max_slope: # Line must bend up at x[ui]. We know the max-entropy cdf passes # through this point, so record it in new_y, keep. # This becomes the new fixed point, and we must restart the scan # from there. fix_point_on_result(ui, upper=True) j = ui + 1 if j >= len(x): break li, ui = j, j min_slope = slope_from_fixed(li, upper=False) max_slope = slope_from_fixed(ui, upper=True) j += 1 continue if j >= len(x): break if upper_slope < max_slope: ui = j max_slope = upper_slope if lower_slope > min_slope: li = j min_slope = lower_slope j += 1 return new_y, keep class GeomDensity(Geom): aes_to_arg: ClassVar = { "fill": ("marker_color", "black"), "color": ("marker_line_color", None), "tooltip": ("hovertext", None), "fill_legend": ("name", None), "alpha": ("marker_opacity", None), } def __init__(self, aes, k=1000, smoothing=0.5, fill=None, color=None, alpha=None, smoothed=False): super().__init__(aes) self.k = k self.smoothing = smoothing self.fill = fill self.color = color self.alpha = alpha self.smoothed = smoothed def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): from hail.expr.functions import _error_from_cdf_python def plot_group(df, idx): data = df.attrs['data'] if self.smoothed: n = data['ranks'][-1] weights = np.diff(data['ranks'][1:-1]) min = data['values'][0] max = data['values'][-1] values = np.array(data['values'][1:-1]) slope = 1 / (max - min) def f(x, prev): inv_scale = (np.sqrt(n * slope) / self.smoothing) * np.sqrt(prev / weights) diff = x[:, np.newaxis] - values grid = ( (3 / (4 * n)) * weights * np.maximum(0, inv_scale - np.power(diff, 2) * np.power(inv_scale, 3)) ) return np.sum(grid, axis=1) round1 = f(values, np.full(len(values), slope)) x_d = np.linspace(min, max, 1000) final = f(x_d, round1) trace_args = { "x": x_d, "y": final, "mode": "lines", "fill": "tozeroy", "row": facet_row, "col": facet_col, } self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_scatter(**trace_args) else: confidence = 5 y = np.array(data['ranks'][1:-1]) / data['ranks'][-1] x = np.array(data['values'][1:-1]) min_x = data['values'][0] max_x = data['values'][-1] err = _error_from_cdf_python(data, 10 ** (-confidence), all_quantiles=True) new_y, keep = _max_entropy_cdf(min_x, max_x, x, y, err) slopes = np.diff([0, *new_y[keep], 1]) / np.diff([min_x, *x[keep], max_x]) left = np.concatenate([[min_x], x[keep]]) right = np.concatenate([x[keep], [max_x]]) widths = right - left trace_args = { "x": [min_x, *x[keep]], "y": slopes, "row": facet_row, "col": facet_col, "width": widths, "offset": 0, } self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_bar(**trace_args) for idx, group_df in enumerate(grouped_data): plot_group(group_df, idx) def get_stat(self): return StatCDF(self.k)
[docs]def geom_density(mapping=aes(), *, k=1000, smoothing=0.5, fill=None, color=None, alpha=None, smoothed=False): """Creates a smoothed density plot. This method uses the `hl.agg.approx_cdf` aggregator to compute a sketch of the distribution of the values of `x`. It then uses an ad hoc method to estimate a smoothed pdf consistent with that cdf. Note: this function currently does not support same interface as R's ggplot. Supported aesthetics: ``x``, ``color``, ``fill`` Parameters ---------- mapping: :class:`Aesthetic` Any aesthetics specific to this geom. k: `int` Passed to the `approx_cdf` aggregator. The size of the aggregator scales linearly with `k`. The default value of `1000` is likely sufficient for most uses. smoothing: `float` Controls the amount of smoothing applied. fill: A single fill color for all density plots, overrides ``fill`` aesthetic. color: A single line color for all density plots, overrides ``color`` aesthetic. alpha: `float` A measure of transparency between 0 and 1. smoothed: `boolean` If true, attempts to fit a smooth kernel density estimator. If false, uses a custom method do generate a variable width histogram directly from the approx_cdf results. Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomDensity(mapping, k, smoothing, fill, color, alpha, smoothed)
class GeomHLine(Geom): def __init__(self, yintercept, linetype="solid", color=None): self.yintercept = yintercept self.aes = aes() self.linetype = linetype self.color = color def apply_to_fig( self, agg_result, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): line_attributes = {"y": self.yintercept, "line_dash": linetype_plotly_to_gg(self.linetype)} if self.color is not None: line_attributes["line_color"] = self.color fig_so_far.add_hline(**line_attributes) def get_stat(self): return StatNone()
[docs]def geom_hline(yintercept, *, linetype="solid", color=None): """Plots a horizontal line at ``yintercept``. Parameters ---------- yintercept : :class:`float` Location to draw line. linetype : :class:`str` Type of line to draw. Choose from "solid", "dashed", "dotted", "longdash", "dotdash". color : :class:`str` Color of line to draw, black by default. Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomHLine(yintercept, linetype=linetype, color=color)
class GeomVLine(Geom): def __init__(self, xintercept, linetype="solid", color=None): self.xintercept = xintercept self.aes = aes() self.linetype = linetype self.color = color def apply_to_fig( self, agg_result, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): line_attributes = {"x": self.xintercept, "line_dash": linetype_plotly_to_gg(self.linetype)} if self.color is not None: line_attributes["line_color"] = self.color fig_so_far.add_vline(**line_attributes) def get_stat(self): return StatNone()
[docs]def geom_vline(xintercept, *, linetype="solid", color=None): """Plots a vertical line at ``xintercept``. Parameters ---------- xintercept : :class:`float` Location to draw line. linetype : :class:`str` Type of line to draw. Choose from "solid", "dashed", "dotted", "longdash", "dotdash". color : :class:`str` Color of line to draw, black by default. Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomVLine(xintercept, linetype=linetype, color=color)
class GeomTile(Geom): def __init__(self, aes): self.aes = aes def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): def plot_group(df): for idx, row in df.iterrows(): x_center = row['x'] y_center = row['y'] width = row['width'] height = row['height'] shape_args = { "type": "rect", "x0": x_center - width / 2, "y0": y_center - height / 2, "x1": x_center + width / 2, "y1": y_center + height / 2, "row": facet_row, "col": facet_col, "opacity": row.get('alpha', 1.0), } if "fill" in df.attrs: shape_args["fillcolor"] = df.attrs["fill"] elif "fill" in row: shape_args["fillcolor"] = row["fill"] else: shape_args["fillcolor"] = "black" fig_so_far.add_shape(**shape_args) for group_df in grouped_data: plot_group(group_df) def get_stat(self): return StatIdentity() def geom_tile(mapping=aes()): return GeomTile(mapping) class GeomFunction(GeomLineBasic): def __init__(self, aes, fun, color): super().__init__(aes, color) self.fun = fun def apply_to_fig( self, agg_result, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): return super().apply_to_fig(agg_result, fig_so_far, precomputed, facet_row, facet_col, legend_cache, is_faceted) def get_stat(self): return StatFunction(self.fun) def geom_func(mapping=aes(), fun=None, color=None): return GeomFunction(mapping, fun=fun, color=color) class GeomArea(Geom): aes_to_arg: ClassVar = { "fill": ("fillcolor", "black"), "color": ("line_color", "rgba(0, 0, 0, 0)"), "tooltip": ("hovertext", None), "fill_legend": ("name", None), } def __init__(self, aes, fill, color): super().__init__(aes) self.fill = fill self.color = color def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): def plot_group(df): trace_args = {"x": df.x, "y": df.y, "row": facet_row, "col": facet_col, "fill": 'tozeroy'} self._add_aesthetics_to_trace_args(trace_args, df) self._update_legend_trace_args(trace_args, legend_cache) fig_so_far.add_scatter(**trace_args) for group_df in grouped_data: plot_group(group_df) def get_stat(self): return StatIdentity()
[docs]def geom_area(mapping=aes(), fill=None, color=None): """Creates a line plot with the area between the line and the x-axis filled in. Supported aesthetics: ``x``, ``y``, ``fill``, ``color``, ``tooltip`` Parameters ---------- mapping: :class:`Aesthetic` Any aesthetics specific to this geom. fill: Color of fill to draw, black by default. Overrides ``fill`` aesthetic. color: Color of line to draw outlining non x-axis facing side, none by default. Overrides ``color`` aesthetic. Returns ------- :class:`FigureAttribute` The geom to be applied. """ return GeomArea(mapping, fill=fill, color=color)
class GeomRibbon(Geom): aes_to_arg: ClassVar = { "fill": ("fillcolor", "black"), "color": ("line_color", "rgba(0, 0, 0, 0)"), "tooltip": ("hovertext", None), "fill_legend": ("name", None), } def __init__(self, aes, fill, color): super().__init__(aes) self.fill = fill self.color = color def apply_to_fig( self, grouped_data, fig_so_far: go.Figure, precomputed, facet_row, facet_col, legend_cache, is_faceted: bool ): def plot_group(df): trace_args_bottom = { "x": df.x, "y": df.ymin, "row": facet_row, "col": facet_col, "mode": "lines", "showlegend": False, } self._add_aesthetics_to_trace_args(trace_args_bottom, df) self._update_legend_trace_args(trace_args_bottom, legend_cache) trace_args_top = { "x": df.x, "y": df.ymax, "row": facet_row, "col": facet_col, "mode": "lines", "fill": 'tonexty', } self._add_aesthetics_to_trace_args(trace_args_top, df) self._update_legend_trace_args(trace_args_top, legend_cache) fig_so_far.add_scatter(**trace_args_bottom) fig_so_far.add_scatter(**trace_args_top) for group_df in grouped_data: plot_group(group_df) def get_stat(self): return StatIdentity()
[docs]def geom_ribbon(mapping=aes(), fill=None, color=None): """Creates filled in area between two lines specified by x, ymin, and ymax Supported aesthetics: ``x``, ``ymin``, ``ymax``, ``color``, ``fill``, ``tooltip`` Parameters ---------- mapping: :class:`Aesthetic` Any aesthetics specific to this geom. fill: Color of fill to draw, black by default. Overrides ``fill`` aesthetic. color: Color of line to draw outlining both side, none by default. Overrides ``color`` aesthetic. :return: :class:`FigureAttribute` The geom to be applied. """ return GeomRibbon(mapping, fill=fill, color=color)