Skip to content

Commit ae3f539

Browse files
terapyonterapyon
authored andcommitted
added docs and fix download function
1 parent c898bc7 commit ae3f539

File tree

7 files changed

+122
-94
lines changed

7 files changed

+122
-94
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ net_vis/nbextension/index.*
155155
# Packed lab extensions
156156
net_vis/labextension
157157

158+
# Auto-generated bundle content for TypeScript
159+
src/standaloneBundleContent.ts
160+
158161
VSCode
159162

160163
*.code-workspace

CHANGES.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@
1111
- Works offline without internet connection or JupyterLab
1212
- Preserves all interactive features (zoom, pan, node selection, drag)
1313

14+
- **One-Click Download Button**: Download HTML directly from JupyterLab
15+
- Download button appears in top-right corner of visualization
16+
- Click to instantly save as `netvis_export_YYYY-MM-DD.html`
17+
- Works independently of kernel state (client-side generation)
18+
- No code required for quick exports
19+
1420
- **Export Customization**:
1521
- Custom title and description for HTML documents
1622
- Configurable container width (CSS values) and height (pixels)
@@ -55,7 +61,7 @@ plotter.export_html("graph.html", download=True)
5561

5662
- **HTMLExporter**: Template-based HTML generation using string.Template
5763
- **Standalone Bundle**: D3.js + rendering code bundled via webpack (~280KB)
58-
- **Test Coverage**: 26 new tests covering all export functionality
64+
- **Test Coverage**: 50 new tests (26 Python + 24 TypeScript) covering all export functionality
5965
- **Error Handling**: Proper exception propagation for file system errors
6066

6167
### Compatibility

docs/source/index.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ To get started with net_vis, install with pip::
4141
# Get HTML as string for embedding
4242
html = plotter.export_html()
4343

44+
**One-Click Download Button**: When viewing a graph in JupyterLab, click the download button (top-right corner) to instantly save the visualization as an HTML file.
45+
4446
**Note**: NetVis uses a MIME renderer that works automatically in JupyterLab 3.x and 4.x. Manual extension enabling is not required.
4547

4648

docs/source/introduction.rst

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ NetVis is a package for interactive visualization of Python NetworkX graphs with
88
Key Features
99
------------
1010

11+
- **Standalone HTML Export (v0.6.0)**: Export visualizations as self-contained HTML files that work offline
12+
- **One-Click Download Button (v0.6.0)**: Download HTML directly from JupyterLab visualization with a single click
1113
- **NetworkX Plotter API (v0.5.0)**: Direct visualization of NetworkX graphs without JSON conversion
1214
- **Interactive D3.js Visualization**: Force-directed graph layout with interactive node dragging, zooming, and panning
1315
- **Multiple Graph Types**: Support for Graph, DiGraph, MultiGraph, and MultiDiGraph
@@ -107,6 +109,50 @@ Version 0.5.0 introduces the **NetworkX Plotter API**, a high-level interface fo
107109
See the :doc:`examples/index` for complete usage examples.
108110

109111

112+
What's New in 0.6.0
113+
-------------------
114+
115+
Version 0.6.0 introduces **Standalone HTML Export**, enabling you to share visualizations without JupyterLab:
116+
117+
**HTML Export API**
118+
Export visualizations as self-contained HTML files::
119+
120+
# Export to file
121+
plotter.export_html("my_graph.html")
122+
123+
# Export with customization
124+
plotter.export_html(
125+
"report.html",
126+
title="Network Analysis Report",
127+
description="Generated analysis results",
128+
width="800px",
129+
height=700
130+
)
131+
132+
# Get HTML as string for embedding
133+
html = plotter.export_html()
134+
135+
**One-Click Download Button**
136+
When viewing a graph in JupyterLab, a download button appears in the top-right corner:
137+
138+
- Click the button to instantly download the visualization as HTML
139+
- Files are automatically named ``netvis_export_YYYY-MM-DD.html``
140+
- Works even when the kernel is stopped (client-side generation)
141+
- No code required for quick exports
142+
143+
**Exported HTML Features**
144+
- Works offline (no internet connection required)
145+
- All JavaScript and CSS embedded inline
146+
- Interactive features preserved (zoom, pan, node dragging)
147+
- Opens in any modern browser (Chrome, Firefox, Safari, Edge)
148+
149+
**Remote Environment Support**
150+
For JupyterHub, Google Colab, or Binder environments::
151+
152+
# Trigger browser download to local PC
153+
plotter.export_html("graph.html", download=True)
154+
155+
110156
Architecture (v0.4.0)
111157
---------------------
112158

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@
2929
"url": "https://github.com/cmscom/netvis"
3030
},
3131
"scripts": {
32-
"build": "yarn run build:lib && yarn run build:labextension:dev",
33-
"build:prod": "yarn run build:lib && yarn run build:labextension",
34-
"build:standalone": "webpack --config webpack.standalone.js",
32+
"build": "yarn run build:standalone && yarn run build:lib && yarn run build:labextension:dev",
33+
"build:prod": "yarn run build:standalone && yarn run build:lib && yarn run build:labextension",
34+
"build:standalone": "webpack --config webpack.standalone.js && node scripts/generate-bundle-module.js",
3535
"build:labextension": "jupyter labextension build .",
3636
"build:labextension:dev": "jupyter labextension build --development True .",
3737
"build:lib": "tsc",

scripts/generate-bundle-module.js

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Generate TypeScript module containing the standalone bundle content.
4+
*
5+
* This script reads the built netvis-standalone.min.js and generates
6+
* a TypeScript file that exports the bundle content as a string constant.
7+
*
8+
* Usage: node scripts/generate-bundle-module.js
9+
*
10+
* Output: src/standaloneBundleContent.ts
11+
*/
12+
13+
const fs = require('fs');
14+
const path = require('path');
15+
16+
const bundlePath = path.join(
17+
__dirname,
18+
'..',
19+
'net_vis',
20+
'resources',
21+
'netvis-standalone.min.js'
22+
);
23+
const outputPath = path.join(__dirname, '..', 'src', 'standaloneBundleContent.ts');
24+
25+
// Read the bundle
26+
if (!fs.existsSync(bundlePath)) {
27+
console.error(`Error: Bundle not found at ${bundlePath}`);
28+
console.error('Run "yarn run build:standalone" first.');
29+
process.exit(1);
30+
}
31+
32+
const bundleContent = fs.readFileSync(bundlePath, 'utf8');
33+
34+
// Generate TypeScript module
35+
const tsContent = `/**
36+
* Auto-generated file containing the standalone bundle content.
37+
* DO NOT EDIT MANUALLY - regenerate with: node scripts/generate-bundle-module.js
38+
*
39+
* This module exports the minified D3.js + NetVis rendering code
40+
* for embedding in standalone HTML exports.
41+
*/
42+
43+
// eslint-disable-next-line max-len
44+
export const STANDALONE_BUNDLE: string = ${JSON.stringify(bundleContent)};
45+
`;
46+
47+
// Write the TypeScript file
48+
fs.writeFileSync(outputPath, tsContent, 'utf8');
49+
50+
console.log(`Generated: ${outputPath}`);
51+
console.log(`Bundle size: ${(bundleContent.length / 1024).toFixed(1)} KB`);

src/htmlExport.ts

Lines changed: 10 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
* for exporting NetVis graphs as standalone HTML files.
66
*/
77

8+
import { STANDALONE_BUNDLE } from './standaloneBundleContent';
9+
810
/**
911
* Configuration for HTML export.
1012
*/
@@ -60,8 +62,8 @@ export function generateStandaloneHtml(config: ExportConfig): string {
6062
// Generate inline CSS
6163
const css = generateCss();
6264

63-
// Generate inline JavaScript (D3.js rendering code)
64-
const js = generateJs();
65+
// Get the pre-built JavaScript bundle (D3.js + rendering code)
66+
const js = getJsBundle();
6567

6668
return `<!DOCTYPE html>
6769
<html lang="en">
@@ -215,93 +217,11 @@ function generateCss(): string {
215217
}
216218

217219
/**
218-
* Generate JavaScript code for standalone HTML.
219-
* This is a minimal D3.js-based graph renderer.
220+
* Get JavaScript bundle for standalone HTML.
221+
*
222+
* Returns the pre-built D3.js + NetVis rendering code bundle.
223+
* This is the same bundle used by Python's HTMLExporter.
220224
*/
221-
function generateJs(): string {
222-
// For the standalone export, we need to include the D3.js bundle
223-
// In the actual implementation, this would be loaded from the build
224-
return `
225-
// NetVis standalone renderer
226-
var netvis = (function() {
227-
// Minimal graph rendering (placeholder for full D3.js bundle)
228-
function renderGraph(container, data) {
229-
if (!container) return;
230-
231-
const width = container.clientWidth || 800;
232-
const height = container.clientHeight || 600;
233-
234-
// Create SVG
235-
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
236-
svg.setAttribute('width', width);
237-
svg.setAttribute('height', height);
238-
svg.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
239-
container.appendChild(svg);
240-
241-
// Simple force-directed layout simulation
242-
const nodes = data.nodes || [];
243-
const links = data.links || [];
244-
245-
// Initialize node positions
246-
nodes.forEach(function(node, i) {
247-
node.x = node.x || width / 2 + (Math.random() - 0.5) * 200;
248-
node.y = node.y || height / 2 + (Math.random() - 0.5) * 200;
249-
});
250-
251-
// Draw links
252-
const linkGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
253-
linkGroup.setAttribute('class', 'netvis-links');
254-
svg.appendChild(linkGroup);
255-
256-
links.forEach(function(link) {
257-
const source = nodes.find(function(n) { return n.id === link.source || n.id === link.source.id; });
258-
const target = nodes.find(function(n) { return n.id === link.target || n.id === link.target.id; });
259-
if (source && target) {
260-
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
261-
line.setAttribute('class', 'netvis-link');
262-
line.setAttribute('x1', source.x);
263-
line.setAttribute('y1', source.y);
264-
line.setAttribute('x2', target.x);
265-
line.setAttribute('y2', target.y);
266-
line.setAttribute('stroke', '#999');
267-
line.setAttribute('stroke-opacity', '0.6');
268-
linkGroup.appendChild(line);
269-
}
270-
});
271-
272-
// Draw nodes
273-
const nodeGroup = document.createElementNS('http://www.w3.org/2000/svg', 'g');
274-
nodeGroup.setAttribute('class', 'netvis-nodes');
275-
svg.appendChild(nodeGroup);
276-
277-
nodes.forEach(function(node) {
278-
const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
279-
g.setAttribute('class', 'netvis-node');
280-
g.setAttribute('transform', 'translate(' + node.x + ',' + node.y + ')');
281-
282-
const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
283-
circle.setAttribute('r', 8);
284-
circle.setAttribute('fill', node.color || '#69b3a2');
285-
circle.setAttribute('stroke', '#fff');
286-
circle.setAttribute('stroke-width', '1.5');
287-
g.appendChild(circle);
288-
289-
if (node.name || node.label) {
290-
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
291-
text.setAttribute('class', 'netvis-node-label');
292-
text.setAttribute('dx', '12');
293-
text.setAttribute('dy', '4');
294-
text.textContent = node.name || node.label;
295-
g.appendChild(text);
296-
}
297-
298-
nodeGroup.appendChild(g);
299-
});
300-
}
301-
302-
return {
303-
renderGraph: renderGraph
304-
};
305-
})();
306-
`;
225+
function getJsBundle(): string {
226+
return STANDALONE_BUNDLE;
307227
}

0 commit comments

Comments
 (0)