diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000000..0e796c3f18 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,3 @@ +node_modules +dist +build \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index cb3ae5b7b5..333cc9fc24 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -26,7 +26,22 @@ "parser": "@typescript-eslint/parser", "extends": [ "plugin:@typescript-eslint/recommended" - ] + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "camelcase": "off", + "@typescript-eslint/camelcase": "off" + + } + }, { + "files": ["**/test/**/*.js"], + "rules": { + "max-len": ["warn", { + "code": 200, + "comments": 200, + "ignoreTrailingComments": true + }] + } }], "rules": { "prettier/prettier": ["error", { @@ -34,6 +49,13 @@ "tabWidth": 4, "bracketSpacing": false }], + "max-len": ["warn", { + "code": 200, + "comments": 80, + "ignoreTrailingComments": true + }], + "@typescript-eslint/camelcase": "off", + "camelcase": "off", "no-const-assign": "error", "no-this-before-super": "error", "no-undef": "error", diff --git a/package.json b/package.json index 74268ada6e..074cf37b3e 100644 --- a/package.json +++ b/package.json @@ -94,7 +94,8 @@ "start": "lerna run start --stream --scope", "precommit": "npm run lint", "lint": "npm-run-all lint:* lint_python", - "lint:eslint": "eslint packages/*/src/**/*.js packages/*/test/**/*.js examples/*/*.js", + "lint:eslint": "eslint \"packages/*/src/**/*.js\" \"packages/*/test/**/*.js\" \"examples/*/*.js\"", + "lint_:tslint": "eslint \"packages/*/src/**/*.ts\" \"packages/*/test/**/*.ts\" \"examples/*/*.ts\"", "lint_python": "node scripts/lint_python.js", "fix:es": "npm run lint:eslint -- --fix", "fix:md": "prettier docs/md/*.md --prose-wrap=always --write", diff --git a/packages/perspective-jupyterlab/src/ts/client.ts b/packages/perspective-jupyterlab/src/ts/client.ts index 916c4360a8..908276f1c3 100644 --- a/packages/perspective-jupyterlab/src/ts/client.ts +++ b/packages/perspective-jupyterlab/src/ts/client.ts @@ -20,11 +20,13 @@ export interface PerspectiveJupyterMessage { } /** - * `PerspectiveJupyterClient` acts as a message bus between the frontend and backend, - * passing messages from `perspective-viewer` (method calls, `to_format()` calls, etc.) to - * the `PerspectiveManager` on the python side of the plugin. + * `PerspectiveJupyterClient` acts as a message bus between the frontend and + * backend, passing messages from `perspective-viewer` (method calls, + * `to_format()` calls, etc.) to the `PerspectiveManager` on the python side of + * the plugin. * - * This client implements the `Client` class as defined in `@finos/perspective/api`. + * This client implements the `Client` class as defined in + * `@finos/perspective/api`. */ export class PerspectiveJupyterClient extends Client { view: DOMWidgetView; @@ -32,7 +34,8 @@ export class PerspectiveJupyterClient extends Client { /** * Create a new instance of the client. * - * @param view {DOMWidgetView} the plugin view that can send messages to the Python backend. + * @param view {DOMWidgetView} the plugin view that can send messages to the + * Python backend. */ constructor(view: DOMWidgetView) { super(); @@ -40,7 +43,8 @@ export class PerspectiveJupyterClient extends Client { } /** - * Given a message, pass it to the `PerspectiveManager` instance on the ipywidget. + * Given a message, pass it to the `PerspectiveManager` instance on the + * ipywidget. * * The sent message conforms to the `PerspectiveJupyterMessage` interface. * diff --git a/packages/perspective-jupyterlab/src/ts/view.ts b/packages/perspective-jupyterlab/src/ts/view.ts index af18bb40ef..f38f267a98 100644 --- a/packages/perspective-jupyterlab/src/ts/view.ts +++ b/packages/perspective-jupyterlab/src/ts/view.ts @@ -13,7 +13,8 @@ import {PerspectiveJupyterWidget} from "./widget"; import {PerspectiveJupyterClient, PerspectiveJupyterMessage} from "./client"; /** - * `PerspectiveView` defines the plugin's DOM and how the plugin interacts with the DOM. + * `PerspectiveView` defines the plugin's DOM and how the plugin interacts with + * the DOM. */ export class PerspectiveView extends DOMWidgetView { pWidget: PerspectiveWidget; @@ -79,7 +80,8 @@ export class PerspectiveView extends DOMWidgetView { } /** - * Attach event handlers, and watch the DOM for state changes in order to reflect them back to Python. + * Attach event handlers, and watch the DOM for state changes in order to + * reflect them back to Python. */ render() { super.render(); @@ -96,7 +98,8 @@ export class PerspectiveView extends DOMWidgetView { this.model.on("change:dark", this.dark_changed, this); this.model.on("change:editable", this.editable_changed, this); - // Watch the viewer DOM so that widget state is always synchronized with DOM attributes. + // Watch the viewer DOM so that widget state is always synchronized with + // DOM attributes. const observer = new MutationObserver(this._synchronize_state.bind(this)); observer.observe(this.pWidget.viewer, { attributes: true, @@ -105,9 +108,11 @@ export class PerspectiveView extends DOMWidgetView { }); /** - * Request a table from the manager. If a table has been loaded, proxy it and kick off subsequent operations. + * Request a table from the manager. If a table has been loaded, proxy + * it and kick off subsequent operations. * - * If a table hasn't been loaded, the viewer won't get a response back and simply waits until it receives a table name. + * If a table hasn't been loaded, the viewer won't get a response back + * and simply waits until it receives a table name. */ this.perspective_client.send({ id: -2, @@ -123,18 +128,21 @@ export class PerspectiveView extends DOMWidgetView { * @param msg {PerspectiveJupyterMessage} */ _handle_message(msg: PerspectiveJupyterMessage) { - // If in client-only mode (no Table on the python widget), message.data is an object containing "data" and "options". + // If in client-only mode (no Table on the python widget), message.data + // is an object containing "data" and "options". if (msg.type === "table") { this._handle_load_message(msg); } else { if (msg.data["cmd"] === "delete") { - // Regardless of client mode, if `delete()` is called we need to clean up the Viewer. + // Regardless of client mode, if `delete()` is called we need to + // clean up the Viewer. this.pWidget.delete(); return; } if (this.pWidget.client === true) { - // In client mode, we need to directly call the methods on the viewer + // In client mode, we need to directly call the methods on the + // viewer const command = msg.data["cmd"]; if (command === "update") { this.pWidget._update(msg.data["data"]); @@ -144,7 +152,9 @@ export class PerspectiveView extends DOMWidgetView { this.pWidget.clear(); } } else { - // Make a deep copy of each message - widget views share the same comm, so mutations on `msg` affect subsequent message handlers. + // Make a deep copy of each message - widget views share the + // same comm, so mutations on `msg` affect subsequent message + // handlers. const message = JSON.parse(JSON.stringify(msg)); delete message.type; @@ -158,8 +168,9 @@ export class PerspectiveView extends DOMWidgetView { } /** - * Given a message that commands the widget to load a dataset or table, process it. - * @param {PerspectiveJupyterMessage} msg + * Given a message that commands the widget to load a dataset or table, + * process it. + * @param {PerspectiveJupyterMessage} msg */ _handle_load_message(msg: PerspectiveJupyterMessage) { if (this.pWidget.client === true) { @@ -179,7 +190,8 @@ export class PerspectiveView extends DOMWidgetView { } /** - * When traitlets are updated in python, update the corresponding value on the front-end viewer. + * When traitlets are updated in python, update the corresponding value on + * the front-end viewer. */ plugin_changed() { this.pWidget.plugin = this.model.get("plugin"); diff --git a/packages/perspective-phosphor/src/ts/widget.ts b/packages/perspective-phosphor/src/ts/widget.ts index ab168d7f7f..dfc31dc1fb 100644 --- a/packages/perspective-phosphor/src/ts/widget.ts +++ b/packages/perspective-phosphor/src/ts/widget.ts @@ -26,7 +26,8 @@ export interface PerspectiveWidgetOptions extends PerspectiveViewerOptions { bindto?: HTMLElement; plugin_config?: PerspectiveViewerOptions; - // these shouldn't exist, PerspectiveViewerOptions should be sufficient e.g. ["row-pivots"] + // these shouldn't exist, PerspectiveViewerOptions should be sufficient e.g. + // ["row-pivots"] column_pivots?: string[]; row_pivots?: string[]; computed_columns?: {[column_name: string]: string}[]; @@ -35,8 +36,7 @@ export interface PerspectiveWidgetOptions extends PerspectiveViewerOptions { /** * Class for perspective phosphor widget. * - * @class PerspectiveWidget (name) - * TODO: document + * @class PerspectiveWidget (name) TODO: document */ export class PerspectiveWidget extends Widget { constructor(name = "Perspective", options: PerspectiveWidgetOptions = {}) { @@ -140,14 +140,14 @@ export class PerspectiveWidget extends Widget { * * @param table a `perspective.table` object. */ - load(table: (TableData | Table), options?: TableOptions): void { + load(table: TableData | Table, options?: TableOptions): void { this.viewer.load(table, options); } /** * Update the viewer with new data. - * - * @param data + * + * @param data */ _update(data: TableData): void { this.viewer.update(data); @@ -161,9 +161,9 @@ export class PerspectiveWidget extends Widget { } /** - * Replaces the data of the viewer's table with new data. New data must conform - * to the schema of the Table. - * + * Replaces the data of the viewer's table with new data. New data must + * conform to the schema of the Table. + * * @param data */ replace(data: TableData): void { @@ -174,13 +174,14 @@ export class PerspectiveWidget extends Widget { * Deletes this element's data and clears it's internal state (but not its * user state). This (or the underlying `perspective.table`'s equivalent * method) must be called in order for its memory to be reclaimed. - * - * If not running in client mode, delete_table defaults to false and the server should - * handle memory cleanup. * - * @param {boolean} delete_table Whether `delete()` should be called on the underlying `Table`. + * If not running in client mode, delete_table defaults to false and the + * server should handle memory cleanup. + * + * @param {boolean} delete_table Whether `delete()` should be called on the + * underlying `Table`. */ - delete(delete_table: boolean = true) { + delete(delete_table = true) { this.viewer.delete(delete_table || this.client); } @@ -301,7 +302,8 @@ export class PerspectiveWidget extends Widget { } /** - * True if the widget is in client-only mode, i.e. the browser has ownership of the widget's data. + * True if the widget is in client-only mode, i.e. the browser has ownership + * of the widget's data. */ get client(): boolean { return this._client; @@ -375,7 +377,7 @@ export class PerspectiveWidget extends Widget { console.warn("Warning: not bound to real element"); } else { const resize_observer = new MutationObserver(viewer.notifyResize.bind(viewer)); - resize_observer.observe(node, { attributes: true }); + resize_observer.observe(node, {attributes: true}); } return viewer; } diff --git a/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js b/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js index 49a1eb21e9..1ef75e1ab8 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/chartFactory.js @@ -111,7 +111,8 @@ const chartFactory = (xAxis, yAxis, cartesian, canvas) => { .call(yAxisComponent); }); - // Render all the series using either the primary or alternate y-scales + // Render all the series using either the primary or alternate + // y-scales if (canvas) { const drawMultiCanvasSeries = selection => { const canvasPlotArea = chart.plotArea(); diff --git a/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js b/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js index 4d1bfd5e6d..8382e8a79f 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/ordinalAxis.js @@ -98,12 +98,14 @@ export const component = settings => { }; }; - const pickAxis = multiLevel => { - if (multiLevel) { - return orient === "horizontal" ? multiAxisBottom : multiAxisLeft; - } - return orient === "horizontal" ? fc.axisOrdinalBottom : fc.axisOrdinalLeft; - }; + // const pickAxis = multiLevel => { + // if (multiLevel) { + // return orient === "horizontal" ? + // multiAxisBottom : multiAxisLeft; + // } + // return orient === "horizontal" ? + // fc.axisOrdinalBottom : fc.axisOrdinalLeft; + // }; const getAxisSet = multiLevel => { if (multiLevel) { diff --git a/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js b/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js index f93da6774e..1d6c7cea9a 100644 --- a/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js +++ b/packages/perspective-viewer-d3fc/src/js/axis/splitterLabels.js @@ -10,7 +10,8 @@ import * as fc from "d3fc"; import {getChartElement} from "../plugin/root"; import {withoutOpacity} from "../series/seriesColors.js"; -// Render a set of labels with the little left/right arrows for moving between axes +// Render a set of labels with the little left/right arrows for moving +// between axes export const splitterLabels = settings => { let labels = []; let alt = false; diff --git a/packages/perspective-viewer-d3fc/src/js/charts/area.js b/packages/perspective-viewer-d3fc/src/js/charts/area.js index c6db206624..0b5419b42e 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/area.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/area.js @@ -74,7 +74,8 @@ function areaChart(container, settings) { // Create the y-axis data for the alt-axis const yAxis2 = yAxisFactory(splitter.altData()); chart.altAxis(yAxis2); - // Give the tooltip the information (i.e. 2 datasets with different scales) + // Give the tooltip the information (i.e. 2 datasets with different + // scales) toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); } diff --git a/packages/perspective-viewer-d3fc/src/js/charts/line.js b/packages/perspective-viewer-d3fc/src/js/charts/line.js index 7b7a431b17..d1dcf235b4 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/line.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/line.js @@ -80,7 +80,8 @@ function lineChart(container, settings) { // Create the y-axis data for the alt-axis const yAxis2 = yAxisFactory(splitter.altData()); chart.altAxis(yAxis2); - // Give the tooltip the information (i.e. 2 datasets with different scales) + // Give the tooltip the information (i.e. 2 datasets with different + // scales) toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); } diff --git a/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js b/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js index a95a9be6d7..9078b9c34a 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/sunburst.js @@ -22,7 +22,10 @@ function sunburst(container, settings) { } const data = treeData(settings); - const color = treeColor(settings, data.map(d => d.extents)); + const color = treeColor( + settings, + data.map(d => d.extents) + ); const sunburstGrid = gridLayoutMultiChart().elementsPrefix("sunburst"); container.datum(data).call(sunburstGrid); diff --git a/packages/perspective-viewer-d3fc/src/js/charts/treemap.js b/packages/perspective-viewer-d3fc/src/js/charts/treemap.js index 3dc49881bc..724c996a34 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/treemap.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/treemap.js @@ -23,7 +23,10 @@ function treemap(container, settings) { if (!settings.treemaps) settings.treemaps = {}; const data = treeData(settings); - const color = treeColor(settings, data.map(d => d.data)); + const color = treeColor( + settings, + data.map(d => d.data) + ); const treemapGrid = gridLayoutMultiChart().elementsPrefix("treemap"); container.datum(data).call(treemapGrid); diff --git a/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js b/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js index fcb77f1922..25136e0cd9 100644 --- a/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js +++ b/packages/perspective-viewer-d3fc/src/js/charts/y-scatter.js @@ -80,7 +80,8 @@ function yScatter(container, settings) { // Create the y-axis data for the alt-axis const yAxis2 = yAxisFactory(splitter.altData()); chart.altAxis(yAxis2); - // Give the tooltip the information (i.e. 2 datasets with different scales) + // Give the tooltip the information (i.e. 2 datasets with different + // scales) toolTip.data(splitter.data()).altDataWithScale({yScale: yAxis2.scale, data: splitter.altData()}); } diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js b/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js index 6a79a1e86b..68da5babd3 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/axis/multi-axis.js @@ -39,7 +39,12 @@ const multiAxis = (orient, baseAxis, scale) => { // add the domain line const range = scale.range(); - const domainPathData = pathTranspose([[range[0], sign * tickSizeOuter], [range[0], 0], [range[1], 0], [range[1], sign * tickSizeOuter]]); + const domainPathData = pathTranspose([ + [range[0], sign * tickSizeOuter], + [range[0], 0], + [range[1], 0], + [range[1], sign * tickSizeOuter] + ]); const domainLine = domainPathDataJoin(container, [data]); domainLine diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js b/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js index 1bc0bc3e8e..a12494533a 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/extent/extentLinear.js @@ -77,7 +77,8 @@ export const extentLinear = function() { return instance; }; - //This function points directly at the paddingStrategy child object's properties for backwards-compatibility. DEPRECATED. + //This function points directly at the paddingStrategy child object's + //properties for backwards-compatibility. DEPRECATED. instance.pad = function() { if (!arguments.length) { return paddingStrategy.pad; @@ -86,7 +87,8 @@ export const extentLinear = function() { return instance; }; - //This function points directly at the paddingStrategy child object's properties for backwards-compatibility. DEPRECATED. + //This function points directly at the paddingStrategy child object's + //properties for backwards-compatibility. DEPRECATED. instance.padUnit = function() { if (!arguments.length) { return paddingStrategy.padUnit; diff --git a/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js b/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js index ef0fcbf6c1..45cc31c20e 100644 --- a/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js +++ b/packages/perspective-viewer-d3fc/src/js/d3fc/padding/hardLimitZero.js @@ -33,7 +33,8 @@ export const hardLimitZeroPadding = () => { let paddedLowerExtent = extent[0] - pad[0] * delta; let paddedUpperExtent = extent[1] + pad[1] * delta; - // If datapoints are exclusively negative or exclusively positive hard limit extent to 0. + // If datapoints are exclusively negative or exclusively positive hard + // limit extent to 0. extent[0] = extent[0] >= 0 && paddedLowerExtent < 0 ? 0 : paddedLowerExtent; extent[1] = extent[1] <= 0 && paddedUpperExtent > 0 ? 0 : paddedUpperExtent; return extent; diff --git a/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js b/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js index 7be4540b98..f8a021351e 100644 --- a/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js +++ b/packages/perspective-viewer-d3fc/src/js/data/splitIntoMultiSeries.js @@ -17,8 +17,9 @@ export function splitIntoMultiSeries(settings, data, {stack = false, excludeEmpt } function splitByValuesIntoMultiSeries(settings, data, {stack = false, excludeEmpty = false}) { - // Create a series for each "split" value, each one containing all the "aggregate" values, - // and "base" values to offset it from the previous series + // Create a series for each "split" value, each one containing all the + // "aggregate" values, and "base" values to offset it from the previous + // series const multiSeries = {}; data.forEach(col => { @@ -39,7 +40,8 @@ function splitByValuesIntoMultiSeries(settings, data, {stack = false, excludeEmp // splitName="split1|split2" const splitName = labels.slice(0, labels.length - 1).join("|"); - // Combine aggregate values for the same split in a single object + // Combine aggregate values for the same split in a single + // object const splitValues = (split[splitName] = split[splitName] || {__ROW_PATH__: col.__ROW_PATH__}); const baseValue = baseValues[baseKey] || 0; diff --git a/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js b/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js index 53bfa32cfb..7323ea20d9 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/scrollableLegend.js @@ -102,7 +102,8 @@ export default (fromLegend, settings) => { const legendContainerRect = selection.node().getBoundingClientRect(); let proposedPageSize = Math.floor(legendContainerRect.height / averageCellHeightPx) - 1; - //if page size is less than all legend items, leave space for the legend controls + // if page size is less than all legend items, leave space for the + // legend controls pageSize = proposedPageSize < domain.length ? proposedPageSize - 1 : proposedPageSize; pageCount = calculatePageCount(proposedPageSize); pageIndex = Math.min(pageIndex, pageCount - 1); diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js index deb231f19d..effff86975 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/draggableComponent.js @@ -59,8 +59,9 @@ export function draggableComponent() { function unpinNodeFromTopRight(node, pinned) { if (pinned !== false) { - // Default behaviour for the legend is to remain pinned to the top right hand corner with a specific margin. - // Once the legend has moved we cannot continue to use that css based approach. + // Default behaviour for the legend is to remain pinned to the top right + // hand corner with a specific margin. Once the legend has moved we + // cannot continue to use that css based approach. d3.select(window).on(resizeForDraggingEvent, function() { const offsets = enforceContainerBoundaries(node, 0, 0); node.style.left = `${node.offsetLeft + offsets.x}px`; diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js index 07a32094fb..3ea289eb58 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/enforceContainerBoundaries.js @@ -29,7 +29,12 @@ export function enforceContainerBoundaries(innerNode, offsetX, offsetY) { }; const adjustedOffsets = {x: offsetX, y: offsetY}; - const boundaries = [{edge: "right", dimension: "x"}, {edge: "left", dimension: "x"}, {edge: "top", dimension: "y"}, {edge: "bottom", dimension: "y"}]; + const boundaries = [ + {edge: "right", dimension: "x"}, + {edge: "left", dimension: "x"}, + {edge: "top", dimension: "y"}, + {edge: "bottom", dimension: "y"} + ]; boundaries.forEach(bound => { if (isElementOverflowing(chartNodeRect, draggedInnerNodeRect, bound.edge)) { diff --git a/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js b/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js index 22d2499cec..2c2d2cacfb 100644 --- a/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js +++ b/packages/perspective-viewer-d3fc/src/js/legend/styling/resizableComponent.js @@ -81,7 +81,12 @@ export function resizableComponent() { const concatCornerEdges = corner => `${corner[0]}${corner[1]}`; const cornerCursorHelper = {topleft: "nwse", topright: "nesw", bottomright: "nwse", bottomleft: "nesw"}; - const cornerHandles = [["top", "left"], ["top", "right"], ["bottom", "right"], ["bottom", "left"]]; + const cornerHandles = [ + ["top", "left"], + ["top", "right"], + ["bottom", "right"], + ["bottom", "left"] + ]; const [topLeftHandle, topRightHandle, bottomRightHandle, bottomLeftHandle] = cornerHandles.map(corner => handlesGroup .append("rect") diff --git a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js index 084d68958a..94e91b72de 100644 --- a/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js +++ b/packages/perspective-viewer-d3fc/src/js/series/treemap/treemapLevelCalculation.js @@ -15,11 +15,13 @@ import {textOpacity} from "./treemapLabel"; const includesAllCrossValues = (d, crossValues) => crossValues.every(val => d.crossValue.split("|").includes(val)); export function calculateSubTreeMap(d, crossValues, nodesMerge, treemapLevel, rootNode, treemapDiv) { - // We can approximate coordinates for most of the tree which will be shunted beyond the viewable area. - // This approach alone results in excessive margins as one goes deeper into the treemap. + // We can approximate coordinates for most of the tree which will be shunted + // beyond the viewable area. This approach alone results in excessive + // margins as one goes deeper into the treemap. approximateAttributesForAllNodes(d, crossValues, nodesMerge, treemapLevel, rootNode); d.mapLevel[treemapLevel].levelRoot = true; - // Use the pre-existing d3 mechanism to calculate the subtree for the viewable area. + // Use the pre-existing d3 mechanism to calculate the subtree for the + // viewable area. recalculateVisibleSubTreeCoordinates(d, treemapDiv.node().getBoundingClientRect().width, treemapDiv.node().getBoundingClientRect().height, treemapLevel); calculateTextOpacities(nodesMerge, treemapLevel); diff --git a/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js b/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js index ba08e6b086..60e689edb3 100644 --- a/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js +++ b/packages/perspective-viewer-d3fc/src/js/zoom/zoomableChart.js @@ -63,9 +63,18 @@ export default () => { selection.select(chartPlotArea).call(zoom.transform, d3.zoomIdentity.translate(x, y).scale(k)); }; - oneYear.on("click", dateClick((start, end) => end.setYear(start.getFullYear() + 1))); - sixMonths.on("click", dateClick((start, end) => end.setMonth(start.getMonth() + 6))); - oneMonth.on("click", dateClick((start, end) => end.setMonth(start.getMonth() + 1))); + oneYear.on( + "click", + dateClick((start, end) => end.setYear(start.getFullYear() + 1)) + ); + sixMonths.on( + "click", + dateClick((start, end) => end.setMonth(start.getMonth() + 6)) + ); + oneMonth.on( + "click", + dateClick((start, end) => end.setMonth(start.getMonth() + 1)) + ); } }); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js index b03cf87d17..37cee679b1 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/data/groupData.spec.js @@ -19,7 +19,11 @@ describe("groupAndStackData should", () => { test("use settings data if no specific data is supplied", () => { const settings = { crossValues: [{name: "cross1", type: "string"}], - data: [{value1: 10, __ROW_PATH__: ["CROSS1.1"]}, {value1: 20, __ROW_PATH__: ["CROSS1.2"]}, {value1: 30, __ROW_PATH__: ["CROSS1.1"]}], + data: [ + {value1: 10, __ROW_PATH__: ["CROSS1.1"]}, + {value1: 20, __ROW_PATH__: ["CROSS1.2"]}, + {value1: 30, __ROW_PATH__: ["CROSS1.1"]} + ], mainValues: [{name: "value1", type: "integer"}], splitValues: [] }; @@ -29,7 +33,11 @@ describe("groupAndStackData should", () => { }); test("use specific data if supplied", () => { - const suppliedData = [{value1: 10, __ROW_PATH__: ["CROSS1.1"]}, {value1: 20, __ROW_PATH__: ["CROSS1.2"]}, {value1: 30, __ROW_PATH__: ["CROSS1.1"]}]; + const suppliedData = [ + {value1: 10, __ROW_PATH__: ["CROSS1.1"]}, + {value1: 20, __ROW_PATH__: ["CROSS1.2"]}, + {value1: 30, __ROW_PATH__: ["CROSS1.1"]} + ]; const settings = { crossValues: [{name: "cross1", type: "string"}], data: suppliedData, @@ -37,7 +45,10 @@ describe("groupAndStackData should", () => { splitValues: [] }; - const extraData = suppliedData.concat([{value1: 40, __ROW_PATH__: ["CROSS1.3"]}, {value1: 50, __ROW_PATH__: ["CROSS1.3"]}]); + const extraData = suppliedData.concat([ + {value1: 40, __ROW_PATH__: ["CROSS1.3"]}, + {value1: 50, __ROW_PATH__: ["CROSS1.3"]} + ]); const groupedResult = groupAndStackData(settings, extraData); expect(groupedResult[0].length).toEqual(5); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js index 5b2c445aa0..0f7e1f0897 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/series/colorStyles.spec.js @@ -70,8 +70,18 @@ describe("colorStyles should", () => { initialiseStyles(container.node(), settings); const result = settings.colorStyles; - expect(result.gradient.full).toEqual([[0, "rgba(77, 52, 47, 0.5)"], [0.5, "rgba(240, 240, 240, 0.5)"], [1, "rgba(26, 35, 126, 0.5)"]]); - expect(result.gradient.positive).toEqual([[0, "rgba(220, 237, 200, 0.5)"], [1, "rgba(26, 35, 126, 0.5)"]]); - expect(result.gradient.negative).toEqual([[0, "rgba(77, 52, 47, 0.5)"], [1, "rgba(254, 235, 101, 0.5)"]]); + expect(result.gradient.full).toEqual([ + [0, "rgba(77, 52, 47, 0.5)"], + [0.5, "rgba(240, 240, 240, 0.5)"], + [1, "rgba(26, 35, 126, 0.5)"] + ]); + expect(result.gradient.positive).toEqual([ + [0, "rgba(220, 237, 200, 0.5)"], + [1, "rgba(26, 35, 126, 0.5)"] + ]); + expect(result.gradient.negative).toEqual([ + [0, "rgba(77, 52, 47, 0.5)"], + [1, "rgba(254, 235, 101, 0.5)"] + ]); }); }); diff --git a/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js b/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js index 1faa90b111..b154092482 100644 --- a/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js +++ b/packages/perspective-viewer-d3fc/test/js/unit/series/seriesRange.spec.js @@ -13,9 +13,19 @@ import * as sinon from "sinon"; const settings = { colorStyles: { gradient: { - positive: [[0, "rgb(0, 0, 0)"], [1, "rgb(100, 0, 0)"]], - negative: [[0, "rgb(0, 0, 0)"], [1, "rgb(0, 100, 0)"]], - full: [[0, "rgb(100, 0, 0)"], [0.5, "rgb(0, 0, 0)"], [1, "rgb(0, 0, 100)"]] + positive: [ + [0, "rgb(0, 0, 0)"], + [1, "rgb(100, 0, 0)"] + ], + negative: [ + [0, "rgb(0, 0, 0)"], + [1, "rgb(0, 100, 0)"] + ], + full: [ + [0, "rgb(100, 0, 0)"], + [0.5, "rgb(0, 0, 0)"], + [1, "rgb(0, 0, 100)"] + ] } } }; diff --git a/packages/perspective-viewer-highcharts/src/js/highcharts/config.js b/packages/perspective-viewer-highcharts/src/js/highcharts/config.js index e8db50103e..25479d973d 100644 --- a/packages/perspective-viewer-highcharts/src/js/highcharts/config.js +++ b/packages/perspective-viewer-highcharts/src/js/highcharts/config.js @@ -128,6 +128,8 @@ export function default_config(aggregates, mode) { type = "heatmap"; } + /* eslint-disable max-len */ + // let new_radius = 0; // if (mode === 'scatter') { // new_radius = Math.min(8, Math.max(4, Math.floor((this.clientWidth + this.clientHeight) / Math.max(300, series[0].data.length / 3)))); @@ -135,6 +137,9 @@ export function default_config(aggregates, mode) { // // read this + define chart schema using _view() + + /* eslint-enable max-len */ + const that = this; const config = this._config; diff --git a/packages/perspective-viewer-highcharts/src/js/highcharts/draw.js b/packages/perspective-viewer-highcharts/src/js/highcharts/draw.js index 8122a8867f..ad0542aa14 100644 --- a/packages/perspective-viewer-highcharts/src/js/highcharts/draw.js +++ b/packages/perspective-viewer-highcharts/src/js/highcharts/draw.js @@ -53,7 +53,7 @@ export const draw = (mode, set_config, restyle) => const columns = config.columns; const [schema, tschema] = await Promise.all([view.schema(false), this._table.schema(false, false)]); - let js, element; + let element; if (task.cancelled) { return; diff --git a/packages/perspective-viewer-highcharts/src/js/highcharts/externals.js b/packages/perspective-viewer-highcharts/src/js/highcharts/externals.js index 6b491b1c5f..64158bf45e 100644 --- a/packages/perspective-viewer-highcharts/src/js/highcharts/externals.js +++ b/packages/perspective-viewer-highcharts/src/js/highcharts/externals.js @@ -187,7 +187,8 @@ Highcharts.setOptions({ grid = axis.labelsGrid = axis.chart.renderer .path() .attr({ - // #58: use tickWidth/tickColor instead of lineWidth/lineColor: + // #58: use tickWidth/tickColor instead of + // lineWidth/lineColor: strokeWidth: tickWidth, // < 4.0.3 "stroke-width": tickWidth, // 4.0.3+ #30 stroke: options.tickColor || "" // for styled mode (tickColor === undefined) diff --git a/packages/perspective-viewer-highcharts/src/js/highcharts/series.js b/packages/perspective-viewer-highcharts/src/js/highcharts/series.js index ff30cf3626..37283f98a2 100644 --- a/packages/perspective-viewer-highcharts/src/js/highcharts/series.js +++ b/packages/perspective-viewer-highcharts/src/js/highcharts/series.js @@ -202,7 +202,11 @@ export function make_y_data(cols, pivots) { } else { sname = sname.slice(0, sname.length - 1).join(", ") || " "; } - let s = column_to_series(col.data.map(val => (val === undefined || val === "" ? null : val)), sname, gname); + let s = column_to_series( + col.data.map(val => (val === undefined || val === "" ? null : val)), + sname, + gname + ); series.push(s); } diff --git a/packages/perspective-viewer-hypergrid/src/js/editors.js b/packages/perspective-viewer-hypergrid/src/js/editors.js index a27b9182a7..7ba3a5cfb8 100644 --- a/packages/perspective-viewer-hypergrid/src/js/editors.js +++ b/packages/perspective-viewer-hypergrid/src/js/editors.js @@ -62,7 +62,8 @@ function setEditorValueDatetime(x) { function setEditorValueText(updated) { // Move keyup so nav and edit don't conflict this.input.addEventListener("keydown", keydown.bind(this)); - // refire mouseover so hover does not go away when mouse stays over edit cell + // refire mouseover so hover does not go away when mouse stays over edit + // cell this.input.addEventListener("mouseover", event => { var mouseMoveEvent = document.createEvent("MouseEvents"); mouseMoveEvent.initMouseEvent( diff --git a/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js b/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js index 9e56a7d84f..43388b56b9 100644 --- a/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js +++ b/packages/perspective-viewer-hypergrid/src/js/perspective-plugin.js @@ -98,9 +98,10 @@ function setPSP(payload, force = false) { this.grid.properties.showTreeColumn = payload.isTree; - // Following call to setData signals the grid to call createColumns and dispatch the - // fin-hypergrid-schema-loaded event (in that order). Here we inject a createColumns override - // into `this` (behavior instance) to complete the setup before the event is dispatched. + // Following call to setData signals the grid to call createColumns and + // dispatch the fin-hypergrid-schema-loaded event (in that order). Here we + // inject a createColumns override into `this` (behavior instance) to + // complete the setup before the event is dispatched. this.createColumns = createColumns; this.refreshColumns = refreshColumns; @@ -233,8 +234,8 @@ function sortColumn(event) { const item_index = config.sort.findIndex(item => item[0] === column_name.trim()); const already_sorted = item_index > -1; - // shift key to enable abs sorting - // alt key to not remove already sorted columns + // shift key to enable abs sorting alt key to not remove already sorted + // columns const abs_sorting = event.detail.keys && (event.detail.keys.indexOf("ALTSHIFT") > -1 || event.detail.keys.indexOf("ALT") > -1) && column.type !== "string"; const shift_pressed = event.detail.keys && (event.detail.keys.indexOf("ALTSHIFT") > -1 || event.detail.keys.indexOf("SHIFT") > -1); let new_sort_direction; @@ -249,14 +250,16 @@ function sortColumn(event) { new_sort_direction = viewer._increment_sort("none", column_sorting, abs_sorting); } - //if alt pressed and column is already sorted, we change the sort for the column and leave the rest as is + //if alt pressed and column is already sorted, we change the sort for the + //column and leave the rest as is if (shift_pressed && already_sorted) { if (new_sort_direction === "none") { config.sort.splice(item_index, 1); } viewer.sort = JSON.stringify(config.sort); } else if (shift_pressed) { - // if alt key is pressed and column is NOT already selected, append the new sort column + // if alt key is pressed and column is NOT already selected, append the + // new sort column config.sort.push([column_name, new_sort_direction]); viewer.sort = JSON.stringify(config.sort); } else { @@ -379,7 +382,8 @@ export const install = function(grid) { return; } - //we have repeated a click in the same spot deslect the value from last time + //we have repeated a click in the same spot deslect the value from last + //time if (hasCTRL && x === mousePoint.x && y === mousePoint.y) { grid.clearMostRecentSelection(); grid.popMouseDown(); @@ -473,8 +477,8 @@ export const install = function(grid) { const height = (this.height = Math.floor(this.div.clientHeight)); //fix ala sir spinka, see - //http://www.html5rocks.com/en/tutorials/canvas/hidpi/ - //just add 'hdpi' as an attribute to the fin-canvas tag + //http://www.html5rocks.com/en/tutorials/canvas/hidpi/ just add 'hdpi' + //as an attribute to the fin-canvas tag let ratio = 1; const isHIDPI = window.devicePixelRatio && this.component.properties.useHiDPI; if (isHIDPI) { @@ -492,7 +496,8 @@ export const install = function(grid) { if (height * ratio !== this.canvas.height || width * ratio !== this.canvas.width || force) { while (render) { if (!this.component.grid.behavior.dataModel._view) { - // If we are awaiting this grid's initialization, yield until it is ready. + // If we are awaiting this grid's initialization, yield + // until it is ready. await new Promise(setTimeout); } render = await new Promise(resolve => this.component.grid.behavior.dataModel.fetchData(undefined, resolve)); diff --git a/packages/perspective-viewer/src/js/computed_column.js b/packages/perspective-viewer/src/js/computed_column.js index 75a80c1331..c321afd374 100644 --- a/packages/perspective-viewer/src/js/computed_column.js +++ b/packages/perspective-viewer/src/js/computed_column.js @@ -348,7 +348,8 @@ class ComputedColumn extends HTMLElement { const current_column_type = current_column.getAttribute("type"); event.swapTarget = this.state.swap_target; - // take the column at the drop target, and set it to the column being swapped + // take the column at the drop target, and set it to the column + // being swapped this._set_input_column(event, current_column_name, current_column_type); // reset swap_target and currentTarget @@ -372,7 +373,8 @@ class ComputedColumn extends HTMLElement { // Called when the column passes over and then leaves the drop target _pass_column(event) { const src = event.currentTarget; - // are we inside the column? if we are, prevent further calls which cause flickering + // are we inside the column? if we are, prevent further calls which + // cause flickering const bounds = src.getBoundingClientRect(); const inside_x = event.pageX >= bounds.left && event.pageX <= bounds.right - 2; const inside_y = event.pageY >= bounds.top && event.pageY <= bounds.bottom - 2; @@ -528,7 +530,8 @@ class ComputedColumn extends HTMLElement { let reset_inputs = true; if (this.state["computation"]) { - // do we need to reset the input? if types/num_params differ then yes + // do we need to reset the input? if types/num_params differ then + // yes reset_inputs = input_type !== this.state["computation"].input_type || num_params !== this.state["computation"].num_params; } @@ -615,7 +618,8 @@ class ComputedColumn extends HTMLElement { this._computation_selector = this.shadowRoot.querySelector("#psp-cc-computation__select"); this._computation_type = this.shadowRoot.querySelector("#psp-cc-computation__type"); this._input_columns = this.shadowRoot.querySelector("#psp-cc-computation-inputs"); - //this._delete_button = this.shadowRoot.querySelector('#psp-cc-button-delete'); + //this._delete_button = + //this.shadowRoot.querySelector('#psp-cc-button-delete'); this._save_button = this.shadowRoot.querySelector("#psp-cc-button-save"); } @@ -627,7 +631,8 @@ class ComputedColumn extends HTMLElement { this.state["name_edited"] = this._column_name_input.innerText && this._column_name_input.innerText.length > 0; this._set_column_name(event); }); - //this._delete_button.addEventListener('click', this._delete_computed_column.bind(this)); + //this._delete_button.addEventListener('click', + //this._delete_computed_column.bind(this)); this._save_button.addEventListener("click", this._save_computed_column.bind(this)); } } diff --git a/packages/perspective-viewer/src/js/custom_styles.js b/packages/perspective-viewer/src/js/custom_styles.js index b679f9b56b..e4acf7744b 100644 --- a/packages/perspective-viewer/src/js/custom_styles.js +++ b/packages/perspective-viewer/src/js/custom_styles.js @@ -42,9 +42,9 @@ function get_font(elem, title) { const font_size = get_style(elem, `${title}font-size`); const font_family = get_style(elem, `${title}font-family`); - // FIXME this sucks but it is difficult to partially apply fonts in Hypergrid's API - // Fonts will not be picked up unless both font-size and font-family are defined - // for a specific scope. + // FIXME this sucks but it is difficult to partially apply fonts in + // Hypergrid's API Fonts will not be picked up unless both font-size and + // font-family are defined for a specific scope. if (!font_size || !font_family) { return undefined; } diff --git a/packages/perspective-viewer/src/js/polyfill.js b/packages/perspective-viewer/src/js/polyfill.js index 87b75a56c6..46534377a4 100644 --- a/packages/perspective-viewer/src/js/polyfill.js +++ b/packages/perspective-viewer/src/js/polyfill.js @@ -4,11 +4,22 @@ Forked from https://github.com/timruffles/mobile-drag-drop/ v2.3.0-rc.2 Copyright (c) 2013 Tim Ruffles -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ const CLASS_PREFIX = "dnd-poly-"; diff --git a/packages/perspective-viewer/src/js/row.js b/packages/perspective-viewer/src/js/row.js index 152910ba53..4651729d4d 100644 --- a/packages/perspective-viewer/src/js/row.js +++ b/packages/perspective-viewer/src/js/row.js @@ -153,8 +153,10 @@ class Row extends HTMLElement { set computed_column(c) { // const data = this._get_computed_data(); - // const computed_input_column = this.shadowRoot.querySelector('#computed_input_column'); - // const computation_name = this.shadowRoot.querySelector('#computation_name'); + // const computed_input_column = + // this.shadowRoot.querySelector('#computed_input_column'); + // const computation_name = + // this.shadowRoot.querySelector('#computation_name'); // computation_name.textContent = data.computation.name; // computed_input_column.textContent = data.input_column; } diff --git a/packages/perspective-viewer/src/js/viewer.js b/packages/perspective-viewer/src/js/viewer.js index abdf482d09..20bb1dc00b 100755 --- a/packages/perspective-viewer/src/js/viewer.js +++ b/packages/perspective-viewer/src/js/viewer.js @@ -43,9 +43,10 @@ const PERSISTENT_ATTRIBUTES = ["editable", "plugin", "row-pivots", "column-pivot * instead, instances of the class are created through the Custom Elements DOM * API. * - * Properties of an instance of this class, such as {@link module:perspective_viewer~PerspectiveViewer#columns}, - * are reflected on the DOM element as Attributes, and should be accessed as - * such - e.g. `instance.setAttribute("columns", JSON.stringify(["a", "b"]))`. + * Properties of an instance of this class, such as + * {@link module:perspective_viewer~PerspectiveViewer#columns}, are reflected on + * the DOM element as Attributes, and should be accessed as such - e.g. + * `instance.setAttribute("columns", JSON.stringify(["a", "b"]))`. * * @class PerspectiveViewer * @extends {HTMLElement} @@ -88,8 +89,8 @@ class PerspectiveViewer extends ActionElement { * * @kind member * @type {array} Array of arrays tuples of column name and - * direction, where the possible values are "asc", "desc", "asc abs", - * "desc abs" and "none". + * direction, where the possible values are "asc", "desc", "asc abs", "desc + * abs" and "none". * @fires PerspectiveViewer#perspective-config-update * @example via Javascript DOM * let elem = document.getElementById('my_viewer'); @@ -157,6 +158,8 @@ class PerspectiveViewer extends ActionElement { this._debounce_update(); } + /* eslint-disable max-len */ + /** * The set of visible columns. * @@ -198,6 +201,8 @@ class PerspectiveViewer extends ActionElement { })(); } + /* eslint-enable max-len */ + /** * The set of column aggregate configurations. * @@ -212,7 +217,8 @@ class PerspectiveViewer extends ActionElement { * let elem = document.getElementById('my_viewer'); * elem.setAttribute('aggregates', JSON.stringify({x: "distinct count"})); * @example via HTML - * + * + * */ @json_attribute aggregates(show) { @@ -238,13 +244,11 @@ class PerspectiveViewer extends ActionElement { * The set of column filter configurations. * * @kind member - * @type {array} filters An arry of filter config objects. A filter - * config object is an array of three elements: - * * The column name. - * * The filter operation as a string. See - * {@link perspective/src/js/config/constants.js} - * * The filter argument, as a string, float or Array as the - * filter operation demands. + * @type {array} filters An arry of filter config objects. A filter config + * object is an array of three elements: * The column name. * The filter + * operation as a string. See + * {@link perspective/src/js/config/constants.js} * The filter argument, as + * a string, float or Array as the filter operation demands. * @fires PerspectiveViewer#perspective-config-update * @example via Javascript DOM * let filters = [ @@ -254,7 +258,8 @@ class PerspectiveViewer extends ActionElement { * let elem = document.getElementById('my_viewer'); * elem.setAttribute('filters', JSON.stringify(filters)); * @example via HTML - * + * + * */ @array_attribute filters(filters) { @@ -418,7 +423,8 @@ class PerspectiveViewer extends ActionElement { /** * This element's `perspective.table.view` instance. The instance itself - * will change after every `PerspectiveViewer#perspective-config-update` event. + * will change after every `PerspectiveViewer#perspective-config-update` + * event. * * @readonly */ @@ -432,14 +438,10 @@ class PerspectiveViewer extends ActionElement { * * @param {any} data The data to load. Works with the same input types * supported by `perspective.table`. - * @returns {Promise} A promise which resolves once the data is - * loaded and a `perspective.view` has been created. - * @fires module:perspective_viewer~PerspectiveViewer#perspective-click PerspectiveViewer#perspective-view-update - * @example Load JSON - * const my_viewer = document.getElementById('#my_viewer'); - * my_viewer.load([ - * {x: 1, y: 'a'}, - * {x: 2, y: 'b'} + * @returns {Promise} A promise which resolves once the data is loaded + * and a `perspective.view` has been created. + * @fires module:perspective_viewer~PerspectiveViewer#perspective-click + * PerspectiveViewer#perspective-view-update * ]); * @example Load CSV * const my_viewer = document.getElementById('#my_viewer'); diff --git a/packages/perspective-viewer/src/js/viewer/action_element.js b/packages/perspective-viewer/src/js/viewer/action_element.js index 499d456bdd..bdfa249bed 100644 --- a/packages/perspective-viewer/src/js/viewer/action_element.js +++ b/packages/perspective-viewer/src/js/viewer/action_element.js @@ -267,7 +267,10 @@ export class ActionElement extends DomElement { this._add_computed_column.addEventListener("click", this._open_computed_column.bind(this)); this._computed_column.addEventListener("perspective-computed-column-save", this._validate_computed_column.bind(this)); this._computed_column.addEventListener("perspective-computed-column-update", this._set_computed_column_input.bind(this)); - //this._side_panel.addEventListener('perspective-computed-column-edit', this._open_computed_column.bind(this)); + // this._side_panel.addEventListener(' + // perspective-computed-column-edit', + // this._open_computed_column.bind(this) + // ); this._config_button.addEventListener("mousedown", this._toggle_config.bind(this)); this._config_button.addEventListener("contextmenu", this._show_context_menu.bind(this)); this._reset_button.addEventListener("click", this.reset.bind(this)); diff --git a/packages/perspective-viewer/src/js/viewer/renderers.js b/packages/perspective-viewer/src/js/viewer/renderers.js index b5b93fefaa..c02c4a0eed 100644 --- a/packages/perspective-viewer/src/js/viewer/renderers.js +++ b/packages/perspective-viewer/src/js/viewer/renderers.js @@ -17,11 +17,14 @@ export const renderers = new (class { * * @param {string} name The logical unique name of the plugin. This will be * used to set the component's `view` attribute. - * @param {object} plugin An object with this plugin's prototype. Valid keys are: - * name : The display name for this plugin. - * create (required) : The creation function - may return a `Promise`. - * delete : The deletion function. - * mode : The selection mode - may be "toggle" or "select". + * @param {object} plugin An object with this plugin's prototype. + * Valid keys are: + * @param {string} plugin.name The display name for this plugin. + * @param {string} plugin.create (required) The creation function - may + * return a `Promise`. + * @param {string} plugin.delete The deletion function. + * @param {string} plugin.mode The selection mode - may be "toggle" or + * "select". */ registerPlugin(name, plugin) { if (RENDERERS[name]) { diff --git a/packages/perspective-viewer/test/js/unit/dom_element.spec.js b/packages/perspective-viewer/test/js/unit/dom_element.spec.js index 3fdbd64547..2508e5e3f0 100644 --- a/packages/perspective-viewer/test/js/unit/dom_element.spec.js +++ b/packages/perspective-viewer/test/js/unit/dom_element.spec.js @@ -26,10 +26,7 @@ describe(DomElement, () => { }); test("the first value and null values are filtered out", () => { - expect(dom_element._autocomplete_choices(json_choices)).toEqual([ - ["somestring"], - ["otherstring"] - ]); + expect(dom_element._autocomplete_choices(json_choices)).toEqual([["somestring"], ["otherstring"]]); }); }); }); diff --git a/packages/perspective/src/config/common.config.js b/packages/perspective/src/config/common.config.js index 74df803141..38f2e2ef1f 100644 --- a/packages/perspective/src/config/common.config.js +++ b/packages/perspective/src/config/common.config.js @@ -29,7 +29,8 @@ module.exports = function({build_worker, no_minify} = {}) { options: {} } }, - // FIXME Workaround for performance regression in @apache-arrow 4.0 + // FIXME Workaround for performance regression in @apache-arrow + // 4.0 { test: /\.js$/, include: /\@apache-arrow[/\\]es5-esm/, diff --git a/packages/perspective/src/js/api/client.js b/packages/perspective/src/js/api/client.js index 92df2db29f..f3b4335004 100644 --- a/packages/perspective/src/js/api/client.js +++ b/packages/perspective/src/js/api/client.js @@ -12,11 +12,11 @@ import {proxy_view} from "./view_api.js"; import {bindall} from "../utils.js"; /** - * Perspective's worker API handles and processes asynchronous messages, interfacing with the Perspective host class. - * - * Child classes must implement the `send()` interface, which defines how messages are dispatched in different contexts. - * - * `handlers` is a dictionary of resolve/reject callbacks for each method the worker receives. + * Perspective's worker API handles and processes asynchronous messages, + * interfacing with the Perspective host class. Child classes must implement + * the `send()` interface, which defines how messages are dispatched in + * different contexts. `handlers` is a dictionary of resolve/reject callbacks + * for each method the worker receives. * * @export */ @@ -75,8 +75,10 @@ export class Client { } /** - * Given the name of a table that is hosted on the server (e.g. using `perspective-python` or `perspective` in NodeJS), - * return a `table` instance that sends all operations and instructions to the `table` on the server. + * Given the name of a table that is hosted on the server (e.g. using + * `perspective-python` or `perspective` in NodeJS), return a `table` + * instance that sends all operations and instructions to the `table` on the + * server. * * @param {string} name */ @@ -89,7 +91,8 @@ export class Client { } /** - * Handle a command from Perspective. If the Client is not initialized, initialize it and dispatch the `perspective-ready` event. + * Handle a command from Perspective. If the Client is not initialized, + * initialize it and dispatch the `perspective-ready` event. * * Otherwise, reject or resolve the incoming command. */ diff --git a/packages/perspective/src/js/api/server.js b/packages/perspective/src/js/api/server.js index 9e2fa5c52e..b02b79fd33 100644 --- a/packages/perspective/src/js/api/server.js +++ b/packages/perspective/src/js/api/server.js @@ -22,10 +22,11 @@ function error_to_json(error) { } /** - * The base class for Perspective's async API. It initializes and keeps track of tables, views, and processes - * messages from the user into Perspective. + * The base class for Perspective's async API. It initializes and keeps track of + * tables, views, and processes messages from the user into Perspective. * - * Child classes must implement the `post()` interface, which defines how the worker sends messages. + * Child classes must implement the `post()` interface, which defines how the + * worker sends messages. */ export class Server { constructor(perspective) { @@ -37,7 +38,8 @@ export class Server { } /** - * `Server` must be extended and the `post` method implemented before it can be initialized. + * `Server` must be extended and the `post` method implemented before it can + * be initialized. */ init(msg) { if (msg.config) { @@ -68,10 +70,12 @@ export class Server { } /** - * Given a message, execute its instructions. This method is the dispatcher for all Perspective actions, - * including table/view creation, deletion, and all method calls to/from the table and view. + * Given a message, execute its instructions. This method is the dispatcher + * for all Perspective actions, including table/view creation, deletion, and + * all method calls to/from the table and view. * - * @param {*} msg an Object containing `cmd` (a String instruction) and associated data for that instruction + * @param {*} msg an Object containing `cmd` (a String instruction) and + * associated data for that instruction * @param {*} client_id */ process(msg, client_id) { @@ -219,7 +223,8 @@ export class Server { msg.cmd === "table_method" ? (obj = this._tables[msg.name]) : (obj = this._views[msg.name]); if (!obj && msg.cmd === "view_method") { - // cannot have a host without a table, but can have a host without a view + // cannot have a host without a table, but can have a host without a + // view this.process_error(msg, {message: "View is not initialized"}); return; } diff --git a/packages/perspective/src/js/api/table_api.js b/packages/perspective/src/js/api/table_api.js index 3a52b2e000..b6311a6785 100644 --- a/packages/perspective/src/js/api/table_api.js +++ b/packages/perspective/src/js/api/table_api.js @@ -12,7 +12,8 @@ import {view} from "./view_api.js"; import {bindall} from "../utils.js"; /** - * Construct a proxy for the table object by creating a "table" message and sending it through the worker. + * Construct a proxy for the table object by creating a "table" message and + * sending it through the worker. * * @param {*} worker * @param {*} data @@ -55,7 +56,8 @@ export function table(worker, data, options) { table.prototype.type = "table"; /** - * Create a new computed table, serializing each computation to a string for processing by the engine. + * Create a new computed table, serializing each computation to a string for + * processing by the engine. * * @param {*} worker * @param {*} computed @@ -83,7 +85,8 @@ function computed_table(worker, computed, name) { } /** - * Create a reference to a Perspective table at `worker` for use by remote clients. + * Create a reference to a Perspective table at `worker` for use by remote + * clients. * * @param {worker} worker the Web Worker at which the table is located. * @param {String} name a unique name for the table. @@ -105,7 +108,8 @@ table.prototype.view = function(config) { return new view(this._worker, this._name, config); }; -// Dispatch table methods that do not create new objects (getters, setters etc.) to the queue for processing. +// Dispatch table methods that do not create new objects (getters, setters etc.) +// to the queue for processing. table.prototype.compute = async_queue("compute", "table_method"); diff --git a/packages/perspective/src/js/api/view_api.js b/packages/perspective/src/js/api/view_api.js index 59817eb51b..66b8322d5f 100644 --- a/packages/perspective/src/js/api/view_api.js +++ b/packages/perspective/src/js/api/view_api.js @@ -11,7 +11,8 @@ import {subscribe, unsubscribe, async_queue} from "./dispatch.js"; import {bindall} from "../utils.js"; /** - * Construct a proxy for the view object by creating a "view" message and sending it through the worker. + * Construct a proxy for the view object by creating a "view" message and + * sending it through the worker. * * @param {*} worker * @param {*} table_name @@ -44,7 +45,8 @@ export function proxy_view(worker, name) { proxy_view.prototype = view.prototype; -// Send view methods that do not create new objects (getters, setters etc.) to the queue for processing. +// Send view methods that do not create new objects (getters, setters etc.) to +// the queue for processing. view.prototype.get_config = async_queue("get_config"); diff --git a/packages/perspective/src/js/config/settings.js b/packages/perspective/src/js/config/settings.js index fdcdeac4a0..f068203fdf 100644 --- a/packages/perspective/src/js/config/settings.js +++ b/packages/perspective/src/js/config/settings.js @@ -31,9 +31,9 @@ module.exports.default = { aggregate: "sum", /** - * The format object for this type. Can be either an `toLocaleString()` - * `options` object for this type (or supertype), or a function - * which returns the formatted string for this type. + * The format object for this type. Can be either an + * `toLocaleString()` `options` object for this type (or supertype), + * or a function which returns the formatted string for this type. */ format: { style: "decimal", diff --git a/packages/perspective/src/js/data_accessor/index.js b/packages/perspective/src/js/data_accessor/index.js index 22ab190a75..43e5c0a1c3 100644 --- a/packages/perspective/src/js/data_accessor/index.js +++ b/packages/perspective/src/js/data_accessor/index.js @@ -129,8 +129,8 @@ export class DataAccessor { } /** - * Resets the internal state of the accessor, preventing - * collisions with previously set data. + * Resets the internal state of the accessor, preventing collisions with + * previously set data. * * @private */ @@ -141,8 +141,8 @@ export class DataAccessor { } /** - * Links the accessor to a package of data for processing, - * calculating its format and size. + * Links the accessor to a package of data for processing, calculating its + * format and size. * * @private * @param {object} data diff --git a/packages/perspective/src/js/emscripten.js b/packages/perspective/src/js/emscripten.js index 3d78b924e5..1ad10993f5 100644 --- a/packages/perspective/src/js/emscripten.js +++ b/packages/perspective/src/js/emscripten.js @@ -7,9 +7,8 @@ * */ -/** Translation layer - * Interface between C++ and JS to handle conversions/data structures that - * were previously handled in non-portable perspective.js +/** Translation layer Interface between C++ and JS to handle conversions/data + * structures that were previously handled in non-portable perspective.js */ export const extract_vector = function(vector) { @@ -37,8 +36,9 @@ export const extract_map = function(map) { }; /** - * Given a C++ vector constructed in Emscripten, fill it with data. Assume that data types are already validated, - * thus Emscripten will throw an error if the vector is filled with the wrong type of data. + * Given a C++ vector constructed in Emscripten, fill it with data. Assume that + * data types are already validated, thus Emscripten will throw an error if the + * vector is filled with the wrong type of data. * * @param {*} vector the `std::vector` to be filled * @param {Array} arr the `Array` from which to draw data diff --git a/packages/perspective/src/js/perspective.js b/packages/perspective/src/js/perspective.js index b8b3b44287..7e448259de 100644 --- a/packages/perspective/src/js/perspective.js +++ b/packages/perspective/src/js/perspective.js @@ -24,7 +24,8 @@ import {Utf8, Float64, Int32, Bool, TimestampMillisecond, Dictionary} from "@apa import formatters from "./view_formatters"; import papaparse from "papaparse"; -// IE fix - chrono::steady_clock depends on performance.now() which does not exist in IE workers +// IE fix - chrono::steady_clock depends on performance.now() which does not +// exist in IE workers if (global.performance === undefined) { global.performance = {now: Date.now}; } @@ -41,7 +42,7 @@ export default function(Module) { let __MODULE__ = Module; let accessor = new DataAccessor(); - /****************************************************************************** + /*************************************************************************** * * Private * @@ -71,13 +72,19 @@ export default function(Module) { /** * Common logic for creating and registering a Table. * - * @param {DataAccessor|Object[]} accessor - the data we provide to the Table - * @param {Object} _Table - `undefined` if a new table will be created, or an `std::shared_ptr` if updating - * @param {Object[]} computed - An array of computed columns to be applied to the table. + * @param {DataAccessor|Object[]} accessor - the data we provide to the + * Table + * @param {Object} _Table - `undefined` if a new table will be created, or + * an `std::shared_ptr
` if updating + * @param {Object[]} computed - An array of computed columns to be applied + * to the table. * @param {String} index - A column name to be used as a primary key. - * @param {Number} limit - an upper bound on the number of rows in the table. If set, new rows that exceed the limit start overwriting old ones from row 0. + * @param {Number} limit - an upper bound on the number of rows in the + * table. If set, new rows that exceed the limit start overwriting old ones + * from row 0. * @param {t_op} op - either `OP_INSERT` or `OP_DELETE` - * @param {boolean} is_update - true if we are updating an already-created table + * @param {boolean} is_update - true if we are updating an already-created + * table * @param {boolean} is_arrow - true if the dataset is in the Arrow format * * @private @@ -98,7 +105,7 @@ export default function(Module) { return _Table; } - /****************************************************************************** + /*************************************************************************** * * View * @@ -106,11 +113,13 @@ export default function(Module) { /** * A View object represents a specific transform (configuration or pivot, - * filter, sort, etc) configuration on an underlying {@link module:perspective~table}. A View - * receives all updates from the {@link module:perspective~table} from which it is derived, and - * can be serialized to JSON or trigger a callback when it is updated. View + * filter, sort, etc) configuration on an underlying + * {@link module:perspective~table}. A View receives all updates from the + * {@link module:perspective~table} from which it is derived, and can be + * serialized to JSON or trigger a callback when it is updated. View * objects are immutable, and will remain in memory and actively process - * updates until its {@link module:perspective~view#delete} method is called. + * updates until its {@link module:perspective~view#delete} method is + * called. * * Note This constructor is not public - Views are created * by invoking the {@link module:perspective~table#view} method. @@ -147,19 +156,21 @@ export default function(Module) { } /** - * A copy of the config object passed to the {@link table#view} method - * which created this {@link module:perspective~view}. + * A copy of the config object passed to the {@link table#view} method which + * created this {@link module:perspective~view}. * - * @returns {Promise} Shared the same key/values properties as {@link module:perspective~view} + * @returns {Promise} Shared the same key/values properties as + * {@link module:perspective~view} */ view.prototype.get_config = function() { return JSON.parse(JSON.stringify(this.config)); }; /** - * Delete this {@link module:perspective~view} and clean up all resources associated with it. - * View objects do not stop consuming resources or processing updates when - * they are garbage collected - you must call this method to reclaim these. + * Delete this {@link module:perspective~view} and clean up all resources + * associated with it. View objects do not stop consuming resources or + * processing updates when they are garbage collected - you must call this + * method to reclaim these. * * @async */ @@ -212,7 +223,8 @@ export default function(Module) { } const extract_vector_scalar = function(vector) { - // handles deletion already - do not call delete() on the input vector again + // handles deletion already - do not call delete() on the input vector + // again let extracted = []; for (let i = 0; i < vector.size(); i++) { let item = vector.get(i); @@ -223,15 +235,17 @@ export default function(Module) { }; /** - * The schema of this {@link module:perspective~view}. A schema is an Object, the keys of which - * are the columns of this {@link module:perspective~view}, and the values are their string type names. - * If this {@link module:perspective~view} is aggregated, theses will be the aggregated types; - * otherwise these types will be the same as the columns in the underlying - * {@link module:perspective~table} + * The schema of this {@link module:perspective~view}. A schema is an + * Object, the keys of which are the columns of this + * {@link module:perspective~view}, and the values are their string type + * names. If this {@link module:perspective~view} is aggregated, theses will + * be the aggregated types; otherwise these types will be the same as the + * columns in the underlying {@link module:perspective~table} * * @async * - * @returns {Promise} A Promise of this {@link module:perspective~view}'s schema. + * @returns {Promise} A Promise of this + * {@link module:perspective~view}'s schema. */ view.prototype.schema = function(override = true) { const schema = extract_map(this._View.schema()); @@ -252,9 +266,11 @@ export default function(Module) { }; /** - * Returns an array of strings containing the column paths of the View without any of the source columns. + * Returns an array of strings containing the column paths of the View + * without any of the source columns. * - * A column path shows the columns that a given cell belongs to after pivots are applied. + * A column path shows the columns that a given cell belongs to after pivots + * are applied. */ view.prototype.column_paths = function() { return extract_vector_scalar(this._View.column_paths()).map(x => x.join(defaults.COLUMN_SEPARATOR_STRING)); @@ -312,7 +328,8 @@ export default function(Module) { const col_name = col_names[cidx]; const col_type = schema[col_name]; if ((cidx - (num_sides > 0 ? 1 : 0)) % (this.config.columns.length + hidden) >= this.config.columns.length) { - // Hidden columns are always at the end, so don't emit these. + // Hidden columns are always at the end, so don't emit + // these. continue; } else if (cidx === start_col && num_sides !== 0) { if (!this.column_only) { @@ -338,7 +355,8 @@ export default function(Module) { const keys = slice.get_pkeys(ridx, 0); formatter.initColumnValue(data, row, "__INDEX__"); for (let i = 0; i < keys.size(); i++) { - // TODO: if __INDEX__ and set index have the same value, don't we need to make sure that it only emits one? + // TODO: if __INDEX__ and set index have the same value, + // don't we need to make sure that it only emits one? const value = __MODULE__.scalar_vec_to_val(keys, i); formatter.addColumnValue(data, row, "__INDEX__", value); } @@ -370,7 +388,8 @@ export default function(Module) { return undefined; } - // mutate the column index if necessary: in pivoted views, columns start at 1 + // mutate the column index if necessary: in pivoted views, columns start + // at 1 const num_sides = this.sides(); if (num_sides > 0) { idx++; @@ -402,24 +421,26 @@ export default function(Module) { * @async * * @param {Object} [options] An optional configuration object. - * @param {number} options.start_row The starting row index from which - * to serialize. - * @param {number} options.end_row The ending row index from which - * to serialize. - * @param {number} options.start_col The starting column index from which - * to serialize. - * @param {number} options.end_col The ending column index from which - * to serialize. - * @param {boolean} [config.index=false] Should the index from the underlying - * {@link module:perspective~table} be in the output (as `"__INDEX__"`). + * @param {number} options.start_row The starting row index from which to + * serialize. + * @param {number} options.end_row The ending row index from which to + * serialize. + * @param {number} options.start_col The starting column index from which to + * serialize. + * @param {number} options.end_col The ending column index from which to + * serialize. + * @param {boolean} [config.index=false] Should the index from the + * underlying {@link module:perspective~table} be in the output (as + * `"__INDEX__"`). * * @returns {Promise} A Promise resolving to An array of Objects - * representing the rows of this {@link module:perspective~view}. If this {@link module:perspective~view} had a - * "row_pivots" config parameter supplied when constructed, each row Object - * will have a "__ROW_PATH__" key, whose value specifies this row's - * aggregated path. If this {@link module:perspective~view} had a "column_pivots" config - * parameter supplied, the keys of this object will be comma-prepended with - * their comma-separated column paths. + * representing the rows of this {@link module:perspective~view}. If this + * {@link module:perspective~view} had a "row_pivots" config parameter + * supplied when constructed, each row Object will have a "__ROW_PATH__" + * key, whose value specifies this row's aggregated path. If this + * {@link module:perspective~view} had a "column_pivots" config parameter + * supplied, the keys of this object will be comma-prepended with their + * comma-separated column paths. */ view.prototype.to_columns = function(options) { return to_format.call(this, options, formatters.jsonTableFormatter); @@ -431,22 +452,23 @@ export default function(Module) { * @async * * @param {Object} [options] An optional configuration object. - * @param {number} options.start_row The starting row index from which - * to serialize. - * @param {number} options.end_row The ending row index from which - * to serialize. - * @param {number} options.start_col The starting column index from which - * to serialize. - * @param {number} options.end_col The ending column index from which - * to serialize. + * @param {number} options.start_row The starting row index from which to + * serialize. + * @param {number} options.end_row The ending row index from which to + * serialize. + * @param {number} options.start_col The starting column index from which to + * serialize. + * @param {number} options.end_col The ending column index from which to + * serialize. * * @returns {Promise} A Promise resolving to An array of Objects - * representing the rows of this {@link module:perspective~view}. If this {@link module:perspective~view} had a - * "row_pivots" config parameter supplied when constructed, each row Object - * will have a "__ROW_PATH__" key, whose value specifies this row's - * aggregated path. If this {@link module:perspective~view} had a "column_pivots" config - * parameter supplied, the keys of this object will be comma-prepended with - * their comma-separated column paths. + * representing the rows of this {@link module:perspective~view}. If this + * {@link module:perspective~view} had a "row_pivots" config parameter + * supplied when constructed, each row Object will have a "__ROW_PATH__" + * key, whose value specifies this row's aggregated path. If this + * {@link module:perspective~view} had a "column_pivots" config parameter + * supplied, the keys of this object will be comma-prepended with their + * comma-separated column paths. */ view.prototype.to_json = function(options) { return to_format.call(this, options, formatters.jsonFormatter); @@ -458,24 +480,25 @@ export default function(Module) { * @async * * @param {Object} [options] An optional configuration object. - * @param {number} options.start_row The starting row index from which - * to serialize. - * @param {number} options.end_row The ending row index from which - * to serialize. - * @param {number} options.start_col The starting column index from which - * to serialize. - * @param {number} options.end_col The ending column index from which - * to serialize. - * @param {Object} options.config A config object for the Papaparse {@link https://www.papaparse.com/docs#json-to-csv} - * config object. + * @param {number} options.start_row The starting row index from which to + * serialize. + * @param {number} options.end_row The ending row index from which to + * serialize. + * @param {number} options.start_col The starting column index from which to + * serialize. + * @param {number} options.end_col The ending column index from which to + * serialize. + * @param {Object} options.config A config object for the Papaparse + * {@link https://www.papaparse.com/docs#json-to-csv} config object. * * @returns {Promise} A Promise resolving to a string in CSV format - * representing the rows of this {@link module:perspective~view}. If this {@link module:perspective~view} had a - * "row_pivots" config parameter supplied when constructed, each row - * will have prepended those values specified by this row's - * aggregated path. If this {@link module:perspective~view} had a "column_pivots" config - * parameter supplied, the keys of this object will be comma-prepended with - * their comma-separated column paths. + * representing the rows of this {@link module:perspective~view}. If this + * {@link module:perspective~view} had a "row_pivots" config parameter + * supplied when constructed, each row will have prepended those values + * specified by this row's aggregated path. If this + * {@link module:perspective~view} had a "column_pivots" config parameter + * supplied, the keys of this object will be comma-prepended with their + * comma-separated column paths. */ view.prototype.to_csv = function(options) { return to_format.call(this, options, formatters.csvFormatter); @@ -490,19 +513,21 @@ export default function(Module) { * * @param {Object} options An optional configuration object. * - * @param {*} options.data_slice A data slice object from which to serialize. + * @param {*} options.data_slice A data slice object from which to + * serialize. * - * @param {number} options.start_row The starting row index from which - * to serialize. - * @param {number} options.end_row The ending row index from which - * to serialize. + * @param {number} options.start_row The starting row index from which to + * serialize. + * @param {number} options.end_row The ending row index from which to + * serialize. * * @returns {Promise} A promise resolving to a TypedArray - * representing the data of the column as retrieved from the {@link module:perspective~view} - all - * pivots, aggregates, sorts, and filters have been applied onto the values - * inside the TypedArray. The TypedArray will be constructed based on data type - - * integers will resolve to Int8Array, Int16Array, or Int32Array. Floats resolve to - * Float32Array or Float64Array. If the column cannot be found, or is not of an + * representing the data of the column as retrieved from the + * {@link module:perspective~view} - all pivots, aggregates, sorts, and + * filters have been applied onto the values inside the TypedArray. The + * TypedArray will be constructed based on data type - integers will resolve + * to Int8Array, Int16Array, or Int32Array. Floats resolve to Float32Array + * or Float64Array. If the column cannot be found, or is not of an * integer/float type, the Promise returns undefined. */ view.prototype.col_to_js_typed_array = function(col_name, options = {}) { @@ -517,19 +542,20 @@ export default function(Module) { * * @param {Object} [options] An optional configuration object. * - * @param {*} options.data_slice A data slice object from which to serialize. + * @param {*} options.data_slice A data slice object from which to + * serialize. * - * @param {number} options.start_row The starting row index from which - * to serialize. - * @param {number} options.end_row The ending row index from which - * to serialize. - * @param {number} options.start_col The starting column index from which - * to serialize. - * @param {number} options.end_col The ending column index from which - * to serialize. + * @param {number} options.start_row The starting row index from which to + * serialize. + * @param {number} options.end_row The ending row index from which to + * serialize. + * @param {number} options.start_col The starting column index from which to + * serialize. + * @param {number} options.end_col The ending column index from which to + * serialize. * - * @returns {Promise} A Table in the Apache Arrow format containing - * data from the view. + * @returns {Promise} A Table in the Apache Arrow format + * containing data from the view. */ view.prototype.to_arrow = function(options = {}) { const names = this._column_names(); @@ -570,9 +596,9 @@ export default function(Module) { }; /** - * The number of aggregated rows in this {@link module:perspective~view}. This is affected by - * the "row_pivots" configuration parameter supplied to this {@link module:perspective~view}'s - * contructor. + * The number of aggregated rows in this {@link module:perspective~view}. + * This is affected by the "row_pivots" configuration parameter supplied to + * this {@link module:perspective~view}'s contructor. * * @async * @@ -583,9 +609,9 @@ export default function(Module) { }; /** - * The number of aggregated columns in this {@link view}. This is affected by - * the "column_pivots" configuration parameter supplied to this {@link view}'s - * contructor. + * The number of aggregated columns in this {@link view}. This is affected + * by the "column_pivots" configuration parameter supplied to this + * {@link view}'s contructor. * * @async * @@ -639,8 +665,8 @@ export default function(Module) { }; /** - * Returns the data of all changed rows in JSON format, or for 1+ sided contexts - * the entire dataset of the view. + * Returns the data of all changed rows in JSON format, or for 1+ sided + * contexts the entire dataset of the view. * @private */ view.prototype._get_step_delta = async function() { @@ -668,7 +694,8 @@ export default function(Module) { }; /** - * Returns an Arrow-serialized dataset that contains the data from updated rows. + * Returns an Arrow-serialized dataset that contains the data from updated + * rows. * * @private */ @@ -680,14 +707,15 @@ export default function(Module) { }; /** - * Register a callback with this {@link module:perspective~view}. Whenever the {@link module:perspective~view}'s - * underlying table emits an update, this callback will be invoked with the - * aggregated row deltas. + * Register a callback with this {@link module:perspective~view}. Whenever + * the {@link module:perspective~view}'s underlying table emits an update, + * this callback will be invoked with the aggregated row deltas. * * @param {function} callback A callback function invoked on update. The * parameter to this callback is dependent on the `mode` parameter: * - "none" (default): The callback is invoked without an argument. - * - "cell": The callback is invoked with the new data for each updated cell, serialized to JSON format. + * - "cell": The callback is invoked with the new data for each updated + * cell, serialized to JSON format. * - "row": The callback is invoked with an Arrow of the updated rows. */ view.prototype.on_update = function(callback, {mode = "none"} = {}) { @@ -752,8 +780,9 @@ export default function(Module) { }; /** - * Register a callback with this {@link module:perspective~view}. Whenever the {@link module:perspective~view} - * is deleted, this callback will be invoked. + * Register a callback with this {@link module:perspective~view}. Whenever + * the {@link module:perspective~view} is deleted, this callback will be + * invoked. * * @param {function} callback A callback function invoked on delete. */ @@ -762,7 +791,8 @@ export default function(Module) { }; /** - * Unregister a previously registered delete callback with this {@link module:perspective~view}. + * Unregister a previously registered delete callback with this + * {@link module:perspective~view}. * * @param {function} callback A delete callback function to be removed */ @@ -773,16 +803,21 @@ export default function(Module) { }; /** - * A view config is a set of options that configures the underlying {@link module:perspective~view}, specifying - * its pivots, columns to show, aggregates, filters, and sorts. + * A view config is a set of options that configures the underlying + * {@link module:perspective~view}, specifying its pivots, columns to show, + * aggregates, filters, and sorts. * - * The view config receives an `Object` containing configuration options, and the `view_config` transforms it into a - * canonical format for interfacing with the core engine. + * The view config receives an `Object` containing configuration options, + * and the `view_config` transforms it into a canonical format for + * interfacing with the core engine. * - * Note This constructor is not public - view config objects should be created using standard - * Javascript `Object`s in the {@link module:perspective~table#view} method, which has an `options` parameter. + * Note This constructor is not public - view config + * objects should be created using standard Javascript `Object`s in the + * {@link module:perspective~table#view} method, which has an `options` + * parameter. * - * @param {Object} config the configuration `Object` passed by the user to the {@link module:perspective~table#view} method. + * @param {Object} config the configuration `Object` passed by the user to + * the {@link module:perspective~table#view} method. * @private * @class * @hideconstructor @@ -800,9 +835,9 @@ export default function(Module) { } /** - * Transform configuration items into `std::vector` objects for interface with C++. - * `this.aggregates` is not transformed into a C++ map, as the use of `ordered_map` in the engine - * makes binding more difficult. + * Transform configuration items into `std::vector` objects for interface + * with C++. `this.aggregates` is not transformed into a C++ map, as the use + * of `ordered_map` in the engine makes binding more difficult. * * @private */ @@ -841,7 +876,7 @@ export default function(Module) { return vector; }; - /****************************************************************************** + /*************************************************************************** * * Table * @@ -853,8 +888,9 @@ export default function(Module) { * each. * * Note This constructor is not public - Tables are created - * by invoking the {@link module:perspective~table} factory method, either on the perspective - * module object, or an a {@link module:perspective~worker} instance. + * by invoking the {@link module:perspective~table} factory method, either + * on the perspective module object, or an a + * {@link module:perspective~worker} instance. * * @class * @hideconstructor @@ -895,8 +931,8 @@ export default function(Module) { }; /** - * Remove all rows in this {@link module:perspective~table} while preserving the schema and - * construction options. + * Remove all rows in this {@link module:perspective~table} while preserving + * the schema and construction options. */ table.prototype.clear = function() { _reset_process(this.get_id()); @@ -914,9 +950,10 @@ export default function(Module) { }; /** - * Delete this {@link module:perspective~table} and clean up all resources associated with it. - * Table objects do not stop consuming resources or processing updates when - * they are garbage collected - you must call this method to reclaim these. + * Delete this {@link module:perspective~table} and clean up all resources + * associated with it. Table objects do not stop consuming resources or + * processing updates when they are garbage collected - you must call this + * method to reclaim these. */ table.prototype.delete = function() { if (this.views.length > 0) { @@ -929,8 +966,9 @@ export default function(Module) { }; /** - * Register a callback with this {@link module:perspective~table}. Whenever the {@link module:perspective~table} - * is deleted, this callback will be invoked. + * Register a callback with this {@link module:perspective~table}. Whenever + * the {@link module:perspective~table} is deleted, this callback will be + * invoked. * * @param {function} callback A callback function invoked on delete. The * parameter to this callback shares a structure with the return type of @@ -941,7 +979,8 @@ export default function(Module) { }; /** - * Unregister a previously registered delete callback with this {@link module:perspective~table}. + * Unregister a previously registered delete callback with this + * {@link module:perspective~table}. * * @param {function} callback A delete callback function to be removed */ @@ -952,9 +991,10 @@ export default function(Module) { }; /** - * The number of accumulated rows in this {@link module:perspective~table}. This is affected by - * the "index" configuration parameter supplied to this {@link module:perspective~view}'s - * contructor - as rows will be overwritten when they share an idnex column. + * The number of accumulated rows in this {@link module:perspective~table}. + * This is affected by the "index" configuration parameter supplied to this + * {@link module:perspective~view}'s contructor - as rows will be + * overwritten when they share an idnex column. * * @async * @@ -965,13 +1005,16 @@ export default function(Module) { }; /** - * The schema of this {@link module:perspective~table}. A schema is an Object whose keys are the - * columns of this {@link module:perspective~table}, and whose values are their string type names. + * The schema of this {@link module:perspective~table}. A schema is an + * Object whose keys are the columns of this + * {@link module:perspective~table}, and whose values are their string type + * names. * * @async - * @param {boolean} computed Should computed columns be included? - * (default false) - * @returns {Promise} A Promise of this {@link module:perspective~table}'s schema. + * @param {boolean} computed Should computed columns be included? (default + * false) + * @returns {Promise} A Promise of this + * {@link module:perspective~table}'s schema. */ table.prototype.schema = function(computed = false, override = true) { let schema = this._Table.get_schema(); @@ -997,13 +1040,15 @@ export default function(Module) { }; /** - * The computed schema of this {@link module:perspective~table}. Returns a schema of only computed - * columns added by the user, the keys of which are computed columns and the values an - * Object containing the associated column_name, column_type, and computation. + * The computed schema of this {@link module:perspective~table}. Returns a + * schema of only computed columns added by the user, the keys of which are + * computed columns and the values an Object containing the associated + * column_name, column_type, and computation. * * @async * - * @returns {Promise} A Promise of this {@link module:perspective~table}'s computed schema. + * @returns {Promise} A Promise of this + * {@link module:perspective~table}'s computed schema. */ table.prototype.computed_schema = function() { if (this.computed.length < 0) return {}; @@ -1028,12 +1073,14 @@ export default function(Module) { }; /** - * Validates a filter configuration, i.e. that the value to filter by is not null or undefined. + * Validates a filter configuration, i.e. that the value to filter by is not + * null or undefined. * * @param {Array} [filter] a filter configuration to test. */ table.prototype.is_valid_filter = function(filter) { - // isNull and isNotNull filter operators are always valid and apply to all schema types + // isNull and isNotNull filter operators are always valid and apply to + // all schema types if (filter[1] === perspective.FILTER_OPERATORS.isNull || filter[1] === perspective.FILTER_OPERATORS.isNotNull) { return true; } @@ -1051,28 +1098,33 @@ export default function(Module) { return typeof value !== "undefined" && value !== null; }; + /* eslint-disable max-len */ + /** - * Create a new {@link module:perspective~view} from this table with a specified - * configuration. - * - * @param {Object} [config] The configuration object for this {@link module:perspective~view}. - * @param {Array} [config.row_pivots] An array of column names - * to use as {@link https://en.wikipedia.org/wiki/Pivot_table#Row_labels Row Pivots}. - * @param {Array} [config.column_pivots] An array of column names - * to use as {@link https://en.wikipedia.org/wiki/Pivot_table#Column_labels Column Pivots}. + * Create a new {@link module:perspective~view} from this table with a + * specified configuration. + * + * @param {Object} [config] The configuration object for this + * {@link module:perspective~view}. + * @param {Array} [config.row_pivots] An array of column names to + * use as {@link https://en.wikipedia.org/wiki/Pivot_table#Row_labels Row Pivots}. + * @param {Array} [config.column_pivots] An array of column names to + * use as {@link https://en.wikipedia.org/wiki/Pivot_table#Column_labels Column Pivots}. * @param {Array} [config.columns] An array of column names for the * output columns. If none are provided, all columns are output. - * @param {Object} [config.aggregates] An object, the keys of which are column - * names, and their respective values are the aggregates calculations to use - * when this view has `row_pivots`. A column provided to `config.columns` - * without an aggregate in this object, will use the default aggregate - * calculation for its type. - * @param {Array>} [config.filter] An Array of Filter configurations to - * apply. A filter configuration is an array of 3 elements: A column name, - * a supported filter comparison string (e.g. '===', '>'), and a value to compare. - * @param {Array} [config.sort] An Array of Sort configurations to apply. - * A sort configuration is an array of 2 elements: A column name, and a sort direction, - * which are: "none", "asc", "desc", "col asc", "col desc", "asc abs", "desc abs", "col asc abs", "col desc abs". + * @param {Object} [config.aggregates] An object, the keys of which are + * column names, and their respective values are the aggregates calculations + * to use when this view has `row_pivots`. A column provided to + * `config.columns` without an aggregate in this object, will use the + * default aggregate calculation for its type. + * @param {Array>} [config.filter] An Array of Filter + * configurations to apply. A filter configuration is an array of 3 + * elements: A column name, a supported filter comparison string (e.g. + * '===', '>'), and a value to compare. + * @param {Array} [config.sort] An Array of Sort configurations to + * apply. A sort configuration is an array of 2 elements: A column name, and + * a sort direction, which are: "none", "asc", "desc", "col asc", "col + * desc", "asc abs", "desc abs", "col asc abs", "col desc abs". * * @example * var view = table.view({ @@ -1083,8 +1135,8 @@ export default function(Module) { * sort: [['value', 'asc']] * }); * - * @returns {view} A new {@link module:perspective~view} object for the supplied configuration, - * bound to this table + * @returns {view} A new {@link module:perspective~view} object for the + * supplied configuration, bound to this table */ table.prototype.view = function(_config = {}) { _clear_process(this.get_id()); @@ -1099,7 +1151,8 @@ export default function(Module) { } } else if (key === "aggregate") { console.warn(`Deprecated: "aggregate" config parameter has been replaced by "aggregates" and "columns"`); - // backwards compatibility: deconstruct `aggregate` into `aggregates` and `columns` + // backwards compatibility: deconstruct `aggregate` into + // `aggregates` and `columns` config["aggregates"] = {}; config["columns"] = []; for (const agg of _config["aggregate"]) { @@ -1143,6 +1196,8 @@ export default function(Module) { return v; }; + /* eslint-enadle max-len */ + let meter; function initialize_profile_thread() { @@ -1164,13 +1219,13 @@ export default function(Module) { } /** - * Updates the rows of a {@link module:perspective~table}. Updated rows are pushed down to any - * derived {@link module:perspective~view} objects. + * Updates the rows of a {@link module:perspective~table}. Updated rows are + * pushed down to any derived {@link module:perspective~view} objects. * * @param {Object|Array|string} data The input data - * for this table. The supported input types mirror the constructor options, minus - * the ability to pass a schema (Object) as this table has - * already been constructed, thus its types are set in stone. + * for this table. The supported input types mirror the constructor + * options, minus the ability to pass a schema (Object) as + * this table has already been constructed, thus its types are set in stone. * * @see {@link module:perspective~table} */ @@ -1229,7 +1284,8 @@ export default function(Module) { try { const op = __MODULE__.t_op.OP_INSERT; - // update the Table in C++, but don't keep the returned Table reference as it is identical + // update the Table in C++, but don't keep the returned Table + // reference as it is identical make_table(pdata, this._Table, this.computed, this.index || "", this.limit, op, true, is_arrow); this.initialized = true; } catch (e) { @@ -1241,8 +1297,8 @@ export default function(Module) { }; /** - * Removes the rows of a {@link module:perspective~table}. Removed rows are pushed down to any - * derived {@link module:perspective~view} objects. + * Removes the rows of a {@link module:perspective~table}. Removed rows are + * pushed down to any derived {@link module:perspective~view} objects. * * @param {Array} data An array of primary keys to remove. * @@ -1269,7 +1325,8 @@ export default function(Module) { try { const op = __MODULE__.t_op.OP_DELETE; - // update the Table in C++, but don't keep the returned Table reference as it is identical + // update the Table in C++, but don't keep the returned Table + // reference as it is identical make_table(pdata, this._Table, undefined, this.index || "", this.limit, op, false, is_arrow); this.initialized = true; } catch (e) { @@ -1281,7 +1338,8 @@ export default function(Module) { }; /** - * Create a new table with the addition of new computed columns (defined as javascript functions) + * Create a new table with the addition of new computed columns (defined as + * javascript functions) * * @param {Computation} computed A computation specification object */ @@ -1306,9 +1364,10 @@ export default function(Module) { * The column names of this table. * * @async - * @param {boolean} computed Should computed columns be included? - * (default false) - * @returns {Promise>} An array of column names for this table. + * @param {boolean} computed Should computed columns be included? (default + * false) + * @returns {Promise>} An array of column names for this + * table. */ table.prototype.columns = function(computed = false) { let schema = this._Table.get_schema(); @@ -1358,22 +1417,24 @@ export default function(Module) { * // Creating a table from a Web Worker (instantiated via the worker() method). * var table = worker.table([{x: 1}, {x: 2}]); * - * @param {Object|Object|Array|string} data The input data - * for this table. When supplied an Object with string values, an empty - * table is returned using this Object as a schema. When an Object with + * @param {Object|Object|Array|string} data The input data for this table. + * When supplied an Object with string values, an empty table is + * returned using this Object as a schema. When an Object with * Array values is supplied, a table is returned using this object's - * key/value pairs as name/columns respectively. When an Array is supplied, - * a table is constructed using this Array's objects as rows. When - * a string is supplied, the parameter as parsed as a CSV. + * key/value pairs as name/columns respectively. When an Array is + * supplied, a table is constructed using this Array's objects as + * rows. When a string is supplied, the parameter as parsed as a + * CSV. * @param {Object} [options] An optional options dictionary. * @param {string} options.index The name of the column in the resulting - * table to treat as an index. When updating this table, rows sharing an - * index of a new row will be overwritten. `index` is mutually exclusive - * to `limit` + * table to treat as an index. When updating this table, rows + * sharing an index of a new row will be overwritten. `index` is + * mutually exclusive to `limit` * @param {integer} options.limit The maximum number of rows that can be - * added to this table. When exceeded, old rows will be overwritten in - * the order they were inserted. `limit` is mutually exclusive to - * `index`. + * added to this table. When exceeded, old rows will be overwritten + * in the order they were inserted. `limit` is mutually exclusive + * to `index`. * * @returns {table} A new {@link module:perspective~table} object. */ @@ -1431,17 +1492,19 @@ export default function(Module) { * Create a WebWorker API that loads perspective in `init` and extends * `post` using the worker's `postMessage` method. * - * If Perspective is running inside a Web Worker, use the WebSorkerServer - * as default. + * If Perspective is running inside a Web Worker, use the WebSorkerServer as + * default. * * @extends Server * @private */ class WebWorkerServer extends Server { /** - * On initialization, listen for messages posted from the client and send it to `Server.process()`. + * On initialization, listen for messages posted from the client and + * send it to `Server.process()`. * - * @param perspective a reference to the Perspective module, allowing the `Server` to access Perspective methods. + * @param perspective a reference to the Perspective module, allowing + * the `Server` to access Perspective methods. */ constructor(perspective) { super(perspective); @@ -1449,19 +1512,23 @@ export default function(Module) { } /** - * Implements the `Server`'s `post()` method using the Web Worker `postMessage()` API. + * Implements the `Server`'s `post()` method using the Web Worker + * `postMessage()` API. * * @param {Object} msg a message to pass to the client - * @param {*} transfer a transferable object to pass to the client, if needed + * @param {*} transfer a transferable object to pass to the client, if + * needed */ post(msg, transfer) { self.postMessage(msg, transfer); } /** - * When initialized, replace Perspective's internal `__MODULE` variable with the WASM binary. + * When initialized, replace Perspective's internal `__MODULE` variable + * with the WASM binary. * - * @param {ArrayBuffer} buffer an ArrayBuffer containing the Perspective WASM code + * @param {ArrayBuffer} buffer an ArrayBuffer containing the Perspective + * WASM code */ init(msg) { if (typeof WebAssembly === "undefined") { @@ -1477,7 +1544,8 @@ export default function(Module) { } /** - * Use WebSorkerServer as default inside a Web Worker, where `window` is replaced with `self`. + * Use WebSorkerServer as default inside a Web Worker, where `window` is + * replaced with `self`. */ if (typeof self !== "undefined" && self.addEventListener) { new WebWorkerServer(perspective); diff --git a/packages/perspective/src/js/perspective.node.js b/packages/perspective/src/js/perspective.node.js index c144744276..4839d001ea 100644 --- a/packages/perspective/src/js/perspective.node.js +++ b/packages/perspective/src/js/perspective.node.js @@ -184,7 +184,8 @@ class WebSocketServer extends Server { msg.id = compound_id; this.REQS[msg.id] = {ws, msg}; try { - // Send all messages to the handler defined in Perspective.Server + // Send all messages to the handler defined in + // Perspective.Server this.process(msg, ws.id); } catch (e) { console.error(e); @@ -232,11 +233,13 @@ class WebSocketServer extends Server { /** * Send an asynchronous message to the Perspective web worker. * - * If the `transferable` param is set, pass two messages: the string representation of the message and then - * the ArrayBuffer data that needs to be transferred. The `is_transferable` flag tells the client to expect the next message - * to be a transferable object. + * If the `transferable` param is set, pass two messages: the string + * representation of the message and then the ArrayBuffer data that needs to + * be transferred. The `is_transferable` flag tells the client to expect the + * next message to be a transferable object. * - * @param {Object} msg a valid JSON-serializable message to pass to the client + * @param {Object} msg a valid JSON-serializable message to pass to the + * client * @param {*} transferable a transferable object to be sent to the client */ post(msg, transferable) { @@ -261,7 +264,8 @@ class WebSocketServer extends Server { } /** - * Expose a Perspective table through the WebSocket, allowing it to be accessed by a unique name. + * Expose a Perspective table through the WebSocket, allowing it to be + * accessed by a unique name. * * @param {String} name * @param {Perspective.table} table @@ -272,7 +276,8 @@ class WebSocketServer extends Server { } /** - * Expose a Perspective view through the WebSocket, allowing it to be accessed by a unique name. + * Expose a Perspective view through the WebSocket, allowing it to be + * accessed by a unique name. * * @param {String} name * @param {Perspective.view} view diff --git a/packages/perspective/src/js/perspective.parallel.js b/packages/perspective/src/js/perspective.parallel.js index ef4caab259..b630fc14f6 100644 --- a/packages/perspective/src/js/perspective.parallel.js +++ b/packages/perspective/src/js/perspective.parallel.js @@ -51,9 +51,11 @@ const override = new (class { })(); /** - * WebWorker extends Perspective's `worker` class and defines interactions using the WebWorker API. + * WebWorker extends Perspective's `worker` class and defines interactions using + * the WebWorker API. * - * This class serves as the client API for transporting messages to/from Web Workers. + * This class serves as the client API for transporting messages to/from Web + * Workers. */ class WebWorkerClient extends Client { constructor(config) { @@ -119,9 +121,11 @@ class WebWorkerClient extends Client { /** * Given a WebSocket URL, connect to the socket located at `url`. * - * The `onmessage` handler receives incoming messages and sends it to the WebWorker through `this._handle`. + * The `onmessage` handler receives incoming messages and sends it to the + * WebWorker through `this._handle`. * - * If the message has a transferable asset, set the `pending_arrow` flag to tell the worker the next message is an ArrayBuffer. + * If the message has a transferable asset, set the `pending_arrow` flag to tell + * the worker the next message is an ArrayBuffer. */ class WebSocketClient extends Client { constructor(url) { @@ -147,9 +151,9 @@ class WebSocketClient extends Client { msg = JSON.parse(msg.data); // If the `is_transferable` flag is set, the worker expects the - // next message to be a transferable object. - // This sets the `_pending_arrow` flag, which triggers a special - // handler for the ArrayBuffer containing arrow data. + // next message to be a transferable object. This sets the + // `_pending_arrow` flag, which triggers a special handler for + // the ArrayBuffer containing arrow data. if (msg.is_transferable) { this._pending_arrow = msg.id; } else { @@ -192,8 +196,8 @@ const WORKER_SINGLETON = (function() { })(); /** - * If Perspective is loaded with the `preload` attribute, pre-initialize - * the worker so it is available at page render. + * If Perspective is loaded with the `preload` attribute, pre-initialize the + * worker so it is available at page render. */ if (document.currentScript && document.currentScript.hasAttribute("preload")) { WORKER_SINGLETON.getInstance(); @@ -203,8 +207,7 @@ const mod = { override: x => override.set(x), /** - * Create a new WebWorkerClient instance. - *s + * Create a new WebWorkerClient instance. s * @param {*} [config] An optional perspective config object override */ worker(config) { @@ -212,9 +215,8 @@ const mod = { }, /** - * Create a new WebSocketClient instance. The `url` parameter is provided, load the worker - * at `url` using a WebSocket. - *s + * Create a new WebSocketClient instance. The `url` parameter is provided, + * load the worker at `url` using a WebSocket. s * @param {*} url Defaults to `window.location.origin` * @param {*} [config] An optional perspective config object override */ diff --git a/packages/perspective/src/js/utils.js b/packages/perspective/src/js/utils.js index b96a2a336c..86ed98c4d3 100644 --- a/packages/perspective/src/js/utils.js +++ b/packages/perspective/src/js/utils.js @@ -115,6 +115,7 @@ if (!String.prototype.includes) { }; } +/* eslint-disable-next-line max-len */ // from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes if (!Array.prototype.includes) { Object.defineProperty(Array.prototype, "includes", { @@ -134,15 +135,12 @@ if (!Array.prototype.includes) { return false; } - // 4. Let n be ? ToInteger(fromIndex). - // (If fromIndex is undefined, this step produces the value 0.) + // 4. Let n be ? ToInteger(fromIndex). (If fromIndex is undefined, + // this step produces the value 0.) var n = fromIndex | 0; - // 5. If n ≥ 0, then - // a. Let k be n. - // 6. Else n < 0, - // a. Let k be len + n. - // b. If k < 0, let k be 0. + // 5. If n ≥ 0, then a. Let k be n. + // 6. Else n < 0, a. Let k be len + n. b. If k < 0, let k be 0. var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); function sameValueZero(x, y) { @@ -151,8 +149,9 @@ if (!Array.prototype.includes) { // 7. Repeat, while k < len while (k < len) { - // a. Let elementK be the result of ? Get(O, ! ToString(k)). - // b. If SameValueZero(searchElement, elementK) is true, return true. + // a. Let elementK be the result of ? Get(O, ! ToString(k)). b. + // If SameValueZero(searchElement, elementK) is true, return + // true. if (sameValueZero(o[k], searchElement)) { return true; } diff --git a/python/perspective/examples/perspective_tornado_server.py b/python/perspective/examples/perspective_tornado_server.py index fc000d35bc..56fb746f1b 100644 --- a/python/perspective/examples/perspective_tornado_server.py +++ b/python/perspective/examples/perspective_tornado_server.py @@ -41,7 +41,8 @@ def make_app(): if __name__ == "__main__": - # Because we use `PerspectiveTornadoHandler`, all that needs to be done in `init` is to start the Tornado server. + # Because we use `PerspectiveTornadoHandler`, all that needs to be done in + # `init` is to start the Tornado server. app = make_app() app.listen(8888) logging.critical("Listening on http://localhost:8888") diff --git a/python/perspective/examples/streaming.py b/python/perspective/examples/streaming.py index af5e3e84e6..faace1b362 100644 --- a/python/perspective/examples/streaming.py +++ b/python/perspective/examples/streaming.py @@ -57,7 +57,8 @@ def make_app(): "lastUpdate": datetime, }, limit=2500) - # Track the table with the name "data_source_one", which will be used in the front-end to access the Table. + # Track the table with the name "data_source_one", which will be used in + # the front-end to access the Table. MANAGER.host_table("data_source_one", TABLE) # update with new data every 50ms diff --git a/python/perspective/perspective/__init__.py b/python/perspective/perspective/__init__.py index 2d79f38f2f..2baa229636 100644 --- a/python/perspective/perspective/__init__.py +++ b/python/perspective/perspective/__init__.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from .core import * # noqa: F401, F403 from .core._version import __version__ # noqa: F401 from .manager import * # noqa: F401, F403 diff --git a/python/perspective/perspective/core/__init__.py b/python/perspective/perspective/core/__init__.py index a720876619..d2980dc648 100644 --- a/python/perspective/perspective/core/__init__.py +++ b/python/perspective/perspective/core/__init__.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from ._version import __version__ # noqa: F401 from .exception import PerspectiveError # noqa: F401 from .aggregate import Aggregate # noqa: F401 diff --git a/python/perspective/perspective/core/_version.py b/python/perspective/perspective/core/_version.py index e4cd53d232..8186395230 100644 --- a/python/perspective/perspective/core/_version.py +++ b/python/perspective/perspective/core/_version.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/core/aggregate.py b/python/perspective/perspective/core/aggregate.py index 9deb00cffc..893dacd318 100644 --- a/python/perspective/perspective/core/aggregate.py +++ b/python/perspective/perspective/core/aggregate.py @@ -1,16 +1,17 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from enum import Enum class Aggregate(Enum): - '''The aggregation operators available in Perspective. Pass these into the `aggregates` arg in `PerspectiveWidget` or - `PerspectiveViewer`. + '''The aggregation operators available in Perspective. Pass these into the + `aggregates` arg in `PerspectiveWidget` or `PerspectiveViewer`. Examples: >>> widget = PerspectiveWidget(data, aggregates={"a": Aggregate.LAST}) diff --git a/python/perspective/perspective/core/data/__init__.py b/python/perspective/perspective/core/data/__init__.py index 0d4cd4d91c..9c4e3060f5 100644 --- a/python/perspective/perspective/core/data/__init__.py +++ b/python/perspective/perspective/core/data/__init__.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/core/data/np.py b/python/perspective/perspective/core/data/np.py index fef39a5f85..66f1c6dbda 100644 --- a/python/perspective/perspective/core/data/np.py +++ b/python/perspective/perspective/core/data/np.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 six import numpy as np from datetime import datetime @@ -13,13 +14,15 @@ def deconstruct_numpy(array): - '''Given a numpy array, parse it and return the data as well as a numpy array of null indices. + '''Given a numpy array, parse it and return the data as well as a numpy + array of null indices. Args: array (numpy.array) Returns: - dict : `array` is the original array, and `mask` is an array of booleans where `True` represents a nan/None value. + `dict`: `array` is the original array, and `mask` is an array of + booleans where `True` represents a nan/None value. ''' mask = [] @@ -48,12 +51,14 @@ def deconstruct_numpy(array): # bool => byte array = array.astype("b", copy=False) elif np.issubdtype(array.dtype, np.datetime64): - # treat days/weeks/months/years as datetime objects - avoid idiosyncracy with days of month, etc. + # treat days/weeks/months/years as datetime objects - avoid idiosyncracy + # with days of month, etc. if array.dtype in DATE_DTYPES: array = array.astype(datetime) # cast datetimes to millisecond timestamps - # because datetime64("nat") is a double, cast to float64 here - C++ handles the rest + # because datetime64("nat") is a double, cast to float64 here - C++ + # handles the rest if array.dtype == np.dtype("datetime64[us]"): array = array.astype(np.float64, copy=False) / 1000 elif array.dtype == np.dtype("datetime64[ns]"): diff --git a/python/perspective/perspective/core/data/pd.py b/python/perspective/perspective/core/data/pd.py index 7fdb56b9c3..631a51bd43 100644 --- a/python/perspective/perspective/core/data/pd.py +++ b/python/perspective/perspective/core/data/pd.py @@ -1,26 +1,29 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 numpy as np import pandas as pd def _parse_datetime_index(index): - '''Given an instance of `pandas.DatetimeIndex`, parse its `freq` and return a `numpy.dtype` - that corresponds to the unit it should be parsed in. + '''Given an instance of `pandas.DatetimeIndex`, parse its `freq` and + return a `numpy.dtype` that corresponds to the unit it should be parsed in. - Because Pandas DataFrames cannot store datetimes in anything other than `datetime64[ns]`, we need - to examine the `DatetimeIndex` itself to understand what unit it needs to be parsed as. + Because `pandas.DataFrame`s cannot store datetimes in anything other than + `datetime64[ns]`, we need to examine the `DatetimeIndex` itself to + understand what unit it needs to be parsed as. Args: index (pandas.DatetimeIndex) Returns: - numpy.dtype : a datetime64 dtype with the correct units depending on `index.freq`. + `numpy.dtype`: a datetime64 dtype with the correct units depending on + `index.freq`. ''' if index.freq is None: return np.dtype("datetime64[ns]") @@ -47,13 +50,15 @@ def _parse_datetime_index(index): def deconstruct_pandas(data): - '''Given a dataframe, flatten it by resetting the index and memoizing the pivots that were applied. + '''Given a dataframe, flatten it by resetting the index and memoizing the + pivots that were applied. Args: data (pandas.dataframe): a Pandas DataFrame to parse Returns: - (pandas.DataFrame, dict): a Pandas DataFrame and a dictionary containing optional members `columns`, `row_pivots`, and `column_pivots`. + (pandas.DataFrame, dict): a Pandas DataFrame and a dictionary containing + optional members `columns`, `row_pivots`, and `column_pivots`. ''' kwargs = {} diff --git a/python/perspective/perspective/core/exception.py b/python/perspective/perspective/core/exception.py index df745d045e..49258503b9 100644 --- a/python/perspective/perspective/core/exception.py +++ b/python/perspective/perspective/core/exception.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/core/filters.py b/python/perspective/perspective/core/filters.py index 4b71b46b3f..9e1c0ed2de 100644 --- a/python/perspective/perspective/core/filters.py +++ b/python/perspective/perspective/core/filters.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/core/plugin.py b/python/perspective/perspective/core/plugin.py index 200402bbeb..f71a912403 100644 --- a/python/perspective/perspective/core/plugin.py +++ b/python/perspective/perspective/core/plugin.py @@ -1,17 +1,17 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from enum import Enum class Plugin(Enum): - '''The plugins (grids/charts) available in Perspective. - - Pass these into the `plugin` arg in `PerspectiveWidget` or `PerspectiveViewer`. + '''The plugins (grids/charts) available in Perspective. Pass these into + the `plugin` arg in `PerspectiveWidget` or `PerspectiveViewer`. Examples: >>> widget = PerspectiveWidget(data, plugin=Plugin.TREEMAP) diff --git a/python/perspective/perspective/core/sort.py b/python/perspective/perspective/core/sort.py index ad6bb793e4..8e08b1f8ed 100644 --- a/python/perspective/perspective/core/sort.py +++ b/python/perspective/perspective/core/sort.py @@ -1,16 +1,17 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from enum import Enum class Sort(Enum): - '''The sort directions available for use in Perspective. Pass these into the `sort` argument of `PerspectiveWidget` or - `PerspectiveViewer`. + '''The sort directions available for use in Perspective. Pass these into + the `sort` argument of `PerspectiveWidget`. Examples: >>> widget = PerspectiveWidget(data, sort=[["a", Sort.DESC]]) diff --git a/python/perspective/perspective/manager/__init__.py b/python/perspective/perspective/manager/__init__.py index 09e3e23bd1..9950da33d6 100644 --- a/python/perspective/perspective/manager/__init__.py +++ b/python/perspective/perspective/manager/__init__.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from .manager import PerspectiveManager # noqa: F401 from .session import PerspectiveSession # noqa: F401 diff --git a/python/perspective/perspective/manager/manager.py b/python/perspective/perspective/manager/manager.py index 8aa2c594f2..da5f0422a5 100644 --- a/python/perspective/perspective/manager/manager.py +++ b/python/perspective/perspective/manager/manager.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 logging import json import random @@ -40,19 +41,24 @@ def default(self, obj): class PerspectiveManager(object): - '''PerspectiveManager is an orchestrator for running Perspective on the server side. - - The core functionality resides in `process()`, which receives JSON-serialized messages from a client (usually `perspective-viewer` in the browser), - executes the commands in the message, and returns the results of those commands back to the `post_callback`. - - The manager cannot create tables or views - use `host_table` or `host_view` to pass Table/View instances to the manager. - - Because Perspective is designed to be used in a shared context, i.e. multiple clients all accessing the same `Table`, - PerspectiveManager comes with the context of `sessions` - an encapsulation of the actions and resources used by a single - connection to Perspective. - - - When a client connects, for example through a websocket, a new session should be spawned using `new_session()`. - - When the websocket closes, call `close()` on the session instance to clean up associated resources. + '''PerspectiveManager is an orchestrator for running Perspective on the + server side. + + The core functionality resides in `process()`, which receives + JSON-serialized messages from a client (usually `perspective-viewer` in the + browser), executes the commands in the message, and returns the results of + those commands back to the `post_callback`. The manager cannot create + tables or views - use `host_table` or `host_view` to pass Table/View + instances to the manager. Because Perspective is designed to be used in a + shared context, i.e. multiple clients all accessing the same `Table`, + PerspectiveManager comes with the context of `sessions` - an + encapsulation of the actions and resources used by a single connection + to Perspective. + + - When a client connects, for example through a websocket, a new session + should be spawned using `new_session()`. + - When the websocket closes, call `close()` on the session instance to + clean up associated resources. ''' def __init__(self): @@ -67,27 +73,34 @@ def host(self, data, name=None): elif isinstance(data, View): self._views[name] = data else: - raise PerspectiveError("Only `Table()` and `View()` instances can be hosted.") + raise PerspectiveError( + "Only `Table()` and `View()` instances can be hosted.") def host_table(self, name, table): - '''Given a reference to a `Table`, manage it and allow operations on it to occur through the Manager.''' + '''Given a reference to a `Table`, manage it and allow operations on it + to occur through the Manager. + ''' name = name or gen_name() self._tables[name] = table return name def host_view(self, name, view): - '''Given a reference to a `View`, add it to the manager's views container.''' + '''Given a reference to a `View`, add it to the manager's views + container. + ''' self._views[name] = view def new_session(self): return PerspectiveSession(self) def _process(self, msg, post_callback, client_id=None): - '''Given a message from the client, process it through the Perspective engine. + '''Given a message from the client, process it through the Perspective + engine. Args: - msg (dict): a message from the client with instructions that map to engine operations - post_callback (callable): a function that returns data to the client + msg (dict): a message from the client with instructions that map to + engine operations post_callback (callable): a function that + returns data to the client ''' if isinstance(msg, str): if msg == "heartbeat": # TODO fix this @@ -95,47 +108,69 @@ def _process(self, msg, post_callback, client_id=None): msg = json.loads(msg) if not isinstance(msg, dict): - raise PerspectiveError("Message passed into `_process` should either be a JSON-serialized string or a dict.") + raise PerspectiveError( + "Message passed into `_process` should either be a " + "JSON-serialized string or a dict.") cmd = msg["cmd"] try: if cmd == "init": # return empty response - post_callback(json.dumps(self._make_message(msg["id"], None), cls=DateTimeEncoder)) + post_callback( + json.dumps( + self._make_message( + msg["id"], + None), + cls=DateTimeEncoder)) elif cmd == "table": try: # create a new Table and track it data_or_schema = msg["args"][0] - self._tables[msg["name"]] = Table(data_or_schema, **msg.get("options", {})) + self._tables[msg["name"]] = Table( + data_or_schema, **msg.get("options", {})) except IndexError: self._tables[msg["name"]] = [] elif cmd == "view": # create a new view and track it with the assigned client_id. - new_view = self._tables[msg["table_name"]].view(**msg.get("config", {})) + new_view = self._tables[msg["table_name"]].view( + **msg.get("config", {})) new_view._client_id = client_id self._views[msg["view_name"]] = new_view elif cmd == "table_method" or cmd == "view_method": self._process_method_call(msg, post_callback) except(PerspectiveError, PerspectiveCppError) as e: # Catch errors and return them to client - post_callback(json.dumps(self._make_error_message(msg["id"], str(e))), cls=DateTimeEncoder) + post_callback( + json.dumps( + self._make_error_message( + msg["id"], + str(e))), + cls=DateTimeEncoder) def _process_method_call(self, msg, post_callback): - '''When the client calls a method, validate the instance it calls on and return the result.''' + '''When the client calls a method, validate the instance it calls on + and return the result. + ''' if msg["cmd"] == "table_method": table_or_view = self._tables.get(msg["name"], None) else: table_or_view = self._views.get(msg["name"], None) if table_or_view is None: - post_callback(json.dumps(self._make_error_message(msg["id"], "View is not initialized"), cls=DateTimeEncoder)) + post_callback( + json.dumps( + self._make_error_message( + msg["id"], + "View is not initialized"), + cls=DateTimeEncoder)) try: if msg.get("subscribe", False) is True: self._process_subscribe(msg, table_or_view, post_callback) else: args = {} if msg["method"] == "schema": - args["as_string"] = True # make sure schema returns string types + # make sure schema returns string types + args["as_string"] = True elif msg["method"].startswith("to_"): # TODO for d in msg.get("args", []): diff --git a/python/perspective/perspective/manager/session.py b/python/perspective/perspective/manager/session.py index 25f62cb87f..9a36d1d5c2 100644 --- a/python/perspective/perspective/manager/session.py +++ b/python/perspective/perspective/manager/session.py @@ -1,28 +1,35 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from random import random class PerspectiveSession(object): - '''Encapsulates the actions and resources of a single connection to Perspective.''' + '''Encapsulates the actions and resources of a single connection to + Perspective. + ''' def __init__(self, manager): - '''Create a new session object, keeping track of a unique client_id and the manager the session belongs to. - - `PerspectiveSession` should not be constructed directly - use `PerspectiveManager.new_session()` to create. + '''Create a new session object, keeping track of a unique client_id and + the manager the session belongs to. `PerspectiveSession` should not be + constructed directly - use `PerspectiveManager.new_session()` to create. ''' self.client_id = str(random()) self.manager = manager def process(self, message, post_callback): - '''Pass a message to the manager's `process` method, which passes the result to `post_callback`.''' + '''Pass a message to the manager's `process` method, which passes the + result to `post_callback`. + ''' self.manager._process(message, post_callback, client_id=self.client_id) def close(self): - '''Remove the views created within this session when the session ends.''' + '''Remove the views created within this session when the session + ends. + ''' self.manager.clear_views(self.client_id) diff --git a/python/perspective/perspective/node/__init__.py b/python/perspective/perspective/node/__init__.py index 67094a0805..fb99b93a13 100644 --- a/python/perspective/perspective/node/__init__.py +++ b/python/perspective/perspective/node/__init__.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 os import os.path import psutil diff --git a/python/perspective/perspective/table/__init__.py b/python/perspective/perspective/table/__init__.py index 6563d8c00e..0f75bb7764 100644 --- a/python/perspective/perspective/table/__init__.py +++ b/python/perspective/perspective/table/__init__.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from .table import Table diff --git a/python/perspective/perspective/table/_accessor.py b/python/perspective/perspective/table/_accessor.py index 82ab2d8ff0..42f9b00763 100644 --- a/python/perspective/perspective/table/_accessor.py +++ b/python/perspective/perspective/table/_accessor.py @@ -1,15 +1,17 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 six import pandas import numpy from distutils.util import strtobool from math import isnan + from ._date_validator import _PerspectiveDateValidator from ..core.data import deconstruct_numpy, deconstruct_pandas from ..core.data.pd import _parse_datetime_index @@ -23,7 +25,8 @@ def _flatten_structure(array): '''Flatten numpy.recarray or structured arrays into a dict.''' - # recarrays/structured arrays do not have guaranteed bit offsets - make a copy of the array to fix + # recarrays/structured arrays do not have guaranteed bit offsets - make a + # copy of the array to fix columns = [numpy.copy(array[col]) for col in array.dtype.names] return dict(zip(array.dtype.names, columns)) @@ -31,21 +34,20 @@ def _flatten_structure(array): def _type_to_format(data_or_schema): '''Deconstructs data passed in by the user into a standard format: - - A list of dicts, each of which represents a single row. - - A dict of lists, each of which represents a single column. + - A :obj:`list` of dicts, each of which represents a single row. + - A dict of :obj:`list`s, each of which represents a single column. Schemas passed in by the user are preserved as-is. - - Pandas DataFrames are flattened and returned as a columnar dataset. - - Finally, an integer is assigned to represent the type of the dataset to the internal engine. + :class:`pandas.DataFrame`s are flattened and returned as a columnar + dataset. Finally, an integer is assigned to represent the type of the + dataset to the internal engine. Returns: - int: type - - 0: records (list[dict]) - - 1: columns (dict[str:list]) + :obj:`int`: type + - 0: records (:obj:`list` of :obj:`dict`) + - 1: columns (:obj:`dict` of :obj:`str` to :obj:`list`) - 2: schema (dist[str]/dict[type]) - {list, dict}: processed data + ():obj:`list`/:obj:`dict`): processed data ''' if isinstance(data_or_schema, list): # records @@ -83,7 +85,9 @@ def _type_to_format(data_or_schema): class _PerspectiveAccessor(object): - '''A uniform accessor that wraps data/schemas of varying formats with a common `marshal` function.''' + '''A uniform accessor that wraps data/schemas of varying formats with a + common :func:`marshal` function. + ''' INTEGER_TYPES = six.integer_types + (numpy.integer,) @@ -102,7 +106,8 @@ def __init__(self, data_or_schema): self._types = [] - # Verify that column names are strings, and that numpy arrays are of type `ndarray` + # Verify that column names are strings, and that numpy arrays are of + # type `ndarray` for name in self._names: if not isinstance(name, six.string_types): raise PerspectiveError( @@ -118,7 +123,8 @@ def __init__(self, data_or_schema): # use the index of the original, unflattened dataframe dtype = _parse_datetime_index(data_or_schema.index) - # keep a string representation of the dtype, as PyBind only has access to the char dtype code + # keep a string representation of the dtype, as PyBind only has + # access to the char dtype code self._types.append(str(dtype)) def data(self): @@ -164,14 +170,17 @@ def get(self, column_name, ridx): return None def marshal(self, cidx, ridx, dtype): - '''Returns the element at the specified column and row index, and marshals it into an object compatible with the core engine's `fill` method. + '''Returns the element at the specified column and row index, and + marshals it into an object compatible with the core engine's + :func:`fill` method. - If DTYPE_DATE or DTYPE_TIME is specified for a string value, attempt to parse the string value or return `None`. + If DTYPE_DATE or DTYPE_TIME is specified for a string value, attempt + to parse the string value or return :obj:`None`. Args: - cidx (int) - ridx (int) - dtype (.libbinding.t_dtype) + cidx (:obj:`int`) + ridx (:obj:`int`) + dtype (:obj:`.libbinding.t_dtype`) Returns: object or None @@ -182,22 +191,26 @@ def marshal(self, cidx, ridx, dtype): if val is None: return val - # first, check for numpy nans without using numpy.isnan as it tries to cast values + # first, check for numpy nans without using numpy.isnan as it tries to + # cast values if isinstance(val, float) and isnan(val): val = None elif isinstance(val, list) and len(val) == 1: # strip out values encased lists val = val[0] elif dtype == t_dtype.DTYPE_BOOL: - # True values are y, yes, t, true, on and 1; false values are n, no, f, false, off and 0. + # True values are y, yes, t, true, on and 1; false values are n, no, + # f, false, off and 0. val = bool(strtobool(str(val))) elif dtype == t_dtype.DTYPE_INT32 or dtype == t_dtype.DTYPE_INT64: if not isinstance(val, bool) and isinstance(val, (float, numpy.floating)): - # should be able to update int columns with either ints or floats + # should be able to update int columns with either ints or + # floats val = int(val) elif dtype == t_dtype.DTYPE_FLOAT32 or dtype == t_dtype.DTYPE_FLOAT64: if not isinstance(val, bool) and isinstance(val, _PerspectiveAccessor.INTEGER_TYPES): - # should be able to update float columns with either ints or floats + # should be able to update float columns with either ints or + # floats val = float(val) elif dtype == t_dtype.DTYPE_DATE: # return datetime.date @@ -218,7 +231,8 @@ def marshal(self, cidx, ridx, dtype): val = val.decode("utf-8") else: if six.PY2: - # six.u mangles quotes with escape sequences - use native unicode() + # six.u mangles quotes with escape sequences - use native + # unicode() val = unicode(val) # noqa: F821 else: val = str(val) @@ -226,13 +240,15 @@ def marshal(self, cidx, ridx, dtype): return val def _get_numpy_column(self, name): - '''For columnar datasets, return the list/Numpy array that contains the data for a single column. + '''For columnar datasets, return the :obj:`list`/Numpy array that + contains the data for a single column. Args: - name (str): the column name to look up + name (:obj:`str)`: the column name to look up Returns: - list/numpy.array/None : returns the column's data, or None if it cannot be found. + (:obj:`list`/numpy.array/None): returns the column's data, or None + if it cannot be found. ''' data = self._data_or_schema.get(name, None) if data is None: @@ -242,14 +258,16 @@ def _get_numpy_column(self, name): def _has_column(self, ridx, name): '''Given a column name, validate that it is in the row. - This allows differentiation between value is None (unset) and value not in row (no-op). + This allows differentiation between value is None (unset) and value not + in row (no-op). Args: ridx (int) name (str) Returns: - bool : True if column is in row, or if column belongs to pkey/op columns required by the engine. False otherwise. + bool: True if column is in row, or if column belongs to pkey/op + columns required by the engine. False otherwise. ''' if name in ("psp_pkey", "psp_okey", "psp_op"): return True diff --git a/python/perspective/perspective/table/_callback_cache.py b/python/perspective/perspective/table/_callback_cache.py index 970563032f..ce09654eaa 100644 --- a/python/perspective/perspective/table/_callback_cache.py +++ b/python/perspective/perspective/table/_callback_cache.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # @@ -18,7 +18,8 @@ def remove_callbacks(self, condition): '''Remove callback functions that satisfy the given condition. Args: - condition (func): a function that returns either True or False. If True is returned, filter the item out. + condition (func): a function that returns either True or False. If + True is returned, filter the item out. ''' if not callable(condition): raise ValueError("callback filter condition must be a callable function!") diff --git a/python/perspective/perspective/table/_constants.py b/python/perspective/perspective/table/_constants.py index 8a1c5d518c..9511b02074 100644 --- a/python/perspective/perspective/table/_constants.py +++ b/python/perspective/perspective/table/_constants.py @@ -1,8 +1,9 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + COLUMN_SEPARATOR_STRING = "|" diff --git a/python/perspective/perspective/table/_data_formatter.py b/python/perspective/perspective/table/_data_formatter.py index c0a1275f8b..3b82e65df6 100644 --- a/python/perspective/perspective/table/_data_formatter.py +++ b/python/perspective/perspective/table/_data_formatter.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 numpy as np from math import trunc from ._constants import COLUMN_SEPARATOR_STRING @@ -20,7 +21,8 @@ def _mod(a, b): '''C-style modulo function''' if b == 0: - # Javascript returns NaN in cases of division by 0; return -1 because None would fail comparisons with other ints + # Javascript returns NaN in cases of division by 0; return -1 because + # None would fail comparisons with other ints return float('nan') d = trunc(float(a) / b) return a - d * b @@ -92,6 +94,8 @@ def to_format(options, view, output_format): for pkey in pkeys: data[-1]['__INDEX__'].append(pkey) elif output_format in ('dict', 'numpy'): + # ensure that `__INDEX__` has the same number of rows as + # returned dataset if len(pkeys) == 0: data["__INDEX__"].append([]) # ensure that `__INDEX__` has the same number of rows as returned dataset for pkey in pkeys: @@ -109,7 +113,9 @@ def to_format(options, view, output_format): def _to_format_helper(view, options=None): - '''Retrieves the data slice and column names in preparation for data serialization.''' + '''Retrieves the data slice and column names in preparation for data + serialization. + ''' options = options or {} opts = _parse_format_options(view, options) diff --git a/python/perspective/perspective/table/_date_validator.py b/python/perspective/perspective/table/_date_validator.py index 42f9492679..7c457e07bd 100644 --- a/python/perspective/perspective/table/_date_validator.py +++ b/python/perspective/perspective/table/_date_validator.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 six import time import numpy @@ -23,13 +24,15 @@ def _normalize_timestamp(obj): - '''Convert a timestamp in seconds to milliseconds.''' + '''Convert a timestamp in seconds to milliseconds. + + If the input overflows, it is treated as milliseconds - otherwise it is + treated as seconds and converted. + ''' try: - # if it overflows, it's milliseconds - otherwise convert from seconds to milliseconds. datetime.fromtimestamp(obj) return int(obj * 1000) except (ValueError, OverflowError): - # milliseconds return int(obj) @@ -37,17 +40,21 @@ class _PerspectiveDateValidator(object): '''Validate and parse dates using the `dateutil` package.''' def parse(self, str): - '''Return a datetime.datetime object containing the parsed date, or None if the date is invalid. + '''Return a datetime.datetime object containing the parsed date, or + None if the date is invalid. - If a ISO date string with a timezone is provided, there is no guarantee that timezones will be properly handled, - as the core engine stores the timestamp as milliseconds since epoch. When a datetime is retrieved from the engine, - it is constructed with the timestamp, thus any timezone set on the input data will not apply to the output data. + If a ISO date string with a timezone is provided, there is no guarantee + that timezones will be properly handled, as the core engine stores the + timestamp as milliseconds since epoch. When a datetime is retrieved from + the engine, it is constructed with the timestamp, thus any timezone set + on the input data will not apply to the output data. Args: str (str): the datestring to parse Returns: - A datetime.date or datetime.datetime object if parse is successful, None otherwise + (:class:`datetime.date`/`datetime.datetime`/`None`): if parse is + successful. ''' try: return parse(str) @@ -55,9 +62,11 @@ def parse(self, str): return None def to_date_components(self, obj): - '''Return a dictionary of string keys and integer values for `year`, `month`, and `day`. + '''Return a dictionary of string keys and integer values for `year`, + `month`, and `day`. - This method converts both datetime.date and numpy.datetime64 objects that contain datetime.date. + This method converts both datetime.date and numpy.datetime64 objects + that contain datetime.date. ''' if obj is None: return obj @@ -84,9 +93,11 @@ def to_date_components(self, obj): } def to_timestamp(self, obj): - '''Return an integer that corresponds to the Unix timestamp, i.e. number of milliseconds since epoch. + '''Return an integer that corresponds to the Unix timestamp, i.e. + number of milliseconds since epoch. - This method converts both datetime.datetime and numpy.datetime64 objects. + This method converts both datetime.datetime and numpy.datetime64 + objects. ''' if obj is None: return obj @@ -126,22 +137,25 @@ def to_timestamp(self, obj): if isinstance(obj, (int, float)): return _normalize_timestamp(obj) - # Convert `datetime.datetime` and `pandas.Timestamp` to millisecond timestamps return int((time.mktime(obj.timetuple()) + obj.microsecond / 1000000.0) * 1000) + # Convert `datetime.datetime` and `pandas.Timestamp` to millisecond + # timestamps def format(self, s): - '''Return either t_dtype.DTYPE_DATE or t_dtype.DTYPE_TIME depending on the format of the parsed date. + '''Return either t_dtype.DTYPE_DATE or t_dtype.DTYPE_TIME depending on + the format of the parsed date. - If the parsed date is invalid, return t_dtype.DTYPE_STR to prevent further attempts at conversion. - - Attempt to use heuristics about dates to minimize false positives, i.e. do not parse dates without separators. + If the parsed date is invalid, return t_dtype.DTYPE_STR to prevent + further attempts at conversion. Attempt to use heuristics about dates + to minimize false positives, i.e. do not parse dates without separators. Args: - str (str): the datestring to parse + str (str): the datestring to parse. ''' if isinstance(s, (bytes, bytearray)): s = s.decode("utf-8") has_separators = bool(search(r"[/. -]", s)) # match commonly-used date separators + # match commonly-used date separators dtype = t_dtype.DTYPE_STR diff --git a/python/perspective/perspective/table/_utils.py b/python/perspective/perspective/table/_utils.py index 45e53514a8..5a58937da6 100644 --- a/python/perspective/perspective/table/_utils.py +++ b/python/perspective/perspective/table/_utils.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from datetime import date, datetime try: @@ -41,7 +42,9 @@ def _dtype_to_pythontype(dtype): def _dtype_to_str(dtype): - '''Returns the normalized string representation of a Perspective type, compatible with Perspective.js''' + '''Returns the normalized string representation of a Perspective type, + compatible with Perspective.js. + ''' mapping = { t_dtype.DTYPE_BOOL: "boolean", t_dtype.DTYPE_FLOAT32: "float", @@ -59,7 +62,9 @@ def _dtype_to_str(dtype): def _str_to_pythontype(typestring): - '''Returns a Python type from the normalized string representation of a Perspective type, i.e. from Perspective.js''' + '''Returns a Python type from the normalized string representation of a + Perspective type, i.e. from Perspective.js + ''' mapping = { "integer": int, "float": float, diff --git a/python/perspective/perspective/table/table.py b/python/perspective/perspective/table/table.py index 20cc7d04fb..ee583d3b64 100644 --- a/python/perspective/perspective/table/table.py +++ b/python/perspective/perspective/table/table.py @@ -131,7 +131,8 @@ def computed_schema(self): def is_valid_filter(self, filter): '''Tests whether a given filter expression string is valid, e.g. that - the filter term is not None or an unparsable date/datetime. + the filter term is not None or an unparsable date/datetime. `null`/ + `not null` operators don't need a comparison value. Args: filter (:obj:`string`): The filter expression to validate. diff --git a/python/perspective/perspective/table/view.py b/python/perspective/perspective/table/view.py index 0b5ae07917..e3884ec75c 100644 --- a/python/perspective/perspective/table/view.py +++ b/python/perspective/perspective/table/view.py @@ -453,7 +453,9 @@ def _num_hidden_cols(self): return hidden def _wrapped_on_update_callback(self, **kwargs): - '''Provide the user-defined callback function with additional metadata from the view.''' + '''Provide the user-defined callback function with additional metadata + from the view. + ''' mode = kwargs["mode"] cache = kwargs["cache"] callback = kwargs["callback"] diff --git a/python/perspective/perspective/table/view_config.py b/python/perspective/perspective/table/view_config.py index c699f70931..e5d2d62a1f 100644 --- a/python/perspective/perspective/table/view_config.py +++ b/python/perspective/perspective/table/view_config.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # @@ -11,27 +11,26 @@ class ViewConfig(object): '''Defines the configuration for a View object.''' def __init__(self, **config): - '''Receives a user-provided config dict and standardizes it for consumption by the python client and the core engine. + '''Receives a user-provided config dict and standardizes it for + reference. Keyword Arguments: - - columns ``(list[str])`` - - A list of column names to be visible to the user. - - row_pivots ``(list[str])`` - - A list of column names to use as row pivots, thus grouping data by row. - - column_pivots ``(list[str])`` - - A list of column names to use as column pivots, thus grouping data by column. - - aggregates ``(dict[str:str])`` - - A dictionary of column names to aggregate types, which specify aggregates for individual columns. - - sort ``(list[list[str]])`` - - A list of lists, each list containing a column name and a sort direction (asc, desc, col asc, col desc). - - filter ``(list[list[str]])`` - - A list of lists, each list containing a column name, a filter comparator, and a value to filter by. + columns (:obj:`list` of :obj:`str`): A list of column names to be + visible to the user. + row_pivots (:obj:`list` of :obj:`str`): A list of column names to + use as row pivots. + column_pivots (:obj:`list` of :obj:`str`): A list of column names + to use as column pivots. + aggregates (:obj:`dict` of :obj:`str` to :obj:`str`): A dictionary + of column names to aggregate types, which specify aggregates + for individual columns. + sort (:obj:`list` of :obj:`list` of :obj:`str`): A list of lists, + each list containing a column name and a sort direction + (``asc``, ``desc``, ``asc abs``, ``desc abs``, ``col asc``, + ``col desc``, ``col asc abs``, ``col desc abs``). + filter (:obj:`list` of :obj:`list` of :obj:`str`): A list of lists, + each list containing a column name, a filter comparator, and a + value to filter by. ''' self._config = config self._row_pivots = self._config.get('row_pivots', []) @@ -45,7 +44,8 @@ def __init__(self, **config): self.column_pivot_depth = self._config.get("column_pivot_depth", None) def get_row_pivots(self): - '''The columns used as [row pivots](https://en.wikipedia.org/wiki/Pivot_table#Row_labels) + '''The columns used as + [row pivots](https://en.wikipedia.org/wiki/Pivot_table#Row_labels) Returns: list : the columns used as row pivots @@ -53,7 +53,8 @@ def get_row_pivots(self): return self._row_pivots def get_column_pivots(self): - '''The columns used as [column pivots](https://en.wikipedia.org/wiki/Pivot_table#Column_labels) + '''The columns used as + [column pivots](https://en.wikipedia.org/wiki/Pivot_table#Column_labels) Returns: list : the columns used as column pivots @@ -64,52 +65,60 @@ def get_aggregates(self): '''Defines the grouping of data within columns. Returns: - dict[str:str] : a vector of string vectors in which the first value is the column name, and the second value is the string representation of an aggregate + dict[str:str] a vector of string vectors in which the first value + is the column name, and the second value is the string + representation of an aggregate ''' return self._aggregates def get_columns(self): - '''The columns that will be shown to the user in the view. If left empty, the view shows all columns in the dataset by default. + '''The columns that will be shown to the user in the view. If left + empty, the view shows all columns in the dataset by default. Returns: - list : the columns shown to the user + `list` : the columns shown to the user ''' return self._columns def get_sort(self): '''The columns that should be sorted, and the direction to sort. - A sort configuration is a list of two elements: a string column name, and a string sort direction, which are: - "none", "asc", "desc", "col asc", "col desc", "asc abs", "desc abs", "col asc abs", and "col desc abs". + A sort configuration is a `list` of two elements: a string column name, + and a string sort direction, which are: "none", "asc", "desc", + "col asc", "col desc", "asc abs", "desc abs", "col asc abs", and + "col desc abs". Returns: - list : the sort configurations of the view stored in a list of lists + `list`: the sort configurations of the view stored in a `list` of + `list`s ''' return self._sort def get_filter(self): '''The columns that should be filtered. - A filter configuration is a list of three elements: - - 0: a string column name + A filter configuration is a `list` of three elements: + 0: `str` column name. 1: a filter comparison string (i.e. "===", ">") - 2: a value to compare (this will be casted to match the type of the column) + 2: a value to compare (this will be casted to match the type of + the column) Returns: - list : the filter configurations of the view stored in a list of lists + `list`: the filter configurations of the view stored in a `list` of + lists ''' return self._filter def get_filter_op(self): - '''When multiple filters are applied, filter_op defines how data should be returned. + '''When multiple filters are applied, filter_op defines how data should + be returned. - Defaults to "and" if not set by the user, meaning that data returned with multiple filters will satisfy all filters. + Defaults to "and" if not set by the user, meaning that data returned + with multiple filters will satisfy all filters. If "or" is provided, + returned data will satsify any one of the filters applied. - If "or" is provided, returned data will satsify any one of the filters applied. - - Returns - string : the filter_op of the view + Returns: + `str`: the filter_op of the view ''' return self._filter_op diff --git a/python/perspective/perspective/tests/conftest.py b/python/perspective/perspective/tests/conftest.py index c52ad6a06b..48d3614a45 100644 --- a/python/perspective/perspective/tests/conftest.py +++ b/python/perspective/perspective/tests/conftest.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 six import time import numpy as np @@ -22,7 +23,9 @@ def _make_period_index(size, time_unit): def _make_dataframe(index, size=10): - '''Create a new random dataframe of `size` and with a DateTimeIndex of frequency `time_unit`.''' + '''Create a new random dataframe of `size` and with a DateTimeIndex of + frequency `time_unit`. + ''' return pd.DataFrame(index=index, data={ "a": np.random.rand(size), "b": np.random.rand(size), @@ -66,7 +69,9 @@ def make_series(size=10, freq="D"): class Sentinel(object): - '''Generic sentinel class for testing side-effectful code in Python 2 and 3.''' + '''Generic sentinel class for testing side-effectful code in Python 2 and + 3. + ''' def __init__(self, value): self.value = value @@ -80,7 +85,8 @@ def set(self, new_value): @fixture() def sentinel(): - '''Pass `sentinel` into a test and call it with `value` to create a new instance of the Sentinel class. + '''Pass `sentinel` into a test and call it with `value` to create a new + instance of the Sentinel class. Example: >>> def test_with_sentinel(self, sentinel): diff --git a/python/perspective/perspective/tests/core/test_aggregates.py b/python/perspective/perspective/tests/core/test_aggregates.py index b130db34bb..4007a8ac04 100644 --- a/python/perspective/perspective/tests/core/test_aggregates.py +++ b/python/perspective/perspective/tests/core/test_aggregates.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from perspective.core import Aggregate from perspective import PerspectiveWidget diff --git a/python/perspective/perspective/tests/core/test_layout.py b/python/perspective/perspective/tests/core/test_layout.py index 003510652d..1f8dda5787 100644 --- a/python/perspective/perspective/tests/core/test_layout.py +++ b/python/perspective/perspective/tests/core/test_layout.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 pandas as pd from mock import patch from perspective import PerspectiveWidget, Plugin, PerspectiveError diff --git a/python/perspective/perspective/tests/core/test_manager.py b/python/perspective/perspective/tests/core/test_manager.py index 7cebe077e7..639498de60 100644 --- a/python/perspective/perspective/tests/core/test_manager.py +++ b/python/perspective/perspective/tests/core/test_manager.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 json from pytest import raises from perspective import Table, PerspectiveError, PerspectiveManager diff --git a/python/perspective/perspective/tests/core/test_plugin.py b/python/perspective/perspective/tests/core/test_plugin.py index 5042360fa0..c78f60311a 100644 --- a/python/perspective/perspective/tests/core/test_plugin.py +++ b/python/perspective/perspective/tests/core/test_plugin.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from perspective.core import Plugin from perspective import PerspectiveWidget diff --git a/python/perspective/perspective/tests/core/test_validate.py b/python/perspective/perspective/tests/core/test_validate.py index 578ad0e2ea..b5aff764c1 100644 --- a/python/perspective/perspective/tests/core/test_validate.py +++ b/python/perspective/perspective/tests/core/test_validate.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from pytest import raises from perspective.core import PerspectiveError from perspective.core import Plugin diff --git a/python/perspective/perspective/tests/core/test_viewer.py b/python/perspective/perspective/tests/core/test_viewer.py index c1df05fe27..6c805cb4dd 100644 --- a/python/perspective/perspective/tests/core/test_viewer.py +++ b/python/perspective/perspective/tests/core/test_viewer.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 numpy as np import pandas as pd from perspective import PerspectiveViewer, Table diff --git a/python/perspective/perspective/tests/core/test_widget.py b/python/perspective/perspective/tests/core/test_widget.py index 4d470212b4..0b1aa33721 100644 --- a/python/perspective/perspective/tests/core/test_widget.py +++ b/python/perspective/perspective/tests/core/test_widget.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 six import numpy as np import pandas as pd diff --git a/python/perspective/perspective/tests/core/test_widget_pandas.py b/python/perspective/perspective/tests/core/test_widget_pandas.py index 74574a16a0..fa86644f8e 100644 --- a/python/perspective/perspective/tests/core/test_widget_pandas.py +++ b/python/perspective/perspective/tests/core/test_widget_pandas.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from datetime import date import pandas as pd import numpy as np diff --git a/python/perspective/perspective/tests/node/__init__.py b/python/perspective/perspective/tests/node/__init__.py index 7e17fd30e8..42db35c427 100644 --- a/python/perspective/perspective/tests/node/__init__.py +++ b/python/perspective/perspective/tests/node/__init__.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/tests/node/test_node.py b/python/perspective/perspective/tests/node/test_node.py index 48d6335cdb..e167b51670 100644 --- a/python/perspective/perspective/tests/node/test_node.py +++ b/python/perspective/perspective/tests/node/test_node.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from perspective.node import Perspective from pytest import mark diff --git a/python/perspective/perspective/tests/table/test_delete.py b/python/perspective/perspective/tests/table/test_delete.py index 9a9d87417e..ad58af7d19 100644 --- a/python/perspective/perspective/tests/table/test_delete.py +++ b/python/perspective/perspective/tests/table/test_delete.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/tests/table/test_exception.py b/python/perspective/perspective/tests/table/test_exception.py index 127c3827d4..c6491d7ccd 100644 --- a/python/perspective/perspective/tests/table/test_exception.py +++ b/python/perspective/perspective/tests/table/test_exception.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from pytest import raises from perspective import Table, PerspectiveError, PerspectiveCppError diff --git a/python/perspective/perspective/tests/table/test_view.py b/python/perspective/perspective/tests/table/test_view.py index ff6138000b..5c2eb059a5 100644 --- a/python/perspective/perspective/tests/table/test_view.py +++ b/python/perspective/perspective/tests/table/test_view.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/tornado_handler/__init__.py b/python/perspective/perspective/tornado_handler/__init__.py index a1c89d5bcc..7c70d1d1ca 100644 --- a/python/perspective/perspective/tornado_handler/__init__.py +++ b/python/perspective/perspective/tornado_handler/__init__.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/tornado_handler/tornado_handler.py b/python/perspective/perspective/tornado_handler/tornado_handler.py index 2b0964c2df..5c5c4e11f9 100644 --- a/python/perspective/perspective/tornado_handler/tornado_handler.py +++ b/python/perspective/perspective/tornado_handler/tornado_handler.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 json import tornado.websocket from datetime import datetime @@ -16,11 +17,14 @@ class DateTimeEncoder(json.JSONEncoder): - '''Create a custom JSON encoder that allows serialization of datetime and date objects.''' + '''Create a custom JSON encoder that allows serialization of datetime and + date objects. + ''' def default(self, obj): if isinstance(obj, datetime.datetime): - # Convert to milliseconds - perspective.js expects millisecond timestamps, but python generates them in seconds. + # Convert to milliseconds - perspective.js expects millisecond + # timestamps, but python generates them in seconds. return _date_validator.to_timestamp(obj) else: return super(DateTimeEncoder, self).default(obj) @@ -29,28 +33,32 @@ def default(self, obj): class PerspectiveTornadoHandler(tornado.websocket.WebSocketHandler): '''PerspectiveTornadoHandler is a drop-in implementation of Perspective. - Use it inside Tornado routing to create a server-side Perspective that is ready to receive websocket messages from - the front-end `perspective-viewer`. + Use it inside Tornado routing to create a server-side Perspective that is + ready to receive websocket messages from the front-end `perspective-viewer`. Examples: >>> MANAGER = PerspectiveViewer() - >>> MANAGER.host_table("data_source_one", Table(pd.read_csv("superstore.csv"))) + >>> MANAGER.host_table("data_source_one", Table( + ... pd.read_csv("superstore.csv"))) >>> app = tornado.web.Application([ - (r"/", MainHandler), - (r"/websocket", PerspectiveTornadoHandler, {"manager": MANAGER, "check_origin": True}) - ]) + ... (r"/", MainHandler), + ... (r"/websocket", PerspectiveTornadoHandler, { + ... "manager": MANAGER, + ... "check_origin": True + ... }) + ... ]) ''' def __init__(self, *args, **kwargs): - '''Create a new instance of the PerspectiveTornadoHandler with the given Manager instance. + '''Create a new instance of the PerspectiveTornadoHandler with the + given Manager instance. Keyword Arguments: + manager (`PerspectiveManager`): A `PerspectiveManager` instance. + Must be provided on initialization. - manager ``(PerspectiveManager)`` - - A `PerspectiveManager` instance. Must be provided on initialization. - - check_origin ``(bool)`` - - If True, all requests will be accepted regardless of origin. Defaults to False. + check_origin (`bool`): If True, all requests will be accepted + regardless of origin. Defaults to False. ''' self._manager = kwargs.pop("manager", None) self._session = self._manager.new_session() @@ -62,24 +70,33 @@ def __init__(self, *args, **kwargs): super(PerspectiveTornadoHandler, self).__init__(*args, **kwargs) def check_origin(self, origin): - '''Returns whether the handler allows requests from origins outside of the host URL.''' + '''Returns whether the handler allows requests from origins outside + of the host URL. + ''' return self._check_origin def on_message(self, message): - '''When the websocket receives a message, send it to the `process` method of the `PerspectiveManager` with a reference to the `post` callback.''' + '''When the websocket receives a message, send it to the `process` + method of the `PerspectiveManager` with a reference to the `post` + callback. + ''' if message == "heartbeat": return message = json.loads(message) self._session.process(message, self.post) def post(self, message): - '''When `post` is called by `PerspectiveManager`, serialize the data to JSON and send it to the client. + '''When `post` is called by `PerspectiveManager`, serialize the data to + JSON and send it to the client. Args: - message (str): a JSON-serialized string containing a message to the front-end `perspective-viewer`. + message (str): a JSON-serialized string containing a message to the + front-end `perspective-viewer`. ''' self.write_message(message) def on_close(self): - '''Remove the views associated with the client when the websocket closes.''' + '''Remove the views associated with the client when the websocket + closes. + ''' self._session.close() diff --git a/python/perspective/perspective/viewer/__init__.py b/python/perspective/perspective/viewer/__init__.py index 03a0db0d27..fd7db54670 100644 --- a/python/perspective/perspective/viewer/__init__.py +++ b/python/perspective/perspective/viewer/__init__.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/viewer/validate.py b/python/perspective/perspective/viewer/validate.py index c6e159d0ac..25779d8c52 100644 --- a/python/perspective/perspective/viewer/validate.py +++ b/python/perspective/perspective/viewer/validate.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from six import iteritems, string_types from ..core.exception import PerspectiveError from ..core import Aggregate, Plugin, ALL_FILTERS, Sort diff --git a/python/perspective/perspective/viewer/viewer.py b/python/perspective/perspective/viewer/viewer.py index 7a7366a92d..b6c9626b40 100644 --- a/python/perspective/perspective/viewer/viewer.py +++ b/python/perspective/perspective/viewer/viewer.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # @@ -15,7 +15,10 @@ class PerspectiveViewer(PerspectiveTraitlets, object): - '''PerspectiveViewer wraps the `perspective.Table` API and exposes an API around creating views, loading data, and updating data.''' + '''PerspectiveViewer wraps the `perspective.Table` API and exposes an API + around creating views, loading data, and updating data. + ''' + def __init__(self, plugin='hypergrid', columns=None, @@ -27,45 +30,45 @@ def __init__(self, plugin_config=None, dark=False, editable=False): - '''Initialize an instance of `PerspectiveViewer` with the given viewer configuration. - - Do not pass a `Table` or data into the constructor - use the `load()` method to provide the viewer with data. - - Keyword Args: - plugin ``(str|perspective.Plugin)`` - - The grid or visualization that will be displayed on render. Defaults to "hypergrid". - - columns ``(list[str])`` - - A list of column names to be visible to the user. - - row_pivots ``(list[str])`` - - A list of column names to use as row pivots, thus grouping data by row. - - column_pivots ``(list[str])`` - - A list of column names to use as column pivots, thus grouping data by column. - - aggregates ``(dict[str:str])`` - - A dictionary of column names to aggregate types, which specify aggregates for individual columns. - - sort ``(list[list[str]])`` - - A list of lists, each list containing a column name and a sort direction (asc, desc, col asc, col desc). - - filter ``(list[list[str]])`` - - A list of lists, each list containing a column name, a filter comparator, and a value to filter by. - - plugin_config ``(dict)`` - - An optional configuration containing the interaction state of a `perspective-viewer`. + '''Initialize an instance of `PerspectiveViewer` with the given viewer + configuration. Do not pass a `Table` or data into the constructor - + use the :func:`load()` method to provide the viewer with data. - dark ``(bool)`` - - Enables/disables dark mode on the viewer. Defaults to ``False``. + Keyword Arguments: + columns (:obj:`list` of :obj:`str`): A list of column names to be + visible to the user. + row_pivots (:obj:`list` of :obj:`str`): A list of column names to + use as row pivots. + column_pivots (:obj:`list` of :obj:`str`): A list of column names + to use as column pivots. + aggregates (:obj:`dict` of :obj:`str` to :obj:`str`): A dictionary + of column names to aggregate types, which specify aggregates + for individual columns. + sort (:obj:`list` of :obj:`list` of :obj:`str`): A list of lists, + each list containing a column name and a sort direction + (``asc``, ``desc``, ``asc abs``, ``desc abs``, ``col asc``, + ``col desc``, ``col asc abs``, ``col desc abs``). + filter (:obj:`list` of :obj:`list` of :obj:`str`): A list of lists, + each list containing a column name, a filter comparator, and a + value to filter by. + plugin (`str`/`perspective.Plugin`): Which plugin to select by + default. + plugin_config (`dict`): Custom config for all plugins by name. + dark (`bool`): Whether to invert the colors. Examples: - >>> viewer = PerspectiveViewer(aggregates={"a": "avg"}, row_pivots=["a"], sort=[["b", "desc"]], filter=[["a", ">", 1]]) + >>> viewer = PerspectiveViewer( + ... aggregates={"a": "avg"}, + ... row_pivots=["a"], + ... sort=[["b", "desc"]], + ... filter=[["a", ">", 1]] + ... ) ''' - # Create an instance of `PerspectiveManager`, which receives messages from the `PerspectiveJupyterClient` on the front-end. + # Create an instance of `PerspectiveManager`, which receives messages + # from the `PerspectiveJupyterClient` on the front-end. self.manager = PerspectiveManager() - self.table_name = None # not a traitlet - only used in the python side of the viewer + self.table_name = None # not a traitlet - only used in python self.view_name = None # Viewer configuration @@ -93,29 +96,29 @@ def view(self): return self.manager.get_view(self.view_name) def load(self, table_or_data, **options): - '''Given a `perspective.Table` or data that can be handled by `perspective.Table`, pass it to the viewer. - - `load()` resets the state of the viewer. - - If a `perspective.Table` is passed into `table_or_data`, `**options` is ignored as the options already set on the `Table` take precedence. + '''Given a `perspective.Table` or data that can be handled by + `perspective.Table`, pass it to the viewer. - If data is passed in, a `perspective.Table` is automatically created by this function, and the options passed to `**config` are extended to the new Table. + `load()` resets the state of the viewer. If a `perspective.Table` + is passed into `table_or_data`, `**options` is ignored as the options + already set on the `Table` take precedence. If data is passed in, a + `perspective.Table` is automatically created by this function, and the + options passed to `**config` are extended to the new Table. Args: - table_or_data (Table|dict|list|pandas.DataFrame): a `perspective.Table` instance or a dataset to be displayed in the viewer. + table_or_data (`Table`|`dict`|`list`|`pandas.DataFrame`): a + `perspective.Table` instance or a dataset to be displayed + in the viewer. Keyword Arguments: - - name ``(str)`` - - An optional name to reference the table by so it can be accessed from the front-end. If not provided, a name will be generated. - - index ``(str)`` - - The name of a column that will be the dataset's primary key. This sorts the dataset in ascending order based on primary key. - - limit ``(int)`` - - The total number of rows that will be loaded into Perspective. - - Cannot be applied at the same time as index - - Updates past ``limit`` begin writing at row 0. + name (`str`): An optional name to reference the table by so it can + be accessed from the front-end. If not provided, a name will + be generated. + index (`str`): A column name to be used as the primary key. + Ignored if a `Table` is supplied. + limit (`int`): A upper limit on the number of rows in the Table. + Cannot be set at the same time as `index`, ignored if a `Table` + is passed in. Examples: >>> from perspective import Table, PerspectiveViewer @@ -123,7 +126,7 @@ def load(self, table_or_data, **options): >>> tbl = Table(data) >>> viewer = PerspectiveViewer() >>> viewer.load(tbl) - >>> viewer.load(data, index="a") # kwargs are forwarded to the `Table` constructor. + >>> viewer.load(data, index="a") # viewer state is reset ''' name = options.pop("name", str(random())) if isinstance(table_or_data, Table): @@ -137,7 +140,8 @@ def load(self, table_or_data, **options): if self.table_name is not None: self.reset() - # If the user does not set columns to show, synchronize viewer state with dataset. + # If the user does not set columns to show, synchronize viewer state + # with dataset. if len(self.columns) == 0: self.columns = table.columns() @@ -145,11 +149,12 @@ def load(self, table_or_data, **options): def update(self, data): '''Update the table under management by the viewer with new data. - - This function follows the semantics of `Table.update()`, and will be affected by whether an index is set on the underlying table. + This function follows the semantics of `Table.update()`, and will be + affected by whether an index is set on the underlying table. Args: - data (dict|list|pandas.DataFrame): the update data for the table. + data (`dict`|`list`|`pandas.DataFrame`): the update data for the + table. ''' self.table.update(data) @@ -162,13 +167,16 @@ def replace(self, data): '''Replaces the rows of this viewer's `Table` with new data. Args: - data : new data to set into the table - must conform to the table's schema. + data (`dict`|`list`|`pandas.DataFrame`): new data to set into the + table - must conform to the table's schema. ''' if self.table is not None: self.table.replace(data) def reset(self): - '''Resets the viewer's attributes and state, but does not delete or modify the underlying `Table`.''' + '''Resets the viewer's attributes and state, but does not delete or + modify the underlying `Table`. + ''' self.row_pivots = [] self.column_pivots = [] self.filters = [] @@ -178,11 +186,13 @@ def reset(self): self.plugin = "hypergrid" def delete(self, delete_table=True): - '''Delete the Viewer's data and clears its internal state. If `delete_table` is True, - the underlying `perspective.Table` and all associated `View`s will be deleted. + '''Delete the Viewer's data and clears its internal state. If + `delete_table` is True, the underlying `perspective.Table` and all + associated `View`s will be deleted. Args: - delete_table (bool) : whether the underlying `Table` will be deleted. Defaults to True. + delete_table (bool) : whether the underlying `Table` will be + deleted. Defaults to True. ''' if self.view: self.view.delete() @@ -199,11 +209,11 @@ def delete(self, delete_table=True): self.reset() def _new_view(self): - '''Create a new View, and assign its name to the viewer. - - Do not call this function - it will be called automatically when the state of the viewer changes. - - There should only be one View associated with the Viewer at any given time - when a new View is created, the old one is destroyed. + '''Create a new View, and assign its name to the viewer. Do not call + this function - it will be called automatically when the state of the + viewer changes. There should only be one View associated with the + Viewer at any given time - when a new View is created, the old one + is destroyed. ''' if not self.table_name: return diff --git a/python/perspective/perspective/viewer/viewer_traitlets.py b/python/perspective/perspective/viewer/viewer_traitlets.py index cccef59734..51f0eddb60 100644 --- a/python/perspective/perspective/viewer/viewer_traitlets.py +++ b/python/perspective/perspective/viewer/viewer_traitlets.py @@ -1,22 +1,24 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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. # + from traitlets import HasTraits, Unicode, List, Bool, Dict, validate from .validate import validate_plugin, validate_columns, validate_row_pivots, validate_column_pivots, \ validate_aggregates, validate_sort, validate_filters, validate_plugin_config class PerspectiveTraitlets(HasTraits): - '''Define the traitlet interface with `PerspectiveJupyterWidget` on the front end. - - Properties that are set here are synchronized between the front-end and back-end. + '''Define the traitlet interface with `PerspectiveJupyterWidget` on the + front end. Attributes which are set here are synchronized between the + front-end and back-end. Examples: - >>> widget = perspective.PerspectiveWidget(data, row_pivots=["a", "b", "c"]) + >>> widget = perspective.PerspectiveWidget( + ... data, row_pivots=["a", "b", "c"]) PerspectiveWidget(row_pivots=["a", "b", "c"]) >>> widget.column_pivots=["b"] >>> widget diff --git a/python/perspective/perspective/widget/__init__.py b/python/perspective/perspective/widget/__init__.py index 1dcb82f787..be4129e58b 100644 --- a/python/perspective/perspective/widget/__init__.py +++ b/python/perspective/perspective/widget/__init__.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/python/perspective/perspective/widget/widget.py b/python/perspective/perspective/widget/widget.py index 8c58bbdf6c..7211dbfd1b 100644 --- a/python/perspective/perspective/widget/widget.py +++ b/python/perspective/perspective/widget/widget.py @@ -1,10 +1,11 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, 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 six import numpy import pandas @@ -20,9 +21,9 @@ def _type_to_string(t): - '''Convert a type object to a string representing a Perspective-supported type. - - Redefine here as we can't have any dependencies on libbinding in client mode. + '''Convert a type object to a string representing a Perspective-supported + type. Redefine here as we can't have any dependencies on libbinding in + client mode. ''' if t in six.integer_types: return "integer" @@ -76,12 +77,13 @@ def _serialize(data): class _PerspectiveWidgetMessage(object): - '''A custom message that will be passed from the Python widget to the front-end. - - When creating new messages, use this class as it defines a concrete schema for the message and - prevents loosely creating `dict` objects everywhere. + '''A custom message that will be passed from the Python widget to the + front-end. - Use `to_dict()` to obtain the message in a form that can be sent through IPyWidgets. + When creating new messages, use this class as it defines a concrete schema + for the message and prevents loosely creating `dict` objects everywhere. + Use `to_dict()` to obtain the message in a form that can be sent through + IPyWidgets. ''' def __init__(self, msg_id, msg_type, msg_data): @@ -100,25 +102,37 @@ def to_dict(self): class PerspectiveWidget(Widget, PerspectiveViewer): - '''`PerspectiveWidget` allows for Perspective to be used in the form of a JupyterLab IPython widget. + ''':class`~perspective.PerspectiveWidget` allows for Perspective to be used + in the form of a JupyterLab IPython widget. - Using `perspective.Table`, you can create a widget that extends the full functionality of `perspective-viewer`. - - Changes on the viewer can be programatically set on the `PerspectiveWidget` instance, and state is maintained across page refreshes. + Using `perspective.Table`, you can create a widget that extends the full + functionality of `perspective-viewer`. Changes on the viewer can be + programatically set on the :class`~perspective.PerspectiveWidget` instance, + and state is maintained across page refreshes. Examples: >>> from perspective import Table, PerspectiveWidget - >>> data = {"a": [1, 2, 3], "b": ["2019/07/11 7:30PM", "2019/07/11 8:30PM", "2019/07/11 9:30PM"]} + >>> data = { + ... "a": [1, 2, 3], + ... "b": [ + ... "2019/07/11 7:30PM", + ... "2019/07/11 8:30PM", + ... "2019/07/11 9:30PM" + ... ] + ... } >>> tbl = Table(data, index="a") - >>> widget = PerspectiveWidget(tbl, row_pivots=["a"], sort=[["b", "desc"]], filter=[["a", ">", 1]]) - >>> widget - PerspectiveWidget(tbl, row_pivots=["a"], sort=[["b", "desc"]], filter=[["a", ">", 1]]) + >>> widget = PerspectiveWidget( + ... tbl, + ... row_pivots=["a"], + ... sort=[["b", "desc"]], + ... filter=[["a", ">", 1]] + ... ) >>> widget.sort [["b", "desc"]] >>> widget.sort.append(["a", "asc"]) >>> widget.sort [["b", "desc"], ["a", "asc"]] - >>> widget.update({"a": [4, 5]}) # updates to the table reflect on the widget + >>> widget.update({"a": [4, 5]}) # Browser UI updates ''' # Required by ipywidgets for proper registration of the backend @@ -135,42 +149,45 @@ def __init__(self, limit=None, client=not is_libpsp(), **kwargs): - '''Initialize an instance of `PerspectiveWidget` with the given table/data and viewer configuration. - - If a pivoted DataFrame or MultiIndex table is passed in, the widget preserves pivots and applies them. + '''Initialize an instance of :class`~perspective.PerspectiveWidget` + with the given table/data and viewer configuration. - See `PerspectiveViewer.__init__` for arguments that transform the view shown in the widget. + If a pivoted DataFrame or MultiIndex table is passed in, the widget + preserves pivots and applies them. See `PerspectiveViewer.__init__` for + arguments that transform the view shown in the widget. Args: - table_or_data (perspective.Table|dict|list|pandas.DataFrame): The ``Table`` or data that will be viewed in the widget. + table_or_data (perspective.Table|dict|list|pandas.DataFrame): The + `Table` or data that will be viewed in the widget. Keyword Arguments: + index (`str`): A column name to be used as the primary key. + Ignored if a `Table` is supplied. + limit (`int`): A upper limit on the number of rows in the Table. + Cannot be set at the same time as `index`, ignored if a `Table` + is passed in. - index ``(str)`` - - A column name to be used as the primary key. Ignored if a `Table` is passed in. + client (`bool`): If True, convert the dataset into an Apache Arrow + binary and create the Table in Javascript using a copy of the + data. Defaults to False. - limit ``(int)`` - - A upper limit on the number of rows in the Table. Cannot be set at the same time as `index`, ignored if a `Table` is passed in. - - client ``(bool)`` - - If True, convert the dataset into an Apache Arrow binary and create the Table in Javascript using a copy of the data. Defaults to False. - - kwargs - - configuration options for the `PerspectiveViewer`, and `Table` constructor if `table_or_data` is a dataset. + kwargs (`dict`): configuration options for the `PerspectiveViewer`, + and `Table` constructor if `table_or_data` is a dataset. Examples: >>> widget = PerspectiveWidget( - {"a": [1, 2, 3]}, - aggregates={"a": "avg"}, - row_pivots=["a"], - sort=[["b", "desc"]], - filter=[["a", ">", 1]]) + ... {"a": [1, 2, 3]}, + ... aggregates={"a": "avg"}, + ... row_pivots=["a"], + ... sort=[["b", "desc"]], + ... filter=[["a", ">", 1]]) ''' self._displayed = False self.on_displayed(self._on_display) - # If `self.client` is True, the front-end `perspective-viewer` is given a copy of the data serialized to Arrow, - # and changes made in Python do not reflect to the front-end. + # If `self.client` is True, the front-end `perspective-viewer` is given + # a copy of the data serialized to Arrow, and changes made in Python + # do not reflect to the front-end. self.client = client if self.client: @@ -200,9 +217,11 @@ def __init__(self, # Initialize the viewer super(PerspectiveWidget, self).__init__(**kwargs) - # Handle messages from the the front end `PerspectiveJupyterClient.send()`. + # Handle messages from the the front end + # `PerspectiveJupyterClient.send()`: # - The "data" value of the message should be a JSON-serialized string. - # - Both `on_msg` and `@observe("value")` must be specified on the handler for custom messages to be parsed by the Python widget. + # - Both `on_msg` and `@observe("value")` must be specified on the + # handler for custom messages to be parsed by the Python widget. self.on_msg(self.handle_message) if self.client: @@ -215,7 +234,8 @@ def __init__(self, if limit is not None: self._client_options["limit"] = limit - # cache self._data so creating multiple views don't reserialize the same data + # cache self._data so creating multiple views don't reserialize the + # same data if not hasattr(self, "_data") or self._data is None: self._data = _serialize(table_or_data) else: @@ -234,8 +254,9 @@ def __init__(self, self.load(table_or_data, **load_kwargs) def load(self, data, **options): - '''Load the widget with data. If running in client mode, this method serializes the data - and calls the browser viewer's load method. Otherwise, it calls `Viewer.load()` using `super()`. + '''Load the widget with data. If running in client mode, this method + serializes the data and calls the browser viewer's load method. + Otherwise, it calls `Viewer.load()` using `super()`. ''' if self.client is True: # serialize the data and send a custom message to the browser @@ -251,8 +272,9 @@ def load(self, data, **options): self.send(message.to_dict()) def update(self, data): - '''Update the widget with new data. If running in client mode, this method serializes the data - and calls the browser viewer's update method. Otherwise, it calls `Viewer.update()` using `super()`. + '''Update the widget with new data. If running in client mode, this + method serializes the data and calls the browser viewer's update + method. Otherwise, it calls `Viewer.update()` using `super()`. ''' if self.client is True: if self._displayed is False: @@ -284,8 +306,10 @@ def clear(self): super(PerspectiveWidget, self).clear() def replace(self, data): - '''Replaces the widget's `Table` with new data conforming to the same schema. Does not clear - user-set state. If in client mode, serializes the data and sends it to the browser.''' + '''Replaces the widget's `Table` with new data conforming to the same + schema. Does not clear user-set state. If in client mode, serializes + the data and sends it to the browser. + ''' if self.client is True: if isinstance(data, pandas.DataFrame) or isinstance(data, pandas.Series): data, _ = deconstruct_pandas(data) @@ -299,11 +323,13 @@ def replace(self, data): super(PerspectiveWidget, self).replace(data) def delete(self, delete_table=True): - '''Delete the Widget's data and clears its internal state. If running in client mode, sends the - `delete()` command to the browser. Otherwise calls `delete` on the underlying viewer. + '''Delete the Widget's data and clears its internal state. If running in + client mode, sends the `delete()` command to the browser. Otherwise + calls `delete` on the underlying viewer. Args: - delete_table (bool) : whether the underlying `Table` will be deleted. Defaults to True. + delete_table (`bool`): whether the underlying `Table` will be + deleted. Defaults to True. ''' if self.client is False: super(PerspectiveWidget, self).delete(delete_table) @@ -315,24 +341,31 @@ def delete(self, delete_table=True): self.close() def post(self, msg, msg_id=None): - '''Post a serialized message to the `PerspectiveJupyterClient` in the front end. + '''Post a serialized message to the `PerspectiveJupyterClient` + in the front end. - The posted message should conform to the `PerspectiveJupyterMessage` interface as defined in `@finos/perspective-jupyterlab`. + The posted message should conform to the `PerspectiveJupyterMessage` + interface as defined in `@finos/perspective-jupyterlab`. Args: - msg (dict): a message from `PerspectiveManager` for the front-end viewer to process. - msg_id (int): an integer id that allows the client to process the message. + msg (dict): a message from `PerspectiveManager` for the front-end + viewer to process. + msg_id (int): an integer id that allows the client to process + the message. ''' message = _PerspectiveWidgetMessage(msg_id, "cmd", msg) self.send(message.to_dict()) @observe("value") def handle_message(self, widget, content, buffers): - '''Given a message from `PerspectiveJupyterClient.send()`, process the message and return the result to `self.post`. + '''Given a message from `PerspectiveJupyterClient.send()`, process the + message and return the result to `self.post`. Args: - widget : a reference to the `Widget` instance that received the message. - content (dict): the message from the front-end. Automatically de-serialized by ipywidgets. + widget: a reference to the `Widget` instance that received the + message. + content (dict): the message from the front-end. Automatically + de-serialized by ipywidgets. buffers : optional arraybuffers from the front-end, if any. ''' if content["type"] == "cmd": @@ -345,8 +378,9 @@ def handle_message(self, widget, content, buffers): msg = self._make_load_message() self.send(msg.to_dict()) - # In client mode, users can call `update()` before the widget is visible. - # This applies the updates after the viewer has loaded the initial dataset. + # In client mode, users can call `update()` before the widget + # is visible. This applies the updates after the viewer has + # loaded the initial dataset. if self.client is True and len(self._predisplay_update_cache) > 0: for data in self._predisplay_update_cache: self.update(data) @@ -356,8 +390,10 @@ def handle_message(self, widget, content, buffers): self.manager._process(parsed, post_callback) def _make_load_message(self): - '''Send a message to the front-end either containing the name of a Table in python, or the serialized - dataset with options while in client mode.''' + '''Send a message to the front-end either containing the name of a + Table in python, or the serialized dataset with options while in client + mode. + ''' msg_data = None if self.client and self._data is not None: # Send data to the client, transferring ownership to the browser @@ -368,7 +404,9 @@ def _make_load_message(self): if len(self._client_options.keys()) > 0: msg_data["options"] = self._client_options elif self.table_name is not None: - # Only pass back the table if it's been loaded. If the table isn't loaded, the `load()` method will handle synchronizing the front-end. + # Only pass back the table if it's been loaded. If the table isn't + # loaded, the `load()` method will handle synchronizing the + # front-end. msg_data = { "table_name": self.table_name } @@ -379,5 +417,7 @@ def _make_load_message(self): raise PerspectiveError("Widget could not find a dataset or a `Table` to load.") def _on_display(self, widget, **kwargs): - '''When the widget has been displayed, make sure `displayed` is set to True so updates stop being cached.''' + '''When the widget has been displayed, make sure `displayed` is set to + True so updates stop being cached. + ''' self._displayed = True diff --git a/python/perspective/setup.py b/python/perspective/setup.py index a0bb8c6d03..dfa2e89ddf 100644 --- a/python/perspective/setup.py +++ b/python/perspective/setup.py @@ -1,4 +1,4 @@ -# ***************************************************************************** +################################################################################ # # Copyright (c) 2019, the Perspective Authors. # diff --git a/scripts/lint_python.js b/scripts/lint_python.js index a4bf48adac..1d816a2575 100644 --- a/scripts/lint_python.js +++ b/scripts/lint_python.js @@ -12,9 +12,6 @@ const resolve = require("path").resolve; const execSync = require("child_process").execSync; const execute = cmd => execSync(cmd, {stdio: "inherit"}); -const VALID_TARGETS = ["node", "table"]; -const HAS_TARGET = args.indexOf("--target") != -1; - const IS_FIX = args.indexOf("--fix") != -1; function docker(target = "perspective", image = "emsdk") { @@ -30,8 +27,7 @@ function docker(target = "perspective", image = "emsdk") { try { let cmd; let lint_cmd = `python3 -m flake8 perspective && echo "lint passed!"`; - let fix_cmd = `autopep8 -v --in-place --aggressive --recursive --exclude build . &&\ - echo "autopep8 formatting complete!"`; + let fix_cmd = `autopep8 -v --in-place --aggressive --recursive --exclude build --exclude tests . && echo "autopep8 formatting complete!"`; if (process.env.PSP_DOCKER) { cmd = `cd python/perspective && ${IS_FIX ? fix_cmd : lint_cmd}`;