Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Package Graph #102

Merged
merged 14 commits into from
Nov 28, 2020
2 changes: 2 additions & 0 deletions packages/common/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
"@lumino/coreutils": "^1.3.0",
"@lumino/signaling": "^1.2.0",
"@lumino/widgets": "^1.6.0",
"@types/cytoscape": "^3.14.9",
"cytoscape": "^3.16.3",
"jupyterlab_toastify": "^4.1.2",
"react-virtualized": "^9.21.1",
"semver": "^6.0.0||^7.0.0",
Expand Down
193 changes: 193 additions & 0 deletions packages/common/src/components/PkgGraph.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import {
MainAreaWidget,
ReactWidget
} from '@jupyterlab/apputils';

import { Widget } from '@lumino/widgets';

import { HTMLSelect } from '@jupyterlab/ui-components';

import cytoscape from 'cytoscape';

import * as React from 'react';

import { CondaEnvironments } from '../services';
import { Conda } from '../tokens';

class Graph extends Widget {
constructor(model: CondaEnvironments) {
super();
this._model = model;
this._selected = '';
this._pkgManager = this._model.getPackageManager();
this._updatePackages();
}

update(): void {
const layout = (): cytoscape.LayoutOptions => {
const ns = new Set<string>();
this._nodes.forEach((n: cytoscape.NodeDefinition) => {
ns.add(n.data.name);
});
if (ns.size >= 50) {
return {
name: 'circle',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the previous implementation from: https://github.com/mamba-org/mamba-navigator/blob/84a3d5a700b30017112d68b98fdd7e63adc9f182/src/components/Network.vue#L70

It looks like it was using the vis.Network, which is likely to be the one from https://visjs.org:

image

Maybe it's a just a matter of using an equivalent as a cytoscape plugin to have a similar layout?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this one looks nicer.
I'll look for a similar layout and modify the backend to get the dependencies.

Maybe something like this one: https://cytoscape.org/cytoscape.js-cose-bilkent/

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it could also be good to have a lighter library than cytoscape.

Possible alternative:

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks both for the info.
I changed the library and added an argument to the PackageHandler API, so now we can request package dependencies.

Screen-Recording-2020-11-16-at-13 45 22

Would be great to access this graph when clicking on a package in jupyter_conda. Maybe having a new column for dependencies or a link in the package description to open the graph in a dialog.

Let me know what do you think.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Maybe having a new column for dependencies or a link in the package description to open the graph in a dialog.

Yes maybe that's the simplest and most visible way for now?

nodeDimensionsIncludeLabels: true,
padding: 5,
spacingFactor: 0.1,
avoidOverlap: true
};
}
return {
name: 'concentric',
nodeDimensionsIncludeLabels: true,
spacingFactor: 0.5,
avoidOverlap: true
};
};

this._cy = cytoscape({
layout: layout(),
container: this.node,
elements: {
nodes: this._nodes,
edges: this._edges
},
style: [
{
selector: 'node',
style: {
width: 'label',
shape: 'rectangle',
content: 'data(name)',
'padding-bottom': '10px',
'text-valign': 'center',
'background-color': '#81bc00',
'background-opacity': 0.4,
'border-width': 1,
'border-color': 'black'
}
},
{
selector: 'edge',
style: {
'curve-style': 'bezier',
'target-arrow-shape': 'triangle'
}
}
]
});
}

public setEnv(env: string): void {
this._selected = env;
this._updatePackages();
}

private async _updatePackages(): Promise<void> {
const available = await this._pkgManager.refresh(true, this._selected);

this._nodes = [];
this._edges = [];
let anterior = "";

available.forEach( (pkg: Conda.IPackage) => {
if (pkg.version_installed) {
console.debug(pkg);
this._nodes.push({ data: { id: pkg.name, name:pkg.name } });
if (anterior !== "") {
this._edges.push({
data: {
source: pkg.name,
target: anterior
},
style: {
'line-style': 'solid',
'line-color': '#F5A636'
},
classes: 'top-center'
});
}

anterior = pkg.name;
}
});

this.update();
}

protected onResize(): void {
if (this._cy) {
this._cy.resize();
this._cy.fit();
}
}

private _model: CondaEnvironments;
private _pkgManager: Conda.IPackageManager;
private _cy: cytoscape.Core;
private _selected: string;
private _nodes: Array<cytoscape.NodeDefinition> = [];
private _edges: Array<cytoscape.EdgeDefinition> = [];
}

export class GraphContainer extends MainAreaWidget<Graph> {
constructor(model: CondaEnvironments) {
super({ content: new Graph(model) });
this.toolbar.addItem('envs', new EnvSwitcher(this.content, model));
}
}

class EnvSwitcher extends ReactWidget {
constructor(graph: Graph, model: CondaEnvironments) {
super();
this.addClass('jp-LogConsole-toolbarLogLevel');
this._graph = graph;
this._model = model;
this._selected = '';
this._envs = [];

this._model.environments
.then( (envs: Conda.IEnvironment[]) => {
envs.forEach( (env: Conda.IEnvironment) => {
this._envs.push(env.name);
if (env.is_default) this._selected = env.name;
});

this.update();
});
}

private handleChange = (event: React.ChangeEvent<HTMLSelectElement>): void => {
this._selected = event.target.value;
this._graph.setEnv(this._selected);
this.update();
};

private handleKeyDown = (event: React.KeyboardEvent): void => {
if (event.keyCode === 13) this._graph.update();
};

render(): JSX.Element {
const envs = this._envs.map(value => { return {label: value, value}; });

return (
<>
<label>Env:</label>
<HTMLSelect
className="jp-LogConsole-toolbarLogLevelDropdown"
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
value={this._selected}
aria-label="Env"
options={envs}
/>
</>
);
}

private _graph: Graph;
private _model: CondaEnvironments;
private _selected: string;
private _envs: string[];
}
1 change: 1 addition & 0 deletions packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ export * from './constants';
export { condaIcon } from './icon';
export { CondaEnvironments } from './services';
export { Conda, IEnvironmentManager } from './tokens';
export { GraphContainer } from './components/PkgGraph';
23 changes: 22 additions & 1 deletion packages/labextension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,15 @@ import {
WidgetTracker
} from '@jupyterlab/apputils';
import { IMainMenu } from '@jupyterlab/mainmenu';
import { buildIcon } from '@jupyterlab/ui-components';
import { ISettingRegistry } from '@jupyterlab/settingregistry';
import {
CondaEnvironments,
CondaEnvWidget,
condaIcon,
CONDA_WIDGET_CLASS,
IEnvironmentManager
IEnvironmentManager,
GraphContainer
} from '@mamba-org/common';
import { INotification } from 'jupyterlab_toastify';
import { managerTour } from './tour';
Expand Down Expand Up @@ -106,9 +108,23 @@ async function activateCondaEnv(
}
});

const commandGraph = 'conda-packages-graph:open';
commands.addCommand(commandGraph, {
label: 'Conda Packages Graph',
caption: 'Open the conda packages graph',
execute: () => {
const widget = new GraphContainer(model);
widget.title.label = 'Packages Graph';
widget.title.icon = buildIcon;
shell.add(widget, 'main');
widget.content.update();
}
});

// Add command to command palette
if (palette) {
palette.addItem({ command, category: 'Settings' });
palette.addItem({ command: commandGraph, category: 'Settings' });
}

// Handle state restoration.
Expand All @@ -117,11 +133,16 @@ async function activateCondaEnv(
command,
name: () => pluginNamespace
});
restorer.restore(tracker, {
command: commandGraph,
name: () => pluginNamespace
});
}

// Add command to settings menu
if (menu) {
menu.settingsMenu.addGroup([{ command: command }], 999);
menu.settingsMenu.addGroup([{ command: commandGraph }], 999);
}

return model;
Expand Down
23 changes: 23 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2648,6 +2648,11 @@
dependencies:
"@babel/types" "^7.3.0"

"@types/cytoscape@^3.14.9":
version "3.14.9"
resolved "https://registry.yarnpkg.com/@types/cytoscape/-/cytoscape-3.14.9.tgz#5e28a54ea53a37b6225465552f76af33dae37082"
integrity sha512-iFPBdTNQEgEEileVXze3XzLrIzyQ3y51RHJEArMi+7PTTDrc1qSmXPqYYYorlw56qIWsDaiQ5SC6ln5Xum+tCQ==

"@types/dom4@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@types/dom4/-/dom4-2.0.1.tgz#506d5781b9bcab81bd9a878b198aec7dee2a6033"
Expand Down Expand Up @@ -4558,6 +4563,14 @@ cyclist@^1.0.1:
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-1.0.1.tgz#596e9698fd0c80e12038c2b82d6eb1b35b6224d9"
integrity sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=

cytoscape@^3.16.3:
version "3.16.3"
resolved "https://registry.yarnpkg.com/cytoscape/-/cytoscape-3.16.3.tgz#8880911da5d6192f6acda1095d18aa7662e89d2f"
integrity sha512-OGu9r5jsU2IgmqDy0NpW1M7ze2muvnJ3yNIB1mlzDLA7fcGI0aeE2hmLIoowi8E3+6HJF8Scx2iXGp11tbXHEg==
dependencies:
heap "^0.2.6"
lodash.debounce "^4.0.8"

dargs@^4.0.1:
version "4.1.0"
resolved "https://registry.yarnpkg.com/dargs/-/dargs-4.1.0.tgz#03a9dbb4b5c2f139bf14ae53f0b8a2a6a86f4e17"
Expand Down Expand Up @@ -6346,6 +6359,11 @@ [email protected]:
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==

heap@^0.2.6:
version "0.2.6"
resolved "https://registry.yarnpkg.com/heap/-/heap-0.2.6.tgz#087e1f10b046932fc8594dd9e6d378afc9d1e5ac"
integrity sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=

hmac-drbg@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1"
Expand Down Expand Up @@ -7950,6 +7968,11 @@ lodash.clonedeep@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=

lodash.debounce@^4.0.8:
version "4.0.8"
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=

lodash.escape@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/lodash.escape/-/lodash.escape-4.0.1.tgz#c9044690c21e04294beaa517712fded1fa88de98"
Expand Down