如果值已更改,则可编辑的 Bokeh DataTable 中的颜色文本

color text in a editable Bokeh DataTable if value has changed

我正在使用 Bokeh DataTable 来呈现 editable table,如果用户更改了值,我希望为单元格中的文本着色。
我正在尝试使用 HTMLTemplateFormatter,但我不确定该怎么做。
如果用户更改了第 2 行的值,我希望文本的颜色是这样的:

基于 :

的示例
from bokeh.plotting import curdoc
from bokeh.models import ColumnDataSource
from bokeh.models.widgets import DataTable, TableColumn, HTMLTemplateFormatter

orig_data = dict(
    cola=[1, 2, 3, 4, 5, 6],
)
data = orig_data
source = ColumnDataSource(data)
template = """
            <div style="color: <%= 
                    (function colorfromint(){
                        if(orig_data.cola != data.cola){return('red')} // I don't know what to write here
                        }()) %>;"> 
                <%= value %>
                </font>
            </div>
            """
formatter = HTMLTemplateFormatter(template=template)
columns = [TableColumn(field="cola", title="CL1", formatter=formatter, width=100)]
data_table = DataTable(source=source,
                       columns=columns,
                       editable=True,
                       width=100)
curdoc().add_root(data_table)

我可以使用 HTMLTemplateFormatter 块比较不同的 table 吗?
如果不是,来自 HTMLTemplateFormatter Bokeh documentation
"The formatter has access other items in the row via the dataContext object passed to the formatted"
所以我能想到的一种解决方案是加入 tables 并与 dataContext 对象进行比较,只显示我 select
的列 但是,我不确定该怎么做,在我看来这是一个 "dirty" 解决方法

我对 python 非常熟悉,但我是 Bokeh 的新手。

有没有好的简单方法呢?
也许 HTMLTemplateFormatter 以外的其他方法?

由于默认的 Bokeh 格式化程序适用于整列,因此您必须创建自己的格式化程序。

下面的示例即使没有 Bokeh 服务器也能正常工作,这就是它使用 show 的原因。但是您可以将其替换为 curdoc().add_root - 它应该可以正常工作。

from bokeh.core.property.container import List
from bokeh.core.property.primitive import Int
from bokeh.io import show
from bokeh.models import ColumnDataSource, CustomJS, StringFormatter
from bokeh.models.widgets import DataTable, TableColumn

data = dict(cola=[1, 2, 3, 4, 5, 6],
            colb=[1, 2, 3, 4, 5, 6])
orig_ds = ColumnDataSource(data)
ds = ColumnDataSource(copy.deepcopy(data))

class RowIndexFormatter(StringFormatter):
    rows = List(Int, default=[])

    # language=TypeScript
    __implementation__ = """\
import {StringFormatter} from "models/widgets/tables/cell_formatters"
import * as p from "core/properties"
import {div} from "core/dom"


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

  export type Props = StringFormatter.Props & {
    rows: p.Property<number[]>
  }
}

export interface RowIndexFormatter extends RowIndexFormatter.Attrs {}

export class RowIndexFormatter extends StringFormatter {
  properties: RowIndexFormatter.Props

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

  static init_RowIndexFormatter(): void {
    this.define<RowIndexFormatter.Props>({
      rows: [ p.Array, [] ]
    })
  }

  doFormat(row: any, _cell: any, value: any, _columnDef: any, _dataContext: any): string {
    // Mostly copied from `StringFormatter`, except for taking `rows` into account.
    const {font_style, text_align, text_color} = this

    const text = div({}, value == null ? "" : `${value}`)
    switch (font_style) {
      case "bold":
        text.style.fontWeight = "bold"
        break
      case "italic":
        text.style.fontStyle = "italic"
        break
    }

    if (text_align != null)
      text.style.textAlign = text_align
    if (text_color != null && this.rows.includes(row))
      text.style.color = text_color

    return text.outerHTML
  }
}
    """

columns = [TableColumn(field="cola", title="CL1", formatter=RowIndexFormatter(text_color='red')),
           TableColumn(field="colb", title="CL2", formatter=RowIndexFormatter(text_color='blue'))]
table = DataTable(source=ds, columns=columns, editable=True)
cb = CustomJS(args=dict(orig_ds=orig_ds, table=table),
              code="""\
                  const columns = new Map(table.columns.map(c => [c.field, c]));
                  for (const c of cb_obj.columns()) {
                      const orig_col = orig_ds.data[c];
                      const formatter = columns.get(c).formatter;
                      formatter.rows = [];
                      cb_obj.data[c].forEach((val, idx) => {
                          if (val != orig_col[idx]) {
                              formatter.rows.push(idx);
                          }
                      });
                  }
                  table.change.emit();
              """)
ds.js_on_change('data', cb)
ds.js_on_change('patching', cb)

show(table)