<!DOCTYPE html>
<html lang="en">
<head>
   <meta charset="UTF-8">
   <meta name="viewport" content="width=device-width, initial-scale=1.0">
   <title>2D & 3D Function Plotter</title>
   <!-- Tailwind CSS for styling -->
   <script src="https://cdn.tailwindcss.com"></script>
   <!-- three.js for 3D rendering -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
   <!-- math.js for formula parsing -->
   <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjs/11.5.0/math.js"></script>
   <!-- ES6 Import Map for three.js addons -->
   <script type="importmap">
       {
           "imports": {
               "three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.module.js",
               "three/addons/": "https://cdn.jsdelivr.net/npm/three@0.128.0/examples/jsm/"
           }
       }
   </script>
   <link rel="preconnect" href="https://fonts.googleapis.com">
   <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
   <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700&family=Fira+Code&display=swap" rel="stylesheet">
   <style>
       body {
           font-family: 'Inter', sans-serif;
       }
       .fira-code {
           font-family: 'Fira Code', monospace;
       }
       /* Style for formula inputs */
       input[type="text"].formula-input {
           background-color: #374151; /* bg-gray-700 */
           border: 1px solid #4b5563; /* border-gray-600 */
           color: white;
           border-radius: 0.375rem; /* rounded-md */
           padding: 0.5rem 0.75rem;
           width: 100%;
           transition: border-color 0.2s, box-shadow 0.2s;
       }
       input[type="text"].formula-input:focus {
           outline: none;
           border-color: #3b82f6; /* border-blue-500 */
           box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px #3b82f6;
       }
       input[type="text"].formula-input.border-red-500 {
            border-color: #ef4444; /* border-red-500 */
       }
        input[type="text"].formula-input.border-red-500:focus {
           box-shadow: 0 0 0 2px #1f2937, 0 0 0 4px #ef4444;
       }
   </style>
</head>
<body class="bg-gray-900 text-gray-200 flex flex-col items-center justify-center min-h-screen p-4">

   <!-- Main container for the graph and UI -->
   <div class="flex flex-col items-center">
       <!-- Graph display element (1000x1000) -->
       <div id="graph-container" class="w-[1000px] h-[1000px] bg-gray-800 rounded-t-lg shadow-2xl overflow-hidden relative border-2 border-gray-700">
           <!-- 2D Canvas, shown/hidden via JS -->
           <canvas id="canvas2d" class="absolute top-0 left-0 w-full h-full"></canvas>
           <!-- 3D Renderer will attach its canvas here -->
       </div>

       <!-- UI control element (1000x300) -->
       <div id="ui-container" class="w-[1000px] h-[300px] bg-gray-800/50 backdrop-blur-sm rounded-b-lg shadow-2xl p-6 flex flex-col justify-between border-x-2 border-b-2 border-gray-700">
           
           <!-- Top section: Mode Toggle and Title -->
           <div class="flex justify-between items-start">
               <div>
                   <h1 class="text-2xl font-bold text-white">Universal Grapher</h1>
                   <p class="text-gray-400">Enter a mathematical expression to plot.</p>
               </div>
               <div class="flex items-center gap-4 bg-gray-900 p-2 rounded-lg">
                   <label class="cursor-pointer">
                       <input type="radio" name="graphType" value="2d" class="sr-only peer" checked>
                       <span class="px-4 py-2 rounded-md text-gray-300 peer-checked:bg-blue-600 peer-checked:text-white transition-colors">2D Graph</span>
                   </label>
                   <label class="cursor-pointer">
                       <input type="radio" name="graphType" value="3d" class="sr-only peer">
                       <span class="px-4 py-2 rounded-md text-gray-300 peer-checked:bg-blue-600 peer-checked:text-white transition-colors">3D Graph</span>
                   </label>
               </div>
           </div>

           <!-- Middle section: Dynamic Controls -->
           <div class="flex-grow flex flex-col items-center justify-center gap-4">
               <!-- 2D Controls -->
               <div id="controls-2d" class="w-full flex items-center gap-4">
                   <span class="text-xl fira-code text-gray-400">y =</span>
                   <input id="formula-2d" type="text" class="formula-input fira-code text-lg" value="50 * cos(x / 20) + 30 * sin(x/10)">
               </div>

               <!-- 3D Controls (initially hidden) -->
               <div id="controls-3d" class="w-full hidden items-center gap-4">
                    <span class="text-xl fira-code text-gray-400">z =</span>
                   <input id="formula-3d" type="text" class="formula-input fira-code text-lg" value="10 * (sin(x/5) + cos(y/5))">
               </div>
               <p id="error-message" class="text-red-400 h-4"></p>
           </div>

           <!-- Bottom section: Plot Button -->
           <div class="flex justify-end">
               <button id="plot-button" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-2 px-6 rounded-lg transition-colors shadow-lg">
                   Plot Graph
               </button>
           </div>
       </div>
   </div>

   <script type="module">
       // Import necessary three.js modules
       import { OrbitControls } from 'three/addons/controls/OrbitControls.js';

       // --- State Management ---
       let currentMode = '2d';
       let scene, camera, renderer, controls, mesh3D; // 3D variables

       // --- DOM Element References ---
       const graphContainer = document.getElementById('graph-container');
       const canvas2d = document.getElementById('canvas2d');
       const ctx = canvas2d.getContext('2d');
       const errorMessage = document.getElementById('error-message');

       const radioButtons = document.querySelectorAll('input[name="graphType"]');
       const controls2d = document.getElementById('controls-2d');
       const controls3d = document.getElementById('controls-3d');
       const formula2DInput = document.getElementById('formula-2d');
       const formula3DInput = document.getElementById('formula-3d');
       const plotButton = document.getElementById('plot-button');

       // --- Initializer ---
       function initialize() {
           setupEventListeners();
           resizeCanvas();
           plotFunction(); 
       }

       // --- Event Setup ---
       function setupEventListeners() {
           radioButtons.forEach(radio => radio.addEventListener('change', handleModeChange));
           plotButton.addEventListener('click', plotFunction);
           window.addEventListener('resize', resizeCanvas);
           formula2DInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') plotFunction(); });
           formula3DInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') plotFunction(); });
       }
       
       // --- Mode Switching Logic ---
       function handleModeChange(event) {
           currentMode = event.target.value;
           errorMessage.textContent = '';
           formula2DInput.classList.remove('border-red-500');
           formula3DInput.classList.remove('border-red-500');

           if (currentMode === '2d') {
               controls2d.classList.remove('hidden');
               controls2d.classList.add('flex');
               controls3d.classList.add('hidden');
               controls3d.classList.remove('flex');

               if (renderer) renderer.domElement.style.display = 'none';
               canvas2d.style.display = 'block';
           } else { // 3D mode
               controls2d.classList.add('hidden');
               controls2d.classList.remove('flex');
               controls3d.classList.remove('hidden');
               controls3d.classList.add('flex');
               
               canvas2d.style.display = 'none';
               if (!renderer) {
                   init3D();
               }
               renderer.domElement.style.display = 'block';
           }
           plotFunction();
       }
       
       // --- General Plotting Handler ---
       function plotFunction() {
           errorMessage.textContent = '';
           if (currentMode === '2d') {
               plot2DFunction();
           } else {
               plot3DFunction();
           }
       }
       
       // --- Canvas & Renderer Sizing ---
       function resizeCanvas() {
           const rect = graphContainer.getBoundingClientRect();
           canvas2d.width = rect.width;
           canvas2d.height = rect.height;

           if (renderer) {
               camera.aspect = rect.width / rect.height;
               camera.updateProjectionMatrix();
               renderer.setSize(rect.width, rect.height);
           }
            if (currentMode === '2d') {
               plot2DFunction();
           }
       }

       // --- 2D Plotting ---
       function plot2DFunction() {
           const formula = formula2DInput.value;
           formula2DInput.classList.remove('border-red-500');
           
           let node, code;
           try {
               node = math.parse(formula);
               code = node.compile();
           } catch (err) {
               formula2DInput.classList.add('border-red-500');
               errorMessage.textContent = `Invalid 2D formula: ${err.message}`;
               return;
           }

           const width = canvas2d.width;
           const height = canvas2d.height;
           const originX = width / 2;
           const originY = height / 2;
           const scale = 1;

           ctx.clearRect(0, 0, width, height);
           ctx.fillStyle = "#1f2937";
           ctx.fillRect(0, 0, width, height);

           ctx.strokeStyle = '#4b5563';
           ctx.lineWidth = 1;
           ctx.beginPath();
           ctx.moveTo(0, originY);
           ctx.lineTo(width, originY);
           ctx.moveTo(originX, 0);
           ctx.lineTo(originX, height);
           ctx.stroke();

           ctx.strokeStyle = '#60a5fa';
           ctx.lineWidth = 2;
           ctx.beginPath();
           for (let i = 0; i < width; i++) {
               const x = (i - originX) / scale;
               const scope = { x: x };
               const y = code.evaluate(scope);
               
               if (isFinite(y)) {
                   ctx.lineTo(i, originY - y * scale);
               } else {
                   ctx.moveTo(i + 1, originY); // Move to next point if discontinuous
               }
           }
           ctx.stroke();
       }

       // --- 3D Scene Initialization ---
       function init3D() {
           const width = graphContainer.clientWidth;
           const height = graphContainer.clientHeight;
           
           scene = new THREE.Scene();
           scene.background = new THREE.Color(0x1f2937);

           camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
           camera.position.set(15, 15, 25);
           
           renderer = new THREE.WebGLRenderer({ antialias: true });
           renderer.setSize(width, height);
           graphContainer.appendChild(renderer.domElement);
           
           scene.add(new THREE.AmbientLight(0xcccccc, 0.8));
           const directionalLight = new THREE.DirectionalLight(0xffffff, 0.9);
           directionalLight.position.set(20, 30, 10);
           scene.add(directionalLight);
           
           const axesHelper = new THREE.AxesHelper(20);
           scene.add(axesHelper);

           const gridHelper = new THREE.GridHelper(40, 40, '#4b5563', '#374151');
           gridHelper.rotation.x = Math.PI / 2;
           scene.add(gridHelper);
           
           controls = new OrbitControls(camera, renderer.domElement);
           controls.enableDamping = true;
           
           animate3D();
       }

       // --- 3D Plotting ---
       function plot3DFunction() {
           if (!scene) init3D();
           
           const formula = formula3DInput.value;
           formula3DInput.classList.remove('border-red-500');

           let node, code;
           try {
               node = math.parse(formula);
               code = node.compile();
           } catch (err) {
               formula3DInput.classList.add('border-red-500');
               errorMessage.textContent = `Invalid 3D formula: ${err.message}`;
               return;
           }

           if (mesh3D) {
               scene.remove(mesh3D);
               mesh3D.geometry.dispose();
               mesh3D.material.dispose();
           }

           const surfaceFunction = (u, v, target) => {
               const range = 20;
               const x = (u - 0.5) * range;
               const y = (v - 0.5) * range;
               const scope = { x: x, y: y };
               const z = code.evaluate(scope);
               target.set(x, y, z);
           };
           
           const geometry = new THREE.ParametricGeometry(surfaceFunction, 50, 50);
           const material = new THREE.MeshStandardMaterial({
               color: 0x60a5fa,
               side: THREE.DoubleSide,
               metalness: 0.2,
               roughness: 0.7
           });
           
           mesh3D = new THREE.Mesh(geometry, material);
           scene.add(mesh3D);
       }
       
       // --- 3D Animation Loop ---
       function animate3D() {
           requestAnimationFrame(animate3D);
           if (controls) controls.update();
           if (renderer && scene && camera) renderer.render(scene, camera);
       }
       
       // --- Start Application ---
       initialize();

   </script>
</body>
</html>

 

We need your consent to load the translations

We use a third-party service to translate the website content that may collect data about your activity. Please review the details in the privacy policy and accept the service to view the translations.