Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions packages/perspective-jupyterlab/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,10 @@
},
"dependencies": {
"@finos/perspective-phosphor": "^0.4.0-rc.4",
"@jupyter-widgets/base": "^2.0.1",
"@phosphor/application": "^1.5.0",
"@phosphor/widgets": "^1.6.0"
"@jupyter-widgets/base": "^2.0.2",
"@jupyterlab/application": "^1.2.1",
"@phosphor/application": "^1.7.3",
"@phosphor/widgets": "^1.9.3"
},
"devDependencies": {
"@finos/perspective-webpack-plugin": "^0.4.0-rc.4",
Expand Down
8 changes: 5 additions & 3 deletions packages/perspective-jupyterlab/src/config/plugin.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ const webpack = require("webpack");

module.exports = {
mode: process.env.PSP_NO_MINIFY || process.env.PSP_DEBUG ? "development" : process.env.NODE_ENV || "production",
entry: "./src/ts/index.ts",
entry: {
index: "./src/ts/index.ts"
},
devtool: "cheap-eval-source-map",
resolveLoader: {
alias: {
Expand All @@ -28,7 +30,7 @@ module.exports = {
maxEntrypointSize: 512000,
maxAssetSize: 512000
},
externals: /\@jupyter|\@phosphor/,
externals: /\@jupyterlab|\@phosphor|\@jupyter-widgets/,
stats: {modules: false, hash: false, version: false, builtAt: false, entrypoints: false},
plugins: [new webpack.ContextReplacementPlugin(/moment[\/\\]locale$/, /(en|es|fr)$/), new PerspectivePlugin()],
module: {
Expand All @@ -54,7 +56,7 @@ module.exports = {
]
},
output: {
filename: "index.js",
filename: "[name].js",
libraryTarget: "umd",
path: path.resolve(__dirname, "../../dist")
}
Expand Down
6 changes: 3 additions & 3 deletions packages/perspective-jupyterlab/src/less/index.less
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ div.PSPContainer,
div.PSPContainer-dark {
overflow: auto;
resize: both;
padding-right: 20px;
padding-bottom: 20px;
padding-right: 5px;
padding-bottom: 5px;
height: 500px;
flex: 1;
}
Expand All @@ -21,5 +21,5 @@ div.PSPContainer-dark {
div.PSPContainer perspective-viewer,
div.PSPContainer-dark perspective-viewer {
display: block;
height: 95%;
height: 98%;
}
10 changes: 9 additions & 1 deletion packages/perspective-jupyterlab/src/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@

export * from "./client";
export * from "./model";
export * from "./plugin";
export * from "./version";
export * from "./view";
export * from "./widget";
Expand All @@ -20,3 +19,12 @@ import "!!style-loader!css-loader!less-loader!../less/index.less";
import "@finos/perspective-viewer-hypergrid";
import "@finos/perspective-viewer-highcharts";

import {JupyterFrontEndPlugin} from '@jupyterlab/application';
import {perspectiveRenderers} from "./renderer";
import {PerspectiveJupyterPlugin} from "./plugin";

/**
* Export the renderer as default.
*/
const plugins: JupyterFrontEndPlugin<any>[] = [PerspectiveJupyterPlugin, perspectiveRenderers];
export default plugins;
1 change: 0 additions & 1 deletion packages/perspective-jupyterlab/src/ts/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,3 @@ export const PerspectiveJupyterPlugin: IPlugin<Application<Widget>, void> = {
autoStart: true
};

export default PerspectiveJupyterPlugin;
249 changes: 249 additions & 0 deletions packages/perspective-jupyterlab/src/ts/renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
import {ActivityMonitor} from '@jupyterlab/coreutils';
import {ILayoutRestorer, JupyterFrontEnd, JupyterFrontEndPlugin} from '@jupyterlab/application';
import {IThemeManager, WidgetTracker, Dialog, showDialog} from '@jupyterlab/apputils';
import {ABCWidgetFactory, DocumentRegistry, IDocumentWidget, DocumentWidget} from '@jupyterlab/docregistry';
import {PerspectiveWidget} from "@finos/perspective-phosphor";

/**
* The name of the factories that creates widgets.
*/
const FACTORY_CSV = 'CSVPerspective';
const FACTORY_JSON = 'JSONPerspective';

const RENDER_TIMEOUT = 1000;

type IPerspectiveDocumentType = "csv" | "json";

// create here to reuse for exception handling
const baddialog = () => {
showDialog({
body: "Perspective could not render the data",
buttons: [Dialog.okButton({ label: "Dismiss" })],
focusNodeSelector: "input",
title: "Error"});
};


export class PerspectiveDocumentWidget extends DocumentWidget<PerspectiveWidget> {
constructor(options: DocumentWidget.IOptionsOptionalContent<PerspectiveWidget>,
type: IPerspectiveDocumentType = "csv") {
super({ content: new PerspectiveWidget("test"), context: options.context, reveal: options.reveal});

this._psp = this.content;
this._type = type;
this._context = options.context;

this._context.ready.then(() => {
this._update();
this._monitor = new ActivityMonitor({
signal: this.context.model.contentChanged,
timeout: RENDER_TIMEOUT
});
this._monitor.activityStopped.connect(this._update, this);
});
}

private _update() {
try {
if(this._type === "csv") {
// load csv directly
const data: string = this._context.model.toString();
this._psp._update(data);
} else if (this._type === "json") {
const data = this._context.model.toJSON() as any;

if (Array.isArray(data) && data.length > 0) {
// already is records form, load directly
this._psp._update(data);

} else {
throw "Not handled";
// TODO
// if (Object.keys(data).length > 0){
// // valid
// const keys = Object.keys(data);

// if(Array.isArray(data[keys[0]])) {
// // convert from columns to records
// const records = [];
// let i = 0;
// while(i < data[keys[0]].length){
// let obj = {} as {[key: string]: any};
// for(let k of keys){
// obj[k] = data[k][i];
// }
// records.push(obj);
// i++;
// }
// this._psp._update(records);

// } else {
// // single record, wrap as array
// this._psp._update([data]);
// }
// } else {
// // invalid
// throw "Not handled";
// }
}
} else {
// don't handle other mimetypes for now
throw "Not handled";
}
} catch {
baddialog();
}

// pickup theme from env
this._psp.dark = (document.body.getAttribute("data-jp-theme-light") === "false");
}

dispose(): void {
if (this._monitor) {
this._monitor.dispose();
}
super.dispose();
}

public get psp() : PerspectiveWidget {
return this._psp;
}

private _type: IPerspectiveDocumentType;
private _context: DocumentRegistry.Context;
private _psp: PerspectiveWidget;
private _monitor: ActivityMonitor<DocumentRegistry.IModel, void> | null = null;
}


/**
* A widget factory for CSV widgets.
*/
export class PerspectiveCSVFactory extends ABCWidgetFactory<IDocumentWidget<PerspectiveWidget> > {
protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget<PerspectiveWidget> {
return new PerspectiveDocumentWidget({context}, "csv");
}
}

/**
* A widget factory for JSON widgets.
*/
export class PerspectiveJSONFactory extends ABCWidgetFactory<IDocumentWidget<PerspectiveWidget> > {
protected createNewWidget(context: DocumentRegistry.Context): IDocumentWidget<PerspectiveWidget> {
return new PerspectiveDocumentWidget({context}, "json");
}
}

/**
* The perspective extension for files
*/
export
const perspectiveRenderers: JupyterFrontEndPlugin<void> = {
activate: activate,
id: '@finos/perspective-jupyterlab:renderers',
requires: [],
optional: [
ILayoutRestorer,
IThemeManager
],
autoStart: true
};

/**
* Activate cssviewer extension for CSV files
*/
function activate(
app: JupyterFrontEnd,
restorer: ILayoutRestorer | null,
themeManager: IThemeManager | null
): void {

const factorycsv = new PerspectiveCSVFactory({
name: FACTORY_CSV,
fileTypes: ['csv'],
defaultFor: ['csv'],
readOnly: true
});

const factoryjson = new PerspectiveJSONFactory({
name: FACTORY_JSON,
fileTypes: ['json', 'jsonl'],
defaultFor: ['json', 'jsonl'],
readOnly: true
});


const trackercsv = new WidgetTracker<IDocumentWidget<PerspectiveWidget>>({
namespace: 'csvperspective'
});

const trackerjson = new WidgetTracker<IDocumentWidget<PerspectiveWidget>>({
namespace: 'jsonperspective'
});

if (restorer) {
// Handle state restoration.
void restorer.restore(trackercsv, {
command: 'docmanager:open',
args: widget => ({ path: widget.context.path, factory: FACTORY_CSV }),
name: widget => widget.context.path
});

void restorer.restore(trackerjson, {
command: 'docmanager:open',
args: widget => ({ path: widget.context.path, factory: FACTORY_JSON }),
name: widget => widget.context.path
});
}

app.docRegistry.addWidgetFactory(factorycsv);
app.docRegistry.addWidgetFactory(factoryjson);

let ftcsv = app.docRegistry.getFileType('csv');
let ftjson = app.docRegistry.getFileType('json');

factorycsv.widgetCreated.connect((sender, widget) => {
// Track the widget.
void trackercsv.add(widget);
// Notify the widget tracker if restore data needs to update.
widget.context.pathChanged.connect(() => {
void trackercsv.save(widget);
});

if (ftcsv) {
widget.title.iconClass = ftcsv.iconClass!;
widget.title.iconLabel = ftcsv.iconLabel!;
}
});

factoryjson.widgetCreated.connect((sender, widget) => {
// Track the widget.
void trackerjson.add(widget);
// Notify the widget tracker if restore data needs to update.
widget.context.pathChanged.connect(() => {
void trackerjson.save(widget);
});

if (ftjson) {
widget.title.iconClass = ftjson.iconClass!;
widget.title.iconLabel = ftjson.iconLabel!;
}
});


// Keep the themes up-to-date.
const updateThemes = () => {
const isLight = themeManager && themeManager.theme ? themeManager.isLight(themeManager.theme) : true;
trackercsv.forEach((pspDocWidget: PerspectiveDocumentWidget) => {
pspDocWidget.psp.dark = !isLight;
});
trackerjson.forEach((pspDocWidget: PerspectiveDocumentWidget) => {
pspDocWidget.psp.dark = !isLight;
});
};

if (themeManager) {
themeManager.themeChanged.connect(updateThemes);
}
}

7 changes: 3 additions & 4 deletions packages/perspective-jupyterlab/src/ts/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {PerspectiveJupyterClient, PerspectiveJupyterMessage} from "./client";
* the DOM.
*/
export class PerspectiveView extends DOMWidgetView {
pWidget: PerspectiveWidget;
pWidget: PerspectiveWidget; // this should be pWidget, but temporarily calling it pWidget for widgets incompatibilities
perspective_client: PerspectiveJupyterClient;

_createElement(tagName: string) {
Expand All @@ -32,14 +32,13 @@ export class PerspectiveView extends DOMWidgetView {
plugin_config: this.model.get("plugin_config"),
computed_columns: [],
client: this.model.get("client"),
dark: this.model.get("dark"),
dark: this.model.get("dark") === null ? // only set if its a bool, otherwise inherit
document.body.getAttribute("data-jp-theme-light") === "false": this.model.get("dark"),
editable: this.model.get("editable"),
bindto: this.el,
view: this
});

this.perspective_client = new PerspectiveJupyterClient(this);

return this.pWidget.node;
}

Expand Down
6 changes: 3 additions & 3 deletions packages/perspective-phosphor/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
"dependencies": {
"@finos/perspective": "^0.4.0-rc.4",
"@finos/perspective-viewer": "^0.4.0-rc.4",
"@phosphor/application": "^1.5.0",
"@phosphor/default-theme": "0.1.0",
"@phosphor/widgets": "^1.6.0",
"@phosphor/application": "^1.7.3",
"@phosphor/default-theme": "0.1.8",
"@phosphor/widgets": "^1.9.3",
"lodash.uniqby": "^4.7.0"
},
"devDependencies": {
Expand Down
2 changes: 1 addition & 1 deletion python/perspective/perspective/viewer/viewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def __init__(self,
sort=None,
filters=None,
plugin_config=None,
dark=False,
dark=None,
editable=False):
'''Initialize an instance of `PerspectiveViewer` with the given viewer
configuration. Do not pass a `Table` or data into the constructor -
Expand Down
2 changes: 1 addition & 1 deletion python/perspective/perspective/viewer/viewer_traitlets.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class PerspectiveTraitlets(HasTraits):
sort = List(default_value=[]).tag(sync=True)
filters = List(default_value=[]).tag(sync=True)
plugin_config = Dict(default_value={}).tag(sync=True)
dark = Bool(False).tag(sync=True)
dark = Bool(None, allow_none=True).tag(sync=True)
editable = Bool(False).tag(sync=True)
client = Bool(False).tag(sync=True)

Expand Down
Loading