Skip to content

Commit

Permalink
fix bar plot
Browse files Browse the repository at this point in the history
  • Loading branch information
hanjinliu committed Jan 28, 2024
1 parent a7223c4 commit 020717a
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 49 deletions.
33 changes: 29 additions & 4 deletions whitecanvas/backend/bokeh/line.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,33 @@ def _plt_set_data(self, data):
for seg in data:
xdata.append(seg[:, 0])
ydata.append(seg[:, 1])
self._data.data["x"] = xdata
self._data.data["y"] = ydata
ndata = self._plt_get_ndata()
edge_color = self._data.data["edge_color"]
width = self._data.data["width"]
dash = self._data.data["dash"]
if len(data) < ndata:
loss = ndata - len(data)
edge_color = edge_color[:-loss]
width = width[:-loss]
dash = dash[:-loss]
elif len(data) > ndata:
if ndata == 0:
edge_color = ["blue"] * len(data)
width = [1.0] * len(data)
dash = ["solid"] * len(data)
else:
gain = len(data) - ndata
edge_color = edge_color + edge_color[-1] * gain
width = width + width[-1] * gain
dash = dash + dash[-1] * gain
data = {
"x": xdata,
"y": ydata,
"edge_color": edge_color,
"width": width,
"dash": dash,
}
self._data.data.update(data)

def _plt_get_edge_width(self) -> NDArray[np.floating]:
return self._data.data["width"]
Expand All @@ -137,13 +162,13 @@ def _plt_set_edge_width(self, width: float):
self._data.data["width"] = width

def _plt_get_edge_style(self) -> list[LineStyle]:
return [from_bokeh_line_style(d) for d in self._data.data["style"]]
return [from_bokeh_line_style(d) for d in self._data.data["dash"]]

def _plt_set_edge_style(self, style: LineStyle | list[LineStyle]):
if isinstance(style, LineStyle):
style = [style] * self._plt_get_ndata()
val = [to_bokeh_line_style(s) for s in style]
self._data.data["style"] = val
self._data.data["dash"] = val

def _plt_get_edge_color(self) -> NDArray[np.float32]:
return np.stack([arr_color(c) for c in self._data.data["edge_color"]], axis=0)
Expand Down
7 changes: 6 additions & 1 deletion whitecanvas/canvas/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ def add_bars(
extent = theme._default("bars.extent", extent)
hatch = theme._default("bars.hatch", hatch)
layer = _l.Bars(
center, height, bottom, bar_width=extent, name=name, orient=orient,
center, height, bottom, extent=extent, name=name, orient=orient,
color=color, alpha=alpha, hatch=hatch, backend=self._get_backend(),
) # fmt: skip
return self.add_layer(layer)
Expand Down Expand Up @@ -1311,6 +1311,8 @@ def _cb_inserted(self, idx: int, layer: _l.Layer):
else:
pad_rel = 0.025
self._autoscale_for_layer(layer, pad_rel=pad_rel)
if isinstance(layer, (_l.LayerGroup, _l.LayerWrapper)):
self._cb_reordered()

def _cb_inserted_overlay(self, idx: int, layer: _l.Layer):
_canvas = self._canvas()
Expand Down Expand Up @@ -1341,6 +1343,9 @@ def _cb_reordered(self):
elif isinstance(layer, _l.LayerGroup):
for child in layer.iter_children_recursive():
layer_backends.append(child._backend)
elif isinstance(layer, _l.LayerWrapper):
for child in _iter_layers(layer):
layer_backends.append(child._backend)
else:
raise RuntimeError(f"type {type(layer)} not expected")
self._canvas()._plt_reorder_layers(layer_backends)
Expand Down
4 changes: 2 additions & 2 deletions whitecanvas/canvas/_stacked.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ def add(
alpha=alpha,
name=name,
hatch=hatch,
bar_width=layer.bar_width,
extent=layer.bar_width,
backend=layer._backend_name,
)
elif isinstance(layer, Band):
Expand Down Expand Up @@ -149,7 +149,7 @@ def add_hist(
data, bins, density=density, range=(bins.min(), bins.max())
)
new_layer = Bars(
centers, counts, bottom=layer.top, bar_width=dx * 2, name=name,
centers, counts, bottom=layer.top, extent=dx * 2, name=name,
color=color, alpha=alpha, orient=layer.orient, hatch=hatch,
backend=layer._backend_name,
) # fmt: skip
Expand Down
3 changes: 2 additions & 1 deletion whitecanvas/canvas/dataframe/_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -429,11 +429,12 @@ def add_barplot(
name: str | None = None,
orient: _Orientation = Orientation.VERTICAL,
capsize: float = 0.1,
extent: float = 0.8,
) -> _lt.WrappedBarPlot[_DF]:
canvas = self._canvas()
layer = _lt.WrappedBarPlot.from_table(
self._df, offset, value, name=name, color=color, hatch=hatch, orient=orient,
capsize=capsize, backend=canvas._get_backend(),
capsize=capsize, extent=extent, backend=canvas._get_backend(),
) # fmt: skip
self._post_add_boxlike(layer, color, orient, value)
return canvas.add_layer(layer)
Expand Down
8 changes: 4 additions & 4 deletions whitecanvas/layers/_primitive/bars.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(
bottom: ArrayLike1D | None = None,
*,
orient: str | Orientation = Orientation.VERTICAL,
bar_width: float = 0.8,
extent: float = 0.8,
name: str | None = None,
color: ColorType = "blue",
alpha: float = 1.0,
Expand All @@ -81,10 +81,10 @@ def __init__(
):
MultiFaceEdgeMixin.__init__(self)
ori = Orientation.parse(orient)
xxyy, xhint, yhint = _norm_bar_inputs(x, height, bottom, ori, bar_width)
xxyy, xhint, yhint = _norm_bar_inputs(x, height, bottom, ori, extent)
super().__init__(name=name)
self._backend = self._create_backend(Backend(backend), *xxyy)
self._bar_width = bar_width
self._bar_width = extent
self._orient = ori
self.face.update(color=color, alpha=alpha, hatch=hatch)
self._x_hint, self._y_hint = xhint, yhint
Expand Down Expand Up @@ -114,7 +114,7 @@ def from_histogram(
if bar_width is None:
bar_width = edges[1] - edges[0]
self = Bars(
centers, counts, bar_width=bar_width, name=name, color=color, alpha=alpha,
centers, counts, extent=bar_width, name=name, color=color, alpha=alpha,
orient=orient, hatch=hatch, backend=backend,
) # fmt: skip
if density:
Expand Down
2 changes: 1 addition & 1 deletion whitecanvas/layers/group/boxplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ def from_arrays(
agg_arr = np.stack(agg_values, axis=1)
box = Bars(
x, agg_arr[3] - agg_arr[1], agg_arr[1], name=name, orient=ori,
bar_width=extent, backend=backend,
extent=extent, backend=backend,
).with_face_multi(
hatch=hatch, color=color, alpha=alpha,
).with_edge(color="black") # fmt: skip
Expand Down
66 changes: 37 additions & 29 deletions whitecanvas/layers/group/labeled.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ class LabeledBars(
_mixin.AbstractFaceEdgeMixin["PlotFace", "PlotEdge"],
Generic[_NFace, _NEdge],
):
evens: RichContainerEvents
events: RichContainerEvents
_events_class = RichContainerEvents

def __init__(
Expand All @@ -331,6 +331,9 @@ def bars(self) -> Bars:
"""The bars layer."""
return self._children[0]

def _main_object_layer(self):
return self.bars

def _get_data_xy(self, layer: Bars | None = None) -> tuple[np.ndarray, np.ndarray]:
if layer is None:
layer = self.bars
Expand All @@ -352,17 +355,12 @@ def from_arrays(
color: ColorType | list[ColorType] = "blue",
alpha: float = 1.0,
hatch: str | Hatch = Hatch.SOLID,
extent: float = 0.8,
backend: str | Backend | None = None,
) -> LabeledBars[_mixin.MultiFace, _mixin.MonoEdge]:
x, height, err_data = _init_mean_sd(x, data, color)
bars = Bars(
x,
height,
backend=backend,
).with_face_multi(
color=color,
hatch=hatch,
alpha=alpha,
bars = Bars(x, height, extent=extent, backend=backend).with_face_multi(
color=color, hatch=hatch, alpha=alpha
)
xerr, yerr = _init_error_bars(x, height, err_data, orient, capsize, backend)
return cls(bars, xerr=xerr, yerr=yerr, name=name)
Expand Down Expand Up @@ -404,6 +402,10 @@ def markers(self) -> Markers[_NFace, _NEdge, _Size]:
"""The markers layer."""
return self.plot.markers

def _main_object_layer(self):
"""The main layer with face that will be used in PlotFace/PlotEdge."""
return self.markers

@classmethod
def from_arrays(
cls,
Expand Down Expand Up @@ -441,23 +443,23 @@ class PlotFace(_mixin.FaceNamespace):
@property
def color(self) -> NDArray[np.floating]:
"""Face color of the bar."""
return self._layer.markers.face.color
return self._layer._main_object_layer().face.color

@color.setter
def color(self, color):
ndata = self._layer.markers.ndata
ndata = self._layer._main_object_layer().ndata
col = as_color_array(color, ndata)
self._layer.markers.with_face_multi(color=col)
self._layer._main_object_layer().with_face_multi(color=col)
self.events.color.emit(col)

@property
def hatch(self) -> _mixin.EnumArray[Hatch]:
"""Face fill hatch."""
return self._layer.markers.face.hatch
return self._layer._main_object_layer().face.hatch

@hatch.setter
def hatch(self, hatch: str | Hatch | Iterable[str | Hatch]):
ndata = self._layer.markers.ndata
ndata = self._layer._main_object_layer().ndata
hatches = as_any_1d_array(hatch, ndata, dtype=object)
self._layer.markers.with_face_multi(hatch=hatches)
self.events.hatch.emit(hatches)
Expand Down Expand Up @@ -502,53 +504,59 @@ def update(


class PlotEdge(_mixin.EdgeNamespace):
_layer: LabeledPlot[_mixin.MultiFace, _mixin.MultiEdge, float]
_layer: LabeledPlot[_NFace, _NEdge, float] | LabeledBars[_NFace, _NEdge]

@property
def color(self) -> NDArray[np.floating]:
"""Edge color of the plot."""
return self._layer.markers.edge.color
return self._layer._main_object_layer().edge.color

@color.setter
def color(self, color: ColorType):
self._layer.markers.with_edge_multi(color=color)
self._layer.xerr.color = color
self._layer.yerr.color = color
self._layer._main_object_layer().with_edge_multi(color=color)
if self._layer.xerr.ndata > 0:
self._layer.xerr.color = color
if self._layer.yerr.ndata > 0:
self._layer.yerr.color = color
self.events.color.emit(color)

@property
def width(self) -> NDArray[np.float32]:
"""Edge widths."""
return self._layer.markers.edge.width
return self._layer._main_object_layer().edge.width

@width.setter
def width(self, width: float):
self._layer.markers.edge.width = width
self._layer.xerr.width = width
self._layer.yerr.width = width
self._layer._main_object_layer().edge.width = width
if self._layer.xerr.ndata > 0:
self._layer.xerr.width = width
if self._layer.yerr.ndata > 0:
self._layer.yerr.width = width
self.events.width.emit(width)

@property
def style(self) -> _mixin.EnumArray[LineStyle]:
"""Edge styles."""
return self._layer.markers.edge.style
return self._layer._main_object_layer().edge.style

@style.setter
def style(self, style: str | LineStyle):
style = LineStyle(style)
self._layer.markers.edge.style = style
self._layer.xerr.style = style
self._layer.yerr.style = style
self._layer._main_object_layer().edge.style = style
if self._layer.xerr.ndata > 0:
self._layer.xerr.style = style
if self._layer.yerr.ndata > 0:
self._layer.yerr.style = style
self.events.style.emit(style)

@property
def alpha(self) -> float:
return self.color[3]
return self.color[:, 3]

@alpha.setter
def alpha(self, value):
color = self.color.copy()
color[3] = value
color[:, 3] = value
self.color = color

def update(
Expand Down
11 changes: 7 additions & 4 deletions whitecanvas/layers/tabular/_box_like.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,13 +459,15 @@ def __init__(
name: str | None = None,
orient: Orientation = Orientation.VERTICAL,
capsize: float = 0.1,
extent: float = 0.8,
backend: str | Backend | None = None,
):
_BoxLikeMixin.__init__(self, source, offset, value, color, hatch)
arrays, self._labels = self._generate_datasets()
x = self._offset_by.generate(self._labels, self._splitby)
base = _lg.LabeledBars.from_arrays(
x, arrays, name=name, orient=orient, capsize=capsize, backend=backend,
x, arrays, name=name, orient=orient, capsize=capsize, extent=extent,
backend=backend,
) # fmt: skip
super().__init__(base, source)
base.with_edge(color=theme.get_theme().foreground_color)
Expand All @@ -486,12 +488,13 @@ def from_table(
name: str | None = None,
orient: str | Orientation = Orientation.VERTICAL,
capsize: float = 0.1,
extent: float = 0.8,
backend: str | Backend | None = None,
) -> WrappedPointPlot[_DF]:
) -> WrappedBarPlot[_DF]:
src = parse(df)
self = WrappedPointPlot(
self = WrappedBarPlot(
src, offset, value, orient=orient, name=name, color=color, hatch=hatch,
capsize=capsize, backend=backend
capsize=capsize, extent=extent, backend=backend,
) # fmt: skip
return self

Expand Down
2 changes: 1 addition & 1 deletion whitecanvas/layers/tabular/_dataframe.py
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ def __init__(

x = self._offset_by.generate(self._labels, splitby)
base = _l.Bars(
x, values, name=name, orient=orient, bar_width=extent, backend=backend
x, values, name=name, orient=orient, extent=extent, backend=backend
).with_face_multi()
super().__init__(base, source)
if color is not None:
Expand Down
4 changes: 2 additions & 2 deletions whitecanvas/theme/_dataclasses.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ class Font(_BaseModel):
class Line(_BaseModel):
"""Line style."""

width: float = _field(1.0)
width: float = _field(2.0)
style: LineStyle = _field(LineStyle.SOLID)


Expand All @@ -107,7 +107,7 @@ class Bars(_BaseModel):
class ErrorBars(_BaseModel):
"""Error bar style."""

width: float = _field(1.0)
width: float = _field(2.0)
style: LineStyle = _field(LineStyle.SOLID)


Expand Down

0 comments on commit 020717a

Please sign in to comment.