<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Function Plotter</title>
<style type="text/css">
.plotterarea {
postitio: relative;
width: 80%;
height: 80%;
overflow: hidden;
background-color: #cccccc;
left: center;
border-left: 2px solid black;
}
.plotter_pen {
position: absolute;
background-color: black;
margin:0px;
width: 3px;
height: 3px;
font-size: 1px;
z-index: 10;
}
.plotter_interpolation {
position: absolute;
background-color: #999999;
margin:0px;
width: 3px;
height: 3px;
font-size: 1px;
z-index: 5;
}
</style>
<script type="text/javascript">
<!--
/**
* Контейнер для точек, что бы скрывать/удалять удобней было.
*/
function PlotterDrawing(name, area) {
this.points=[]; //контейнер с точками
this.area=area; //предок, обьект PlotterArea
this.name=name; //имя графика
this.initing=false; //флаг инициализации, пока установлен флаг, скрыть или удалить график нельзя
}
/**
* Скрыть/показать график
* @param visible - true - показать, false - скрыть график
*/
PlotterDrawing.prototype.setVisible=function(visible) {
if(this.initing) return;
for(var i=0; i<this.points.length; i++) this.points[i].style.visibility=visible? "true": "false";
}
/**
* Удалить график
*/
PlotterDrawing.prototype.remove=function() {
if(this.initing) return;
for(var i=0; i<this.points.length; i++) this.area.area.removeChild(this.points[i]);
this.points=[];
this.area.drawings["~"+this.name]=null;
}
/**
* Если установить в true, то дополняем все несуществующие точки простейшей
* интерполяцией. Не рекомендуеться для медленных машин.
*/
PlotterArea.use_interpolation=true;
/**
* Создать активное поле для графиков. Аргументом передаём слой/блочный элемент
* где рисуем граифк.
*/
function PlotterArea(obj) {
if(!obj||obj.nodeType!==1||obj.offsetWidth<1||obj.offsetHeight<1) throw "PlotterArea.<constructor> IlligalArgumentException: area is not a HTML element";
this.area=obj;
this.width=obj.offsetWidth
this.height=obj.offsetHeight;
this.plotter=document.createElement("SPAN"); //дефолтовый мелок
this.plotter.className="plotter_pen";
this.plotter.style.visibility="hidden";
this.area.appendChild(this.plotter);
this.drawings={}; //регистр графиков
}
/**
* Нарисовать график. Аргументами передаём имя, функцию и опциональные параметры.
* @param name - имя графика
* @param func - функция, что будем рисовать
* @param start - стартовое значение для аргумента функции
* @param step - шаг, с которым будем проходить по функции
* @param coef - выравнивающий коефициент, -1 <= func(arg)*coef <= 1
*/
PlotterArea.prototype.draw=function(name, func, start, step, coef) {
if(typeof(func)!="function"||name=="") throw "PlotterArea.draw: IlligalArgumentException: name and function must be specified";
var d=this.getDrawing(name); //если уже существует такой график, то удалим
this.drawings["~"+name]=(d=(d!=null)? (d.remove(),d): new PlotterDrawing(name, this));
d.initing=true;
var steps=Math.floor(this.width/this.plotter.offsetWidth); //количество точек вмещаемых в область рисования
start=start==null? 0: start;
step=step==null? 1: step;
coef=coef==null? 1: coef;
var pts=[];
for(var i=0; i<steps; i++) pts.push(func(start+step*i)*coef); //must be [-1...1]
var prnt=this, pos=0, prevp=null;
var tmr=window.setInterval(drawThread, 50); //ставим отрисовку с паузой в миллисекундах
//исполняемый тред, тот что будет рисовать точки
function drawThread() {
if(pos>=pts.length) {
d.initing=false;
window.clearTimeout(tmr);
return;
}
var p=prnt.plotter.cloneNode(true);
p.style.visibility="visible";
p.style.left=(pos*prnt.plotter.offsetWidth)+"px";
p.style.top=(prnt.height/2 - pts[pos]*prnt.height/2)+"px";
prnt.area.appendChild(p);
d.points.push(p);
/*
* Если разрешенна простая интерполяция, то дорисовываем недостающие точки.
* Медленный процес под Мозиллой, не советую... =/
*/
var bijsteps;
if(PlotterArea.use_interpolation && prevp!=null && (bijsteps=(Math.abs(prevp.offsetTop-p.offsetTop)/prnt.plotter.offsetHeight))>1) {
var sp=prnt.plotter.offsetWidth/bijsteps;
var sign=(prevp.offsetTop>p.offsetTop)? -1: 1;
var x, y, pi, prevx=prevp.offsetLeft;
for(var i=0; i<bijsteps; i++) {
x=Math.round(prevp.offsetLeft + sp*i);
if(pi&&x==prevx) { //простенькая оптимизация, дабы не генерить кучу точек друг под дружкой
pi.style.height=(pi.offsetHeight+prnt.plotter.offsetHeight)+"px";
if(sign<0) pi.style.top=(pi.offsetTop-prnt.plotter.offsetHeight)+"px";
} else {
pi=prnt.plotter.cloneNode(true);
pi.className="plotter_interpolation";
pi.style.visibility="visible";
pi.style.left=x+"px";
y=Math.round(prevp.offsetTop + prnt.plotter.offsetHeight*sign*(i+1))
pi.style.top=y+"px";
if(sign<0&&y>prevp.offsetTop) alert(prevp.offsetTop+", "+y);
prnt.area.appendChild(pi);
d.points.push(pi);
prevx=pi.offsetLeft;
}
}
}
prevp=p;
pos++;
}
}
/**
* Поставить мелок. Аргументом принимаем HTML элемент, им может быть что угодно,
* от картинки, до целой страницы(шутка ;-))
*/
PlotterArea.prototype.setPlotter=function(pen) {
if(!pen||pen.nodeType!=1) throw "PlotterArea.setPlotter: IlligalArgumentException: pen is not a HTML element";
this.plotter=pen;
}
/**
* Достать график по имени, полученный график можно затем скрыть или удалить.
*/
PlotterArea.prototype.getDrawing=function(name) {
return (typeof(this.drawings["~"+name])=="object")? this.drawings["~"+name]: null;
}
//=============================- Test -============================
var area;
function drawSinus() {
area=new PlotterArea(document.getElementById("graph"));
try {
area.draw("sinus", Math.sin, 0, 0.1, 0.9);
} catch(e) {
alert(e.toString());
}
}
function removeSinus() {
if(area) {
var d=area.getDrawing("sinus");
if(d.initing) alert("График сейчас рисуеться, я бы не рискнул его остановить =))");
else d.remove();
}
}
//-->
</script>
</head>
<body>
<div style="width: 100%; height: 100%; text-align: center;">
<h3>Движение по траектории синуса</h3>
<button onclick="drawSinus()">Вперёд</button> <button onclick="removeSinus()">Удалить график</button><br><br>
<div id="graph" class="plotterarea">
<div style="position: absolute; font-size: 1px; width: 100%; height:2px; background-color: black; left: 0px; top: 50%;"</div>
</div>
</body>
</html>