Skip to content

Commit f5cec10

Browse files
authored
feat(charts): Add visualization blocks support (#66)
1 parent cddc017 commit f5cec10

File tree

12 files changed

+2308
-130
lines changed

12 files changed

+2308
-130
lines changed

build/esbuild/build.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,11 @@ async function buildAll() {
331331
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'dataframeRenderer', 'dataframeRenderer.js'),
332332
{ target: 'web', watch: isWatchMode }
333333
),
334+
build(
335+
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'vega-renderer', 'index.ts'),
336+
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'vegaRenderer', 'vegaRenderer.js'),
337+
{ target: 'web', watch: isWatchMode }
338+
),
334339
build(
335340
path.join(extensionFolder, 'src', 'webviews', 'webview-side', 'variable-view', 'index.tsx'),
336341
path.join(extensionFolder, 'dist', 'webviews', 'webview-side', 'viewers', 'variableView.js'),

package-lock.json

Lines changed: 1636 additions & 94 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1847,6 +1847,15 @@
18471847
"application/vnd.deepnote.dataframe.v3+json"
18481848
],
18491849
"requiresMessaging": "optional"
1850+
},
1851+
{
1852+
"id": "deepnote-vega-renderer",
1853+
"displayName": "Deepnote Vega Chart Renderer",
1854+
"entrypoint": "./dist/webviews/webview-side/vegaRenderer/vegaRenderer.js",
1855+
"mimeTypes": [
1856+
"application/vnd.vega.v5+json"
1857+
],
1858+
"requiresMessaging": "optional"
18501859
}
18511860
],
18521861
"viewsContainers": {
@@ -2162,12 +2171,14 @@
21622171
"bootstrap-less": "^3.3.8",
21632172
"clsx": "^2.1.1",
21642173
"cross-fetch": "^3.1.5",
2174+
"d3-format": "^3.1.0",
21652175
"encoding": "^0.1.13",
21662176
"fast-deep-equal": "^2.0.1",
21672177
"format-util": "^1.0.5",
21682178
"fs-extra": "^4.0.3",
21692179
"glob": "^7.1.2",
21702180
"iconv-lite": "^0.6.3",
2181+
"immer": "^10.1.3",
21712182
"inversify": "^6.0.1",
21722183
"isomorphic-ws": "^4.0.1",
21732184
"jquery": "^3.6.0",
@@ -2187,6 +2198,7 @@
21872198
"react-redux": "^7.1.1",
21882199
"react-svg-pan-zoom": "3.9.0",
21892200
"react-svgmt": "1.1.11",
2201+
"react-vega": "^7.7.1",
21902202
"react-virtualized": "^9.21.1",
21912203
"redux": "^4.0.4",
21922204
"redux-logger": "^3.0.6",
@@ -2203,6 +2215,9 @@
22032215
"tcp-port-used": "^1.0.1",
22042216
"tmp": "^0.2.4",
22052217
"url-parse": "^1.5.10",
2218+
"vega": "^5.33.0",
2219+
"vega-embed": "^6.25.0",
2220+
"vega-lite": "^5.21.0",
22062221
"vscode-debugprotocol": "^1.41.0",
22072222
"vscode-languageclient": "8.0.2-next.5",
22082223
"vscode-tas-client": "^0.1.84",
@@ -2223,6 +2238,7 @@
22232238
"@types/chai-arrays": "^2.0.1",
22242239
"@types/chai-as-promised": "^7.1.6",
22252240
"@types/cors": "^2.8.6",
2241+
"@types/d3-format": "^3.0.4",
22262242
"@types/debug": "^4.1.5",
22272243
"@types/dedent": "^0.7.0",
22282244
"@types/del": "^4.0.0",

src/kernels/execution/cellExecution.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ import { KernelError } from '../errors/kernelError';
3232
import { getCachedSysPrefix } from '../../platform/interpreter/helpers';
3333
import { getCellMetadata } from '../../platform/common/utils';
3434
import { NotebookCellExecutionState, notebookCellExecutions } from '../../platform/notebooks/cellExecutionStateService';
35-
import { createBlockFromPocket } from '../../notebooks/deepnote/pocket';
3635
import { createPythonCode } from '@deepnote/blocks';
36+
import { DeepnoteDataConverter } from '../../notebooks/deepnote/deepnoteDataConverter';
3737

3838
/**
3939
* Factory for CellExecution objects.
@@ -416,10 +416,11 @@ export class CellExecution implements ICellExecution, IDisposable {
416416
metadata: this.cell.metadata,
417417
outputs: [...(this.cell.outputs || [])]
418418
};
419-
const deepnoteBlock = createBlockFromPocket(cellData, this.cell.index);
420419

421-
// Use createPythonCode to generate code with table state already included
422-
logger.info(`Cell ${this.cell.index}: Using createPythonCode to generate execution code with table state`);
420+
const dataConverter = new DeepnoteDataConverter();
421+
const deepnoteBlock = dataConverter.convertCellToBlock(cellData, this.cell.index);
422+
423+
logger.info(`Cell ${this.cell.index}: Using createPythonCode for ${deepnoteBlock.type} block`);
423424
code = createPythonCode(deepnoteBlock);
424425

425426
// Generate metadata from our cell (some kernels expect this.)
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { NotebookCellData, NotebookCellKind } from 'vscode';
2+
3+
import type { BlockConverter } from './blockConverter';
4+
import type { DeepnoteBlock } from '../deepnoteTypes';
5+
6+
type DataframeFilter = {
7+
column: string;
8+
operator:
9+
| 'is-equal'
10+
| 'is-not-equal'
11+
| 'is-one-of'
12+
| 'is-not-one-of'
13+
| 'is-not-null'
14+
| 'is-null'
15+
| 'text-contains'
16+
| 'text-does-not-contain'
17+
| 'greater-than'
18+
| 'greater-than-or-equal'
19+
| 'less-than'
20+
| 'less-than-or-equal'
21+
| 'between'
22+
| 'outside-of'
23+
| 'is-relative-today'
24+
| 'is-after'
25+
| 'is-before'
26+
| 'is-on';
27+
comparativeValues: string[];
28+
};
29+
30+
interface FilterMetadata {
31+
/** @deprecated Use advancedFilters instead */
32+
filter?: unknown;
33+
advancedFilters?: DataframeFilter[];
34+
}
35+
36+
interface VisualizationCellMetadata {
37+
deepnote_variable_name?: string;
38+
deepnote_visualization_spec?: Record<string, unknown>;
39+
deepnote_chart_filter?: FilterMetadata;
40+
}
41+
42+
/**
43+
* Converter for Deepnote visualization blocks (chart blocks).
44+
* Displays blocks as editable JSON with variable name, spec, and filters.
45+
* The JSON is converted to Python code at execution time.
46+
*/
47+
export class VisualizationBlockConverter implements BlockConverter {
48+
applyChangesToBlock(block: DeepnoteBlock, cell: NotebookCellData): void {
49+
block.content = '';
50+
51+
// Parse the JSON from the cell to update metadata
52+
try {
53+
const config = JSON.parse(cell.value || '{}');
54+
55+
block.metadata = {
56+
...block.metadata,
57+
deepnote_variable_name: config.variable || '',
58+
deepnote_visualization_spec: config.spec || {},
59+
deepnote_chart_filter: {
60+
advancedFilters: config.filters || []
61+
}
62+
};
63+
} catch (error) {
64+
// If JSON parsing fails, leave metadata unchanged
65+
console.warn('Failed to parse visualization JSON:', error);
66+
}
67+
}
68+
69+
canConvert(blockType: string): boolean {
70+
return blockType.toLowerCase() === 'visualization';
71+
}
72+
73+
convertToCell(block: DeepnoteBlock): NotebookCellData {
74+
const metadata = block.metadata as VisualizationCellMetadata | undefined;
75+
const variableName = metadata?.deepnote_variable_name || 'df';
76+
const spec = metadata?.deepnote_visualization_spec || {};
77+
const filters = metadata?.deepnote_chart_filter?.advancedFilters || [];
78+
79+
// Create a clean JSON representation that users can edit
80+
const config = {
81+
variable: variableName,
82+
spec: spec,
83+
filters: filters
84+
};
85+
86+
const jsonContent = JSON.stringify(config, null, 2);
87+
const cell = new NotebookCellData(NotebookCellKind.Code, jsonContent, 'JSON');
88+
89+
return cell;
90+
}
91+
92+
getSupportedTypes(): string[] {
93+
return ['visualization'];
94+
}
95+
}

0 commit comments

Comments
 (0)