--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/svghmi/pythonic.js Wed May 11 12:12:16 2022 +0200
@@ -0,0 +1,206 @@
+/*
+
+From https://github.com/keyvan-m-sadeghi/pythonic
+
+Slightly modified in order to be usable in browser (i.e. not as a node.js module)
+
+The MIT License (MIT)
+
+Copyright (c) 2016 Assister.Ai
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+*/
+
+class Iterator {
+ constructor(generator) {
+ this[Symbol.iterator] = generator;
+ }
+
+ async * [Symbol.asyncIterator]() {
+ for (const element of this) {
+ yield await element;
+ }
+ }
+
+ map(callback) {
+ const result = [];
+ for (const element of this) {
+ result.push(callback(element));
+ }
+
+ return result;
+ }
+
+ filter(callback) {
+ const result = [];
+ for (const element of this) {
+ if (callback(element)) {
+ result.push(element);
+ }
+ }
+
+ return result;
+ }
+
+ reduce(callback, initialValue) {
+ let empty = typeof initialValue === 'undefined';
+ let accumulator = initialValue;
+ let index = 0;
+ for (const currentValue of this) {
+ if (empty) {
+ accumulator = currentValue;
+ empty = false;
+ continue;
+ }
+
+ accumulator = callback(accumulator, currentValue, index, this);
+ index++;
+ }
+
+ if (empty) {
+ throw new TypeError('Reduce of empty Iterator with no initial value');
+ }
+
+ return accumulator;
+ }
+
+ some(callback) {
+ for (const element of this) {
+ if (callback(element)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ every(callback) {
+ for (const element of this) {
+ if (!callback(element)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static fromIterable(iterable) {
+ return new Iterator(function * () {
+ for (const element of iterable) {
+ yield element;
+ }
+ });
+ }
+
+ toArray() {
+ return Array.from(this);
+ }
+
+ next() {
+ if (!this.currentInvokedGenerator) {
+ this.currentInvokedGenerator = this[Symbol.iterator]();
+ }
+
+ return this.currentInvokedGenerator.next();
+ }
+
+ reset() {
+ delete this.currentInvokedGenerator;
+ }
+}
+
+function rangeSimple(stop) {
+ return new Iterator(function * () {
+ for (let i = 0; i < stop; i++) {
+ yield i;
+ }
+ });
+}
+
+function rangeOverload(start, stop, step = 1) {
+ return new Iterator(function * () {
+ for (let i = start; i < stop; i += step) {
+ yield i;
+ }
+ });
+}
+
+function range(...args) {
+ if (args.length < 2) {
+ return rangeSimple(...args);
+ }
+
+ return rangeOverload(...args);
+}
+
+function enumerate(iterable) {
+ return new Iterator(function * () {
+ let index = 0;
+ for (const element of iterable) {
+ yield [index, element];
+ index++;
+ }
+ });
+}
+
+const _zip = longest => (...iterables) => {
+ if (iterables.length < 2) {
+ throw new TypeError("zip takes 2 iterables at least, "+iterables.length+" given");
+ }
+
+ return new Iterator(function * () {
+ const iterators = iterables.map(iterable => Iterator.fromIterable(iterable));
+ while (true) {
+ const row = iterators.map(iterator => iterator.next());
+ const check = longest ? row.every.bind(row) : row.some.bind(row);
+ if (check(next => next.done)) {
+ return;
+ }
+
+ yield row.map(next => next.value);
+ }
+ });
+};
+
+const zip = _zip(false), zipLongest= _zip(true);
+
+function items(obj) {
+ let {keys, get} = obj;
+ if (obj instanceof Map) {
+ keys = keys.bind(obj);
+ get = get.bind(obj);
+ } else {
+ keys = function () {
+ return Object.keys(obj);
+ };
+
+ get = function (key) {
+ return obj[key];
+ };
+ }
+
+ return new Iterator(function * () {
+ for (const key of keys()) {
+ yield [key, get(key)];
+ }
+ });
+}
+
+/*
+module.exports = {Iterator, range, enumerate, zip: _zip(false), zipLongest: _zip(true), items};
+*/
--- a/svghmi/widget_xygraph.ysl2 Fri May 06 11:04:54 2022 +0200
+++ b/svghmi/widget_xygraph.ysl2 Wed May 11 12:12:16 2022 +0200
@@ -40,7 +40,7 @@
// not to clip data.
this.clip = false;
- let y_min = -Infinity, y_max = Infinity;
+ let y_min = Infinity, y_max = -Infinity;
// Compute visible Y range by merging fixed curves Y ranges
for(let minmax of this.minmaxes){
@@ -48,12 +48,12 @@
let [min,max] = minmax;
if(min < y_min)
y_min = min;
- if(max > y_max)
+ if(max > y_max)
y_max = max;
}
}
- if(y_min !== -Infinity && y_max !== Infinity){
+ if(y_min !== Infinity && y_max !== -Infinity){
this.fixed_y_range = true;
} else {
this.fixed_y_range = false;
@@ -66,16 +66,16 @@
this.init_specific();
this.reference = new ReferenceFrame(
- [[this.x_interval_minor_mark, this.x_interval_major_mark],
- [this.y_interval_minor_mark, this.y_interval_major_mark]],
- [this.y_axis_label, this.x_axis_label],
- [this.x_axis_line, this.y_axis_line],
+ [[this.x_interval_minor_mark_elt, this.x_interval_major_mark_elt],
+ [this.y_interval_minor_mark_elt, this.y_interval_major_mark_elt]],
+ [this.x_axis_label_elt, this.y_axis_label_elt],
+ [this.x_axis_line_elt, this.y_axis_line_elt],
[this.x_format, this.y_format]);
// create <clipPath> path and attach it to widget
- clipPath = document.createElementNS(xmlns,"clipPath");
- clipPathPath = document.createElementNS(xmlns,"path");
- clipPathPathDattr = document.createAttribute("d");
+ let clipPath = document.createElementNS(xmlns,"clipPath");
+ let clipPathPath = document.createElementNS(xmlns,"path");
+ let clipPathPathDattr = document.createAttribute("d");
clipPathPathDattr.value = this.reference.getClipPathPathDattr();
clipPathPath.setAttributeNode(clipPathPathDattr);
clipPath.appendChild(clipPathPath);
@@ -86,7 +86,7 @@
curve.setAttribute("clip-path", "url(#" + clipPath.id + ")");
}
- this.curves_data = [];
+ this.curves_data = this.curves.map(_unused => []);
this.max_data_length = this.args[0];
}
@@ -119,6 +119,7 @@
this.ymin = value;
}
}
+ let Ylength = this.ymax - this.ymin;
// recompute X range based on curent time ad buffer depth
// TODO: get PLC time instead of browser time
@@ -134,31 +135,30 @@
// FIXME: use SVG getPathData and setPathData when available.
// https://svgwg.org/specs/paths/#InterfaceSVGPathData
// https://github.com/jarek-foksa/path-data-polyfill
-
+
let [base_point, xvect, yvect] = this.reference.getBaseRef();
- this.curves_d_attr =
- zip(this.curves_data, this.curves).map(function([data,curve]){
- let new_d = data.map(function([y, i]){
- // compute curve point from data, ranges, and base_ref
- let x = Xmin + i * Xlength / data_length;
- let xv = vectorscale(xvect, (x - Xmin) / Xlength);
- let yv = vectorscale(yvect, (y - Ymin) / Ylength);
- let px = base_point.x + xv.x + yv.x;
- let py = base_point.y + xv.y + yv.y;
- if(!this.fixed_y_range){
- if(ymin_damaged && y > this.ymin) this.ymin = y;
- if(xmin_damaged && x > this.xmin) this.xmin = x;
- }
-
- return " " + px + "," + py;
+ this.curves_d_attr =
+ zip(this.curves_data, this.curves).map(([data,curve]) => {
+ let new_d = data.map((y, i) => {
+ // compute curve point from data, ranges, and base_ref
+ let x = this.xmin + i * Xlength / data_length;
+ let xv = vectorscale(xvect, (x - this.xmin) / Xlength);
+ let yv = vectorscale(yvect, (y - this.ymin) / Ylength);
+ let px = base_point.x + xv.x + yv.x;
+ let py = base_point.y + xv.y + yv.y;
+ if(!this.fixed_y_range){
+ if(ymin_damaged && y < this.ymin) this.ymin = y;
+ if(ymax_damaged && y > this.ymax) this.ymax = y;
+ }
+
+ return " " + px + "," + py;
+ });
+
+ new_d.unshift("M ");
+
+ return new_d.join('');
});
- new_d.unshift("M ");
- new_d.push(" z");
-
- return new_d.join('');
- }
-
// computed curves "d" attr is applied to svg curve during animate();
this.request_animate();
@@ -166,12 +166,17 @@
animate(){
- // move marks and update labels
- this.reference.applyRanges([this.XRange, this.YRange]);
-
- // apply computed curves "d" attributes
- for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){
- curve.setAttribute("d", d_attr);
+ // move elements only if enough data
+ if(this.curves_data.some(data => data.length > 1)){
+
+ // move marks and update labels
+ this.reference.applyRanges([[this.xmin, this.xmax],
+ [this.ymin, this.ymax]]);
+
+ // apply computed curves "d" attributes
+ for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){
+ curve.setAttribute("d", d_attr);
+ }
}
}
@@ -179,8 +184,8 @@
}
widget_defs("XYGraph") {
- labels("/x_interval_minor_mark /x_axis_line /x_interval_major_mark /x_axis_label")
- labels("/y_interval_minor_mark /y_axis_line /y_interval_major_mark /y_axis_label")
+ labels("/x_interval_minor_mark /x_axis_line /x_interval_major_mark /x_axis_label");
+ labels("/y_interval_minor_mark /y_axis_line /y_interval_major_mark /y_axis_label");
| init_specific() {
@@ -219,12 +224,18 @@
function move_elements_to_group(elements) {
let newgroup = document.createElementNS(xmlns,"g");
for(let element of elements){
- element.parentElement().removeChild(element);
+ let parent = element.parentElement;
+ if(parent !== null)
+ parent.removeChild(element);
newgroup.appendChild(element);
}
return newgroup;
}
function getLinesIntesection(l1, l2) {
+ let [l1start, l1vect] = l1;
+ let [l2start, l2vect] = l2;
+
+ let b;
/*
Compute intersection of two lines
=================================
@@ -243,45 +254,35 @@
intersection = l2start + l2vect * b
==> solve : "l1start + l1vect * a = l2start + l2vect * b" to find a and b and then intersection
- (1) l1start.x + l1vect.x * a = l2start.x + l2vect.x * b
- (2) l1start.y + l1vect.y * a = l2start.y + l2vect.y * b
+ (1) l1start.y + l1vect.y * a = l2start.y + l2vect.y * b
+ (2) l1start.x + l1vect.x * a = l2start.x + l2vect.x * b
// express a
- (1) a = (l2start.x + l2vect.x * b) / (l1start.x + l1vect.x)
+ (1) a = (l2start.y + l2vect.y * b - l1start.y) / l1vect.y
// substitute a to have only b
- (1+2) l1start.y + l1vect.y * (l2start.x + l2vect.x * b) / (l1start.x + l1vect.x) = l2start.y + l2vect.y * b
+ (1+2) l1start.x + l1vect.x * (l2start.y + l2vect.y * b - l1start.y) / l1vect.y = l2start.x + l2vect.x * b
// expand to isolate b
- (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
- (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)
+ l1start.x + l1vect.x * l2start.y / l1vect.y + l2vect.y / l1vect.y * b - l1start.y / l1vect.y = l2start.x + l2vect.x * b
// factorize b
- (2) l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y = b * (l2vect.y - (l1vect.y * l2vect.x ) / (l1start.x + l1vect.x))
+ l1start.x + l1vect.x * l2start.y / l1vect.y - l1start.y / l1vect.y - l2start.x = b * ( l2vect.x - l2vect.y / l1vect.y)
// extract b
- (2) b = (l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y) / ((l2vect.y - (l1vect.y * l2vect.x ) / (l1start.x + l1vect.x)))
+ b = (l1start.x + l1vect.x * l2start.y / l1vect.y - l1start.y / l1vect.y - l2start.x) / ( l2vect.x - l2vect.y / l1vect.y)
*/
- let [l1start, l1vect] = l1;
- let [l2start, l2vect] = l2;
-
- let b = (l1start.y + (l1vect.y * l2start.x) / (l1start.x + l1vect.x) - l2start.y) / ((l2vect.y - (l1vect.y * l2vect.x ) / (l1start.x + l1vect.x)));
+ /* avoid divison by zero by swapping (1) and (2) */
+ if(l1vect.y == 0 ){
+ b = (l1start.y + l1vect.y * l2start.x / l1vect.x - l1start.x / l1vect.x - l2start.y) / ( l2vect.y - l2vect.x / l1vect.x);
+ }else{
+ b = (l1start.x + l1vect.x * l2start.y / l1vect.y - l1start.y / l1vect.y - l2start.x) / ( l2vect.x - l2vect.y / l1vect.y);
+ }
return new DOMPoint(l2start.x + l2vect.x * b, l2start.y + l2vect.y * b);
};
-
-// From https://stackoverflow.com/a/48293566
-function *zip (...iterables){
- let iterators = iterables.map(i => i[Symbol.iterator]() )
- while (true) {
- let results = iterators.map(iter => iter.next() )
- if (results.some(res => res.done) ) return
- else yield results.map(res => res.value )
- }
-}
-
class ReferenceFrame {
constructor(
// [[Xminor,Xmajor], [Yminor,Ymajor]]
@@ -293,7 +294,7 @@
// [Xformat, Yformat] printf-like formating strings
formats
){
- this.axes = zip(labels,marks,lines,formats).map((...args) => new Axis(...args));
+ this.axes = zip(labels,marks,lines,formats).map(args => new Axis(...args));
let [lx,ly] = this.axes.map(axis => axis.line);
let [[xstart, xvect], [ystart, yvect]] = [lx,ly];
@@ -324,7 +325,7 @@
applyRanges(ranges){
for(let [range,axis] of zip(ranges,this.axes)){
- axis.applyRange(range);
+ axis.applyRange(...range);
}
}
@@ -340,46 +341,19 @@
Given axes lines are not starting at the same point, hereafter is
calculus of parallelogram base point.
- ^ given Y axis
+ ^ given Y axis (yvect)
/ /
/ /
/ /
- xstart /---------/--------------> given X axis
- / /
+ xstart *---------*--------------> given X axis (xvect)
+ / /origin
/ /
- /---------/--------------
+ *---------*--------------
base_point ystart
- base_point = xstart + yvect * a
- base_point = ystart + xvect * b
- ==> solve : "xstart + yvect * a = ystart + xvect * b" to find a and b and then base_point
-
- (1) xstart.x + yvect.x * a = ystart.x + xvect.x * b
- (2) xstart.y + yvect.y * a = ystart.y + xvect.y * b
-
- // express a
- (1) a = (ystart.x + xvect.x * b) / (xstart.x + yvect.x)
-
- // substitute a to have only b
- (1+2) xstart.y + yvect.y * (ystart.x + xvect.x * b) / (xstart.x + yvect.x) = ystart.y + xvect.y * b
-
- // expand to isolate b
- (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
- (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)
-
- // factorize b
- (2) xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y = b * (xvect.y - (yvect.y * xvect.x ) / (xstart.x + yvect.x))
-
- // extract b
- (2) b = (xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y) / ((xvect.y - (yvect.y * xvect.x ) / (xstart.x + yvect.x)))
*/
- let b = (xstart.y + (yvect.y * ystart.x) / (xstart.x + yvect.x) - ystart.y) / ((xvect.y - (yvect.y * xvect.x ) / (xstart.x + yvect.x)));
- let base_point = new DOMPoint(ystart.x + xvect.x * b, ystart.y + xvect.y * b);
-
- // // compute given origin
- // // from drawing : given_origin = xstart - xvect * b
- // let given_origin = new DOMPoint(xstart.x - xvect.x * b, xstart.y - xvect.y * b);
+ let base_point = getLinesIntesection([xstart,yvect],[ystart,xvect]);
return base_point;
@@ -407,13 +381,13 @@
};
// group marks an labels together
- let parent = line.parentElement()
- marks_group = move_elements_to_group(marks);
- marks_and_label_group = move_elements_to_group([marks_group_use, label]);
- group = move_elements_to_group([marks_and_label_group,line]);
+ let parent = line.parentElement;
+ let marks_group = move_elements_to_group(marks);
+ let marks_and_label_group = move_elements_to_group([marks_group, label]);
+ let group = move_elements_to_group([marks_and_label_group,line]);
parent.appendChild(group);
- // Add transforms to group
+ // Add transforms to group
for(let name of ["base","origin"]){
let transform = svg_root.createSVGTransform();
group.transform.baseVal.appendItem(transform);
@@ -429,16 +403,16 @@
}
setBasePoint(base_point){
- // move Axis to base point
- let [start, _vect] = this.lineElement;
+ // move Axis to base point
+ let [start, _vect] = this.line;
let v = vector(start, base_point);
this.base_transform.setTranslate(v.x, v.y);
- // Move marks and label to base point.
- // _|_______ _|________
- // | ' | ==> '
- // | 0 0
- // | |
+ // Move marks and label to base point.
+ // _|_______ _|________
+ // | ' | ==> '
+ // | 0 0
+ // | |
for(let [markname,mark] of zip(["minor", "major"],this.marks)){
let transform = this[markname+"_base_transform"];
@@ -456,31 +430,6 @@
}
}
- applyOriginAndUnitVector(offset, unit_vect){
- // offset is a representing position of an
- // axis along the opposit axis line, expressed in major marks units
- // unit_vect is the translation in between to major marks
-
- // ^
- // | unit_vect
- // |<--->
- // _________|__________>
- // ^ | ' | ' | '
- // |yoffset | 1
- // | |
- // v xoffset|
- // X<------>|
- // base_point
-
- // move major marks and label to first positive mark position
- let v = vectorscale(unit_vect, offset+1);
- this.label_slide_transform.setTranslate(v.x, v.x);
- this.major_slide_transform.setTranslate(v.x, v.x);
- // move minor mark to first half positive mark position
- let h = vectorscale(unit_vect, offset+0.5);
- this.minor_slide_transform.setTranslate(h.x, h.x);
- }
-
applyRange(min, max){
let range = max - min;
@@ -544,7 +493,28 @@
let mark_count = umax-umin;
// apply unit vector to marks and label
- this.label_and_marks.applyOriginAndUnitVector(offset, unit_vect);
+ // offset is a representing position of an
+ // axis along the opposit axis line, expressed in major marks units
+ // unit_vect is the translation in between to major marks
+
+ // ^
+ // | unit_vect
+ // |<--->
+ // _________|__________>
+ // ^ | ' | ' | '
+ // |yoffset | 1
+ // | |
+ // v xoffset|
+ // X<------>|
+ // base_point
+
+ // move major marks and label to first positive mark position
+ let v = vectorscale(unit_vect, offset+unit);
+ this.label_slide_transform.setTranslate(v.x, v.y);
+ this.major_slide_transform.setTranslate(v.x, v.y);
+ // move minor mark to first half positive mark position
+ let h = vectorscale(unit_vect, offset+(unit/2));
+ this.minor_slide_transform.setTranslate(h.x, h.y);
// duplicate marks and labels as needed
let current_mark_count = this.mlg_clones.length;
@@ -552,15 +522,15 @@
// cloneNode() label and add a svg:use of marks in a new group
let newgroup = document.createElementNS(xmlns,"g");
let transform = svg_root.createSVGTransform();
- let newlabel = cloneNode(this.label);
+ let newlabel = this.label.cloneNode(true);
let newuse = document.createElementNS(xmlns,"use");
let newuseAttr = document.createAttribute("xlink:href");
newuseAttr.value = "#"+this.marks_group.id;
- newuse.setAttributeNode(newuseAttr.value);
+ newuse.setAttributeNode(newuseAttr);
newgroup.transform.baseVal.appendItem(transform);
newgroup.appendChild(newlabel);
newgroup.appendChild(newuse);
- this.mlg_clones.push([tranform,newgroup]);
+ this.mlg_clones.push([transform,newgroup]);
}
// move marks and labels, set labels
@@ -571,15 +541,15 @@
let text = this.format ? sprintf(this.format, val) : val.toString();
if(u == uoffset){
// apply offset to original marks and label groups
- this.origin_transform.setTranslate(vec.x, vec.x);
+ this.origin_transform.setTranslate(vec.x, vec.y);
// update original label text
- this.label_and_mark.label.textContent = text;
+ this.label.getElementsByTagName("tspan")[0].textContent = text;
} else {
let [transform,element] = this.mlg_clones[i++];
// apply unit vector*N to marks and label groups
- transform.setTranslate(vec.x, vec.x);
+ transform.setTranslate(vec.x, vec.y);
// update label text
element.getElementsByTagName("tspan")[0].textContent = text;