出于某种目的想做这个东西,顺便可以提供给GMA的用户&&放在博客园。
实现上只是简单的描点,加上一个相邻两点连线的开关,完全没有技术含量。而且函数图像一旦多起来就会变卡。
瓶颈在隐函数的绘制,这个超烦,计算量一下子就上去了。我的做法是把画布格成很多个60*60的小方格,先扫一遍方格边框上有没有零点,有就进这个方格绘图,没有就不管它了。绘图的部分也牺牲了精度换取速度,当然相关参数都还给用户自己调。结果还是挺慢的,但还可以接受啦。
效果还行
接下来打算兹兹一下js语句做函数表达式,然后再加上一维时间(动态图)。三维空间的话估计等我读了大学才能来做了……空间几何完全不行啊T_T
诸位聚聚如果有什么更好的函数渲染算法给点点建议啊……
<?php
require_once("style.php");
?>
<style>
[UnSelect=YES]{
-webkit-touch-callout: none; /* iOS Safari */
-webkit-user-select: none; /* Chrome/Safari/Opera */
-khtml-user-select: none; /* Konqueror */
-moz-user-select: none; /* Firefox */
-ms-user-select: none; /* Internet Explorer/Edge */
}
#mask{
width:100%;
height:100%;
background:#000;
opacity:0.5;
position:fixed;
top:0;
left: 0;
z-index: 100;
display:none;
}
.notes{
color:#FFFFFF;
font-weight:800;
font-size:20px;
}
</style>
<body>
<div id="mask"></div>
<div id="control" class="ui inverted segment" style="background-color:black;position:fixed;margin:auto;left:0;right:0;top:0;bottom:0;z-index:101;width:600px;height:500px;overflow:auto;display:none;">
<div id="function">
<button class="ui green button" onclick="Add()">Add function</button>
<span class="ui divider"></span>
<span id="mod" style="display:none" name="0">
<select class="ui dropdown" style="border-radius:5px;" onchange="FunctionChange(this)">
<option value="0">Cartesian</option>
<option value="1">Polar</option>
<option value="2">Implicit</option>
<option value="3">Parameter</option>
</select>
<input type="color" style="width:20px;height:20px;border-radius:5px;"/>
<span style="display:inline">
<span class="notes">y=</span>
<span class="ui input" name="Fun"><input/></span>
<span class="notes"></span>
<span class="ui input" style="display:none"><input/></span>
</span>
<button class="ui red button" onclick="Delete(this.parentNode)">Delete</button>
<span class="ui checkbox">
<input type="checkbox" onclick="DrawLine(this.parentNode)"/>
<label style="color:white;">Draw Line</label>
</span>
<span style="display:none;margin-top:6px;font-size:80%;">
<span class="notes"></span>
<span class="ui input" style="width:50px;"><input/></span>
<span class="notes"></span>
<span class="ui input" style="width:50px;"><input/></span>
<span class="notes"></span>
<span class="ui input" style="width:50px;"><input/></span>
</span>
<span class="ui divider" style="height:40px"></span>
</span>
</div>
<div class="title" UnSelect="YES" onclick="ShowOption(this)" style="color:white;cursor:pointer;font-size:17px;line-height:35px;font-weight:bold;"><i class="Caret Right icon"></i>Advanced Options</div>
<div id="option" UnSelect="YES" style="color:white;font-size:50%;font-family: Consolas,Monaco,monospace;display:none;transition:2s;line-height:30px;">
<span class="notes">X:</span>
<span class="ui input"><input id="FunLx" style="width:180px;"/></span>
<span class="notes">~</span>
<span class="ui input"><input id="FunRx" style="width:180px;"/></span>
<br>
<span class="notes">Y:</span>
<span class="ui input"><input id="FunLy" style="width:180px;"/></span>
<span class="notes">~</span>
<span class="ui input"><input id="FunRy" style="width:180px;"/></span>
<br>
<span class="notes">Coordinate:</span>
<span class="ui input"><input id="FontStyle" style="width:200px;" value=""/></span>
<br>
<span class="ui checkbox">
<input type="checkbox" id="sizelimit" checked="1"/>
<label style="color:white;">Size Limit</label>
</span>
<br>
<span class="notes">PointRadii:</span>
<span class="ui input"><input id="PointRadii" style="width:50px;" value="1"/></span>
<br>
<span class="notes">Scale:</span>
<span class="ui input"><input id="Scale" style="width:50px;" value="0.9"/></span>
<br>
<span class="ui checkbox">
<input type="checkbox" id="DrawMoving" checked=1/>
<label style="color:white;">Draw while moving</label>
</span>
<br>
<span class="notes" id="size"></span>
</div>
<span class="ui divider"></span>
<button class="ui blue button" onclick="change()">Draw</button>
</div>
<div id="main">
<h1 class="ui header" UnSelect="YES" style="">
<i class="book icon"></i>
<div class="content">Tools</div>
</h1>
<h1 align="center" UnSelect="YES">函数图像绘制工具</h1>
<div align="center" style="width:100%;height:70%;">
<canvas id="graph"></canvas>
</div>
<div UnSelect="YES">
<button class="ui green button" onclick="mask()">Control</button>
<button class="ui green button" onclick="redraw()">Reflash</button>
<!--<p id="map"></p>-->
</div>
<script>
function $(id) {
return document.getElementById(id);
}
function getRandomColor(){
return '#'+('00000'+(Math.random()*0x1000000<<0).toString(16)).substr(-6);
}
function ischar(c){return (c>='a'&&c<='z')||(c>='A'&&c<='Z');}
function isdigit(c){return c>='0'&&c<='9';}
function ChangeToPointX(x){return parseInt((x-FunLx)/(FunRx-FunLx)*FunW);}
function ChangeToPointY(y){return FunH-1-parseInt((y-FunLy)/(FunRy-FunLy)*FunH);}
function priority(c){
switch(c){
case '(':{return 0;break;}
case '+':{return 1;break;}
case '-':{return 1;break;}
case '*':{return 2;break;}
case '/':{return 2;break;}
case '^':{return 3;break;}
default:{return -1;break;}
}
}
function isopt(c){return priority(c)!=-1;}
function SingleCalc(c,a,b){
if (c=='+') return a+b;else
if (c=='-') return a-b;else
if (c=='*') return a*b;else
if (c=='/') return a/b;else
if (c=='^') return Math.pow(a,b);else
return NaN
}
function FunWork(f,x){
switch(f){
case "":{return x;break;}
case "SIN":{return Math.sin(x);break;}
case "COS":{return Math.cos(x);break;}
case "TAN":{return Math.tan(x);break;}
case "ABS":{return Math.abs(x);break;}
case "SQRT":{return Math.sqrt(x);break;}
case "LN":{return Math.log(x);break;}
case "LOG":{return Math.log(x)/Math.LN2;break;}//2为底
case "LG":{return Math.log(x)/Math.LN10;break;}//10为底
case "FLOOR":{return Math.floor(x);break;}
case "CEIL":{return Math.ceil(x);break;}
case "INT":{return parseInt(x);break;}
default:{return NaN;break;}
}
}
function FunInit(F){
F=F.replace(/sin/g,"SIN");
F=F.replace(/cos/g,"COS");
F=F.replace(/tan/g,"TAN");
F=F.replace(/abs/g,"ABS");
F=F.replace(/sqrt/g,"SQRT");
F=F.replace(/ln/g,"LN");
F=F.replace(/log/g,"LOG");
F=F.replace(/lg/g,"LG");
F=F.replace(/floor/g,"FLOOR");
F=F.replace(/ceil/g,"CEIL");
F=F.replace(/int/g,"INT");
return F;
}
var ca=$("graph"),el=ca.getContext("2d"),fun=$("function"),eps=1e-12;
var FunW=ca.parentNode.clientWidth,FunH=ca.parentNode.clientHeight,FunLx=-FunW/100,FunRx=FunW/100,FunLy=-FunH/100,FunRy=FunH/100,PR,tableX,tableY,tmp,countX,countY,Funstage=0,MoX,MoY,MIN=1e-4,MAX=1e8,FUN;
var dir=[[0,1],[1,0],[0,-1],[-1,0],[1,1],[1,-1],[-1,1],[-1,-1]];
var FontStyle="bold 12px Georgia";
ca.width=FunW;ca.height=FunH;$("size").innerHTML="Size:"+FunW+"*"+FunH;
function Calc(fun,X,Value){
var number=new Array(),opt=new Array(),F=new Array(),now=0,f="",tmp,a,b,sign=1,base=0,j;
for (var i=0;i<fun.length;i++){
for (j=0;j<X.length;j++)
if (X[j]==fun[i]){
if (i==0||isopt(fun[i-1])) now=Value[j];else now*=Value[j];
break;
}
if (j==X.length)
if (fun[i]=='(') F.push(f),f="",opt.push('(');else
if (fun[i]==')'){
number.push(now*sign);now=0;sign=1;base=0;
while ((tmp=opt.pop())!='('){
b=number.pop();a=number.pop();
tmp=SingleCalc(tmp,a,b);
number.push(tmp);
}
now=FunWork(F.pop(),number.pop());
}else
if (fun[i]=='.') base=1;else
if (fun[i]=='+'&&(i==0||priority(fun[i-1])!=-1));else
if (fun[i]=='-'&&(i==0||priority(fun[i-1])!=-1)) sign=-1;else
if (fun[i]=='e') if (i==0||isopt(fun[i-1])) now=Math.E;else now*=Math.E;else
if (fun[i]=='p'&&fun[i+1]=='i'){if (i==0||isopt(fun[i-1])) now=Math.PI;else now*=Math.PI;i+=1;}else
if (isdigit(fun[i])) if (base==0) now=now*10+(fun[i]-'0');else base/=10,now+=base*(fun[i]-'0');else
if (ischar(fun[i])) f+=fun[i];else if (isopt(fun[i])){
number.push(now*sign);now=0;sign=1;base=0;
var s=priority(fun[i]);
if (s==-1) return 0;
while (s<=priority(opt[opt.length-1])){
b=number.pop();a=number.pop();
tmp=SingleCalc(opt.pop(),a,b);
number.push(tmp);
}
opt.push(fun[i]);
}
}
number.push(now*sign);
while (opt.length>0){
b=number.pop();a=number.pop();
tmp=SingleCalc(opt.pop(),a,b);
number.push(tmp);
}
return number.pop();
}
function drawarc(x,y,R){
el.beginPath();
el.arc(x,y,R,0,Math.PI*2);
el.closePath();
el.fill();
}
function drawline(lx,ly,px,py){
el.beginPath();
el.moveTo(lx,ly);
el.lineTo(px,py);
el.closePath();
el.stroke();
}
function gettable(){
tmp=(FunRx-FunLx+eps)/20;
tableX=1;countX=0;countY=0;
while(tableX<tmp) tableX*=10;
while(tableX/10>tmp) tableX/=10,countX++;
if (tableX>=1) countX=0;
if (tableX/5>tmp) tableX/=5,countX++;else if (tableX/2>tmp) tableX/=2,countX++;
for (var i=parseInt(FunLx/tableX)+(FunLx>0);i*tableX<FunRx;i++){
el.fillStyle=i==0?"#000000":"#CDB7B5";
tmp=(i*tableX-FunLx)/(FunRx-FunLx)*FunW;
el.fillRect(tmp,0,1,FunH); el.fillStyle="#000000";
el.font=FontStyle;
el.fillText((i*tableX).toFixed(countX),tmp+2,10);
} tmp=(FunRy-FunLy+eps)/20;
tableY=1; while(tableY<tmp) tableY*=10;
while(tableY/10>tmp) tableY/=10,countY++;
if (tableY/5>tmp) tableY/=5,countY++;else if (tableY/2>tmp) tableY/=2,countY++;
if (tableY>=1) countY=0;
for (var i=parseInt(FunLy/tableY)+(FunLy>0);i*tableY<FunRy;i++){
el.fillStyle=i==0?"#000000":"#CDB7B5";
tmp=(i*tableY-FunLy)/(FunRy-FunLy)*FunH;
el.fillRect(0,FunH-1-tmp,FunW,1); el.fillStyle="#000000";
el.font=FontStyle;
el.fillText((i*tableY).toFixed(countY),0,FunH-1-tmp);
}
//$("map").innerHTML=tableX+" "+tableY;
}
function PCalc(i,j){return Calc(FUN,['x','y'],[FunLx+(FunRx-FunLx)/FunW*i,FunRy-(FunRy-FunLy)/FunH*j]);}
function ImpDraw(x,y,X,Y,jump){
if (x+X>FunW) X=FunW-x;if (y+Y>FunH) Y=FunH-y;
var Imp=new Array(),tmp;
if (X>jump)X/=jump;
if (Y>jump)Y/=jump;
for (var i=-1;i<=X;i+=1){
Imp[i+1]=new Array();
for (var j=-1;j<=Y;j+=1) Imp[i+1].push(PCalc(i*jump+x,j*jump+y));
}
for (var i=0;i<X;i+=1)
for (var j=0;j<Y;j+=1)
for (var k=0;k<4;k++)
if (Imp[i+1][j+1]*Imp[i+1+dir[k][0]][j+1+dir[k][1]]<0) {drawarc(i*jump+x,j*jump+y,PR);break;}
}
function getfunction(){
var group=document.getElementsByName("Fun"),x,y,R,lax,lay,px,py,color,OutSide,type,ValueL,ValueR,ValueS,DLc,tmp,TMP;
PR=$("PointRadii").value;
for (var k=1;k<group.length;k++){
var gf=group[k].parentNode.parentNode;
OutSide=1;type=gf.children[0].value;DLc=gf.children[4].children[0].checked;
FUN=FunInit((group[k].children[0].value).toLowerCase());
color=gf.children[1].value;
el.fillStyle=el.strokeStyle=color;
switch (type){
case '0':{
for (var i=0;i<FunW;i++){
x=FunLx+(FunRx-FunLx)/FunW*i;
y=Calc(FUN,['x'],[x]);
if (isNaN(y)) continue;
px=i;py=ChangeToPointY(y);
if (y>=FunLy&&y<FunRy){
drawarc(px,py,PR);
if (DLc) drawline(lax,lay,px,py);
OutSide=0;
}else{
if (DLc) if (!OutSide) drawline(lax,lay,px,py);
OutSide=1;
}
lax=px;lay=py;
}
break;
}
case '1':{
ValueL=Calc(gf.children[5].children[1].children[0].value,[],[]);
ValueR=Calc(gf.children[5].children[3].children[0].value,[],[]);
ValueS=Calc(gf.children[5].children[5].children[0].value,[],[]);
for (var i=ValueL;i<ValueR+ValueS-eps;i+=ValueS){
if (i>ValueR) i=ValueR;
R=Calc(FUN,['t'],[i]);
x=R*Math.cos(i);y=R*Math.sin(i);
px=ChangeToPointX(x);py=ChangeToPointY(y);
if (FunLx<=x&&x<FunRx&&FunLy<=y&&y<FunRy){
drawarc(px,py,PR);
if (DLc) drawline(lax,lay,px,py);
OutSide=0;
}else{
if (DLc) if (!OutSide) drawline(lax,lay,px,py);
OutSide=1;
}
lax=px;lay=py;
}
break;
}
case '2':{
var SpaceW=Calc(gf.children[5].children[1].children[0].value,[],[]),
SpaceH=Calc(gf.children[5].children[3].children[0].value,[],[]),
jump=Calc(gf.children[5].children[5].children[0].value,[],[]),Imp=new Array();
for (var i=0;i*SpaceW<FunW;i+=1){
Imp[i]=new Array();
for (var j=0;j*SpaceH<FunH;j+=1) Imp[i].push(0);
}
for (var i=0,_i=0;i<FunW;i+=SpaceW,_i+=1)
for (var j=0,_j=0;j<FunH;j+=SpaceH,_j+=1){
//if (!Imp[_i][_j]||(!_i&&!Imp[_i-1][_j]))
{
tmp=PCalc(i,j-1);
for (var l=0;l<SpaceH&&j+l<FunH;l++)
if (TMP=tmp,tmp=PCalc(i,j+l),TMP*tmp<0||Math.abs(tmp)<eps) {Imp[_i][_j]=1;if (_i) Imp[_i-1][_j]=1;break;}
}
//if (!Imp[_i][_j]||(!_j&&!Imp[_i][_j-1]))
{
tmp=PCalc(i-1,j);
for (var l=0;l<SpaceW&&i+l<FunW;l++)
if (TMP=tmp,tmp=PCalc(i+l,j),TMP*tmp<0||Math.abs(tmp)<eps) {Imp[_i][_j]=1;if (_j) Imp[_i][_j-1]=1;break;}
}
}
for (var i=0;i<Imp.length;i+=1)
for (var j=0;j<Imp[i].length;j+=1)
if (Imp[i][j]) ImpDraw(i*SpaceW,j*SpaceH,SpaceW,SpaceH,jump);
break;
}
case '3':{
ValueL=Calc(gf.children[5].children[1].children[0].value,[],[]);
ValueR=Calc(gf.children[5].children[3].children[0].value,[],[]);
ValueS=Calc(gf.children[5].children[5].children[0].value,[],[]);
_FUN=FunInit((group[k].parentNode.children[3].children[0].value).toLowerCase());
for (var i=ValueL;i<ValueR+ValueS-eps;i+=ValueS){
if (i>ValueR) i=ValueR;
x=Calc(FUN,['t'],[i]);y=Calc(_FUN,['t'],[i]);
px=ChangeToPointX(x);py=ChangeToPointY(y);
if (FunLx<=x&&x<FunRx&&FunLy<=y&&y<FunRy){
drawarc(px,py,PR);
if (DLc) drawline(lax,lay,px,py);
OutSide=0;
}else{
if (DLc) if (!OutSide) drawline(lax,lay,px,py);
OutSide=1;
}
lax=px;lay=py;
}
break;
}
}
}
}
function redraw(){
el.clearRect(0,0,FunW,FunH);
gettable();
if (Funstage!=1||$("DrawMoving").checked) getfunction();
}
function change(){
FunLx=parseFloat($("FunLx").value);
FunRx=parseFloat($("FunRx").value);
FunLy=parseFloat($("FunLy").value);
FunRy=parseFloat($("FunRy").value);
FontStyle=$("FontStyle").value;
redraw();
}
function update(){
$("FunLx").value=FunLx;
$("FunRx").value=FunRx;
$("FunLy").value=FunLy;
$("FunRy").value=FunRy;
$("FontStyle").value=FontStyle;
}
function Scale(x,y,times){
if (x<0||x>=FunW||y<0||y>=FunH) return;
if ($("sizelimit").checked){
if (times<1&&(FunRx-FunLx<MIN||FunRy-FunLy<MIN)) return;
if (times>1&&(FunRx-FunLx>MAX||FunRy-FunLy>MAX)) return;
}
x=FunLx+(FunRx-FunLx)/FunW*x;
y=FunLy+(FunRy-FunLy)/FunH*y;
FunLx=x-(x-FunLx)*times;FunRx=x+(FunRx-x)*times;
FunLy=y-(y-FunLy)*times;FunRy=y+(FunRy-y)*times;
}
ca.onmousedown=function(ob){
MoX=ob.layerX;MoY=ob.layerY;
Funstage=1;
}
ca.onmousemove=function(ob){
if (Funstage!=1) return;
var NoX,NoY,det;
NoX=ob.layerX;NoY=ob.layerY;
det=(MoX-NoX)/FunW*(FunRx-FunLx);FunLx+=det;FunRx+=det;
det=(NoY-MoY)/FunH*(FunRy-FunLy);FunLy+=det;FunRy+=det;
MoX=NoX;MoY=NoY;
redraw();update();
}
ca.onmouseup=function(ob){
if (Funstage==1){
Funstage=0;
redraw();
}
}
ca.onmouseleave=function(ob){
if (Funstage==1){
Funstage=0;
redraw();
}
}
ca.onmousewheel=function(ob){
ob=ob||window.event;ob.preventDefault();
var ScaleRate=$("Scale").value;
var detail;
if(ob.wheelDelta)detail=ob.wheelDelta;else if(ob.detail)detail=ob.detail;
if (detail>0) Scale(ob.layerX,FunH-1-ob.layerY,ScaleRate);else Scale(ob.layerX,FunH-1-ob.layerY,1/ScaleRate);
redraw();update();
}
function Add(){
var New=$("mod").cloneNode(true);
New.style.display="block";
New.children[1].value=getRandomColor();
fun.appendChild(New);
}
function Delete(node){
node.parentNode.removeChild(node);
}
function DrawLine(ob){
//if (ob.className=="ui checkbox checked") ob.className="ui checkbox";else ob.className="ui checkbox checked";
redraw();
}
function ShowOption(obj){
obj=obj.children[0];
if (obj.className=="Caret Right icon") obj.className="Caret Down icon",$("option").style.display="block";else obj.className="Caret Right icon",$("option").style.display="none";
}
function FunctionChange(obj){
var fi=0,la=2,v=obj.value;
obj=obj.parentNode;
obj.children[5].style.display="none";
obj.children[2].style.display="inline";
obj.children[2].children[3].style.display="none";
if (v==0){
obj.children[2].children[fi].innerHTML="y=";
obj.children[2].children[la].innerHTML="";
}else if (v==1){
obj.children[2].children[fi].innerHTML="r=";
obj.children[2].children[la].innerHTML="";
obj.children[5].style.display="block";
obj.children[5].children[0].innerHTML="t:";
obj.children[5].children[1].children[0].value="0";
obj.children[5].children[2].innerHTML="~";
obj.children[5].children[3].children[0].value="2pi";
obj.children[5].children[4].innerHTML=" Space:";
obj.children[5].children[5].children[0].value="0.02";
}else if (v==2){
obj.children[2].children[fi].innerHTML="";
obj.children[2].children[la].innerHTML="=0";
obj.children[5].style.display="block";
obj.children[5].children[0].innerHTML="SpaceW:";
obj.children[5].children[1].children[0].value="60";
obj.children[5].children[2].innerHTML=" SpaceH:";
obj.children[5].children[3].children[0].value="60";
obj.children[5].children[4].innerHTML=" Jump:";
obj.children[5].children[5].children[0].value="3";
}else if (v==3){
obj.children[2].style.display="block";
obj.children[2].children[3].style.display="inline";
obj.children[2].children[fi].innerHTML="x=";
obj.children[2].children[la].innerHTML=" y=";
obj.children[5].style.display="block";
obj.children[5].children[0].innerHTML="t:";
obj.children[5].children[1].children[0].value="0";
obj.children[5].children[2].innerHTML="~";
obj.children[5].children[3].children[0].value="1";
obj.children[5].children[4].innerHTML=" Space:";
obj.children[5].children[5].children[0].value="0.1"; }
}
redraw();update();
var maskobj=$("mask");
var control=$("control");
function mask(){
if (maskobj.style.display=="block") control.style.display=maskobj.style.display="none";else control.style.display=maskobj.style.display="block";
}
maskobj.onclick=function(){
mask();
}
</script>
</div>
</body>