20 |
20 |
21 shortdesc > Cartesian trend graph showing values of given variables over time |
21 shortdesc > Cartesian trend graph showing values of given variables over time |
22 |
22 |
23 path name="value" count="1+" accepts="HMI_INT,HMI_REAL" > value |
23 path name="value" count="1+" accepts="HMI_INT,HMI_REAL" > value |
24 |
24 |
25 arg name="size" accepts="int" > buffer size |
25 arg name="xrange" accepts="int,time" > X axis range expressed either in samples or duration. |
26 arg name="xformat" count="optional" accepts="string" > format string for X label |
26 arg name="xformat" count="optional" accepts="string" > format string for X label |
27 arg name="yformat" count="optional" accepts="string" > format string for Y label |
27 arg name="yformat" count="optional" accepts="string" > format string for Y label |
28 arg name="ymin" count="optional" accepts="int,real" > minimum value foe Y axis |
|
29 arg name="ymax" count="optional" accepts="int,real" > maximum value for Y axis |
|
30 } |
28 } |
31 |
29 |
32 widget_class("XYGraph") { |
30 widget_class("XYGraph") { |
33 || |
31 || |
34 frequency = 1; |
32 frequency = 1; |
35 init() { |
33 init() { |
36 [this.x_size, |
34 let x_duration_s; |
|
35 [x_duration_s, |
37 this.x_format, this.y_format] = this.args; |
36 this.x_format, this.y_format] = this.args; |
|
37 |
|
38 let timeunit = x_duration_s.slice(-1); |
|
39 let factor = { |
|
40 "s":1, |
|
41 "m":60, |
|
42 "h":3600, |
|
43 "d":86400}[timeunit]; |
|
44 if(factor == undefined){ |
|
45 this.max_data_length = Number(x_duration_s); |
|
46 this.x_duration = undefined; |
|
47 }else{ |
|
48 let duration = factor*Number(x_duration_s.slice(0,-1)); |
|
49 this.max_data_length = undefined; |
|
50 this.x_duration = duration*1000; |
|
51 } |
|
52 |
38 |
53 |
39 // Min and Max given with paths are meant to describe visible range, |
54 // Min and Max given with paths are meant to describe visible range, |
40 // not to clip data. |
55 // not to clip data. |
41 this.clip = false; |
56 this.clip = false; |
42 |
57 |
95 for(let curve of this.curves){ |
110 for(let curve of this.curves){ |
96 curve.setAttribute("clip-path", "url(#" + clipPath.id + ")"); |
111 curve.setAttribute("clip-path", "url(#" + clipPath.id + ")"); |
97 } |
112 } |
98 |
113 |
99 this.curves_data = this.curves.map(_unused => []); |
114 this.curves_data = this.curves.map(_unused => []); |
100 this.max_data_length = this.args[0]; |
|
101 } |
115 } |
102 |
116 |
103 dispatch(value,oldval, index) { |
117 dispatch(value,oldval, index) { |
104 // TODO: get PLC time instead of browser time |
118 // TODO: get PLC time instead of browser time |
105 let time = Date.now(); |
119 let time = Date.now(); |
112 let data_length = this.curves_data[index].length; |
126 let data_length = this.curves_data[index].length; |
113 let ymin_damaged = false; |
127 let ymin_damaged = false; |
114 let ymax_damaged = false; |
128 let ymax_damaged = false; |
115 let overflow; |
129 let overflow; |
116 |
130 |
117 if(data_length > this.max_data_length){ |
131 if(this.max_data_length == undefined){ |
118 // remove first item |
132 let peremption = time - this.x_duration; |
119 [this.xmin, overflow] = this.curves_data[index].shift(); |
133 let oldest = this.curves_data[index][0][0] |
120 data_length = data_length - 1; |
134 this.xmin = peremption; |
|
135 if(oldest < peremption){ |
|
136 // remove first item |
|
137 overflow = this.curves_data[index].shift()[1]; |
|
138 data_length = data_length - 1; |
|
139 } |
121 } else { |
140 } else { |
122 if(this.xmin == undefined){ |
141 if(data_length > this.max_data_length){ |
123 this.xmin = time; |
142 // remove first item |
|
143 [this.xmin, overflow] = this.curves_data[index].shift(); |
|
144 data_length = data_length - 1; |
|
145 } else { |
|
146 if(this.xmin == undefined){ |
|
147 this.xmin = time; |
|
148 } |
124 } |
149 } |
125 } |
150 } |
126 |
151 |
127 this.xmax = time; |
152 this.xmax = time; |
128 let Xrange = this.xmax - this.xmin; |
153 let Xrange = this.xmax - this.xmin; |
139 this.ymin = value; |
164 this.ymin = value; |
140 } |
165 } |
141 } |
166 } |
142 let Yrange = this.ymax - this.ymin; |
167 let Yrange = this.ymax - this.ymin; |
143 |
168 |
|
169 // apply margin by moving min and max to enlarge range |
144 let [xMargin,yMargin] = zip(this.Margins, [Xrange, Yrange]).map(([m,l]) => m*l); |
170 let [xMargin,yMargin] = zip(this.Margins, [Xrange, Yrange]).map(([m,l]) => m*l); |
145 [[this.dxmin, this.dxmax],[this.dymin,this.dymax]] = |
171 [[this.dxmin, this.dxmax],[this.dymin,this.dymax]] = |
146 [[this.xmin-xMargin, this.xmax+xMargin], |
172 [[this.xmin-xMargin, this.xmax+xMargin], |
147 [this.ymin-yMargin, this.ymax+yMargin]]; |
173 [this.ymin-yMargin, this.ymax+yMargin]]; |
148 Xrange += 2*xMargin; |
174 Xrange += 2*xMargin; |
161 let xv = vectorscale(xvect, (x - this.dxmin) / Xrange); |
187 let xv = vectorscale(xvect, (x - this.dxmin) / Xrange); |
162 let yv = vectorscale(yvect, (y - this.dymin) / Yrange); |
188 let yv = vectorscale(yvect, (y - this.dymin) / Yrange); |
163 let px = base_point.x + xv.x + yv.x; |
189 let px = base_point.x + xv.x + yv.x; |
164 let py = base_point.y + xv.y + yv.y; |
190 let py = base_point.y + xv.y + yv.y; |
165 if(!this.fixed_y_range){ |
191 if(!this.fixed_y_range){ |
|
192 // update min and max from curve data if needed |
166 if(ymin_damaged && y < this.ymin) this.ymin = y; |
193 if(ymin_damaged && y < this.ymin) this.ymin = y; |
167 if(ymax_damaged && y > this.ymax) this.ymax = y; |
194 if(ymax_damaged && y > this.ymax) this.ymax = y; |
168 } |
195 } |
169 |
196 |
170 return " " + px + "," + py; |
197 return " " + px + "," + py; |