From 27e7b2d1f533d7ff268a3a309a2a89f0150475f3 Mon Sep 17 00:00:00 2001 From: Andrew Stein Date: Tue, 12 Jan 2021 00:32:52 -0500 Subject: [PATCH 1/3] Tests for #1285 --- packages/perspective/test/js/sort.js | 86 ++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/packages/perspective/test/js/sort.js b/packages/perspective/test/js/sort.js index 1fe0aec6f8..c2ddc25ed6 100644 --- a/packages/perspective/test/js/sort.js +++ b/packages/perspective/test/js/sort.js @@ -16,6 +16,92 @@ const data = { module.exports = perspective => { describe("Sorts", function() { + describe("With aggregates", function() { + describe("aggregates, in a sorted column with nulls", async function() { + const data2 = { + w: [3.5, 4.5, null, null, null, null, 1.5, 2.5], + x: [1, 2, 3, 4, 4, 3, 2, 1], + y: ["a", "b", "c", "d", "e", "f", "g", "h"] + }; + + it("sum", async function() { + var table = perspective.table(data2); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "sum", + x: "unique" + }, + sort: [["w", "asc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + w: [12, 0, 0, 0, 0, 1.5, 2.5, 3.5, 4.5], + x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + }); + + view.delete(); + table.delete(); + }); + + it("unique", async function() { + var table = perspective.table(data2); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "unique", + x: "unique" + }, + sort: [["w", "asc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + w: [null, null, null, null, null, 1.5, 2.5, 3.5, 4.5], + x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + }); + + view.delete(); + table.delete(); + }); + + it("avg", async function() { + var table = perspective.table(data2); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "avg", + x: "unique" + }, + sort: [["w", "asc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + w: [3, null, null, null, null, 1.5, 2.5, 3.5, 4.5], + x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + }); + + // Broken result: + // { + // __ROW_PATH__: [[], ["a"], ["b"], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"]], + // w: [3, 3.5, 4.5, null, null, null, null, 1.5, 2.5], + // x: [null, 1, 2, 3, 4, 4, 3, 2, 1] + // }; + + view.delete(); + table.delete(); + }); + }); + }); + describe("On hidden columns", function() { it("Column path should not emit hidden sorts", async function() { var table = perspective.table(data); From 2f10db36a1b149e1d8337c34bdd2ffb811456325 Mon Sep 17 00:00:00 2001 From: Jun Tan Date: Thu, 21 Jan 2021 18:32:58 -0500 Subject: [PATCH 2/3] Fix #1285: enable nan comparison in WASM, log errors with verbose messages, add tests, fix benchmark suite --- cpp/perspective/src/cpp/base.cpp | 10 +- .../src/include/perspective/multi_sort.h | 2 - .../src/include/perspective/traversal.h | 9 ++ docs/md/development.md | 5 - .../bench/perspective.benchmark.js | 98 ++++++++++++- packages/perspective-bench/bench/versions.js | 23 ++- packages/perspective/test/js/sort.js | 135 +++++++++++++++++- .../perspective/tests/table/test_view.py | 66 +++++++++ scripts/bench.js | 11 -- 9 files changed, 325 insertions(+), 34 deletions(-) diff --git a/cpp/perspective/src/cpp/base.cpp b/cpp/perspective/src/cpp/base.cpp index f63811ad2c..656c067d3b 100644 --- a/cpp/perspective/src/cpp/base.cpp +++ b/cpp/perspective/src/cpp/base.cpp @@ -22,10 +22,14 @@ namespace perspective { void psp_abort(const std::string& message) { #ifdef PSP_ENABLE_WASM - std::cerr << "Abort(): " << message << std::endl; + std::string error = "Abort(): " + message; + const char* error_cstr = error.c_str(); + EM_ASM({ - throw new Error('abort()'); - }); + // copy string out from heap + // https://emscripten.org/docs/api_reference/emscripten.h.html#c.EM_ASM + throw new Error(UTF8ToString($0)); + }, error_cstr); #else throw PerspectiveException(message.c_str()); #endif diff --git a/cpp/perspective/src/include/perspective/multi_sort.h b/cpp/perspective/src/include/perspective/multi_sort.h index b9d002876d..41905f68a8 100644 --- a/cpp/perspective/src/include/perspective/multi_sort.h +++ b/cpp/perspective/src/include/perspective/multi_sort.h @@ -98,7 +98,6 @@ cmp_mselem(const t_mselem& a, const t_mselem& b, const std::vector& t_sorttype order = sort_order[idx]; -#ifndef PSP_ENABLE_WASM t_nancmp nancmp = nan_compare(order, first, second); if (first.is_floating_point() && nancmp.m_active) { @@ -113,7 +112,6 @@ cmp_mselem(const t_mselem& a, const t_mselem& b, const std::vector& default: { continue; } break; } } -#endif if (first == second) continue; diff --git a/cpp/perspective/src/include/perspective/traversal.h b/cpp/perspective/src/include/perspective/traversal.h index fdb7061ddc..0fcc6e8295 100644 --- a/cpp/perspective/src/include/perspective/traversal.h +++ b/cpp/perspective/src/include/perspective/traversal.h @@ -113,6 +113,15 @@ class t_traversal { std::shared_ptr> m_nodes; }; +/** + * @brief Sort implementation for `t_ctx1` and `t_ctx2` contexts. + * + * @tparam SRC_T + * @param config + * @param sortby + * @param src + * @param ctx2 + */ template void t_traversal::sort_by(const t_config& config, const std::vector& sortby, diff --git a/docs/md/development.md b/docs/md/development.md index 1e1fac7c57..5c2d5cbe3f 100644 --- a/docs/md/development.md +++ b/docs/md/development.md @@ -273,11 +273,6 @@ your changes to preserve them for future comparison. yarn bench ``` -Use the `--limit ` flag to control the number of Perspective versions -that the benchmark suite will run, where `` is an integer greater -than 0. If `` cannot be parsed, is 0, or is greater than the number of -versions, the benchmark suite will run all previous versions of Perspective. - The benchmarks report and `results.json` show a histogram of current performance, as well as that of the previous `results.json`. Running this should probably be standard practice after making a large change which may affect diff --git a/packages/perspective-bench/bench/perspective.benchmark.js b/packages/perspective-bench/bench/perspective.benchmark.js index a3e5ba31b5..cb652ca91c 100644 --- a/packages/perspective-bench/bench/perspective.benchmark.js +++ b/packages/perspective-bench/bench/perspective.benchmark.js @@ -149,11 +149,47 @@ describe("Update", async () => { table = worker.table(data.arrow.slice()); view = table.view(); - let test_data = await static_view[`to_${name}`]({end_row: 500}); + const test_data = await static_view[`to_${name}`]({end_row: 500}); + benchmark(name, async () => { for (let i = 0; i < 5; i++) { table.update(test_data.slice ? test_data.slice() : test_data); - await table.size(); + await view.num_rows(); + } + }); + }); + + describe("ctx0 sorted", async () => { + table = worker.table(data.arrow.slice()); + view = table.view({ + sort: [["Customer Name", "desc"]] + }); + + const test_data = await static_view[`to_${name}`]({end_row: 500}); + + benchmark(name, async () => { + for (let i = 0; i < 5; i++) { + table.update(test_data.slice ? test_data.slice() : test_data); + await view.num_rows(); + } + }); + }); + + describe("ctx0 sorted deep", async () => { + table = worker.table(data.arrow.slice()); + //table_indexed = worker.table(data.arrow.slice(), {index: "Row ID"}); + view = table.view({ + sort: [ + ["Customer Name", "desc"], + ["Order Date", "asc"] + ] + }); + + const test_data = await static_view[`to_${name}`]({end_row: 500}); + benchmark(name, async () => { + for (let i = 0; i < 5; i++) { + table.update(test_data.slice ? test_data.slice() : test_data); + await view.num_rows(); } }); }); @@ -168,7 +204,7 @@ describe("Update", async () => { benchmark(name, async () => { for (let i = 0; i < 5; i++) { table.update(test_data.slice ? test_data.slice() : test_data); - await table.size(); + await view.num_rows(); } }); }); @@ -182,7 +218,7 @@ describe("Update", async () => { benchmark(name, async () => { for (let i = 0; i < 5; i++) { table.update(test_data.slice ? test_data.slice() : test_data); - await table.size(); + await view.num_rows(); } }); }); @@ -197,7 +233,7 @@ describe("Update", async () => { benchmark(name, async () => { for (let i = 0; i < 5; i++) { table.update(test_data.slice ? test_data.slice() : test_data); - await table.size(); + await view.num_rows(); } }); }); @@ -212,7 +248,7 @@ describe("Update", async () => { benchmark(name, async () => { for (let i = 0; i < 5; i++) { table.update(test_data.slice ? test_data.slice() : test_data); - await table.size(); + await view.num_rows(); } }); }); @@ -226,7 +262,7 @@ describe("Update", async () => { benchmark(name, async () => { for (let i = 0; i < 5; i++) { table.update(test_data.slice ? test_data.slice() : test_data); - await table.size(); + await view.num_rows(); } }); }); @@ -371,6 +407,54 @@ describe("View", async () => { }); }); + describe(`${cat} sorted`, async () => { + let view; + + afterEach(async () => { + await view.delete(); + }); + + benchmark(`sorted float asc`, async () => { + view = table.view({ + aggregate, + row_pivot, + column_pivot, + sort: [["Sales", "asc"]] + }); + await view.schema(); + }); + + benchmark(`sorted float desc`, async () => { + view = table.view({ + aggregate, + row_pivot, + column_pivot, + sort: [["Sales", "asc"]] + }); + await view.schema(); + }); + + benchmark(`sorted str asc`, async () => { + view = table.view({ + aggregate, + row_pivot, + column_pivot, + sort: [["Customer Name", "asc"]] + }); + await view.schema(); + }); + + benchmark(`sorted str desc`, async () => { + view = table.view({ + aggregate, + row_pivot, + column_pivot, + sort: [["Customer Name", "desc"]] + }); + await view.schema(); + }); + }); + describe(cat, async () => { let view; diff --git a/packages/perspective-bench/bench/versions.js b/packages/perspective-bench/bench/versions.js index d0b8a7f020..238c7bca9c 100644 --- a/packages/perspective-bench/bench/versions.js +++ b/packages/perspective-bench/bench/versions.js @@ -34,7 +34,28 @@ const JPMC_VERSIONS = [ const FINOS_VERSIONS = ["0.3.1", "0.3.0", "0.3.0-rc.3", "0.3.0-rc.2", "0.3.0-rc.1"]; -const UMD_VERSIONS = ["0.5.6", "0.5.5", "0.5.4", "0.5.3", "0.5.2", "0.5.1", "0.5.0", "0.4.8", "0.4.7", "0.4.6", "0.4.5", "0.4.4", "0.4.2", "0.4.1", "0.4.0", "0.3.9", "0.3.8", "0.3.7", "0.3.6"]; +const UMD_VERSIONS = [ + "0.6.0", + "0.5.6", + "0.5.5", + "0.5.4", + "0.5.3", + "0.5.2", + "0.5.1", + "0.5.0", + "0.4.8", + "0.4.7", + "0.4.6", + "0.4.5", + "0.4.4", + "0.4.2", + "0.4.1", + "0.4.0", + "0.3.9", + "0.3.8", + "0.3.7", + "0.3.6" +]; async function run() { await PerspectiveBench.run("master", "bench/perspective.benchmark.js", `http://${process.env.PSP_DOCKER_PUPPETEER ? `localhost` : `host.docker.internal`}:8080/perspective.js`, { diff --git a/packages/perspective/test/js/sort.js b/packages/perspective/test/js/sort.js index c2ddc25ed6..d91aec59fc 100644 --- a/packages/perspective/test/js/sort.js +++ b/packages/perspective/test/js/sort.js @@ -14,16 +14,114 @@ const data = { z: [true, false, true, false, true, false, true, false] }; +const data2 = { + w: [3.5, 4.5, null, null, null, null, 1.5, 2.5], + x: [1, 2, 3, 4, 4, 3, 2, 1], + y: ["a", "b", "c", "d", "e", "f", "g", "h"] +}; + module.exports = perspective => { describe("Sorts", function() { - describe("With aggregates", function() { - describe("aggregates, in a sorted column with nulls", async function() { - const data2 = { - w: [3.5, 4.5, null, null, null, null, 1.5, 2.5], + describe("With nulls", () => { + it("asc", async function() { + var table = perspective.table(data2); + var view = table.view({ + columns: ["x", "w"], + sort: [["w", "asc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + w: [null, null, null, null, 1.5, 2.5, 3.5, 4.5], + x: [3, 4, 4, 3, 2, 1, 1, 2] + }); + + view.delete(); + table.delete(); + }); + + it("desc", async function() { + var table = perspective.table(data2); + var view = table.view({ + columns: ["x", "w"], + sort: [["w", "desc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + w: [4.5, 3.5, 2.5, 1.5, null, null, null, null], + x: [2, 1, 1, 2, 3, 4, 4, 3] + }); + + view.delete(); + table.delete(); + }); + + it("asc datetime", async function() { + var table = perspective.table({ + w: [new Date(2020, 0, 1, 12, 30, 45), new Date(2020, 0, 1), null, null, null, null, new Date(2008, 0, 1, 12, 30, 45), new Date(2020, 12, 1, 12, 30, 45)], x: [1, 2, 3, 4, 4, 3, 2, 1], y: ["a", "b", "c", "d", "e", "f", "g", "h"] - }; + }); + + var view = table.view({ + columns: ["x", "w"], + sort: [["w", "asc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + w: [ + null, + null, + null, + null, + new Date(2008, 0, 1, 12, 30, 45).getTime(), + new Date(2020, 0, 1).getTime(), + new Date(2020, 0, 1, 12, 30, 45).getTime(), + new Date(2020, 12, 1, 12, 30, 45).getTime() + ], + x: [3, 4, 4, 3, 2, 2, 1, 1] + }); + view.delete(); + table.delete(); + }); + + it("desc datetime", async function() { + var table = perspective.table({ + w: [new Date(2020, 0, 1, 12, 30, 45), new Date(2020, 0, 1), null, null, null, null, new Date(2008, 0, 1, 12, 30, 45), new Date(2020, 12, 1, 12, 30, 45)], + x: [1, 2, 3, 4, 4, 3, 2, 1], + y: ["a", "b", "c", "d", "e", "f", "g", "h"] + }); + + var view = table.view({ + columns: ["x", "w"], + sort: [["w", "desc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + w: [ + new Date(2020, 12, 1, 12, 30, 45).getTime(), + new Date(2020, 0, 1, 12, 30, 45).getTime(), + new Date(2020, 0, 1).getTime(), + new Date(2008, 0, 1, 12, 30, 45).getTime(), + null, + null, + null, + null + ], + x: [1, 1, 2, 2, 3, 4, 4, 3] + }); + + view.delete(); + table.delete(); + }); + }); + + describe("With aggregates", function() { + describe("aggregates, in a sorted column with nulls", async function() { it("sum", async function() { var table = perspective.table(data2); var view = table.view({ @@ -47,6 +145,33 @@ module.exports = perspective => { table.delete(); }); + it("sum of floats", async function() { + var table = perspective.table({ + w: [3.25, 4.51, null, null, null, null, 1.57, 2.59], + x: [1, 2, 3, 4, 4, 3, 2, 1], + y: ["a", "b", "c", "d", "e", "f", "g", "h"] + }); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "sum", + x: "unique" + }, + sort: [["w", "asc"]] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + w: [11.92, 0, 0, 0, 0, 1.57, 2.59, 3.25, 4.51], + x: [null, 3, 4, 4, 3, 2, 1, 1, 2] + }); + + view.delete(); + table.delete(); + }); + it("unique", async function() { var table = perspective.table(data2); var view = table.view({ diff --git a/python/perspective/perspective/tests/table/test_view.py b/python/perspective/perspective/tests/table/test_view.py index 8411db58ac..8e3a609352 100644 --- a/python/perspective/perspective/tests/table/test_view.py +++ b/python/perspective/perspective/tests/table/test_view.py @@ -545,6 +545,72 @@ def test_view_sort_hidden(self): view = tbl.view(sort=[["a", "desc"]], columns=["b"]) assert view.to_records() == [{"b": 4}, {"b": 2}] + def test_view_sort_avg_nan(self): + data = { + "w": [3.5, 4.5, None, None, None, None, 1.5, 2.5], + "x": [1, 2, 3, 4, 4, 3, 2, 1], + "y": ["a", "b", "c", "d", "e", "f", "g", "h"] + } + tbl = Table(data) + view = tbl.view( + columns=["x", "w"], + row_pivots=["y"], + sort=[["w", "asc"]], + aggregates={ + "w": "avg", + "x": "unique" + }, + ) + assert view.to_dict() == { + "__ROW_PATH__": [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + "w": [3, None, None, None, None, 1.5, 2.5, 3.5, 4.5], + "x": [None, 3, 4, 4, 3, 2, 1, 1, 2] + } + + def test_view_sort_sum_nan(self): + data = { + "w": [3.5, 4.5, None, None, None, None, 1.5, 2.5], + "x": [1, 2, 3, 4, 4, 3, 2, 1], + "y": ["a", "b", "c", "d", "e", "f", "g", "h"] + } + tbl = Table(data) + view = tbl.view( + columns=["x", "w"], + row_pivots=["y"], + sort=[["w", "asc"]], + aggregates={ + "w": "sum", + "x": "unique" + }, + ) + assert view.to_dict() == { + "__ROW_PATH__": [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + "w": [12, 0, 0, 0, 0, 1.5, 2.5, 3.5, 4.5], + "x": [None, 3, 4, 4, 3, 2, 1, 1, 2] + } + + def test_view_sort_unique_nan(self): + data = { + "w": [3.5, 4.5, None, None, None, None, 1.5, 2.5], + "x": [1, 2, 3, 4, 4, 3, 2, 1], + "y": ["a", "b", "c", "d", "e", "f", "g", "h"] + } + tbl = Table(data) + view = tbl.view( + columns=["x", "w"], + row_pivots=["y"], + sort=[["w", "asc"]], + aggregates={ + "w": "unique", + "x": "unique" + }, + ) + assert view.to_dict() == { + "__ROW_PATH__": [[], ["c"], ["d"], ["e"], ["f"], ["g"], ["h"], ["a"], ["b"]], + "w": [None, None, None, None, None, 1.5, 2.5, 3.5, 4.5], + "x": [None, 3, 4, 4, 3, 2, 1, 1, 2] + } + # filter def test_view_filter_int_eq(self): diff --git a/scripts/bench.js b/scripts/bench.js index fe61dd3246..38dc9e2586 100644 --- a/scripts/bench.js +++ b/scripts/bench.js @@ -10,8 +10,6 @@ const {execute} = require("./script_utils.js"); const args = process.argv.slice(2); -const LIMIT = args.indexOf("--limit"); -const IS_DELTA = args.indexOf("--delta"); if (process.env.PSP_PROJECT === undefined || process.env.PSP_PROJECT === "js") { function docker() { @@ -22,15 +20,6 @@ if (process.env.PSP_PROJECT === undefined || process.env.PSP_PROJECT === "js") { } cmd += " perspective/puppeteer nice -n -20 node_modules/.bin/lerna exec --scope=@finos/perspective-bench -- yarn bench"; - if (LIMIT !== -1) { - let limit = args[LIMIT + 1]; - cmd += ` --limit ${limit}`; - } - - if (IS_DELTA !== -1) { - console.log("Running benchmarking suite for delta - only comparing results within master."); - cmd += " --delta"; - } return cmd; } From a5febfb4843c15e4a9ae9006576d6ba1b959c180 Mon Sep 17 00:00:00 2001 From: Jun Tan Date: Thu, 21 Jan 2021 19:37:55 -0500 Subject: [PATCH 3/3] multisort tests --- packages/perspective/test/js/sort.js | 113 +++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/packages/perspective/test/js/sort.js b/packages/perspective/test/js/sort.js index d91aec59fc..0058bb7ec6 100644 --- a/packages/perspective/test/js/sort.js +++ b/packages/perspective/test/js/sort.js @@ -20,6 +20,12 @@ const data2 = { y: ["a", "b", "c", "d", "e", "f", "g", "h"] }; +const data3 = { + w: [3.5, 4.5, null, null, null, null, 1.5, 2.5], + x: [1, 2, 3, 4, 4, 3, 2, 1], + y: ["a", "a", "a", "a", "b", "b", "b", "b"] +}; + module.exports = perspective => { describe("Sorts", function() { describe("With nulls", () => { @@ -224,6 +230,113 @@ module.exports = perspective => { view.delete(); table.delete(); }); + + describe("Multiple hidden sort", () => { + it("sum", async function() { + var table = perspective.table(data3); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "sum" + }, + sort: [ + ["x", "desc"], + ["w", "desc"] + ] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"]], + w: [12, 8, 4], + x: [20, 10, 10] + }); + + view.delete(); + table.delete(); + }); + + it("sum of floats", async function() { + var table = perspective.table({ + w: [3.25, 4.51, null, null, null, null, 1.57, 2.59], + x: [1, 2, 3, 4, 4, 3, 2, 1], + y: ["a", "a", "a", "a", "b", "b", "b", "b"] + }); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "sum" + }, + sort: [ + ["x", "desc"], + ["w", "desc"] + ] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"]], + w: [11.92, 7.76, 4.16], + x: [20, 10, 10] + }); + + view.delete(); + table.delete(); + }); + + it("unique", async function() { + var table = perspective.table(data3); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "unique" + }, + sort: [ + ["x", "desc"], + ["w", "desc"] + ] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"]], + w: [null, null, null], + x: [20, 10, 10] + }); + + view.delete(); + table.delete(); + }); + + it("avg", async function() { + var table = perspective.table(data3); + var view = table.view({ + columns: ["x", "w"], + row_pivots: ["y"], + aggregates: { + w: "avg" + }, + sort: [ + ["x", "desc"], + ["w", "desc"] + ] + }); + + const json = await view.to_columns(); + expect(json).toEqual({ + __ROW_PATH__: [[], ["a"], ["b"]], + // 4 and 2 are the avg of the non-null rows + w: [3, 4, 2], + x: [20, 10, 10] + }); + + view.delete(); + table.delete(); + }); + }); }); });