70 [this.y_interval_minor_mark_elt, this.y_interval_major_mark_elt]], |
70 [this.y_interval_minor_mark_elt, this.y_interval_major_mark_elt]], |
71 [this.x_axis_label_elt, this.y_axis_label_elt], |
71 [this.x_axis_label_elt, this.y_axis_label_elt], |
72 [this.x_axis_line_elt, this.y_axis_line_elt], |
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 let max_stroke_width = 0; |
|
76 for(let curve of this.curves){ |
|
77 if(curve.style.strokeWidth > max_stroke_width){ |
|
78 max_stroke_width = curve.style.strokeWidth; |
|
79 } |
|
80 } |
|
81 |
|
82 this.Margins=this.reference.getLengths().map(length => max_stroke_width/length); |
|
83 |
75 // create <clipPath> path and attach it to widget |
84 // create <clipPath> path and attach it to widget |
76 let clipPath = document.createElementNS(xmlns,"clipPath"); |
85 let clipPath = document.createElementNS(xmlns,"clipPath"); |
77 let clipPathPath = document.createElementNS(xmlns,"path"); |
86 let clipPathPath = document.createElementNS(xmlns,"path"); |
78 let clipPathPathDattr = document.createAttribute("d"); |
87 let clipPathPathDattr = document.createAttribute("d"); |
79 clipPathPathDattr.value = this.reference.getClipPathPathDattr(); |
88 clipPathPathDattr.value = this.reference.getClipPathPathDattr(); |
80 clipPathPath.setAttributeNode(clipPathPathDattr); |
89 clipPathPath.setAttributeNode(clipPathPathDattr); |
81 clipPath.appendChild(clipPathPath); |
90 clipPath.appendChild(clipPathPath); |
|
91 clipPath.id = randomId(); |
82 this.element.appendChild(clipPath); |
92 this.element.appendChild(clipPath); |
83 |
93 |
84 // assign created clipPath to clip-path property of curves |
94 // assign created clipPath to clip-path property of curves |
85 for(let curve of this.curves){ |
95 for(let curve of this.curves){ |
86 curve.setAttribute("clip-path", "url(#" + clipPath.id + ")"); |
96 curve.setAttribute("clip-path", "url(#" + clipPath.id + ")"); |
127 if(value < this.ymin){ |
137 if(value < this.ymin){ |
128 ymin_damaged = false; |
138 ymin_damaged = false; |
129 this.ymin = value; |
139 this.ymin = value; |
130 } |
140 } |
131 } |
141 } |
132 let Ylength = this.ymax - this.ymin; |
142 let Yrange = this.ymax - this.ymin; |
|
143 |
|
144 let [xMargin,yMargin] = zip(this.Margins, [Xrange, Yrange]).map(([m,l]) => m*l); |
|
145 [[this.dxmin, this.dxmax],[this.dymin,this.dymax]] = |
|
146 [[this.xmin-xMargin, this.xmax+xMargin], |
|
147 [this.ymin-yMargin, this.ymax+yMargin]]; |
|
148 Xrange += 2*xMargin; |
|
149 Yrange += 2*yMargin; |
133 |
150 |
134 // recompute curves "d" attribute |
151 // recompute curves "d" attribute |
135 // FIXME: use SVG getPathData and setPathData when available. |
152 // FIXME: use SVG getPathData and setPathData when available. |
136 // https://svgwg.org/specs/paths/#InterfaceSVGPathData |
153 // https://svgwg.org/specs/paths/#InterfaceSVGPathData |
137 // https://github.com/jarek-foksa/path-data-polyfill |
154 // https://github.com/jarek-foksa/path-data-polyfill |
139 let [base_point, xvect, yvect] = this.reference.getBaseRef(); |
156 let [base_point, xvect, yvect] = this.reference.getBaseRef(); |
140 this.curves_d_attr = |
157 this.curves_d_attr = |
141 zip(this.curves_data, this.curves).map(([data,curve]) => { |
158 zip(this.curves_data, this.curves).map(([data,curve]) => { |
142 let new_d = data.map(([x,y], i) => { |
159 let new_d = data.map(([x,y], i) => { |
143 // compute curve point from data, ranges, and base_ref |
160 // compute curve point from data, ranges, and base_ref |
144 let xv = vectorscale(xvect, (x - this.xmin) / Xlength); |
161 let xv = vectorscale(xvect, (x - this.dxmin) / Xrange); |
145 let yv = vectorscale(yvect, (y - this.ymin) / Ylength); |
162 let yv = vectorscale(yvect, (y - this.dymin) / Yrange); |
146 let px = base_point.x + xv.x + yv.x; |
163 let px = base_point.x + xv.x + yv.x; |
147 let py = base_point.y + xv.y + yv.y; |
164 let py = base_point.y + xv.y + yv.y; |
148 if(!this.fixed_y_range){ |
165 if(!this.fixed_y_range){ |
149 if(ymin_damaged && y < this.ymin) this.ymin = y; |
166 if(ymin_damaged && y < this.ymin) this.ymin = y; |
150 if(ymax_damaged && y > this.ymax) this.ymax = y; |
167 if(ymax_damaged && y > this.ymax) this.ymax = y; |
167 |
184 |
168 // move elements only if enough data |
185 // move elements only if enough data |
169 if(this.curves_data.some(data => data.length > 1)){ |
186 if(this.curves_data.some(data => data.length > 1)){ |
170 |
187 |
171 // move marks and update labels |
188 // move marks and update labels |
172 this.reference.applyRanges([[this.xmin, this.xmax], |
189 this.reference.applyRanges([[this.dxmin, this.dxmax], |
173 [this.ymin, this.ymax]]); |
190 [this.dymin, this.dymax]]); |
174 |
191 |
175 // apply computed curves "d" attributes |
192 // apply computed curves "d" attributes |
176 for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){ |
193 for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){ |
177 curve.setAttribute("d", d_attr); |
194 curve.setAttribute("d", d_attr); |
178 } |
195 } |
218 |
235 |
219 function vectorscale(p1, p2) { |
236 function vectorscale(p1, p2) { |
220 return new DOMPoint(p2 * p1.x , p2 * p1.y); |
237 return new DOMPoint(p2 * p1.x , p2 * p1.y); |
221 }; |
238 }; |
222 |
239 |
|
240 function vectorLength(p1) { |
|
241 return Math.sqrt(p1.x*p1.x + p1.y*p1.y); |
|
242 }; |
|
243 |
|
244 function randomId(){ |
|
245 return Date.now().toString(36) + Math.random().toString(36).substr(2); |
|
246 } |
|
247 |
223 function move_elements_to_group(elements) { |
248 function move_elements_to_group(elements) { |
224 let newgroup = document.createElementNS(xmlns,"g"); |
249 let newgroup = document.createElementNS(xmlns,"g"); |
225 newgroup.id = Date.now().toString(36) + Math.random().toString(36).substr(2); |
250 newgroup.id = randomId(); |
226 |
251 |
227 for(let element of elements){ |
252 for(let element of elements){ |
228 let parent = element.parentElement; |
253 let parent = element.parentElement; |
229 if(parent !== null) |
254 if(parent !== null) |
230 parent.removeChild(element); |
255 parent.removeChild(element); |
306 + -xvect.x + "," + -xvect.y + " " |
331 + -xvect.x + "," + -xvect.y + " " |
307 + -yvect.x + "," + -yvect.y + " z"; |
332 + -yvect.x + "," + -yvect.y + " z"; |
308 |
333 |
309 this.base_ref = [base_point, xvect, yvect]; |
334 this.base_ref = [base_point, xvect, yvect]; |
310 |
335 |
|
336 this.lengths = [xvect,yvect].map(v => vectorLength(v)); |
|
337 |
311 for(let axis of this.axes){ |
338 for(let axis of this.axes){ |
312 axis.setBasePoint(base_point); |
339 axis.setBasePoint(base_point); |
313 } |
340 } |
|
341 } |
|
342 |
|
343 getLengths(){ |
|
344 return this.lengths; |
314 } |
345 } |
315 |
346 |
316 getBaseRef(){ |
347 getBaseRef(){ |
317 return this.base_ref; |
348 return this.base_ref; |
318 } |
349 } |
370 |
401 |
371 // add transforms for elements sliding along the axis line |
402 // add transforms for elements sliding along the axis line |
372 for(let [elementname,element] of zip(["minor", "major", "label"],[...marks,label])){ |
403 for(let [elementname,element] of zip(["minor", "major", "label"],[...marks,label])){ |
373 for(let name of ["base","slide"]){ |
404 for(let name of ["base","slide"]){ |
374 let transform = svg_root.createSVGTransform(); |
405 let transform = svg_root.createSVGTransform(); |
375 element.transform.baseVal.appendItem(transform); |
406 element.transform.baseVal.insertItemBefore(transform,0); |
376 this[elementname+"_"+name+"_transform"]=transform; |
407 this[elementname+"_"+name+"_transform"]=transform; |
377 }; |
408 }; |
378 }; |
409 }; |
379 |
410 |
380 // group marks an labels together |
411 // group marks an labels together |
393 |
424 |
394 this.marks_and_label_group_transform = svg_root.createSVGTransform(); |
425 this.marks_and_label_group_transform = svg_root.createSVGTransform(); |
395 this.marks_and_label_group.transform.baseVal.appendItem(this.marks_and_label_group_transform); |
426 this.marks_and_label_group.transform.baseVal.appendItem(this.marks_and_label_group_transform); |
396 |
427 |
397 this.duplicates = []; |
428 this.duplicates = []; |
398 this.last_mark_count = 0; |
429 this.last_duplicate_index = 0; |
399 } |
430 } |
400 |
431 |
401 setBasePoint(base_point){ |
432 setBasePoint(base_point){ |
402 // move Axis to base point |
433 // move Axis to base point |
403 let [start, _vect] = this.line; |
434 let [start, _vect] = this.line; |
443 // Unit is pow(10, integer_number ) |
474 // Unit is pow(10, integer_number ) |
444 // |
475 // |
445 // - To transform order of magnitude to an integer, floor() is used. |
476 // - To transform order of magnitude to an integer, floor() is used. |
446 // This results in a count of mark fluctuating in between 10 and 100. |
477 // This results in a count of mark fluctuating in between 10 and 100. |
447 // |
478 // |
448 // - To spare resources result is better in between 5 and 50, |
479 // - To spare resources result is better in between 3 and 30, |
449 // and log10(5) is substracted to order of magnitude to obtain this |
480 // and log10(3) is substracted to order of magnitude to obtain this |
450 // log10(5) ~= 0.69897 |
481 let unit = Math.pow(10, Math.floor(Math.log10(range)-Math.log10(3))); |
451 |
|
452 |
|
453 let unit = Math.pow(10, Math.floor(Math.log10(range)-0.69897)); |
|
454 |
482 |
455 // TODO: for time values (ms), units may be : |
483 // TODO: for time values (ms), units may be : |
456 // 1 -> ms |
484 // 1 -> ms |
457 // 10 -> s/100 |
485 // 10 -> s/100 |
458 // 100 -> s/10 |
486 // 100 -> s/10 |
506 // v xoffset| |
534 // v xoffset| |
507 // X<------>| |
535 // X<------>| |
508 // base_point |
536 // base_point |
509 |
537 |
510 // move major marks and label to first positive mark position |
538 // move major marks and label to first positive mark position |
511 let v = vectorscale(unit_vect, unit); |
539 // let v = vectorscale(unit_vect, unit); |
512 this.label_slide_transform.setTranslate(v.x, v.y); |
540 // this.label_slide_transform.setTranslate(v.x, v.y); |
513 this.major_slide_transform.setTranslate(v.x, v.y); |
541 // this.major_slide_transform.setTranslate(v.x, v.y); |
514 // move minor mark to first half positive mark position |
542 // move minor mark to first half positive mark position |
515 v = vectorscale(unit_vect, unit/2); |
543 let v = vectorscale(unit_vect, unit/2); |
516 this.minor_slide_transform.setTranslate(v.x, v.y); |
544 this.minor_slide_transform.setTranslate(v.x, v.y); |
517 |
545 |
518 // duplicate marks and labels as needed |
546 // duplicate marks and labels as needed |
519 let current_mark_count = this.duplicates.length; |
547 let current_mark_count = this.duplicates.length; |
520 for(let i = current_mark_count; i <= mark_count; i++){ |
548 for(let i = current_mark_count; i <= mark_count; i++){ |
521 // cloneNode() label and add a svg:use of marks in a new group |
549 // cloneNode() label and add a svg:use of marks in a new group |
522 let newgroup = document.createElementNS(xmlns,"g"); |
550 let newgroup = document.createElementNS(xmlns,"g"); |
523 let transform = svg_root.createSVGTransform(); |
551 let transform = svg_root.createSVGTransform(); |
524 let newlabel = this.label.cloneNode(true); |
552 let newlabel = this.label.cloneNode(true); |
525 let newuse = document.createElementNS(xmlns,"use"); |
553 let newuse = document.createElementNS(xmlns,"use"); |
526 let newuseAttr = document.createAttribute("xlink:href"); |
554 let newuseAttr = document.createAttribute("href"); |
527 newuseAttr.value = "#"+this.marks_group.id; |
555 newuseAttr.value = "#"+this.marks_group.id; |
528 newuse.setAttributeNode(newuseAttr); |
556 newuse.setAttributeNode(newuseAttr); |
529 newgroup.transform.baseVal.appendItem(transform); |
557 newgroup.transform.baseVal.appendItem(transform); |
530 newgroup.appendChild(newlabel); |
558 newgroup.appendChild(newlabel); |
531 newgroup.appendChild(newuse); |
559 newgroup.appendChild(newuse); |
558 // -5 -4 | |
586 // -5 -4 | |
559 // offset | |
587 // offset | |
560 // X<--------->| |
588 // X<--------->| |
561 // base_point |
589 // base_point |
562 |
590 |
|
591 let duplicate_index = 0; |
563 for(let mark_index = 0; mark_index <= mark_count; mark_index++){ |
592 for(let mark_index = 0; mark_index <= mark_count; mark_index++){ |
564 let i = 0; |
|
565 let val = (mark_min + mark_index) * unit; |
593 let val = (mark_min + mark_index) * unit; |
566 let vec = vectorscale(unit_vect, offset + val - min); |
594 let vec = vectorscale(unit_vect, val - min); |
567 let text = this.format ? sprintf(this.format, val) : val.toString(); |
595 let text = this.format ? sprintf(this.format, val) : val.toString(); |
568 if(mark_index == mark_offset){ |
596 if(mark_index == mark_offset){ |
569 // apply offset to original marks and label groups |
597 // apply offset to original marks and label groups |
570 this.marks_and_label_group_transform.setTranslate(vec.x, vec.y); |
598 this.marks_and_label_group_transform.setTranslate(vec.x, vec.y); |
571 |
599 |
572 // update original label text |
600 // update original label text |
573 this.label.getElementsByTagName("tspan")[0].textContent = text; |
601 this.label.getElementsByTagName("tspan")[0].textContent = text; |
574 } else { |
602 } else { |
575 let [transform,element] = this.duplicates[i++]; |
603 let [transform,element] = this.duplicates[duplicate_index++]; |
576 |
604 |
577 // apply unit vector*N to marks and label groups |
605 // apply unit vector*N to marks and label groups |
578 transform.setTranslate(vec.x, vec.y); |
606 transform.setTranslate(vec.x, vec.y); |
579 |
607 |
580 // update label text |
608 // update label text |
581 element.getElementsByTagName("tspan")[0].textContent = text; |
609 element.getElementsByTagName("tspan")[0].textContent = text; |
582 |
610 |
583 // Attach to group if not already |
611 // Attach to group if not already |
584 if(i >= this.last_mark_count){ |
612 if(element.parentElement == null){ |
585 this.group.appendChild(element); |
613 this.group.appendChild(element); |
586 } |
614 } |
587 } |
615 } |
588 } |
616 } |
589 |
617 |
|
618 let save_duplicate_index = duplicate_index; |
590 // dettach marks and label from group if not anymore visible |
619 // dettach marks and label from group if not anymore visible |
591 for(let i = current_mark_count; i < this.last_mark_count; i++){ |
620 for(;duplicate_index < this.last_duplicate_index; duplicate_index++){ |
592 let [transform,element] = this.duplicates[i]; |
621 let [transform,element] = this.duplicates[duplicate_index]; |
593 this.group.removeChild(element); |
622 this.group.removeChild(element); |
594 } |
623 } |
595 |
624 |
596 this.last_mark_count = current_mark_count; |
625 this.last_duplicate_index = save_duplicate_index; |
597 |
626 |
598 return vectorscale(unit_vect, offset); |
627 return vectorscale(unit_vect, offset); |
599 } |
628 } |
600 } |
629 } |
601 || |
630 || |