Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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;
226 changes: 226 additions & 0 deletions packages/perspective-jupyterlab/src/ts/renderer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
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 {
// Column-oriented or single records JSON
// don't handle for now, just need to implement
// a simple transform but we can't handle all
// cases
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