<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/MathML2/dtd/xhtml-math11-f.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Draw parametric curves</title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
    <style>
        v {color:red;}
        bloque {display:block;}
        t1 {display:block; background-color:transparent; margin-left: 1mm; color:#a00; margin-top:2mm; font-weight:bold; font-size:120%;}
        t2 {display:block; background-color:transparent; margin-left:10mm; color:#000; font-weight:bold; font-size:110%;}
        t2 info {display:none; color:blue; cursor:pointer;}
        t2:hover info {display:inline}
        t3 {display:block; background-color:transparent; margin-left:10mm;}
        label:hover {color:red}
        #t1,#t2,#uxMin,#uxMax,#uyMin,#uyMax,#pixW,#pixH  {border:thin solid transparent; text-align:center;}
        input[type="text"] {border:thin solid}
        input[type="text"]:hover {background-color:rgb(255,255,100); border:thin solid red}
    </style>
</head>

<body style="margin:0">
<h2 style="background-color:#eee;  margin:0 0 5mm 0; padding:3mm">Draw parametric curves on the plane</h2>

    <canvas id="canvas" style="float:right; margin:1mm; border:medium groove; cursor:pointer;" onmousedown="if (event.button==0) OnDragStart(event)"
        onDOMMouseScroll="mouseWheelEvent(event)"
        onmousewheel="mouseWheelEvent(event)"
    />

    <cuadroParametros style="display:table; margin:2mm 2mm 2mm 10mm;">
        <t1>Curve</t1>
            <t2><label>x(<v>t</v>) = <input type="text" size="23" id="x(t)" onchange="x_t=cambioInputFuncion(this, x_t);  if (dibujarTrasCambio) dibuja()" value="t"/></label>  <info onclick="showInfo(this,'infoFunciones')">info</info></t2>
            <t2><label>y(<v>t</v>) = <input type="text" size="23" id="y(t)" onchange="y_t=cambioInputFuncion(this, y_t);  if (dibujarTrasCambio) dibuja()" value="t"/></label>  <info onclick="showInfo(this,'infoFunciones')">info</info></t2>
            <t2><v>t</v> &isin; [<input type="text" size="4" id="t1" onchange="t1=cambioInputReal(this, t1,-1000,1000);  if (dibujarTrasCambio) dibuja()"/> ,
                <input type="text" size="4" id="t2"  onchange="t2=cambioInputReal(this, t2,-1000,1000);  if (dibujarTrasCambio) dibuja()"/>]
                &emsp; <label style="font-weight:100">&delta;<v>t</v> = <input type="text" maxlength="10" size="5" id="dt"  onchange="dt=cambioInputReal(this, dt,1E-6,1000);  if (dibujarTrasCambio) dibuja()"/></label>
            </t2>
            <t3><label>Parameters: <input type="text" size="20" id="parámetros" onchange="eval(this.value);  if (dibujarTrasCambio) dibuja()"/></label></t3>
        
        <t1><boton idRef="bloqueEscala" estado="expandido" onclick="expandirBoton(this)"/> Scale</t1>
            <bloque id="bloqueEscala">
                <t3><em>x</em> range:
                    [<input type="text" size="3" id="uxMin" onchange="uxMin=cambioTextInput(this, uxMin,-10000,10000,validoReal);  cambiaVista();  if (dibujarTrasCambio) dibuja()" onkeypress="soloAdmiteDigitos(event,true)"/> ,
                     <input type="text" size="3" id="uxMax" onchange="uxMax=cambioTextInput(this, uxMax, -10000,10000,validoReal);  cambiaVista();  if (dibujarTrasCambio) dibuja()" onkeypress="soloAdmiteDigitos(event,true)"/>]
                </t3>
                <t3><em>y</em> range:
                    [<input type="text" size="3" id="uyMin" onchange="uyMin=cambioTextInput(this, uyMin,-10000,10000,validoReal);  cambiaVista();  if (dibujarTrasCambio) dibuja()" onkeypress="soloAdmiteDigitos(event,true)"/> ,
                     <input type="text" size="3" id="uyMax" onchange="uyMax=cambioTextInput(this, uyMax, -10000,10000,validoReal);  cambiaVista();  if (dibujarTrasCambio) dibuja()" onkeypress="soloAdmiteDigitos(event,true)"/>]
                </t3>
                <t3>Image size:
                    (<input type="text" size="3" id="pixW" onchange="pixW=cambioTextInput(this, pixW,1,1024,validoEntero);
                        canvas.width=pixW;                        cambiaVista();  if (dibujarTrasCambio) dibuja()" onkeypress="soloAdmiteDigitos(event,true)"/> ,
                     <input type="text" size="3" id="pixH" onchange="pixH=cambioTextInput(this, pixH,1,1024,validoEntero);
                        canvas.height=pixH;                        cambiaVista();  if (dibujarTrasCambio) dibuja()" onkeypress="soloAdmiteDigitos(event,true)"/>)
                </t3>
            </bloque>
        
        <t1><boton idRef="bloqueEstilos" estado="colapsado" onclick="expandirBoton(this)"/> Style</t1>
            <bloque id="bloqueEstilos">
                <t3><boton idRef="bloqueGrafica" estado="colapsado" onclick="expandirBoton(this)"/>
                    <label><input type="checkbox" id="dibujarGrafica" onchange="dibujarGrafica=this.checked;  if (dibujarTrasCambio) dibuja()"/> Graph</label>
                    <bloque id="bloqueGrafica">
                        <t3><label>Width (pixels) = <input type="text" maxlength="10" size="10" id="anchoGrafica" onchange="anchoGrafica=cambioInputReal(this, anchoGrafica,0.1,100);  if (dibujarTrasCambio) dibuja()"/></label></t3>
                        <t3><label>Color = <input type="text" size="10" id="colorGrafica" onchange="colorGrafica=this.value;  if (dibujarTrasCambio) dibuja()"/></label></t3>
                    </bloque>
                </t3>
                <t3><boton idRef="bloqueEjes" estado="colapsado" onclick="expandirBoton(this)"/>
                    <label><input type="checkbox" id="dibujarEjes" onchange="dibujarEjes=this.checked;  if (dibujarTrasCambio) dibuja()"/> Axis</label>
                    <bloque id="bloqueEjes">
                        <t3><label>Width (pixels) = <input type="text" maxlength="10" size="10" id="anchoEjes" onchange="anchoEjes=cambioInputReal(this, anchoEjes,0.1,100);  if (dibujarTrasCambio) dibuja()"/></label></t3>
                        <t3><label>Color = <input type="text" maxlength="10" size="10" id="colorEjes" onchange="colorEjes=this.value;  if (dibujarTrasCambio) dibuja()"/></label></t3>
                    </bloque>
                </t3>
                <t3><boton idRef="bloqueGrid" estado="colapsado" onclick="expandirBoton(this)"/>
                    <label><input type="checkbox" id="dibujarGrid" onchange="dibujarGrid=this.checked;  if (dibujarTrasCambio) dibuja()"/> Grid</label>
                    <bloque id="bloqueGrid">
                        <t3><label>Width (pixels) = <input type="text" maxlength="10" size="10" id="anchoGrid" onchange="anchoGrid=cambioInputReal(this,anchoGrid,0.1,100);  if (dibujarTrasCambio) dibuja()"/></label></t3>
                        <t3><label>Color = <input type="text" maxlength="10" size="10" id="colorGrid" onchange="colorGrid=this.value;  if (dibujarTrasCambio) dibuja()"/></label></t3>
                    </bloque>
                </t3>
                <t3><label>Background color = <input type="text" size="10" id="colorFondo" onchange="colorFondo=this.value;  if (dibujarTrasCambio) dibuja()"/></label></t3>
                <t3><label><input type="checkbox" id="borrarAntesDibujar" onchange="borrarAntesDibujar=this.checked;"/> Erase before drawing</label></t3>
                <t3><label><input type="checkbox" id="dibujarTrasCambio"
                    onchange="dibujarTrasCambio=this.checked; document.getElementById('dibujar').style.display=(dibujarTrasCambio?'none':'')"/> Authomatically draw after changes</label></t3>
            </bloque>
        <t3><input type="button" id="dibujar" value="DIBUJAR" onclick="dibuja()" style="display:none; width:100px; height:100px"/></t3>
        <br/><br/>
    </cuadroParametros>

<div id="infoFunciones" style="display:none; position:absolute; width:500px; height:200px; border:thin solid; background-color:#ffd"
    onmouseout="window.status = event.clientY">
    <div style="height:100%; width:100%">
    <span style="display:block; background-color:#ddf; text-align:center">
        Allowed functions    </span>
    <ul style="margin:0">
        <li><t>Operators:</t> +, -, *, /</li>
        <li><t>Trigonometric functions:</t> cos(<v>x</v>), sin(<v>x</v>), tan(<v>x</v>), sec(<v>x</v>), csc(<v>x</v>), cot(<v>x</v>), arccos(<v>x</v>), arcsin(<v>x</v>), arctan(<v>x</v>), arcsec(<v>x</v>), arccsc(<v>x</v>), arccot(<v>x</v>)</li>
        <li><t>Hyperbolic functions:</t> cosh(<v>x</v>), sinh(<v>x</v>), tanh(<v>x</v>), sech(<v>x</v>), csch(<v>x</v>), coth(<v>x</v>), arccosh(<v>x</v>), arcsinh(<v>x</v>), arctanh(<v>x</v>), arcsech(<v>x</v>), arccsch(<v>x</v>), arccoth(<v>x</v>)</li>
        <li><t>Powers:</t> power(<v>a</v>,<v>e</v>), exp(<v>x</v>), sqrt(<v>x</v>), root(<v>n</v>,<v>x</v>), ln(<v>x</v>), log2(<v>x</v>), log10(<v>x</v>)</li>
        <li><t>other:</t> abs(<v>x</v>), sgn(<v>x</v>), frac(<v>x</v>), floor(<v>x</v>), ceil(<v>x</v>), H(<v>x</v>), rect(<v>x</v>), sinc(<v>x</v>), min(<v>x</v>,<v>y</v>), max(<v>x</v>,<v>y</v>)</li>
    </ul>
    </div>
</div>
<div style="position:fixed; display:block; width:100%; bottom:0; margin:0; background-color:#eee; color:#ccc; text-align:center; border-top:thin solid #ccc; padding:3px">&copy; Daniel Herrera Tobar &emsp;<a href="mailto:danielht.ecc@gmail.com" style="color:#ccc">Mail</a></div>

<table id="aviso_javascript" style="display:table;  position:fixed; left:0; top:0;  width:100%; height:100%; background-color:rgba(200,100,0,0.5);">
    <tr><td style="width:20%" rowspan="3"/><td style="height:20%"/><td style="width:20%" rowspan="3"/></tr>
    <tr><td style="height:60%; background-color:#ffa; color:#000; text-align:center; border:thick ridge #ccc; font-size:30px; outline:1mm solid white">
            This page needs javascript activated to work            
        </td>
    </tr>
    <tr><td style="height:20%"/></tr>
</table>
</body>



<script type="text/javascript">
<![CDATA[

//AVISOS
    document.getElementById("aviso_javascript").style.display="none";

//FUNCIONES
    function cos(x)  {return Math.cos(x);}
    function sin(x)  {return Math.sin(x);}
    function tan(x)  {return Math.tan(x);}
    function sec(x)  {return 1/Math.cos(x);}
    function csc(x)  {return 1/Math.sin(x);}
    function cot(x)  {return 1/Math.tan(x);}

    function arccos(x)  {return Math.acos(x);}
    function arcsin(x)  {return Math.asin(x);}
    function arctan(x)  {return Math.atan(x);}
    function arcsec(x)  {return Math.acos(1/x);}
    function arccsc(x)  {return Math.asin(1/x);}
    function arccot(x)  {return Math.atan(1/x);}

    function cosh(x)  {return (Math.exp(x)+Math.exp(-x))/2;}
    function sinh(x)  {return (Math.exp(x)-Math.exp(-x))/2;}
    function tanh(x)  {var t=Math.exp(2*x);  return (t-1)/(t+1);}
    function sech(x)  {return 1/cosh(x);}
    function csch(x)  {return 1/senh(x);}
    function coth(x)  {return 1/tanh(x);}

    function arccosh(x)  {return Math.log(x+Math.sqrt(x*x-1));}
    function arcsinh(x)  {return Math.log(x+Math.sqrt(x*x+1));}
    function arctanh(x)  {return 0.5*Math.log((1+x)/(1-x));}
    function arcsech(x)  {return arccosh(1/x);}
    function arccsch(x)  {return arcsenh(1/x);}
    function arccoth(x)  {return arctanh(1/x);}

    function power(a,b)  {return Math.pow(a,b);}
    function exp(x)  {return Math.exp(x);}
    function ln(x)  {return Math.log(x);}
    function log2(x)   {return Math.LOG2E*Math.log(x);}
    function log10(x)  {return Math.LOG10E*Math.log(x);}
    function sqrt(x)  {return Math.sqrt(x);}
    function root(n,x)  {return Math.pow(x,1/n);}

    function abs(x)  {return Math.abs(x);}
    function min(x,y)  {return Math.min(x,y);}
    function max(x,y)  {return Math.max(x,y);}

    function sgn(x)  {return (x<0?-1:(x>0?1:0));}
    function H(x)  {return (x>=0) ? 1 : 0;}
    function rect(x)  {return ((x<0.5)&&(x>-0.5))? 1 : 0;}
    
    function frac(x)  {return x-Math.floor(x);} 
    function floor(x)  {return Math.floor(x);} 
    function ceil(x)  {return Math.ceil(x);}

    function sinc(x)  {return Math.sin(x)/x;}

    function showInfo(boton,infoId)
    {   var infoElement = document.getElementById(infoId);
        var box = boton.getBoundingClientRect();
        infoElement.style.display="block"; 
        infoElement.style.left = String(parseInt(box.left)) + "px";
        infoElement.style.top  = String(parseInt(box.top)) + "px";
        Monitoriza(infoElement);
    }

    var monitorBox,elementoMonitorizado;
    function Monitoriza(elemento)
    {   monitorBox = elemento.getBoundingClientRect();
        if (monitorBox == null)  { infoFunciones.style.display="none";  return; }
        elementoMonitorizado = elemento;
        document.onmousemove=Monitor;
    }
    function Monitor(evento)
    {   if ((evento.clientY > monitorBox.bottom) || (evento.clientY < monitorBox.top) || (evento.clientX > monitorBox.right) || (evento.clientX < monitorBox.left))
        {   elementoMonitorizado.style.display="none";
            document.onmousemove=null;
        }
    }

//ESCALA
    var hxx=1, hyy=1;
    var tx =0, ty =0;
    var uxMin,uxMax,uyMin,uyMax;
    function cambiaVista()
    {   hxx =  pixW / (uxMax-uxMin);    tx = -hxx*uxMin;
        hyy =  pixH / (uyMin-uyMax);    ty = -hyy*uyMax;
    }
    function pixX(ux) {return hxx*ux + tx;}
    function pixY(uy) {return hyy*uy + ty;}

    function CARGA_ESCALA()  //a llamar después de haber creado los controles correspondientes
    {   document.getElementById("uxMin").value = uxMin;
        document.getElementById("uxMax").value = uxMax;
        document.getElementById("uyMin").value = uyMin;
        document.getElementById("uyMax").value = uyMax;
    }
    
//VALORES        
    t1  = -10;      document.getElementById("t1").value = t1;
    t2  =  10;      document.getElementById("t2").value = t2;
    dt  = 0.01;     document.getElementById("dt").value = dt;
    x_t = "t";                  document.getElementById('x(t)').value = x_t;
    y_t = "0.08*(t-a)*(t-b)*(t-c)"; document.getElementById('y(t)').value = y_t;
    document.getElementById('parámetros').value = "a=-5;  b=0;  c=7";   eval(document.getElementById('parámetros').value);

//DIBUJOS
    dibujarGrid = true;     document.getElementById('dibujarGrid').checked = dibujarGrid;
    anchoGrid   = 0.2;      document.getElementById('anchoGrid').value = anchoGrid;
    colorGrid   = "#f00";   document.getElementById('colorGrid').value = colorGrid;

    dibujarEjes = true;     document.getElementById('dibujarEjes').checked = dibujarEjes;
    anchoEjes   = 2;        document.getElementById('anchoEjes').value = anchoEjes;
    colorEjes   = "#aaa";   document.getElementById('colorEjes').value = colorEjes;

    dibujarGrafica = true;  document.getElementById('dibujarGrafica').checked = dibujarGrafica;
    anchoGrafica= 3;        document.getElementById('anchoGrafica').value = anchoGrafica;
    colorGrafica= "blue";   document.getElementById('colorGrafica').value = colorGrafica;
    
    colorFondo= "transparent";   document.getElementById('colorFondo').value = colorFondo;

    borrarAntesDibujar= true;  document.getElementById('borrarAntesDibujar').checked = borrarAntesDibujar;
    dibujarTrasCambio = true;  document.getElementById('dibujarTrasCambio').checked = dibujarTrasCambio;

    pixW=300;      document.getElementById("pixW").value = pixW;
    pixH=300;      document.getElementById("pixH").value = pixH;

    function dibuja()
    {   if (borrarAntesDibujar)
        {   contexto.fillStyle=colorFondo;
            contexto.clearRect(0,0,pixW,pixH);
            contexto.fillRect(0,0,pixW,pixH);  //borra la imagen anterior
        }
        if (dibujarGrid) dibujaGrid();
        if (dibujarEjes) dibujaEjes();
        
        if (dibujarGrafica) dibujaGrafica();
            }
    function dibujaGrafica()
    {   var primero = true;
        contexto.beginPath();
        contexto.lineWidth=anchoGrafica;
        contexto.strokeStyle=colorGrafica;
        for (t=t1; t<t2; t+=dt)
        {   ux=eval(x_t);  if (isNaN(ux)) {window.alert("x(t) value not valid for t="+String(t));  break;}
            uy=eval(y_t);  if (isNaN(uy)) {window.alert("y(t) value not valid for t="+String(t));  break;}
            if (primero)  { contexto.moveTo(pixX(ux),pixY(uy)); primero=false; }
            else            contexto.lineTo(pixX(ux),pixY(uy));
        }
        contexto.stroke();
        contexto.closePath();
    }
    function dibujaGrid()
    {   contexto.lineWidth=anchoGrid;
        contexto.strokeStyle=colorGrid;
        var u,p;
        contexto.beginPath();
            for (u=Math.ceil(uxMin);  u<=uxMax;  u++)
            {   p = hxx*u + tx;
                contexto.moveTo(p,0);
                contexto.lineTo(p,pixH);
            }
            for (u=Math.ceil(uyMin);  u<=uyMax;  u++)
            {   p = hyy*u + ty; 
                contexto.moveTo(0,p);
                contexto.lineTo(pixW,p);
            }
            contexto.stroke();
        contexto.closePath();
    }
    function dibujaEjes()
    {   contexto.lineWidth=anchoEjes;
        contexto.strokeStyle=colorEjes;
        contexto.beginPath();
            contexto.moveTo(pixX(0),0);
            contexto.lineTo(pixX(0),pixH);
            contexto.moveTo(0,pixY(0));
            contexto.lineTo(pixW,pixY(0));
            contexto.stroke();
        contexto.closePath();
    }

        canvas = document.getElementById("canvas"); 
    contexto = canvas.getContext("2d");
    canvas.width = pixW;
    canvas.height= pixH;
    
    uxMin=-10;
    uxMax= 10;
    uyMin=-10;
    uyMax= 10;
    CARGA_ESCALA();
    cambiaVista();
    dibuja();

//ZOOM
    var cambiandoVista = false;
    function mouseWheelEvent(evento)
    {   var k, numPasos;
        if (cambiandoVista) return;
        cambiandoVista =true;

        if (evento.wheelDelta == undefined) numPasos = evento.detail;            //Firefox
        else
        {   if (evento.detail==0)   numPasos = -evento.wheelDelta /120;  //Chrome
            else                    numPasos = evento.detail;  //Opera
        }
        var canvasBox=canvas.getBoundingClientRect()
        var ux = (evento.clientX - canvasBox.left - tx) / hxx;
        var uy = (evento.clientY - canvasBox.top  - ty) / hyy 
        while (numPasos != 0)
        {   if (numPasos > 0)   //acercar
            {   if ((Math.abs(uxMax-uxMin) > pixW) || (Math.abs(uyMax-uyMin) > pixH))  numPasos=0;
                k=1.25;
                numPasos--;
            }
            else    //alejar
            {   if ((Math.abs(uxMax-uxMin) < 1) || (Math.abs(uyMax-uyMin) < 1))  numPasos=0;
                k=0.8;
                numPasos++;
            }
            uxMax = k*(uxMax-ux) + ux;
            uxMin = k*(uxMin-ux) + ux;
            uyMax = k*(uyMax-uy) + uy;
            uyMin = k*(uyMin-uy) + uy;
        }
        CARGA_ESCALA();
        cambiaVista();
        dibuja();
        cambiandoVista =false;
    }

//ARRASTRE VENTANA
    var drag_pixX,drag_pixY;
    var dragging=false;
    var dragImage = new Image();
    var dragImage_loaded=false;
        dragImage.onload = function()  {dragImage_loaded=true;}
    var dragEx_onmouseup = undefined;
    var dragEx_onmousemove = undefined;
    var dragEx_cursor = undefined;

    function OnDragStart(evento)
    {   if (dragging) { OnDragEnd(evento); return; }
        dragging = false;
        drag_pixX = evento.clientX;
        drag_pixY = evento.clientY;
        dragImage_loaded=false;
        dragImage.src = canvas.toDataURL();
        //captura el evento "onmouseup"
            dragEx_onmouseup = document.onmouseup;
            document.onmouseup = OnDragEnd;
        //captura el evento "onmousemove"
            dragEx_onmousemove = document.onmousemove;
            document.onmousemove = OnDragMove;
        //cambia el puntero del ratón
            dragEx_cursor = canvas.style.cursor;
            canvas.style.cursor="url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABYAAAAZCAYAAAA14t7uAAAAAXNSR0IArs4c6QAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9oDChYgGMkvbGsAAAJCSURBVEjHnZXLbtNQEIa/cew0tzohtJtIgMSebhAvgsQCIfEAXHY8AS/Big08A1t2LGCDVB6ASiyggqYhNJfazhkWPse1rdhJOpLlsc/Rf2b+mX8ObGeae5x1uIZ1Lcgd4BDQyKwc8MtWu/3a+n3At0/BvArgC1UFOAEeAwSSbV0tF4v7dn0CvAC+ALdr8K5Sd+YoKH9frpLCmvUfAb1rAXc6ndPyemnvMwCpAPaBOFaDjyAi2WmAiAiWCso+8LyO4wQgiqLyf9m2+nVk3+zutbJAh8PhYpe2qgMej0ajTy7K8Xjc2AV4m9RUVVVEpIrXXTh2dgj8NCCqigGSgvjqqVgnV2cRcIoFNHZLrIYEJUFR1bWHeQDzaElOSa9ymQTA57rIqjLwANrBnju9H4bhEyvTDnAG/NJrDBtXPDWqSEqHiAjdbvfpbDYLVCdv08hCq5wpCWHhnYHJICuem0oHnsgfVRVNu0BE5N0ijoA5se4TyDQDL1LhwPtr+/gM6IkI09mFxGpIVPF9P99GOb1PdxLIDLg76O0TiKdGjVYXLNwIXB7Q34FAROJFHGUROz5riyVyAryvAnYDaNAOmpO0R0PLeTHa/EHWbwL/bN9/rFLeX8ATka+BeAJgjKnSu3NHVlABMPc3XKAPgKTppfPHqLIqCSJ3ZQEYYAgkm2bFCmgALdIUaHA1dPziDPuRu5bU30JEBri0HdT3RM4r9r2xnfUbaPk7qFTtLLl17+io9+34+KGIHKjqOXDD1qUNfACW/wFcwThGwJIE/gAAAABJRU5ErkJggg=='),move";
        dragging = true;
    }

    var mouseMove_locked=false;
    function OnDragMove(evento)
    {   if (mouseMove_locked) return;
        mouseMove_locked = true;
        if (dragImage_loaded)
        {   contexto.clearRect(0,0,pixW,pixH);
            contexto.drawImage(dragImage, evento.clientX-drag_pixX , evento.clientY-drag_pixY);
        }
        if (dragEx_onmousemove != undefined)  dragEx_onmousemove(evento);
        mouseMove_locked = false;
    }

    function OnDragEnd(evento)
    {   //cálculo de la distancia movida durante el arrastre
            var inc_px = evento.clientX - drag_pixX;
            var inc_py = evento.clientY - drag_pixY;
        //cambio de las coordenadas y dibujado en las nuevas coordenadas
            tx += inc_px;
            ty += inc_py;
            var inc_ux = inc_px / hxx;
            var inc_uy = inc_py / hyy;
            uxMax -= inc_ux;
            uxMin -= inc_ux;
            uyMax -= inc_uy;
            uyMin -= inc_uy;
            CARGA_ESCALA();
            dibuja();
        //llama al handler por defecto
            if (dragEx_onmouseup != undefined)  dragEx_onmouseup(evento);
        //des-captura el evento "document.onmouseup"
            document.onmouseup = dragEx_onmouseup;
            dragEx_onmouseup = undefined;
        //des-captura el evento "document.onmousemove"
            document.onmousemove = dragEx_onmousemove;
            dragEx_onmousemove = undefined;
        //recupera el puntero del ratón
            canvas.style.cursor = dragEx_cursor;
        dragging = false;
    }

//MENÚ
    var lista = document.getElementsByTagName("boton");
    for (i=0; i<lista.length; i++)
        expandirBoton( lista.item(i) , lista.item(i).getAttribute("estado")=='expandido');

    function expandirBoton(boton,expandir)
    {   var bloque = document.getElementById(boton.getAttribute("idRef"));
        if (expandir==undefined)  expandir = (boton.getAttribute('estado')=='colapsado');  //cambia el estado si no se ha especificado
        if (expandir)
        {   bloque.style.display="block";
            boton.setAttribute("estado","expandido");
            boton.innerHTML="<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAAXElEQVQ4je3UwQnAMAiF4beFF90lozna28GB7KFNoLcQA4W2wncx8F8CAuekmaWILFHVBJDoMXcvIzmiY1EREX/weoDntO1Bb3ggSDK9YdoLfvkjwb6oup2wTXAAdpN2nHqlJ+kAAAAASUVORK5CYII='/>";
        }
        else
        {   bloque.style.display="none";
            boton.setAttribute("estado","colapsado");
            boton.innerHTML="<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAARCAYAAADdRIy+AAAAdklEQVQ4jd3UywnAIBBF0deFG+3F0qY0W5DBel4WQZkEgsFMFolwNy6OPxDYB1NKDCEsFWMkAKJjrTWqKmutS6kqSykDHRNPEpH7IAES8AclX6MfBzswyx20O54eWTJGFjj3k0d5HbR36gZOF7Rgn3ja4QtzChtjRFwLpmUCPQAAAABJRU5ErkJggg=='/>";
        }    
    }

//INPUTs
    const validoReal=0;
    const validoEntero=1;
    const LOCAL_NoPuedeSerMenorQue = "Can't be less than; ";
    const LOCAL_NoPuedeSerMayorQue = "Can't be more than; ";
    function cambioTextInput(elemento,valorEx,valorMin,valorMax,tipoValido)
    {   if ((tipoValido != validoReal) && (tipoValido != validoEntero))
        {   ERROR_DEL_PROGRAMA(1);  return;  }  //error en el programa: Se ha encontrado un tipo de dato no previsto
        inputCambiado=false;
        var valor;
        switch (tipoValido)
        {   case validoReal  : valor = parseFloat(elemento.value,10);  break;
            case validoEntero: valor = parseInt  (elemento.value,10);  break;
        }
        if (!isNaN(valor))  //es un número
        {   if (valor < valorMin)   window.alert(LOCAL_NoPuedeSerMenorQue + String(valorMin));
            else
            {   if (valor > valorMax)   window.alert(LOCAL_NoPuedeSerMayorQue + String(valorMax));
                else
                {   if (valor != valorEx)   //valor correcto, y distinto del anterior
                    {   inputCambiado=true;
                        valorEx=valor;
                    }
                }
            }
        }
        elemento.value=String(valorEx);
        return valorEx;
    }
    function cambioInputReal (elemento, vEx,vMin,vMax)  { return cambioTextInput(elemento,vEx,vMin,vMax,validoReal); }
    function cambioInputTexto(elemento, vEx,vMin,vMax)  { return cambioTextInput(elemento,vEx,0,0,validoTexto); }

    function soloAdmiteDigitos(e,admiteComa)
    {   if (e.charCode == 0) return;
        if ((e.charCode > 47) && (e.charCode < 58)) return;
        if ((e.charCode == 45) && (e.target.selectionStart == 0)) return;  //"-"
        if (admiteComa==true)
            if (e.charCode == 46) return;  //"."
        e.preventDefault(true);
    }

    function cambioInputFuncion(elementoInput, exValor)
    {   var s = elementoInput.value;
        t=0;
        try  { eval(s); }
        catch(m)
        {   window.alert("Non valid algebraic expression: \""+m+"\"");
            elementoInput.value=exValor;
            return exValor;
        }
        return s;
    }
]]>
</script>
</html>