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
Binary file added docs/assets/chartifact-examples.zip
Binary file not shown.
9 changes: 8 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
"dependencies": {
"@mdit/plugin-attrs": "^0.22.0",
"@mdit/plugin-container": "^0.22.0",
"fflate": "^0.8.2",
"markdown-it": "^14.1.0",
"tabulator-tables": "^6.3.1",
"vega": "^6.1.2",
Expand Down
9 changes: 4 additions & 5 deletions packages/vscode/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,22 @@

**Declarative, interactive data documents**

Chartifact is a low-code document format for creating interactive, data-driven pages such as reports, dashboards, and presentations. It travels like a document and works like an app. Easily editable and remixable, it’s a file type for an AI-native world.
Chartifact is a low-code document format for creating interactive, data-driven pages such as reports, dashboards, and presentations. It travels like a document and works like an app. Designed for use with your LLM to produce a shareable artifact of your analytic conversations.

## Features


This extension works with two file formats: `*.idoc.md` and `*.idoc.json`.

### Unzip samples
- **Explorer View**: Right-click on a folder and select "NewCreate Chartifact Examples Folder". This will create a folder of examples that you and your agent can use for refernce when creating your own document.

### Create Documents
- **Explorer View**: Right-click on a folder and select "New Chartifact Interactive Document (JSON)" or "New Chartifact Interactive Document (Markdown)".

### Preview Documents
- **Explorer View**: Right-click on a `*.idoc.md` or `*.idoc.json` file and select "Preview Chartifact Interactive Document".
- **Editor Title Bar**: Open a document and select "Preview Chartifact Interactive Document (Split View)" for side-by-side editing and previewing.

### Edit Documents
- **Explorer View**: Right-click on a `*.idoc.md` or `*.idoc.json` file and select "Edit Chartifact Interactive Document".

### Convert Documents
- **Explorer View**: Convert between formats:
- "Convert to HTML" for sharing standalone files.
Expand Down
34 changes: 14 additions & 20 deletions packages/vscode/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
{
"name": "chartifact",
"displayName": "Chartifact",
"private": true,
"description": "",
"version": "0.0.3",
"description": "Declarative, interactive data documents",
"version": "1.0.0",
"publisher": "msrvida",
"repository": {
"type": "git",
"url": "git+https://github.com/microsoft/chartifact.git"
},
"author": "Dan Marshall",
"license": "MIT",
"bugs": {
"url": "https://github.com/microsoft/chartifact/issues"
Expand All @@ -19,7 +17,12 @@
"vscode": "^1.101.0"
},
"categories": [
"Other"
"Other",
"Visualization"
],
"keywords": [
"data visualization",
"dataviz"
],
"activationEvents": [],
"browser": "./dist/web/extension.js",
Expand Down Expand Up @@ -51,8 +54,8 @@
"category": "Chartifact Interactive Documents"
},
{
"command": "chartifact.editIdoc",
"title": "Edit Chartifact Interactive Document",
"command": "chartifact.createExamplesFolder",
"title": "Create Chartifact Examples Folder",
"category": "Chartifact Interactive Documents"
},
{
Expand Down Expand Up @@ -100,8 +103,8 @@
"group": "chartifact"
},
{
"command": "chartifact.editIdoc",
"when": "resourceExtname == .json && resourceFilename =~ /\\.idoc( copy( \\d+)?)?\\.json$/",
"command": "chartifact.createExamplesFolder",
"when": "explorerResourceIsFolder",
"group": "chartifact"
}
],
Expand Down Expand Up @@ -142,16 +145,6 @@
"command": "chartifact.convertToMarkdown",
"when": "resourceExtname == .json && resourceFilename =~ /\\.idoc( copy( \\d+)?)?\\.json$/",
"group": "chartifact"
},
{
"command": "chartifact.editIdoc",
"when": "resourceExtname == .md && resourceFilename =~ /\\.idoc( copy( \\d+)?)?\\.md$/",
"group": "chartifact"
},
{
"command": "chartifact.editIdoc",
"when": "resourceExtname == .json && resourceFilename =~ /\\.idoc( copy( \\d+)?)?\\.json$/",
"group": "chartifact"
}
]
}
Expand All @@ -162,6 +155,7 @@
"test": "vscode-test-web --browserType=chromium --extensionDevelopmentPath=. --extensionTestsPath=dist/web/test/suite/extensionTests.js",
"pretest": "npm run compile-web",
"vscode:prepublish": "npm run package-web",
"prebuild": "npm run resources",
"build": "npm run resources && npm run compile-web",
"build:08": "npm run build",
"compile-web": "npm run check-types && npm run lint && npm run resources && node esbuild.js",
Expand All @@ -179,4 +173,4 @@
"devDependencies": {
"@vscode/vsce": "^3.6.0"
}
}
}
1 change: 1 addition & 0 deletions packages/vscode/scripts/resources.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const resources = [
//sample docs
'../../docs/assets/examples/json/grocery-list.idoc.json',
'../../docs/assets/examples/markdown/seattle-weather/1.idoc.md',
'../../docs/assets/chartifact-examples.zip',
];

const errors = [];
Expand Down
108 changes: 108 additions & 0 deletions packages/vscode/src/web/command-create-examples.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
*/
import * as vscode from 'vscode';
import { unzipSync } from 'fflate';
import { getBinaryResourceContent } from './resources';

/**
* Creates a chartifact-examples folder and extracts the examples ZIP file into it
* @param uri The URI where to create the folder (usually a workspace folder or selected folder)
*/
export async function createExamplesFolder(uri?: vscode.Uri): Promise<void> {
// Determine the target directory
let targetDir: vscode.Uri;

if (uri && uri.scheme === 'file') {
// Check if the URI is a directory
try {
const stat = await vscode.workspace.fs.stat(uri);
if (stat.type === vscode.FileType.Directory) {
targetDir = uri;
} else {
// It's a file, use its parent directory
const pathParts = uri.path.split('/');
pathParts.pop(); // Remove the filename
targetDir = uri.with({ path: pathParts.join('/') });
}
} catch {
// If stat fails, assume it's a directory
targetDir = uri;
}
} else {
// No URI provided, use the first workspace folder
const workspaceFolders = vscode.workspace.workspaceFolders;
if (!workspaceFolders || workspaceFolders.length === 0) {
throw new Error('No workspace folder available. Please open a folder first.');
}
targetDir = workspaceFolders[0].uri;
}

// Find an available folder name
const baseFolderUri = vscode.Uri.joinPath(targetDir, 'chartifact-examples');
let availableFolderUri = baseFolderUri;

// Check if folder exists and find an available name
let counter = 1;
while (true) {
try {
await vscode.workspace.fs.stat(availableFolderUri);
// Folder exists, try next number
availableFolderUri = vscode.Uri.joinPath(targetDir, `chartifact-examples-${counter}`);
counter++;
} catch {
// Folder doesn't exist, we can use this name
break;
}
}

try {
// Get the examples ZIP file from the cached resources
const zipData = getBinaryResourceContent('chartifact-examples.zip');

// Create the examples folder
await vscode.workspace.fs.createDirectory(availableFolderUri);

// Extract the ZIP file
const unzipped = unzipSync(zipData);

for (const [filePath, fileData] of Object.entries(unzipped)) {
const targetPath = vscode.Uri.joinPath(availableFolderUri, filePath);

// Create parent directories if they don't exist
const pathParts = filePath.split('/');
if (pathParts.length > 1) {
const parentParts = pathParts.slice(0, -1);
const parentDir = vscode.Uri.joinPath(availableFolderUri, ...parentParts);
try {
await vscode.workspace.fs.createDirectory(parentDir);
} catch {
// Directory might already exist, ignore error
}
}

// Write the file
await vscode.workspace.fs.writeFile(targetPath, fileData);
}

// Show success message and offer to open the folder
const folderName = availableFolderUri.path.split('/').pop();
const action = await vscode.window.showInformationMessage(
`Successfully created and extracted chartifact-examples to "${folderName}"`,
'Open Folder',
'Show in Explorer'
);

if (action === 'Open Folder') {
// Open the examples folder in the explorer
vscode.commands.executeCommand('revealInExplorer', availableFolderUri);
} else if (action === 'Show in Explorer') {
// Reveal in VS Code explorer
vscode.commands.executeCommand('revealInExplorer', availableFolderUri);
}

} catch (error) {
throw new Error(`Failed to create examples folder: ${error instanceof Error ? error.message : error}`);
}
}
1 change: 1 addition & 0 deletions packages/vscode/src/web/command-preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ function getWebviewContent(webView: vscode.Webview, context: vscode.ExtensionCon

// Build the resource links block
const resourceLinks = [
script(resourceUrl('vega.min.js')),
script(resourceUrl('chartifact.host.umd.js')),
script(resourceUrl('preview.js')),
].join('\n ');
Expand Down
12 changes: 12 additions & 0 deletions packages/vscode/src/web/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ export async function activate(context: vscode.ExtensionContext) {

context.subscriptions.push(convertToMarkdownDisposable);

// Register the create examples folder command
const createExamplesFolderDisposable = vscode.commands.registerCommand('chartifact.createExamplesFolder', async (uri?: vscode.Uri) => {
try {
const { createExamplesFolder } = await import('./command-create-examples.js');
await createExamplesFolder(uri);
} catch (error) {
vscode.window.showErrorMessage(`Failed to create examples folder: ${error}`);
}
});

context.subscriptions.push(createExamplesFolderDisposable);

// Ensure preview manager is disposed when extension deactivates
context.subscriptions.push({
dispose: () => previewManager.dispose()
Expand Down
43 changes: 41 additions & 2 deletions packages/vscode/src/web/resources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
*/
import * as vscode from 'vscode';

// Cache for resource contents - can be string content or Error object
const cachedResources: Record<string, string | Error> = {};
// Cache for resource contents - can be string content, Uint8Array (binary), or Error object
const cachedResources: Record<string, string | Uint8Array | Error> = {};

// Initialize resource contents (call this during extension activation)
export const initializeResources = async (context: vscode.ExtensionContext): Promise<void> => {
Expand Down Expand Up @@ -41,6 +41,10 @@ export const initializeResources = async (context: vscode.ExtensionContext): Pro
'html-json.js',
'html-markdown.js',
];

const binaryResourcesToLoad: string[] = [
'chartifact-examples.zip',
];

for (const filename of resourcesToLoad) {
try {
Expand All @@ -54,6 +58,18 @@ export const initializeResources = async (context: vscode.ExtensionContext): Pro
cachedResources[filename] = error instanceof Error ? error : new Error(`Failed to load ${filename}`);
}
}

for (const filename of binaryResourcesToLoad) {
try {
const resourceUri = vscode.Uri.joinPath(context.extensionUri, 'resources', filename);
const fileData = await vscode.workspace.fs.readFile(resourceUri);

cachedResources[filename] = fileData;
} catch (error) {
console.error(`Failed to read ${filename}:`, error);
cachedResources[filename] = error instanceof Error ? error : new Error(`Failed to load ${filename}`);
}
}
};

// Get a cached resource by name
Expand All @@ -68,5 +84,28 @@ export const getResourceContent = (resourceName: string): string => {
throw resource;
}

if (typeof resource !== 'string') {
throw new Error(`Resource ${resourceName} is not a text resource`);
}

return resource;
};

// Get a cached binary resource by name
export const getBinaryResourceContent = (resourceName: string): Uint8Array => {
const resource = cachedResources[resourceName];

if (!resource) {
throw new Error(`Resource not found: ${resourceName}`);
}

if (resource instanceof Error) {
throw resource;
}

if (typeof resource === 'string') {
throw new Error(`Resource ${resourceName} is not a binary resource`);
}

return resource;
};
33 changes: 33 additions & 0 deletions packages/web-deploy/agent-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Agent Instructions for Chartifact Examples

## Overview

This folder is a collection of data-driven documentation and visualization examples, organized for use with the Chartifact system. The files are structured to demonstrate features, data sources, and visualization patterns using both JSON and Markdown formats.

## Key Directories

- `chartifact-examples/json/`: Source data and configuration files for Chartifact, using `.idoc.json` format.
- `chartifact-examples/markdown/`: Human-readable documentation and examples, mirroring the JSON files, using `.idoc.md` format.
- `chartifact-examples/schema/`: Contains the JSON schema (`idoc_v1.json`) and TypeScript definitions (`idoc_v1.d.ts`) for the `.idoc` document format.

## Patterns and Conventions

- Each example or feature is represented in both JSON and Markdown, with matching filenames and directory structure.
- Features are grouped under `features/`, and scenario-based examples (e.g., `seattle-weather/`) are grouped by topic.
- The `.idoc.json` files conform to the schema in `schema/idoc_v1.json`. Validate new or edited JSON files against this schema.
- Markdown files (`.idoc.md`) are intended for human consumption and may include narrative, code snippets, and visualization descriptions.

## Developer Workflows

- The `chartifact-examples` directory is intended to be a read-only reference for developers working with the Chartifact system.
- Developers should not modify files directly in this directory; instead, they should use these as examples to give to the agent.

## Key tips

Plugins: these are markdown blocks denoted by triple backticks. In Chartifact, some of these these have both a serialization format (like json or yaml) followed by a plugin name. For example:

```json vega
{
"description": "Having 'vega' in the plugin block is critical, otherwise it is simply json which will not be interactive."
}
```
Loading