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

Make PaletteSelect use stylehseets instead of inline styles #14052

Merged
merged 3 commits into from
Sep 6, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
Make PaletteSelect use stylesheets instead of inline styles
  • Loading branch information
mattpap committed Sep 5, 2024
commit 5104af2ff4d84972fae42fecfed618bde1124d99
6 changes: 6 additions & 0 deletions bokehjs/src/less/widgets/palette_select_item.less
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
gap: 0.5em;
}

.bk-swatch {
width: 100px;
height: auto;
align-self: stretch;
}

.bk-item {
--active-tool-highlight: #26aae1;

Expand Down
118 changes: 53 additions & 65 deletions bokehjs/src/lib/models/widgets/palette_select.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import type {Keys} from "core/dom"
import {div, canvas, empty, px} from "core/dom"
import type {StyleSheetLike} from "core/dom"
import {div, empty, px, InlineStyleSheet} from "core/dom"
import type {StyleSheetLike, Keys} from "core/dom"
import {DropPane} from "core/util/panes"
import type * as p from "core/properties"
import {enumerate} from "core/util/iterator"
import {color2css} from "core/util/color"
import {cycle} from "core/util/math"
import {linspace} from "core/util/array"
import {assert} from "core/util/assert"

import {InputWidget, InputWidgetView} from "./input_widget"
import * as inputs_css from "styles/widgets/inputs.css"
Expand All @@ -28,8 +28,11 @@ export class PaletteSelectView extends InputWidgetView {
protected _value_el: HTMLElement
protected _pane: DropPane

protected readonly _style = new InlineStyleSheet()
protected readonly _style_menu = new InlineStyleSheet()

override stylesheets(): StyleSheetLike[] {
return [...super.stylesheets(), palette_select_css.default, item_css.default, icons_css.default]
return [...super.stylesheets(), palette_select_css.default, item_css.default, icons_css.default, this._style]
}

override connect_signals(): void {
Expand All @@ -53,56 +56,12 @@ export class PaletteSelectView extends InputWidgetView {
this._pane.el.style.setProperty("--number-of-columns", `${ncols}`)
}

protected _render_image(item: Item): HTMLCanvasElement {
const [_name, colors] = item
const {swatch_width, swatch_height} = this.model

const width = swatch_width
const height = swatch_height == "auto" ? swatch_width : swatch_height

const img = canvas({width, height})
const ctx = img.getContext("2d")!

const n = colors.length
const dx = 100.0/n

for (const [color, i] of enumerate(colors)) {
ctx.beginPath()
ctx.rect(i*dx, 0, dx, 20)
const css_color = color2css(color)
ctx.strokeStyle = css_color
ctx.fillStyle = css_color
ctx.fill()
ctx.stroke()
}

return img
}

protected _render_item(item: Item): HTMLElement {
const [name, colors] = item
const {swatch_width, swatch_height} = this.model

const n = colors.length
const stops = linspace(0, 100, n + 1)
const color_map: string[] = []

for (const [color, i] of enumerate(colors)) {
const [from, to] = [stops[i], stops[i + 1]]
color_map.push(`${color2css(color)} ${from}% ${to}%`)
}

const img = div()
img.style.background = `linear-gradient(to right, ${color_map.join(", ")})`
img.style.width = px(swatch_width)
if (swatch_height == "auto") {
img.style.alignSelf = "stretch"
} else {
img.style.height = px(swatch_height)
}

const entry = div({class: item_css.entry}, img, name)
return entry
const [name] = item
const i = this.model.items.indexOf(item)
assert(i != -1)
const swatch = div({class: item_css.swatch, id: `item_${i}`})
return div({class: item_css.entry}, swatch, div(name))
}

protected _render_value(): HTMLElement | null {
Expand Down Expand Up @@ -134,8 +93,39 @@ export class PaletteSelectView extends InputWidgetView {
override render(): void {
super.render()

const item_els: HTMLElement[] = []
const {swatch_width, swatch_height} = this.model
this._style.replace(`
.${item_css.swatch} {
width: ${swatch_width}px;
height: ${swatch_height == "auto" ? "auto" : px(swatch_height)};
}
`)

for (const [item, i] of enumerate(this.model.items)) {
const [, colors] = item

const n = colors.length
const stops = linspace(0, 100, n + 1)
const color_map: string[] = []

for (const [color, i] of enumerate(colors)) {
const [from, to] = [stops[i], stops[i + 1]]
color_map.push(`${color2css(color)} ${from}% ${to}%`)
}

const gradient = color_map.join(", ")
this._style.append(`
#item_${i} {
background: linear-gradient(to right, ${gradient});
}
`)
}

// The widget and its menu are independent components, so they need
// to have their own stylesheets.
this._style_menu.replace(this._style.css)

const item_els: HTMLElement[] = []
for (const [item, i] of enumerate(this.model.items)) {
const entry_el = this._render_item(item)
const item_el = div({class: item_css.item, tabIndex: 0}, entry_el)
Expand Down Expand Up @@ -183,7 +173,7 @@ export class PaletteSelectView extends InputWidgetView {
this._pane = new DropPane(item_els, {
target: this.group_el,
prevent_hide: this.input_el,
extra_stylesheets: [item_css.default, pane_css.default],
extra_stylesheets: [item_css.default, pane_css.default, this._style_menu],
})

this._update_ncols()
Expand Down Expand Up @@ -253,9 +243,9 @@ export namespace PaletteSelect {
export type Props = InputWidget.Props & {
value: p.Property<string>
items: p.Property<Item[]>
ncols: p.Property<number>
swatch_width: p.Property<number>
swatch_height: p.Property<number | "auto">
ncols: p.Property<number>
}
}

Expand All @@ -272,14 +262,12 @@ export class PaletteSelect extends InputWidget {
static {
this.prototype.default_view = PaletteSelectView

this.define<PaletteSelect.Props>(({Int, Str, List, NonNegative, Positive, Or, Auto}) => {
return {
value: [ Str ],
items: [ List(Item) ],
swatch_width: [ NonNegative(Int), 100 ],
swatch_height: [ Or(Auto, NonNegative(Int)), "auto" ],
ncols: [ Positive(Int), 1 ],
}
})
this.define<PaletteSelect.Props>(({Int, Str, List, NonNegative, Positive, Or, Auto}) => ({
value: [ Str ],
items: [ List(Item) ],
ncols: [ Positive(Int), 1 ],
swatch_width: [ NonNegative(Int), 100 ],
swatch_height: [ Or(Auto, NonNegative(Int)), "auto" ],
}))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PaletteSelect bbox=[0, 0, 200, 56]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
PaletteSelect bbox=[0, 0, 120, 31]
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions bokehjs/test/integration/widgets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@ describe("Widgets", () => {
await view.ready
})

it("with swatch_width=20px", async () => {
const obj = new PaletteSelect({value: "Magma", items, ncols: 3, swatch_width: 20})
const {view} = await display(obj, [500, 200])

await tap(view.input_el)
await view.ready
})

it("with swatch_height=50px", async () => {
const obj = new PaletteSelect({value: "Magma", items, ncols: 3, swatch_height: 50})
const {view} = await display(obj, [500, 400])

await tap(view.input_el)
await view.ready
})

it("with disabled=true", async () => {
const obj = new PaletteSelect({value: "Accent", items, disabled: true})
const {view} = await display(obj, [250, 50])
Expand Down
3 changes: 2 additions & 1 deletion examples/interaction/widgets/color_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
color_map0 = PaletteSelect(title="Choose palette:", value="Turbo", items=items0)
color_map1 = PaletteSelect(title="Choose palette (grid):", value="PuBu", items=items1, ncols=3)
color_map2 = PaletteSelect(title="Choose palette (disabled):", value="PuBu", items=items1, disabled=True)
color_map3 = PaletteSelect(title="Choose palette (large swatch):", value="PuBu", items=items1, ncols=3, swatch_height=50)

layout = column(color_map0, color_map1, color_map2)
layout = column(color_map0, color_map1, color_map2, color_map3)
show(layout)