UNPKG

@bokeh/bokehjs

Version:

Interactive, novel data visualization

343 lines 11.2 kB
import tz from "timezone"; import * as Numbro from "@bokeh/numbro"; import { _ } from "underscore.template"; import * as p from "../../../core/properties"; import { div, i } from "../../../core/dom"; import { isExpr, isField, isValue } from "../../../core/vectorization"; import { RoundingFunction } from "../../../core/enums"; import { isNumber, isString } from "../../../core/util/types"; import { to_fixed } from "../../../core/util/string"; import { color2css, rgba2css } from "../../../core/util/color"; import { Model } from "../../../model"; import { ColorMapper } from "../../mappers/color_mapper"; import { unreachable } from "../../../core/util/assert"; export class CellFormatter extends Model { static __name__ = "CellFormatter"; constructor(attrs) { super(attrs); } doFormat(_row, _cell, value, _columnDef, _dataContext) { if (value == null) { return ""; } else { return `${value}`.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"); } } } export class StringFormatter extends CellFormatter { static __name__ = "StringFormatter"; constructor(attrs) { super(attrs); } static { this.define(({ Str }) => ({ font_style: [p.FontStyleSpec, { value: "normal" }], text_align: [p.TextAlignSpec, { value: "left" }], text_color: [p.ColorSpec, null], background_color: [p.ColorSpec, null], nan_format: [Str, "NaN"], null_format: [Str, "(null)"], })); } doFormat(_row, _cell, value, _columnDef, dataContext) { const { font_style, text_align, text_color, background_color } = this; if (Number.isNaN(value)) { value = this.nan_format; } else if (value == null) { value = this.null_format; } const text = div(value == null ? "" : `${value}`); // Font style let resolved_font_style; if (isValue(font_style)) { resolved_font_style = font_style.value; } else if (isField(font_style)) { resolved_font_style = dataContext[font_style.field]; } else if (isExpr(font_style)) { // TODO } else { unreachable(); } switch (resolved_font_style) { case "normal": // nothing to do break; case "italic": text.style.fontStyle = "italic"; break; case "bold": text.style.fontWeight = "bold"; break; case "bold italic": text.style.fontStyle = "italic"; text.style.fontWeight = "bold"; break; } // Text align if (isValue(text_align)) { text.style.textAlign = text_align.value; } else if (isField(text_align)) { text.style.textAlign = dataContext[text_align.field]; } else if (isExpr(text_align)) { // TODO } else { unreachable(); } // Text color // Handle the most common case first : isValue and value == null if (isValue(text_color)) { if (text_color.value != null) { text.style.color = color2css(text_color.value); } } else if (isField(text_color)) { if (text_color.transform != null && text_color.transform instanceof ColorMapper) { const rgba_array = text_color.transform.rgba_mapper.v_compute([dataContext[text_color.field]]); const [r, g, b, a] = rgba_array; text.style.color = rgba2css([r, g, b, a]); } else { text.style.color = color2css(dataContext[text_color.field]); } } else if (isExpr(text_color)) { // TODO } else { unreachable(); } // Background color if (isValue(background_color)) { if (background_color.value != null) { text.style.backgroundColor = color2css(background_color.value); } } else if (isField(background_color)) { if (background_color.transform != null && background_color.transform instanceof ColorMapper) { const rgba_array = background_color.transform.rgba_mapper.v_compute([dataContext[background_color.field]]); const [r, g, b, a] = rgba_array; text.style.backgroundColor = rgba2css([r, g, b, a]); } else { text.style.backgroundColor = color2css(dataContext[background_color.field]); } } else if (isExpr(background_color)) { // TODO } else { unreachable(); } return text.outerHTML; } } export class ScientificFormatter extends StringFormatter { static __name__ = "ScientificFormatter"; constructor(attrs) { super(attrs); } static { this.define(({ Float }) => ({ precision: [Float, 10], power_limit_high: [Float, 5], power_limit_low: [Float, -3], })); this.override({ nan_format: "-", null_format: "-", }); } get scientific_limit_low() { return 10.0 ** this.power_limit_low; } get scientific_limit_high() { return 10.0 ** this.power_limit_high; } doFormat(row, cell, value, columnDef, dataContext) { const need_sci = Math.abs(value) <= this.scientific_limit_low || Math.abs(value) >= this.scientific_limit_high; let precision = this.precision; // toExponential does not handle precision values < 0 correctly if (precision < 1) { precision = 1; } if (Number.isNaN(value)) { value = this.nan_format; } else if (value == null) { value = this.null_format; } else if (value == 0) { value = to_fixed(value, 1); } else if (need_sci) { value = value.toExponential(precision); } else { value = to_fixed(value, precision); } // add StringFormatter formatting return super.doFormat(row, cell, value, columnDef, dataContext); } } export class NumberFormatter extends StringFormatter { static __name__ = "NumberFormatter"; constructor(attrs) { super(attrs); } static { this.define(({ Str }) => ({ format: [Str, "0,0"], language: [Str, "en"], rounding: [RoundingFunction, "round"], })); this.override({ nan_format: "-", null_format: "-", }); } doFormat(row, cell, value, columnDef, dataContext) { const { format, language, nan_format, null_format } = this; const rounding = (() => { switch (this.rounding) { case "round": case "nearest": return Math.round; case "floor": case "rounddown": return Math.floor; case "ceil": case "roundup": return Math.ceil; } })(); if (Number.isNaN(value)) { value = nan_format; } else if (value == null) { value = null_format; } else { value = Numbro.format(value, format, language, rounding); } return super.doFormat(row, cell, value, columnDef, dataContext); } } export class BooleanFormatter extends CellFormatter { static __name__ = "BooleanFormatter"; constructor(attrs) { super(attrs); } static { this.define(({ Str }) => ({ icon: [Str, "check"], })); } doFormat(_row, _cell, value, _columnDef, _dataContext) { return !!value ? i({ class: this.icon }).outerHTML : ""; } } export class DateFormatter extends StringFormatter { static __name__ = "DateFormatter"; constructor(attrs) { super(attrs); } static { this.define(({ Str }) => ({ format: [Str, "ISO-8601"], })); this.override({ nan_format: "-", null_format: "-", }); } getFormat() { // using definitions provided here: https://api.jqueryui.com/datepicker/ // except not implementing TICKS switch (this.format) { case "ATOM": case "W3C": case "RFC-3339": case "ISO-8601": return "%Y-%m-%d"; case "COOKIE": return "%a, %d %b %Y"; case "RFC-850": return "%A, %d-%b-%y"; case "RFC-1123": case "RFC-2822": return "%a, %e %b %Y"; case "RSS": case "RFC-822": case "RFC-1036": return "%a, %e %b %y"; case "TIMESTAMP": return undefined; default: return this.format; } } doFormat(row, cell, value, columnDef, dataContext) { function parse(date) { /* Parse bare dates as UTC. */ const has_tz = /Z$|[+-]\d\d((:?)\d\d)?$/.test(date); // ISO 8601 TZ designation or offset const iso_date = has_tz ? date : `${date}Z`; return new Date(iso_date).getTime(); } const epoch = (() => { if (value == null || isNumber(value)) { return value; } else if (isString(value)) { const epoch = Number(value); return isNaN(epoch) ? parse(value) : epoch; } else if (value instanceof Date) { return value.valueOf(); } else { return Number(value); } })(); const NaT = -9223372036854776.0; const date = (() => { if (Number.isNaN(epoch) || epoch == NaT) { return this.nan_format; } else if (value == null) { return this.null_format; } else { return tz(epoch, this.getFormat()); } })(); return super.doFormat(row, cell, date, columnDef, dataContext); } } export class HTMLTemplateFormatter extends CellFormatter { static __name__ = "HTMLTemplateFormatter"; constructor(attrs) { super(attrs); } static { this.define(({ Str }) => ({ template: [Str, "<%= value %>"], })); } doFormat(_row, _cell, value, _columnDef, dataContext) { const { template } = this; if (value == null) { return ""; } else { const compiled_template = _.template(template); const context = { ...dataContext, value }; return compiled_template(context); } } } //# sourceMappingURL=cell_formatters.js.map