svghmi/widget_xygraph.ysl2
changeset 3484 32eaba9cf30e
parent 3474 3ba74350237d
child 3488 6ef4ffcf9761
equal deleted inserted replaced
3474:3ba74350237d 3484:32eaba9cf30e
    38 
    38 
    39             // Min and Max given with paths are meant to describe visible range,
    39             // Min and Max given with paths are meant to describe visible range,
    40             // not to clip data.
    40             // not to clip data.
    41             this.clip = false;
    41             this.clip = false;
    42 
    42 
    43             let y_min = -Infinity, y_max = Infinity;
    43             let y_min = Infinity, y_max = -Infinity;
    44 
    44 
    45             // Compute visible Y range by merging fixed curves Y ranges
    45             // Compute visible Y range by merging fixed curves Y ranges
    46             for(let minmax of this.minmaxes){
    46             for(let minmax of this.minmaxes){
    47                if(minmax){
    47                if(minmax){
    48                    let [min,max] = minmax;
    48                    let [min,max] = minmax;
    49                    if(min < y_min)
    49                    if(min < y_min)
    50                        y_min = min;
    50                        y_min = min;
    51                    if(max > y_max) 
    51                    if(max > y_max)
    52                        y_max = max;
    52                        y_max = max;
    53                }
    53                }
    54             }
    54             }
    55 
    55 
    56             if(y_min !== -Infinity && y_max !== Infinity){
    56             if(y_min !== Infinity && y_max !== -Infinity){
    57                this.fixed_y_range = true;
    57                this.fixed_y_range = true;
    58             } else {
    58             } else {
    59                this.fixed_y_range = false;
    59                this.fixed_y_range = false;
    60             }
    60             }
    61 
    61 
    64 
    64 
    65             this.curves = [];
    65             this.curves = [];
    66             this.init_specific();
    66             this.init_specific();
    67 
    67 
    68             this.reference = new ReferenceFrame(
    68             this.reference = new ReferenceFrame(
    69                 [[this.x_interval_minor_mark, this.x_interval_major_mark],
    69                 [[this.x_interval_minor_mark_elt, this.x_interval_major_mark_elt],
    70                  [this.y_interval_minor_mark, this.y_interval_major_mark]],
    70                  [this.y_interval_minor_mark_elt, this.y_interval_major_mark_elt]],
    71                 [this.y_axis_label, this.x_axis_label],
    71                 [this.x_axis_label_elt, this.y_axis_label_elt],
    72                 [this.x_axis_line, this.y_axis_line],
    72                 [this.x_axis_line_elt, this.y_axis_line_elt],
    73                 [this.x_format, this.y_format]);
    73                 [this.x_format, this.y_format]);
    74 
    74 
    75             // create <clipPath> path and attach it to widget
    75             // create <clipPath> path and attach it to widget
    76             clipPath = document.createElementNS(xmlns,"clipPath");
    76             let clipPath = document.createElementNS(xmlns,"clipPath");
    77             clipPathPath = document.createElementNS(xmlns,"path");
    77             let clipPathPath = document.createElementNS(xmlns,"path");
    78             clipPathPathDattr = document.createAttribute("d");
    78             let clipPathPathDattr = document.createAttribute("d");
    79             clipPathPathDattr.value = this.reference.getClipPathPathDattr();
    79             clipPathPathDattr.value = this.reference.getClipPathPathDattr();
    80             clipPathPath.setAttributeNode(clipPathPathDattr);
    80             clipPathPath.setAttributeNode(clipPathPathDattr);
    81             clipPath.appendChild(clipPathPath);
    81             clipPath.appendChild(clipPathPath);
    82             this.element.appendChild(clipPath);
    82             this.element.appendChild(clipPath);
    83 
    83 
    84             // assign created clipPath to clip-path property of curves
    84             // assign created clipPath to clip-path property of curves
    85             for(let curve of this.curves){
    85             for(let curve of this.curves){
    86                 curve.setAttribute("clip-path", "url(#" + clipPath.id + ")");
    86                 curve.setAttribute("clip-path", "url(#" + clipPath.id + ")");
    87             }
    87             }
    88 
    88 
    89             this.curves_data = [];
    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             // naive local buffer impl. 
    94             // naive local buffer impl. 
   117                 if(value < this.ymin){
   117                 if(value < this.ymin){
   118                     ymin_damaged = false;
   118                     ymin_damaged = false;
   119                     this.ymin = value;
   119                     this.ymin = value;
   120                 }
   120                 }
   121             }
   121             }
       
   122             let Ylength = this.ymax - this.ymin;
   122 
   123 
   123             // recompute X range based on curent time ad buffer depth
   124             // recompute X range based on curent time ad buffer depth
   124             // TODO: get PLC time instead of browser time
   125             // TODO: get PLC time instead of browser time
   125             const d = new Date();
   126             const d = new Date();
   126             let time = d.getTime();
   127             let time = d.getTime();
   132 
   133 
   133             // recompute curves "d" attribute
   134             // recompute curves "d" attribute
   134             // FIXME: use SVG getPathData and setPathData when available.
   135             // FIXME: use SVG getPathData and setPathData when available.
   135             //        https://svgwg.org/specs/paths/#InterfaceSVGPathData
   136             //        https://svgwg.org/specs/paths/#InterfaceSVGPathData
   136             //        https://github.com/jarek-foksa/path-data-polyfill
   137             //        https://github.com/jarek-foksa/path-data-polyfill
   137            
   138 
   138             let [base_point, xvect, yvect] = this.reference.getBaseRef();
   139             let [base_point, xvect, yvect] = this.reference.getBaseRef();
   139             this.curves_d_attr = 
   140             this.curves_d_attr =
   140                 zip(this.curves_data, this.curves).map(function([data,curve]){
   141                 zip(this.curves_data, this.curves).map(([data,curve]) => {
   141                 let new_d = data.map(function([y, i]){
   142                     let new_d = data.map((y, i) => {
   142                     // compute curve point from data, ranges, and base_ref
   143                         // compute curve point from data, ranges, and base_ref
   143                     let x = Xmin + i * Xlength / data_length;
   144                         let x = this.xmin + i * Xlength / data_length;
   144                     let xv = vectorscale(xvect, (x - Xmin) / Xlength);
   145                         let xv = vectorscale(xvect, (x - this.xmin) / Xlength);
   145                     let yv = vectorscale(yvect, (y - Ymin) / Ylength);
   146                         let yv = vectorscale(yvect, (y - this.ymin) / Ylength);
   146                     let px = base_point.x + xv.x + yv.x;
   147                         let px = base_point.x + xv.x + yv.x;
   147                     let py = base_point.y + xv.y + yv.y;
   148                         let py = base_point.y + xv.y + yv.y;
   148                     if(!this.fixed_y_range){
   149                         if(!this.fixed_y_range){
   149                         if(ymin_damaged && y > this.ymin) this.ymin = y;
   150                             if(ymin_damaged && y < this.ymin) this.ymin = y;
   150                         if(xmin_damaged && x > this.xmin) this.xmin = x;
   151                             if(ymax_damaged && y > this.ymax) this.ymax = y;
   151                     }
   152                         }
   152 
   153 
   153                     return " " + px + "," + py;
   154                         return " " + px + "," + py;
       
   155                     });
       
   156 
       
   157                     new_d.unshift("M ");
       
   158 
       
   159                     return new_d.join('');
   154                 });
   160                 });
   155 
   161 
   156                 new_d.unshift("M ");
       
   157                 new_d.push(" z");
       
   158 
       
   159                 return new_d.join('');
       
   160             }
       
   161 
       
   162             // computed curves "d" attr is applied to svg curve during animate();
   162             // computed curves "d" attr is applied to svg curve during animate();
   163 
   163 
   164             this.request_animate();
   164             this.request_animate();
   165         }
   165         }
   166 
   166 
   167         animate(){
   167         animate(){
   168 
   168 
   169             // move marks and update labels
   169             // move elements only if enough data
   170             this.reference.applyRanges([this.XRange, this.YRange]);
   170             if(this.curves_data.some(data => data.length > 1)){
   171 
   171 
   172             // apply computed curves "d" attributes
   172                 // move marks and update labels
   173             for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){
   173                 this.reference.applyRanges([[this.xmin, this.xmax],
   174                 curve.setAttribute("d", d_attr);
   174                                             [this.ymin, this.ymax]]);
       
   175 
       
   176                 // apply computed curves "d" attributes
       
   177                 for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){
       
   178                     curve.setAttribute("d", d_attr);
       
   179                 }
   175             }
   180             }
   176         }
   181         }
   177 
   182 
   178     ||
   183     ||
   179 }
   184 }
   180 
   185 
   181 widget_defs("XYGraph") {
   186 widget_defs("XYGraph") {
   182     labels("/x_interval_minor_mark /x_axis_line /x_interval_major_mark /x_axis_label")
   187     labels("/x_interval_minor_mark /x_axis_line /x_interval_major_mark /x_axis_label");
   183     labels("/y_interval_minor_mark /y_axis_line /y_interval_major_mark /y_axis_label")
   188     labels("/y_interval_minor_mark /y_axis_line /y_interval_major_mark /y_axis_label");
   184 
   189 
   185     |     init_specific() {
   190     |     init_specific() {
   186 
   191 
   187     // collect all curve_n labelled children
   192     // collect all curve_n labelled children
   188     foreach "$hmi_element/*[regexp:test(@inkscape:label,'^curve_[0-9]+$')]" {
   193     foreach "$hmi_element/*[regexp:test(@inkscape:label,'^curve_[0-9]+$')]" {
   217 };
   222 };
   218 
   223 
   219 function move_elements_to_group(elements) {
   224 function move_elements_to_group(elements) {
   220     let newgroup = document.createElementNS(xmlns,"g");
   225     let newgroup = document.createElementNS(xmlns,"g");
   221     for(let element of elements){
   226     for(let element of elements){
   222         element.parentElement().removeChild(element);
   227         let parent = element.parentElement;
       
   228         if(parent !== null)
       
   229             parent.removeChild(element);
   223         newgroup.appendChild(element);
   230         newgroup.appendChild(element);
   224     }
   231     }
   225     return newgroup;
   232     return newgroup;
   226 }
   233 }
   227 function getLinesIntesection(l1, l2) {
   234 function getLinesIntesection(l1, l2) {
       
   235     let [l1start, l1vect] = l1;
       
   236     let [l2start, l2vect] = l2;
       
   237 
       
   238     let b;
   228     /*
   239     /*
   229     Compute intersection of two lines
   240     Compute intersection of two lines
   230     =================================
   241     =================================
   231 
   242 
   232                           ^ l2vect
   243                           ^ l2vect
   241 
   252 
   242     intersection = l1start + l1vect * a
   253     intersection = l1start + l1vect * a
   243     intersection = l2start + l2vect * b
   254     intersection = l2start + l2vect * b
   244     ==> solve : "l1start + l1vect * a = l2start + l2vect * b" to find a and b and then intersection
   255     ==> solve : "l1start + l1vect * a = l2start + l2vect * b" to find a and b and then intersection
   245 
   256 
   246     (1)   l1start.x + l1vect.x * a = l2start.x + l2vect.x * b
   257     (1)   l1start.y + l1vect.y * a = l2start.y + l2vect.y * b
   247     (2)   l1start.y + l1vect.y * a = l2start.y + l2vect.y * b
   258     (2)   l1start.x + l1vect.x * a = l2start.x + l2vect.x * b
   248 
   259 
   249     // express a
   260     // express a
   250     (1)   a = (l2start.x + l2vect.x * b) / (l1start.x + l1vect.x)
   261     (1)   a = (l2start.y + l2vect.y * b - l1start.y) / l1vect.y
   251 
   262 
   252     // substitute a to have only b
   263     // substitute a to have only b
   253     (1+2) l1start.y + l1vect.y * (l2start.x + l2vect.x * b) / (l1start.x + l1vect.x) = l2start.y + l2vect.y * b
   264     (1+2) l1start.x + l1vect.x * (l2start.y + l2vect.y * b - l1start.y) / l1vect.y = l2start.x + l2vect.x * b
   254 
   265 
   255     // expand to isolate b
   266     // expand to isolate b
   256     (2) l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) + (l1vect.y * l2vect.x * b) / (l1start.x + l1vect.x) = l2start.y + l2vect.y * b
   267     l1start.x + l1vect.x * l2start.y / l1vect.y + l2vect.y / l1vect.y * b - l1start.y / l1vect.y = l2start.x + l2vect.x * b
   257     (2) l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y = l2vect.y * b - (l1vect.y * l2vect.x * b) / (l1start.x + l1vect.x)
       
   258 
   268 
   259     // factorize b
   269     // factorize b
   260     (2) l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y = b * (l2vect.y - (l1vect.y * l2vect.x ) / (l1start.x + l1vect.x))
   270     l1start.x + l1vect.x * l2start.y / l1vect.y - l1start.y / l1vect.y - l2start.x = b * ( l2vect.x - l2vect.y / l1vect.y)
   261 
   271 
   262     // extract b
   272     // extract b
   263     (2) b = (l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y) / ((l2vect.y - (l1vect.y * l2vect.x ) / (l1start.x + l1vect.x)))
   273     b = (l1start.x + l1vect.x * l2start.y / l1vect.y  - l1start.y / l1vect.y - l2start.x)  / ( l2vect.x - l2vect.y / l1vect.y)
   264     */
   274     */
   265 
   275 
   266     let [l1start, l1vect] = l1;
   276     /* avoid divison by zero by swapping (1) and (2) */
   267     let [l2start, l2vect] = l2;
   277     if(l1vect.y == 0 ){
   268 
   278         b = (l1start.y + l1vect.y * l2start.x / l1vect.x  - l1start.x / l1vect.x - l2start.y)  / ( l2vect.y - l2vect.x / l1vect.x);
   269     let b = (l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y) / ((l2vect.y - (l1vect.y * l2vect.x ) / (l1start.x + l1vect.x)));
   279     }else{
       
   280         b = (l1start.x + l1vect.x * l2start.y / l1vect.y  - l1start.y / l1vect.y - l2start.x)  / ( l2vect.x - l2vect.y / l1vect.y);
       
   281     }
   270 
   282 
   271     return new DOMPoint(l2start.x + l2vect.x * b, l2start.y + l2vect.y * b);
   283     return new DOMPoint(l2start.x + l2vect.x * b, l2start.y + l2vect.y * b);
   272 };
   284 };
   273 
       
   274 
       
   275 // From https://stackoverflow.com/a/48293566
       
   276 function *zip (...iterables){
       
   277     let iterators = iterables.map(i => i[Symbol.iterator]() )
       
   278     while (true) {
       
   279         let results = iterators.map(iter => iter.next() )
       
   280         if (results.some(res => res.done) ) return
       
   281         else yield results.map(res => res.value )
       
   282     }
       
   283 }
       
   284 
   285 
   285 class ReferenceFrame {
   286 class ReferenceFrame {
   286     constructor(
   287     constructor(
   287         // [[Xminor,Xmajor], [Yminor,Ymajor]]
   288         // [[Xminor,Xmajor], [Yminor,Ymajor]]
   288         marks,
   289         marks,
   291         // [Xline, Yline]
   292         // [Xline, Yline]
   292         lines,
   293         lines,
   293         // [Xformat, Yformat] printf-like formating strings
   294         // [Xformat, Yformat] printf-like formating strings
   294         formats
   295         formats
   295     ){
   296     ){
   296         this.axes = zip(labels,marks,lines,formats).map((...args) => new Axis(...args));
   297         this.axes = zip(labels,marks,lines,formats).map(args => new Axis(...args));
   297 
   298 
   298         let [lx,ly] = this.axes.map(axis => axis.line);
   299         let [lx,ly] = this.axes.map(axis => axis.line);
   299         let [[xstart, xvect], [ystart, yvect]] = [lx,ly];
   300         let [[xstart, xvect], [ystart, yvect]] = [lx,ly];
   300         let base_point = this.getBasePoint();
   301         let base_point = this.getBasePoint();
   301 
   302 
   322         return this.clipPathPathDattr;
   323         return this.clipPathPathDattr;
   323     }
   324     }
   324 
   325 
   325     applyRanges(ranges){
   326     applyRanges(ranges){
   326         for(let [range,axis] of zip(ranges,this.axes)){
   327         for(let [range,axis] of zip(ranges,this.axes)){
   327             axis.applyRange(range);
   328             axis.applyRange(...range);
   328         }
   329         }
   329     }
   330     }
   330 
   331 
   331     getBasePoint() {
   332     getBasePoint() {
   332         let [[xstart, xvect], [ystart, yvect]] = this.axes.map(axis => axis.line);
   333         let [[xstart, xvect], [ystart, yvect]] = this.axes.map(axis => axis.line);
   338         Clipping region is a parallelogram containing axes lines,
   339         Clipping region is a parallelogram containing axes lines,
   339         and whose sides are parallel to axes line respectively.
   340         and whose sides are parallel to axes line respectively.
   340         Given axes lines are not starting at the same point, hereafter is
   341         Given axes lines are not starting at the same point, hereafter is
   341         calculus of parallelogram base point.
   342         calculus of parallelogram base point.
   342 
   343 
   343                               ^ given Y axis
   344                               ^ given Y axis (yvect)
   344                    /         /
   345                    /         /
   345                   /         /
   346                   /         /
   346                  /         /
   347                  /         /
   347          xstart /---------/--------------> given X axis
   348          xstart *---------*--------------> given X axis (xvect)
   348                /         /
   349                /         /origin
   349               /         /
   350               /         /
   350              /---------/--------------
   351              *---------*--------------
   351         base_point   ystart
   352         base_point   ystart
   352 
   353 
   353         base_point = xstart + yvect * a
       
   354         base_point = ystart + xvect * b
       
   355         ==> solve : "xstart + yvect * a = ystart + xvect * b" to find a and b and then base_point
       
   356 
       
   357         (1)   xstart.x + yvect.x * a = ystart.x + xvect.x * b
       
   358         (2)   xstart.y + yvect.y * a = ystart.y + xvect.y * b
       
   359 
       
   360         // express a
       
   361         (1)   a = (ystart.x + xvect.x * b) / (xstart.x + yvect.x)
       
   362 
       
   363         // substitute a to have only b
       
   364         (1+2) xstart.y + yvect.y * (ystart.x + xvect.x * b) / (xstart.x + yvect.x) = ystart.y + xvect.y * b
       
   365 
       
   366         // expand to isolate b
       
   367         (2) xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) + (yvect.y * xvect.x * b) / (xstart.x + yvect.x) = ystart.y + xvect.y * b
       
   368         (2) xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y = xvect.y * b - (yvect.y * xvect.x * b) / (xstart.x + yvect.x)
       
   369 
       
   370         // factorize b
       
   371         (2) xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y = b * (xvect.y - (yvect.y * xvect.x ) / (xstart.x + yvect.x))
       
   372 
       
   373         // extract b
       
   374         (2) b = (xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y) / ((xvect.y - (yvect.y * xvect.x ) / (xstart.x + yvect.x)))
       
   375         */
   354         */
   376 
   355 
   377         let b = (xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y) / ((xvect.y - (yvect.y * xvect.x ) / (xstart.x + yvect.x)));
   356         let base_point = getLinesIntesection([xstart,yvect],[ystart,xvect]);
   378         let base_point = new DOMPoint(ystart.x + xvect.x * b, ystart.y + xvect.y * b);
       
   379 
       
   380         // // compute given origin
       
   381         // // from drawing : given_origin = xstart - xvect * b
       
   382         // let given_origin = new DOMPoint(xstart.x - xvect.x * b, xstart.y - xvect.y * b);
       
   383 
   357 
   384         return base_point;
   358         return base_point;
   385 
   359 
   386     }
   360     }
   387 
   361 
   405                 this[elementname+"_"+name+"_transform"]=transform;
   379                 this[elementname+"_"+name+"_transform"]=transform;
   406             };
   380             };
   407         };
   381         };
   408 
   382 
   409         // group marks an labels together
   383         // group marks an labels together
   410         let parent = line.parentElement()
   384         let parent = line.parentElement;
   411         marks_group = move_elements_to_group(marks);
   385         let marks_group = move_elements_to_group(marks);
   412         marks_and_label_group = move_elements_to_group([marks_group_use, label]);
   386         let marks_and_label_group = move_elements_to_group([marks_group, label]);
   413         group = move_elements_to_group([marks_and_label_group,line]);
   387         let group = move_elements_to_group([marks_and_label_group,line]);
   414         parent.appendChild(group);
   388         parent.appendChild(group);
   415 
   389 
   416         // Add transforms to group 
   390         // Add transforms to group
   417         for(let name of ["base","origin"]){
   391         for(let name of ["base","origin"]){
   418             let transform = svg_root.createSVGTransform();
   392             let transform = svg_root.createSVGTransform();
   419             group.transform.baseVal.appendItem(transform);
   393             group.transform.baseVal.appendItem(transform);
   420             this[name+"_transform"]=transform;
   394             this[name+"_transform"]=transform;
   421         };
   395         };
   427         this.mlg_clones = [];
   401         this.mlg_clones = [];
   428         this.last_mark_count = 0;
   402         this.last_mark_count = 0;
   429     }
   403     }
   430 
   404 
   431     setBasePoint(base_point){
   405     setBasePoint(base_point){
   432         // move Axis to base point 
   406         // move Axis to base point
   433         let [start, _vect] = this.lineElement;
   407         let [start, _vect] = this.line;
   434         let v = vector(start, base_point);
   408         let v = vector(start, base_point);
   435         this.base_transform.setTranslate(v.x, v.y);
   409         this.base_transform.setTranslate(v.x, v.y);
   436 
   410 
   437         // Move marks and label to base point. 
   411         // Move marks and label to base point.
   438         // _|_______         _|________
   412         // _|_______          _|________
   439         //  |  '  |     ==>   ' 
   413         //  |  '  |     ==>    '
   440         //  |     0           0
   414         //  |     0            0
   441         //  |                 |
   415         //  |                  |
   442 
   416 
   443         for(let [markname,mark] of zip(["minor", "major"],this.marks)){
   417         for(let [markname,mark] of zip(["minor", "major"],this.marks)){
   444             let transform = this[markname+"_base_transform"];
   418             let transform = this[markname+"_base_transform"];
   445             let pos = vector(
   419             let pos = vector(
   446                 // Marks are expected to be paths
   420                 // Marks are expected to be paths
   452             this[markname+"_base_transform"].setTranslate(-pos.x, -pos.x);
   426             this[markname+"_base_transform"].setTranslate(-pos.x, -pos.x);
   453             if(markname == "major"){ // label follow major mark
   427             if(markname == "major"){ // label follow major mark
   454                 this.label_base_transform.setTranslate(-pos.x, -pos.x);
   428                 this.label_base_transform.setTranslate(-pos.x, -pos.x);
   455             }
   429             }
   456         }
   430         }
   457     }
       
   458 
       
   459     applyOriginAndUnitVector(offset, unit_vect){
       
   460         // offset is a representing position of an 
       
   461         // axis along the opposit axis line, expressed in major marks units
       
   462         // unit_vect is the translation in between to major marks
       
   463 
       
   464         //              ^
       
   465         //              | unit_vect
       
   466         //              |<--->
       
   467         //     _________|__________>
       
   468         //     ^  |  '  |  '  |  '
       
   469         //     |yoffset |     1 
       
   470         //     |        |
       
   471         //     v xoffset|
       
   472         //     X<------>|
       
   473         // base_point
       
   474 
       
   475         // move major marks and label to first positive mark position
       
   476         let v = vectorscale(unit_vect, offset+1);
       
   477         this.label_slide_transform.setTranslate(v.x, v.x);
       
   478         this.major_slide_transform.setTranslate(v.x, v.x);
       
   479         // move minor mark to first half positive mark position
       
   480         let h = vectorscale(unit_vect, offset+0.5);
       
   481         this.minor_slide_transform.setTranslate(h.x, h.x);
       
   482     }
   431     }
   483 
   432 
   484     applyRange(min, max){
   433     applyRange(min, max){
   485         let range = max - min;
   434         let range = max - min;
   486 
   435 
   542         let unit_vect = vectorscale(vect, unit/range);
   491         let unit_vect = vectorscale(vect, unit/range);
   543         let [umin, umax, uoffset] = [min,max,offset].map(val => Math.round(val/unit));
   492         let [umin, umax, uoffset] = [min,max,offset].map(val => Math.round(val/unit));
   544         let mark_count = umax-umin;
   493         let mark_count = umax-umin;
   545 
   494 
   546         // apply unit vector to marks and label
   495         // apply unit vector to marks and label
   547         this.label_and_marks.applyOriginAndUnitVector(offset, unit_vect);
   496         // offset is a representing position of an 
       
   497         // axis along the opposit axis line, expressed in major marks units
       
   498         // unit_vect is the translation in between to major marks
       
   499 
       
   500         //              ^
       
   501         //              | unit_vect
       
   502         //              |<--->
       
   503         //     _________|__________>
       
   504         //     ^  |  '  |  '  |  '
       
   505         //     |yoffset |     1 
       
   506         //     |        |
       
   507         //     v xoffset|
       
   508         //     X<------>|
       
   509         // base_point
       
   510 
       
   511         // move major marks and label to first positive mark position
       
   512         let v = vectorscale(unit_vect, offset+unit);
       
   513         this.label_slide_transform.setTranslate(v.x, v.y);
       
   514         this.major_slide_transform.setTranslate(v.x, v.y);
       
   515         // move minor mark to first half positive mark position
       
   516         let h = vectorscale(unit_vect, offset+(unit/2));
       
   517         this.minor_slide_transform.setTranslate(h.x, h.y);
   548 
   518 
   549         // duplicate marks and labels as needed
   519         // duplicate marks and labels as needed
   550         let current_mark_count = this.mlg_clones.length;
   520         let current_mark_count = this.mlg_clones.length;
   551         for(let i = current_mark_count; i <= mark_count; i++){
   521         for(let i = current_mark_count; i <= mark_count; i++){
   552             // cloneNode() label and add a svg:use of marks in a new group
   522             // cloneNode() label and add a svg:use of marks in a new group
   553             let newgroup = document.createElementNS(xmlns,"g");
   523             let newgroup = document.createElementNS(xmlns,"g");
   554             let transform = svg_root.createSVGTransform();
   524             let transform = svg_root.createSVGTransform();
   555             let newlabel = cloneNode(this.label);
   525             let newlabel = this.label.cloneNode(true);
   556             let newuse = document.createElementNS(xmlns,"use");
   526             let newuse = document.createElementNS(xmlns,"use");
   557             let newuseAttr = document.createAttribute("xlink:href");
   527             let newuseAttr = document.createAttribute("xlink:href");
   558             newuseAttr.value = "#"+this.marks_group.id;
   528             newuseAttr.value = "#"+this.marks_group.id;
   559             newuse.setAttributeNode(newuseAttr.value);
   529             newuse.setAttributeNode(newuseAttr);
   560             newgroup.transform.baseVal.appendItem(transform);
   530             newgroup.transform.baseVal.appendItem(transform);
   561             newgroup.appendChild(newlabel);
   531             newgroup.appendChild(newlabel);
   562             newgroup.appendChild(newuse);
   532             newgroup.appendChild(newuse);
   563             this.mlg_clones.push([tranform,newgroup]);
   533             this.mlg_clones.push([transform,newgroup]);
   564         }
   534         }
   565 
   535 
   566         // move marks and labels, set labels
   536         // move marks and labels, set labels
   567         for(let u = 0; u <= mark_count; u++){
   537         for(let u = 0; u <= mark_count; u++){
   568             let i = 0;
   538             let i = 0;
   569             let val = (umin + u) * unit;
   539             let val = (umin + u) * unit;
   570             let vec = vectorscale(unit_vect, offset + val);
   540             let vec = vectorscale(unit_vect, offset + val);
   571             let text = this.format ? sprintf(this.format, val) : val.toString();
   541             let text = this.format ? sprintf(this.format, val) : val.toString();
   572             if(u == uoffset){
   542             if(u == uoffset){
   573                 // apply offset to original marks and label groups
   543                 // apply offset to original marks and label groups
   574                 this.origin_transform.setTranslate(vec.x, vec.x);
   544                 this.origin_transform.setTranslate(vec.x, vec.y);
   575 
   545 
   576                 // update original label text
   546                 // update original label text
   577                 this.label_and_mark.label.textContent = text;
   547                 this.label.getElementsByTagName("tspan")[0].textContent = text;
   578             } else {
   548             } else {
   579                 let [transform,element] = this.mlg_clones[i++];
   549                 let [transform,element] = this.mlg_clones[i++];
   580 
   550 
   581                 // apply unit vector*N to marks and label groups
   551                 // apply unit vector*N to marks and label groups
   582                 transform.setTranslate(vec.x, vec.x);
   552                 transform.setTranslate(vec.x, vec.y);
   583 
   553 
   584                 // update label text
   554                 // update label text
   585                 element.getElementsByTagName("tspan")[0].textContent = text;
   555                 element.getElementsByTagName("tspan")[0].textContent = text;
   586 
   556 
   587                 // Attach to group if not already
   557                 // Attach to group if not already