Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to customize toolbars with any UI components #13571

Open
wants to merge 4 commits into
base: branch-3.5
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions bokehjs/src/less/divider.less
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
:host {
// TODO: shared with tool_button.less
--button-color: lightgray;
}

:host {
flex: 0 0 auto;
background-color: var(--button-color);
}

:host(.bk-horizontal) {
height: 10px;
width: 1px;
}

:host(.bk-vertical) {
height: 1px;
width: 10px;
}
26 changes: 16 additions & 10 deletions bokehjs/src/less/logo.less
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
.bk-logo {
margin: 5px;
position: relative;
display: block;
:host {
// TODO: shared with tool_button.less
--button-width: 30px;
--button-height: 30px;
}

:host {
flex: 0 0 auto;
background-repeat: no-repeat;
&.bk-grey {
filter: grayscale(100%);
}
background-position: center;
}

:host(.bk-grey) {
filter: grayscale(100%);
}

.bk-logo-small {
width: 20px;
height: 20px;
:host(.bk-small) {
width: var(--button-width);
height: var(--button-width);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAABx0RVh0U29mdHdhcmUAQWRvYmUgRmlyZXdvcmtzIENTNui8sowAAAOkSURBVDiNjZRtaJVlGMd/1/08zzln5zjP1LWcU9N0NkN8m2CYjpgQYQXqSs0I84OLIC0hkEKoPtiH3gmKoiJDU7QpLgoLjLIQCpEsNJ1vqUOdO7ppbuec5+V+rj4ctwzd8IIbbi6u+8f1539dt3A78eXC7QizUF7gyV1fD1Yqg4JWz84yffhm0qkFqBogB9rM8tZdtwVsPUhWhGcFJngGeWrPzHm5oaMmkfEg1usvLFyc8jLRqDOMru7AyC8saQr7GG7f5fvDeH7Ej8CM66nIF+8yngt6HWaKh7k49Soy9nXurCi1o3qUbS3zWfrYeQDTB/Qj6kX6Ybhw4B+bOYoLKCC9H3Nu/leUTZ1JdRWkkn2ldcCamzrcf47KKXdAJllSlxAOkRgyHsGC/zRday5Qld9DyoM4/q/rUoy/CXh3jzOu3bHUVZeU+DEn8FInkPBFlu3+nW3Nw0mk6vCDiWg8CeJaxEwuHS3+z5RgY+YBR6V1Z1nxSOfoaPa4LASWxxdNp+VWTk7+4vzaou8v8PN+xo+KY2xsw6une2frhw05CTYOmQvsEhjhWjn0bmXPjpE1+kplmmkP3suftwTubK9Vq22qKmrBhpY4jvd5afdRA3wGjFAgcnTK2s4hY0/GPNIb0nErGMCRxWOOX64Z8RAC4oCXdklmEvcL8o0BfkNK4lUg9HTl+oPlQxdNo3Mg4Nv175e/1LDGzZen30MEjRUtmXSfiTVu1kK8W4txyV6BMKlbgk3lMwYCiusNy9fVfvvwMxv8Ynl6vxoByANLTWplvuj/nF9m2+PDtt1eiHPBr1oIfhCChQMBw6Aw0UulqTKZdfVvfG7VcfIqLG9bcldL/+pdWTLxLUy8Qq38heUIjh4XlzZxzQm19lLFlr8vdQ97rjZVOLf8nclzckbcD4wxXMidpX30sFd37Fv/GtwwhzhxGVAprjbg0gCAEeIgwCZyTV2Z1REEW8O4py0wsjeloKoMr6iCY6dP92H6Vw/oTyICIthibxjm/DfN9lVz8IqtqKYLUXfoKVMVQVVJOElGjrnnUt9T9wbgp8AyYKaGlqingHZU/uG2NTZSVqwHQTWkx9hxjkpWDaCg6Ckj5qebgBVbT3V3NNXMSiWSDdGV3hrtzla7J+duwPOToIg42ChPQOQjspnSlp1V+Gjdged7+8UN5CRAV7a5EdFNwCjEaBR27b3W890TE7g24NAP/mMDXRWrGoFPQI9ls/MWO2dWFAar/xcOIImbbpA3zgAAAABJRU5ErkJggg==);
}
1 change: 1 addition & 0 deletions bokehjs/src/less/tool_button.less
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

:host {
position: relative;
flex: 0 0 auto;

width: var(--button-width);
height: var(--button-height);
Expand Down
38 changes: 0 additions & 38 deletions bokehjs/src/less/toolbar.less
Original file line number Diff line number Diff line change
Expand Up @@ -25,52 +25,14 @@
transition: visibility 0.3s linear, opacity 0.3s linear;
}

.bk-logo {
flex-shrink: 0;
}

:host(.bk-above), :host(.bk-below) {
flex-direction: row;
justify-content: flex-end;

.bk-logo {
order: 1;
margin-left: 5px;
margin-right: 0px;
}
}

:host(.bk-left), :host(.bk-right) {
flex-direction: column;
justify-content: flex-start;

.bk-logo {
order: 0;
margin-bottom: 5px;
margin-top: 0px;
}
}

/// Dividier

.bk-divider {
content: " ";
display: inline-block;
background-color: var(--button-color);
}

:host(.bk-above), :host(.bk-below) {
.bk-divider {
height: 10px;
width: 1px;
}
}

:host(.bk-left), :host(.bk-right) {
.bk-divider {
height: 1px;
width: 10px;
}
}

/// Overflow button
Expand Down
4 changes: 2 additions & 2 deletions bokehjs/src/lib/core/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ export type LinePolicy = typeof LinePolicy["__type__"]
export const Location = Enum("above", "below", "left", "right")
export type Location = typeof Location["__type__"]

export const Logo = Enum("normal", "grey")
export type Logo = typeof Logo["__type__"]
export const LogoStyle = Enum("normal", "grey")
export type LogoStyle = typeof LogoStyle["__type__"]

export const MapType = Enum("satellite", "roadmap", "terrain", "hybrid")
export type MapType = typeof MapType["__type__"]
Expand Down
2 changes: 2 additions & 0 deletions bokehjs/src/lib/core/kinds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,8 @@ export const FontSize = Str
export const Font = Str
export const Angle = Float

export const Primitive = Or(Null, Bool, Float, Str)

// backwards compatibility aliases (these collide with built-in types)
/** @deprecated */
export const Boolean = Bool
Expand Down
2 changes: 1 addition & 1 deletion bokehjs/src/lib/models/annotations/toolbar_panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ export class ToolbarPanelView extends AnnotationView {
this.el.style.position = "absolute"

const {style} = this.toolbar_view.el
if (this.toolbar_view.model.horizontal) {
if (this.toolbar_view.horizontal) {
style.width = "100%"
style.height = "unset"
} else {
Expand Down
27 changes: 20 additions & 7 deletions bokehjs/src/lib/models/filters/group_filter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
import {Filter} from "./filter"
import type {ColumnarDataSource} from "../sources/columnar_data_source"
import {SimilarComparator} from "core/util/eq"
import {isArray} from "core/util/types"
import type * as p from "core/properties"
import {Indices} from "core/types"
import {logger} from "core/logging"
import type {ColumnarDataSource} from "../sources/columnar_data_source"
import {Or, List, Primitive} from "core/kinds"

const GroupBy = Or(List(Primitive), Primitive)
type GroupBy = typeof GroupBy["__type__"]

export namespace GroupFilter {
export type Attrs = p.AttrsOf<Props>

export type Props = Filter.Props & {
column_name: p.Property<string>
group: p.Property<string>
group: p.Property<GroupBy>
// TODO: tolerance for FP
}
}

Expand All @@ -25,20 +32,26 @@ export class GroupFilter extends Filter {
static {
this.define<GroupFilter.Props>(({Str}) => ({
column_name: [ Str ],
group: [ Str ],
group: [ GroupBy ],
}))
}

compute_indices(source: ColumnarDataSource): Indices {
const column = source.get_column(this.column_name)
const {column_name} = this
const column = source.get_column(column_name)
const size = source.get_length() ?? 1
if (column == null) {
logger.warn(`${this}: groupby column '${this.column_name}' not found in the data source`)
logger.warn(`${this}: column '${column_name}' not found in the data source`)
return Indices.all_set(size)
} else {
const indices = new Indices(size, 0)
const group = (() => {
const {group} = this
return isArray(group) ? group : [group]
})()
const indices = Indices.all_unset(size)
const cmp = new SimilarComparator()
for (let i = 0; i < indices.size; i++) {
if (column[i] === this.group) {
if (group.some((value) => cmp.eq(column[i], value))) {
indices.set(i)
}
}
Expand Down
2 changes: 1 addition & 1 deletion bokehjs/src/lib/models/layouts/flex_box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export abstract class FlexBoxView extends LayoutDOMView {

view.style.append(":host", {flex, align_self})

// undo `width/height: 100%` and let `align-self: strech` do the work
// undo `width/height: 100%` and let `align-self: stretch` do the work
if (this._direction == "row") {
if (sizing.height_policy == "max") {
view.style.append(":host", {height: "auto"})
Expand Down
2 changes: 1 addition & 1 deletion bokehjs/src/lib/models/plots/plot_canvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -786,7 +786,7 @@ export class PlotView extends LayoutDOMView implements Paintable {
}

async build_tool_views(): Promise<void> {
const tool_models = flat_map(this.model.toolbar.tools, (item) => item instanceof ToolProxy ? item.tools : [item])
const tool_models = flat_map(this.model.toolbar.computed_tools, (item) => item instanceof ToolProxy ? item.tools : [item])
const {created} = await build_views(this.tool_views, [...tool_models], {parent: this})
created.map((tool_view) => this.canvas_view.ui_event_bus.register_tool(tool_view))
}
Expand Down
39 changes: 39 additions & 0 deletions bokehjs/src/lib/models/tools/divider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {UIElement, UIElementView} from "../ui/ui_element"
import type {ToolbarView} from "./toolbar"
import type {StyleSheetLike} from "core/dom"
import * as divider_css from "styles/divider.css"
import type * as p from "core/properties"

export class DividerView extends UIElementView {
declare model: Divider
declare parent: ToolbarView

override stylesheets(): StyleSheetLike[] {
return [...super.stylesheets(), divider_css.default]
}

override render(): void {
super.render()
this.class_list.add(divider_css[this.parent.orientation])
}
}

export namespace Divider {
export type Attrs = p.AttrsOf<Props>
export type Props = UIElement.Props
}

export interface Divider extends Divider.Attrs {}

export class Divider extends UIElement {
declare properties: Divider.Props
declare __view_type__: DividerView

constructor(attrs?: Partial<Divider.Attrs>) {
super(attrs)
}

static {
this.prototype.default_view = DividerView
}
}
53 changes: 53 additions & 0 deletions bokehjs/src/lib/models/tools/logo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {UIElement, UIElementView} from "../ui/ui_element"
import type {ToolbarView} from "./toolbar"
import {LogoStyle as LogoStyle} from "core/enums"
import type {StyleSheetLike} from "core/dom"
import {a} from "core/dom"
import * as logo_css from "styles/logo.css"
import type * as p from "core/properties"

export class LogoView extends UIElementView {
declare model: Logo
declare parent: ToolbarView

override stylesheets(): StyleSheetLike[] {
return [...super.stylesheets(), logo_css.default]
}

override render(): void {
super.render()

this.class_list.add(logo_css.small)
this.class_list.toggle(logo_css.grey, this.model.style == "grey")

const logo_el = a({href: "https://bokeh.org/", target: "_blank"})
this.shadow_el.append(logo_el)
}
}

export namespace Logo {
export type Attrs = p.AttrsOf<Props>

export type Props = UIElement.Props & {
style: p.Property<LogoStyle>
}
}

export interface Logo extends Logo.Attrs {}

export class Logo extends UIElement {
declare properties: Logo.Props
declare __view_type__: LogoView

constructor(attrs?: Partial<Logo.Attrs>) {
super(attrs)
}

static {
this.prototype.default_view = LogoView

this.define<Logo.Props>(() => ({
style: [ LogoStyle, "normal" ],
}))
}
}
2 changes: 1 addition & 1 deletion bokehjs/src/lib/models/tools/tool_button.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export abstract class ToolButtonView extends UIElementView {

const {location} = this.parent.model
const reverse = location == "left" || location == "above"
const orientation = this.parent.model.horizontal ? "vertical" : "horizontal"
const orientation = this.parent.horizontal ? "vertical" : "horizontal"
const items = this.model.tool.menu ?? []
this._menu = new ContextMenu(!reverse ? items : reversed(items), {
target: this.parent.el,
Expand Down
2 changes: 1 addition & 1 deletion bokehjs/src/lib/models/tools/tool_proxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import type {InspectTool} from "./inspectors/inspect_tool"
import type {MenuItem} from "core/util/menus"
import {enumerate, some} from "core/util/iterator"

export type ToolLike<T extends Tool> = T | ToolProxy<T>
export type ToolLike<T extends Tool = Tool> = T | ToolProxy<T>

export namespace ToolProxy {
export type Attrs<T extends Tool> = p.AttrsOf<Props<T>>
Expand Down