Compare commits

...
Sign in to create a new pull request.

6 commits

Author SHA1 Message Date
DJP
471cf90048 Add comprehensive documentation
📚 NEW DOCUMENTATION:
 README.md - Complete project overview:
   - Feature showcase with badges and sections
   - Quick start guide with multiple server options
   - Detailed usage guide with Mermaid syntax examples
   - Browser compatibility matrix
   - Export/import format documentation
   - Troubleshooting FAQ
   - Contributing guidelines

🛠️ INSTALL.md - Detailed installation guide:
   - Multiple deployment methods (PHP, Python, Node.js, Docker)
   - Web server setup (Apache, Nginx, shared hosting)
   - Security considerations and HTTPS setup
   - Performance optimization tips
   - Comprehensive troubleshooting section
   - Environment-specific configurations

📖 Documentation Features:
   - Professional formatting with emojis and badges
   - Code examples for all supported syntaxes
   - Step-by-step installation instructions
   - Common issues and solutions
   - Browser compatibility information
   - Project structure overview

Perfect for:
🎯 New users getting started
🚀 Production deployments
🔧 Troubleshooting issues
👥 Team onboarding

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 10:18:20 -04:00
DJP
89c8c78331 Fix UX: Import Project now available immediately
UX IMPROVEMENTS:
🔧 Import Project button always visible:
   - No longer hidden until after D3 rendering
   - Available immediately when page loads
   - Logical workflow: Import → Render (not Render → Import)

📥 Better import workflow:
   1. Page loads → Import Project button visible
   2. Click Import Project → Select .json file
   3. Project imports → Automatically renders with D3
   4. D3 buttons appear → Ready for editing

 Smart auto-rendering:
   - Import automatically switches to D3 mode
   - Preserves exact positioning from imported project
   - Shows D3-specific buttons (Export, Clear Positions)
   - Updated success message explains D3 auto-rendering

🎯 Now supports proper workflow:
   - Start fresh → Import existing project → Continue editing
   - No need to render first just to access import
   - Intuitive user experience

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 09:48:19 -04:00
DJP
708a9af042 Add comprehensive project export/import system
NEW FEATURES:
📦 Complete Project Export (.json):
   - Mermaid code + exact node positions + styling
   - Metadata (export date, title, description)
   - Version tracking and validation
   - Filename: mermaid-project-YYYY-MM-DD.json

📥 Complete Project Import:
   - Restores exact layout and positioning
   - Validates file format before import
   - Updates both code textarea and D3 visualization
   - Preserves all styling and properties
   - Shows import confirmation with metadata

🔄 Workflow:
   1. Create your diagram with D3 renderer
   2. Drag nodes to perfect positions
   3. Customize colors, text, shapes
   4. Click 'Export Project' → Downloads .json
   5. Later: Click 'Import Project' → Select .json
   6. Diagram restores with EXACT same layout\!

💾 Project File Contents:
   - version: File format version
   - mermaidCode: Pure Mermaid syntax
   - d3Data: Complete node/edge data with positions
   - renderSettings: Canvas size and renderer info
   - metadata: Title, description, export timestamp

 Perfect for:
   - Sharing diagrams with exact layouts
   - Version control of visual designs
   - Backing up complex positioning work
   - Collaborative editing workflows

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 09:44:55 -04:00
DJP
a88afcf4b5 Fix D3 renderer critical issues
FIXES:
🔧 Shape Detection: Now properly honors Mermaid syntax
   - A[text] → Rectangle 
   - B(text) → Circle 
   - C{text} → Diamond 
   - Added debug logging for shape parsing

💾 Position Persistence: localStorage integration
   - Positions save automatically when dragging
   - Positions restore on page refresh/reload
   - Positions preserved during sync operations
   - 'Reset Positions' button to clear saved positions

🔄 Sync Position Preservation:
   - generateMermaidCode() now saves positions before updating
   - No more position loss during visual-to-code sync
   - LocalStorage acts as position memory between operations

🎯 Better Initial Positioning:
   - Smarter random positioning (avoids edges)
   - Saved positions take priority over automatic layout
   - Clear debugging for position loading/saving

The three major issues are now resolved:
 Shapes honor Mermaid syntax (B(Go shopping) = circle)
 Positions persist across sessions (localStorage)
 Sync operations preserve layout (no jumping\!)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 09:27:28 -04:00
DJP
96fc8af786 Complete D3.js property panel integration
Features added:
- Full property panel integration with D3 renderer
- Node editing: text, shape, colors with live visual updates
- Edge editing: labels, types, colors, widths with live updates
- Element selection with visual highlighting
- Delete functionality for nodes and edges with cascade cleanup
- Bidirectional sync: D3 visual changes → Mermaid code updates
- Shape transformation support (rectangle ↔ circle ↔ diamond)
- Real-time code generation preserving all styling

D3 renderer now has feature parity with Mermaid renderer plus:
 Persistent positioning (no jumping back!)
 Smooth drag & drop interactions
 Live property editing with instant visual feedback
 Complete bidirectional synchronization

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 09:24:06 -04:00
DJP
c86a3cf7aa Add D3.js hybrid renderer
Features:
- D3.js renderer using existing Mermaid parser
- True drag & drop with persistent positioning
- Force simulation for automatic initial layout
- Support for node shapes (rectangle, circle, diamond)
- Edge labels and styling from Mermaid syntax
- Click handlers for nodes and edges (ready for property editing)
- Maintains full compatibility with Mermaid syntax

Usage:
- Click 'Render with D3' to use D3 renderer instead of Mermaid
- Drag nodes to reposition - positions stay fixed
- All existing Mermaid code parsing works unchanged

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-11 09:17:19 -04:00
3 changed files with 1505 additions and 83 deletions

431
INSTALL.md Normal file
View file

@ -0,0 +1,431 @@
# 📋 Installation Guide
Complete installation instructions for the Interactive Mermaid Editor across different environments.
## 🎯 Quick Start (30 seconds)
```bash
# Clone and run
git clone git@bitbucket.org:zlalani/mermaid-edit.git
cd mermaid-edit
php -S localhost:8000
# Open http://localhost:8000
```
## 💻 Local Development Setup
### Prerequisites
- **Web Server** (PHP built-in, Python, Node.js, or traditional server)
- **Modern Browser** (Chrome 88+, Firefox 85+, Safari 14+, Edge 88+)
- **Git** (for cloning repository)
### Method 1: PHP Built-in Server (Recommended)
**Requirements:** PHP 7.4 or newer
```bash
# 1. Clone repository
git clone git@bitbucket.org:zlalani/mermaid-edit.git
cd mermaid-edit
# 2. Start PHP server
php -S localhost:8000
# 3. Open browser
open http://localhost:8000
```
**Advantages:**
- ✅ Zero configuration
- ✅ PHP features work out of the box
- ✅ Fast setup
### Method 2: Python HTTP Server
**Requirements:** Python 3.x or Python 2.7
```bash
# 1. Clone repository
git clone git@bitbucket.org:zlalani/mermaid-edit.git
cd mermaid-edit
# 2. Start Python server
# Python 3:
python3 -m http.server 8000
# Python 2:
python -m SimpleHTTPServer 8000
# 3. Open browser
open http://localhost:8000
```
**Note:** PHP features will not work with Python server.
### Method 3: Node.js HTTP Server
**Requirements:** Node.js and npm
```bash
# 1. Install http-server globally (one time)
npm install -g http-server
# 2. Clone repository
git clone git@bitbucket.org:zlalani/mermaid-edit.git
cd mermaid-edit
# 3. Start Node server
http-server -p 8000
# 4. Open browser
open http://localhost:8000
```
**Alternative:** Using npx (no global install)
```bash
npx http-server -p 8000
```
### Method 4: Docker (Advanced)
Create `Dockerfile`:
```dockerfile
FROM php:8.1-apache
COPY . /var/www/html/
EXPOSE 80
```
Create `docker-compose.yml`:
```yaml
version: '3.8'
services:
mermaid-editor:
build: .
ports:
- "8000:80"
volumes:
- .:/var/www/html
```
Run:
```bash
docker-compose up -d
open http://localhost:8000
```
## 🌐 Web Server Deployment
### Apache Setup
1. **Upload files** to web root (e.g., `/var/www/html/mermaid-edit/`)
2. **Set permissions**:
```bash
# Set directory permissions
find /var/www/html/mermaid-edit -type d -exec chmod 755 {} \;
# Set file permissions
find /var/www/html/mermaid-edit -type f -exec chmod 644 {} \;
```
3. **Configure Apache** (optional `.htaccess`):
```apache
# .htaccess
RewriteEngine On
DirectoryIndex index.php
# Security headers
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
```
4. **Access**: `http://yourdomain.com/mermaid-edit/`
### Nginx Setup
1. **Upload files** to web root
2. **Configure Nginx**:
```nginx
server {
listen 80;
server_name yourdomain.com;
root /var/www/html/mermaid-edit;
index index.php index.html;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
fastcgi_pass unix:/var/run/php/php8.1-fpm.sock;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
3. **Restart Nginx**: `sudo systemctl restart nginx`
### Shared Hosting (cPanel/Plesk)
1. **Upload via FTP/File Manager**:
- Extract files to `public_html/mermaid-edit/`
2. **Set permissions** via cPanel File Manager:
- Directories: 755
- Files: 644
3. **Access**: `http://yourdomain.com/mermaid-edit/`
## ⚙️ Configuration
### Basic Configuration
Edit `config.php`:
```php
<?php
// Site settings
define('SITE_TITLE', 'My Interactive Mermaid Editor');
define('DEBUG_MODE', false);
// Optional: Custom settings
define('MAX_FILE_SIZE', '10MB');
define('ALLOWED_EXTENSIONS', 'mmd,json');
?>
```
### Environment Variables (Advanced)
Create `.env` file:
```env
SITE_TITLE="Interactive Mermaid Editor"
DEBUG_MODE=false
MAX_UPLOAD_SIZE=10485760
```
### Customization
#### Change Default Colors
Edit CSS variables in `index.php`:
```css
:root {
--primary-btn-color: #your-color;
--background-color: #your-background;
/* ... other variables ... */
}
```
#### Modify Canvas Size
Edit D3 renderer initialization:
```javascript
this.width = 1200; // Change from 800
this.height = 800; // Change from 600
```
## 🔒 Security Considerations
### Production Deployment
1. **Disable Debug Mode**:
```php
define('DEBUG_MODE', false);
```
2. **Set Secure Headers**:
```apache
Header always set X-Frame-Options DENY
Header always set X-Content-Type-Options nosniff
Header always set X-XSS-Protection "1; mode=block"
```
3. **File Permissions**:
```bash
chmod 755 directories
chmod 644 files
chmod 600 config.php # Restrict config access
```
4. **Hide Sensitive Files**:
```apache
# .htaccess
<Files "config.php">
Order Allow,Deny
Deny from all
</Files>
```
### HTTPS Setup
1. **Obtain SSL Certificate** (Let's Encrypt recommended)
2. **Configure HTTPS redirect**
3. **Update any hardcoded HTTP URLs**
## 🧪 Testing Installation
### Verification Checklist
1. **Basic Access**:
- [ ] Page loads without errors
- [ ] Default Mermaid diagram renders
- [ ] Dark mode toggle works
2. **D3 Features**:
- [ ] "Render with D3" button works
- [ ] Nodes are draggable
- [ ] Property panel appears on click
- [ ] Positions persist after page refresh
3. **Export/Import**:
- [ ] Export .mmd file works
- [ ] Export project (.json) works
- [ ] Import project restores layout
- [ ] PNG export functions
4. **Browser Console**:
- [ ] No JavaScript errors
- [ ] Position save/load messages appear
### Common Issues & Fixes
**"File not found" errors:**
```bash
# Check file permissions
ls -la index.php
# Should show: -rw-r--r--
```
**PHP errors:**
```bash
# Check PHP error log
tail -f /var/log/apache2/error.log
# or
tail -f /var/log/nginx/error.log
```
**JavaScript not working:**
- Check browser console (F12)
- Verify CDN resources load (Mermaid.js, D3.js)
- Test with different browser
**Positions not saving:**
- Check localStorage is enabled
- Verify browser supports modern JavaScript
- Check for browser extensions blocking storage
## 🚀 Performance Optimization
### For Large Diagrams
1. **Increase PHP Limits**:
```ini
; php.ini
memory_limit = 256M
max_execution_time = 300
max_input_vars = 3000
```
2. **Browser Optimization**:
- Use Chrome for best D3 performance
- Close unnecessary browser tabs
- Disable browser extensions during editing
### Caching (Production)
1. **Enable Browser Caching**:
```apache
# .htaccess
<IfModule mod_expires.c>
ExpiresActive On
ExpiresByType text/css "access plus 1 month"
ExpiresByType application/javascript "access plus 1 month"
</IfModule>
```
2. **Enable Gzip Compression**:
```apache
<IfModule mod_deflate.c>
AddOutputFilterByType DEFLATE text/plain
AddOutputFilterByType DEFLATE text/html
AddOutputFilterByType DEFLATE text/css
AddOutputFilterByType DEFLATE application/javascript
</IfModule>
```
## 🆘 Troubleshooting
### Installation Issues
**Problem: Permission denied**
```bash
# Fix permissions
sudo chown -R www-data:www-data /var/www/html/mermaid-edit
sudo chmod -R 755 /var/www/html/mermaid-edit
```
**Problem: PHP not working**
```bash
# Check PHP version
php --version
# Install PHP if missing (Ubuntu/Debian)
sudo apt update
sudo apt install php php-cli
# Install PHP (macOS with Homebrew)
brew install php
```
**Problem: Port already in use**
```bash
# Find process using port
lsof -i :8000
# Kill process
kill -9 [PID]
# Or use different port
php -S localhost:8080
```
### Runtime Issues
**Problem: Blank page**
- Check browser console for JavaScript errors
- Verify all CDN resources load
- Check PHP error logs
**Problem: Drag and drop not working**
- Ensure you're using "Render with D3"
- Check browser supports modern JavaScript
- Try different browser
**Problem: Import/Export not working**
- Check browser localStorage is enabled
- Verify file permissions for uploads
- Check browser security settings
## 📞 Getting Help
If you encounter issues:
1. **Check browser console** (F12 → Console)
2. **Review server error logs**
3. **Test in different browser**
4. **Create GitHub issue** with:
- Browser version
- Server environment
- Error messages
- Steps to reproduce
## ✅ Success!
Once installed, you should see:
- 🎨 Interactive Mermaid Editor interface
- 🖱️ Draggable nodes in D3 mode
- 🎨 Property panel for editing
- 💾 Export/import functionality
**Happy diagram editing!** 🚀

328
README.md
View file

@ -1,110 +1,280 @@
# Mermaid Diagram Tool
# 🎨 Interactive Mermaid Editor
An interactive web-based tool for creating, editing, and exporting Mermaid diagrams.
A powerful visual editor for Mermaid diagrams with **persistent positioning**, **real-time editing**, and **bidirectional synchronization** between visual elements and code.
![Mermaid Diagram Tool](https://mermaid.js.org/assets/img/header.png)
![Version](https://img.shields.io/badge/version-2.0-blue)
![License](https://img.shields.io/badge/license-MIT-green)
![PHP](https://img.shields.io/badge/PHP-7.4+-purple)
![D3.js](https://img.shields.io/badge/D3.js-v7-orange)
![Mermaid](https://img.shields.io/badge/Mermaid-Compatible-red)
## Features
## Features
### Core Features
- **Live Diagram Rendering**: See your Mermaid diagram code visualized in real-time
- **Dark/Light Mode**: Toggle between dark and light UI themes while keeping diagrams in high-contrast mode
- **Export Options**: Download diagrams as high-resolution PNG files or as .mmd source files
- **Import Feature**: Upload and edit existing .mmd diagram files
- **Responsive Design**: Works on desktop and mobile devices with adaptive layout
### 🎯 **Dual Rendering Modes**
- **Mermaid Renderer**: Standard Mermaid.js rendering with automatic layout
- **D3 Renderer**: Interactive D3.js rendering with **persistent positioning**
### Technical Highlights
- **Local Storage**: Remembers your UI theme preference between sessions
- **High-Resolution Export**: PNG exports are generated at 4x resolution for high-quality images
- **Syntax Highlighting**: Improved readability for diagram code (via Mermaid's built-in rendering)
- **Client-Side Processing**: No server communication needed for diagram rendering or file operations
### 🖱️ **Visual Editing**
- **Drag & Drop**: Move nodes with mouse - positions stay permanently
- **Click to Edit**: Select any node or edge to edit properties
- **Real-time Updates**: Changes appear instantly in the diagram
- **Shape Support**: Rectangles, circles, diamonds with live transformation
## Getting Started
### 🎨 **Styling & Customization**
- **Node Properties**: Text, colors, shapes, sizes
- **Edge Properties**: Labels, colors, line styles, widths
- **Color Picker**: Full color customization with live preview
- **Multiple Shapes**: Rectangle `[text]`, Circle `(text)`, Diamond `{text}`
### Installation
1. Clone this repository to your local environment
2. No build process required - open `index.php` in a PHP-enabled web server
3. Alternatively, deploy the files to any PHP web hosting environment
### 🔄 **Bidirectional Sync**
- **Visual → Code**: Edit visually, Mermaid code updates automatically
- **Code → Visual**: Paste Mermaid code, renders with all features
- **Syntax Preservation**: Maintains proper Mermaid syntax compatibility
### Prerequisites
- PHP 7.0 or higher
- Modern web browser (Chrome, Firefox, Safari, Edge)
- No special server requirements beyond basic PHP support
### 💾 **Project Management**
- **Export Projects**: Save complete layouts with positioning data
- **Import Projects**: Restore exact layouts from saved files
- **Position Persistence**: Node positions survive page refreshes
- **Mermaid Export**: Standard `.mmd` file export for compatibility
## Usage Guide
### 🎪 **Advanced Features**
- **Subgraph Support**: Group nodes for better organization
- **Dark Mode**: Toggle between light and dark themes
- **PNG Export**: High-resolution image export
- **Layout Memory**: Automatic position saving via localStorage
### Creating a Diagram
1. Enter Mermaid syntax in the "Input Mermaid Diagram Code" textarea
2. Click "Render Diagram" to visualize your diagram in the output area
3. Edit and re-render as needed to refine your diagram
## 🚀 Quick Start
### Example Diagram Syntax
```
graph TD
A[Christmas] -->|Get money| B(Go shopping)
B --> C{Let me think}
C -->|One| D[Android]
C -->|Two| E[Nuka Cola]
C -->|Three| F[Cold fusion generator]
### Method 1: Local Web Server (Recommended)
1. **Clone the repository**
```bash
git clone git@bitbucket.org:zlalani/mermaid-edit.git
cd mermaid-edit
```
2. **Start local server** (choose one):
**PHP Built-in Server:**
```bash
php -S localhost:8000
```
**Python HTTP Server:**
```bash
# Python 3
python -m http.server 8000
# Python 2
python -m SimpleHTTPServer 8000
```
**Node.js HTTP Server:**
```bash
npx http-server -p 8000
```
3. **Open in browser**
```
http://localhost:8000
```
### Method 2: Web Server Deployment
1. **Upload files** to your web server
2. **Configure PHP** (if using PHP features)
3. **Set permissions** (755 for directories, 644 for files)
4. **Access via domain/IP**
## 📖 Usage Guide
### Getting Started
1. **Create a diagram** by typing Mermaid syntax in the left panel
2. **Choose renderer**:
- Click **"Render Diagram"** for standard Mermaid
- Click **"Render with D3"** for interactive editing
3. **Start editing** - click nodes/edges to customize
### Interactive Editing Workflow
#### Basic Editing
```mermaid
flowchart TD
A[Start] --> B{Decision}
B -->|Yes| C[Action]
B -->|No| D[End]
```
### Importing a Diagram
1. Click "Import .mmd" button
2. Select a .mmd file from your local computer
3. The diagram code will be loaded and automatically rendered
1. **Click "Render with D3"**
2. **Drag nodes** to desired positions
3. **Click nodes** to edit text, colors, shapes
4. **Click edges** to edit labels, styles, colors
5. **Positions save automatically**
### Exporting Your Work
- **PNG Export**: Click "Download as PNG" to save the rendered diagram as a high-resolution image
- **Source Code Export**: Click "Download as .mmd" to save your Mermaid code for later use
#### Project Management
- **Export Project**: Saves everything (code + positions + styling)
- **Import Project**: Restores exact layout from saved file
- **Export .mmd**: Standard Mermaid file for other tools
- **Export PNG**: High-resolution image
### Using Dark Mode
- Click the sun/moon icon in the top-right corner to toggle between dark and light modes
- Your preference is automatically saved for future visits
- The diagram area always remains in light mode for optimal readability and export quality
### Supported Mermaid Syntax
## Diagram Types Supported
#### Node Types
```mermaid
A[Rectangle] # Rectangle shape
B(Rounded) # Circle/rounded shape
C{Diamond} # Diamond shape
D((Circle)) # Perfect circle
```
This tool supports all Mermaid diagram types, including:
#### Connection Types
```mermaid
A --> B # Arrow
A --- B # Line
A -.-> B # Dotted arrow
A ==> B # Thick arrow
A -->|Label| B # Labeled connection
```
- Flowcharts
- Sequence diagrams
- Class diagrams
- State diagrams
- Entity Relationship diagrams
- User Journey diagrams
- Gantt charts
- Pie charts
- Requirement diagrams
#### Styling
```mermaid
flowchart TD
A[Node] --> B[Another]
style A fill:#f96,color:#fff
style B fill:#bbf,color:#000
linkStyle 0 stroke:#f66,stroke-width:3px
```
## Customization
## 🔧 Configuration
The tool can be customized by modifying the following files:
### Basic Configuration (config.php)
```php
<?php
define('SITE_TITLE', 'Interactive Mermaid Editor');
define('DEBUG_MODE', false);
?>
```
- `config.php`: Change site title and other configuration settings
- `index.php`: Modify UI layout, styling, and JavaScript functionality
- `css/style.css`: Additional CSS styling (if needed)
### Customization Options
- **Canvas Size**: Modify `width` and `height` in D3 renderer
- **Color Defaults**: Update default colors in CSS variables
- **Theme Settings**: Customize light/dark mode colors
## Troubleshooting
## 🌐 Browser Compatibility
| Browser | Version | Status |
|---------|---------|--------|
| Chrome | 88+ | ✅ Full Support |
| Firefox | 85+ | ✅ Full Support |
| Safari | 14+ | ✅ Full Support |
| Edge | 88+ | ✅ Full Support |
**Requirements:**
- Modern browser with ES6+ support
- JavaScript enabled
- LocalStorage support (for position persistence)
## 📁 Project Structure
```
mermaid-edit/
├── index.php # Main application
├── config.php # Configuration
├── css/
│ └── style.css # Styles (legacy)
├── js/ # JavaScript (legacy)
├── OLD/ # Previous versions
├── README.md # This file
└── .git/ # Git repository
```
## 🔄 Export/Import Format
### Project Export (.json)
```json
{
"version": "1.0",
"exportedAt": "2024-01-15T10:30:00.000Z",
"mermaidCode": "flowchart TD\n A[Start] --> B[End]",
"d3Data": {
"nodes": [
{
"id": "A",
"text": "Start",
"shape": "rect",
"position": {"x": 240, "y": 100, "fx": 240, "fy": 100},
"style": {"fill": "#ffc406", "color": "#000000"}
}
],
"edges": [
{
"source": "A",
"target": "B",
"label": "",
"type": "-->",
"style": {"stroke": "#000", "strokeWidth": 2}
}
]
},
"renderSettings": {
"width": 800,
"height": 600,
"renderer": "D3"
},
"metadata": {
"title": "My Diagram",
"description": "Interactive Mermaid project"
}
}
```
## 🚨 Troubleshooting
### Common Issues
1. **Diagram Not Rendering**: Check your Mermaid syntax for errors, which will display in the output area
2. **Export Not Working**: Ensure JavaScript is enabled in your browser
3. **UI Issues**: Try clearing browser cache or using incognito/private mode
## Credits and License
**Q: Positions don't save**
- A: Use "Render with D3" for persistent positioning
- A: Check browser localStorage is enabled
- Built using [Mermaid.js](https://mermaid.js.org/)
- Licensed under MIT license
**Q: Import doesn't work**
- A: Ensure JSON file format is valid
- A: Check browser console for error messages
## Future Development Plans
**Q: Shapes don't match Mermaid syntax**
- A: Verify correct syntax: `[rect]`, `(round)`, `{diamond}`
- A: Check console for parsing errors
Potential future enhancements:
- Diagram template library
- Custom theme builder
- Multiple diagram tabs
- Collaborative editing features
- Integration with version control systems
**Q: Performance issues**
- A: Large diagrams may be slow in D3 mode
- A: Use standard Mermaid mode for complex diagrams
## 🤝 Contributing
1. **Fork** the repository
2. **Create feature branch** (`git checkout -b feature/amazing-feature`)
3. **Commit changes** (`git commit -m 'Add amazing feature'`)
4. **Push to branch** (`git push origin feature/amazing-feature`)
5. **Open Pull Request**
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- **Mermaid.js** - Original diagram rendering
- **D3.js** - Interactive visualizations
- **Contributors** - All developers who helped improve this tool
## 📞 Support
- **Issues**: [Create GitHub Issue](https://github.com/your-repo/issues)
- **Documentation**: This README and inline code comments
- **Community**: Share your diagrams and get help
---
*This tool is designed to simplify the creation and sharing of Mermaid diagrams for documentation, presentations, and educational purposes.*
**Made with ❤️ for better diagram editing**
*Interactive Mermaid Editor - Where visual meets code*

829
index.php
View file

@ -219,6 +219,7 @@ require_once 'config.php';
}
</style>
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script src="https://d3js.org/d3.v7.min.js"></script>
</head>
<body>
<header>
@ -252,9 +253,14 @@ require_once 'config.php';
C -->|Three| F[Cold fusion generator]</textarea>
<div class="button-container">
<button id="renderBtn">Render Diagram</button>
<button id="renderD3Btn">Render with D3</button>
<button id="clearPositionsBtn" style="display: none;">Reset Positions</button>
<button id="exportMmdBtn">Download as .mmd</button>
<button id="exportProjectBtn" style="display: none;">Export Project</button>
<button id="importMmdBtn">Import .mmd</button>
<button id="importProjectBtn">Import Project</button>
<input type="file" id="fileInput" accept=".mmd" style="display: none;">
<input type="file" id="projectFileInput" accept=".json" style="display: none;">
</div>
</div>
<div class="output-area">
@ -329,6 +335,52 @@ require_once 'config.php';
document.addEventListener('DOMContentLoaded', renderMermaidDiagram);
document.getElementById('renderBtn').addEventListener('click', renderMermaidDiagram);
// Add D3 render button
document.getElementById('renderD3Btn').addEventListener('click', function() {
const mermaidCode = document.getElementById('mermaidInput').value;
d3Renderer.render(mermaidCode);
document.getElementById('clearPositionsBtn').style.display = 'inline-block';
document.getElementById('exportProjectBtn').style.display = 'inline-block';
});
// Add clear positions button
document.getElementById('clearPositionsBtn').addEventListener('click', function() {
localStorage.removeItem('d3_node_positions');
console.log('Cleared saved positions');
alert('Positions reset! Next render will use automatic layout.');
});
// Add export project button
document.getElementById('exportProjectBtn').addEventListener('click', function() {
d3Renderer.exportProject();
});
// Add import project button
document.getElementById('importProjectBtn').addEventListener('click', function() {
document.getElementById('projectFileInput').click();
});
// Handle project file import
document.getElementById('projectFileInput').addEventListener('change', function(event) {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = function(e) {
try {
const projectData = JSON.parse(e.target.result);
d3Renderer.importProject(projectData);
} catch (error) {
console.error('Error parsing project file:', error);
alert('Error reading project file: ' + error.message);
}
};
reader.readAsText(file);
// Reset file input
event.target.value = '';
});
document.getElementById('exportBtn').addEventListener('click', function() {
const svg = document.querySelector('#mermaidOutput svg');
@ -528,12 +580,15 @@ require_once 'config.php';
if (bracketLabel) {
text = bracketLabel;
shape = 'rect';
console.log(`Node ${nodeId}: [${text}] → shape: rect`);
} else if (parenLabel) {
text = parenLabel;
shape = 'round';
console.log(`Node ${nodeId}: (${text}) → shape: round`);
} else if (braceLabel) {
text = braceLabel;
shape = 'diamond';
console.log(`Node ${nodeId}: {${text}} → shape: diamond`);
}
return { id: nodeId, text, shape };
@ -623,10 +678,11 @@ require_once 'config.php';
}
determineNodeShape(nodeString) {
if (nodeString.includes('[') && nodeString.includes(']')) return 'rect';
if (nodeString.includes('(') && nodeString.includes(')')) return 'round';
if (nodeString.includes('{') && nodeString.includes('}')) return 'diamond';
console.log('Determining shape for:', nodeString);
if (nodeString.includes('((') && nodeString.includes('))')) return 'circle';
if (nodeString.includes('{') && nodeString.includes('}')) return 'diamond';
if (nodeString.includes('(') && nodeString.includes(')')) return 'round';
if (nodeString.includes('[') && nodeString.includes(']')) return 'rect';
return 'rect';
}
@ -1628,8 +1684,773 @@ require_once 'config.php';
}
}
// Initialize the editor
// D3.js Renderer - Hybrid approach using existing Mermaid parser
class D3Renderer {
constructor() {
this.svg = null;
this.width = 800;
this.height = 600;
this.parser = new MermaidParser();
this.simulation = null;
this.nodes = [];
this.edges = [];
}
render(mermaidCode) {
console.log('D3 Rendering Mermaid code:', mermaidCode);
// Parse using existing Mermaid parser
const parsedData = this.parser.parseCode(mermaidCode);
if (!parsedData) {
console.error('Failed to parse Mermaid code');
return;
}
console.log('Parsed data for D3:', parsedData);
// Clear previous render
d3.select('#mermaidOutput').selectAll('*').remove();
// Create SVG
this.svg = d3.select('#mermaidOutput')
.append('svg')
.attr('width', this.width)
.attr('height', this.height)
.attr('viewBox', `0 0 ${this.width} ${this.height}`)
.style('border', '1px solid #ccc');
// Create arrow marker
this.svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 20)
.attr('refY', 0)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
// Convert parsed data to D3 format
this.prepareD3Data(parsedData);
// Create force simulation
this.createForceSimulation();
// Draw edges
this.drawEdges();
// Draw nodes
this.drawNodes();
console.log('D3 render complete');
}
prepareD3Data(parsedData) {
// Load saved positions from localStorage
const savedPositions = this.loadSavedPositions();
// Convert nodes
this.nodes = Array.from(parsedData.nodes.values()).map(node => {
const savedPos = savedPositions.get(node.id);
return {
id: node.id,
text: node.text,
shape: node.shape,
style: node.style,
x: savedPos?.x || node.position?.x || (Math.random() * (this.width - 200)) + 100,
y: savedPos?.y || node.position?.y || (Math.random() * (this.height - 200)) + 100,
fx: savedPos?.fx || null, // fixed x (for dragging)
fy: savedPos?.fy || null // fixed y (for dragging)
};
});
// Convert edges
this.edges = parsedData.edges.map(edge => ({
source: edge.from,
target: edge.to,
label: edge.label,
type: edge.type,
style: edge.style
}));
console.log('D3 nodes with positions:', this.nodes);
console.log('D3 edges:', this.edges);
}
loadSavedPositions() {
const savedPositions = new Map();
try {
const positionsData = localStorage.getItem('d3_node_positions');
if (positionsData) {
const positions = JSON.parse(positionsData);
Object.keys(positions).forEach(nodeId => {
savedPositions.set(nodeId, positions[nodeId]);
});
console.log('Loaded saved positions:', savedPositions);
}
} catch (error) {
console.error('Error loading saved positions:', error);
}
return savedPositions;
}
saveCurrentPositions() {
const positions = {};
this.nodes.forEach(node => {
positions[node.id] = {
x: node.x,
y: node.y,
fx: node.fx,
fy: node.fy
};
});
try {
localStorage.setItem('d3_node_positions', JSON.stringify(positions));
console.log('Saved node positions to localStorage:', positions);
} catch (error) {
console.error('Error saving positions:', error);
}
}
createForceSimulation() {
this.simulation = d3.forceSimulation(this.nodes)
.force('link', d3.forceLink(this.edges).id(d => d.id).distance(150))
.force('charge', d3.forceManyBody().strength(-500))
.force('center', d3.forceCenter(this.width / 2, this.height / 2))
.force('collision', d3.forceCollide().radius(60));
}
drawEdges() {
const linkContainer = this.svg.append('g').attr('class', 'links');
this.linkElements = linkContainer.selectAll('line')
.data(this.edges)
.enter().append('line')
.attr('stroke', d => d.style.stroke || '#000')
.attr('stroke-width', d => d.style.strokeWidth || 2)
.attr('marker-end', 'url(#arrowhead)')
.style('cursor', 'pointer')
.on('click', (event, d) => {
console.log('Edge clicked:', d);
this.selectEdge(event.currentTarget, d);
});
// Add edge labels
this.labelElements = linkContainer.selectAll('text')
.data(this.edges.filter(d => d.label))
.enter().append('text')
.text(d => d.label)
.attr('font-size', 12)
.attr('fill', '#333')
.attr('text-anchor', 'middle')
.style('pointer-events', 'none');
}
drawNodes() {
const nodeContainer = this.svg.append('g').attr('class', 'nodes');
const nodeGroups = nodeContainer.selectAll('g')
.data(this.nodes)
.enter().append('g')
.attr('class', 'node-group')
.style('cursor', 'pointer')
.call(this.createDragBehavior());
// Add node shapes
nodeGroups.each((d, i, nodes) => {
const group = d3.select(nodes[i]);
this.addNodeShape(group, d);
});
// Add node text
nodeGroups.append('text')
.text(d => d.text)
.attr('text-anchor', 'middle')
.attr('dominant-baseline', 'middle')
.attr('font-family', 'Montserrat, sans-serif')
.attr('font-size', 14)
.attr('fill', d => d.style.color || '#000')
.style('pointer-events', 'none');
// Click handler for nodes
nodeGroups.on('click', (event, d) => {
console.log('Node clicked:', d);
this.selectNode(event.currentTarget, d);
});
this.nodeElements = nodeGroups;
// Update positions on simulation tick
this.simulation.on('tick', () => {
this.linkElements
.attr('x1', d => d.source.x)
.attr('y1', d => d.source.y)
.attr('x2', d => d.target.x)
.attr('y2', d => d.target.y);
this.labelElements
.attr('x', d => (d.source.x + d.target.x) / 2)
.attr('y', d => (d.source.y + d.target.y) / 2);
this.nodeElements
.attr('transform', d => `translate(${d.x}, ${d.y})`);
});
}
addNodeShape(group, d) {
const width = Math.max(80, d.text.length * 8);
const height = 40;
switch (d.shape) {
case 'round':
group.append('ellipse')
.attr('rx', width / 2)
.attr('ry', height / 2)
.attr('fill', d.style.fill || '#ffc406')
.attr('stroke', d.style.stroke || '#000')
.attr('stroke-width', 2);
break;
case 'diamond':
group.append('polygon')
.attr('points', `0,${-height/2} ${width/2},0 0,${height/2} ${-width/2},0`)
.attr('fill', d.style.fill || '#ffc406')
.attr('stroke', d.style.stroke || '#000')
.attr('stroke-width', 2);
break;
default: // rectangle
group.append('rect')
.attr('x', -width / 2)
.attr('y', -height / 2)
.attr('width', width)
.attr('height', height)
.attr('fill', d.style.fill || '#ffc406')
.attr('stroke', d.style.stroke || '#000')
.attr('stroke-width', 2)
.attr('rx', 5);
break;
}
}
createDragBehavior() {
return d3.drag()
.on('start', (event, d) => {
if (!event.active) this.simulation.alphaTarget(0.3).restart();
d.fx = d.x;
d.fy = d.y;
console.log('Drag start:', d.id);
})
.on('drag', (event, d) => {
d.fx = event.x;
d.fy = event.y;
})
.on('end', (event, d) => {
if (!event.active) this.simulation.alphaTarget(0);
// Keep nodes fixed at dragged position
// d.fx = null; d.fy = null; // Uncomment to allow nodes to float freely
console.log(`Node ${d.id} positioned at (${d.fx}, ${d.fy})`);
// Auto-save positions when dragging ends
this.saveCurrentPositions();
});
}
selectNode(element, nodeData) {
// Clear previous selections
this.clearSelections();
// Highlight selected node
d3.select(element).select('rect, ellipse, polygon')
.attr('stroke', '#ff6b6b')
.attr('stroke-width', 3)
.style('filter', 'drop-shadow(0 0 5px rgba(255, 107, 107, 0.5))');
this.selectedElement = element;
this.selectedData = nodeData;
// Show property panel using existing MermaidEditor method
this.showNodePropertyPanel(nodeData);
}
selectEdge(element, edgeData) {
// Clear previous selections
this.clearSelections();
// Highlight selected edge
d3.select(element)
.attr('stroke', '#ff6b6b')
.attr('stroke-width', 3);
this.selectedElement = element;
this.selectedData = edgeData;
// Show property panel using existing MermaidEditor method
this.showEdgePropertyPanel(edgeData);
}
clearSelections() {
// Clear node selections
this.svg.selectAll('.node-group rect, .node-group ellipse, .node-group polygon')
.attr('stroke', '#000')
.attr('stroke-width', 2)
.style('filter', 'none');
// Clear edge selections
this.svg.selectAll('.links line')
.attr('stroke', d => d.style.stroke || '#000')
.attr('stroke-width', d => d.style.strokeWidth || 2);
this.selectedElement = null;
this.selectedData = null;
}
showNodePropertyPanel(nodeData) {
const panel = document.getElementById('propertyPanel');
const content = document.getElementById('propertyContent');
content.innerHTML = `
<label>Node ID:</label>
<input type="text" id="d3NodeId" value="${nodeData.id}" readonly>
<label>Node Text:</label>
<input type="text" id="d3NodeText" value="${nodeData.text}">
<label>Node Shape:</label>
<select id="d3NodeShape">
<option value="rect" ${nodeData.shape === 'rect' ? 'selected' : ''}>Rectangle</option>
<option value="round" ${nodeData.shape === 'round' ? 'selected' : ''}>Circle</option>
<option value="diamond" ${nodeData.shape === 'diamond' ? 'selected' : ''}>Diamond</option>
</select>
<label>Node Color:</label>
<input type="color" id="d3NodeColor" value="${nodeData.style.fill || '#ffc406'}">
<label>Text Color:</label>
<input type="color" id="d3TextColor" value="${nodeData.style.color || '#000000'}">
<button onclick="d3Renderer.updateNodeProperties()">Update Node</button>
<button onclick="d3Renderer.deleteNode()">Delete Node</button>
`;
panel.classList.add('visible');
panel.style.display = 'block';
}
showEdgePropertyPanel(edgeData) {
const panel = document.getElementById('propertyPanel');
const content = document.getElementById('propertyContent');
content.innerHTML = `
<label>From:</label>
<input type="text" value="${edgeData.source.id || edgeData.source}" readonly>
<label>To:</label>
<input type="text" value="${edgeData.target.id || edgeData.target}" readonly>
<label>Edge Label:</label>
<input type="text" id="d3EdgeLabel" value="${edgeData.label || ''}">
<label>Edge Type:</label>
<select id="d3EdgeType">
<option value="-->" ${edgeData.type === '-->' ? 'selected' : ''}>Arrow (-->)</option>
<option value="---" ${edgeData.type === '---' ? 'selected' : ''}>Line (---)</option>
<option value="-..->" ${edgeData.type === '-..->' ? 'selected' : ''}>Dotted (-.->)</option>
</select>
<label>Edge Color:</label>
<input type="color" id="d3EdgeColor" value="${edgeData.style.stroke || '#000000'}">
<label>Edge Width:</label>
<input type="range" id="d3EdgeWidth" min="1" max="8" value="${edgeData.style.strokeWidth || 2}">
<span id="d3WidthValue">${edgeData.style.strokeWidth || 2}px</span>
<button onclick="d3Renderer.updateEdgeProperties()">Update Edge</button>
<button onclick="d3Renderer.deleteEdge()">Delete Edge</button>
`;
// Add live width display
setTimeout(() => {
const widthSlider = document.getElementById('d3EdgeWidth');
const widthDisplay = document.getElementById('d3WidthValue');
if (widthSlider && widthDisplay) {
widthSlider.addEventListener('input', () => {
widthDisplay.textContent = widthSlider.value + 'px';
});
}
}, 100);
panel.classList.add('visible');
panel.style.display = 'block';
}
updateNodeProperties() {
if (!this.selectedData) return;
const nodeText = document.getElementById('d3NodeText').value;
const nodeShape = document.getElementById('d3NodeShape').value;
const nodeColor = document.getElementById('d3NodeColor').value;
const textColor = document.getElementById('d3TextColor').value;
console.log('Updating D3 node properties:', {
id: this.selectedData.id,
text: nodeText,
shape: nodeShape,
color: nodeColor,
textColor: textColor
});
// Update the data
this.selectedData.text = nodeText;
this.selectedData.shape = nodeShape;
this.selectedData.style.fill = nodeColor;
this.selectedData.style.color = textColor;
// Update the visual elements
const selectedGroup = d3.select(this.selectedElement);
// Update text
selectedGroup.select('text')
.text(nodeText)
.attr('fill', textColor);
// Update shape and color
selectedGroup.select('rect, ellipse, polygon')
.attr('fill', nodeColor);
// If shape changed, redraw the node
if (nodeShape !== this.selectedData.shape) {
selectedGroup.select('rect, ellipse, polygon').remove();
this.addNodeShape(selectedGroup, this.selectedData);
}
console.log('Node updated successfully');
// Generate updated Mermaid code
this.generateMermaidCode();
}
updateEdgeProperties() {
if (!this.selectedData) return;
const edgeLabel = document.getElementById('d3EdgeLabel').value;
const edgeType = document.getElementById('d3EdgeType').value;
const edgeColor = document.getElementById('d3EdgeColor').value;
const edgeWidth = document.getElementById('d3EdgeWidth').value;
console.log('Updating D3 edge properties:', {
label: edgeLabel,
type: edgeType,
color: edgeColor,
width: edgeWidth
});
// Update the data
this.selectedData.label = edgeLabel;
this.selectedData.type = edgeType;
this.selectedData.style.stroke = edgeColor;
this.selectedData.style.strokeWidth = parseInt(edgeWidth);
// Update the visual elements
d3.select(this.selectedElement)
.attr('stroke', edgeColor)
.attr('stroke-width', edgeWidth);
// Update or add edge label
if (edgeLabel) {
// Find or create label element
let labelElement = this.svg.select(`.links text[data-edge="${this.selectedData.source.id}-${this.selectedData.target.id}"]`);
if (labelElement.empty()) {
labelElement = this.svg.select('.links').append('text')
.attr('data-edge', `${this.selectedData.source.id}-${this.selectedData.target.id}`)
.attr('font-size', 12)
.attr('fill', '#333')
.attr('text-anchor', 'middle')
.style('pointer-events', 'none');
}
labelElement.text(edgeLabel);
}
console.log('Edge updated successfully');
// Generate updated Mermaid code
this.generateMermaidCode();
}
deleteNode() {
if (!this.selectedData) return;
const nodeId = this.selectedData.id;
console.log('Deleting node:', nodeId);
// Remove from data arrays
this.nodes = this.nodes.filter(n => n.id !== nodeId);
this.edges = this.edges.filter(e =>
(e.source.id || e.source) !== nodeId &&
(e.target.id || e.target) !== nodeId
);
// Remove visual elements
d3.select(this.selectedElement).remove();
// Remove connected edges
this.svg.selectAll('.links line')
.filter(d => (d.source.id || d.source) === nodeId || (d.target.id || d.target) === nodeId)
.remove();
// Remove connected edge labels
this.svg.selectAll('.links text')
.filter(d => (d.source.id || d.source) === nodeId || (d.target.id || d.target) === nodeId)
.remove();
// Hide property panel
document.getElementById('propertyPanel').style.display = 'none';
// Update simulation
this.simulation.nodes(this.nodes);
this.simulation.force('link').links(this.edges);
this.simulation.alpha(1).restart();
// Generate updated Mermaid code
this.generateMermaidCode();
}
deleteEdge() {
if (!this.selectedData) return;
console.log('Deleting edge:', this.selectedData);
// Remove from data array
this.edges = this.edges.filter(e => e !== this.selectedData);
// Remove visual elements
d3.select(this.selectedElement).remove();
// Remove label if exists
this.svg.selectAll('.links text')
.filter(d => d === this.selectedData)
.remove();
// Hide property panel
document.getElementById('propertyPanel').style.display = 'none';
// Update simulation
this.simulation.force('link').links(this.edges);
this.simulation.alpha(1).restart();
// Generate updated Mermaid code
this.generateMermaidCode();
}
generateMermaidCode() {
console.log('Generating Mermaid code from D3 data...');
// Save current positions before generating code
this.saveCurrentPositions();
// Convert D3 data back to parser format
const nodes = new Map();
this.nodes.forEach(node => {
nodes.set(node.id, {
id: node.id,
text: node.text,
shape: node.shape,
style: node.style,
position: { x: node.fx || node.x, y: node.fy || node.y }
});
});
const edges = this.edges.map(edge => ({
from: edge.source.id || edge.source,
to: edge.target.id || edge.target,
type: edge.type,
label: edge.label || '',
style: edge.style
}));
// Use existing parser to generate code
const newCode = this.parser.generateCode(
nodes,
edges,
'flowchart',
'TD',
new Map() // No subgraphs for now
);
// Update textarea
document.getElementById('mermaidInput').value = newCode;
console.log('Generated Mermaid code:', newCode);
console.log('Positions preserved in localStorage');
}
// Export complete project (Mermaid code + positions + styling)
exportProject() {
console.log('Exporting complete project...');
// Make sure positions are saved
this.saveCurrentPositions();
const projectData = {
version: "1.0",
exportedAt: new Date().toISOString(),
mermaidCode: document.getElementById('mermaidInput').value,
d3Data: {
nodes: this.nodes.map(node => ({
id: node.id,
text: node.text,
shape: node.shape,
style: node.style,
position: {
x: node.x,
y: node.y,
fx: node.fx,
fy: node.fy
}
})),
edges: this.edges.map(edge => ({
source: edge.source.id || edge.source,
target: edge.target.id || edge.target,
label: edge.label,
type: edge.type,
style: edge.style
}))
},
renderSettings: {
width: this.width,
height: this.height,
renderer: 'D3'
},
metadata: {
title: `Mermaid Project ${new Date().toLocaleDateString()}`,
description: "Exported from Interactive Mermaid Editor with D3 positioning"
}
};
const blob = new Blob([JSON.stringify(projectData, null, 2)],
{ type: 'application/json' });
const url = URL.createObjectURL(blob);
const downloadLink = document.createElement('a');
downloadLink.download = `mermaid-project-${new Date().toISOString().slice(0,10)}.json`;
downloadLink.href = url;
downloadLink.click();
URL.revokeObjectURL(url);
console.log('Project exported:', projectData);
return projectData;
}
// Import complete project and restore exact layout
importProject(projectData) {
console.log('Importing project:', projectData);
try {
// Validate project data
if (!projectData.mermaidCode || !projectData.d3Data) {
throw new Error('Invalid project file format');
}
// Restore Mermaid code
document.getElementById('mermaidInput').value = projectData.mermaidCode;
// Restore D3 data
this.nodes = projectData.d3Data.nodes.map(node => ({
id: node.id,
text: node.text,
shape: node.shape,
style: node.style,
x: node.position.x,
y: node.position.y,
fx: node.position.fx,
fy: node.position.fy
}));
this.edges = projectData.d3Data.edges.map(edge => ({
source: edge.source,
target: edge.target,
label: edge.label,
type: edge.type,
style: edge.style
}));
// Save positions to localStorage
const positions = {};
this.nodes.forEach(node => {
positions[node.id] = {
x: node.x,
y: node.y,
fx: node.fx,
fy: node.fy
};
});
localStorage.setItem('d3_node_positions', JSON.stringify(positions));
// Render with restored data
this.renderFromImportedData();
// Show D3-specific buttons since we're now in D3 mode
document.getElementById('clearPositionsBtn').style.display = 'inline-block';
document.getElementById('exportProjectBtn').style.display = 'inline-block';
console.log('Project imported successfully');
alert(`Project imported successfully!\nTitle: ${projectData.metadata?.title}\nExported: ${new Date(projectData.exportedAt).toLocaleString()}\n\nThe diagram has been rendered with D3 to preserve exact positioning.`);
} catch (error) {
console.error('Error importing project:', error);
alert(`Error importing project: ${error.message}`);
}
}
// Special render method for imported data (skips parsing)
renderFromImportedData() {
console.log('Rendering from imported data...');
// Clear previous render
d3.select('#mermaidOutput').selectAll('*').remove();
// Create SVG
this.svg = d3.select('#mermaidOutput')
.append('svg')
.attr('width', this.width)
.attr('height', this.height)
.attr('viewBox', `0 0 ${this.width} ${this.height}`)
.style('border', '1px solid #ccc');
// Create arrow marker
this.svg.append('defs').append('marker')
.attr('id', 'arrowhead')
.attr('viewBox', '0 -5 10 10')
.attr('refX', 20)
.attr('refY', 0)
.attr('markerWidth', 8)
.attr('markerHeight', 8)
.attr('orient', 'auto')
.append('path')
.attr('d', 'M0,-5L10,0L0,5')
.attr('fill', '#000');
// Create force simulation with imported positions
this.simulation = d3.forceSimulation(this.nodes)
.force('link', d3.forceLink(this.edges).id(d => d.id).distance(150))
.force('charge', d3.forceManyBody().strength(-300))
.force('center', d3.forceCenter(this.width / 2, this.height / 2))
.force('collision', d3.forceCollide().radius(60))
.alpha(0); // Start with low energy to preserve imported positions
// Draw edges and nodes
this.drawEdges();
this.drawNodes();
console.log('Imported data rendered successfully');
}
}
// Initialize both renderers
const mermaidEditor = new MermaidEditor();
const d3Renderer = new D3Renderer();
// Initialize everything when DOM is ready
document.addEventListener('DOMContentLoaded', function() {