Skip to content

Commit

Permalink
Make auto-ranging configurable in data renderers
Browse files Browse the repository at this point in the history
  • Loading branch information
mattpap committed Sep 20, 2023
1 parent c10fa45 commit cb01d0a
Show file tree
Hide file tree
Showing 13 changed files with 94 additions and 38 deletions.
18 changes: 18 additions & 0 deletions bokehjs/src/lib/core/util/bbox.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,24 @@ export function empty(): Rect {
}
}

export function x_only(x0: number, x1: number): Rect {
return {
x0,
x1,
y0: Infinity,
y1: -Infinity,
}
}

export function y_only(y0: number, y1: number): Rect {
return {
x0: Infinity,
x1: -Infinity,
y0,
y1,
}
}

export function positive_x(): Rect {
return {
x0: Number.MIN_VALUE,
Expand Down
10 changes: 3 additions & 7 deletions bokehjs/src/lib/models/annotations/box_annotation.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import {Annotation, AnnotationView} from "./annotation"
import type {Scale} from "../scales/scale"
import type {AutoRanged} from "../ranges/data_range1d"
import {auto_ranged} from "../ranges/data_range1d"
import type {AutoRanged} from "../ranges/auto_ranged"
import {auto_ranged} from "../ranges/auto_ranged"
import * as mixins from "core/property_mixins"
import type * as visuals from "core/visuals"
import type {SerializableState} from "core/view"
import {CoordinateUnits} from "core/enums"
import type * as p from "core/properties"
import type {LRTB, Corners, CoordinateMapper} from "core/util/bbox"
import {BBox, empty} from "core/util/bbox"
import {BBox} from "core/util/bbox"
import type {PanEvent, PinchEvent, Pannable, Pinchable, MoveEvent, Moveable, KeyModifiers} from "core/ui_events"
import {Enum} from "../../core/kinds"
import {Signal} from "core/signaling"
Expand Down Expand Up @@ -87,10 +87,6 @@ export class BoxAnnotationView extends AnnotationView implements Pannable, Pinch
return {x0, x1, y0, y1}
}

log_bounds(): Rect {
return empty()
}

get mappers(): LRTB<CoordinateMapper> {
function mapper(units: CoordinateUnits, scale: Scale, view: CoordinateMapper, canvas: CoordinateMapper) {
switch (units) {
Expand Down
8 changes: 2 additions & 6 deletions bokehjs/src/lib/models/annotations/poly_annotation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {Annotation, AnnotationView} from "./annotation"
import type {Scale} from "../scales/scale"
import type {AutoRanged} from "../ranges/data_range1d"
import {auto_ranged} from "../ranges/data_range1d"
import type {AutoRanged} from "../ranges/auto_ranged"
import {auto_ranged} from "../ranges/auto_ranged"
import * as mixins from "core/property_mixins"
import type * as visuals from "core/visuals"
import type {SerializableState} from "core/view"
Expand Down Expand Up @@ -127,10 +127,6 @@ export class PolyAnnotationView extends AnnotationView implements Pannable, Move
return empty()
}

log_bounds(): Rect {
return empty()
}

protected _mappers(): {x: CoordinateMapper, y: CoordinateMapper} {
const mapper = (units: CoordinateUnits, scale: Scale,
view: CoordinateMapper, canvas: CoordinateMapper): CoordinateMapper => {
Expand Down
4 changes: 2 additions & 2 deletions bokehjs/src/lib/models/plots/plot_canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import type {Axis} from "../axes/axis"
import {AxisView} from "../axes/axis"
import type {ToolbarPanelView} from "../annotations/toolbar_panel"
import {ToolbarPanel} from "../annotations/toolbar_panel"
import type {AutoRanged} from "../ranges/data_range1d"
import {is_auto_ranged} from "../ranges/data_range1d"
import type {AutoRanged} from "../ranges/auto_ranged"
import {is_auto_ranged} from "../ranges/auto_ranged"

import {Reset} from "core/bokeh_events"
import type {ViewStorage, IterViews} from "core/build_views"
Expand Down
19 changes: 16 additions & 3 deletions bokehjs/src/lib/models/plots/range_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import type {Bounds} from "../ranges/data_range1d"
import {DataRange1d} from "../ranges/data_range1d"
import type {CartesianFrame} from "../canvas/cartesian_frame"
import type {CoordinateMapping} from "../coordinates/coordinate_mapping"
import type {Dimensions} from "../ranges/auto_ranged"
import type {PlotView} from "./plot_canvas"
import type {Interval} from "core/types"
import type {Interval, Rect} from "core/types"
import * as bbox from "core/util/bbox"
import {logger} from "core/logging"

export type RangeInfo = {
Expand Down Expand Up @@ -68,12 +70,23 @@ export class RangeManager {
calculate_log_bounds = true
}

function restrict(bounds: Rect, dimensions: Dimensions): Rect {
switch (dimensions) {
case "both": return bounds
case "x": return bbox.x_only(bounds.x0, bounds.x1)
case "y": return bbox.y_only(bounds.y0, bounds.y1)
case "none": return bbox.empty()
}
}

for (const renderer of this.parent.auto_ranged_renderers) {
const bds = renderer.bounds()
const dimensions = renderer.bounds_dimensions?.() ?? "both"

const bds = restrict(renderer.bounds(), dimensions)
bounds.set(renderer.model, bds)

if (calculate_log_bounds) {
const log_bds = renderer.log_bounds()
const log_bds = restrict(renderer.log_bounds?.() ?? bbox.empty(), dimensions)
log_bounds.set(renderer.model, log_bds)
}
}
Expand Down
19 changes: 19 additions & 0 deletions bokehjs/src/lib/models/ranges/auto_ranged.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import type {RendererView} from "../renderers/renderer"
import type {Rect} from "core/types"
import {Enum} from "../../core/kinds"

export const Dimensions = Enum("both", "x", "y", "none")
export type Dimensions = typeof Dimensions["__type__"]

export const auto_ranged = Symbol("auto_ranged")

export interface AutoRanged {
readonly [auto_ranged]: true
bounds_dimensions?(): Dimensions
bounds(): Rect
log_bounds?(): Rect
}

export function is_auto_ranged<T extends RendererView>(r: T): r is T & AutoRanged {
return auto_ranged in r
}
14 changes: 1 addition & 13 deletions bokehjs/src/lib/models/ranges/data_range1d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {DataRange} from "./data_range"
import type {Renderer, RendererView} from "../renderers/renderer"
import type {Renderer} from "../renderers/renderer"
import {PaddingUnits, StartEnd} from "core/enums"
import type {Rect} from "core/types"
import {flat_map} from "core/util/iterator"
Expand All @@ -9,18 +9,6 @@ import * as bbox from "core/util/bbox"
import type {PlotView} from "../plots/plot"
import {compute_renderers} from "../util"

export const auto_ranged = Symbol("auto_ranged")

export interface AutoRanged {
readonly [auto_ranged]: true
bounds(): Rect
log_bounds(): Rect
}

export function is_auto_ranged<T extends RendererView>(r: T): r is T & AutoRanged {
return auto_ranged in r
}

export type Dim = 0 | 1
export type Bounds = Map<Renderer, Rect>

Expand Down
16 changes: 13 additions & 3 deletions bokehjs/src/lib/models/renderers/data_renderer.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {Renderer, RendererView} from "./renderer"
import type {GlyphView} from "../glyphs/glyph"
import type {Scale} from "../scales/scale"
import type {AutoRanged} from "../ranges/data_range1d"
import {auto_ranged} from "../ranges/data_range1d"
import type {AutoRanged} from "../ranges/auto_ranged"
import {Dimensions, auto_ranged} from "../ranges/auto_ranged"
import type {SelectionManager} from "core/selection_manager"
import type * as p from "core/properties"
import type {Rect} from "core/types"
Expand All @@ -23,6 +23,10 @@ export abstract class DataRendererView extends RendererView implements AutoRange

readonly [auto_ranged] = true

bounds_dimensions(): Dimensions {
return this.model.auto_ranging
}

bounds(): Rect {
return this.glyph_view.bounds()
}
Expand All @@ -35,7 +39,9 @@ export abstract class DataRendererView extends RendererView implements AutoRange
export namespace DataRenderer {
export type Attrs = p.AttrsOf<Props>

export type Props = Renderer.Props
export type Props = Renderer.Props & {
auto_ranging: p.Property<Dimensions>
}

export type Visuals = Renderer.Visuals
}
Expand All @@ -51,6 +57,10 @@ export abstract class DataRenderer extends Renderer {
}

static {
this.define<DataRenderer.Props>(() => ({
auto_ranging: [ Dimensions, "both" ],
}))

this.override<DataRenderer.Props>({
level: "glyph",
})
Expand Down
8 changes: 6 additions & 2 deletions src/bokeh/models/annotations/geometry.py
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,12 @@ def _build_glyph_renderer(model: type[Glyph], kwargs: dict[str, Any]) -> GlyphRe
if data_source is Undefined:
data_source = ColumnDataSource()

Check warning on line 396 in src/bokeh/models/annotations/geometry.py

View check run for this annotation

Codecov / codecov/patch

src/bokeh/models/annotations/geometry.py#L396

Added line #L396 was not covered by tests

glyph = model(**kwargs)
return GlyphRenderer(data_source=data_source, glyph=glyph, **glyph_renderer_kwargs)
return GlyphRenderer(
data_source=data_source,
glyph=model(**kwargs),
auto_ranging="none",
**glyph_renderer_kwargs,
)

#-----------------------------------------------------------------------------
# Code
Expand Down
2 changes: 0 additions & 2 deletions src/bokeh/models/glyphs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@
''' Display a variety of visual shapes whose attributes can be associated
with data columns from ``ColumnDataSources``.
The full list of glyphs is below:
.. toctree::
Expand Down
11 changes: 11 additions & 0 deletions src/bokeh/models/renderers/renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,17 @@ class DataRenderer(Renderer):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)

auto_ranging = Enum("both", "x", "y", "none", default="both", help="""
Defines which dimensions will participate in auto-ranging.
.. note::
Note that a renderer can be removed from auto-ranging for a given
range that supports auto-ranging, by not including it that range's
``renderers`` list and when ``renderers`` isn't set to ``"auto"``
mode.
""").accepts(Bool, lambda value: "both" if value else "none")

level = Override(default="glyph")

@abstract
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/defaults.json5
Original file line number Diff line number Diff line change
Expand Up @@ -3171,6 +3171,7 @@
DataRenderer: {
__extends__: "Renderer",
level: "glyph",
auto_ranging: "both",
},
GuideRenderer: {
__extends__: "Renderer",
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/bokeh/models/test_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,7 @@ def test_legacy_Band() -> None:
assert band.data_source == data_source
assert band.name == "band_annotation"
assert band.level == "annotation"
assert band.auto_ranging == "none"

def test_legacy_Whisker() -> None:
data_source = ColumnDataSource()
Expand All @@ -542,6 +543,7 @@ def test_legacy_Whisker() -> None:
assert whisker.data_source == data_source
assert whisker.name == "whisker_annotation"
assert whisker.level == "annotation"
assert whisker.auto_ranging == "none"

#-----------------------------------------------------------------------------
# Dev API
Expand Down

0 comments on commit cb01d0a

Please sign in to comment.