Skip to content

Commit

Permalink
Datagrid column-style menu
Browse files Browse the repository at this point in the history
  • Loading branch information
texodus committed Apr 20, 2021
1 parent e73ed41 commit b6d0d8d
Show file tree
Hide file tree
Showing 18 changed files with 555 additions and 61 deletions.
4 changes: 2 additions & 2 deletions packages/perspective-viewer-datagrid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"test:build": "cpx \"test/html/*\" dist/umd",
"test:run": "jest --rootDir=. --config=../perspective-test/jest.config.js --color",
"test": "npm-run-all test:build test:run",
"watch:babel": "babel src/js --source-maps --watch --out-dir dist/esm",
"watch:webpack": "webpack --watch --color --config src/config/umd.config.js",
"watch:rollup": "rollup --watch --config src/config/rollup.config.js",
"watch:webpack": "webpack --watch --color --config src/config/webpack.config.js",
"watch": "npm-run-all -p watch:*",
"clean": "rimraf dist",
"clean:screenshots": "rimraf \"screenshots/**/*.@(failed|diff).png\""
Expand Down
30 changes: 30 additions & 0 deletions packages/perspective-viewer-datagrid/src/js/color_utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/******************************************************************************
*
* Copyright (c) 2017, the Perspective Authors.
*
* This file is part of the Perspective library, distributed under the terms of
* the Apache License 2.0. The full license can be found in the LICENSE file.
*
*/

export const PLUGIN_SYMBOL = Symbol("Plugin Symbol");

export function hexToRgb(hex) {
var bigint = parseInt(hex.trim().slice(1), 16);
var r = (bigint >> 16) & 255;
var g = (bigint >> 8) & 255;
var b = bigint & 255;
return [r, g, b];
}

export function rgbaToRgb([r, g, b, a], source = [255, 255, 255]) {
function f(i, c) {
return ((1 - a) * (source[i] / 255) + a * (c / 255)) * 255;
}

return [f(0, r), f(1, g), f(2, b)];
}

export function infer_foreground_from_background([r, g, b]) {
return Math.sqrt(r * r * 0.299 + g * g * 0.587 + b * b * 0.114) > 130 ? "#161616" : "#ffffff";
}
15 changes: 13 additions & 2 deletions packages/perspective-viewer-datagrid/src/js/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {configureRowSelectable, deselect} from "./row_selection.js";
import {configureClick} from "./click.js";
import {configureEditable} from "./editing.js";
import {configureSortable} from "./sorting.js";
import {PLUGIN_SYMBOL} from "./plugin_menu.js";

const VIEWER_MAP = new WeakMap();
const INSTALLED = new WeakMap();
Expand Down Expand Up @@ -159,9 +160,19 @@ class DatagridPlugin {
}
}

static save() {}
static save() {
if (VIEWER_MAP.has(this._datavis)) {
const datagrid = VIEWER_MAP.get(this._datavis);
if (datagrid[PLUGIN_SYMBOL]) {
return JSON.parse(JSON.stringify(datagrid[PLUGIN_SYMBOL]));
}
}
}

static restore() {}
static restore(token) {
const datagrid = get_or_create_datagrid(this, this._datavis);
datagrid[PLUGIN_SYMBOL] = token;
}
}

/**
Expand Down
254 changes: 254 additions & 0 deletions packages/perspective-viewer-datagrid/src/js/plugin_menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/******************************************************************************
*
* Copyright (c) 2017, the Perspective Authors.
*
* This file is part of the Perspective library, distributed under the terms of
* the Apache License 2.0. The full license can be found in the LICENSE file.
*
*/

import {hexToRgb} from "./color_utils.js";

export const PLUGIN_SYMBOL = Symbol("Plugin Symbol");

class PluginMenu extends HTMLElement {
constructor() {
super();
}

open(pv_plugin, regularTable, target, column_meta) {
this._pv_plugin = pv_plugin;
this._regularTable = regularTable;

const rect = target.getBoundingClientRect();
this.setAttribute("tabindex", "0");
this.className = "column-plugin-menu";
this.attachShadow({mode: "open"});
const pset = regularTable[PLUGIN_SYMBOL] || {};
const meta = regularTable.getMeta(target);
const column_name = meta.column_header[meta.column_header.length - 1];
const column_type = pv_plugin._schema[column_name];
const column_styles = (pset[column_name] = pset[column_name] || {});
const default_prec = column_type === "float" ? 2 : 0;
const is_numeric = column_type === "float" || column_type === "integer";
if (!is_numeric) {
this.drop();
return;
}

const fixed = (column_styles.fixed + 1 || default_prec + 1) - 1;
const fixed_example = fixed > 0 ? "0." + "0".repeat(fixed - 1) + "1" : "1";

let html = `
<style>
:host {
position: absolute;
left: ${rect.left}px;
top: ${rect.bottom}px;
padding: 6px;
outline: none;
font-family: "Open Sans";
font-size: 12px;
font-weight: 300;
border: inherit;
box-shadow: 0 2px 4px 0 rgb(0 0 0 / 10%);
}
:host input.parameter {
max-width: 65px;
background: none;
color: inherit;
border: 0px solid transparent;
border-bottom-width: 1px;
border-color: var(--input--border-color);
outline: none;
}
:host input[type=radio], :host input[type=checkbox], :host > div > div > span:first-child {
width: 24px;
margin: 0;
}
:host > div > div {
display: flex;
align-items: center;
height: 24px;
}
:host div.section {
margin-bottom: 8px;
}
:host input[type=color] {
width: 24px;
}
:host .operator {
font-family: "Roboto Mono", monospace;
white-space:pre;
}
:host input.parameter[disabled] {
opacity: 0.5;
}
</style>
<div>
<div>
<input type="checkbox" checked disabled>
</input>
<span id="fixed-examples">Prec ${fixed_example}</span>
</div>
<div class="section">
<span></span>
<input
id="fixed-param"
value="${fixed}"
class="parameter"
${is_numeric ? "" : "disabled"}
type="number"
min="0"
step="1">
</input>
</div>
<div>
<input
id="color-selected"
type="checkbox"
${column_styles.pos_color !== undefined ? "checked" : ""}>
</input>
<input
id="color-param"
value="${column_styles?.pos_color !== undefined ? column_styles.pos_color[0] : pv_plugin._pos_color[0]}"
${column_styles.pos_color !== undefined ? "" : "disabled"}
class="parameter"
type="color">
</input>
<span class="operator"> + / - </span>
<input
id="neg-color-param"
value="${column_styles?.neg_color !== undefined ? column_styles.neg_color[0] : pv_plugin._neg_color[0]}"
${column_styles.neg_color !== undefined ? "" : "disabled"}
class="parameter"
type="color">
</input>
</div>
<div>
<input
id="color-mode-1"
name="color-mode"
type="radio"
value="foreground"
class="parameter" \
${column_styles.color_mode !== "background" && column_styles.color_mode !== "gradient" ? "checked" : ""}>
</input>
<span>Foreground</span>
</div>
<div>
<input
id="color-mode-2"
name="color-mode"
type="radio"
value="background"
class="parameter"
${column_styles.color_mode === "background" ? "checked" : ""}>
</input>
<span>Background</span>
</div>
<div>
<input
id="color-mode-3"
name="color-mode"
type="radio"
value="gradient"
class="parameter"
${column_styles.color_mode === "gradient" ? "checked" : ""}>
</input>
<span>Gradient</span>
</div>
<div>
<span></span>
<input
id="gradient-param"
value="${Math.ceil(column_styles.opacity + 1 || column_meta[column_name] + 1) - 1}"
class="parameter"
${column_styles.opacity !== undefined ? "" : "disabled"}
type="number"
min="0">
</input>
</div>
</div>
`;

this.shadowRoot.innerHTML += html;
document.body.appendChild(this);
this.shadowRoot.addEventListener(
"input",
(this._changeListener = () => {
const config = {};
const fixed = parseInt(this.shadowRoot.querySelector(`#fixed-param`).value);
const fixed_example = fixed > 0 ? "Prec 0." + "0".repeat(fixed - 1) + "1" : "Prec 1";
this.shadowRoot.querySelector(`#fixed-examples`).textContent = fixed_example;
if (isFinite(fixed) && fixed !== default_prec && fixed > -1) {
config.fixed = fixed;
} else {
// TODO validation
}

if (!this.shadowRoot.querySelector(`#color-mode-1`).checked) {
if (this.shadowRoot.querySelector(`#color-mode-2`).checked) {
config.color_mode = "background";
} else {
config.color_mode = "gradient";
}
}

const opacity = this.shadowRoot.querySelector(`#gradient-param`);
if (config.color_mode === "gradient") {
opacity.disabled = false;
config.opacity = parseInt(opacity.value);
} else {
opacity.value = Math.ceil(column_meta[column_name]);
opacity.disabled = true;
}

const is_color = this.shadowRoot.querySelector(`#color-selected`).checked;
const color = this.shadowRoot.querySelector(`#color-param`);
const neg_color = this.shadowRoot.querySelector(`#neg-color-param`);
if (is_color) {
color.disabled = neg_color.disabled = false;
config.pos_color = [color.value].concat(hexToRgb(color.value));
config.neg_color = [neg_color.value].concat(hexToRgb(neg_color.value));
} else {
color.disabled = neg_color.disabled = true;
}

regularTable[PLUGIN_SYMBOL] = regularTable[PLUGIN_SYMBOL] || {};
regularTable[PLUGIN_SYMBOL][column_name] = config;
regularTable.draw({preserve_width: true});
regularTable.parentElement.dispatchEvent(new Event("perspective-config-update"));
})
);

this.addEventListener("blur", (this._drop_handler = this.drop.bind(this)));
regularTable.addEventListener("regular-table-scroll", this._drop_handler);
this.focus();
}

drop() {
if (document.body.contains(this)) {
this.removeEventListener("blur", this._drop_handler);
this._regularTable?.removeEventListener("regular-table-scroll", this._drop_handler);
document.body.removeChild(this);
this.shadowRoot.removeEventListener("input", this._changeListener);
}
this._pv_plugin._open_column_styles_menu.pop();
this._regularTable.draw();
}
}

customElements.define("perspective-viewer-datagrid-plugin-menu", PluginMenu);

export function activate_plugin_menu(regularTable, target, meta) {
const menu = document.createElement("perspective-viewer-datagrid-plugin-menu");
menu.open(this, regularTable, target, meta);
}
Loading

0 comments on commit b6d0d8d

Please sign in to comment.