@perspective-dev/viewer-datagrid 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/perspective-viewer-datagrid.js +4 -22
- package/dist/cdn/perspective-viewer-datagrid.js.map +4 -4
- package/dist/css/perspective-viewer-datagrid-toolbar.css +1 -1
- package/dist/css/perspective-viewer-datagrid.css +1 -1
- package/dist/esm/color_utils.d.ts +22 -0
- package/dist/esm/custom_elements/datagrid.d.ts +16 -21
- package/dist/esm/data_listener/format_cell.d.ts +1 -1
- package/dist/esm/data_listener/formatter_cache.d.ts +1 -1
- package/dist/esm/data_listener/index.d.ts +3 -2
- package/dist/esm/event_handlers/click/edit_click.d.ts +3 -2
- package/dist/esm/event_handlers/click.d.ts +4 -6
- package/dist/esm/event_handlers/dispatch_click.d.ts +3 -2
- package/dist/esm/event_handlers/expand_collapse.d.ts +1 -1
- package/dist/esm/event_handlers/focus.d.ts +4 -5
- package/dist/esm/event_handlers/header_click.d.ts +5 -3
- package/dist/esm/event_handlers/keydown/edit_keydown.d.ts +3 -4
- package/dist/esm/event_handlers/select_region.d.ts +3 -1
- package/dist/esm/event_handlers/sort.d.ts +8 -7
- package/dist/esm/model/create.d.ts +1 -1
- package/dist/esm/model/meta_columns.d.ts +1 -0
- package/dist/esm/perspective-viewer-datagrid.js +3 -3
- package/dist/esm/perspective-viewer-datagrid.js.map +4 -4
- package/dist/esm/plugin/activate.d.ts +1 -1
- package/dist/esm/plugin/column_config_schema.d.ts +31 -0
- package/dist/esm/style_handlers/body.d.ts +3 -3
- package/dist/esm/style_handlers/column_header.d.ts +4 -3
- package/dist/esm/style_handlers/consolidated.d.ts +3 -47
- package/dist/esm/style_handlers/editable.d.ts +3 -2
- package/dist/esm/style_handlers/focus.d.ts +4 -4
- package/dist/esm/style_handlers/group_header.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/boolean.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/cell_flash.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/datetime.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/numeric.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/row_header.d.ts +1 -1
- package/dist/esm/style_handlers/table_cell/string.d.ts +1 -1
- package/dist/esm/style_handlers/types.d.ts +0 -4
- package/dist/esm/types.d.ts +10 -17
- package/package.json +2 -4
- package/src/css/regular_table.css +87 -31
- package/src/css/row-hover.css +20 -7
- package/src/css/toolbar.css +11 -0
- package/src/ts/color_utils.ts +181 -16
- package/src/ts/custom_elements/datagrid.ts +70 -56
- package/src/ts/custom_elements/toolbar.ts +4 -5
- package/src/ts/data_listener/format_cell.ts +28 -9
- package/src/ts/data_listener/format_tree_header.ts +2 -2
- package/src/ts/data_listener/formatter_cache.ts +9 -96
- package/src/ts/data_listener/index.ts +13 -11
- package/src/ts/event_handlers/click/edit_click.ts +10 -6
- package/src/ts/event_handlers/click.ts +39 -68
- package/src/ts/event_handlers/dispatch_click.ts +27 -25
- package/src/ts/event_handlers/expand_collapse.ts +11 -8
- package/src/ts/event_handlers/focus.ts +38 -35
- package/src/ts/event_handlers/header_click.ts +107 -62
- package/src/ts/event_handlers/keydown/edit_keydown.ts +60 -54
- package/src/ts/event_handlers/select_region.ts +153 -131
- package/src/ts/event_handlers/sort.ts +20 -25
- package/src/ts/get_cell_config.ts +10 -3
- package/src/ts/model/column_overrides.ts +16 -9
- package/src/ts/model/create.ts +68 -55
- package/src/ts/{event_handlers/deselect_all.ts → model/meta_columns.ts} +33 -14
- package/src/ts/model/toolbar.ts +33 -8
- package/src/ts/plugin/activate.ts +122 -92
- package/src/ts/plugin/column_config_schema.ts +187 -0
- package/src/ts/plugin/draw.ts +1 -0
- package/src/ts/plugin/restore.ts +6 -2
- package/src/ts/plugin/save.ts +2 -5
- package/src/ts/style_handlers/body.ts +48 -51
- package/src/ts/style_handlers/column_header.ts +22 -21
- package/src/ts/style_handlers/consolidated.ts +23 -123
- package/src/ts/style_handlers/editable.ts +16 -10
- package/src/ts/style_handlers/focus.ts +7 -5
- package/src/ts/style_handlers/group_header.ts +13 -6
- package/src/ts/style_handlers/table_cell/boolean.ts +3 -3
- package/src/ts/style_handlers/table_cell/cell_flash.ts +11 -11
- package/src/ts/style_handlers/table_cell/datetime.ts +3 -3
- package/src/ts/style_handlers/table_cell/numeric.ts +24 -25
- package/src/ts/style_handlers/table_cell/row_header.ts +2 -2
- package/src/ts/style_handlers/table_cell/string.ts +20 -18
- package/src/ts/style_handlers/types.ts +0 -10
- package/src/ts/types.ts +28 -20
- package/dist/esm/event_handlers/deselect_all.d.ts +0 -5
- package/dist/esm/event_handlers/row_select_click.d.ts +0 -4
- package/dist/esm/plugin/column_style_controls.d.ts +0 -28
- package/src/ts/event_handlers/row_select_click.ts +0 -92
- package/src/ts/plugin/column_style_controls.ts +0 -76
package/src/ts/color_utils.ts
CHANGED
|
@@ -10,15 +10,182 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
import chroma from "chroma-js";
|
|
14
13
|
import type { ColorRecord } from "./types.js";
|
|
15
14
|
|
|
15
|
+
/** 8-bit sRGB color as `[r, g, b]` with each channel in `[0, 255]`. */
|
|
16
|
+
export type RGB = [number, number, number];
|
|
17
|
+
|
|
18
|
+
/** HSL color as `[h, s, l]` with `h` in degrees `[0, 360)` and `s`, `l` in `[0, 1]`. */
|
|
19
|
+
export type HSL = [number, number, number];
|
|
20
|
+
|
|
21
|
+
const parse_cache = new Map<string, RGB>();
|
|
22
|
+
let parse_ctx: CanvasRenderingContext2D | null = null;
|
|
23
|
+
|
|
24
|
+
/** Parse a CSS hex color (`#rgb`, `#rgba`, `#rrggbb`, `#rrggbbaa`). Returns `null` if `input` is not a hex literal. Alpha is ignored. */
|
|
25
|
+
function parse_hex(input: string): RGB | null {
|
|
26
|
+
const s = input.startsWith("#") ? input.slice(1) : input;
|
|
27
|
+
if (s.length === 3 || s.length === 4) {
|
|
28
|
+
const r = parseInt(s[0] + s[0], 16);
|
|
29
|
+
const g = parseInt(s[1] + s[1], 16);
|
|
30
|
+
const b = parseInt(s[2] + s[2], 16);
|
|
31
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
|
|
32
|
+
return [r, g, b];
|
|
33
|
+
}
|
|
34
|
+
} else if (s.length === 6 || s.length === 8) {
|
|
35
|
+
const r = parseInt(s.slice(0, 2), 16);
|
|
36
|
+
const g = parseInt(s.slice(2, 4), 16);
|
|
37
|
+
const b = parseInt(s.slice(4, 6), 16);
|
|
38
|
+
if (!isNaN(r) && !isNaN(g) && !isNaN(b)) {
|
|
39
|
+
return [r, g, b];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Parse a CSS `rgb()` or `rgba()` functional color. Returns `null` if `input` does not match. Alpha is ignored. */
|
|
47
|
+
function parse_rgb_fn(input: string): RGB | null {
|
|
48
|
+
const m = input.match(
|
|
49
|
+
/^rgba?\(\s*([\d.]+)\s*[, ]\s*([\d.]+)\s*[, ]\s*([\d.]+)/i,
|
|
50
|
+
);
|
|
51
|
+
if (!m) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [
|
|
56
|
+
Math.round(parseFloat(m[1])),
|
|
57
|
+
Math.round(parseFloat(m[2])),
|
|
58
|
+
Math.round(parseFloat(m[3])),
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Fallback parser that defers to the browser by assigning `input` to a 2D canvas `fillStyle` and re-reading the normalized value. Handles named colors, `hsl()`, etc. Returns `[0, 0, 0]` if the value is invalid or no canvas context is available. */
|
|
63
|
+
function parse_via_canvas(input: string): RGB {
|
|
64
|
+
if (!parse_ctx) {
|
|
65
|
+
const canvas = document.createElement("canvas");
|
|
66
|
+
canvas.width = canvas.height = 1;
|
|
67
|
+
parse_ctx = canvas.getContext("2d");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!parse_ctx) {
|
|
71
|
+
return [0, 0, 0];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
parse_ctx.fillStyle = "#000";
|
|
75
|
+
parse_ctx.fillStyle = input;
|
|
76
|
+
const normalized = parse_ctx.fillStyle as string;
|
|
77
|
+
return parse_hex(normalized) ?? parse_rgb_fn(normalized) ?? [0, 0, 0];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Parse any CSS color string into an `RGB` triple. Tries hex and `rgb()` fast paths, then falls back to a canvas-based parser for named colors, `hsl()`, etc. Results are memoized per input. */
|
|
81
|
+
export function parseColor(input: string): RGB {
|
|
82
|
+
const key = input.trim();
|
|
83
|
+
const cached = parse_cache.get(key);
|
|
84
|
+
if (cached) {
|
|
85
|
+
return cached;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const rgb = parse_hex(key) ?? parse_rgb_fn(key) ?? parse_via_canvas(key);
|
|
89
|
+
parse_cache.set(key, rgb);
|
|
90
|
+
return rgb;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Format a single channel as a clamped, zero-padded two-digit hex byte. */
|
|
94
|
+
function toHex(c: number): string {
|
|
95
|
+
const v = Math.max(0, Math.min(255, Math.round(c)));
|
|
96
|
+
return v.toString(16).padStart(2, "0");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Format an `RGB` triple as a `#rrggbb` hex string. Channels are clamped to `[0, 255]`. */
|
|
100
|
+
export function rgbToHex([r, g, b]: RGB): string {
|
|
101
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Convert sRGB to HSL. Output `h` is in degrees `[0, 360)`; `s` and `l` are in `[0, 1]`. */
|
|
105
|
+
export function rgbToHsl([r, g, b]: RGB): HSL {
|
|
106
|
+
const rn = r / 255,
|
|
107
|
+
gn = g / 255,
|
|
108
|
+
bn = b / 255;
|
|
109
|
+
const max = Math.max(rn, gn, bn);
|
|
110
|
+
const min = Math.min(rn, gn, bn);
|
|
111
|
+
const l = (max + min) / 2;
|
|
112
|
+
const d = max - min;
|
|
113
|
+
let h = 0;
|
|
114
|
+
let s = 0;
|
|
115
|
+
if (d !== 0) {
|
|
116
|
+
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
117
|
+
if (max === rn) {
|
|
118
|
+
h = ((gn - bn) / d + (gn < bn ? 6 : 0)) * 60;
|
|
119
|
+
} else if (max === gn) {
|
|
120
|
+
h = ((bn - rn) / d + 2) * 60;
|
|
121
|
+
} else {
|
|
122
|
+
h = ((rn - gn) / d + 4) * 60;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return [h, s, l];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Convert HSL to sRGB. `h` is wrapped into `[0, 360)`; `s` and `l` are expected in `[0, 1]`. Output channels are rounded to integers in `[0, 255]`. */
|
|
130
|
+
export function hslToRgb([h, s, l]: HSL): RGB {
|
|
131
|
+
const hn = (((h % 360) + 360) % 360) / 360;
|
|
132
|
+
if (s === 0) {
|
|
133
|
+
const v = Math.round(l * 255);
|
|
134
|
+
return [v, v, v];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
|
|
138
|
+
const p = 2 * l - q;
|
|
139
|
+
const f = (t: number): number => {
|
|
140
|
+
if (t < 0) {
|
|
141
|
+
t += 1;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (t > 1) {
|
|
145
|
+
t -= 1;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (t < 1 / 6) {
|
|
149
|
+
return p + (q - p) * 6 * t;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (t < 1 / 2) {
|
|
153
|
+
return q;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (t < 2 / 3) {
|
|
157
|
+
return p + (q - p) * (2 / 3 - t) * 6;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return p;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
return [
|
|
164
|
+
Math.round(f(hn + 1 / 3) * 255),
|
|
165
|
+
Math.round(f(hn) * 255),
|
|
166
|
+
Math.round(f(hn - 1 / 3) * 255),
|
|
167
|
+
];
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Blend two `RGB` colors using LRGB (gamma-naive linear) interpolation,
|
|
172
|
+
* matching chroma-js's default `mix` mode. `f` is the weight of `b` in `[0, 1]`
|
|
173
|
+
* (0 returns `a`, 1 returns `b`).
|
|
174
|
+
*/
|
|
175
|
+
export function mixRgb(a: RGB, b: RGB, f = 0.5): RGB {
|
|
176
|
+
return [
|
|
177
|
+
Math.round(Math.sqrt(a[0] * a[0] * (1 - f) + b[0] * b[0] * f)),
|
|
178
|
+
Math.round(Math.sqrt(a[1] * a[1] * (1 - f) + b[1] * b[1] * f)),
|
|
179
|
+
Math.round(Math.sqrt(a[2] * a[2] * (1 - f) + b[2] * b[2] * f)),
|
|
180
|
+
];
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** 50/50 LRGB blend of CSS color `a` with `RGB`-ish triple `b`, returned as `#rrggbb`. */
|
|
16
184
|
export function blend(a: string, b: number[]): string {
|
|
17
|
-
return
|
|
185
|
+
return rgbToHex(mixRgb(parseColor(a), [b[0], b[1], b[2]], 0.5));
|
|
18
186
|
}
|
|
19
187
|
|
|
20
|
-
|
|
21
|
-
// the color of a heatmap cell over the background.
|
|
188
|
+
/** Composite a premultiplied-style `RGBA` cell color over `source` (default white) and return the resulting opaque `RGB`. Used to flatten heatmap cells against the background. */
|
|
22
189
|
export function rgbaToRgb(
|
|
23
190
|
[r, g, b, a]: [number, number, number, number],
|
|
24
191
|
source: [number, number, number] = [255, 255, 255],
|
|
@@ -30,7 +197,7 @@ export function rgbaToRgb(
|
|
|
30
197
|
return [f(0, r), f(1, g), f(2, b)];
|
|
31
198
|
}
|
|
32
199
|
|
|
33
|
-
|
|
200
|
+
/** Pick a readable foreground (`#161616` or `#ffffff`) for the given background using a perceptual luminance threshold. */
|
|
34
201
|
export function infer_foreground_from_background([r, g, b]: [
|
|
35
202
|
number,
|
|
36
203
|
number,
|
|
@@ -42,21 +209,19 @@ export function infer_foreground_from_background([r, g, b]: [
|
|
|
42
209
|
: "#ffffff";
|
|
43
210
|
}
|
|
44
211
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
const [
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const [r2, g2, b2] =
|
|
51
|
-
.set("hsl.h", (chromahex.get("hsl.h") + 15) % 360)
|
|
52
|
-
.rgb();
|
|
212
|
+
/** Build a CSS `linear-gradient` that fans `rgb` ±15° in hue, used as the negative-value swatch in column color pickers. */
|
|
213
|
+
function make_gradient(rgb: RGB): string {
|
|
214
|
+
const [h, s, l] = rgbToHsl(rgb);
|
|
215
|
+
const [r, g, b] = rgb;
|
|
216
|
+
const [r1, g1, b1] = hslToRgb([h - 15, s, l]);
|
|
217
|
+
const [r2, g2, b2] = hslToRgb([h + 15, s, l]);
|
|
53
218
|
return `linear-gradient(to right top,rgb(${r1},${g1},${b1}),rgb(${r},${g},${b}) 50%,rgb(${r2},${g2},${b2}))`;
|
|
54
219
|
}
|
|
55
220
|
|
|
221
|
+
/** Precompute the tuple of derived color strings (RGB channels, gradient, opaque/transparent rgba) cached on the model for a configured plugin color. */
|
|
56
222
|
export function make_color_record(color: string): ColorRecord {
|
|
57
|
-
const
|
|
58
|
-
const _neg_grad = make_gradient(
|
|
59
|
-
const rgb = chroma_neg.rgb();
|
|
223
|
+
const rgb = parseColor(color);
|
|
224
|
+
const _neg_grad = make_gradient(rgb);
|
|
60
225
|
return [
|
|
61
226
|
color,
|
|
62
227
|
rgb[0],
|
|
@@ -15,19 +15,22 @@ import { activate } from "../plugin/activate.js";
|
|
|
15
15
|
import { restore } from "../plugin/restore.js";
|
|
16
16
|
import { save } from "../plugin/save.js";
|
|
17
17
|
import { draw } from "../plugin/draw.js";
|
|
18
|
-
import
|
|
19
|
-
|
|
20
|
-
} from "../plugin/
|
|
18
|
+
import column_config_schema, {
|
|
19
|
+
ColumnConfigSchema,
|
|
20
|
+
} from "../plugin/column_config_schema.js";
|
|
21
21
|
import datagridStyles from "../../../dist/css/perspective-viewer-datagrid.css";
|
|
22
22
|
import { format_raw } from "../data_listener/format_cell.js";
|
|
23
|
+
import { sourceColumn } from "@perspective-dev/viewer/src/ts/column-format.js";
|
|
23
24
|
|
|
24
25
|
import type { View, ViewWindow } from "@perspective-dev/client";
|
|
25
|
-
import type {
|
|
26
|
+
import type {
|
|
27
|
+
IPerspectiveViewerPlugin,
|
|
28
|
+
PluginStaticConfig,
|
|
29
|
+
} from "@perspective-dev/viewer";
|
|
26
30
|
import type {
|
|
27
31
|
DatagridModel,
|
|
28
32
|
DatagridToolbarElement,
|
|
29
33
|
EditMode,
|
|
30
|
-
PerspectiveViewerElement,
|
|
31
34
|
DatagridPluginConfig,
|
|
32
35
|
ColumnsConfig,
|
|
33
36
|
} from "../types.js";
|
|
@@ -113,44 +116,64 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
113
116
|
return await activate.call(this, view);
|
|
114
117
|
}
|
|
115
118
|
|
|
116
|
-
|
|
117
|
-
return
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
get min_config_columns(): number | undefined {
|
|
129
|
-
return undefined;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
get config_column_names(): string[] {
|
|
133
|
-
return ["Columns"];
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
get group_rollups(): string[] {
|
|
137
|
-
return ["rollup", "flat", "total"];
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Give the Datagrid a higher priority so it is loaded
|
|
142
|
-
* over the default charts by default.
|
|
143
|
-
*/
|
|
144
|
-
get priority(): number {
|
|
145
|
-
return 1;
|
|
119
|
+
get_static_config(): PluginStaticConfig {
|
|
120
|
+
return {
|
|
121
|
+
name: "Datagrid",
|
|
122
|
+
category: "Basic",
|
|
123
|
+
select_mode: "toggle",
|
|
124
|
+
config_column_names: ["Columns"],
|
|
125
|
+
group_rollup_modes: ["rollup", "flat", "total"],
|
|
126
|
+
// Higher priority than the chart plugins so the Datagrid is
|
|
127
|
+
// loaded by default.
|
|
128
|
+
priority: 1,
|
|
129
|
+
can_render_column_styles: true,
|
|
130
|
+
};
|
|
146
131
|
}
|
|
147
132
|
|
|
148
|
-
|
|
149
|
-
|
|
133
|
+
plugin_config_schema(): ColumnConfigSchema {
|
|
134
|
+
const fields = [];
|
|
135
|
+
fields.push({
|
|
136
|
+
kind: "Enum",
|
|
137
|
+
key: "edit_mode",
|
|
138
|
+
default: "READ_ONLY",
|
|
139
|
+
variants: [
|
|
140
|
+
{ value: "EDIT", label: "Edit" },
|
|
141
|
+
{ value: "READ_ONLY", label: "Read-only" },
|
|
142
|
+
{ value: "SELECT_ROW", label: "Row Select" },
|
|
143
|
+
{ value: "SELECT_COLUMN", label: "Column Select" },
|
|
144
|
+
{ value: "SELECT_REGION", label: "Region Select" },
|
|
145
|
+
{ value: "SELECT_ROW_TREE", label: "Tree Select" },
|
|
146
|
+
],
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
fields.push({
|
|
150
|
+
kind: "Bool",
|
|
151
|
+
key: "scroll_lock",
|
|
152
|
+
default: false,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return {
|
|
156
|
+
fields,
|
|
157
|
+
};
|
|
150
158
|
}
|
|
151
159
|
|
|
152
|
-
|
|
153
|
-
|
|
160
|
+
column_config_schema(
|
|
161
|
+
type: string,
|
|
162
|
+
group: string | undefined,
|
|
163
|
+
column_name: string,
|
|
164
|
+
current_value: Record<string, unknown> | null,
|
|
165
|
+
viewer_config?: { group_by?: string[]; group_rollup_mode?: string },
|
|
166
|
+
column_stats?: { abs_max: number },
|
|
167
|
+
): ColumnConfigSchema {
|
|
168
|
+
return column_config_schema.call(
|
|
169
|
+
this,
|
|
170
|
+
type as any,
|
|
171
|
+
group,
|
|
172
|
+
column_name,
|
|
173
|
+
current_value,
|
|
174
|
+
viewer_config,
|
|
175
|
+
column_stats,
|
|
176
|
+
);
|
|
154
177
|
}
|
|
155
178
|
|
|
156
179
|
async draw(view: View): Promise<void> {
|
|
@@ -170,9 +193,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
170
193
|
}
|
|
171
194
|
}
|
|
172
195
|
|
|
173
|
-
async render(viewport?: ViewWindow): Promise<string> {
|
|
174
|
-
const viewer = this.parentElement as PerspectiveViewerElement;
|
|
175
|
-
const view = await viewer.getView();
|
|
196
|
+
async render(view: View, viewport?: ViewWindow): Promise<string> {
|
|
176
197
|
const json = await view.to_columns(viewport as any);
|
|
177
198
|
const cols = await view.column_paths(viewport as any);
|
|
178
199
|
|
|
@@ -192,7 +213,7 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
192
213
|
const pluginConfig = (this.regular_table as any)[
|
|
193
214
|
PRIVATE_PLUGIN_SYMBOL
|
|
194
215
|
] as ColumnsConfig | undefined;
|
|
195
|
-
const columnName = col_name
|
|
216
|
+
const columnName = sourceColumn(col_name);
|
|
196
217
|
const formatter = format_raw(
|
|
197
218
|
type,
|
|
198
219
|
pluginConfig?.[columnName] || {},
|
|
@@ -204,13 +225,14 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
204
225
|
out += col[ridx] + "\t";
|
|
205
226
|
}
|
|
206
227
|
}
|
|
228
|
+
|
|
207
229
|
out += "\n";
|
|
208
230
|
}
|
|
209
231
|
|
|
210
232
|
return out.trim();
|
|
211
233
|
}
|
|
212
234
|
|
|
213
|
-
async resize(): Promise<void> {
|
|
235
|
+
async resize(_view: View): Promise<void> {
|
|
214
236
|
if (!this.isConnected || this.offsetParent == null) {
|
|
215
237
|
return;
|
|
216
238
|
}
|
|
@@ -225,25 +247,17 @@ export class HTMLPerspectiveViewerDatagridPluginElement
|
|
|
225
247
|
this.regular_table.clear();
|
|
226
248
|
}
|
|
227
249
|
|
|
228
|
-
|
|
250
|
+
save(): any {
|
|
229
251
|
return save.call(this);
|
|
230
252
|
}
|
|
231
253
|
|
|
232
|
-
|
|
233
|
-
token: DatagridPluginConfig,
|
|
234
|
-
columns_config?: ColumnsConfig,
|
|
235
|
-
): Promise<any> {
|
|
254
|
+
restore(token: DatagridPluginConfig, columns_config?: ColumnsConfig): void {
|
|
236
255
|
return restore.call(this, token, columns_config ?? {});
|
|
237
256
|
}
|
|
238
257
|
|
|
239
|
-
|
|
240
|
-
// Get view from model if available, otherwise no-op
|
|
241
|
-
if (this.model?._view) {
|
|
242
|
-
await this.draw(this.model._view);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
258
|
+
restyle() {}
|
|
245
259
|
|
|
246
|
-
|
|
260
|
+
delete(): void {
|
|
247
261
|
this.disconnectedCallback();
|
|
248
262
|
this._toolbar = undefined;
|
|
249
263
|
if ((this.regular_table as any).table_model) {
|
|
@@ -10,12 +10,10 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
+
import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
|
|
13
14
|
import TOOLBAR_STYLE from "../../../dist/css/perspective-viewer-datagrid-toolbar.css";
|
|
14
15
|
import { toggle_edit_mode, toggle_scroll_lock } from "../model/toolbar.js";
|
|
15
|
-
import type {
|
|
16
|
-
DatagridPluginElement,
|
|
17
|
-
PerspectiveViewerElement,
|
|
18
|
-
} from "../types.js";
|
|
16
|
+
import type { DatagridPluginElement } from "../types.js";
|
|
19
17
|
|
|
20
18
|
const stylesheet = new CSSStyleSheet();
|
|
21
19
|
stylesheet.replaceSync(TOOLBAR_STYLE);
|
|
@@ -53,7 +51,7 @@ export class HTMLPerspectiveViewerDatagridToolbarElement extends HTMLElement {
|
|
|
53
51
|
</div>
|
|
54
52
|
`;
|
|
55
53
|
|
|
56
|
-
const viewer = this.parentElement as
|
|
54
|
+
const viewer = this.parentElement as HTMLPerspectiveViewerElement;
|
|
57
55
|
const plugin = this.previousElementSibling as DatagridPluginElement;
|
|
58
56
|
|
|
59
57
|
plugin._scroll_lock = this.shadowRoot!.querySelector(
|
|
@@ -66,6 +64,7 @@ export class HTMLPerspectiveViewerDatagridToolbarElement extends HTMLElement {
|
|
|
66
64
|
plugin._edit_button = this.shadowRoot!.querySelector(
|
|
67
65
|
"#edit_mode",
|
|
68
66
|
) as HTMLElement;
|
|
67
|
+
|
|
69
68
|
plugin._edit_button.addEventListener("click", () => {
|
|
70
69
|
toggle_edit_mode.call(plugin);
|
|
71
70
|
plugin.regular_table.draw();
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { FormatterCache, Formatter } from "./formatter_cache.js";
|
|
14
14
|
import type { DatagridModel, ColumnsConfig, ColumnConfig } from "../types.js";
|
|
15
|
-
import { ColumnType } from "@perspective-dev/client";
|
|
15
|
+
import type { ColumnType } from "@perspective-dev/client";
|
|
16
16
|
|
|
17
17
|
const FORMAT_CACHE = new FormatterCache();
|
|
18
18
|
const MAX_BAR_WIDTH_PCT = 1;
|
|
@@ -44,7 +44,11 @@ export function format_cell(
|
|
|
44
44
|
const plugin: ColumnConfig = plugins[title] || {};
|
|
45
45
|
const is_numeric = type === "integer" || type === "float";
|
|
46
46
|
|
|
47
|
-
if (
|
|
47
|
+
if (
|
|
48
|
+
is_numeric &&
|
|
49
|
+
(plugin?.number_fg_mode === "bar" ||
|
|
50
|
+
plugin?.number_fg_mode === "label-bar")
|
|
51
|
+
) {
|
|
48
52
|
const a = Math.max(
|
|
49
53
|
0,
|
|
50
54
|
Math.min(
|
|
@@ -54,15 +58,30 @@ export function format_cell(
|
|
|
54
58
|
),
|
|
55
59
|
);
|
|
56
60
|
|
|
57
|
-
const
|
|
58
|
-
const anchor = (val as number) >= 0 ? "left" : "right";
|
|
61
|
+
const anchor = (val as number) >= 0 ? "" : "justify-self:flex-end;";
|
|
59
62
|
const pct = (a * 100).toFixed(2);
|
|
60
|
-
div.setAttribute(
|
|
61
|
-
"style",
|
|
62
|
-
`width:calc(${pct}% - 4px);position:absolute;${anchor}:2px;height:80%;top:10%;pointer-events:none;`,
|
|
63
|
-
);
|
|
64
63
|
|
|
65
|
-
|
|
64
|
+
if (plugin.number_fg_mode === "bar") {
|
|
65
|
+
const div = this._div_factory.get();
|
|
66
|
+
div.className = "psp-bar";
|
|
67
|
+
div.setAttribute(
|
|
68
|
+
"style",
|
|
69
|
+
`${anchor}width:${pct}%;height:80%;top:10%;pointer-events:none;background:var(--psp-label-bar-color)`,
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
return div;
|
|
73
|
+
} else {
|
|
74
|
+
const formatter = FORMAT_CACHE.get(type, plugin);
|
|
75
|
+
const label = formatter ? formatter.format(val) : (val as string);
|
|
76
|
+
|
|
77
|
+
const div = this._div_factory.get();
|
|
78
|
+
div.className = "psp-bar";
|
|
79
|
+
div.setAttribute(
|
|
80
|
+
"style",
|
|
81
|
+
`--label:"${label}";${anchor}width:${pct}%;height:80%;top:10%;pointer-events:none;background:var(--psp-label-bar-color)`,
|
|
82
|
+
);
|
|
83
|
+
return div;
|
|
84
|
+
}
|
|
66
85
|
} else if (plugin?.format === "link" && type === "string") {
|
|
67
86
|
const anchor = document.createElement("a");
|
|
68
87
|
anchor.setAttribute("href", val as string);
|
|
@@ -27,7 +27,7 @@ export function* format_tree_header_row_path(
|
|
|
27
27
|
): Generator<RowHeaderCell[]> {
|
|
28
28
|
const plugins: ColumnsConfig =
|
|
29
29
|
(regularTable as any)[PRIVATE_PLUGIN_SYMBOL] || {};
|
|
30
|
-
for (
|
|
30
|
+
for (const path of paths) {
|
|
31
31
|
const fullPath: unknown[] = ["TOTAL", ...path];
|
|
32
32
|
const last = fullPath[fullPath.length - 1];
|
|
33
33
|
let newPath: RowHeaderCell[] = fullPath
|
|
@@ -61,7 +61,7 @@ export function* format_flat_header_row_path(
|
|
|
61
61
|
const plugins: ColumnsConfig =
|
|
62
62
|
(regularTable as any)[PRIVATE_PLUGIN_SYMBOL] || {};
|
|
63
63
|
|
|
64
|
-
for (
|
|
64
|
+
for (const path of paths) {
|
|
65
65
|
yield path.map((part, i) =>
|
|
66
66
|
format_cell.call(this, row_headers[i], part, plugins, true),
|
|
67
67
|
) as RowHeaderCell[];
|
|
@@ -10,7 +10,12 @@
|
|
|
10
10
|
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
|
11
11
|
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
|
12
12
|
|
|
13
|
-
import { ColumnType } from "@perspective-dev/client";
|
|
13
|
+
import type { ColumnType } from "@perspective-dev/client";
|
|
14
|
+
import {
|
|
15
|
+
createDateFormatter,
|
|
16
|
+
createDatetimeFormatter,
|
|
17
|
+
createNumberFormatter,
|
|
18
|
+
} from "@perspective-dev/viewer/src/ts/column-format.js";
|
|
14
19
|
import type { ColumnConfig } from "../types.js";
|
|
15
20
|
|
|
16
21
|
export interface Formatter {
|
|
@@ -26,30 +31,6 @@ class BooleanFormatter implements Formatter {
|
|
|
26
31
|
// PluginConfig is a subset of ColumnConfig with the formatting properties
|
|
27
32
|
type PluginConfig = Pick<ColumnConfig, "date_format" | "number_format">;
|
|
28
33
|
|
|
29
|
-
const LEGACY_CONFIG: Record<
|
|
30
|
-
string,
|
|
31
|
-
{ format: Intl.NumberFormatOptions | Intl.DateTimeFormatOptions }
|
|
32
|
-
> = {
|
|
33
|
-
float: {
|
|
34
|
-
format: {
|
|
35
|
-
style: "decimal",
|
|
36
|
-
minimumFractionDigits: 2,
|
|
37
|
-
maximumFractionDigits: 2,
|
|
38
|
-
},
|
|
39
|
-
},
|
|
40
|
-
datetime: {
|
|
41
|
-
format: {
|
|
42
|
-
dateStyle: "short",
|
|
43
|
-
timeStyle: "medium",
|
|
44
|
-
} as Intl.DateTimeFormatOptions,
|
|
45
|
-
},
|
|
46
|
-
date: {
|
|
47
|
-
format: {
|
|
48
|
-
dateStyle: "short",
|
|
49
|
-
} as Intl.DateTimeFormatOptions,
|
|
50
|
-
},
|
|
51
|
-
};
|
|
52
|
-
|
|
53
34
|
export class FormatterCache {
|
|
54
35
|
private _formatters: Map<string, Formatter | false>;
|
|
55
36
|
|
|
@@ -61,89 +42,21 @@ export class FormatterCache {
|
|
|
61
42
|
_type: ColumnType,
|
|
62
43
|
plugin: PluginConfig,
|
|
63
44
|
): Intl.DateTimeFormat {
|
|
64
|
-
|
|
65
|
-
const options: Intl.DateTimeFormatOptions = {
|
|
66
|
-
timeZone: plugin.date_format?.timeZone,
|
|
67
|
-
dateStyle:
|
|
68
|
-
plugin.date_format?.dateStyle === "disabled"
|
|
69
|
-
? undefined
|
|
70
|
-
: (plugin.date_format?.dateStyle ?? "short"),
|
|
71
|
-
timeStyle:
|
|
72
|
-
plugin.date_format?.timeStyle === "disabled"
|
|
73
|
-
? undefined
|
|
74
|
-
: (plugin.date_format?.timeStyle ?? "medium"),
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
return new Intl.DateTimeFormat(
|
|
78
|
-
navigator.languages as string[],
|
|
79
|
-
options,
|
|
80
|
-
);
|
|
81
|
-
} else {
|
|
82
|
-
const options: Intl.DateTimeFormatOptions = {
|
|
83
|
-
timeZone: plugin.date_format?.timeZone,
|
|
84
|
-
hour12: plugin.date_format?.hour12 ?? true,
|
|
85
|
-
fractionalSecondDigits:
|
|
86
|
-
plugin.date_format?.fractionalSecondDigits,
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
if (plugin.date_format?.year !== "disabled") {
|
|
90
|
-
options.year = plugin.date_format?.year ?? "2-digit";
|
|
91
|
-
}
|
|
92
|
-
if (plugin.date_format?.month !== "disabled") {
|
|
93
|
-
options.month = plugin.date_format?.month ?? "numeric";
|
|
94
|
-
}
|
|
95
|
-
if (plugin.date_format?.day !== "disabled") {
|
|
96
|
-
options.day = plugin.date_format?.day ?? "numeric";
|
|
97
|
-
}
|
|
98
|
-
if (
|
|
99
|
-
plugin.date_format?.weekday &&
|
|
100
|
-
plugin.date_format?.weekday !== "disabled"
|
|
101
|
-
) {
|
|
102
|
-
options.weekday = plugin.date_format.weekday;
|
|
103
|
-
}
|
|
104
|
-
if (plugin.date_format?.hour !== "disabled") {
|
|
105
|
-
options.hour = plugin.date_format?.hour ?? "numeric";
|
|
106
|
-
}
|
|
107
|
-
if (plugin.date_format?.minute !== "disabled") {
|
|
108
|
-
options.minute = plugin.date_format?.minute ?? "numeric";
|
|
109
|
-
}
|
|
110
|
-
if (plugin.date_format?.second !== "disabled") {
|
|
111
|
-
options.second = plugin.date_format?.second ?? "numeric";
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return new Intl.DateTimeFormat(
|
|
115
|
-
navigator.languages as string[],
|
|
116
|
-
options,
|
|
117
|
-
);
|
|
118
|
-
}
|
|
45
|
+
return createDatetimeFormatter(plugin.date_format);
|
|
119
46
|
}
|
|
120
47
|
|
|
121
48
|
private create_date_formatter(
|
|
122
49
|
_type: ColumnType,
|
|
123
50
|
plugin: PluginConfig,
|
|
124
51
|
): Intl.DateTimeFormat {
|
|
125
|
-
|
|
126
|
-
timeZone: "utc",
|
|
127
|
-
dateStyle:
|
|
128
|
-
plugin.date_format?.dateStyle === "disabled"
|
|
129
|
-
? undefined
|
|
130
|
-
: (plugin.date_format?.dateStyle ?? "short"),
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
return new Intl.DateTimeFormat(
|
|
134
|
-
navigator.languages as string[],
|
|
135
|
-
options,
|
|
136
|
-
);
|
|
52
|
+
return createDateFormatter(plugin.date_format);
|
|
137
53
|
}
|
|
138
54
|
|
|
139
55
|
private create_number_formatter(
|
|
140
56
|
type: ColumnType,
|
|
141
57
|
plugin: PluginConfig,
|
|
142
58
|
): Intl.NumberFormat {
|
|
143
|
-
|
|
144
|
-
plugin.number_format ??
|
|
145
|
-
(LEGACY_CONFIG[type]?.format as Intl.NumberFormatOptions);
|
|
146
|
-
return new Intl.NumberFormat(navigator.languages as string[], format);
|
|
59
|
+
return createNumberFormatter(type, plugin.number_format);
|
|
147
60
|
}
|
|
148
61
|
|
|
149
62
|
private create_boolean_formatter(
|