mirror of
https://github.com/olehomelchenko/astrolabe-nvc.git
synced 2025-12-21 21:22:23 +00:00
Implement Vega-Lite rendering with error handling and dynamic library loading
This commit is contained in:
@@ -105,17 +105,26 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### **Phase 4: Vega-Lite Rendering**
|
### **Phase 4: Vega-Lite Rendering** ✅ **COMPLETE**
|
||||||
**Goal**: Live preview of visualizations
|
**Goal**: Live preview of visualizations
|
||||||
|
|
||||||
- [ ] Load Vega-Embed from CDN
|
- [x] Load Vega-Embed from CDN
|
||||||
- [ ] Create render function that takes spec from editor
|
- [x] Create render function that takes spec from editor
|
||||||
- [ ] Add debounced auto-render on editor change
|
- [x] Add debounced auto-render on editor change
|
||||||
- [ ] Display rendered chart in right panel
|
- [x] Display rendered chart in right panel
|
||||||
- [ ] Handle rendering errors gracefully (show in preview panel)
|
- [x] Handle rendering errors gracefully (show in preview panel)
|
||||||
|
|
||||||
**Deliverable**: Editor → live chart pipeline working with auto-refresh
|
**Deliverable**: Editor → live chart pipeline working with auto-refresh
|
||||||
|
|
||||||
|
**Key Achievements**:
|
||||||
|
- Successfully resolved AMD loader conflicts with Monaco Editor
|
||||||
|
- Implemented UMD build loading with temporary AMD disable/restore
|
||||||
|
- Added 1.5s debounced rendering for smooth editing experience
|
||||||
|
- Created comprehensive error handling with user-friendly messages
|
||||||
|
- Established working editor → JSON parsing → Vega-Lite rendering pipeline
|
||||||
|
- Sample bar chart loads and renders immediately on page load
|
||||||
|
- Live preview updates automatically as user edits JSON specification
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### **Phase 5: Data Model + LocalStorage**
|
### **Phase 5: Data Model + LocalStorage**
|
||||||
@@ -275,5 +284,5 @@ Astrolabe is a focused tool for managing, editing, and previewing Vega-Lite visu
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Current Phase**: Phase 4 - Vega-Lite Rendering
|
**Current Phase**: Phase 5 - Data Model + LocalStorage
|
||||||
**Status**: Ready to begin implementation
|
**Status**: Ready to begin implementation
|
||||||
92
index.html
92
index.html
@@ -83,14 +83,7 @@
|
|||||||
Preview
|
Preview
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-content">
|
<div class="panel-content">
|
||||||
<div class="preview-placeholder">
|
<div id="vega-preview" style="height: 100%; width: 100%; overflow: auto;"></div>
|
||||||
<div class="placeholder">
|
|
||||||
Vega-Lite visualization will render here
|
|
||||||
</div>
|
|
||||||
<div style="margin-top: 8px; font-size: 12px; color: #adb5bd;">
|
|
||||||
Live preview • Auto-refresh • Error display
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -98,6 +91,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
let editor; // Global editor instance
|
let editor; // Global editor instance
|
||||||
|
let renderTimeout; // For debouncing
|
||||||
|
|
||||||
// Sample Vega-Lite specification
|
// Sample Vega-Lite specification
|
||||||
const sampleSpec = {
|
const sampleSpec = {
|
||||||
@@ -122,6 +116,74 @@
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Render function that takes spec from editor
|
||||||
|
async function renderVisualization() {
|
||||||
|
const previewContainer = document.getElementById('vega-preview');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get current content from editor
|
||||||
|
const specText = editor.getValue();
|
||||||
|
const spec = JSON.parse(specText);
|
||||||
|
|
||||||
|
// Render with Vega-Embed (use global variable)
|
||||||
|
await window.vegaEmbed('#vega-preview', spec, {
|
||||||
|
actions: false, // Hide action menu for cleaner look
|
||||||
|
renderer: 'svg' // Use SVG for better quality
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
// Handle rendering errors gracefully
|
||||||
|
previewContainer.innerHTML = `
|
||||||
|
<div style="padding: 20px; color: #d32f2f; font-size: 12px; font-family: monospace;">
|
||||||
|
<strong>Rendering Error:</strong><br>
|
||||||
|
${error.message}
|
||||||
|
<br><br>
|
||||||
|
<em>Check your JSON syntax and Vega-Lite specification.</em>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debounced render function
|
||||||
|
function debouncedRender() {
|
||||||
|
clearTimeout(renderTimeout);
|
||||||
|
renderTimeout = setTimeout(renderVisualization, 1500); // 500ms delay
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load Vega libraries dynamically with UMD builds
|
||||||
|
function loadVegaLibraries() {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Temporarily disable AMD define to avoid conflicts
|
||||||
|
const originalDefine = window.define;
|
||||||
|
window.define = undefined;
|
||||||
|
|
||||||
|
// Load Vega
|
||||||
|
const vegaScript = document.createElement('script');
|
||||||
|
vegaScript.src = 'https://unpkg.com/vega@5/build/vega.min.js';
|
||||||
|
vegaScript.onload = () => {
|
||||||
|
// Load Vega-Lite
|
||||||
|
const vegaLiteScript = document.createElement('script');
|
||||||
|
vegaLiteScript.src = 'https://unpkg.com/vega-lite@5/build/vega-lite.min.js';
|
||||||
|
vegaLiteScript.onload = () => {
|
||||||
|
// Load Vega-Embed
|
||||||
|
const vegaEmbedScript = document.createElement('script');
|
||||||
|
vegaEmbedScript.src = 'https://unpkg.com/vega-embed@6/build/vega-embed.min.js';
|
||||||
|
vegaEmbedScript.onload = () => {
|
||||||
|
// Restore AMD define
|
||||||
|
window.define = originalDefine;
|
||||||
|
resolve();
|
||||||
|
};
|
||||||
|
vegaEmbedScript.onerror = reject;
|
||||||
|
document.head.appendChild(vegaEmbedScript);
|
||||||
|
};
|
||||||
|
vegaLiteScript.onerror = reject;
|
||||||
|
document.head.appendChild(vegaLiteScript);
|
||||||
|
};
|
||||||
|
vegaScript.onerror = reject;
|
||||||
|
document.head.appendChild(vegaScript);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', function () {
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
// Initialize Monaco Editor
|
// Initialize Monaco Editor
|
||||||
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' } });
|
require.config({ paths: { vs: 'https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.47.0/min/vs' } });
|
||||||
@@ -131,9 +193,7 @@
|
|||||||
try {
|
try {
|
||||||
const response = await fetch('https://vega.github.io/schema/vega-lite/v5.json');
|
const response = await fetch('https://vega.github.io/schema/vega-lite/v5.json');
|
||||||
vegaLiteSchema = await response.json();
|
vegaLiteSchema = await response.json();
|
||||||
console.log('Vega-Lite schema loaded successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Failed to load Vega-Lite schema:', error);
|
|
||||||
vegaLiteSchema = null;
|
vegaLiteSchema = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +209,9 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load Vega libraries before creating editor
|
||||||
|
await loadVegaLibraries();
|
||||||
|
|
||||||
// Create the editor with improved configuration
|
// Create the editor with improved configuration
|
||||||
editor = monaco.editor.create(document.getElementById('monaco-editor'), {
|
editor = monaco.editor.create(document.getElementById('monaco-editor'), {
|
||||||
value: JSON.stringify(sampleSpec, null, 2),
|
value: JSON.stringify(sampleSpec, null, 2),
|
||||||
@@ -163,7 +226,13 @@
|
|||||||
formatOnType: true
|
formatOnType: true
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('Monaco Editor initialized with enhanced Vega-Lite schema support');
|
// Add debounced auto-render on editor change
|
||||||
|
editor.onDidChangeModelContent(() => {
|
||||||
|
debouncedRender();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
renderVisualization();
|
||||||
});
|
});
|
||||||
|
|
||||||
// Basic toggle functionality
|
// Basic toggle functionality
|
||||||
@@ -202,7 +271,6 @@
|
|||||||
const headerLinks = document.querySelectorAll('.header-link');
|
const headerLinks = document.querySelectorAll('.header-link');
|
||||||
headerLinks.forEach(link => {
|
headerLinks.forEach(link => {
|
||||||
link.addEventListener('click', function () {
|
link.addEventListener('click', function () {
|
||||||
console.log('Header link clicked:', this.textContent);
|
|
||||||
// TODO: Implement actual functionality in future phases
|
// TODO: Implement actual functionality in future phases
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -108,15 +108,15 @@ body {
|
|||||||
|
|
||||||
/* Panel sizing */
|
/* Panel sizing */
|
||||||
.snippet-panel {
|
.snippet-panel {
|
||||||
flex: 0 0 25%;
|
flex: 0 0 20%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editor-panel {
|
.editor-panel {
|
||||||
flex: 0 0 50%;
|
flex: 0 0 30%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.preview-panel {
|
.preview-panel {
|
||||||
flex: 0 0 25%;
|
flex: 0 0 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Toggle buttons */
|
/* Toggle buttons */
|
||||||
|
|||||||
Reference in New Issue
Block a user