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 export type Props = HTMLBox.Props & { animation: p.Property config: p.Property columns: p.Property source: p.Property duration: p.Property style: p.Property tooltip: p.Property } } export interface VizzuChart extends VizzuChart.Attrs {} export class VizzuChart extends HTMLBox { declare properties: VizzuChart.Props constructor(attrs?: Partial) { super(attrs) } static override __module__ = "panel.models.vizzu" static { this.prototype.default_view = VizzuChartView this.define(({Any, List, Bool, Float, Ref}) => ({ animation: [ Any, {} ], config: [ Any, {} ], columns: [ List(Any), [] ], source: [ Ref(ColumnDataSource) ], duration: [ Float, 500 ], style: [ Any, {} ], tooltip: [ Bool, false ], })) } }