Files
Automaaval/dist/zacatraz/_internal/panel/models/vizzu.ts
T
2026-03-14 21:48:05 +00:00

201 lines
5.9 KiB
TypeScript

import {div} from "@bokehjs/core/dom"
import type * as p from "@bokehjs/core/properties"
import {isArray, isObject} from "@bokehjs/core/util/types"
import {ColumnDataSource} from "@bokehjs/models/sources/column_data_source"
import {ModelEvent} from "@bokehjs/core/bokeh_events"
import type {Attrs} from "@bokehjs/core/types"
import {debounce} from "debounce"
import {HTMLBox, HTMLBoxView} from "./layout"
export class VizzuEvent extends ModelEvent {
override event_name: string = "vizzu_event"
override publish: boolean = true
constructor(readonly data: any) {
super()
}
protected override get event_values(): Attrs {
return {model: this.origin, data: this.data}
}
}
const VECTORIZED_PROPERTIES = ["x", "y", "color", "label", "lightness", "size", "splittedBy", "dividedBy"]
export class VizzuChartView extends HTMLBoxView {
declare model: VizzuChart
container: HTMLDivElement
update: string[] = []
vizzu_view: any
_animating: boolean = false
override connect_signals(): void {
super.connect_signals()
const update = debounce(() => {
if (!this.valid_config) {
console.warn("Vizzu config not valid given current data.")
return
} else if ((this.update.length === 0) || this._animating) {
return
} else {
let change = {}
for (const prop of this.update) {
if (prop === "config") {
change = {...change, config: this.config()}
} else if (prop === "data") {
change = {...change, data: this.data()}
} else {
change = {...change, style: this.model.style}
}
}
this._animating = true
this.vizzu_view.animate(change, `${this.model.duration}ms`).then(() => {
this._animating = false
if (this.update.length > 0) {
update()
}
})
this.update = []
}
}, 20)
const update_prop = (prop: string) => {
if (!this.update.includes(prop)) {
this.update.push(prop)
}
update()
}
const {config, tooltip, style} = this.model.properties
this.on_change(config, () => update_prop("config"))
this.on_change(this.model.source.properties.data, () => update_prop("data"))
this.connect(this.model.source.streaming, () => update_prop("data"))
this.connect(this.model.source.patching, () => update_prop("data"))
this.on_change(tooltip, () => {
this.vizzu_view.feature("tooltip", this.model.tooltip)
})
this.on_change(style, () => update_prop("style"))
}
get valid_config(): boolean {
const columns = this.model.source.columns()
if ("channels" in this.model.config) {
for (const col of Object.values(this.model.config.channels)) {
if (isArray(col)) {
for (const c of col) {
if (col != null && !columns.includes(c as string)) {
return false
}
}
} else if (isObject(col)) {
for (const prop of Object.keys(col)) {
for (const c of ((col as any)[prop] as string[])) {
if (col != null && !columns.includes(c)) {
return false
}
}
}
} else if (col != null && !columns.includes(col as string)) {
return false
}
}
} else {
for (const prop of VECTORIZED_PROPERTIES) {
if (prop in this.model.config && !columns.includes(this.model.config[prop])) {
return false
}
}
}
return true
}
private config(): any {
let config = {...this.model.config}
if ("channels" in config) {
config.channels = {...config.channels}
}
if (config.preset != undefined) {
config = (window as any).Vizzu.presets[config.preset](config)
}
return config
}
private data(): any {
const series = []
for (const column of this.model.columns) {
let array = [...this.model.source.get_array(column.name)]
if (column.type === "datetime" || column.type == "date") {
column.type = "dimension"
}
if (column.type === "dimension") {
array = array.map(String)
}
series.push({...column, values: array})
}
return {series}
}
override render(): void {
super.render()
this.container = div({style: {display: "contents"}})
this.shadow_el.append(this.container)
const state = {config: this.config(), data: this.data(), style: this.model.style}
this.vizzu_view = new (window as any).Vizzu(this.container, state)
this._animating = true
this.vizzu_view.initializing.then((chart: any) => {
chart.on("click", (event: any) => {
this.model.trigger_event(new VizzuEvent({...event.target, ...event.detail}))
})
chart.feature("tooltip", this.model.tooltip)
this._animating = false
})
}
override remove(): void {
if (this.vizzu_view) {
this.vizzu_view.detach()
}
super.remove()
}
}
export namespace VizzuChart {
export type Attrs = p.AttrsOf<Props>
export type Props = HTMLBox.Props & {
animation: p.Property<any>
config: p.Property<any>
columns: p.Property<any>
source: p.Property<ColumnDataSource>
duration: p.Property<number>
style: p.Property<any>
tooltip: p.Property<boolean>
}
}
export interface VizzuChart extends VizzuChart.Attrs {}
export class VizzuChart extends HTMLBox {
declare properties: VizzuChart.Props
constructor(attrs?: Partial<VizzuChart.Attrs>) {
super(attrs)
}
static override __module__ = "panel.models.vizzu"
static {
this.prototype.default_view = VizzuChartView
this.define<VizzuChart.Props>(({Any, List, Bool, Float, Ref}) => ({
animation: [ Any, {} ],
config: [ Any, {} ],
columns: [ List(Any), [] ],
source: [ Ref(ColumnDataSource) ],
duration: [ Float, 500 ],
style: [ Any, {} ],
tooltip: [ Bool, false ],
}))
}
}