JavaScript/Tutorials/Funktionsplotter: Unterschied zwischen den Versionen

Aus SELFHTML-Wiki
Wechseln zu: Navigation, Suche
K
K (replaced: <source lang="javascript"> → <syntaxhighlight lang="javascript"> (15), </source> → </syntaxhighlight> (15))
 
(14 dazwischenliegende Versionen von 3 Benutzern werden nicht angezeigt)
Zeile 2: Zeile 2:
  
 
Es wird ein Script vorgestellt, das mathematische Funktionen über Inputelemente einliest und grafisch darstellt. Für die grafische Darstellung wird das [[JavaScript/Canvas|Canvas]]-Element verwendet.
 
Es wird ein Script vorgestellt, das mathematische Funktionen über Inputelemente einliest und grafisch darstellt. Für die grafische Darstellung wird das [[JavaScript/Canvas|Canvas]]-Element verwendet.
 +
 +
Es gibt auch [[Datenvisualisierung/Funktionsplotter|eine Version des Funktionsplotters]], die das SVG-Element verwendet.
  
 
Das Script besteht aus dem Grafikobjekt <code>grafik</code>, dem Plotobjekt <code>plot</code> und dem Funktionsplotter.
 
Das Script besteht aus dem Grafikobjekt <code>grafik</code>, dem Plotobjekt <code>plot</code> und dem Funktionsplotter.
Zeile 12: Zeile 14:
 
;SW.grafik(grafikelement): legt das Canvas-Element an.
 
;SW.grafik(grafikelement): legt das Canvas-Element an.
 
:grafikelement: ID des Elements oder Referenz auf das Element, in dem das Canvas-Element angelegt wird.
 
:grafikelement: ID des Elements oder Referenz auf das Element, in dem das Canvas-Element angelegt wird.
:SW.grafik definiert die Methoden:
+
 
 +
SW.grafik definiert die Methoden:
  
 
;.setwidth(width): setzt die Linienstärke.
 
;.setwidth(width): setzt die Linienstärke.
Zeile 40: Zeile 43:
 
;.del(): löscht die Grafik.
 
;.del(): löscht die Grafik.
  
Das folgende Beispiel zeigt das Grafikobjekt sowie seine Anwendung.
+
{{Beispiel|titel=Das Grafikobjekt|
 
+
{{BeispielCode|
{{Beispiel|zeige=Beispiel: JS-Anw-FktPlot-0.html|
+
<syntaxhighlight lang="javascript">
{{BeispielCode|<source lang="html5">
 
<!DOCTYPE html>
 
<html lang="de">
 
<head>
 
<meta charset="UTF-8">
 
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<meta name="description" content="Plottet Funktionen">
 
<title>Test Grafikobjekt</title>
 
</source>
 
<source lang="css">
 
<style>
 
  #plotarea { background-color:#ffffff; width:300px; height:300px; border: 1px solid black }
 
</style>
 
</source>
 
<source lang="javascript">
 
<script>
 
 
"use strict";
 
"use strict";
  
var SW = {};
+
var SW = window.SW || {};
  
 
// Das Grafikobjekt
 
// Das Grafikobjekt
 
SW.grafik = function(grafikelement) {
 
SW.grafik = function(grafikelement) {
 +
  this.method = "canvas";
 
   // Canvas in Größe des "grafikelement" anlegen
 
   // Canvas in Größe des "grafikelement" anlegen
 
   if(typeof grafikelement == "string") grafikelement = document.getElementById(grafikelement);
 
   if(typeof grafikelement == "string") grafikelement = document.getElementById(grafikelement);
Zeile 72: Zeile 60:
 
   cv.width = this.w;
 
   cv.width = this.w;
 
   cv.height = this.h;
 
   cv.height = this.h;
  //cv.style.position = "absolute";
 
 
   grafikelement.appendChild(cv);
 
   grafikelement.appendChild(cv);
 
   var context = cv.getContext("2d");  
 
   var context = cv.getContext("2d");  
  this.w = context.canvas.width;
 
  this.h = context.canvas.height;
 
 
   context.lineWidth = 1;
 
   context.lineWidth = 1;
 
   context.globalAlpha = 1.0;
 
   context.globalAlpha = 1.0;
Zeile 87: Zeile 72:
 
   // Linie von (xs,ys) nach (xe,ye) in Farbe color zeichnen
 
   // Linie von (xs,ys) nach (xe,ye) in Farbe color zeichnen
 
   this.line = function(xs,ys,xe,ye,color) {
 
   this.line = function(xs,ys,xe,ye,color) {
    xs=Math.round(xs); xe=Math.round(xe);
 
    ys=Math.round(ys); ye=Math.round(ye);
 
 
     context.strokeStyle = color;
 
     context.strokeStyle = color;
 
     context.beginPath();
 
     context.beginPath();
     context.moveTo(xs,this.h-ys);
+
     context.moveTo(Math.round(xs),Math.round(this.h-ys));
     context.lineTo(xe,this.h-ye);
+
     context.lineTo(Math.round(xe),Math.round(this.h-ye));
 
     context.stroke();
 
     context.stroke();
 
   } // line
 
   } // line
  
 
   // Polylinie mit den Werten in points in Farbe color zeichnen
 
   // Polylinie mit den Werten in points in Farbe color zeichnen
   this.polyline=function(points,color) {  
+
   this.polyline = function(points,color) {  
 
     context.strokeStyle = color;
 
     context.strokeStyle = color;
 
     context.beginPath();
 
     context.beginPath();
Zeile 108: Zeile 91:
 
   // Polylinie mit den Werten in points zeichnen
 
   // Polylinie mit den Werten in points zeichnen
 
   // Die von der Polylinie umschlossene Fläche wird in Farbe color mit Alphawert alpha eingefärbt
 
   // Die von der Polylinie umschlossene Fläche wird in Farbe color mit Alphawert alpha eingefärbt
   this.polyfill=function(points,color,alpha) {  
+
   this.polyfill = function(points,color,alpha) {  
 
     context.fillStyle = color;
 
     context.fillStyle = color;
 
     context.globalAlpha = alpha;
 
     context.globalAlpha = alpha;
Zeile 116: Zeile 99:
 
       context.lineTo(Math.round(points[i].x),this.h-Math.round(points[i].y));
 
       context.lineTo(Math.round(points[i].x),this.h-Math.round(points[i].y));
 
     context.fill();
 
     context.fill();
     context.globalAlpha = 1;
+
     context.globalAlpha = 1.0;
 
   } // polyfill
 
   } // polyfill
 
    
 
    
Zeile 124: Zeile 107:
 
   // align: Bezug für (x,y), zwei Buchstaben, z.B. lu für links unten, s. case
 
   // align: Bezug für (x,y), zwei Buchstaben, z.B. lu für links unten, s. case
 
   // diretion: Textrichtung: v für vertikal, sonst horizontal
 
   // diretion: Textrichtung: v für vertikal, sonst horizontal
   this.text=function(x,y,size,color,text,align,direction) {
+
   this.text = function(x,y,size,color,text,align,direction) {
 
     var align_h = "m";
 
     var align_h = "m";
 
     var align_v = "m";
 
     var align_v = "m";
Zeile 139: Zeile 122:
 
       case "m": context.textAlign = "center"; break;
 
       case "m": context.textAlign = "center"; break;
 
       case "r": context.textAlign = "end"; break;
 
       case "r": context.textAlign = "end"; break;
       default:  context.textAlign = "start"; break;
+
       default:  context.textAlign = "center"; break;
 
     }
 
     }
 
     switch(align_v) {
 
     switch(align_v) {
Zeile 145: Zeile 128:
 
       case "m": context.textBaseline = "middle" ; break;
 
       case "m": context.textBaseline = "middle" ; break;
 
       case "u": context.textBaseline = "bottom" ; break;
 
       case "u": context.textBaseline = "bottom" ; break;
       default:  context.textBaseline = "bottom" ; break;
+
       default:  context.textBaseline = "middle" ; break;
 
     }
 
     }
     context.font = size+" sans-serif";
+
     context.font = size + " sans-serif";
 
     context.fillStyle = color;
 
     context.fillStyle = color;
 
     context.fillText(text,0,0);
 
     context.fillText(text,0,0);
Zeile 154: Zeile 137:
  
 
   // Canvas löschen
 
   // Canvas löschen
   this.del=function() {
+
   this.del = function() {
 
     context.clearRect(0, 0, this.w, this.h);
 
     context.clearRect(0, 0, this.w, this.h);
 
   } // del
 
   } // del
 
} // grafik
 
} // grafik
 +
</syntaxhighlight>
 +
}}
 +
}}
 +
 +
Das folgende Beispiel zeigt die Anwendung des Grafikobjekts.
 +
 +
{{Beispiel|zeige=Beispiel:JS-Anw-FktPlot-Canvas-0.html|
 +
{{BeispielCode|
 +
<syntaxhighlight lang="html5">
 +
<!DOCTYPE html>
 +
<html lang="de">
 +
<head>
 +
<meta charset="UTF-8">
 +
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 +
<title>Beispiel: Test Grafikobjekt auf Canvas-Basis</title>
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="css">
 +
<style>
 +
  #plotarea { background-color:#ffffff; width:90%; height:80vh; border: 1px solid black }
 +
</style>
 +
</syntaxhighlight>
  
 +
<syntaxhighlight lang="javascript">
 +
<script src="grafik_canvas.js"></script>
 +
<script>
 +
"use strict";
 
window.addEventListener("DOMContentLoaded",function() {
 
window.addEventListener("DOMContentLoaded",function() {
 
   var grafik = new SW.grafik("plotarea");
 
   var grafik = new SW.grafik("plotarea");
   grafik.line(20,20,280,40,"black");
+
   grafik.line(20,20,grafik.w-20,40,"black");
   var werte = [{x:20 ,y:25 },{x:15 ,y:290 },{x:290 ,y:250 },{x:270 ,y:100 },{x:200 ,y:50 }];
+
   var werte = [{x:20 ,y:25 },{x:15 ,y:grafik.h-10 },{x:grafik.w-10 ,y:grafik.h-50 },{x:grafik.w-30 ,y:100 },{x:grafik.w-100 ,y:50 }];
 
   grafik.setwidth(3);
 
   grafik.setwidth(3);
 
   grafik.polyline(werte,"green");
 
   grafik.polyline(werte,"green");
   werte = [{x:70 ,y:70 },{x:70 ,y:230 },{x:230 ,y:230 },{x:230 ,y:70 }];
+
   werte = [{x:70 ,y:70 },{x:70 ,y:grafik.h-70 },{x:grafik.w-70 ,y:grafik.h-70 },{x:grafik.w-70 ,y:70 }];
 
   grafik.polyfill(werte,"red",0.2);
 
   grafik.polyfill(werte,"red",0.2);
 
   grafik.text(70,70,"0.8em","blue","Text_ro","ro","h"); // Referenzpunkt rechts oben
 
   grafik.text(70,70,"0.8em","blue","Text_ro","ro","h"); // Referenzpunkt rechts oben
 
   grafik.text(70,70,"1em","blue","Text_lu","lu","h"); // Referenzpunkt links unten
 
   grafik.text(70,70,"1em","blue","Text_lu","lu","h"); // Referenzpunkt links unten
   grafik.text(230,150,"1.5em","blue","Text_mm","mm","v"); // Referenzpunkt mitte mitte
+
   grafik.text(grafik.w-70,grafik.h/2,"1.5em","blue","Text_mm","mm","v"); // Referenzpunkt mitte mitte
 
});
 
});
 
 
 
</script>   
 
</script>   
</source>
+
</syntaxhighlight>
<source lang="html5">
+
<syntaxhighlight lang="html5">
 
</head>
 
</head>
 +
 
<body>
 
<body>
   <h1>Test Grafikobjekt</h1>
+
   <h1>Beispiel: Test Grafikobjekt auf Canvas-Basis</h1>
 
   <figure id="plotarea"></figure>
 
   <figure id="plotarea"></figure>
 
</body>
 
</body>
 
</html>
 
</html>
</source>}}}}
+
</syntaxhighlight>}}}}
 
 
  
 
== Das Plotobjekt ==
 
== Das Plotobjekt ==
Zeile 193: Zeile 200:
 
:xstr, ystr: Bezeichner der Objektelemente mit den x- und y-Werten im Datenarray. Defaultwerte sind x und y. Das Datenarray sieht dann so aus: [{x: xwert, y: ywert}{...},...]
 
:xstr, ystr: Bezeichner der Objektelemente mit den x- und y-Werten im Datenarray. Defaultwerte sind x und y. Das Datenarray sieht dann so aus: [{x: xwert, y: ywert}{...},...]
  
sw.plot definiert die Methoden:
+
SW.plot definiert die Methoden:
  
 
;.scale(daten): ermittelt zu den Werten im Array daten die kleinsten und größten Werte.
 
;.scale(daten): ermittelt zu den Werten im Array daten die kleinsten und größten Werte.
:a: Array der Form [{xstr: xwert, ystr: ywert}{...},...]
+
:daten: Array der Form [{xstr: xwert, ystr: ywert}{...},...]
  
 
;.clear(): löscht das Diagramm
 
;.clear(): löscht das Diagramm
Zeile 218: Zeile 225:
 
;.fillopac : Deckkraft der Füllfarbe unter der Plotlinie (0.1)
 
;.fillopac : Deckkraft der Füllfarbe unter der Plotlinie (0.1)
  
{{Beispiel|zeige=Beispiel: JS-Anw-FktPlot-1.html|
+
{{Beispiel|titel=Das Plotobjekt|
{{BeispielCode|<source lang="html5">
+
{{BeispielCode|
<!DOCTYPE html>
+
<syntaxhighlight lang="javascript">
<html lang="de">
 
<head>
 
<meta charset="UTF-8">
 
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<meta name="description" content="Plottet Funktionen">
 
<title>Test Plotobjekt</title>
 
</source>
 
<source lang="css">
 
<style>
 
  #plotarea { background-color:#ffffff; margin:0; height:calc(100vh - 7em); min-height:300px; }
 
</style>
 
</source>
 
<source lang="javascript">
 
<script>
 
 
"use strict";
 
"use strict";
  
var SW = {};
+
var SW = window.SW || {};
 
 
// Hilfsfunktionen
 
  
 +
// Math.log10 wird noch nicht von allen Browsern unterstützt
 
if(!Math.log10) Math.log10 = function(x) { return Math.log(x)/Math.LN10; };
 
if(!Math.log10) Math.log10 = function(x) { return Math.log(x)/Math.LN10; };
 
// Das Grafikobjekt
 
SW.grafik = function(grafikelement) {
 
  // s. oben
 
}
 
  
 
// Das Plotobjekt
 
// Das Plotobjekt
Zeile 261: Zeile 248:
 
   this.labelcol = "black";
 
   this.labelcol = "black";
 
   this.fillopac = 0.1;
 
   this.fillopac = 0.1;
 
 
  // Erstellt ein figure-Element in "parentnode" bei (x,y) mit Größen w und h
 
  var makegraele = function(parentnode,id,x,y,w,h) {
 
    var ele = document.createElement("figure");
 
    ele.style.position = "absolute";
 
    ele.style.margin = "0";
 
    if(typeof id == "string" && id.length) ele.id = id;
 
    if(typeof x == "number") ele.style.left = x + "px";
 
    if(typeof y == "number") ele.style.top = y + "px";
 
    if(typeof w == "number") ele.style.width = w + "px";
 
    if(typeof h == "number") ele.style.height = h + "px";
 
    parentnode.appendChild(ele);
 
    return ele;
 
  } // makegraele
 
 
    
 
    
 
   // Plotbereich anlegen
 
   // Plotbereich anlegen
 
   if(typeof feld == "string") feld = document.getElementById(feld);
 
   if(typeof feld == "string") feld = document.getElementById(feld);
 
   feld.innerHTML = "";
 
   feld.innerHTML = "";
  var w = feld.offsetWidth;
 
  var h = feld.offsetHeight;
 
  var ifeld = makegraele(feld,"","","",w,h);
 
  
 
   // Einige Variablen
 
   // Einige Variablen
Zeile 290: Zeile 260:
 
   var dx,dy,fx,fy;
 
   var dx,dy,fx,fy;
 
   var gr = null;
 
   var gr = null;
  var xlabel = null;
 
  var ylabel = null;
 
  
   // Zu den Werten in a xmin, xmax, ymin und ymax ermiteln
+
   // Zu den Werten in daten xmin, xmax, ymin und ymax ermitteln
 
   this.scale = function(daten) {
 
   this.scale = function(daten) {
     if(xmin==xmax) {
+
     if(xmin==xmax) { // Startwerte beim ersten Datensatz
 
       xmax = xmin = daten[0][xobj];
 
       xmax = xmin = daten[0][xobj];
 
       ymax = ymin = daten[0][yobj];
 
       ymax = ymin = daten[0][yobj];
Zeile 310: Zeile 278:
 
   // Plotbereich leeren
 
   // Plotbereich leeren
 
   this.clear = function() {
 
   this.clear = function() {
     ifeld.innerHTML = "";
+
     feld.innerHTML = "";
 
     xmax = xmin = ymax = ymin = xfak = yfak = 0;
 
     xmax = xmin = ymax = ymin = xfak = yfak = 0;
 
   } // clear
 
   } // clear
Zeile 317: Zeile 285:
 
   // xtext und ytext sind die Beschriftungen der Achsen
 
   // xtext und ytext sind die Beschriftungen der Achsen
 
   this.frame = function(x0,y0,xtext,ytext) {
 
   this.frame = function(x0,y0,xtext,ytext) {
     // Die Bereiche für das Diagramm und die Achsenbeschriftungen anlegen
+
     this.x0 = x0;
    ifeld.innerHTML = "";
+
     this.y0 = y0;
     this.pele = makegraele(ifeld,"",x0,0,w-x0,h-y0);
+
     // Den Bereich für das Diagramm anlegen
     if(xtext.length) xlabel = new SW.grafik(makegraele(ifeld,"",x0,h-y0,w-x0,y0));
+
    feld.innerHTML = "";
     if(ytext.length) ylabel = new SW.grafik(makegraele(ifeld,"",0,0,x0,h-y0));
+
     gr = new SW.grafik(feld);
     gr = new SW.grafik(this.pele);
+
     this.method = gr.method;
 
     // Achsenbeschriftungen
 
     // Achsenbeschriftungen
     if(xtext.length) xlabel.text(xlabel.w/2,0,".9em",this.labelcol,xtext,"mu","h");  
+
     if(xtext.length) gr.text((gr.w-x0)/2+x0,0,".9em",this.labelcol,xtext,"mu","h");  
     if(ytext.length) ylabel.text(10,ylabel.h/2,".9em",this.labelcol,ytext,"mm","v");
+
     if(ytext.length) gr.text(10,(gr.h-y0)/2+y0,".9em",this.labelcol,ytext,"mm","v");
 +
    // xmin und xmax auf die nächst kleinere bzw. größere "glatte" Zahl runden und den
 
     // Abstand der Tics auf glatte Zahlen (1 2 5 0) für x-Achse legen
 
     // Abstand der Tics auf glatte Zahlen (1 2 5 0) für x-Achse legen
 
     if(xmax==xmin) { xmin -= 0.5; xmax += 0.5; }
 
     if(xmax==xmin) { xmin -= 0.5; xmax += 0.5; }
Zeile 331: Zeile 300:
 
     xmin -= dx; xmax += dx;
 
     xmin -= dx; xmax += dx;
 
     dx = xmax - xmin;
 
     dx = xmax - xmin;
     fx = Math.pow(10,Math.floor(Math.log10(dx))-1);
+
     fx = Math.pow(10,Math.floor(Math.log10(dx))-1); // Die Größenordnung ermitteln
 
     xmin = Math.floor(xmin/fx)*fx;
 
     xmin = Math.floor(xmin/fx)*fx;
 
     xmax = Math.ceil(xmax/fx)*fx;
 
     xmax = Math.ceil(xmax/fx)*fx;
     xfak = gr.w/(xmax-xmin);
+
     xfak = (gr.w-x0)/(xmax-xmin);
 
     var tx = ticdist(100*dx/gr.w);
 
     var tx = ticdist(100*dx/gr.w);
 
     var mxmin = Math.ceil(xmin/tx)*tx;
 
     var mxmin = Math.ceil(xmin/tx)*tx;
     // Tics und Zahlen  
+
     // Tics und Zahlen an der x-Achse
 
     gr.setwidth(this.ticwidth);
 
     gr.setwidth(this.ticwidth);
 
     for(var x=mxmin;x<=xmax;x+=tx) {
 
     for(var x=mxmin;x<=xmax;x+=tx) {
       var xx = (x-xmin)*xfak;
+
       var xx = (x-xmin)*xfak + x0;
       gr.line(xx,0,xx,gr.h,this.gridcol);
+
       gr.line(xx,y0,xx,gr.h,this.gridcol);
       if(xtext.length && xx<(gr.w-5) && xx>5)  
+
       if(xtext.length && xx<(gr.w-5) && xx>5) gr.text(xx,y0-2,".8em",this.labelcol,myround(x,tx),"mo","h");
        xlabel.text(xx,xlabel.h-2,".8em",this.labelcol,myround(x,tx),"mo","h");
 
 
     }
 
     }
     // Abstand der Tics auf glatte Zahlen (1 2 5 0) für y-Achse legen
+
    // ymin und ymax auf die nächst kleinere bzw. größere "glatte" Zahl runden und den
 +
     // Abstand der Tics auf glatte Zahlen (1 2 5 0) für x-Achse legen
 
     if(ymax==ymin) { ymin -= 0.5; ymax += 0.5; }
 
     if(ymax==ymin) { ymin -= 0.5; ymax += 0.5; }
 
     dy = (ymax - ymin)/100;  
 
     dy = (ymax - ymin)/100;  
 
     ymin -= dy; ymax += dy;
 
     ymin -= dy; ymax += dy;
 
     dy = ymax - ymin;
 
     dy = ymax - ymin;
     fy = Math.pow(10,Math.floor(Math.log10(dy))-1);
+
     fy = Math.pow(10,Math.floor(Math.log10(dy))-1); // Die Größenordnung ermitteln
 
     ymin = Math.floor(ymin/fy)*fy;
 
     ymin = Math.floor(ymin/fy)*fy;
 
     ymax = Math.ceil(ymax/fy)*fy;
 
     ymax = Math.ceil(ymax/fy)*fy;
     yfak = gr.h/(ymax-ymin);
+
     yfak = (gr.h-y0)/(ymax-ymin);
 
     var ty = ticdist(gr.h<250 ?  50*dy/gr.h : 100*dy/gr.h);
 
     var ty = ticdist(gr.h<250 ?  50*dy/gr.h : 100*dy/gr.h);
 
     var mymin = Math.ceil(ymin/ty)*ty;
 
     var mymin = Math.ceil(ymin/ty)*ty;
     // Tics und Zahlen  
+
     // Tics und Zahlen an der y-Achse
 
     for(var y=mymin;y<=ymax;y+=ty) {
 
     for(var y=mymin;y<=ymax;y+=ty) {
       var yy = (y-ymin)*yfak;
+
       var yy = (y-ymin)*yfak + y0;
       gr.line(0,yy,gr.w,yy,this.gridcol);
+
       gr.line(x0,yy,gr.w,yy,this.gridcol);
       if(ytext.length && yy<(gr.h-5) && yy>5)  
+
       if(ytext.length && yy<(gr.h-5) && yy>5) gr.text(x0-2,yy,".8em",this.labelcol,myround(y,ty),"rm","h");
        ylabel.text(ylabel.w-2,yy,".8em",this.labelcol,myround(y,ty),"rm","h");
 
 
     }
 
     }
 
     gr.setwidth(this.borderwidth);
 
     gr.setwidth(this.borderwidth);
     gr.line(   0,   0,gr.w,   0,this.framecol);
+
     gr.polyline([
    gr.line(gr.w,  0,gr.w,gr.h,this.framecol);
+
      {x:x0, y: y0},
    gr.line(gr.w,gr.h,  0,gr.h,this.framecol);
+
      {x:gr.w-this.borderwidth, y:y0},
    gr.line(  0,gr.h,  0,  0,this.framecol);
+
      {x:gr.w-this.borderwidth, y:gr.h-this.borderwidth},
 +
      {x:x0, y:gr.h-this.borderwidth},
 +
      {x:x0, y:y0}],
 +
      this.framecol);
 
   } // frame
 
   } // frame
  
Zeile 376: Zeile 347:
 
     var arr=[];
 
     var arr=[];
 
     for(var i=0,l=daten.length;i<l;i++)
 
     for(var i=0,l=daten.length;i<l;i++)
       arr.push({x:(daten[i][xobj]-xmin)*xfak, y:(daten[i][yobj]-ymin)*yfak});
+
       arr.push({x:(daten[i][xobj]-xmin)*xfak+this.x0, y:(daten[i][yobj]-ymin)*yfak+this.y0});
 
     if(this.fillopac>0) {
 
     if(this.fillopac>0) {
       var y0;
+
       var fillline;
       if(ymax*ymin<=0) y0 = -ymin*yfak ;  
+
       if(ymax*ymin<=0) fillline = -ymin*yfak+this.y0 ;  
       else if(ymin>0) y0 = 1;
+
       else if(ymin>0) fillline = 1+this.y0;
       else y0 = h-1;
+
       else fillline = gr.h-1;
       arr.push({x:(daten[l-1][xobj]-xmin)*xfak,y:y0});
+
       arr.push({x:(daten[l-1][xobj]-xmin)*xfak+this.x0,y:fillline});
       arr.push({x:(daten[0][xobj]-xmin)*xfak,y:y0});
+
       arr.push({x:(daten[0][xobj]-xmin)*xfak+this.x0,y:fillline});
       arr.push({x:(daten[0][xobj]-xmin)*xfak,y:(daten[0][yobj]-ymin)*yfak});
+
       arr.push({x:(daten[0][xobj]-xmin)*xfak+this.x0,y:(daten[0][yobj]-ymin)*yfak+this.y0});
 
       gr.polyfill(arr,color,this.fillopac);
 
       gr.polyfill(arr,color,this.fillopac);
 
       arr.length -= 3;
 
       arr.length -= 3;
Zeile 410: Zeile 381:
 
   } // ticdist
 
   } // ticdist
 
} // plot
 
} // plot
 +
</syntaxhighlight>
 +
}}
 +
}}
 +
 +
Das folgende Beispiel zeigt die Anwendung des Plotobjekts:
 +
 +
{{Beispiel|zeige=Beispiel: JS-Anw-FktPlot-Canvas-1.html|
 +
{{BeispielCode|
 +
<syntaxhighlight lang="html5">
 +
<!DOCTYPE html>
 +
<html lang="de">
 +
<head>
 +
<meta charset="UTF-8">
 +
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 +
<title>Beispiel: Test Plotobjekt auf Canvas-Basis</title>
 +
</syntaxhighlight>
 +
<syntaxhighlight lang="css">
 +
<style>
 +
  #plotarea { background-color:#ffffff; margin:0; height:calc(100vh - 7em); min-height:300px; }
 +
</style>
 +
</syntaxhighlight>
 +
<syntaxhighlight lang="javascript">
 +
<script src="grafik_canvas.js"></script>
 +
<script src="plot.js"></script>
 +
<script>
 +
 +
"use strict";
  
 
window.addEventListener("DOMContentLoaded",function() {
 
window.addEventListener("DOMContentLoaded",function() {
Zeile 424: Zeile 422:
 
});
 
});
 
</script>
 
</script>
</source>
+
</syntaxhighlight>
<source lang="html5">
+
<syntaxhighlight lang="html5">
 
</head>
 
</head>
 
<body>
 
<body>
   <h1>Test Plotobjekt</h1>
+
   <h1>Beispiel: Test Plotobjekt auf Canvas-Basis</h1>
 
   <figure id="plotarea"></figure>
 
   <figure id="plotarea"></figure>
 
</body>
 
</body>
 
</html>
 
</html>
</source>}}}}
+
</syntaxhighlight>}}}}
 
 
  
 
== Der Funktionsplotter ==
 
== Der Funktionsplotter ==
  
Der Funktionsplotter befindet sich im Eventhandler für das Ereignis <code>DOMContentLoaded</code>. Hier werden die Referenzen auf die HTML-Elemente ermittelt und die benötihten Eventhandler angelegt.
+
Der Funktionsplotter befindet sich im Eventhandler für das Ereignis <code>DOMContentLoaded</code>. Hier werden die Referenzen auf die HTML-Elemente ermittelt und die benötigten Eventhandler angelegt.
  
 
In der Funktion <code>fkt_plotter</code> werden dann die Inputfelder mit den Funktionen und den Min- und Maxwerten für die X-Achse ausgelesen, die Funktionswerte berechnet und der Plot erstellt. Zusätzlich wird noch in einem versteckten Div-Element eine gekürzte Wertetabelle für Screenreader angelegt.
 
In der Funktion <code>fkt_plotter</code> werden dann die Inputfelder mit den Funktionen und den Min- und Maxwerten für die X-Achse ausgelesen, die Funktionswerte berechnet und der Plot erstellt. Zusätzlich wird noch in einem versteckten Div-Element eine gekürzte Wertetabelle für Screenreader angelegt.
Zeile 443: Zeile 440:
 
Leider ist <code>canvas</code> pixelorientiert. Im Beispiel zur Plotfunktion wird zwar die Größe des Plotbereichs in Abhängigkeit von der Größe des Viewports festgelegt, aber die Grafik behält ihre Größe bei nachträglicher Änderung der Viewportgröße. Daher wurde im Funktionsplotter auch noch ein Eventhandler für das Ereignis <code>resize</code> erstellt, der den Plot löscht und mit den neuen Maßen neu anlegt.
 
Leider ist <code>canvas</code> pixelorientiert. Im Beispiel zur Plotfunktion wird zwar die Größe des Plotbereichs in Abhängigkeit von der Größe des Viewports festgelegt, aber die Grafik behält ihre Größe bei nachträglicher Änderung der Viewportgröße. Daher wurde im Funktionsplotter auch noch ein Eventhandler für das Ereignis <code>resize</code> erstellt, der den Plot löscht und mit den neuen Maßen neu anlegt.
  
Das folgende Beispiel zeigt den Funktionsplotter.
+
Das Plotterscript sieht so aus:
  
{{Beispiel|zeige=Beispiel: JS-Anw-FktPlot-2.html|
+
{{Beispiel|titel=Das Plotterscript|
{{BeispielCode|<source lang="html5">
+
{{BeispielCode|
<!DOCTYPE html>
+
<syntaxhighlight lang="javascript">
<html lang="de">
 
<head>
 
<meta charset="UTF-8">
 
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 
<meta name="description" content="Plottet Funktionen">
 
<title>Funktionsplot</title>
 
</source>
 
<source lang="css">
 
<style>
 
  #plotarea { background-color:#ffffff; margin:0; height:calc(100vh - 19em); min-height:300px; }
 
  #plotter { margin-bottom: 1em; border: none }
 
  #plotter div { display:inline-block; vertical-align:top }
 
  #plotter div:nth-child(1) label { display:block; }
 
  #plotter div:nth-child(2) button { display:block; margin: 1em 0 0 5em;} 
 
  #idxmin, #idxmax { text-align:right; width:10em; }
 
  #fkt1, #fkt2, #fkt3 { width:30em; }
 
</style>
 
</source>
 
<source lang="javascript">
 
<script>
 
 
"use strict";
 
"use strict";
 
var SW = {};
 
 
if(!Math.log10) Math.log10 = function(x) { return Math.log(x)/Math.LN10; };
 
 
// Das Grafikobjekt
 
SW.grafik = function(grafikelement) {
 
  // s. oben
 
}
 
 
// Das Plotobjekt
 
SW.plot = function(feld,xstr,ystr) {
 
  // s. oben
 
}
 
  
 
window.addEventListener("DOMContentLoaded",function() {
 
window.addEventListener("DOMContentLoaded",function() {
   // Zahlen einlesen bei Bedarf korrigieren
+
   // Zahlen einlesen und bei Bedarf korrigieren
 
   var get_num = function(e) {
 
   var get_num = function(e) {
 
     var num = e.value;
 
     var num = e.value;
Zeile 494: Zeile 457:
 
   // Der Funktionsplotter
 
   // Der Funktionsplotter
 
   var fkt_plotter = function() {
 
   var fkt_plotter = function() {
     var gl = [];
+
     var funktionen = [];
 
     // Funktionen lesen
 
     // Funktionen lesen
     for(var i=0;i<e_fkt.length;i++ ) gl[i] = e_fkt[i].value.replace(/,/g,".");
+
     for(var i=0;i<e_fkt.length;i++ ) funktionen[i] = e_fkt[i].value;
 
     // xmin und xmax lesen, prüfen und bei Bedarf korrigieren
 
     // xmin und xmax lesen, prüfen und bei Bedarf korrigieren
 
     var xmin = get_num(e_xmin);
 
     var xmin = get_num(e_xmin);
Zeile 505: Zeile 468:
 
       e_xmax.value = xmax;
 
       e_xmax.value = xmax;
 
     }
 
     }
     var dx = (xmax - xmin)/npt;
+
     else if (xmax<xmin) {
    if (dx<0) {
+
       var t = xmax;
      dx = -dx;
 
       var t=xmax;
 
 
       xmax = xmin;
 
       xmax = xmin;
 
       xmin = t;
 
       xmin = t;
Zeile 514: Zeile 475:
 
       e_xmax.value = xmax;
 
       e_xmax.value = xmax;
 
     }
 
     }
 +
    var dx = (xmax - xmin)/npt;
 
     // Werte für Diagramm berechnen ...
 
     // Werte für Diagramm berechnen ...
 
     var daten = [];
 
     var daten = [];
     for(var i=0;i<gl.length;i++) {
+
     for(var i=0;i<funktionen.length;i++) {
 
       try {  
 
       try {  
         if(gl[i].length && gl[i].search(/\S/)!=-1) {
+
         if(funktionen[i].length && funktionen[i].search(/\S/)!=-1) {
           var fun = new Function("x","with(Math){return("+gl[i]+")}");
+
           var fun = new Function("x","with(Math){return("+funktionen[i]+")}");
           var ar= [];
+
           var ar = [];
 
           for(var x=xmin;x<=xmax;x+=dx) ar.push( { x: x, y: fun(x) } );
 
           for(var x=xmin;x<=xmax;x+=dx) ar.push( { x: x, y: fun(x) } );
 
           daten.push(ar);
 
           daten.push(ar);
Zeile 526: Zeile 488:
 
       }
 
       }
 
       catch(e) {  
 
       catch(e) {  
         alert("Fehler in der Formel Nr "+(i+1)+":\n\n"+gl[i]+"\n\n"+e);  
+
         alert("Fehler in der Formel Nr "+(i+1)+":\n\n"+funktionen[i]+"\n\n"+e);  
 
       }
 
       }
 
     }
 
     }
Zeile 540: Zeile 502:
 
       tabelle += "<table>";
 
       tabelle += "<table>";
 
       tabelle += "<caption>Wertetabelle für Funktion "+(j+1)+"</caption>";
 
       tabelle += "<caption>Wertetabelle für Funktion "+(j+1)+"</caption>";
       tabelle += "<thead><tr><th colspan='2'>"+gl[j]+"</th></tr>";
+
       tabelle += "<thead><tr><th colspan='2'>"+funktionen[j]+"</th></tr>";
       tabelle += "<tr><th>x</th><th>y"+j+"</th></tr></thead>";
+
       tabelle += "<tr><th>x</th><th>y"+(j+1)+"</th></tr></thead>";
 
       tabelle += "<tbody>";
 
       tabelle += "<tbody>";
       for(var i=0;i<daten[j].length;i+=npt/20 )  
+
       for(var i=0;i<daten[j].length;i+=npt/20 ) tabelle += "<tr><td>"+daten[j][i].x.toFixed(3)+"</td><td>"+daten[j][i].y.toFixed(3)+"</td></tr>";
        tabelle += "<tr><td>"+daten[j][i].x.toFixed(3)+"</td><td>"+daten[0][i].y.toFixed(3)+"</td></tr>";
 
 
       tabelle += "</tbody>"
 
       tabelle += "</tbody>"
 
       tabelle += "</table>";
 
       tabelle += "</table>";
Zeile 572: Zeile 533:
 
     exp: "exp(x)",
 
     exp: "exp(x)",
 
     log: "log(x)",
 
     log: "log(x)",
 +
    pow: "pow(x,2)",
 
     sin: "sin(x)",
 
     sin: "sin(x)",
 
     sqrt: "sqrt(x)",
 
     sqrt: "sqrt(x)",
Zeile 597: Zeile 559:
 
   for(i=0;i<e_jsfkt.length;i++) e_jsfkt[i].addEventListener("click",function() {  
 
   for(i=0;i<e_jsfkt.length;i++) e_jsfkt[i].addEventListener("click",function() {  
 
     e_fkt[current].value += "+"+ js_fkt[this.innerHTML] });
 
     e_fkt[current].value += "+"+ js_fkt[this.innerHTML] });
  window.addEventListener("resize",function() {
 
    plt = new SW.plot(e_plotarea) ;
 
    fkt_plotter();
 
  });
 
 
   // Plottbereich anlegen und Startwerte plotten
 
   // Plottbereich anlegen und Startwerte plotten
   plt = new SW.plot(e_plotarea) ;
+
   plt = new SW.plot(e_plotarea);
 
   fkt_plotter();
 
   fkt_plotter();
 +
  // SVG kann sich an Größenänderungen anpassen, bei Canvas muss der Plot neu erstellt werden
 +
  if(plt.method=="canvas")
 +
    window.addEventListener("resize",function() {
 +
      plt = new SW.plot(e_plotarea);
 +
      fkt_plotter();
 +
    });
 
});
 
});
</script>
+
</syntaxhighlight>
 +
}}
 +
}}
 +
 
 +
 
 +
Das folgende Beispiel zeigt den Funktionsplotter.
 +
 
 +
{{Beispiel|zeige=Beispiel: JS-Anw-FktPlot-Canvas-2.html|
 +
{{BeispielCode|
 +
<syntaxhighlight lang="html5">
 +
<!DOCTYPE html>
 +
<html lang="de">
 +
<head>
 +
<meta charset="UTF-8">
 +
<meta name="viewport" content="width=device-width, initial-scale=1.0">
 +
<meta name="description" content="Plottet Funktionen">
 +
<title>Beispiel: Funktionsplot auf Canvas-Basis</title>
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="css">
 +
 
 +
<style>
 +
  #plotarea { background-color:#ffffff; margin:0; height:calc(100vh - 19em); min-height:300px; }
 +
  #plotter { margin-bottom: 1em; border: none }
 +
  #plotter div { display:inline-block; vertical-align:top }
 +
  #plotter div:nth-child(1) label { display:block; }
 +
  #plotter div:nth-child(2) button { display:block; margin: 1em 0 0 5em;}
 +
  #idxmin, #idxmax { text-align:right; width:10em; }
 +
  #fkt1, #fkt2, #fkt3 { width:30em; }
 +
</style>
 +
 +
</syntaxhighlight>
 +
 
 +
<syntaxhighlight lang="javascript">
 +
<script src="grafik_canvas.js"></script>
 +
<script src="plot.js"></script>
 +
<script src="fkt_plotter.js"></script>
 +
 +
</syntaxhighlight>
  
</source>
+
<syntaxhighlight lang="html5">
<source lang="javascript">
 
 
</head>
 
</head>
 +
 
<body>
 
<body>
   <h1>Funktionsplot</h1>
+
   <h1>Beispiel: Funktionsplot auf Canvas-Basis</h1>
 
   <p>Stellt mathematische Funktionen grafisch dar.</p>
 
   <p>Stellt mathematische Funktionen grafisch dar.</p>
 
   <figure id="plotarea"></figure>
 
   <figure id="plotarea"></figure>
   <output hidden id="wertetabelle" aria-live="polite"></output>
+
   <div hidden id="wertetabelle" aria-live="polite"></div>
  
 
   <fieldset id="plotter">
 
   <fieldset id="plotter">
Zeile 637: Zeile 639:
 
     <button>tanh</button> und
 
     <button>tanh</button> und
 
     <button>Puls</button>.
 
     <button>Puls</button>.
   </p>
+
   </p>
  
 
   <p id="jsfkt">Javascript unterstützt folgende Funktionen:
 
   <p id="jsfkt">Javascript unterstützt folgende Funktionen:
Zeile 647: Zeile 649:
 
     <button>exp</button>,
 
     <button>exp</button>,
 
     <button>log</button>,
 
     <button>log</button>,
 +
    <button>pow</button>,
 
     <button>sin</button>,
 
     <button>sin</button>,
 
     <button>sqrt</button> und
 
     <button>sqrt</button> und
Zeile 653: Zeile 656:
 
</body>
 
</body>
 
</html>
 
</html>
</source>}}}}
+
</syntaxhighlight>
 
+
}}
 +
}}
  
[[Kategorie: JavaScript]]
+
{{Kategorie|JavaScript|Funktionsplotter}}
 +
[[Kategorie:Canvas]]
 +
[[Kategorie:Tutorial]]
 +
[[Kategorie:Datenvisualisierung]]

Aktuelle Version vom 7. Juli 2024, 19:45 Uhr


Es wird ein Script vorgestellt, das mathematische Funktionen über Inputelemente einliest und grafisch darstellt. Für die grafische Darstellung wird das Canvas-Element verwendet.

Es gibt auch eine Version des Funktionsplotters, die das SVG-Element verwendet.

Das Script besteht aus dem Grafikobjekt grafik, dem Plotobjekt plot und dem Funktionsplotter.


Das Grafikobjekt

Das Grafikobjekt ist die Schnittstelle zur Canvas-API.

SW.grafik(grafikelement)
legt das Canvas-Element an.
grafikelement: ID des Elements oder Referenz auf das Element, in dem das Canvas-Element angelegt wird.

SW.grafik definiert die Methoden:

.setwidth(width)
setzt die Linienstärke.
width: Linienstärke in Pixeln.
.line(xs,ys,xe,ye,color)
zeichnet eine Linie von (xs,ys) nach (xe,ye) in Farbe color.
xs,ys: Koordinaten des Startpunkts in Pixeln,
xe,ye: Koordinaten des Endpunkts in Pixeln,
color: Linienfarbe im css-Format.
.polyline(points,color)
zeichnet einen Linienzug.
points: Array der Form [{x: xwert, y: ywert},{…},…] mit den Koordinaten der Stützpunkte in Pixeln.
color: Linienfarbe im css-Format.
.polyfill(points,color,a)
zeichnet einen Linienzug. Die vom Linienzug umschlossene Fläche wird eingefärbt.
points: Array der Form [{x: xwert, y: ywert},{…},…] mit den Koordinaten der Stützpunkte in Pixeln.
color: Füllfarbe im css-Format.
alpha: Transparenz, 0<=alpha<=1
.text(x,y,size,color,text,align,direction)
gibt Text aus.
x,y: Koordinaten des Bezugspunkts in Pixeln,
size: Texthöhe im css-Format
color: Linienfarbe im css-Format.
align: String aus zwei Buchstaben, gibt die Lage des Bezugspunkts relativ zum Text an. Der erste Buchstabe darf die Werte l, m, oder r annehmen für links, mitte oder rechts. Der zweite Buchstabe darf die Werte o, m oder u annehmen für oben, mitte oder unten.
direction: gibt die Laufrichtung des Textes an, darf die Werte h und v für horizontal und vertikal annehmen.
.del()
löscht die Grafik.
Das Grafikobjekt
"use strict";

var SW = window.SW || {};

// Das Grafikobjekt
SW.grafik = function(grafikelement) {
  this.method = "canvas";
  // Canvas in Größe des "grafikelement" anlegen
  if(typeof grafikelement == "string") grafikelement = document.getElementById(grafikelement);
  this.w = grafikelement.offsetWidth;
  this.h = grafikelement.offsetHeight;
  var cv = document.createElement("canvas");
  cv.width = this.w;
  cv.height = this.h;
  grafikelement.appendChild(cv);
  var context = cv.getContext("2d"); 
  context.lineWidth = 1;
  context.globalAlpha = 1.0;
  
  // Linienstärke setzen
  this.setwidth = function(width) {
    context.lineWidth = width;
  } // setwidth
  
  // Linie von (xs,ys) nach (xe,ye) in Farbe color zeichnen
  this.line = function(xs,ys,xe,ye,color) {
    context.strokeStyle = color;
    context.beginPath();
    context.moveTo(Math.round(xs),Math.round(this.h-ys));
    context.lineTo(Math.round(xe),Math.round(this.h-ye));
    context.stroke();
  } // line

  // Polylinie mit den Werten in points in Farbe color zeichnen
  this.polyline = function(points,color) { 
    context.strokeStyle = color;
    context.beginPath();
    context.moveTo(Math.round(points[0].x),this.h-Math.round(points[0].y));
    for(var i=1,l=points.length;i<l;i++) 
      context.lineTo(Math.round(points[i].x),this.h-Math.round(points[i].y));
    context.stroke();
  } // polyline

  // Polylinie mit den Werten in points zeichnen
  // Die von der Polylinie umschlossene Fläche wird in Farbe color mit Alphawert alpha eingefärbt
  this.polyfill = function(points,color,alpha) { 
    context.fillStyle = color;
    context.globalAlpha = alpha;
    context.beginPath();
    context.moveTo(Math.round(points[0].x),this.h-Math.round(points[0].y));
    for(var i=1,l=points.length;i<l;i++) 
      context.lineTo(Math.round(points[i].x),this.h-Math.round(points[i].y));
    context.fill();
    context.globalAlpha = 1.0;
  } // polyfill
  
  // Text an (x,y) ausgeben
  // size: Schriftgröße
  // text: Text
  // align: Bezug für (x,y), zwei Buchstaben, z.B. lu für links unten, s. case
  // diretion: Textrichtung: v für vertikal, sonst horizontal
  this.text = function(x,y,size,color,text,align,direction) {
    var align_h = "m";
    var align_v = "m";
    if(align && align.length) {
      align_h = align.substr(0,1);
      if(align.length>1) align_v = align.substr(1,1);
    }
    context.save();
    context.translate(x,this.h-y);
    if(direction && direction=="v") 
      context.rotate(1.5*Math.PI);
    switch(align_h) {
      case "l": context.textAlign = "start"; break;
      case "m": context.textAlign = "center"; break;
      case "r": context.textAlign = "end"; break;
      default:  context.textAlign = "center"; break;
    }
    switch(align_v) {
      case "o": context.textBaseline = "top" ; break;
      case "m": context.textBaseline = "middle" ; break;
      case "u": context.textBaseline = "bottom" ; break;
      default:  context.textBaseline = "middle" ; break;
    }
    context.font = size + " sans-serif";
    context.fillStyle = color;
    context.fillText(text,0,0);
    context.restore();
  } // text

  // Canvas löschen
  this.del = function() {
    context.clearRect(0, 0, this.w, this.h);
  } // del
} // grafik

Das folgende Beispiel zeigt die Anwendung des Grafikobjekts.

Beispiel ansehen …
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Beispiel: Test Grafikobjekt auf Canvas-Basis</title>
<style>
  #plotarea { background-color:#ffffff; width:90%; height:80vh; border: 1px solid black }
</style>
<script src="grafik_canvas.js"></script>
<script>
"use strict";
window.addEventListener("DOMContentLoaded",function() {
  var grafik = new SW.grafik("plotarea");
  grafik.line(20,20,grafik.w-20,40,"black");
  var werte = [{x:20 ,y:25 },{x:15 ,y:grafik.h-10 },{x:grafik.w-10 ,y:grafik.h-50 },{x:grafik.w-30 ,y:100 },{x:grafik.w-100 ,y:50 }];
  grafik.setwidth(3);
  grafik.polyline(werte,"green");
  werte = [{x:70 ,y:70 },{x:70 ,y:grafik.h-70 },{x:grafik.w-70 ,y:grafik.h-70 },{x:grafik.w-70 ,y:70 }];
  grafik.polyfill(werte,"red",0.2);
  grafik.text(70,70,"0.8em","blue","Text_ro","ro","h"); // Referenzpunkt rechts oben
  grafik.text(70,70,"1em","blue","Text_lu","lu","h"); // Referenzpunkt links unten
  grafik.text(grafik.w-70,grafik.h/2,"1.5em","blue","Text_mm","mm","v"); // Referenzpunkt mitte mitte
});
</script>
</head>

<body>
  <h1>Beispiel: Test Grafikobjekt auf Canvas-Basis</h1>
  <figure id="plotarea"></figure>
</body>
</html>

Das Plotobjekt

Das Plotobjekt erstellt das Diagramm mit Achsen und Achsenbeschriftung.

SW.plot(feld,xstr,ystr)
legt den Bereich für das Diagramm an.
Feld: ID des Elements oder Referenz auf das Element, in dem das Diagramm angelegt wird.
xstr, ystr: Bezeichner der Objektelemente mit den x- und y-Werten im Datenarray. Defaultwerte sind x und y. Das Datenarray sieht dann so aus: [{x: xwert, y: ywert}{...},...]

SW.plot definiert die Methoden:

.scale(daten)
ermittelt zu den Werten im Array daten die kleinsten und größten Werte.
daten: Array der Form [{xstr: xwert, ystr: ywert}{...},...]
.clear()
löscht das Diagramm
.frame(x0,y0,xtext,ytext)
legt das Achsenkreuz an.
x0, y0: Koordinaten der linken unteren Ecke in Pixel.
xtext, ytext: Beschriftung der Achsen.
.plot(daten,color) Plottet die Daten.
daten: Array der Form [{xstr: xwert, ystr: ywert}{...},...]
color: Linienfarbe im css-Format.

Weitere Eigenschaften und deren Defaultwerte:

.ticwidth 
Linienstärke der Gitterlinien (1)
.linewidth 
Linienstärke der Plotlinien (1)
.borderwidth 
Linienstärke des Rahmens um den Plot (2)
.framecol 
Farbe des Rahmens ("black")
.gridcol 
Farbe der Gitterlinien ("gray")
.labelcol 
Farbe der Achsenbeschriftung ("black")
.fillopac 
Deckkraft der Füllfarbe unter der Plotlinie (0.1)
Das Plotobjekt
"use strict";

var SW = window.SW || {};

// Math.log10 wird noch nicht von allen Browsern unterstützt
if(!Math.log10) Math.log10 = function(x) { return Math.log(x)/Math.LN10; };

// Das Plotobjekt
// feld ist das Objekt bzw. dessen Id, in dem das Diagramm erstellt werden soll
// xstr und ystr geben die Bezeichner der Objektelemente mit den x- und y-Werten im Datenarray an.
// Defaultwerte sind x und y. Das Datenarray sieht dan so aus: [{x:xwert,y:ywert}{...},...]
SW.plot = function(feld,xstr,ystr) {
  // Defaultwerte
  this.ticwidth = 1;
  this.linewidth = 1;
  this.borderwidth = 2;
  this.framecol = "black";
  this.gridcol = "gray";
  this.labelcol = "black";
  this.fillopac = 0.1;
  
  // Plotbereich anlegen
  if(typeof feld == "string") feld = document.getElementById(feld);
  feld.innerHTML = "";

  // Einige Variablen
  var xobj = xstr?xstr:"x";
  var yobj = ystr?ystr:"y";
  var xmin=0,xmax=0,ymin=0,ymax=0;
  var xfak=0,yfak=0;
  var dx,dy,fx,fy;
  var gr = null;

  // Zu den Werten in daten xmin, xmax, ymin und ymax ermitteln
  this.scale = function(daten) {
    if(xmin==xmax) { // Startwerte beim ersten Datensatz
      xmax = xmin = daten[0][xobj];
      ymax = ymin = daten[0][yobj];
    }
    for(var i=1;i<daten.length;i++) {
      var t = daten[i];
      if(t[xobj]<xmin) xmin = t[xobj];
      if(t[xobj]>xmax) xmax = t[xobj];
      if(t[yobj]<ymin) ymin = t[yobj];
      if(t[yobj]>ymax) ymax = t[yobj];
    }
  } // scale
  
  // Plotbereich leeren
  this.clear = function() {
    feld.innerHTML = "";
    xmax = xmin = ymax = ymin = xfak = yfak = 0;
  } // clear

  // Achsenkreuz, Tics und Beschriftung, linke untere Ecke bei (x0,y0)
  // xtext und ytext sind die Beschriftungen der Achsen
  this.frame = function(x0,y0,xtext,ytext) {
    this.x0 = x0;
    this.y0 = y0;
    // Den Bereich für das Diagramm anlegen
    feld.innerHTML = "";
    gr = new SW.grafik(feld);
    this.method = gr.method;
    // Achsenbeschriftungen
    if(xtext.length) gr.text((gr.w-x0)/2+x0,0,".9em",this.labelcol,xtext,"mu","h"); 
    if(ytext.length) gr.text(10,(gr.h-y0)/2+y0,".9em",this.labelcol,ytext,"mm","v");
    // xmin und xmax auf die nächst kleinere bzw. größere "glatte" Zahl runden und den 
    // Abstand der Tics auf glatte Zahlen (1 2 5 0) für x-Achse legen
    if(xmax==xmin) { xmin -= 0.5; xmax += 0.5; }
    dx = (xmax - xmin)/100;    
    xmin -= dx; xmax += dx;
    dx = xmax - xmin;
    fx = Math.pow(10,Math.floor(Math.log10(dx))-1); // Die Größenordnung ermitteln
    xmin = Math.floor(xmin/fx)*fx;
    xmax = Math.ceil(xmax/fx)*fx;
    xfak = (gr.w-x0)/(xmax-xmin);
    var tx = ticdist(100*dx/gr.w);
    var mxmin = Math.ceil(xmin/tx)*tx;
    // Tics und Zahlen an der x-Achse
    gr.setwidth(this.ticwidth);
    for(var x=mxmin;x<=xmax;x+=tx) {
      var xx = (x-xmin)*xfak + x0;
      gr.line(xx,y0,xx,gr.h,this.gridcol);
      if(xtext.length && xx<(gr.w-5) && xx>5) gr.text(xx,y0-2,".8em",this.labelcol,myround(x,tx),"mo","h");
    }
    // ymin und ymax auf die nächst kleinere bzw. größere "glatte" Zahl runden und den 
    // Abstand der Tics auf glatte Zahlen (1 2 5 0) für x-Achse legen
    if(ymax==ymin) { ymin -= 0.5; ymax += 0.5; }
    dy = (ymax - ymin)/100; 
    ymin -= dy; ymax += dy;
    dy = ymax - ymin;
    fy = Math.pow(10,Math.floor(Math.log10(dy))-1); // Die Größenordnung ermitteln
    ymin = Math.floor(ymin/fy)*fy;
    ymax = Math.ceil(ymax/fy)*fy;
    yfak = (gr.h-y0)/(ymax-ymin);
    var ty = ticdist(gr.h<250 ?  50*dy/gr.h : 100*dy/gr.h);
    var mymin = Math.ceil(ymin/ty)*ty;
    // Tics und Zahlen an der y-Achse
    for(var y=mymin;y<=ymax;y+=ty) {
      var yy = (y-ymin)*yfak + y0;
      gr.line(x0,yy,gr.w,yy,this.gridcol);
      if(ytext.length && yy<(gr.h-5) && yy>5) gr.text(x0-2,yy,".8em",this.labelcol,myround(y,ty),"rm","h");
    }
    gr.setwidth(this.borderwidth);
    gr.polyline([
      {x:x0, y: y0},
      {x:gr.w-this.borderwidth, y:y0},
      {x:gr.w-this.borderwidth, y:gr.h-this.borderwidth},
      {x:x0, y:gr.h-this.borderwidth},
      {x:x0, y:y0}],
      this.framecol);
  } // frame

  // Daten Plotten
  // daten: Datenarray mit Objekten mit den x- und y-Werten
  // color Diagrammfarbe
  this.plot = function(daten,color) {
    var arr=[];
    for(var i=0,l=daten.length;i<l;i++)
      arr.push({x:(daten[i][xobj]-xmin)*xfak+this.x0, y:(daten[i][yobj]-ymin)*yfak+this.y0});
    if(this.fillopac>0) {
      var fillline;
      if(ymax*ymin<=0) fillline = -ymin*yfak+this.y0 ; 
      else if(ymin>0) fillline = 1+this.y0;
      else fillline = gr.h-1;
      arr.push({x:(daten[l-1][xobj]-xmin)*xfak+this.x0,y:fillline});
      arr.push({x:(daten[0][xobj]-xmin)*xfak+this.x0,y:fillline});
      arr.push({x:(daten[0][xobj]-xmin)*xfak+this.x0,y:(daten[0][yobj]-ymin)*yfak+this.y0});
      gr.polyfill(arr,color,this.fillopac);
      arr.length -= 3;
    }
    gr.setwidth(this.linewidth);
    gr.polyline(arr,color);
  } // plot
  
  // Hilfsfunktionen zum Runden
  var myround = function(z,d) { 
    var l10 = Math.floor(Math.log10(d));
    var f = Math.pow(10,l10); 
    var zz = Math.round(z/f)*f;
    var zzz = Number(zz.toPrecision(15)).toString(10);
    return zzz; 
  }
  
  // Hilfsfunktion zum berechnen des Abstands der Achsen-Tics, Abstände auf 1 2 5 0 gerundet
  var ticdist = function(td) { 
    var td10 = Math.pow(10,Math.floor(Math.log10(td)));
    td = Math.round(td/td10);
    td = Number(String(td).replace(/3/,"2").replace(/[4567]/,"5").replace(/[89]/,"10"));
    td *= td10;
    return td;
  } // ticdist
} // plot

Das folgende Beispiel zeigt die Anwendung des Plotobjekts:

Beispiel ansehen …
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Beispiel: Test Plotobjekt auf Canvas-Basis</title>
<style>
  #plotarea { background-color:#ffffff; margin:0; height:calc(100vh - 7em); min-height:300px; }
</style>
<script src="grafik_canvas.js"></script>
<script src="plot.js"></script>
<script>

"use strict";

window.addEventListener("DOMContentLoaded",function() {
  var daten = [],
    xmin = -Math.PI,
    xmax = Math.PI,
    npt = 1000;
  var dx = ( xmax - xmin ) / npt;    
  for(var x=xmin;x<=xmax;x+=dx) daten.push( { x: x, y: Math.sin(x) } );
  var plt = new SW.plot("plotarea") ;
  plt.scale(daten);
  plt.frame(50,35,"x","sin(x)");
  plt.plot(daten,"red");
});
</script>
</head>
<body>
  <h1>Beispiel: Test Plotobjekt auf Canvas-Basis</h1>
  <figure id="plotarea"></figure>
</body>
</html>

Der Funktionsplotter

Der Funktionsplotter befindet sich im Eventhandler für das Ereignis DOMContentLoaded. Hier werden die Referenzen auf die HTML-Elemente ermittelt und die benötigten Eventhandler angelegt.

In der Funktion fkt_plotter werden dann die Inputfelder mit den Funktionen und den Min- und Maxwerten für die X-Achse ausgelesen, die Funktionswerte berechnet und der Plot erstellt. Zusätzlich wird noch in einem versteckten Div-Element eine gekürzte Wertetabelle für Screenreader angelegt.

Leider ist canvas pixelorientiert. Im Beispiel zur Plotfunktion wird zwar die Größe des Plotbereichs in Abhängigkeit von der Größe des Viewports festgelegt, aber die Grafik behält ihre Größe bei nachträglicher Änderung der Viewportgröße. Daher wurde im Funktionsplotter auch noch ein Eventhandler für das Ereignis resize erstellt, der den Plot löscht und mit den neuen Maßen neu anlegt.

Das Plotterscript sieht so aus:

Das Plotterscript
"use strict";

window.addEventListener("DOMContentLoaded",function() {
  // Zahlen einlesen und bei Bedarf korrigieren
  var get_num = function(e) {
    var num = e.value;
    if (isNaN(num)) { num = num.replace(/,/g,"."); }
    if (isNaN(num) || num.length==0 ) { num = 0.0; e.value = num; }
    return parseFloat(num);
  }
  // Der Funktionsplotter
  var fkt_plotter = function() {
    var funktionen = [];
    // Funktionen lesen
    for(var i=0;i<e_fkt.length;i++ ) funktionen[i] = e_fkt[i].value;
    // xmin und xmax lesen, prüfen und bei Bedarf korrigieren
    var xmin = get_num(e_xmin);
    var xmax = get_num(e_xmax);
    if (xmax==xmin) {
      xmin -= 0.5; xmax += 0.5;
      e_xmin.value = xmin;
      e_xmax.value = xmax;
    }
    else if (xmax<xmin) {
      var t = xmax;
      xmax = xmin;
      xmin = t;
      e_xmin.value = xmin;
      e_xmax.value = xmax;
    }
    var dx = (xmax - xmin)/npt;
    // Werte für Diagramm berechnen ...
    var daten = [];
    for(var i=0;i<funktionen.length;i++) {
      try { 
        if(funktionen[i].length && funktionen[i].search(/\S/)!=-1) {
          var fun = new Function("x","with(Math){return("+funktionen[i]+")}");
          var ar = [];
          for(var x=xmin;x<=xmax;x+=dx) ar.push( { x: x, y: fun(x) } );
          daten.push(ar);
        }
      }
      catch(e) { 
        alert("Fehler in der Formel Nr "+(i+1)+":\n\n"+funktionen[i]+"\n\n"+e); 
      }
    }
    // ... und plotten
    plt.clear();
    plt.fillopac = .3;
    for(var i=0;i<daten.length;i++) if(!isNaN(daten[i][0].y)) plt.scale(daten[i]);
    plt.frame(50,35,"x","y");
    for(var i=0;i<daten.length;i++) if(!isNaN(daten[i][0].y)) plt.plot(daten[i],cols[i]);
    // ... und Ausgabe in versteckter Tabelle für Screenreader
    var tabelle = "";
    for(var j=0;j<daten.length;j++) {
      tabelle += "<table>";
      tabelle += "<caption>Wertetabelle für Funktion "+(j+1)+"</caption>";
      tabelle += "<thead><tr><th colspan='2'>"+funktionen[j]+"</th></tr>";
      tabelle += "<tr><th>x</th><th>y"+(j+1)+"</th></tr></thead>";
      tabelle += "<tbody>";
      for(var i=0;i<daten[j].length;i+=npt/20 ) tabelle += "<tr><td>"+daten[j][i].x.toFixed(3)+"</td><td>"+daten[j][i].y.toFixed(3)+"</td></tr>";
      tabelle += "</tbody>"
      tabelle += "</table>";
    }
    e_wertetabelle.innerHTML = tabelle;
  } // fkt_plotter

  var i,j,e_plotarea,e_wertetabelle,e_fkt=[],e_xmin,e_xmax,plt,e_jsfkt,e_vdeffkt;
  var npt = 1000;
  var current = 0;
  var cols = ["#ff0000","#008000","#0000ff"];
  var vdef_fkt = { 
    AM: "(1-0.3*sin(x/4))*sin(3*x)",
    PM: "sin(3*x-2*sin(x/3))",
    "sin(x)/x": "sin(x)/x",
    sinh: "(exp(x)-exp(-x))/2",
    cosh: "(exp(x)+exp(-x))/2",
    tanh: "(exp(x)-exp(-x))/(exp(x)+exp(-x))",
    Puls: "(function(){var y=0;for(var ii=10;ii<30;ii++) y+=sin(ii*x);return y})(x)"
  };
  var js_fkt = {
    abs: "abs(x)",
    acos: "acos(x)",
    asin: "asin(x)",
    atan: "atan(x)",
    cos: "cos(x)",
    exp: "exp(x)",
    log: "log(x)",
    pow: "pow(x,2)",
    sin: "sin(x)",
    sqrt: "sqrt(x)",
    tan: "tan(x)",
  };

  // Referenzen
  e_plotarea = document.getElementById("plotarea");
  e_wertetabelle = document.getElementById("wertetabelle");
  e_xmin = document.getElementById("idxmin");
  e_xmax = document.getElementById("idxmax");
  e_jsfkt = document.querySelectorAll("#jsfkt button"); 
  e_vdeffkt = document.querySelectorAll("#vdeffkt button"); 
  for(i=0;i<3;i++) {
    e_fkt[i] = document.getElementById("fkt"+(i+1));
    document.getElementById("f"+(i+1)).style.color = cols[i];
  }
  // Die Eventhandler
  document.getElementById("plotbutton").addEventListener("click",fkt_plotter);
  document.getElementById("fkt1").addEventListener("click",function(){ current = 0 });
  document.getElementById("fkt2").addEventListener("click",function(){ current = 1 });
  document.getElementById("fkt3").addEventListener("click",function(){ current = 2 });
  for(i=0;i<e_vdeffkt.length;i++) e_vdeffkt[i].addEventListener("click",function() { 
    e_fkt[current].value = vdef_fkt[this.innerHTML] });
  for(i=0;i<e_jsfkt.length;i++) e_jsfkt[i].addEventListener("click",function() { 
    e_fkt[current].value += "+"+ js_fkt[this.innerHTML] });
  // Plottbereich anlegen und Startwerte plotten
  plt = new SW.plot(e_plotarea);
  fkt_plotter();
  // SVG kann sich an Größenänderungen anpassen, bei Canvas muss der Plot neu erstellt werden
  if(plt.method=="canvas")
    window.addEventListener("resize",function() {
      plt = new SW.plot(e_plotarea);
      fkt_plotter();
    });
});


Das folgende Beispiel zeigt den Funktionsplotter.

Beispiel ansehen …
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="Plottet Funktionen">
<title>Beispiel: Funktionsplot auf Canvas-Basis</title>
<style>
  #plotarea { background-color:#ffffff; margin:0; height:calc(100vh - 19em); min-height:300px; }
  #plotter { margin-bottom: 1em; border: none }
  #plotter div { display:inline-block; vertical-align:top }
  #plotter div:nth-child(1) label { display:block; }
  #plotter div:nth-child(2) button { display:block; margin: 1em 0 0 5em;}	
  #idxmin, #idxmax { text-align:right; width:10em; }
  #fkt1, #fkt2, #fkt3 { width:30em; }
</style>
<script src="grafik_canvas.js"></script>
<script src="plot.js"></script>
<script src="fkt_plotter.js"></script>
</head>

<body>
  <h1>Beispiel: Funktionsplot auf Canvas-Basis</h1>
  <p>Stellt mathematische Funktionen grafisch dar.</p>
  <figure id="plotarea"></figure>
  <div hidden id="wertetabelle" aria-live="polite"></div>

  <fieldset id="plotter">
  <div aria-live="polite">
    <label id="f1">f<sub>1</sub>(x) = <input type="text" id="fkt1" value="sin(x)/x"></label>
    <label id="f2">f<sub>2</sub>(x) = <input type="text" id="fkt2" value="sin(x)"></label>
    <label id="f3">f<sub>3</sub>(x) = <input type="text" id="fkt3" value="cos(x)"></label>
  </div>
  <div>
    <label>xmin = <input lang="en" type="number" step="any" id="idxmin" value="-20"></label>
    <label>xmax = <input lang="en" type="number" step="any" id="idxmax" value="20"></label>
    <button id="plotbutton">Plotten</button>
  </div>
  </fieldset>

  <p id="vdeffkt">Vordefinierte Funktionen:
    <button>AM</button>,
    <button>PM</button>,
    <button>sin(x)/x</button>,
    <button>sinh</button>,
    <button>cosh</button>,
    <button>tanh</button> und
    <button>Puls</button>.
  </p>	

  <p id="jsfkt">Javascript unterstützt folgende Funktionen:
    <button>abs</button>,
    <button>acos</button>,
    <button>asin</button>,
    <button>atan</button>,
    <button>cos</button>,
    <button>exp</button>,
    <button>log</button>,
    <button>pow</button>,
    <button>sin</button>,
    <button>sqrt</button> und
    <button>tan</button>.
  Bitte den Definitionsbereich der Funktionen beachten.</p>
</body>
</html>