|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>3D ggplot2 Simulator</title> |
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script> |
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/PapaParse/5.3.0/papaparse.min.js"></script> |
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
|
<style> |
|
|
.plot-container { |
|
|
transition: all 0.3s ease; |
|
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); |
|
|
} |
|
|
.plot-container:hover { |
|
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); |
|
|
} |
|
|
.theme-btn { |
|
|
transition: all 0.2s ease; |
|
|
} |
|
|
.theme-btn:hover { |
|
|
transform: scale(1.05); |
|
|
} |
|
|
.control-panel { |
|
|
backdrop-filter: blur(10px); |
|
|
background-color: rgba(255, 255, 255, 0.85); |
|
|
} |
|
|
@media (max-width: 768px) { |
|
|
.control-panel { |
|
|
width: 100%; |
|
|
margin-left: 0; |
|
|
} |
|
|
} |
|
|
.file-upload-label { |
|
|
transition: all 0.3s ease; |
|
|
} |
|
|
.file-upload-label:hover { |
|
|
background-color: #4f46e5; |
|
|
color: white; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body class="bg-gray-50 min-h-screen"> |
|
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
|
|
<header class="mb-8 text-center"> |
|
|
<h1 class="text-4xl font-bold text-indigo-800 mb-2">3D ggplot2 Simulator</h1> |
|
|
<p class="text-lg text-gray-600">Create beautiful 3D visualizations with ggplot2-inspired aesthetics</p> |
|
|
</header> |
|
|
|
|
|
<div class="flex flex-col lg:flex-row gap-8"> |
|
|
|
|
|
<div class="flex-1"> |
|
|
<div class="bg-white rounded-xl p-4 plot-container" id="plot-container"> |
|
|
<div id="3d-plot" class="w-full h-[500px]"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mt-6 bg-white rounded-xl p-6 shadow-sm"> |
|
|
<h3 class="text-xl font-semibold text-gray-800 mb-4">Try Sample Datasets</h3> |
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4"> |
|
|
<button onclick="loadSampleData('iris')" class="bg-indigo-100 hover:bg-indigo-200 text-indigo-800 py-3 px-4 rounded-lg flex items-center justify-center transition-colors"> |
|
|
<i class="fas fa-seedling mr-2"></i> Iris Dataset |
|
|
</button> |
|
|
<button onclick="loadSampleData('mtcars')" class="bg-indigo-100 hover:bg-indigo-200 text-indigo-800 py-3 px-4 rounded-lg flex items-center justify-center transition-colors"> |
|
|
<i class="fas fa-car mr-2"></i> mtcars Dataset |
|
|
</button> |
|
|
<button onclick="loadSampleData('random')" class="bg-indigo-100 hover:bg-indigo-200 text-indigo-800 py-3 px-4 rounded-lg flex items-center justify-center transition-colors"> |
|
|
<i class="fas fa-random mr-2"></i> Random Data |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="lg:w-80 control-panel p-6 rounded-xl shadow-sm sticky top-4"> |
|
|
<h2 class="text-xl font-bold text-gray-800 mb-6 flex items-center"> |
|
|
<i class="fas fa-sliders-h mr-2 text-indigo-600"></i> Controls |
|
|
</h2> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">Upload Your Data (CSV)</label> |
|
|
<label for="file-upload" class="file-upload-label cursor-pointer flex items-center justify-center border-2 border-dashed border-indigo-300 rounded-lg py-3 px-4 text-indigo-600 hover:bg-indigo-50"> |
|
|
<i class="fas fa-cloud-upload-alt mr-2"></i> |
|
|
<span>Choose File</span> |
|
|
<input id="file-upload" type="file" class="hidden" accept=".csv" onchange="handleFileUpload(event)"> |
|
|
</label> |
|
|
<div id="file-name" class="text-sm text-gray-500 mt-2"></div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<h3 class="text-sm font-medium text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-table mr-2"></i> Variable Mapping |
|
|
</h3> |
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">X Axis</label> |
|
|
<select id="x-axis" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 border"> |
|
|
<option value="">Select variable</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Y Axis</label> |
|
|
<select id="y-axis" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 border"> |
|
|
<option value="">Select variable</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Z Axis</label> |
|
|
<select id="z-axis" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 border"> |
|
|
<option value="">Select variable</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Color By</label> |
|
|
<select id="color-by" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 border"> |
|
|
<option value="">None (single color)</option> |
|
|
</select> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<h3 class="text-sm font-medium text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-paint-brush mr-2"></i> Plot Customization |
|
|
</h3> |
|
|
<div class="space-y-4"> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Plot Type</label> |
|
|
<select id="plot-type" class="w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3 border"> |
|
|
<option value="scatter">Scatter Plot</option> |
|
|
<option value="surface">Surface Plot</option> |
|
|
<option value="line">Line Plot</option> |
|
|
</select> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Point Size</label> |
|
|
<input id="point-size" type="range" min="1" max="20" value="5" class="w-full"> |
|
|
</div> |
|
|
<div> |
|
|
<label class="block text-xs font-medium text-gray-500 mb-1">Opacity</label> |
|
|
<input id="opacity" type="range" min="0.1" max="1" step="0.1" value="0.8" class="w-full"> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="mb-6"> |
|
|
<h3 class="text-sm font-medium text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-palette mr-2"></i> Color Theme |
|
|
</h3> |
|
|
<div class="grid grid-cols-3 gap-2"> |
|
|
<button onclick="setTheme('ggplot')" class="theme-btn bg-white border border-gray-200 rounded-md p-2 hover:border-indigo-400"> |
|
|
<div class="h-4 w-full bg-gradient-to-r from-blue-400 via-green-500 to-red-500 rounded-sm mb-1"></div> |
|
|
<span class="text-xs">ggplot</span> |
|
|
</button> |
|
|
<button onclick="setTheme('viridis')" class="theme-btn bg-white border border-gray-200 rounded-md p-2 hover:border-indigo-400"> |
|
|
<div class="h-4 w-full bg-gradient-to-r from-purple-500 via-teal-500 to-yellow-500 rounded-sm mb-1"></div> |
|
|
<span class="text-xs">Viridis</span> |
|
|
</button> |
|
|
<button onclick="setTheme('plasma')" class="theme-btn bg-white border border-gray-200 rounded-md p-2 hover:border-indigo-400"> |
|
|
<div class="h-4 w-full bg-gradient-to-r from-purple-700 via-pink-500 to-yellow-300 rounded-sm mb-1"></div> |
|
|
<span class="text-xs">Plasma</span> |
|
|
</button> |
|
|
<button onclick="setTheme('magma')" class="theme-btn bg-white border border-gray-200 rounded-md p-2 hover:border-indigo-400"> |
|
|
<div class="h-4 w-full bg-gradient-to-r from-black via-red-900 to-yellow-200 rounded-sm mb-1"></div> |
|
|
<span class="text-xs">Magma</span> |
|
|
</button> |
|
|
<button onclick="setTheme('cividis')" class="theme-btn bg-white border border-gray-200 rounded-md p-2 hover:border-indigo-400"> |
|
|
<div class="h-4 w-full bg-gradient-to-r from-blue-900 via-yellow-300 to-yellow-100 rounded-sm mb-1"></div> |
|
|
<span class="text-xs">Cividis</span> |
|
|
</button> |
|
|
<button onclick="setTheme('custom')" class="theme-btn bg-white border border-gray-200 rounded-md p-2 hover:border-indigo-400"> |
|
|
<div class="h-4 w-full bg-gradient-to-r from-indigo-500 via-purple-500 to-pink-500 rounded-sm mb-1"></div> |
|
|
<span class="text-xs">Custom</span> |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<button onclick="updatePlot()" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white py-2 px-4 rounded-md flex items-center justify-center transition-colors"> |
|
|
<i class="fas fa-sync-alt mr-2"></i> Update Visualization |
|
|
</button> |
|
|
|
|
|
|
|
|
<div class="mt-6 pt-4 border-t border-gray-200"> |
|
|
<h3 class="text-sm font-medium text-gray-700 mb-3 flex items-center"> |
|
|
<i class="fas fa-download mr-2"></i> Export |
|
|
</h3> |
|
|
<div class="grid grid-cols-2 gap-2"> |
|
|
<button onclick="exportPNG()" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded-md text-sm flex items-center justify-center"> |
|
|
<i class="fas fa-image mr-2"></i> PNG |
|
|
</button> |
|
|
<button onclick="exportSVG()" class="bg-gray-100 hover:bg-gray-200 text-gray-800 py-2 px-3 rounded-md text-sm flex items-center justify-center"> |
|
|
<i class="fas fa-file-image mr-2"></i> SVG |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<script> |
|
|
|
|
|
let currentData = []; |
|
|
let currentColumns = []; |
|
|
let currentTheme = 'ggplot'; |
|
|
|
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { |
|
|
loadSampleData('iris'); |
|
|
}); |
|
|
|
|
|
|
|
|
function loadSampleData(dataset) { |
|
|
let data, columns; |
|
|
|
|
|
switch(dataset) { |
|
|
case 'iris': |
|
|
data = [ |
|
|
{sepal_length: 5.1, sepal_width: 3.5, petal_length: 1.4, petal_width: 0.2, species: 'setosa'}, |
|
|
{sepal_length: 4.9, sepal_width: 3.0, petal_length: 1.4, petal_width: 0.2, species: 'setosa'}, |
|
|
{sepal_length: 4.7, sepal_width: 3.2, petal_length: 1.3, petal_width: 0.2, species: 'setosa'}, |
|
|
{sepal_length: 6.3, sepal_width: 3.3, petal_length: 6.0, petal_width: 2.5, species: 'virginica'}, |
|
|
{sepal_length: 5.8, sepal_width: 2.7, petal_length: 5.1, petal_width: 1.9, species: 'virginica'}, |
|
|
{sepal_length: 7.1, sepal_width: 3.0, petal_length: 5.9, petal_width: 2.1, species: 'virginica'}, |
|
|
{sepal_length: 6.3, sepal_width: 2.9, petal_length: 5.6, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.5, sepal_width: 3.0, petal_length: 5.8, petal_width: 2.2, species: 'virginica'}, |
|
|
{sepal_length: 7.6, sepal_width: 3.0, petal_length: 6.6, petal_width: 2.1, species: 'virginica'}, |
|
|
{sepal_length: 4.9, sepal_width: 2.5, petal_length: 4.5, petal_width: 1.7, species: 'versicolor'}, |
|
|
{sepal_length: 7.3, sepal_width: 2.9, petal_length: 6.3, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.7, sepal_width: 2.5, petal_length: 5.8, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 7.2, sepal_width: 3.6, petal_length: 6.1, petal_width: 2.5, species: 'virginica'}, |
|
|
{sepal_length: 6.5, sepal_width: 3.2, petal_length: 5.1, petal_width: 2.0, species: 'virginica'}, |
|
|
{sepal_length: 6.4, sepal_width: 2.7, petal_length: 5.3, petal_width: 1.9, species: 'virginica'}, |
|
|
{sepal_length: 6.8, sepal_width: 3.0, petal_length: 5.5, petal_width: 2.1, species: 'virginica'}, |
|
|
{sepal_length: 5.7, sepal_width: 2.5, petal_length: 5.0, petal_width: 2.0, species: 'virginica'}, |
|
|
{sepal_length: 5.8, sepal_width: 2.8, petal_length: 5.1, petal_width: 2.4, species: 'virginica'}, |
|
|
{sepal_length: 6.4, sepal_width: 3.2, petal_length: 5.3, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 6.5, sepal_width: 3.0, petal_length: 5.5, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 7.7, sepal_width: 3.8, petal_length: 6.7, petal_width: 2.2, species: 'virginica'}, |
|
|
{sepal_length: 7.7, sepal_width: 2.6, petal_length: 6.9, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 6.0, sepal_width: 2.2, petal_length: 5.0, petal_width: 1.5, species: 'virginica'}, |
|
|
{sepal_length: 6.9, sepal_width: 3.2, petal_length: 5.7, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 5.6, sepal_width: 2.8, petal_length: 4.9, petal_width: 2.0, species: 'virginica'}, |
|
|
{sepal_length: 7.7, sepal_width: 2.8, petal_length: 6.7, petal_width: 2.0, species: 'virginica'}, |
|
|
{sepal_length: 6.3, sepal_width: 2.7, petal_length: 4.9, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.7, sepal_width: 3.3, petal_length: 5.7, petal_width: 2.1, species: 'virginica'}, |
|
|
{sepal_length: 7.2, sepal_width: 3.2, petal_length: 6.0, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.2, sepal_width: 2.8, petal_length: 4.8, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.1, sepal_width: 3.0, petal_length: 4.9, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.4, sepal_width: 2.8, petal_length: 5.6, petal_width: 2.1, species: 'virginica'}, |
|
|
{sepal_length: 7.2, sepal_width: 3.0, petal_length: 5.8, petal_width: 1.6, species: 'virginica'}, |
|
|
{sepal_length: 7.4, sepal_width: 2.8, petal_length: 6.1, petal_width: 1.9, species: 'virginica'}, |
|
|
{sepal_length: 7.9, sepal_width: 3.8, petal_length: 6.4, petal_width: 2.0, species: 'virginica'}, |
|
|
{sepal_length: 6.4, sepal_width: 2.8, petal_length: 5.6, petal_width: 2.2, species: 'virginica'}, |
|
|
{sepal_length: 6.3, sepal_width: 2.8, petal_length: 5.1, petal_width: 1.5, species: 'virginica'}, |
|
|
{sepal_length: 6.1, sepal_width: 2.6, petal_length: 5.6, petal_width: 1.4, species: 'virginica'}, |
|
|
{sepal_length: 7.7, sepal_width: 3.0, petal_length: 6.1, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 6.3, sepal_width: 3.4, petal_length: 5.6, petal_width: 2.4, species: 'virginica'}, |
|
|
{sepal_length: 6.4, sepal_width: 3.1, petal_length: 5.5, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.0, sepal_width: 3.0, petal_length: 4.8, petal_width: 1.8, species: 'virginica'}, |
|
|
{sepal_length: 6.9, sepal_width: 3.1, petal_length: 5.4, petal_width: 2.1, species: 'virginica'}, |
|
|
{sepal_length: 6.7, sepal_width: 3.1, petal_length: 5.6, petal_width: 2.4, species: 'virginica'}, |
|
|
{sepal_length: 6.9, sepal_width: 3.1, petal_length: 5.1, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 5.8, sepal_width: 2.7, petal_length: 5.1, petal_width: 1.9, species: 'virginica'}, |
|
|
{sepal_length: 6.8, sepal_width: 3.2, petal_length: 5.9, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 6.7, sepal_width: 3.3, petal_length: 5.7, petal_width: 2.5, species: 'virginica'}, |
|
|
{sepal_length: 6.7, sepal_width: 3.0, petal_length: 5.2, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 6.3, sepal_width: 2.5, petal_length: 5.0, petal_width: 1.9, species: 'virginica'}, |
|
|
{sepal_length: 6.5, sepal_width: 3.0, petal_length: 5.2, petal_width: 2.0, species: 'virginica'}, |
|
|
{sepal_length: 6.2, sepal_width: 3.4, petal_length: 5.4, petal_width: 2.3, species: 'virginica'}, |
|
|
{sepal_length: 5.9, sepal_width: 3.0, petal_length: 5.1, petal_width: 1.8, species: 'virginica'} |
|
|
]; |
|
|
columns = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']; |
|
|
break; |
|
|
|
|
|
case 'mtcars': |
|
|
data = [ |
|
|
{mpg: 21.0, cyl: 6, disp: 160, hp: 110, drat: 3.90, wt: 2.620, qsec: 16.46, vs: 0, am: 1, gear: 4, carb: 4, model: "Mazda RX4"}, |
|
|
{mpg: 21.0, cyl: 6, disp: 160, hp: 110, drat: 3.90, wt: 2.875, qsec: 17.02, vs: 0, am: 1, gear: 4, carb: 4, model: "Mazda RX4 Wag"}, |
|
|
{mpg: 22.8, cyl: 4, disp: 108, hp: 93, drat: 3.85, wt: 2.320, qsec: 18.61, vs: 1, am: 1, gear: 4, carb: 1, model: "Datsun 710"}, |
|
|
{mpg: 21.4, cyl: 6, disp: 258, hp: 110, drat: 3.08, wt: 3.215, qsec: 19.44, vs: 1, am: 0, gear: 3, carb: 1, model: "Hornet 4 Drive"}, |
|
|
{mpg: 18.7, cyl: 8, disp: 360, hp: 175, drat: 3.15, wt: 3.440, qsec: 17.02, vs: 0, am: 0, gear: 3, carb: 2, model: "Hornet Sportabout"}, |
|
|
{mpg: 18.1, cyl: 6, disp: 225, hp: 105, drat: 2.76, wt: 3.460, qsec: 20.22, vs: 1, am: 0, gear: 3, carb: 1, model: "Valiant"}, |
|
|
{mpg: 14.3, cyl: 8, disp: 360, hp: 245, drat: 3.21, wt: 3.570, qsec: 15.84, vs: 0, am: 0, gear: 3, carb: 4, model: "Duster 360"}, |
|
|
{mpg: 24.4, cyl: 4, disp: 146.7, hp: 62, drat: 3.69, wt: 3.190, qsec: 20.00, vs: 1, am: 0, gear: 4, carb: 2, model: "Merc 240D"}, |
|
|
{mpg: 22.8, cyl: 4, disp: 140.8, hp: 95, drat: 3.92, wt: 3.150, qsec: 22.90, vs: 1, am: 0, gear: 4, carb: 2, model: "Merc 230"}, |
|
|
{mpg: 19.2, cyl: 6, disp: 167.6, hp: 123, drat: 3.92, wt: 3.440, qsec: 18.30, vs: 1, am: 0, gear: 4, carb: 4, model: "Merc 280"}, |
|
|
{mpg: 17.8, cyl: 6, disp: 167.6, hp: 123, drat: 3.92, wt: 3.440, qsec: 18.90, vs: 1, am: 0, gear: 4, carb: 4, model: "Merc 280C"}, |
|
|
{mpg: 16.4, cyl: 8, disp: 275.8, hp: 180, drat: 3.07, wt: 4.070, qsec: 17.40, vs: 0, am: 0, gear: 3, carb: 3, model: "Merc 450SE"}, |
|
|
{mpg: 17.3, cyl: 8, disp: 275.8, hp: 180, drat: 3.07, wt: 3.730, qsec: 17.60, vs: 0, am: 0, gear: 3, carb: 3, model: "Merc 450SL"}, |
|
|
{mpg: 15.2, cyl: 8, disp: 275.8, hp: 180, drat: 3.07, wt: 3.780, qsec: 18.00, vs: 0, am: 0, gear: 3, carb: 3, model: "Merc 450SLC"}, |
|
|
{mpg: 10.4, cyl: 8, disp: 472, hp: 205, drat: 2.93, wt: 5.250, qsec: 17.98, vs: 0, am: 0, gear: 3, carb: 4, model: "Cadillac Fleetwood"}, |
|
|
{mpg: 10.4, cyl: 8, disp: 460, hp: 215, drat: 3.00, wt: 5.424, qsec: 17.82, vs: 0, am: 0, gear: 3, carb: 4, model: "Lincoln Continental"}, |
|
|
{mpg: 14.7, cyl: 8, disp: 440, hp: 230, drat: 3.23, wt: 5.345, qsec: 17.42, vs: 0, am: 0, gear: 3, carb: 4, model: "Chrysler Imperial"}, |
|
|
{mpg: 32.4, cyl: 4, disp: 78.7, hp: 66, drat: 4.08, wt: 2.200, qsec: 19.47, vs: 1, am: 1, gear: 4, carb: 1, model: "Fiat 128"}, |
|
|
{mpg: 30.4, cyl: 4, disp: 75.7, hp: 52, drat: 4.93, wt: 1.615, qsec: 18.52, vs: 1, am: 1, gear: 4, carb: 2, model: "Honda Civic"}, |
|
|
{mpg: 33.9, cyl: 4, disp: 71.1, hp: 65, drat: 4.22, wt: 1.835, qsec: 19.90, vs: 1, am: 1, gear: 4, carb: 1, model: "Toyota Corolla"}, |
|
|
{mpg: 21.5, cyl: 4, disp: 120.1, hp: 97, drat: 3.70, wt: 2.465, qsec: 20.01, vs: 1, am: 0, gear: 3, carb: 1, model: "Toyota Corona"}, |
|
|
{mpg: 15.5, cyl: 8, disp: 318, hp: 150, drat: 2.76, wt: 3.520, qsec: 16.87, vs: 0, am: 0, gear: 3, carb: 2, model: "Dodge Challenger"}, |
|
|
{mpg: 15.2, cyl: 8, disp: 304, hp: 150, drat: 3.15, wt: 3.435, qsec: 17.30, vs: 0, am: 0, gear: 3, carb: 2, model: "AMC Javelin"}, |
|
|
{mpg: 13.3, cyl: 8, disp: 350, hp: 245, drat: 3.73, wt: 3.840, qsec: 15.41, vs: 0, am: 0, gear: 3, carb: 4, model: "Camaro Z28"}, |
|
|
{mpg: 19.2, cyl: 8, disp: 400, hp: 175, drat: 3.08, wt: 3.845, qsec: 17.05, vs: 0, am: 0, gear: 3, carb: 2, model: "Pontiac Firebird"}, |
|
|
{mpg: 27.3, cyl: 4, disp: 79, hp: 66, drat: 4.08, wt: 1.935, qsec: 18.90, vs: 1, am: 1, gear: 4, carb: 1, model: "Fiat X1-9"}, |
|
|
{mpg: 26, cyl: 4, disp: 120.3, hp: 91, drat: 4.43, wt: 2.140, qsec: 16.70, vs: 0, am: 1, gear: 5, carb: 2, model: "Porsche 914-2"}, |
|
|
{mpg: 30.4, cyl: 4, disp: 95.1, hp: 113, drat: 3.77, wt: 1.513, qsec: 16.90, vs: 1, am: 1, gear: 5, carb: 2, model: "Lotus Europa"}, |
|
|
{mpg: 15.8, cyl: 8, disp: 351, hp: 264, drat: 4.22, wt: 3.170, qsec: 14.50, vs: 0, am: 1, gear: 5, carb: 4, model: "Ford Pantera L"}, |
|
|
{mpg: 19.7, cyl: 6, disp: 145, hp: 175, drat: 3.62, wt: 2.770, qsec: 15.50, vs: 0, am: 1, gear: 5, carb: 6, model: "Ferrari Dino"}, |
|
|
{mpg: 15, cyl: 8, disp: 301, hp: 335, drat: 3.54, wt: 3.570, qsec: 14.60, vs: 0, am: 1, gear: 5, carb: 8, model: "Maserati Bora"}, |
|
|
{mpg: 21.4, cyl: 4, disp: 121, hp: 109, drat: 4.11, wt: 2.780, qsec: 18.60, vs: 1, am: 1, gear: 4, carb: 2, model: "Volvo 142E"} |
|
|
]; |
|
|
columns = ['mpg', 'cyl', 'disp', 'hp', 'drat', 'wt', 'qsec', 'vs', 'am', 'gear', 'carb', 'model']; |
|
|
break; |
|
|
|
|
|
case 'random': |
|
|
data = []; |
|
|
columns = ['x', 'y', 'z', 'category']; |
|
|
for (let i = 0; i < 100; i++) { |
|
|
data.push({ |
|
|
x: Math.random() * 10, |
|
|
y: Math.random() * 10, |
|
|
z: Math.random() * 10, |
|
|
category: Math.random() > 0.5 ? 'A' : 'B' |
|
|
}); |
|
|
} |
|
|
break; |
|
|
} |
|
|
|
|
|
currentData = data; |
|
|
currentColumns = columns; |
|
|
updateColumnSelectors(); |
|
|
updatePlot(); |
|
|
|
|
|
|
|
|
document.getElementById('file-name').textContent = `Loaded: ${dataset} dataset`; |
|
|
} |
|
|
|
|
|
|
|
|
function handleFileUpload(event) { |
|
|
const file = event.target.files[0]; |
|
|
if (!file) return; |
|
|
|
|
|
document.getElementById('file-name').textContent = `Uploading: ${file.name}`; |
|
|
|
|
|
Papa.parse(file, { |
|
|
header: true, |
|
|
complete: function(results) { |
|
|
currentData = results.data; |
|
|
currentColumns = results.meta.fields; |
|
|
updateColumnSelectors(); |
|
|
updatePlot(); |
|
|
|
|
|
document.getElementById('file-name').textContent = `Loaded: ${file.name}`; |
|
|
}, |
|
|
error: function(error) { |
|
|
console.error("Error parsing CSV:", error); |
|
|
document.getElementById('file-name').textContent = `Error loading file`; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateColumnSelectors() { |
|
|
const xAxis = document.getElementById('x-axis'); |
|
|
const yAxis = document.getElementById('y-axis'); |
|
|
const zAxis = document.getElementById('z-axis'); |
|
|
const colorBy = document.getElementById('color-by'); |
|
|
|
|
|
|
|
|
xAxis.innerHTML = '<option value="">Select variable</option>'; |
|
|
yAxis.innerHTML = '<option value="">Select variable</option>'; |
|
|
zAxis.innerHTML = '<option value="">Select variable</option>'; |
|
|
colorBy.innerHTML = '<option value="">None (single color)</option>'; |
|
|
|
|
|
|
|
|
currentColumns.forEach(col => { |
|
|
const option = document.createElement('option'); |
|
|
option.value = col; |
|
|
option.textContent = col; |
|
|
|
|
|
xAxis.appendChild(option.cloneNode(true)); |
|
|
yAxis.appendChild(option.cloneNode(true)); |
|
|
zAxis.appendChild(option.cloneNode(true)); |
|
|
colorBy.appendChild(option.cloneNode(true)); |
|
|
}); |
|
|
|
|
|
|
|
|
if (currentColumns.includes('x') || currentColumns.includes('X')) { |
|
|
xAxis.value = currentColumns.includes('x') ? 'x' : 'X'; |
|
|
} |
|
|
if (currentColumns.includes('y') || currentColumns.includes('Y')) { |
|
|
yAxis.value = currentColumns.includes('y') ? 'y' : 'Y'; |
|
|
} |
|
|
if (currentColumns.includes('z') || currentColumns.includes('Z')) { |
|
|
zAxis.value = currentColumns.includes('z') ? 'z' : 'Z'; |
|
|
} |
|
|
if (currentColumns.includes('category') || currentColumns.includes('group')) { |
|
|
colorBy.value = currentColumns.includes('category') ? 'category' : 'group'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function setTheme(theme) { |
|
|
currentTheme = theme; |
|
|
updatePlot(); |
|
|
} |
|
|
|
|
|
|
|
|
function getColorScale() { |
|
|
switch(currentTheme) { |
|
|
case 'ggplot': |
|
|
return ['#619CFF', '#00BA38', '#F8766D']; |
|
|
case 'viridis': |
|
|
return ['#440154', '#3B528B', '#21908C', '#5DC963', '#FDE725']; |
|
|
case 'plasma': |
|
|
return ['#0D0887', '#7E03A8', '#CC4678', '#F89441', '#F0F921']; |
|
|
case 'magma': |
|
|
return ['#000004', '#3B0F70', '#8C2981', '#DE4968', '#FE9F6D', '#FCFDBF']; |
|
|
case 'cividis': |
|
|
return ['#00204D', '#31446B', '#666870', '#958F78', '#D1BC5F', '#FDE333']; |
|
|
case 'custom': |
|
|
return ['#6366F1', '#8B5CF6', '#EC4899']; |
|
|
default: |
|
|
return ['#6366F1', '#8B5CF6', '#EC4899']; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function updatePlot() { |
|
|
const xAxis = document.getElementById('x-axis').value; |
|
|
const yAxis = document.getElementById('y-axis').value; |
|
|
const zAxis = document.getElementById('z-axis').value; |
|
|
const colorBy = document.getElementById('color-by').value; |
|
|
const plotType = document.getElementById('plot-type').value; |
|
|
const pointSize = document.getElementById('point-size').value; |
|
|
const opacity = document.getElementById('opacity').value; |
|
|
|
|
|
if (!xAxis || !yAxis || !zAxis) { |
|
|
alert("Please select X, Y, and Z axes variables"); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
let traces = []; |
|
|
const colorScale = getColorScale(); |
|
|
|
|
|
if (colorBy && currentColumns.includes(colorBy)) { |
|
|
|
|
|
const groups = {}; |
|
|
currentData.forEach(row => { |
|
|
const groupValue = row[colorBy]; |
|
|
if (!groups[groupValue]) { |
|
|
groups[groupValue] = { |
|
|
x: [], |
|
|
y: [], |
|
|
z: [], |
|
|
text: [] |
|
|
}; |
|
|
} |
|
|
groups[groupValue].x.push(row[xAxis]); |
|
|
groups[groupValue].y.push(row[yAxis]); |
|
|
groups[groupValue].z.push(row[zAxis]); |
|
|
groups[groupValue].text.push(Object.entries(row).map(([key, val]) => `${key}: ${val}`).join('<br>')); |
|
|
}); |
|
|
|
|
|
|
|
|
const groupValues = Object.keys(groups); |
|
|
groupValues.forEach((group, i) => { |
|
|
const colorIndex = i % colorScale.length; |
|
|
const trace = { |
|
|
x: groups[group].x, |
|
|
y: groups[group].y, |
|
|
z: groups[group].z, |
|
|
text: groups[group].text, |
|
|
name: group, |
|
|
type: plotType, |
|
|
mode: plotType === 'scatter' ? 'markers' : 'lines', |
|
|
marker: { |
|
|
size: plotType === 'scatter' ? pointSize : undefined, |
|
|
color: colorScale[colorIndex], |
|
|
opacity: opacity |
|
|
}, |
|
|
line: plotType !== 'scatter' ? { |
|
|
color: colorScale[colorIndex], |
|
|
width: pointSize / 2 |
|
|
} : undefined, |
|
|
hoverinfo: 'text' |
|
|
}; |
|
|
traces.push(trace); |
|
|
}); |
|
|
} else { |
|
|
|
|
|
const x = currentData.map(row => row[xAxis]); |
|
|
const y = currentData.map(row => row[yAxis]); |
|
|
const z = currentData.map(row => row[zAxis]); |
|
|
const text = currentData.map(row => Object.entries(row).map(([key, val]) => `${key}: ${val}`).join('<br>')); |
|
|
|
|
|
const trace = { |
|
|
x: x, |
|
|
y: y, |
|
|
z: z, |
|
|
text: text, |
|
|
type: plotType, |
|
|
mode: plotType === 'scatter' ? 'markers' : 'lines', |
|
|
marker: { |
|
|
size: plotType === 'scatter' ? pointSize : undefined, |
|
|
color: colorScale[0], |
|
|
opacity: opacity |
|
|
}, |
|
|
line: plotType !== 'scatter' ? { |
|
|
color: colorScale[0], |
|
|
width: pointSize / 2 |
|
|
} : undefined, |
|
|
hoverinfo: 'text' |
|
|
}; |
|
|
traces.push(trace); |
|
|
} |
|
|
|
|
|
|
|
|
const layout = { |
|
|
title: '3D Visualization', |
|
|
scene: { |
|
|
xaxis: { title: xAxis, gridcolor: 'rgb(200, 200, 200)', backgroundcolor: 'rgb(240, 240, 240)' }, |
|
|
yaxis: { title: yAxis, gridcolor: 'rgb(200, 200, 200)', backgroundcolor: 'rgb(240, 240, 240)' }, |
|
|
zaxis: { title: zAxis, gridcolor: 'rgb(200, 200, 200)', backgroundcolor: 'rgb(240, 240, 240)' }, |
|
|
bgcolor: 'rgb(255, 255, 255)' |
|
|
}, |
|
|
margin: { l: 0, r: 0, b: 0, t: 30 }, |
|
|
paper_bgcolor: 'rgba(0,0,0,0)', |
|
|
plot_bgcolor: 'rgba(0,0,0,0)', |
|
|
legend: { |
|
|
bgcolor: 'rgba(255, 255, 255, 0.8)', |
|
|
bordercolor: 'rgba(0, 0, 0, 0.2)', |
|
|
borderwidth: 1 |
|
|
} |
|
|
}; |
|
|
|
|
|
Plotly.newPlot('3d-plot', traces, layout, { responsive: true }); |
|
|
} |
|
|
|
|
|
|
|
|
function exportPNG() { |
|
|
Plotly.downloadImage('3d-plot', { |
|
|
format: 'png', |
|
|
width: 1200, |
|
|
height: 800, |
|
|
filename: '3d-ggplot-plot' |
|
|
}); |
|
|
} |
|
|
|
|
|
function exportSVG() { |
|
|
Plotly.downloadImage('3d-plot', { |
|
|
format: 'svg', |
|
|
width: 1200, |
|
|
height: 800, |
|
|
filename: '3d-ggplot-plot' |
|
|
}); |
|
|
} |
|
|
</script> |
|
|
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=guillermo2323/ggplotgrahs-like-app" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
|
</html> |