364 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			364 lines
		
	
	
		
			9.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  **      ==============================
 | |
|  **       O           O      O   OOOO
 | |
|  **       O           O     O O  O   O
 | |
|  **       O           O     O O  O   O
 | |
|  **       OOOO   OOOO O     OOO  OOOO
 | |
|  **       O   O       O    O   O O   O
 | |
|  **       O   O       O    O   O O   O
 | |
|  **       OOOO        OOOO O   O OOOO
 | |
|  **      ==============================
 | |
|  **      Dr. Stefan Bosse http://www.bsslab.de
 | |
|  **
 | |
|  **      COPYRIGHT: THIS SOFTWARE, EXECUTABLE AND SOURCE CODE IS OWNED
 | |
|  **                 BY THE AUTHOR(S).
 | |
|  **                 THIS SOURCE CODE MAY NOT BE COPIED, EXTRACTED,
 | |
|  **                 MODIFIED, OR OTHERWISE USED IN A CONTEXT
 | |
|  **                 OUTSIDE OF THE SOFTWARE SYSTEM.
 | |
|  **
 | |
|  **    $AUTHORS:     Stefan Bosse
 | |
|  **    $INITIAL:     (C) 2006-2017 bLAB
 | |
|  **    $CREATED:     1-10-17 by sbosse.
 | |
|  **    $VERSION:     1.3.1
 | |
|  **
 | |
|  **    $INFO:
 | |
|  **
 | |
|  **  X11/Widget library - High-level plot/data visualization widgets extending the window class 
 | |
|  **
 | |
|  **
 | |
|  ** plot({
 | |
|  **  type:'vector',
 | |
|  **  x,y,min?,max?,
 | |
|  **  width,height
 | |
|  **  margin?,
 | |
|  **  columns?,
 | |
|  **  label?:{x0:string,x1:string,y0:string,y1:string},
 | |
|  **  bar?: {
 | |
|  **    fill:{
 | |
|  **      color:string
 | |
|  **    }
 | |
|  **  },
 | |
|  **  init?:function (i) { return 0.0 } // Creates vector:number [] attribute!
 | |
|  ** })
 | |
|  **
 | |
|  **    $ENDOFINFO
 | |
| */
 | |
| 
 | |
| var Comp = Require('com/compat');
 | |
| 
 | |
| 
 | |
| function window(options) {
 | |
|   this.plots={};
 | |
| }
 | |
| 
 | |
| function update(dst,src) {
 | |
|   for(var a in src) {
 | |
|     if (typeof dst[a] == 'object' && typeof src[a] == 'object')
 | |
|       update(dst[a],src[a]);
 | |
|     else
 | |
|       dst[a]=src[a];
 | |
|   }    
 | |
| }
 | |
| function modifies(attr,shape) {
 | |
|   for(var a in attr) {
 | |
|     if (typeof attr[a] == 'object' && typeof shape[a] == 'object')
 | |
|       return modifies(attr[a],shape[a]);
 | |
|     else if (attr[a] != shape[a]) return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| function makeLabel(options,modify) {
 | |
|   if (options.label.y0) {
 | |
|     if (modify) this.remove(options.id+':Ly0');
 | |
|     this.add({
 | |
|       id:options.id+':Ly0',
 | |
|       shape:'text',
 | |
|       align:'left',
 | |
|       x:options.x-15,
 | |
|       y:options.y+options.height,
 | |
|       text:options.label.y0
 | |
|     });
 | |
|   }
 | |
|   if (options.label.y1) {
 | |
|     if (modify) this.remove(options.id+':Ly1');
 | |
|     this.add({
 | |
|       id:options.id+':Ly1',
 | |
|       shape:'text',
 | |
|       align:'left',
 | |
|       x:options.x-15,
 | |
|       y:options.y,
 | |
|       text:options.label.y1
 | |
|     });
 | |
|   }
 | |
|   if (options.label.x0) {
 | |
|     if (modify) this.remove(options.id+':Lx0');
 | |
|     this.add({
 | |
|       id:options.id+':Lx0',
 | |
|       shape:'text',
 | |
|       align:'left',
 | |
|       x:options.x,
 | |
|       y:options.y+options.height+15,
 | |
|       text:options.label.x0
 | |
|     });
 | |
|   }
 | |
|   if (options.label.x1) {
 | |
|     if (modify) this.remove(options.id+':Lx1');
 | |
|     this.add({
 | |
|       id:options.id+':Lx1',
 | |
|       shape:'text',
 | |
|       align:'left',
 | |
|       x:options.x+options.width,
 | |
|       y:options.y+options.height,
 | |
|       text:options.label.x1
 | |
|     });
 | |
|   }
 | |
| }
 | |
| /** Plot a metric matrix (image) or a grid (2d plot) of nodes
 | |
| **
 | |
| ** typeof options = {
 | |
| **  type='matrix',
 | |
| **  x,y,     
 | |
| **  rows?,columns?,margin?,
 | |
| **  align?={'center'|'left'},
 | |
| **  node: {
 | |
| **    shape,
 | |
| **    width,height,
 | |
| **    fill:{color:string},
 | |
| **    line:{color:string,width?}
 | |
| **  },
 | |
| **  init?: function (^i,^j) -> ^attr | [][],
 | |
| **  map?: function (^i,^j,^v) -> ^attr:object,
 | |
| **  matrix?: *[][]
 | |
| ** }
 | |
| */
 | |
| function plotMatrix (options) {
 | |
|   for(var j=0; j<options.rows;j++) {
 | |
|     for(var i=0; i<options.columns; i++) {
 | |
|       var node=Comp.obj.copy(options.node);
 | |
|       node.x=options.x+i*(node.width+options.margin);
 | |
|       node.y=options.y+j*(node.height+options.margin);
 | |
|       node.id=options.id+':N'+(i+j*options.columns);
 | |
|       if (typeof options.init == 'function') 
 | |
|         update(node,options.init(i,j));
 | |
|       else if (typeof options.map == 'function' && options.matrix) 
 | |
|         update(node,options.map(i,j,options.matrix[j][i]));
 | |
|       this.add(node);
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| 
 | |
| function deleteLine (options,i) {
 | |
|   var id=options.id+':L'+i;
 | |
|   this.remove(this.objects[id]);
 | |
| }
 | |
| 
 | |
| function plotLine (options,i) {
 | |
|   var x0,y0,x1,y1,id;
 | |
|   x0=options.x+int(options.width/options.columns*i);
 | |
|   x1=options.x+int(options.width/options.columns*(i+1));
 | |
|   y0=options.y+options.height-int(options.height/(options.max-options.min)*options.vector[i]);
 | |
|   y1=options.y+options.height-int(options.height/(options.max-options.min)*options.vector[i+1]);
 | |
|   this.add({
 | |
|     id:options.id+':L'+i,
 | |
|     shape:'line',
 | |
|     line:{
 | |
|       width:options.line.width||1,
 | |
|       color:options.line.color||'black'
 | |
|     },
 | |
|     points:[
 | |
|       {x:x0,y:y0},{x:x1,y:y1}
 | |
|     ]
 | |
|   });
 | |
| }
 | |
| 
 | |
| function plotLines (options) {
 | |
|   for(var i=0; i<options.columns-1; i++) {
 | |
|     plotLine.call(this,options,i);
 | |
|   }
 | |
|   if (options.label) {
 | |
|     makeLabel.call(this,options);
 | |
|   }
 | |
| }
 | |
| 
 | |
| function deleteBar (options,i) {
 | |
|   var id=options.id+':B'+i;
 | |
|   this.remove(this.objects[id]);
 | |
| }
 | |
| 
 | |
| function plotBar (options,i) {
 | |
|   var w=options.width/options.columns-(options.margin||0),
 | |
|       x,y,h,id;
 | |
|   x=options.x+int(options.width/options.columns*i)-w/2;
 | |
|   h=int(options.height/(options.max-options.min)*(options.vector[i]-options.min));
 | |
|   
 | |
|   if (options.min>=0 || options.max <= 0) {
 | |
|     h=Math.max(1,h);
 | |
|     y=options.y+options.height-h;
 | |
|   } else {
 | |
|     off=int(options.height/(options.max-options.min)*(-options.min));
 | |
|     if (options.vector[i]<0)
 | |
|       y=options.y+options.height-off, h=Math.max(1,off-h);
 | |
|     else
 | |
|       h=Math.max(1,h-off),y=options.y+options.height-off-h;
 | |
|   }
 | |
|     
 | |
|   this.add({
 | |
|     id:options.id+':B'+i,
 | |
|     shape:'rect',
 | |
|     align:'left',
 | |
|     x:x,
 | |
|     y:y,
 | |
|     width:w,
 | |
|     height:h,
 | |
|     fill:{
 | |
|       color:(options.bar.fill && options.bar.fill.color)||'black'
 | |
|     },
 | |
|   });
 | |
| }
 | |
| 
 | |
| function plotBars (options) {
 | |
|   for(var i=0; i<options.columns; i++) {
 | |
|     plotBar.call(this,options,i);
 | |
|   }
 | |
|   if (options.label) {
 | |
|     makeLabel.call(this,options);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /** Plot of a metric vector (1d plot)
 | |
| **
 | |
| ** typeof options = {
 | |
| **  type='vector',
 | |
| **  x,y,     
 | |
| **  columns?,
 | |
| **  vector?:[],
 | |
| **  align?={'center'|'left'},
 | |
| **  min?,max?,
 | |
| **  point?: {
 | |
| **    shape,
 | |
| **    width,height,
 | |
| **    fill?:{color:string},
 | |
| **    line?:{color:string}
 | |
| **  },
 | |
| **  line?:{color?,width?},
 | |
| **  bar?:{fill?,line?},
 | |
| **  init: function (i) -> number | []
 | |
| ** }
 | |
| */
 | |
| 
 | |
| function plotVector (options) {
 | |
|   var v,x,y,x0,y0,x1,y1,min,max;
 | |
|   options.vector=options.vector||[];
 | |
|   for(var i=0; i<options.columns; i++) {
 | |
|     v=undefined;
 | |
|     if (typeof options.init == 'function') v=options.init(i);
 | |
|     else if (typeof options.init == 'array' || typeof options.init == 'object')
 | |
|       v=options.init[i]||options.min||0;
 | |
|     if(v != undefined) options.vector[i]=v;
 | |
|     if(min==undefined) min=options.vector[i];
 | |
|     else min=Math.min(min,options.vector[i]);
 | |
|     if(max==undefined) max=options.vector[i];
 | |
|     else max=Math.max(max,options.vector[i]);
 | |
|   }
 | |
|   if (options.min==undefined) options.min=min;
 | |
|   if (options.max==undefined) options.max=max;
 | |
|   if (options.line) plotLines.call(this,options);
 | |
|   if (options.bar) plotBars.call(this,options);
 | |
| }
 | |
| 
 | |
| /** Modify a plot or a part of a plot
 | |
| *   typeof attr = object | number | undefined
 | |
| *   typeof part = {i,j} | {i} | number | undefined
 | |
| */ 
 | |
| function modify(id,attr,part) {
 | |
|   var i,j,id,plot=this.plots[id];
 | |
|   switch (plot.type) {
 | |
|     case 'matrix':
 | |
|       if (part && typeof part.i == 'number' && typeof part.j == 'number') {
 | |
|         id=id+':N'+(part.i+(plot.columns*part.j));
 | |
|         if (plot.matrix && plot.map && attr == undefined) {
 | |
|           // Matrix was externally modified; update visual
 | |
|           attr=plot.map(part.i,part.j,plot.matrix[part.j][part.i]);
 | |
|         }
 | |
|         this.modify(id,attr);
 | |
|       }
 | |
|       break;
 | |
|     case 'vector':
 | |
|       if (!attr && !part) {
 | |
|         // Update entire plot
 | |
|         if (plot.line) {
 | |
|           for(i=0;i<plot.columns-1;i++) {
 | |
|             id=plot.id+':L'+i;
 | |
|             this.remove(this.objects[id]);
 | |
|           }
 | |
|           plotLines.call(this,plot);
 | |
|         } else if (plot.bar) {
 | |
|           for(i=0;i<plot.columns;i++) {
 | |
|             id=plot.id+':B'+i;
 | |
|             this.remove(this.objects[id]);
 | |
|           }
 | |
|           plotBars.call(this,plot);        
 | |
|         }
 | |
|       } else if (typeof attr == 'number') {
 | |
|         i=(typeof part == 'object')?part.i:part;
 | |
|         if (i>=0 && i<plot.columns) 
 | |
|           plot.vector[i]=attr;
 | |
|         if (plot.line) {
 | |
|           if (i>0 && i<plot.columns) 
 | |
|             deleteLine.call(this,plot,i-1),plotLine.call(this,plot,i-1);
 | |
|           if (i>=0 && i<plot.columns-1) 
 | |
|             deleteLine.call(this,plot,i),plotLine.call(this,plot,i);
 | |
|           if (i>0 && i<plot.columns-2) 
 | |
|             deleteLine.call(this,plot,i+1),plotLine.call(this,plot,i+1);
 | |
|         } else if (plot.bar) {
 | |
|           if (i>=0 && i<plot.columns) 
 | |
|             deleteBar.call(this,plot,i),plotBar.call(this,plot,i);
 | |
|         }
 | |
|       } else if (attr && attr.label) {
 | |
|         if (modifies(attr,plot)) {
 | |
|           update(plot,attr);
 | |
|           makeLabel.call(this,plot,true);
 | |
|         }
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
| }
 | |
| 
 | |
| // Main entry method
 | |
| window.prototype.plot = function (options) {
 | |
|   if (!options.id) options.id='Plot'+Object.keys(this.plots).length;
 | |
|   this.plots[options.id]=options;
 | |
|   switch (options.type) {
 | |
|     case 'matrix':
 | |
|       if (options.margin==undefined) options.margin=0;
 | |
|       if (options.matrix) 
 | |
|         options.rows=options.matrix.length,
 | |
|         options.columns=options.matrix[0].length;
 | |
|       options.width=options.width||
 | |
|                     (options.columns*options.node.width+(options.columns-1)*options.margin);
 | |
|       options.height=options.height||
 | |
|                     (options.rows*options.node.height+(options.rows-1)*options.margin);
 | |
|       if (options.align && options.align=='center')
 | |
|         options.x=options.x-options.width/2,
 | |
|         options.y=options.y+options.height/2;
 | |
|       plotMatrix.call(this,options);
 | |
|       break;
 | |
|     case 'vector':
 | |
|       if (options.align && options.align=='center')
 | |
|         options.x=options.x-options.width/2,
 | |
|         options.y=options.y+options.height/2;
 | |
|       plotVector.call(this,options);
 | |
|       break;
 | |
|   }
 | |
|   return options.id;
 | |
| }
 | |
| 
 | |
| 
 | |
| 
 | |
| module.exports = {
 | |
|   modify:modify,
 | |
|   window:window
 | |
| }
 |