svghmi/widget_xygraph.ysl2
changeset 3488 6ef4ffcf9761
parent 3484 32eaba9cf30e
child 3490 4f252e8d6759
equal deleted inserted replaced
3487:efa45e7cb04b 3488:6ef4ffcf9761
    89             this.curves_data = this.curves.map(_unused => []);
    89             this.curves_data = this.curves.map(_unused => []);
    90             this.max_data_length = this.args[0];
    90             this.max_data_length = this.args[0];
    91         }
    91         }
    92 
    92 
    93         dispatch(value,oldval, index) {
    93         dispatch(value,oldval, index) {
       
    94             // TODO: get PLC time instead of browser time
       
    95             let time = Date.now();
       
    96 
    94             // naive local buffer impl. 
    97             // naive local buffer impl. 
    95             // data is updated only when graph is visible
    98             // data is updated only when graph is visible
    96             // TODO: replace with separate recording
    99             // TODO: replace with separate recording
    97 
   100 
    98             this.curves_data[index].push(value);
   101             this.curves_data[index].push([time, value]);
    99             let data_length = this.curves_data[index].length;
   102             let data_length = this.curves_data[index].length;
   100             let ymin_damaged = false;
   103             let ymin_damaged = false;
   101             let ymax_damaged = false;
   104             let ymax_damaged = false;
   102             let overflow;
   105             let overflow;
   103 
   106 
   104             if(data_length > this.max_data_length){
   107             if(data_length > this.max_data_length){
   105                 // remove first item
   108                 // remove first item
   106                 overflow = this.curves_data[index].shift();
   109                 [this.xmin, overflow] = this.curves_data[index].shift();
   107                 data_length = data_length - 1;
   110                 data_length = data_length - 1;
   108             }
   111             } else {
       
   112                 if(this.xmin == undefined){
       
   113                     this.xmin = time;
       
   114                 }
       
   115             }
       
   116 
       
   117             this.xmax = time;
       
   118             let Xlength = this.xmax - this.xmin;
   109 
   119 
   110             if(!this.fixed_y_range){
   120             if(!this.fixed_y_range){
   111                 ymin_damaged = overflow <= this.ymin;
   121                 ymin_damaged = overflow <= this.ymin;
   112                 ymax_damaged = overflow >= this.ymax;
   122                 ymax_damaged = overflow >= this.ymax;
   113                 if(value > this.ymax){
   123                 if(value > this.ymax){
   119                     this.ymin = value;
   129                     this.ymin = value;
   120                 }
   130                 }
   121             }
   131             }
   122             let Ylength = this.ymax - this.ymin;
   132             let Ylength = this.ymax - this.ymin;
   123 
   133 
   124             // recompute X range based on curent time ad buffer depth
       
   125             // TODO: get PLC time instead of browser time
       
   126             const d = new Date();
       
   127             let time = d.getTime();
       
   128 
       
   129             // FIXME: this becomes wrong when graph is not visible and updated all the time
       
   130             [this.xmin, this.xmax] = [time - data_length*1000/this.frequency, time];
       
   131             let Xlength = this.xmax - this.xmin;
       
   132 
       
   133 
       
   134             // recompute curves "d" attribute
   134             // recompute curves "d" attribute
   135             // FIXME: use SVG getPathData and setPathData when available.
   135             // FIXME: use SVG getPathData and setPathData when available.
   136             //        https://svgwg.org/specs/paths/#InterfaceSVGPathData
   136             //        https://svgwg.org/specs/paths/#InterfaceSVGPathData
   137             //        https://github.com/jarek-foksa/path-data-polyfill
   137             //        https://github.com/jarek-foksa/path-data-polyfill
   138 
   138 
   139             let [base_point, xvect, yvect] = this.reference.getBaseRef();
   139             let [base_point, xvect, yvect] = this.reference.getBaseRef();
   140             this.curves_d_attr =
   140             this.curves_d_attr =
   141                 zip(this.curves_data, this.curves).map(([data,curve]) => {
   141                 zip(this.curves_data, this.curves).map(([data,curve]) => {
   142                     let new_d = data.map((y, i) => {
   142                     let new_d = data.map(([x,y], i) => {
   143                         // compute curve point from data, ranges, and base_ref
   143                         // compute curve point from data, ranges, and base_ref
   144                         let x = this.xmin + i * Xlength / data_length;
       
   145                         let xv = vectorscale(xvect, (x - this.xmin) / Xlength);
   144                         let xv = vectorscale(xvect, (x - this.xmin) / Xlength);
   146                         let yv = vectorscale(yvect, (y - this.ymin) / Ylength);
   145                         let yv = vectorscale(yvect, (y - this.ymin) / Ylength);
   147                         let px = base_point.x + xv.x + yv.x;
   146                         let px = base_point.x + xv.x + yv.x;
   148                         let py = base_point.y + xv.y + yv.y;
   147                         let py = base_point.y + xv.y + yv.y;
   149                         if(!this.fixed_y_range){
   148                         if(!this.fixed_y_range){
   221     return new DOMPoint(p2 * p1.x , p2 * p1.y);
   220     return new DOMPoint(p2 * p1.x , p2 * p1.y);
   222 };
   221 };
   223 
   222 
   224 function move_elements_to_group(elements) {
   223 function move_elements_to_group(elements) {
   225     let newgroup = document.createElementNS(xmlns,"g");
   224     let newgroup = document.createElementNS(xmlns,"g");
       
   225     newgroup.id = Date.now().toString(36) + Math.random().toString(36).substr(2);
       
   226 
   226     for(let element of elements){
   227     for(let element of elements){
   227         let parent = element.parentElement;
   228         let parent = element.parentElement;
   228         if(parent !== null)
   229         if(parent !== null)
   229             parent.removeChild(element);
   230             parent.removeChild(element);
   230         newgroup.appendChild(element);
   231         newgroup.appendChild(element);
   233 }
   234 }
   234 function getLinesIntesection(l1, l2) {
   235 function getLinesIntesection(l1, l2) {
   235     let [l1start, l1vect] = l1;
   236     let [l1start, l1vect] = l1;
   236     let [l2start, l2vect] = l2;
   237     let [l2start, l2vect] = l2;
   237 
   238 
   238     let b;
   239 
   239     /*
   240     /*
   240     Compute intersection of two lines
   241     Compute intersection of two lines
   241     =================================
   242     =================================
   242 
   243 
   243                           ^ l2vect
   244                           ^ l2vect
   248                      / intersection
   249                      / intersection
   249                     /
   250                     /
   250                    /
   251                    /
   251                    l2start
   252                    l2start
   252 
   253 
   253     intersection = l1start + l1vect * a
   254 	*/
   254     intersection = l2start + l2vect * b
   255     let [x1, y1, x3, y3] = [l1start.x, l1start.y, l2start.x, l2start.y];
   255     ==> solve : "l1start + l1vect * a = l2start + l2vect * b" to find a and b and then intersection
   256 	let [x2, y2, x4, y4] = [x1+l1vect.x, y1+l1vect.y, x3+l2vect.x, y3+l2vect.y];
   256 
   257 
   257     (1)   l1start.y + l1vect.y * a = l2start.y + l2vect.y * b
   258 	// line intercept math by Paul Bourke http://paulbourke.net/geometry/pointlineplane/
   258     (2)   l1start.x + l1vect.x * a = l2start.x + l2vect.x * b
   259 	// Determine the intersection point of two line segments
   259 
   260 	// Return FALSE if the lines don't intersect
   260     // express a
   261 
   261     (1)   a = (l2start.y + l2vect.y * b - l1start.y) / l1vect.y
   262     // Check if none of the lines are of length 0
   262 
   263     if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) {
   263     // substitute a to have only b
   264         return false
   264     (1+2) l1start.x + l1vect.x * (l2start.y + l2vect.y * b - l1start.y) / l1vect.y = l2start.x + l2vect.x * b
   265     }
   265 
   266 
   266     // expand to isolate b
   267     denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1))
   267     l1start.x + l1vect.x * l2start.y / l1vect.y + l2vect.y / l1vect.y * b - l1start.y / l1vect.y = l2start.x + l2vect.x * b
   268 
   268 
   269     // Lines are parallel
   269     // factorize b
   270     if (denominator === 0) {
   270     l1start.x + l1vect.x * l2start.y / l1vect.y - l1start.y / l1vect.y - l2start.x = b * ( l2vect.x - l2vect.y / l1vect.y)
   271         return false
   271 
   272     }
   272     // extract b
   273 
   273     b = (l1start.x + l1vect.x * l2start.y / l1vect.y  - l1start.y / l1vect.y - l2start.x)  / ( l2vect.x - l2vect.y / l1vect.y)
   274     let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator
   274     */
   275     let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator
   275 
   276 
   276     /* avoid divison by zero by swapping (1) and (2) */
   277     // Return a object with the x and y coordinates of the intersection
   277     if(l1vect.y == 0 ){
   278     let x = x1 + ua * (x2 - x1)
   278         b = (l1start.y + l1vect.y * l2start.x / l1vect.x  - l1start.x / l1vect.x - l2start.y)  / ( l2vect.y - l2vect.x / l1vect.x);
   279     let y = y1 + ua * (y2 - y1)
   279     }else{
   280 
   280         b = (l1start.x + l1vect.x * l2start.y / l1vect.y  - l1start.y / l1vect.y - l2start.x)  / ( l2vect.x - l2vect.y / l1vect.y);
   281     return new DOMPoint(x,y);
   281     }
       
   282 
       
   283     return new DOMPoint(l2start.x + l2vect.x * b, l2start.y + l2vect.y * b);
       
   284 };
   282 };
   285 
   283 
   286 class ReferenceFrame {
   284 class ReferenceFrame {
   287     constructor(
   285     constructor(
   288         // [[Xminor,Xmajor], [Yminor,Ymajor]]
   286         // [[Xminor,Xmajor], [Yminor,Ymajor]]
   322     getClipPathPathDattr(){
   320     getClipPathPathDattr(){
   323         return this.clipPathPathDattr;
   321         return this.clipPathPathDattr;
   324     }
   322     }
   325 
   323 
   326     applyRanges(ranges){
   324     applyRanges(ranges){
   327         for(let [range,axis] of zip(ranges,this.axes)){
   325         let origin_moves = zip(ranges,this.axes).map(([range,axis]) => axis.applyRange(...range));
   328             axis.applyRange(...range);
   326 		zip(origin_moves.reverse(),this.axes).forEach(([vect,axis]) => axis.moveOrigin(vect));
   329         }
       
   330     }
   327     }
   331 
   328 
   332     getBasePoint() {
   329     getBasePoint() {
   333         let [[xstart, xvect], [ystart, yvect]] = this.axes.map(axis => axis.line);
   330         let [[xstart, xvect], [ystart, yvect]] = this.axes.map(axis => axis.line);
   334 
   331 
   380             };
   377             };
   381         };
   378         };
   382 
   379 
   383         // group marks an labels together
   380         // group marks an labels together
   384         let parent = line.parentElement;
   381         let parent = line.parentElement;
   385         let marks_group = move_elements_to_group(marks);
   382         this.marks_group = move_elements_to_group(marks);
   386         let marks_and_label_group = move_elements_to_group([marks_group, label]);
   383         this.marks_and_label_group = move_elements_to_group([this.marks_group, label]);
   387         let group = move_elements_to_group([marks_and_label_group,line]);
   384         this.group = move_elements_to_group([this.marks_and_label_group,line]);
   388         parent.appendChild(group);
   385         parent.appendChild(this.group);
   389 
   386 
   390         // Add transforms to group
   387         // Add transforms to group
   391         for(let name of ["base","origin"]){
   388         for(let name of ["base","origin"]){
   392             let transform = svg_root.createSVGTransform();
   389             let transform = svg_root.createSVGTransform();
   393             group.transform.baseVal.appendItem(transform);
   390             this.group.transform.baseVal.appendItem(transform);
   394             this[name+"_transform"]=transform;
   391             this[name+"_transform"]=transform;
   395         };
   392         };
   396 
   393 
   397         this.group = group;
   394         this.marks_and_label_group_transform = svg_root.createSVGTransform();
   398         this.marks_group = marks_group;
   395         this.marks_and_label_group.transform.baseVal.appendItem(this.marks_and_label_group_transform);
   399         this.marks_and_label_group = marks_and_label_group;
   396 
   400 
   397         this.duplicates = [];
   401         this.mlg_clones = [];
       
   402         this.last_mark_count = 0;
   398         this.last_mark_count = 0;
   403     }
   399     }
   404 
   400 
   405     setBasePoint(base_point){
   401     setBasePoint(base_point){
   406         // move Axis to base point
   402         // move Axis to base point
   413         //  |  '  |     ==>    '
   409         //  |  '  |     ==>    '
   414         //  |     0            0
   410         //  |     0            0
   415         //  |                  |
   411         //  |                  |
   416 
   412 
   417         for(let [markname,mark] of zip(["minor", "major"],this.marks)){
   413         for(let [markname,mark] of zip(["minor", "major"],this.marks)){
   418             let transform = this[markname+"_base_transform"];
       
   419             let pos = vector(
   414             let pos = vector(
   420                 // Marks are expected to be paths
   415                 // Marks are expected to be paths
   421                 // paths are expected to be lines
   416                 // paths are expected to be lines
   422                 // intersection with axis line is taken 
   417                 // intersection with axis line is taken 
   423                 // as reference for mark position
   418                 // as reference for mark position
   424                 base_point, getLinesIntesection(
   419                 getLinesIntesection(
   425                     this.line, lineFromPath(mark)));
   420                     this.line, lineFromPath(mark)),base_point);
   426             this[markname+"_base_transform"].setTranslate(-pos.x, -pos.x);
   421             this[markname+"_base_transform"].setTranslate(pos.x - v.x, pos.y - v.y);
   427             if(markname == "major"){ // label follow major mark
   422             if(markname == "major"){ // label follow major mark
   428                 this.label_base_transform.setTranslate(-pos.x, -pos.x);
   423                 this.label_base_transform.setTranslate(pos.x - v.x, pos.y - v.y);
   429             }
   424             }
   430         }
   425         }
   431     }
   426     }
       
   427 
       
   428 	moveOrigin(vect){
       
   429 		this.origin_transform.setTranslate(vect.x, vect.y);
       
   430 	}
   432 
   431 
   433     applyRange(min, max){
   432     applyRange(min, max){
   434         let range = max - min;
   433         let range = max - min;
   435 
   434 
   436         // compute how many units for a mark
   435         // compute how many units for a mark
   486         let offset = (max>=0 && min>=0) ? 0 : (
   485         let offset = (max>=0 && min>=0) ? 0 : (
   487                      (max<0 && min<0)   ? range : -min);
   486                      (max<0 && min<0)   ? range : -min);
   488 
   487 
   489         // compute unit vector
   488         // compute unit vector
   490         let [_start, vect] = this.line;
   489         let [_start, vect] = this.line;
   491         let unit_vect = vectorscale(vect, unit/range);
   490         let unit_vect = vectorscale(vect, 1/range);
   492         let [umin, umax, uoffset] = [min,max,offset].map(val => Math.round(val/unit));
   491         let [mark_min, mark_max, mark_offset] = [min,max,offset].map(val => Math.round(val/unit));
   493         let mark_count = umax-umin;
   492         let mark_count = mark_max-mark_min;
   494 
   493 
   495         // apply unit vector to marks and label
   494         // apply unit vector to marks and label
   496         // offset is a representing position of an 
   495         // offset is a representing position of an 
   497         // axis along the opposit axis line, expressed in major marks units
   496         // axis along the opposit axis line, expressed in major marks units
   498         // unit_vect is the translation in between to major marks
   497         // unit_vect is unit vector
   499 
   498 
   500         //              ^
   499         //              ^
   501         //              | unit_vect
   500         //              | unit_vect
   502         //              |<--->
   501         //              |<--->
   503         //     _________|__________>
   502         //     _________|__________>
   507         //     v xoffset|
   506         //     v xoffset|
   508         //     X<------>|
   507         //     X<------>|
   509         // base_point
   508         // base_point
   510 
   509 
   511         // move major marks and label to first positive mark position
   510         // move major marks and label to first positive mark position
   512         let v = vectorscale(unit_vect, offset+unit);
   511         let v = vectorscale(unit_vect, unit);
   513         this.label_slide_transform.setTranslate(v.x, v.y);
   512         this.label_slide_transform.setTranslate(v.x, v.y);
   514         this.major_slide_transform.setTranslate(v.x, v.y);
   513         this.major_slide_transform.setTranslate(v.x, v.y);
   515         // move minor mark to first half positive mark position
   514         // move minor mark to first half positive mark position
   516         let h = vectorscale(unit_vect, offset+(unit/2));
   515         v = vectorscale(unit_vect, unit/2);
   517         this.minor_slide_transform.setTranslate(h.x, h.y);
   516         this.minor_slide_transform.setTranslate(v.x, v.y);
   518 
   517 
   519         // duplicate marks and labels as needed
   518         // duplicate marks and labels as needed
   520         let current_mark_count = this.mlg_clones.length;
   519         let current_mark_count = this.duplicates.length;
   521         for(let i = current_mark_count; i <= mark_count; i++){
   520         for(let i = current_mark_count; i <= mark_count; i++){
   522             // cloneNode() label and add a svg:use of marks in a new group
   521             // cloneNode() label and add a svg:use of marks in a new group
   523             let newgroup = document.createElementNS(xmlns,"g");
   522             let newgroup = document.createElementNS(xmlns,"g");
   524             let transform = svg_root.createSVGTransform();
   523             let transform = svg_root.createSVGTransform();
   525             let newlabel = this.label.cloneNode(true);
   524             let newlabel = this.label.cloneNode(true);
   528             newuseAttr.value = "#"+this.marks_group.id;
   527             newuseAttr.value = "#"+this.marks_group.id;
   529             newuse.setAttributeNode(newuseAttr);
   528             newuse.setAttributeNode(newuseAttr);
   530             newgroup.transform.baseVal.appendItem(transform);
   529             newgroup.transform.baseVal.appendItem(transform);
   531             newgroup.appendChild(newlabel);
   530             newgroup.appendChild(newlabel);
   532             newgroup.appendChild(newuse);
   531             newgroup.appendChild(newuse);
   533             this.mlg_clones.push([transform,newgroup]);
   532             this.duplicates.push([transform,newgroup]);
   534         }
   533         }
   535 
   534 
   536         // move marks and labels, set labels
   535         // move marks and labels, set labels
   537         for(let u = 0; u <= mark_count; u++){
   536         // 
       
   537         // min > 0, max > 0, offset = 0
       
   538         //         ^
       
   539         //         |________>
       
   540         //        '| |  '  |
       
   541         //         | 6     7
       
   542         //         X
       
   543         //     base_point
       
   544         //
       
   545         // min < 0, max > 0, offset = -min
       
   546         //              ^
       
   547         //     _________|__________>
       
   548         //     '  |  '  |  '  |  '
       
   549         //       -1     |     1 
       
   550         //       offset |
       
   551         //     X<------>|
       
   552         // base_point
       
   553         //
       
   554         // min < 0, max < 0, offset = range
       
   555         //                 ^
       
   556         //     ____________|    
       
   557         //      '  |  '  | |'
       
   558         //        -5    -4 |
       
   559         //         offset  |
       
   560         //     X<--------->|
       
   561         // base_point
       
   562 
       
   563         for(let mark_index = 0; mark_index <= mark_count; mark_index++){
   538             let i = 0;
   564             let i = 0;
   539             let val = (umin + u) * unit;
   565             let val = (mark_min + mark_index) * unit;
   540             let vec = vectorscale(unit_vect, offset + val);
   566             let vec = vectorscale(unit_vect, offset + val - min);
   541             let text = this.format ? sprintf(this.format, val) : val.toString();
   567             let text = this.format ? sprintf(this.format, val) : val.toString();
   542             if(u == uoffset){
   568             if(mark_index == mark_offset){
   543                 // apply offset to original marks and label groups
   569                 // apply offset to original marks and label groups
   544                 this.origin_transform.setTranslate(vec.x, vec.y);
   570                 this.marks_and_label_group_transform.setTranslate(vec.x, vec.y);
   545 
   571 
   546                 // update original label text
   572                 // update original label text
   547                 this.label.getElementsByTagName("tspan")[0].textContent = text;
   573                 this.label.getElementsByTagName("tspan")[0].textContent = text;
   548             } else {
   574             } else {
   549                 let [transform,element] = this.mlg_clones[i++];
   575                 let [transform,element] = this.duplicates[i++];
   550 
   576 
   551                 // apply unit vector*N to marks and label groups
   577                 // apply unit vector*N to marks and label groups
   552                 transform.setTranslate(vec.x, vec.y);
   578                 transform.setTranslate(vec.x, vec.y);
   553 
   579 
   554                 // update label text
   580                 // update label text
   561             }
   587             }
   562         }
   588         }
   563 
   589 
   564         // dettach marks and label from group if not anymore visible
   590         // dettach marks and label from group if not anymore visible
   565         for(let i = current_mark_count; i < this.last_mark_count; i++){
   591         for(let i = current_mark_count; i < this.last_mark_count; i++){
   566             let [transform,element] = this.mlg_clones[i];
   592             let [transform,element] = this.duplicates[i];
   567             this.group.removeChild(element);
   593             this.group.removeChild(element);
   568         }
   594         }
   569 
   595 
   570         this.last_mark_count = current_mark_count;
   596         this.last_mark_count = current_mark_count;
       
   597 
       
   598 		return vectorscale(unit_vect, offset);
   571     }
   599     }
   572 }
   600 }
   573 ||
   601 ||