diff --git a/.gitignore b/.gitignore
index bcab7c7d91..896bde24da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -117,3 +117,4 @@ website/i18n/*
.idea
cmake-build-debug
+.ipynb_checkpoints
diff --git a/packages/perspective-viewer-highcharts/src/js/config.js b/packages/perspective-viewer-highcharts/src/js/config.js
index e28b93730b..47b55c78e3 100644
--- a/packages/perspective-viewer-highcharts/src/js/config.js
+++ b/packages/perspective-viewer-highcharts/src/js/config.js
@@ -118,7 +118,7 @@ export function default_config(aggregates, mode) {
const that = this,
config = that._view._config;
- const axis_titles = get_axis_titles(config.aggregate, config.sort);
+ const axis_titles = get_axis_titles(config.aggregate);
const pivot_titles = get_pivot_titles(config.row_pivot, config.column_pivot);
return {
@@ -248,14 +248,12 @@ export function default_config(aggregates, mode) {
}
}
-function get_axis_titles(aggs, sort) {
- const sort_titles = sort.map(row => row[0]);
+function get_axis_titles(aggs) {
let titles = [];
for (let i = 0; i < aggs.length; i++) {
let axis_title = aggs[i].column;
- if(!sort_titles.includes(axis_title))
- titles.push(aggs[i].column);
+ titles.push(axis_title);
}
return titles;
}
diff --git a/packages/perspective-viewer-highcharts/src/js/series.js b/packages/perspective-viewer-highcharts/src/js/series.js
index 9630fd7715..c6ca08bc82 100644
--- a/packages/perspective-viewer-highcharts/src/js/series.js
+++ b/packages/perspective-viewer-highcharts/src/js/series.js
@@ -7,10 +7,7 @@
*
*/
-/******************************************************************************
- *
- * Y
- */
+import {COLUMN_SEPARATOR_STRING} from "@jpmorganchase/perspective/src/js/defaults.js";
function row_to_series(series, sname, gname) {
let s;
@@ -89,12 +86,12 @@ class ColumnsIterator {
for (let row of this.rows) {
if (this.columns === undefined) {
this.columns = Object.keys(row).filter(prop => {
- let cname = prop.split(',');
+ let cname = prop.split(COLUMN_SEPARATOR_STRING);
cname = cname[cname.length - 1];
return prop !== "__ROW_PATH__" && this.hidden.indexOf(cname) === -1;
});
this.is_stacked = this.columns.map(value =>
- value.substr(value.lastIndexOf(',') + 1, value.length)
+ value.substr(value.lastIndexOf(COLUMN_SEPARATOR_STRING) + 1, value.length)
).filter((value, index, self) =>
self.indexOf(value) === index
).length > 1;
@@ -111,7 +108,7 @@ export function make_y_data(js, pivots, hidden) {
for (let row of rows2) {
for (let prop of rows2.columns) {
- let sname = prop.split(',');
+ let sname = prop.split(COLUMN_SEPARATOR_STRING);
let gname = sname[sname.length - 1];
if (rows2.is_stacked) {
sname = sname.join(", ") || gname;
@@ -226,12 +223,12 @@ export function make_xy_data(js, schema, columns, pivots, col_pivots, hidden) {
} else {
let prev, group = [], s;
let cols = Object.keys(js[0]).filter(prop => {
- let cname = prop.split(',');
+ let cname = prop.split(COLUMN_SEPARATOR_STRING);
cname = cname[cname.length - 1];
return prop !== "__ROW_PATH__" && hidden.indexOf(cname) === -1;
});
for (let prop of cols) {
- let column_levels = prop.split(',');
+ let column_levels = prop.split(COLUMN_SEPARATOR_STRING);
let group_name = column_levels.slice(0, column_levels.length - 1).join(",") || " ";
if (prev === undefined) {
prev = group_name;
@@ -382,7 +379,7 @@ function make_levels(row_pivots) {
function make_configs(series, levels) {
let configs = [];
for (let data of series) {
- let title = data.name.split(',');
+ let title = data.name.split(COLUMN_SEPARATOR_STRING);
configs.push({
layoutAlgorithm: 'squarified',
allowDrillToNode: true,
@@ -409,7 +406,7 @@ export function make_tree_data(js, row_pivots, hidden, aggregates, leaf_only) {
for (let idx = 0; idx < rows2.columns.length; idx++) {
let prop = rows2.columns[idx];
- let sname = prop.split(',');
+ let sname = prop.split(COLUMN_SEPARATOR_STRING);
let gname = sname[sname.length - 1];
sname = sname.slice(0, sname.length - 1).join(", ") || " ";
if (idx % aggregates.length === 0) {
diff --git a/packages/perspective-viewer-highcharts/src/js/tooltip.js b/packages/perspective-viewer-highcharts/src/js/tooltip.js
index 3e95e88712..105009801e 100644
--- a/packages/perspective-viewer-highcharts/src/js/tooltip.js
+++ b/packages/perspective-viewer-highcharts/src/js/tooltip.js
@@ -101,10 +101,19 @@ export function format_tooltip(context, type, schema, axis_titles, pivot_titles)
function collate_single_value(title, raw_value, schema) {
const type = get_axis_type(title, schema);
const formatted_value = format_value(raw_value, type);
+
+ /* columns in aggregate AND in sort need to show up, but
+ * columns not in aggregate but NOT in sort need to hide */
+ if (formatted_value === 'NaN' || formatted_value === null || formatted_value === undefined)
+ return '';
+
return `${ title }: ${ formatted_value }
`;
}
function collate_multiple_values(titles, values) {
+ if (values.length <= 0)
+ return '';
+
let output = [];
for (let i = 0; i < titles.length; i++) {
output.push(`${ titles[i] }: ${ values[i] }
`)
diff --git a/packages/perspective-viewer-hypergrid/src/js/psp-to-hypergrid.js b/packages/perspective-viewer-hypergrid/src/js/psp-to-hypergrid.js
index 182bb01ed7..e95edcd915 100644
--- a/packages/perspective-viewer-hypergrid/src/js/psp-to-hypergrid.js
+++ b/packages/perspective-viewer-hypergrid/src/js/psp-to-hypergrid.js
@@ -7,11 +7,13 @@
*
*/
+import {COLUMN_SEPARATOR_STRING} from "@jpmorganchase/perspective/src/js/defaults.js";
+
const TREE_COLUMN_INDEX = require('fin-hypergrid/src/behaviors/Behavior').prototype.treeColumnIndex;
function filter_hidden(hidden, json) {
for (let key of Object.keys(json)) {
- const split_key = key.split(',');
+ const split_key = key.split(COLUMN_SEPARATOR_STRING);
if (hidden.indexOf(split_key[split_key.length - 1].trim()) >= 0) {
delete json[key];
}
@@ -36,7 +38,7 @@ function psp2hypergrid(data, hidden, schema, tschema, row_pivots) {
var is_tree = !!row_pivots.length;
var flat_columns = Object.keys(data).filter(row => row !== '__ROW_PATH__');
- var columnPaths = flat_columns.map(row => row.split(','));
+ var columnPaths = flat_columns.map(row => row.split(COLUMN_SEPARATOR_STRING));
let rows = [];
diff --git a/packages/perspective-viewer/src/html/computed_column.html b/packages/perspective-viewer/src/html/computed_column.html
index 8cd120a53b..076e8f204c 100644
--- a/packages/perspective-viewer/src/html/computed_column.html
+++ b/packages/perspective-viewer/src/html/computed_column.html
@@ -14,21 +14,22 @@
${csv}`;
timer();
},
@@ -232,8 +224,9 @@ function column_visibility_clicked(ev) {
}
} else {
// check if we're manipulating computed column input
- if(ev.path[1].id === 'psp-cc-computation__input-column') {
- this._computed_column._clear_input_column();
+ if(ev.path[1].classList.contains('psp-cc-computation__input-column')) {
+ // this._computed_column._register_inputs();
+ this._computed_column.deselect_column(ev.currentTarget.getAttribute('name'));
this._update_column_view();
return;
}
@@ -319,14 +312,14 @@ function set_aggregate_attribute(aggs) {
function _format_computed_data(cc) {
return {
column_name: cc[0],
- input_column: cc[1].input_column,
+ input_columns: cc[1].input_columns,
input_type: cc[1].input_type,
computation: cc[1].computation,
type: cc[1].type
};
}
-async function loadTable(table) {
+async function loadTable(table, redraw = true) {
this.querySelector('#app').classList.add('hide_message');
this.setAttribute('updating', true);
@@ -431,7 +424,6 @@ async function loadTable(table) {
let aggregate = aggregates
.filter(a => a.column === cc_data.column_name)
.map(a => a.op)[0];
- console.log(aggregate, cc_data);
let row = new_row.call(this, cc_data.column_name, cc_data.type, aggregate, null, null, cc_data);
this._inactive_columns.appendChild(row);
}
@@ -458,7 +450,6 @@ async function loadTable(table) {
// fixme better approach please
for (let cc of computed_cols) {
let cc_data = _format_computed_data(cc);
- console.log(aggregates);
let aggregate = aggregates
.filter(a => a.column === cc_data.column_name)
.map(a => a.op)[0];
@@ -485,8 +476,7 @@ async function loadTable(table) {
this.querySelector('#side_panel__actions').style.visibility = "visible";
this.filters = this.getAttribute('filters');
-
- this._debounce_update();
+ this._debounce_update(redraw);
}
function new_row(name, type, aggregate, filter, sort, computed) {
@@ -586,7 +576,7 @@ class CancelTask {
}
-function update() {
+function update(redraw = true) {
if (!this._table) return;
let row_pivots = this._view_columns('#row_pivots perspective-row');
let column_pivots = this._view_columns('#column_pivots perspective-row');
@@ -639,27 +629,34 @@ function update() {
});
const timer = this._render_time();
- this._render_count = (this._render_count || 0) + 1;
- if (this._task) {
- this._task.cancel();
- }
- const task = this._task = new CancelTask(() => {
- this._render_count--;
- });
- task.initial = true;
-
- this._plugin.create.call(this, this._datavis, this._view, task).catch(err => {
- console.warn(err);
- }).finally(() => {
- if (!this.hasAttribute('render_time')) {
- this.dispatchEvent(new Event('perspective-view-update'));
+ if (redraw) {
+ this._render_count = (this._render_count || 0) + 1;
+ if (this._task) {
+ this._task.cancel();
}
+ const task = this._task = new CancelTask(() => {
+ this._render_count--;
+ });
+ task.initial = true;
+
+ this._plugin.create.call(this, this._datavis, this._view, task).catch(err => {
+ console.warn(err);
+ }).finally(() => {
+ if (!this.hasAttribute('render_time')) {
+ this.dispatchEvent(new Event('perspective-view-update'));
+ }
+ timer();
+ task.cancel();
+ if (this._render_count === 0) {
+ this.removeAttribute('updating');
+ }
+ });
+ } else {
timer();
- task.cancel();
if (this._render_count === 0) {
this.removeAttribute('updating');
}
- });
+ }
}
/******************************************************************************
@@ -843,19 +840,22 @@ class ViewPrivate extends HTMLElement {
}
}
+ // Computed columns
_open_computed_column(event) {
- const data = event.detail;
+ //const data = event.detail;
event.stopImmediatePropagation();
- if (event.type === 'perspective-computed-column-edit') {
+ /*if (event.type === 'perspective-computed-column-edit') {
this._computed_column._edit_computed_column(data);
- }
+ }*/
this._computed_column.style.display = 'flex';
this._side_panel_actions.style.display = 'none';
}
_set_computed_column_input(event) {
- this._computed_column_input_column.appendChild(
- new_row.call(this, event.detail.name, event.detail.type));
+ event.detail.target.appendChild(
+ new_row.call(this,
+ event.detail.column.name,
+ event.detail.column.type));
this._update_column_view();
}
@@ -873,19 +873,16 @@ class ViewPrivate extends HTMLElement {
computation: data.computation,
column: computed_column_name,
func: data.computation.func,
- inputs: [data.input_column.name],
- input_type: data.input_column.type,
+ inputs: data.input_columns.map(col => col.name),
+ input_type: data.computation.input_type,
type: data.computation.return_type,
}];
const table = this._table.add_computed(params);
- loadTable.call(this, table).then(() => {
+ loadTable.call(this, table, false).then(() => {
this._update_column_view();
//this.dispatchEvent(new Event('perspective-view-update'));
-
- this._computed_column.style.display = 'none';
- this._side_panel_actions.style.display = 'flex';
- this._computed_column._clear_state();
+ this._computed_column._close_computed_column();
});
});
}
@@ -910,7 +907,7 @@ class ViewPrivate extends HTMLElement {
this._side_panel_actions = this.querySelector('#side_panel__actions');
this._add_computed_column = this.querySelector('#add-computed-column');
this._computed_column = this.querySelector('perspective-computed-column');
- this._computed_column_input_column = this._computed_column.querySelector('#psp-cc-computation__input-column');
+ this._computed_column_inputs = this._computed_column.querySelector('#psp-cc-computation-inputs');
this._inner_drop_target = this.querySelector('#drop_target_inner');
this._drop_target = this.querySelector('#drop_target');
this._config_button = this.querySelector('#config_button');
@@ -969,9 +966,9 @@ class ViewPrivate extends HTMLElement {
_register_debounce_instance() {
const _update = _.debounce(update.bind(this), 10);
- this._debounce_update = () => {
+ this._debounce_update = (redraw) => {
this.setAttribute('updating', true);
- _update();
+ _update(redraw);
}
}
}
diff --git a/packages/perspective-viewer/src/less/computed_column.less b/packages/perspective-viewer/src/less/computed_column.less
index e2607ed56d..15291c55e0 100644
--- a/packages/perspective-viewer/src/less/computed_column.less
+++ b/packages/perspective-viewer/src/less/computed_column.less
@@ -121,13 +121,35 @@ perspective-computed-column {
-moz-appearance: none;
appearance: none;
background: #fff;
- border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid @border-color;
font-size: 12px;
padding: 3px 4px;
flex-basis: 500px;
}
}
+ #psp-cc-name {
+ background-color: #eee;
+ border-bottom: 1px solid @border-color;
+ font-size: 12px;
+ color: #333;
+ padding-left: 2px;
+ width: 100%;
+
+ &:empty:before{
+ content: "New Column";
+ color: @border-color;
+ }
+
+ * {
+ display: inline;
+ }
+
+ br {
+ display: none;
+ }
+ }
+
.column_row() {
font-family: monospace;
display: flex;
@@ -170,7 +192,11 @@ perspective-computed-column {
}
}
- #psp-cc-computation__input-column {
+ #psp-cc-computation-inputs {
+
+ }
+
+ .psp-cc-computation__input-column {
display: flex;
align-items: center;
background-color: rgba(255,255,255,0.3);
@@ -209,7 +235,7 @@ perspective-computed-column {
}
}
- #psp-cc-computation {
+ .psp-cc__content {
align-items: center;
display: flex;
margin-top: 5px;
@@ -249,34 +275,13 @@ perspective-computed-column {
color: #666;
}
- #psp-cc-name {
- background-color: #eee;
- border-bottom: none;
- font-size: 12px;
- color: #333;
- padding-left: 2px;
-
- * {
- display: inline;
- }
-
- br {
- display: none;
- }
- }
-
- #psp-cc-name:empty:before{
- content: "New Column";
- color: #ccc;
- }
-
#psp-cc-computation__drop-target-hover {
display: none;
}
.psp-cc__button {
border-radius: 0;
- border-bottom: 1px solid #ccc;
+ border-bottom: 1px solid @border-color;
border-left: 0;
border-right: 0;
border-top: 0;
diff --git a/packages/perspective-viewer/src/less/default.less b/packages/perspective-viewer/src/less/default.less
index 7039dd35a6..26321982a7 100644
--- a/packages/perspective-viewer/src/less/default.less
+++ b/packages/perspective-viewer/src/less/default.less
@@ -121,14 +121,20 @@ perspective-viewer {
}
#app.columns_horizontal {
+ #side_panel {
+ max-width: none;
+ }
+
#columns_container {
flex-direction: row-reverse;
}
+
#active_columns, #inactive_columns {
display: flex;
flex-direction: column;
flex: 1 !important;
}
+
#active_columns {
padding-right: 8px;
perspective-row {
diff --git a/packages/perspective-viewer/src/less/material.dark.less b/packages/perspective-viewer/src/less/material.dark.less
index 97a55f92ad..3879d9b69a 100644
--- a/packages/perspective-viewer/src/less/material.dark.less
+++ b/packages/perspective-viewer/src/less/material.dark.less
@@ -269,6 +269,7 @@ perspective-viewer {
}
#psp-cc-computation__input-column {
+ background: none;
border-bottom-color: @coolgrey600;
&.dropping {
diff --git a/packages/perspective-viewer/src/less/material.less b/packages/perspective-viewer/src/less/material.less
index 42626c9715..86896227db 100644
--- a/packages/perspective-viewer/src/less/material.less
+++ b/packages/perspective-viewer/src/less/material.less
@@ -394,7 +394,7 @@ perspective-viewer #app {
perspective-computed-column {
font-family: @material-sans-serif-fonts !important;
- #psp-cc-computation__input-column {
+ .psp-cc-computation__input-column {
background: none;
perspective-row {
margin-left: -24px;
diff --git a/packages/perspective-viewer/src/less/row.less b/packages/perspective-viewer/src/less/row.less
index 384fcea5ba..b7ae1221c5 100644
--- a/packages/perspective-viewer/src/less/row.less
+++ b/packages/perspective-viewer/src/less/row.less
@@ -75,7 +75,7 @@ perspective-row {
margin-left: 5px;
&:before {
- content: "\01F527"
+ //content: "\01F527"
}
}
diff --git a/packages/perspective-viewer/src/less/view.less b/packages/perspective-viewer/src/less/view.less
index e3c769df54..1150a8b505 100644
--- a/packages/perspective-viewer/src/less/view.less
+++ b/packages/perspective-viewer/src/less/view.less
@@ -78,6 +78,10 @@ perspective-viewer {
min-height: 20px;
}
+ #side_panel {
+ max-width: 30%;
+ }
+
#active_columns, #inactive_columns {
list-style: none;
padding: 5px;
@@ -211,6 +215,7 @@ perspective-viewer {
align-items: center;
justify-content: center;
}
+
#top_panel {
display: flex;
flex-wrap: wrap;
diff --git a/packages/perspective-viewer/test/js/computed_column_tests.js b/packages/perspective-viewer/test/js/computed_column_tests.js
index 672aaa2fce..1b33584f73 100644
--- a/packages/perspective-viewer/test/js/computed_column_tests.js
+++ b/packages/perspective-viewer/test/js/computed_column_tests.js
@@ -13,15 +13,9 @@ const add_computed_column = async (page) => {
await page.evaluate(element => element.setAttribute('columns', '["Row ID","Quantity"]'), viewer);
await page.click('#add-computed-column');
await page.$eval('perspective-computed-column', element => {
- element._set_state('input_column', {
- name: 'Order Date',
- type: 'date'
- });
- element._set_state('column_name', 'new_cc');
- element._set_state('name_edited', true);
- element._apply_state();
+ const columns = [{name: 'Order Date', type: 'date'}];
+ element._apply_state(columns, element.computations['day_of_week'], 'new_cc');
});
- await page.select('#psp-cc-computation__select', 'day_of_week');
await page.click('#psp-cc-button-save');
await page.waitForSelector('perspective-viewer:not([updating])');
await page.evaluate(element => element.setAttribute('aggregates', '{"new_cc":"dominant"}'), viewer);
@@ -58,33 +52,38 @@ exports.default = function() {
await page.click('#add-computed-column');
await page.$eval('perspective-computed-column', element => {
// call internal APIs to bypass drag/drop action
- element._set_state('input_column', {
- name: 'Order Date',
- type: 'date',
- });
- element._set_state('column_name', 'new_cc');
- element._set_state('name_edited', true);
- element._apply_state();
+ const columns = [{name: 'State', type: 'string'}];
+ element._apply_state(columns, element.computations['lowercase'], 'new_cc');
+ });
+ });
+
+ test.capture("setting multiple column parameters should set input.", async page => {
+ await page.click('#config_button');
+ const viewer = await page.$('perspective-viewer');
+ await page.evaluate(element => element.setAttribute('columns', '["Row ID","Quantity"]'), viewer);
+ await page.$('perspective-viewer');
+ await page.click('#add-computed-column');
+ await page.$eval('perspective-computed-column', element => {
+ const columns = [
+ {name: 'Quantity', type: 'integer'},
+ {name: 'Row ID', type: 'integer'}
+ ];
+ element._apply_state(columns, element.computations['add'], 'new_cc');
});
});
// computation
- test.capture("computations of the same type should not clear input column.", async page => {
+ test.capture("computations should clear input column.", async page => {
await page.click('#config_button');
const viewer = await page.$('perspective-viewer');
await page.evaluate(element => element.setAttribute('columns', '["Row ID","Quantity"]'), viewer);
await page.$('perspective-viewer');
await page.click('#add-computed-column');
await page.$eval('perspective-computed-column', element => {
- element._set_state('input_column', {
- name: 'Order Date',
- type: 'date',
- });
- element._set_state('column_name', 'new_cc');
- element._set_state('name_edited', true);
- element._apply_state();
+ const columns = [{name: 'State', type: 'string'}];
+ element._apply_state(columns, element.computations['lowercase'], 'new_cc');
});
- await page.select('#psp-cc-computation__select', 'day_of_week');
+ await page.select('#psp-cc-computation__select', 'subtract');
});
// save
diff --git a/packages/perspective-viewer/test/results/results.json b/packages/perspective-viewer/test/results/results.json
index 92eb4d3089..90fbcbf4f9 100644
--- a/packages/perspective-viewer/test/results/results.json
+++ b/packages/perspective-viewer/test/results/results.json
@@ -10,19 +10,20 @@
"superstore.html/sorts by a hidden column.": "57bd2b3739ab342c71d6878722db2d95",
"superstore.html/filters by a numeric column.": "dc1596fb2db82243f2200a0b9d8d35b8",
"superstore.html/shows horizontal columns on small viewports.": "a182af50a17d94732c8fa6540fc56233",
- "superstore.html/setting a valid column should set it as input.": "7ab0c6a33a4bfa826edf8d2a2f2fe5a2",
- "superstore.html/computations of the same type should not clear input column.": "f608917490556d33c44d291960163aa4",
- "superstore.html/saving a computed column should add it to inactive columns.": "60e618267929f77003dad9fd9f337577",
+ "superstore.html/setting a valid column should set it as input.": "7e9051e056a2723f886f7ecd340fd068",
+ "superstore.html/setting multiple column parameters should set input.": "162a2bd64e307b628c6e7423f55b85dc",
+ "superstore.html/computations should clear input column.": "894575c747a636c1de8c7616acb63357",
+ "superstore.html/saving a computed column should add it to inactive columns.": "a831f7659b423d5051af088cf2208e18",
"superstore.html/aggregates by computed column.": "f9ba5b75dd267d3dddca65976fbd63f2",
"superstore.html/pivots by computed column.": "d4467ad3612e90136fa391196e61189b",
- "superstore.html/sorts by computed column.": "7a4c989e68159de9f2ed70a1e6380ef9",
- "superstore.html/filters by computed column.": "12d31929677602612fdb26ee04958eef",
"superstore.html/computed column aggregates should persist.": "0dbc9d15dc1a9d62d62e94b534447787",
+ "superstore.html/sorts by computed column.": "4daa6067842eecdddb9f3c06fbbb4326",
+ "superstore.html/filters by computed column.": "129c3afb5027f2e3454addcc212cd13d",
"blank.html/Handles reloading with a schema.": "9cda0b27c92efb59599e11dffbad169e",
"superstore.html/pivots by a column.": "71c2f17f6ade6513574aa0143a84c634",
- "superstore.html/click on add computed column button opens the UI.": "4dd9778ed1f321d928570d7b3e60a473",
+ "superstore.html/click on add computed column button opens the UI.": "84bec558c77c5d54a608a135f3e7f9c2",
"superstore.html/click on close button closes the UI.": "c10ffcce1cc9d93fdbcaeccfc12c7ca7",
- "superstore.html/saving without parameters should show an error message.": "1d22204b783657c99cf03f2e862224ac",
+ "superstore.html/saving without parameters should show an error message.": "657c4319e6177c16880c38e8cccfaf30",
"superstore.html/doesn't leak tables.": "f60686399c38b6154be75b3ebed74c83",
"superstore.html/doesn't leak views when setting row pivots.": "9b51f44a534c4d49e4ad3876afacda0c",
"superstore.html/doesn't leak views when setting filters.": "e67e550265c4256b52788e2a27d572b0"
diff --git a/packages/perspective/src/js/defaults.js b/packages/perspective/src/js/defaults.js
index e18d22f200..358a40337f 100644
--- a/packages/perspective/src/js/defaults.js
+++ b/packages/perspective/src/js/defaults.js
@@ -119,6 +119,8 @@ const DATE_FILTERS = [
"!="
];
+export const COLUMN_SEPARATOR_STRING = "|";
+
export const TYPE_FILTERS = {
'string': STRING_FILTERS,
'float': NUMBER_FILTERS,
diff --git a/packages/perspective/src/js/perspective.js b/packages/perspective/src/js/perspective.js
index c646e3d83e..6fc058a689 100644
--- a/packages/perspective/src/js/perspective.js
+++ b/packages/perspective/src/js/perspective.js
@@ -7,7 +7,7 @@
*
*/
-import {AGGREGATE_DEFAULTS, FILTER_DEFAULTS, SORT_ORDERS, TYPE_AGGREGATES, TYPE_FILTERS} from "./defaults.js";
+import {AGGREGATE_DEFAULTS, FILTER_DEFAULTS, SORT_ORDERS, TYPE_AGGREGATES, TYPE_FILTERS, COLUMN_SEPARATOR_STRING} from "./defaults.js";
import {DateParser, is_valid_date} from "./date_parser.js";
import {Precision} from "@apache-arrow/es5-esm/type";
@@ -25,7 +25,7 @@ if (typeof self !== "undefined" && self.performance === undefined) {
self.performance = {now: Date.now};
}
-const CHUNKED_THRESHOLD = 100000
+const CHUNKED_THRESHOLD = 100000;
module.exports = function (Module) {
let __MODULE__ = Module;
@@ -484,7 +484,7 @@ view.prototype._column_names = function() {
}
col_name = col_name.reverse();
col_name.push(name);
- col_name = col_name.join(",");
+ col_name = col_name.join(COLUMN_SEPARATOR_STRING);
col_path.delete();
}
col_names.push(col_name);
@@ -518,7 +518,7 @@ view.prototype.schema = async function() {
let new_schema = {};
let col_names = this._column_names();
for (let col_name of col_names) {
- col_name = col_name.split(',');
+ col_name = col_name.split(COLUMN_SEPARATOR_STRING);
col_name = col_name[col_name.length - 1];
if (types[col_name] === 1 || types[col_name] === 2) {
new_schema[col_name] = "integer";
@@ -548,7 +548,7 @@ const map_aggregate_types = function(col_name, orig_type, aggregate) {
for (let agg in aggregate) {
let found_agg = aggregate[agg];
- if (found_agg.column.join(',') === col_name) {
+ if (found_agg.column.join(COLUMN_SEPARATOR_STRING) === col_name) {
if (INTEGER_AGGS.includes(found_agg.op)) {
return "integer";
} else if (FLOAT_AGGS.includes(found_agg.op)) {
@@ -1014,7 +1014,7 @@ table.prototype._computed_schema = function() {
const column = {};
column.type = column_type;
- column.input_column = computed[i].inputs[0]; // edit to support multiple input columns
+ column.input_columns = computed[i].inputs;
column.input_type = computed[i].input_type;
column.computation = computed[i].computation;
@@ -1187,7 +1187,7 @@ table.prototype.view = function(config) {
throw `'${agg.op}' has incorrect arity ('${dep_length}') for column dependencies.`;
}
}
- aggregates.push([agg.name || agg.column.join(","), agg_op, agg.column]);
+ aggregates.push([agg.name || agg.column.join(COLUMN_SEPARATOR_STRING), agg_op, agg.column]);
}
} else {
let agg_op = __MODULE__.t_aggtype.AGGTYPE_DISTINCT_COUNT;
@@ -1452,7 +1452,7 @@ table.prototype._column_metadata = function () {
if (computed_col !== undefined) {
meta.computed = {
- input_column: computed_col.input_column,
+ input_columns: computed_col.input_columns,
input_type: computed_col.input_type,
computation: computed_col.computation
}
@@ -1471,9 +1471,14 @@ table.prototype._column_metadata = function () {
}
/**
- * Column metadata for this table. If the column is computed, the `computed` property
- * is an Object containing `input_column`, `input_type`, and `computation`. Otherwise,
- * `computed` is `undefined`.
+ * Column metadata for this table.
+ *
+ * If the column is computed, the `computed` property is an Object containing:
+ * - Array `input_columns`
+ * - String `input_type`
+ * - Object `computation`.
+ *
+ * Otherwise, `computed` is `undefined`.
*
* @async
*
diff --git a/packages/perspective/src/js/view_formatters.js b/packages/perspective/src/js/view_formatters.js
index caadf8c570..cadb479898 100644
--- a/packages/perspective/src/js/view_formatters.js
+++ b/packages/perspective/src/js/view_formatters.js
@@ -21,6 +21,8 @@ const jsonFormatter = {
};
const csvFormatter = Object.assign({}, jsonFormatter, {
+ addColumnValue: (data, row, colName, value) => row[colName.split('|').join(',')].unshift(value),
+ setColumnValue: (data, row, colName, value) => row[colName.split('|').join(',')] = value,
formatData: (data, config) => papaparse.unparse(data, config)
});
diff --git a/packages/perspective/test/js/constructors.js b/packages/perspective/test/js/constructors.js
index f692112017..46f1afd9fc 100644
--- a/packages/perspective/test/js/constructors.js
+++ b/packages/perspective/test/js/constructors.js
@@ -366,7 +366,7 @@ module.exports = (perspective) => {
const result = await table2.computed_schema();
const expected = {
"plus2": {
- input_column: "x",
+ input_columns: ["x"],
input_type: "integer",
computation: computation,
type: "integer"
diff --git a/packages/perspective/test/js/pivots.js b/packages/perspective/test/js/pivots.js
index 9bf162ab3e..18fabf2912 100644
--- a/packages/perspective/test/js/pivots.js
+++ b/packages/perspective/test/js/pivots.js
@@ -71,9 +71,9 @@ module.exports = (perspective) => {
],
});
var answer = [
- {__ROW_PATH__: [], x: 2.5, "x,y": 2.8333333333333335},
- {__ROW_PATH__: [ false ], x: 3, "x,y": 3.3333333333333335},
- {__ROW_PATH__: [ true ], x: 2, "x,y": 2.3333333333333335},
+ {__ROW_PATH__: [], x: 2.5, "x|y": 2.8333333333333335},
+ {__ROW_PATH__: [ false ], x: 3, "x|y": 3.3333333333333335},
+ {__ROW_PATH__: [ true ], x: 2, "x|y": 2.3333333333333335},
];
let result = await view.to_json();
expect(answer).toEqual(result);
@@ -480,10 +480,10 @@ module.exports = (perspective) => {
column_pivot: ['y']
});
var answer = [
- {"a,x":1,"a,y":"a","a,z":true,"b,x":null,"b,y":null,"b,z":null,"c,x":null,"c,y":null,"c,z":null,"d,x":null,"d,y":null,"d,z":null},
- {"a,x":null,"a,y":null,"a,z":null,"b,x":2,"b,y":"b","b,z":false,"c,x":null,"c,y":null,"c,z":null,"d,x":null,"d,y":null,"d,z":null},
- {"a,x":null,"a,y":null,"a,z":null,"b,x":null,"b,y":null,"b,z":null,"c,x":3,"c,y":"c","c,z":true,"d,x":null,"d,y":null,"d,z":null},
- {"a,x":null,"a,y":null,"a,z":null,"b,x":null,"b,y":null,"b,z":null,"c,x":null,"c,y":null,"c,z":null,"d,x":4,"d,y":"d","d,z":false}
+ {"a|x":1,"a|y":"a","a|z":true,"b|x":null,"b|y":null,"b|z":null,"c|x":null,"c|y":null,"c|z":null,"d|x":null,"d|y":null,"d|z":null},
+ {"a|x":null,"a|y":null,"a|z":null,"b|x":2,"b|y":"b","b|z":false,"c|x":null,"c|y":null,"c|z":null,"d|x":null,"d|y":null,"d|z":null},
+ {"a|x":null,"a|y":null,"a|z":null,"b|x":null,"b|y":null,"b|z":null,"c|x":3,"c|y":"c","c|z":true,"d|x":null,"d|y":null,"d|z":null},
+ {"a|x":null,"a|y":null,"a|z":null,"b|x":null,"b|y":null,"b|z":null,"c|x":null,"c|y":null,"c|z":null,"d|x":4,"d|y":"d","d|z":false}
];
let result2 = await view.to_json();
expect(answer).toEqual(result2);
@@ -496,11 +496,11 @@ module.exports = (perspective) => {
row_pivot: ['x']
});
var answer = [
- {"__ROW_PATH__":[],"a,x":1,"a,y":1,"a,z":1,"b,x":1,"b,y":1,"b,z":1,"c,x":1,"c,y":1,"c,z":1,"d,x":1,"d,y":1,"d,z":1},
- {"__ROW_PATH__":[1],"a,x":1,"a,y":1,"a,z":1,"b,x":null,"b,y":null,"b,z":null,"c,x":null,"c,y":null,"c,z":null,"d,x":null,"d,y":null,"d,z":null},
- {"__ROW_PATH__":[2],"a,x":null,"a,y":null,"a,z":null,"b,x":1,"b,y":1,"b,z":1,"c,x":null,"c,y":null,"c,z":null,"d,x":null,"d,y":null,"d,z":null},
- {"__ROW_PATH__":[3],"a,x":null,"a,y":null,"a,z":null,"b,x":null,"b,y":null,"b,z":null,"c,x":1,"c,y":1,"c,z":1,"d,x":null,"d,y":null,"d,z":null},
- {"__ROW_PATH__":[4],"a,x":null,"a,y":null,"a,z":null,"b,x":null,"b,y":null,"b,z":null,"c,x":null,"c,y":null,"c,z":null,"d,x":1,"d,y":1,"d,z":1}
+ {"__ROW_PATH__":[],"a|x":1,"a|y":1,"a|z":1,"b|x":1,"b|y":1,"b|z":1,"c|x":1,"c|y":1,"c|z":1,"d|x":1,"d|y":1,"d|z":1},
+ {"__ROW_PATH__":[1],"a|x":1,"a|y":1,"a|z":1,"b|x":null,"b|y":null,"b|z":null,"c|x":null,"c|y":null,"c|z":null,"d|x":null,"d|y":null,"d|z":null},
+ {"__ROW_PATH__":[2],"a|x":null,"a|y":null,"a|z":null,"b|x":1,"b|y":1,"b|z":1,"c|x":null,"c|y":null,"c|z":null,"d|x":null,"d|y":null,"d|z":null},
+ {"__ROW_PATH__":[3],"a|x":null,"a|y":null,"a|z":null,"b|x":null,"b|y":null,"b|z":null,"c|x":1,"c|y":1,"c|z":1,"d|x":null,"d|y":null,"d|z":null},
+ {"__ROW_PATH__":[4],"a|x":null,"a|y":null,"a|z":null,"b|x":null,"b|y":null,"b|z":null,"c|x":null,"c|y":null,"c|z":null,"d|x":1,"d|y":1,"d|z":1}
];
let result2 = await view.to_json();
expect(answer).toEqual(result2);