7670 <xsl:text>active inactive</xsl:text> |
7823 <xsl:text>active inactive</xsl:text> |
7671 </xsl:with-param> |
7824 </xsl:with-param> |
7672 <xsl:with-param name="mandatory" select="'no'"/> |
7825 <xsl:with-param name="mandatory" select="'no'"/> |
7673 </xsl:call-template> |
7826 </xsl:call-template> |
7674 </xsl:template> |
7827 </xsl:template> |
|
7828 <xsl:template match="widget[@type='XYGraph']" mode="widget_desc"> |
|
7829 <type> |
|
7830 <xsl:value-of select="@type"/> |
|
7831 </type> |
|
7832 <longdesc> |
|
7833 <xsl:text>XYGraph draws a cartesian trend graph re-using styles given for axis, |
|
7834 </xsl:text> |
|
7835 <xsl:text>grid/marks, legends and curves. |
|
7836 </xsl:text> |
|
7837 <xsl:text> |
|
7838 </xsl:text> |
|
7839 <xsl:text>Elements labeled "x_axis" and "y_axis" are svg:groups containg: |
|
7840 </xsl:text> |
|
7841 <xsl:text> - "axis_label" svg:text gives style an alignment for axis labels. |
|
7842 </xsl:text> |
|
7843 <xsl:text> - "interval_major_mark" and "interval_minor_mark" are svg elements to be |
|
7844 </xsl:text> |
|
7845 <xsl:text> duplicated along axis line to form intervals marks. |
|
7846 </xsl:text> |
|
7847 <xsl:text> - "axis_line" svg:path is the axis line. Paths must be intersect and their |
|
7848 </xsl:text> |
|
7849 <xsl:text> bounding box is the chart wall. |
|
7850 </xsl:text> |
|
7851 <xsl:text> |
|
7852 </xsl:text> |
|
7853 <xsl:text>Elements labeled "curve_0", "curve_1", ... are paths whose styles are used |
|
7854 </xsl:text> |
|
7855 <xsl:text>to draw curves corresponding to data from variables passed as HMI tree paths. |
|
7856 </xsl:text> |
|
7857 <xsl:text>"curve_0" is mandatory. HMI variables outnumbering given curves are ignored. |
|
7858 </xsl:text> |
|
7859 <xsl:text> |
|
7860 </xsl:text> |
|
7861 </longdesc> |
|
7862 <shortdesc> |
|
7863 <xsl:text>Cartesian trend graph showing values of given variables over time</xsl:text> |
|
7864 </shortdesc> |
|
7865 <path name="value" count="1+" accepts="HMI_INT,HMI_REAL"> |
|
7866 <xsl:text>value</xsl:text> |
|
7867 </path> |
|
7868 <arg name="size" accepts="int"> |
|
7869 <xsl:text>buffer size</xsl:text> |
|
7870 </arg> |
|
7871 <arg name="xformat" count="optional" accepts="string"> |
|
7872 <xsl:text>format string for X label</xsl:text> |
|
7873 </arg> |
|
7874 <arg name="yformat" count="optional" accepts="string"> |
|
7875 <xsl:text>format string for Y label</xsl:text> |
|
7876 </arg> |
|
7877 <arg name="xmin" count="optional" accepts="int,real"> |
|
7878 <xsl:text>minimum value foe X axis</xsl:text> |
|
7879 </arg> |
|
7880 <arg name="xmax" count="optional" accepts="int,real"> |
|
7881 <xsl:text>maximum value for X axis</xsl:text> |
|
7882 </arg> |
|
7883 </xsl:template> |
|
7884 <xsl:template match="widget[@type='XYGraph']" mode="widget_class"> |
|
7885 <xsl:text>class </xsl:text> |
|
7886 <xsl:text>XYGraphWidget</xsl:text> |
|
7887 <xsl:text> extends Widget{ |
|
7888 </xsl:text> |
|
7889 <xsl:text> frequency = 1; |
|
7890 </xsl:text> |
|
7891 <xsl:text> init() { |
|
7892 </xsl:text> |
|
7893 <xsl:text> let x_duration_s; |
|
7894 </xsl:text> |
|
7895 <xsl:text> [x_duration_s, |
|
7896 </xsl:text> |
|
7897 <xsl:text> this.x_format, this.y_format] = this.args; |
|
7898 </xsl:text> |
|
7899 <xsl:text> |
|
7900 </xsl:text> |
|
7901 <xsl:text> let timeunit = x_duration_s.slice(-1); |
|
7902 </xsl:text> |
|
7903 <xsl:text> let factor = { |
|
7904 </xsl:text> |
|
7905 <xsl:text> "s":1, |
|
7906 </xsl:text> |
|
7907 <xsl:text> "m":60, |
|
7908 </xsl:text> |
|
7909 <xsl:text> "h":3600, |
|
7910 </xsl:text> |
|
7911 <xsl:text> "d":86400}[timeunit]; |
|
7912 </xsl:text> |
|
7913 <xsl:text> if(factor == undefined){ |
|
7914 </xsl:text> |
|
7915 <xsl:text> this.max_data_length = Number(x_duration_s); |
|
7916 </xsl:text> |
|
7917 <xsl:text> this.x_duration = undefined; |
|
7918 </xsl:text> |
|
7919 <xsl:text> }else{ |
|
7920 </xsl:text> |
|
7921 <xsl:text> let duration = factor*Number(x_duration_s.slice(0,-1)); |
|
7922 </xsl:text> |
|
7923 <xsl:text> this.max_data_length = undefined; |
|
7924 </xsl:text> |
|
7925 <xsl:text> this.x_duration = duration*1000; |
|
7926 </xsl:text> |
|
7927 <xsl:text> } |
|
7928 </xsl:text> |
|
7929 <xsl:text> |
|
7930 </xsl:text> |
|
7931 <xsl:text> |
|
7932 </xsl:text> |
|
7933 <xsl:text> // Min and Max given with paths are meant to describe visible range, |
|
7934 </xsl:text> |
|
7935 <xsl:text> // not to clip data. |
|
7936 </xsl:text> |
|
7937 <xsl:text> this.clip = false; |
|
7938 </xsl:text> |
|
7939 <xsl:text> |
|
7940 </xsl:text> |
|
7941 <xsl:text> let y_min = Infinity, y_max = -Infinity; |
|
7942 </xsl:text> |
|
7943 <xsl:text> |
|
7944 </xsl:text> |
|
7945 <xsl:text> // Compute visible Y range by merging fixed curves Y ranges |
|
7946 </xsl:text> |
|
7947 <xsl:text> for(let minmax of this.minmaxes){ |
|
7948 </xsl:text> |
|
7949 <xsl:text> if(minmax){ |
|
7950 </xsl:text> |
|
7951 <xsl:text> let [min,max] = minmax; |
|
7952 </xsl:text> |
|
7953 <xsl:text> if(min < y_min) |
|
7954 </xsl:text> |
|
7955 <xsl:text> y_min = min; |
|
7956 </xsl:text> |
|
7957 <xsl:text> if(max > y_max) |
|
7958 </xsl:text> |
|
7959 <xsl:text> y_max = max; |
|
7960 </xsl:text> |
|
7961 <xsl:text> } |
|
7962 </xsl:text> |
|
7963 <xsl:text> } |
|
7964 </xsl:text> |
|
7965 <xsl:text> |
|
7966 </xsl:text> |
|
7967 <xsl:text> if(y_min !== Infinity && y_max !== -Infinity){ |
|
7968 </xsl:text> |
|
7969 <xsl:text> this.fixed_y_range = true; |
|
7970 </xsl:text> |
|
7971 <xsl:text> } else { |
|
7972 </xsl:text> |
|
7973 <xsl:text> this.fixed_y_range = false; |
|
7974 </xsl:text> |
|
7975 <xsl:text> } |
|
7976 </xsl:text> |
|
7977 <xsl:text> |
|
7978 </xsl:text> |
|
7979 <xsl:text> this.ymin = y_min; |
|
7980 </xsl:text> |
|
7981 <xsl:text> this.ymax = y_max; |
|
7982 </xsl:text> |
|
7983 <xsl:text> |
|
7984 </xsl:text> |
|
7985 <xsl:text> this.curves = []; |
|
7986 </xsl:text> |
|
7987 <xsl:text> this.init_specific(); |
|
7988 </xsl:text> |
|
7989 <xsl:text> |
|
7990 </xsl:text> |
|
7991 <xsl:text> this.reference = new ReferenceFrame( |
|
7992 </xsl:text> |
|
7993 <xsl:text> [[this.x_interval_minor_mark_elt, this.x_interval_major_mark_elt], |
|
7994 </xsl:text> |
|
7995 <xsl:text> [this.y_interval_minor_mark_elt, this.y_interval_major_mark_elt]], |
|
7996 </xsl:text> |
|
7997 <xsl:text> [this.x_axis_label_elt, this.y_axis_label_elt], |
|
7998 </xsl:text> |
|
7999 <xsl:text> [this.x_axis_line_elt, this.y_axis_line_elt], |
|
8000 </xsl:text> |
|
8001 <xsl:text> [this.x_format, this.y_format]); |
|
8002 </xsl:text> |
|
8003 <xsl:text> |
|
8004 </xsl:text> |
|
8005 <xsl:text> let max_stroke_width = 0; |
|
8006 </xsl:text> |
|
8007 <xsl:text> for(let curve of this.curves){ |
|
8008 </xsl:text> |
|
8009 <xsl:text> if(curve.style.strokeWidth > max_stroke_width){ |
|
8010 </xsl:text> |
|
8011 <xsl:text> max_stroke_width = curve.style.strokeWidth; |
|
8012 </xsl:text> |
|
8013 <xsl:text> } |
|
8014 </xsl:text> |
|
8015 <xsl:text> } |
|
8016 </xsl:text> |
|
8017 <xsl:text> |
|
8018 </xsl:text> |
|
8019 <xsl:text> this.Margins=this.reference.getLengths().map(length => max_stroke_width/length); |
|
8020 </xsl:text> |
|
8021 <xsl:text> |
|
8022 </xsl:text> |
|
8023 <xsl:text> // create <clipPath> path and attach it to widget |
|
8024 </xsl:text> |
|
8025 <xsl:text> let clipPath = document.createElementNS(xmlns,"clipPath"); |
|
8026 </xsl:text> |
|
8027 <xsl:text> let clipPathPath = document.createElementNS(xmlns,"path"); |
|
8028 </xsl:text> |
|
8029 <xsl:text> let clipPathPathDattr = document.createAttribute("d"); |
|
8030 </xsl:text> |
|
8031 <xsl:text> clipPathPathDattr.value = this.reference.getClipPathPathDattr(); |
|
8032 </xsl:text> |
|
8033 <xsl:text> clipPathPath.setAttributeNode(clipPathPathDattr); |
|
8034 </xsl:text> |
|
8035 <xsl:text> clipPath.appendChild(clipPathPath); |
|
8036 </xsl:text> |
|
8037 <xsl:text> clipPath.id = randomId(); |
|
8038 </xsl:text> |
|
8039 <xsl:text> this.element.appendChild(clipPath); |
|
8040 </xsl:text> |
|
8041 <xsl:text> |
|
8042 </xsl:text> |
|
8043 <xsl:text> // assign created clipPath to clip-path property of curves |
|
8044 </xsl:text> |
|
8045 <xsl:text> for(let curve of this.curves){ |
|
8046 </xsl:text> |
|
8047 <xsl:text> curve.setAttribute("clip-path", "url(#" + clipPath.id + ")"); |
|
8048 </xsl:text> |
|
8049 <xsl:text> } |
|
8050 </xsl:text> |
|
8051 <xsl:text> |
|
8052 </xsl:text> |
|
8053 <xsl:text> this.curves_data = this.curves.map(_unused => []); |
|
8054 </xsl:text> |
|
8055 <xsl:text> } |
|
8056 </xsl:text> |
|
8057 <xsl:text> |
|
8058 </xsl:text> |
|
8059 <xsl:text> dispatch(value,oldval, index) { |
|
8060 </xsl:text> |
|
8061 <xsl:text> // TODO: get PLC time instead of browser time |
|
8062 </xsl:text> |
|
8063 <xsl:text> let time = Date.now(); |
|
8064 </xsl:text> |
|
8065 <xsl:text> |
|
8066 </xsl:text> |
|
8067 <xsl:text> // naive local buffer impl. |
|
8068 </xsl:text> |
|
8069 <xsl:text> // data is updated only when graph is visible |
|
8070 </xsl:text> |
|
8071 <xsl:text> // TODO: replace with separate recording |
|
8072 </xsl:text> |
|
8073 <xsl:text> |
|
8074 </xsl:text> |
|
8075 <xsl:text> this.curves_data[index].push([time, value]); |
|
8076 </xsl:text> |
|
8077 <xsl:text> let data_length = this.curves_data[index].length; |
|
8078 </xsl:text> |
|
8079 <xsl:text> let ymin_damaged = false; |
|
8080 </xsl:text> |
|
8081 <xsl:text> let ymax_damaged = false; |
|
8082 </xsl:text> |
|
8083 <xsl:text> let overflow; |
|
8084 </xsl:text> |
|
8085 <xsl:text> |
|
8086 </xsl:text> |
|
8087 <xsl:text> if(this.max_data_length == undefined){ |
|
8088 </xsl:text> |
|
8089 <xsl:text> let peremption = time - this.x_duration; |
|
8090 </xsl:text> |
|
8091 <xsl:text> let oldest = this.curves_data[index][0][0] |
|
8092 </xsl:text> |
|
8093 <xsl:text> this.xmin = peremption; |
|
8094 </xsl:text> |
|
8095 <xsl:text> if(oldest < peremption){ |
|
8096 </xsl:text> |
|
8097 <xsl:text> // remove first item |
|
8098 </xsl:text> |
|
8099 <xsl:text> overflow = this.curves_data[index].shift()[1]; |
|
8100 </xsl:text> |
|
8101 <xsl:text> data_length = data_length - 1; |
|
8102 </xsl:text> |
|
8103 <xsl:text> } |
|
8104 </xsl:text> |
|
8105 <xsl:text> } else { |
|
8106 </xsl:text> |
|
8107 <xsl:text> if(data_length > this.max_data_length){ |
|
8108 </xsl:text> |
|
8109 <xsl:text> // remove first item |
|
8110 </xsl:text> |
|
8111 <xsl:text> [this.xmin, overflow] = this.curves_data[index].shift(); |
|
8112 </xsl:text> |
|
8113 <xsl:text> data_length = data_length - 1; |
|
8114 </xsl:text> |
|
8115 <xsl:text> } else { |
|
8116 </xsl:text> |
|
8117 <xsl:text> if(this.xmin == undefined){ |
|
8118 </xsl:text> |
|
8119 <xsl:text> this.xmin = time; |
|
8120 </xsl:text> |
|
8121 <xsl:text> } |
|
8122 </xsl:text> |
|
8123 <xsl:text> } |
|
8124 </xsl:text> |
|
8125 <xsl:text> } |
|
8126 </xsl:text> |
|
8127 <xsl:text> |
|
8128 </xsl:text> |
|
8129 <xsl:text> this.xmax = time; |
|
8130 </xsl:text> |
|
8131 <xsl:text> let Xrange = this.xmax - this.xmin; |
|
8132 </xsl:text> |
|
8133 <xsl:text> |
|
8134 </xsl:text> |
|
8135 <xsl:text> if(!this.fixed_y_range){ |
|
8136 </xsl:text> |
|
8137 <xsl:text> ymin_damaged = overflow <= this.ymin; |
|
8138 </xsl:text> |
|
8139 <xsl:text> ymax_damaged = overflow >= this.ymax; |
|
8140 </xsl:text> |
|
8141 <xsl:text> if(value > this.ymax){ |
|
8142 </xsl:text> |
|
8143 <xsl:text> ymax_damaged = false; |
|
8144 </xsl:text> |
|
8145 <xsl:text> this.ymax = value; |
|
8146 </xsl:text> |
|
8147 <xsl:text> } |
|
8148 </xsl:text> |
|
8149 <xsl:text> if(value < this.ymin){ |
|
8150 </xsl:text> |
|
8151 <xsl:text> ymin_damaged = false; |
|
8152 </xsl:text> |
|
8153 <xsl:text> this.ymin = value; |
|
8154 </xsl:text> |
|
8155 <xsl:text> } |
|
8156 </xsl:text> |
|
8157 <xsl:text> } |
|
8158 </xsl:text> |
|
8159 <xsl:text> let Yrange = this.ymax - this.ymin; |
|
8160 </xsl:text> |
|
8161 <xsl:text> |
|
8162 </xsl:text> |
|
8163 <xsl:text> // apply margin by moving min and max to enlarge range |
|
8164 </xsl:text> |
|
8165 <xsl:text> let [xMargin,yMargin] = zip(this.Margins, [Xrange, Yrange]).map(([m,l]) => m*l); |
|
8166 </xsl:text> |
|
8167 <xsl:text> [[this.dxmin, this.dxmax],[this.dymin,this.dymax]] = |
|
8168 </xsl:text> |
|
8169 <xsl:text> [[this.xmin-xMargin, this.xmax+xMargin], |
|
8170 </xsl:text> |
|
8171 <xsl:text> [this.ymin-yMargin, this.ymax+yMargin]]; |
|
8172 </xsl:text> |
|
8173 <xsl:text> Xrange += 2*xMargin; |
|
8174 </xsl:text> |
|
8175 <xsl:text> Yrange += 2*yMargin; |
|
8176 </xsl:text> |
|
8177 <xsl:text> |
|
8178 </xsl:text> |
|
8179 <xsl:text> // recompute curves "d" attribute |
|
8180 </xsl:text> |
|
8181 <xsl:text> // FIXME: use SVG getPathData and setPathData when available. |
|
8182 </xsl:text> |
|
8183 <xsl:text> // https://svgwg.org/specs/paths/#InterfaceSVGPathData |
|
8184 </xsl:text> |
|
8185 <xsl:text> // https://github.com/jarek-foksa/path-data-polyfill |
|
8186 </xsl:text> |
|
8187 <xsl:text> |
|
8188 </xsl:text> |
|
8189 <xsl:text> let [base_point, xvect, yvect] = this.reference.getBaseRef(); |
|
8190 </xsl:text> |
|
8191 <xsl:text> this.curves_d_attr = |
|
8192 </xsl:text> |
|
8193 <xsl:text> zip(this.curves_data, this.curves).map(([data,curve]) => { |
|
8194 </xsl:text> |
|
8195 <xsl:text> let new_d = data.map(([x,y], i) => { |
|
8196 </xsl:text> |
|
8197 <xsl:text> // compute curve point from data, ranges, and base_ref |
|
8198 </xsl:text> |
|
8199 <xsl:text> let xv = vectorscale(xvect, (x - this.dxmin) / Xrange); |
|
8200 </xsl:text> |
|
8201 <xsl:text> let yv = vectorscale(yvect, (y - this.dymin) / Yrange); |
|
8202 </xsl:text> |
|
8203 <xsl:text> let px = base_point.x + xv.x + yv.x; |
|
8204 </xsl:text> |
|
8205 <xsl:text> let py = base_point.y + xv.y + yv.y; |
|
8206 </xsl:text> |
|
8207 <xsl:text> if(!this.fixed_y_range){ |
|
8208 </xsl:text> |
|
8209 <xsl:text> // update min and max from curve data if needed |
|
8210 </xsl:text> |
|
8211 <xsl:text> if(ymin_damaged && y < this.ymin) this.ymin = y; |
|
8212 </xsl:text> |
|
8213 <xsl:text> if(ymax_damaged && y > this.ymax) this.ymax = y; |
|
8214 </xsl:text> |
|
8215 <xsl:text> } |
|
8216 </xsl:text> |
|
8217 <xsl:text> |
|
8218 </xsl:text> |
|
8219 <xsl:text> return " " + px + "," + py; |
|
8220 </xsl:text> |
|
8221 <xsl:text> }); |
|
8222 </xsl:text> |
|
8223 <xsl:text> |
|
8224 </xsl:text> |
|
8225 <xsl:text> new_d.unshift("M "); |
|
8226 </xsl:text> |
|
8227 <xsl:text> |
|
8228 </xsl:text> |
|
8229 <xsl:text> return new_d.join(''); |
|
8230 </xsl:text> |
|
8231 <xsl:text> }); |
|
8232 </xsl:text> |
|
8233 <xsl:text> |
|
8234 </xsl:text> |
|
8235 <xsl:text> // computed curves "d" attr is applied to svg curve during animate(); |
|
8236 </xsl:text> |
|
8237 <xsl:text> |
|
8238 </xsl:text> |
|
8239 <xsl:text> this.request_animate(); |
|
8240 </xsl:text> |
|
8241 <xsl:text> } |
|
8242 </xsl:text> |
|
8243 <xsl:text> |
|
8244 </xsl:text> |
|
8245 <xsl:text> animate(){ |
|
8246 </xsl:text> |
|
8247 <xsl:text> |
|
8248 </xsl:text> |
|
8249 <xsl:text> // move elements only if enough data |
|
8250 </xsl:text> |
|
8251 <xsl:text> if(this.curves_data.some(data => data.length > 1)){ |
|
8252 </xsl:text> |
|
8253 <xsl:text> |
|
8254 </xsl:text> |
|
8255 <xsl:text> // move marks and update labels |
|
8256 </xsl:text> |
|
8257 <xsl:text> this.reference.applyRanges([[this.dxmin, this.dxmax], |
|
8258 </xsl:text> |
|
8259 <xsl:text> [this.dymin, this.dymax]]); |
|
8260 </xsl:text> |
|
8261 <xsl:text> |
|
8262 </xsl:text> |
|
8263 <xsl:text> // apply computed curves "d" attributes |
|
8264 </xsl:text> |
|
8265 <xsl:text> for(let [curve, d_attr] of zip(this.curves, this.curves_d_attr)){ |
|
8266 </xsl:text> |
|
8267 <xsl:text> curve.setAttribute("d", d_attr); |
|
8268 </xsl:text> |
|
8269 <xsl:text> } |
|
8270 </xsl:text> |
|
8271 <xsl:text> } |
|
8272 </xsl:text> |
|
8273 <xsl:text> } |
|
8274 </xsl:text> |
|
8275 <xsl:text> |
|
8276 </xsl:text> |
|
8277 <xsl:text>} |
|
8278 </xsl:text> |
|
8279 </xsl:template> |
|
8280 <xsl:template match="widget[@type='XYGraph']" mode="widget_defs"> |
|
8281 <xsl:param name="hmi_element"/> |
|
8282 <xsl:call-template name="defs_by_labels"> |
|
8283 <xsl:with-param name="hmi_element" select="$hmi_element"/> |
|
8284 <xsl:with-param name="labels"> |
|
8285 <xsl:text>/x_interval_minor_mark /x_axis_line /x_interval_major_mark /x_axis_label</xsl:text> |
|
8286 </xsl:with-param> |
|
8287 </xsl:call-template> |
|
8288 <xsl:call-template name="defs_by_labels"> |
|
8289 <xsl:with-param name="hmi_element" select="$hmi_element"/> |
|
8290 <xsl:with-param name="labels"> |
|
8291 <xsl:text>/y_interval_minor_mark /y_axis_line /y_interval_major_mark /y_axis_label</xsl:text> |
|
8292 </xsl:with-param> |
|
8293 </xsl:call-template> |
|
8294 <xsl:text> init_specific() { |
|
8295 </xsl:text> |
|
8296 <xsl:for-each select="$hmi_element/*[regexp:test(@inkscape:label,'^curve_[0-9]+$')]"> |
|
8297 <xsl:variable name="label" select="@inkscape:label"/> |
|
8298 <xsl:variable name="id" select="@id"/> |
|
8299 <xsl:if test="$hmi_element/*[not($id = @id) and @inkscape:label=$label]"> |
|
8300 <xsl:message terminate="yes"> |
|
8301 <xsl:text>XYGraph id="</xsl:text> |
|
8302 <xsl:value-of select="$id"/> |
|
8303 <xsl:text>", label="</xsl:text> |
|
8304 <xsl:value-of select="$label"/> |
|
8305 <xsl:text>" : elements with data_n label must be unique.</xsl:text> |
|
8306 </xsl:message> |
|
8307 </xsl:if> |
|
8308 <xsl:text> this.curves[</xsl:text> |
|
8309 <xsl:value-of select="substring(@inkscape:label, 7)"/> |
|
8310 <xsl:text>] = id("</xsl:text> |
|
8311 <xsl:value-of select="@id"/> |
|
8312 <xsl:text>"); /* </xsl:text> |
|
8313 <xsl:value-of select="@inkscape:label"/> |
|
8314 <xsl:text> */ |
|
8315 </xsl:text> |
|
8316 </xsl:for-each> |
|
8317 <xsl:text> } |
|
8318 </xsl:text> |
|
8319 </xsl:template> |
|
8320 <declarations:XYGraph/> |
|
8321 <xsl:template match="declarations:XYGraph"> |
|
8322 <xsl:text> |
|
8323 </xsl:text> |
|
8324 <xsl:text>/* </xsl:text> |
|
8325 <xsl:value-of select="local-name()"/> |
|
8326 <xsl:text> */ |
|
8327 </xsl:text> |
|
8328 <xsl:text> |
|
8329 </xsl:text> |
|
8330 <xsl:text>function lineFromPath(path_elt) { |
|
8331 </xsl:text> |
|
8332 <xsl:text> let start = path_elt.getPointAtLength(0); |
|
8333 </xsl:text> |
|
8334 <xsl:text> let end = path_elt.getPointAtLength(path_elt.getTotalLength()); |
|
8335 </xsl:text> |
|
8336 <xsl:text> return [start, new DOMPoint(end.x - start.x , end.y - start.y)]; |
|
8337 </xsl:text> |
|
8338 <xsl:text>}; |
|
8339 </xsl:text> |
|
8340 <xsl:text> |
|
8341 </xsl:text> |
|
8342 <xsl:text>function vector(p1, p2) { |
|
8343 </xsl:text> |
|
8344 <xsl:text> return new DOMPoint(p2.x - p1.x , p2.y - p1.y); |
|
8345 </xsl:text> |
|
8346 <xsl:text>}; |
|
8347 </xsl:text> |
|
8348 <xsl:text> |
|
8349 </xsl:text> |
|
8350 <xsl:text>function vectorscale(p1, p2) { |
|
8351 </xsl:text> |
|
8352 <xsl:text> return new DOMPoint(p2 * p1.x , p2 * p1.y); |
|
8353 </xsl:text> |
|
8354 <xsl:text>}; |
|
8355 </xsl:text> |
|
8356 <xsl:text> |
|
8357 </xsl:text> |
|
8358 <xsl:text>function vectorLength(p1) { |
|
8359 </xsl:text> |
|
8360 <xsl:text> return Math.sqrt(p1.x*p1.x + p1.y*p1.y); |
|
8361 </xsl:text> |
|
8362 <xsl:text>}; |
|
8363 </xsl:text> |
|
8364 <xsl:text> |
|
8365 </xsl:text> |
|
8366 <xsl:text>function randomId(){ |
|
8367 </xsl:text> |
|
8368 <xsl:text> return Date.now().toString(36) + Math.random().toString(36).substr(2); |
|
8369 </xsl:text> |
|
8370 <xsl:text>} |
|
8371 </xsl:text> |
|
8372 <xsl:text> |
|
8373 </xsl:text> |
|
8374 <xsl:text>function move_elements_to_group(elements) { |
|
8375 </xsl:text> |
|
8376 <xsl:text> let newgroup = document.createElementNS(xmlns,"g"); |
|
8377 </xsl:text> |
|
8378 <xsl:text> newgroup.id = randomId(); |
|
8379 </xsl:text> |
|
8380 <xsl:text> |
|
8381 </xsl:text> |
|
8382 <xsl:text> for(let element of elements){ |
|
8383 </xsl:text> |
|
8384 <xsl:text> let parent = element.parentElement; |
|
8385 </xsl:text> |
|
8386 <xsl:text> if(parent !== null) |
|
8387 </xsl:text> |
|
8388 <xsl:text> parent.removeChild(element); |
|
8389 </xsl:text> |
|
8390 <xsl:text> newgroup.appendChild(element); |
|
8391 </xsl:text> |
|
8392 <xsl:text> } |
|
8393 </xsl:text> |
|
8394 <xsl:text> return newgroup; |
|
8395 </xsl:text> |
|
8396 <xsl:text>} |
|
8397 </xsl:text> |
|
8398 <xsl:text>function getLinesIntesection(l1, l2) { |
|
8399 </xsl:text> |
|
8400 <xsl:text> let [l1start, l1vect] = l1; |
|
8401 </xsl:text> |
|
8402 <xsl:text> let [l2start, l2vect] = l2; |
|
8403 </xsl:text> |
|
8404 <xsl:text> |
|
8405 </xsl:text> |
|
8406 <xsl:text> |
|
8407 </xsl:text> |
|
8408 <xsl:text> /* |
|
8409 </xsl:text> |
|
8410 <xsl:text> Compute intersection of two lines |
|
8411 </xsl:text> |
|
8412 <xsl:text> ================================= |
|
8413 </xsl:text> |
|
8414 <xsl:text> |
|
8415 </xsl:text> |
|
8416 <xsl:text> ^ l2vect |
|
8417 </xsl:text> |
|
8418 <xsl:text> / |
|
8419 </xsl:text> |
|
8420 <xsl:text> / |
|
8421 </xsl:text> |
|
8422 <xsl:text> / |
|
8423 </xsl:text> |
|
8424 <xsl:text> l1start ----------X--------------> l1vect |
|
8425 </xsl:text> |
|
8426 <xsl:text> / intersection |
|
8427 </xsl:text> |
|
8428 <xsl:text> / |
|
8429 </xsl:text> |
|
8430 <xsl:text> / |
|
8431 </xsl:text> |
|
8432 <xsl:text> l2start |
|
8433 </xsl:text> |
|
8434 <xsl:text> |
|
8435 </xsl:text> |
|
8436 <xsl:text> */ |
|
8437 </xsl:text> |
|
8438 <xsl:text> let [x1, y1, x3, y3] = [l1start.x, l1start.y, l2start.x, l2start.y]; |
|
8439 </xsl:text> |
|
8440 <xsl:text> let [x2, y2, x4, y4] = [x1+l1vect.x, y1+l1vect.y, x3+l2vect.x, y3+l2vect.y]; |
|
8441 </xsl:text> |
|
8442 <xsl:text> |
|
8443 </xsl:text> |
|
8444 <xsl:text> // line intercept math by Paul Bourke http://paulbourke.net/geometry/pointlineplane/ |
|
8445 </xsl:text> |
|
8446 <xsl:text> // Determine the intersection point of two line segments |
|
8447 </xsl:text> |
|
8448 <xsl:text> // Return FALSE if the lines don't intersect |
|
8449 </xsl:text> |
|
8450 <xsl:text> |
|
8451 </xsl:text> |
|
8452 <xsl:text> // Check if none of the lines are of length 0 |
|
8453 </xsl:text> |
|
8454 <xsl:text> if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) { |
|
8455 </xsl:text> |
|
8456 <xsl:text> return false |
|
8457 </xsl:text> |
|
8458 <xsl:text> } |
|
8459 </xsl:text> |
|
8460 <xsl:text> |
|
8461 </xsl:text> |
|
8462 <xsl:text> denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)) |
|
8463 </xsl:text> |
|
8464 <xsl:text> |
|
8465 </xsl:text> |
|
8466 <xsl:text> // Lines are parallel |
|
8467 </xsl:text> |
|
8468 <xsl:text> if (denominator === 0) { |
|
8469 </xsl:text> |
|
8470 <xsl:text> return false |
|
8471 </xsl:text> |
|
8472 <xsl:text> } |
|
8473 </xsl:text> |
|
8474 <xsl:text> |
|
8475 </xsl:text> |
|
8476 <xsl:text> let ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator |
|
8477 </xsl:text> |
|
8478 <xsl:text> let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator |
|
8479 </xsl:text> |
|
8480 <xsl:text> |
|
8481 </xsl:text> |
|
8482 <xsl:text> // Return a object with the x and y coordinates of the intersection |
|
8483 </xsl:text> |
|
8484 <xsl:text> let x = x1 + ua * (x2 - x1) |
|
8485 </xsl:text> |
|
8486 <xsl:text> let y = y1 + ua * (y2 - y1) |
|
8487 </xsl:text> |
|
8488 <xsl:text> |
|
8489 </xsl:text> |
|
8490 <xsl:text> return new DOMPoint(x,y); |
|
8491 </xsl:text> |
|
8492 <xsl:text>}; |
|
8493 </xsl:text> |
|
8494 <xsl:text> |
|
8495 </xsl:text> |
|
8496 <xsl:text>class ReferenceFrame { |
|
8497 </xsl:text> |
|
8498 <xsl:text> constructor( |
|
8499 </xsl:text> |
|
8500 <xsl:text> // [[Xminor,Xmajor], [Yminor,Ymajor]] |
|
8501 </xsl:text> |
|
8502 <xsl:text> marks, |
|
8503 </xsl:text> |
|
8504 <xsl:text> // [Xlabel, Ylabel] |
|
8505 </xsl:text> |
|
8506 <xsl:text> labels, |
|
8507 </xsl:text> |
|
8508 <xsl:text> // [Xline, Yline] |
|
8509 </xsl:text> |
|
8510 <xsl:text> lines, |
|
8511 </xsl:text> |
|
8512 <xsl:text> // [Xformat, Yformat] printf-like formating strings |
|
8513 </xsl:text> |
|
8514 <xsl:text> formats |
|
8515 </xsl:text> |
|
8516 <xsl:text> ){ |
|
8517 </xsl:text> |
|
8518 <xsl:text> this.axes = zip(labels,marks,lines,formats).map(args => new Axis(...args)); |
|
8519 </xsl:text> |
|
8520 <xsl:text> |
|
8521 </xsl:text> |
|
8522 <xsl:text> let [lx,ly] = this.axes.map(axis => axis.line); |
|
8523 </xsl:text> |
|
8524 <xsl:text> let [[xstart, xvect], [ystart, yvect]] = [lx,ly]; |
|
8525 </xsl:text> |
|
8526 <xsl:text> let base_point = this.getBasePoint(); |
|
8527 </xsl:text> |
|
8528 <xsl:text> |
|
8529 </xsl:text> |
|
8530 <xsl:text> // setup clipping for curves |
|
8531 </xsl:text> |
|
8532 <xsl:text> this.clipPathPathDattr = |
|
8533 </xsl:text> |
|
8534 <xsl:text> "m " + base_point.x + "," + base_point.y + " " |
|
8535 </xsl:text> |
|
8536 <xsl:text> + xvect.x + "," + xvect.y + " " |
|
8537 </xsl:text> |
|
8538 <xsl:text> + yvect.x + "," + yvect.y + " " |
|
8539 </xsl:text> |
|
8540 <xsl:text> + -xvect.x + "," + -xvect.y + " " |
|
8541 </xsl:text> |
|
8542 <xsl:text> + -yvect.x + "," + -yvect.y + " z"; |
|
8543 </xsl:text> |
|
8544 <xsl:text> |
|
8545 </xsl:text> |
|
8546 <xsl:text> this.base_ref = [base_point, xvect, yvect]; |
|
8547 </xsl:text> |
|
8548 <xsl:text> |
|
8549 </xsl:text> |
|
8550 <xsl:text> this.lengths = [xvect,yvect].map(v => vectorLength(v)); |
|
8551 </xsl:text> |
|
8552 <xsl:text> |
|
8553 </xsl:text> |
|
8554 <xsl:text> for(let axis of this.axes){ |
|
8555 </xsl:text> |
|
8556 <xsl:text> axis.setBasePoint(base_point); |
|
8557 </xsl:text> |
|
8558 <xsl:text> } |
|
8559 </xsl:text> |
|
8560 <xsl:text> } |
|
8561 </xsl:text> |
|
8562 <xsl:text> |
|
8563 </xsl:text> |
|
8564 <xsl:text> getLengths(){ |
|
8565 </xsl:text> |
|
8566 <xsl:text> return this.lengths; |
|
8567 </xsl:text> |
|
8568 <xsl:text> } |
|
8569 </xsl:text> |
|
8570 <xsl:text> |
|
8571 </xsl:text> |
|
8572 <xsl:text> getBaseRef(){ |
|
8573 </xsl:text> |
|
8574 <xsl:text> return this.base_ref; |
|
8575 </xsl:text> |
|
8576 <xsl:text> } |
|
8577 </xsl:text> |
|
8578 <xsl:text> |
|
8579 </xsl:text> |
|
8580 <xsl:text> getClipPathPathDattr(){ |
|
8581 </xsl:text> |
|
8582 <xsl:text> return this.clipPathPathDattr; |
|
8583 </xsl:text> |
|
8584 <xsl:text> } |
|
8585 </xsl:text> |
|
8586 <xsl:text> |
|
8587 </xsl:text> |
|
8588 <xsl:text> applyRanges(ranges){ |
|
8589 </xsl:text> |
|
8590 <xsl:text> let origin_moves = zip(ranges,this.axes).map(([range,axis]) => axis.applyRange(...range)); |
|
8591 </xsl:text> |
|
8592 <xsl:text> zip(origin_moves.reverse(),this.axes).forEach(([vect,axis]) => axis.moveOrigin(vect)); |
|
8593 </xsl:text> |
|
8594 <xsl:text> } |
|
8595 </xsl:text> |
|
8596 <xsl:text> |
|
8597 </xsl:text> |
|
8598 <xsl:text> getBasePoint() { |
|
8599 </xsl:text> |
|
8600 <xsl:text> let [[xstart, xvect], [ystart, yvect]] = this.axes.map(axis => axis.line); |
|
8601 </xsl:text> |
|
8602 <xsl:text> |
|
8603 </xsl:text> |
|
8604 <xsl:text> /* |
|
8605 </xsl:text> |
|
8606 <xsl:text> Compute graph clipping region base point |
|
8607 </xsl:text> |
|
8608 <xsl:text> ======================================== |
|
8609 </xsl:text> |
|
8610 <xsl:text> |
|
8611 </xsl:text> |
|
8612 <xsl:text> Clipping region is a parallelogram containing axes lines, |
|
8613 </xsl:text> |
|
8614 <xsl:text> and whose sides are parallel to axes line respectively. |
|
8615 </xsl:text> |
|
8616 <xsl:text> Given axes lines are not starting at the same point, hereafter is |
|
8617 </xsl:text> |
|
8618 <xsl:text> calculus of parallelogram base point. |
|
8619 </xsl:text> |
|
8620 <xsl:text> |
|
8621 </xsl:text> |
|
8622 <xsl:text> ^ given Y axis (yvect) |
|
8623 </xsl:text> |
|
8624 <xsl:text> / / |
|
8625 </xsl:text> |
|
8626 <xsl:text> / / |
|
8627 </xsl:text> |
|
8628 <xsl:text> / / |
|
8629 </xsl:text> |
|
8630 <xsl:text> xstart *---------*--------------> given X axis (xvect) |
|
8631 </xsl:text> |
|
8632 <xsl:text> / /origin |
|
8633 </xsl:text> |
|
8634 <xsl:text> / / |
|
8635 </xsl:text> |
|
8636 <xsl:text> *---------*-------------- |
|
8637 </xsl:text> |
|
8638 <xsl:text> base_point ystart |
|
8639 </xsl:text> |
|
8640 <xsl:text> |
|
8641 </xsl:text> |
|
8642 <xsl:text> */ |
|
8643 </xsl:text> |
|
8644 <xsl:text> |
|
8645 </xsl:text> |
|
8646 <xsl:text> let base_point = getLinesIntesection([xstart,yvect],[ystart,xvect]); |
|
8647 </xsl:text> |
|
8648 <xsl:text> |
|
8649 </xsl:text> |
|
8650 <xsl:text> return base_point; |
|
8651 </xsl:text> |
|
8652 <xsl:text> |
|
8653 </xsl:text> |
|
8654 <xsl:text> } |
|
8655 </xsl:text> |
|
8656 <xsl:text> |
|
8657 </xsl:text> |
|
8658 <xsl:text>} |
|
8659 </xsl:text> |
|
8660 <xsl:text> |
|
8661 </xsl:text> |
|
8662 <xsl:text>class Axis { |
|
8663 </xsl:text> |
|
8664 <xsl:text> constructor(label, marks, line, format){ |
|
8665 </xsl:text> |
|
8666 <xsl:text> this.lineElement = line; |
|
8667 </xsl:text> |
|
8668 <xsl:text> this.line = lineFromPath(line); |
|
8669 </xsl:text> |
|
8670 <xsl:text> this.format = format; |
|
8671 </xsl:text> |
|
8672 <xsl:text> |
|
8673 </xsl:text> |
|
8674 <xsl:text> this.label = label; |
|
8675 </xsl:text> |
|
8676 <xsl:text> this.marks = marks; |
|
8677 </xsl:text> |
|
8678 <xsl:text> |
|
8679 </xsl:text> |
|
8680 <xsl:text> |
|
8681 </xsl:text> |
|
8682 <xsl:text> // add transforms for elements sliding along the axis line |
|
8683 </xsl:text> |
|
8684 <xsl:text> for(let [elementname,element] of zip(["minor", "major", "label"],[...marks,label])){ |
|
8685 </xsl:text> |
|
8686 <xsl:text> for(let name of ["base","slide"]){ |
|
8687 </xsl:text> |
|
8688 <xsl:text> let transform = svg_root.createSVGTransform(); |
|
8689 </xsl:text> |
|
8690 <xsl:text> element.transform.baseVal.insertItemBefore(transform,0); |
|
8691 </xsl:text> |
|
8692 <xsl:text> this[elementname+"_"+name+"_transform"]=transform; |
|
8693 </xsl:text> |
|
8694 <xsl:text> }; |
|
8695 </xsl:text> |
|
8696 <xsl:text> }; |
|
8697 </xsl:text> |
|
8698 <xsl:text> |
|
8699 </xsl:text> |
|
8700 <xsl:text> // group marks an labels together |
|
8701 </xsl:text> |
|
8702 <xsl:text> let parent = line.parentElement; |
|
8703 </xsl:text> |
|
8704 <xsl:text> this.marks_group = move_elements_to_group(marks); |
|
8705 </xsl:text> |
|
8706 <xsl:text> this.marks_and_label_group = move_elements_to_group([this.marks_group, label]); |
|
8707 </xsl:text> |
|
8708 <xsl:text> this.group = move_elements_to_group([this.marks_and_label_group,line]); |
|
8709 </xsl:text> |
|
8710 <xsl:text> parent.appendChild(this.group); |
|
8711 </xsl:text> |
|
8712 <xsl:text> |
|
8713 </xsl:text> |
|
8714 <xsl:text> // Add transforms to group |
|
8715 </xsl:text> |
|
8716 <xsl:text> for(let name of ["base","origin"]){ |
|
8717 </xsl:text> |
|
8718 <xsl:text> let transform = svg_root.createSVGTransform(); |
|
8719 </xsl:text> |
|
8720 <xsl:text> this.group.transform.baseVal.appendItem(transform); |
|
8721 </xsl:text> |
|
8722 <xsl:text> this[name+"_transform"]=transform; |
|
8723 </xsl:text> |
|
8724 <xsl:text> }; |
|
8725 </xsl:text> |
|
8726 <xsl:text> |
|
8727 </xsl:text> |
|
8728 <xsl:text> this.marks_and_label_group_transform = svg_root.createSVGTransform(); |
|
8729 </xsl:text> |
|
8730 <xsl:text> this.marks_and_label_group.transform.baseVal.appendItem(this.marks_and_label_group_transform); |
|
8731 </xsl:text> |
|
8732 <xsl:text> |
|
8733 </xsl:text> |
|
8734 <xsl:text> this.duplicates = []; |
|
8735 </xsl:text> |
|
8736 <xsl:text> this.last_duplicate_index = 0; |
|
8737 </xsl:text> |
|
8738 <xsl:text> } |
|
8739 </xsl:text> |
|
8740 <xsl:text> |
|
8741 </xsl:text> |
|
8742 <xsl:text> setBasePoint(base_point){ |
|
8743 </xsl:text> |
|
8744 <xsl:text> // move Axis to base point |
|
8745 </xsl:text> |
|
8746 <xsl:text> let [start, _vect] = this.line; |
|
8747 </xsl:text> |
|
8748 <xsl:text> let v = vector(start, base_point); |
|
8749 </xsl:text> |
|
8750 <xsl:text> this.base_transform.setTranslate(v.x, v.y); |
|
8751 </xsl:text> |
|
8752 <xsl:text> |
|
8753 </xsl:text> |
|
8754 <xsl:text> // Move marks and label to base point. |
|
8755 </xsl:text> |
|
8756 <xsl:text> // _|_______ _|________ |
|
8757 </xsl:text> |
|
8758 <xsl:text> // | ' | ==> ' |
|
8759 </xsl:text> |
|
8760 <xsl:text> // | 0 0 |
|
8761 </xsl:text> |
|
8762 <xsl:text> // | | |
|
8763 </xsl:text> |
|
8764 <xsl:text> |
|
8765 </xsl:text> |
|
8766 <xsl:text> for(let [markname,mark] of zip(["minor", "major"],this.marks)){ |
|
8767 </xsl:text> |
|
8768 <xsl:text> let pos = vector( |
|
8769 </xsl:text> |
|
8770 <xsl:text> // Marks are expected to be paths |
|
8771 </xsl:text> |
|
8772 <xsl:text> // paths are expected to be lines |
|
8773 </xsl:text> |
|
8774 <xsl:text> // intersection with axis line is taken |
|
8775 </xsl:text> |
|
8776 <xsl:text> // as reference for mark position |
|
8777 </xsl:text> |
|
8778 <xsl:text> getLinesIntesection( |
|
8779 </xsl:text> |
|
8780 <xsl:text> this.line, lineFromPath(mark)),base_point); |
|
8781 </xsl:text> |
|
8782 <xsl:text> this[markname+"_base_transform"].setTranslate(pos.x - v.x, pos.y - v.y); |
|
8783 </xsl:text> |
|
8784 <xsl:text> if(markname == "major"){ // label follow major mark |
|
8785 </xsl:text> |
|
8786 <xsl:text> this.label_base_transform.setTranslate(pos.x - v.x, pos.y - v.y); |
|
8787 </xsl:text> |
|
8788 <xsl:text> } |
|
8789 </xsl:text> |
|
8790 <xsl:text> } |
|
8791 </xsl:text> |
|
8792 <xsl:text> } |
|
8793 </xsl:text> |
|
8794 <xsl:text> |
|
8795 </xsl:text> |
|
8796 <xsl:text> moveOrigin(vect){ |
|
8797 </xsl:text> |
|
8798 <xsl:text> this.origin_transform.setTranslate(vect.x, vect.y); |
|
8799 </xsl:text> |
|
8800 <xsl:text> } |
|
8801 </xsl:text> |
|
8802 <xsl:text> |
|
8803 </xsl:text> |
|
8804 <xsl:text> applyRange(min, max){ |
|
8805 </xsl:text> |
|
8806 <xsl:text> let range = max - min; |
|
8807 </xsl:text> |
|
8808 <xsl:text> |
|
8809 </xsl:text> |
|
8810 <xsl:text> // compute how many units for a mark |
|
8811 </xsl:text> |
|
8812 <xsl:text> // |
|
8813 </xsl:text> |
|
8814 <xsl:text> // - Units are expected to be an order of magnitude smaller than range, |
|
8815 </xsl:text> |
|
8816 <xsl:text> // so that marks are not too dense and also not too sparse. |
|
8817 </xsl:text> |
|
8818 <xsl:text> // Order of magnitude of range is log10(range) |
|
8819 </xsl:text> |
|
8820 <xsl:text> // |
|
8821 </xsl:text> |
|
8822 <xsl:text> // - Units are necessarily power of ten, otherwise it is complicated to |
|
8823 </xsl:text> |
|
8824 <xsl:text> // fill the text in labels... |
|
8825 </xsl:text> |
|
8826 <xsl:text> // Unit is pow(10, integer_number ) |
|
8827 </xsl:text> |
|
8828 <xsl:text> // |
|
8829 </xsl:text> |
|
8830 <xsl:text> // - To transform order of magnitude to an integer, floor() is used. |
|
8831 </xsl:text> |
|
8832 <xsl:text> // This results in a count of mark fluctuating in between 10 and 100. |
|
8833 </xsl:text> |
|
8834 <xsl:text> // |
|
8835 </xsl:text> |
|
8836 <xsl:text> // - To spare resources result is better in between 3 and 30, |
|
8837 </xsl:text> |
|
8838 <xsl:text> // and log10(3) is substracted to order of magnitude to obtain this |
|
8839 </xsl:text> |
|
8840 <xsl:text> let unit = Math.pow(10, Math.floor(Math.log10(range)-Math.log10(3))); |
|
8841 </xsl:text> |
|
8842 <xsl:text> |
|
8843 </xsl:text> |
|
8844 <xsl:text> // TODO: for time values (ms), units may be : |
|
8845 </xsl:text> |
|
8846 <xsl:text> // 1 -> ms |
|
8847 </xsl:text> |
|
8848 <xsl:text> // 10 -> s/100 |
|
8849 </xsl:text> |
|
8850 <xsl:text> // 100 -> s/10 |
|
8851 </xsl:text> |
|
8852 <xsl:text> // 1000 -> s |
|
8853 </xsl:text> |
|
8854 <xsl:text> // 60000 -> min |
|
8855 </xsl:text> |
|
8856 <xsl:text> // 3600000 -> hour |
|
8857 </xsl:text> |
|
8858 <xsl:text> // ... |
|
8859 </xsl:text> |
|
8860 <xsl:text> // |
|
8861 </xsl:text> |
|
8862 <xsl:text> |
|
8863 </xsl:text> |
|
8864 <xsl:text> // Compute position of origin along axis [0...range] |
|
8865 </xsl:text> |
|
8866 <xsl:text> |
|
8867 </xsl:text> |
|
8868 <xsl:text> // min < 0, max > 0, offset = -min |
|
8869 </xsl:text> |
|
8870 <xsl:text> // _____________|________________ |
|
8871 </xsl:text> |
|
8872 <xsl:text> // ... -3 -2 -1 |0 1 2 3 4 ... |
|
8873 </xsl:text> |
|
8874 <xsl:text> // <--offset---> ^ |
|
8875 </xsl:text> |
|
8876 <xsl:text> // |_original |
|
8877 </xsl:text> |
|
8878 <xsl:text> |
|
8879 </xsl:text> |
|
8880 <xsl:text> // min > 0, max > 0, offset = 0 |
|
8881 </xsl:text> |
|
8882 <xsl:text> // |________________ |
|
8883 </xsl:text> |
|
8884 <xsl:text> // |6 7 8 9 10... |
|
8885 </xsl:text> |
|
8886 <xsl:text> // ^ |
|
8887 </xsl:text> |
|
8888 <xsl:text> // |_original |
|
8889 </xsl:text> |
|
8890 <xsl:text> |
|
8891 </xsl:text> |
|
8892 <xsl:text> // min < 0, max < 0, offset = max-min (range) |
|
8893 </xsl:text> |
|
8894 <xsl:text> // _____________|_ |
|
8895 </xsl:text> |
|
8896 <xsl:text> // ... -5 -4 -3 |-2 |
|
8897 </xsl:text> |
|
8898 <xsl:text> // <--offset---> ^ |
|
8899 </xsl:text> |
|
8900 <xsl:text> // |_original |
|
8901 </xsl:text> |
|
8902 <xsl:text> |
|
8903 </xsl:text> |
|
8904 <xsl:text> let offset = (max>=0 && min>=0) ? 0 : ( |
|
8905 </xsl:text> |
|
8906 <xsl:text> (max<0 && min<0) ? range : -min); |
|
8907 </xsl:text> |
|
8908 <xsl:text> |
|
8909 </xsl:text> |
|
8910 <xsl:text> // compute unit vector |
|
8911 </xsl:text> |
|
8912 <xsl:text> let [_start, vect] = this.line; |
|
8913 </xsl:text> |
|
8914 <xsl:text> let unit_vect = vectorscale(vect, 1/range); |
|
8915 </xsl:text> |
|
8916 <xsl:text> let [mark_min, mark_max, mark_offset] = [min,max,offset].map(val => Math.round(val/unit)); |
|
8917 </xsl:text> |
|
8918 <xsl:text> let mark_count = mark_max-mark_min; |
|
8919 </xsl:text> |
|
8920 <xsl:text> |
|
8921 </xsl:text> |
|
8922 <xsl:text> // apply unit vector to marks and label |
|
8923 </xsl:text> |
|
8924 <xsl:text> // offset is a representing position of an |
|
8925 </xsl:text> |
|
8926 <xsl:text> // axis along the opposit axis line, expressed in major marks units |
|
8927 </xsl:text> |
|
8928 <xsl:text> // unit_vect is unit vector |
|
8929 </xsl:text> |
|
8930 <xsl:text> |
|
8931 </xsl:text> |
|
8932 <xsl:text> // ^ |
|
8933 </xsl:text> |
|
8934 <xsl:text> // | unit_vect |
|
8935 </xsl:text> |
|
8936 <xsl:text> // |<---> |
|
8937 </xsl:text> |
|
8938 <xsl:text> // _________|__________> |
|
8939 </xsl:text> |
|
8940 <xsl:text> // ^ | ' | ' | ' |
|
8941 </xsl:text> |
|
8942 <xsl:text> // |yoffset | 1 |
|
8943 </xsl:text> |
|
8944 <xsl:text> // | | |
|
8945 </xsl:text> |
|
8946 <xsl:text> // v xoffset| |
|
8947 </xsl:text> |
|
8948 <xsl:text> // X<------>| |
|
8949 </xsl:text> |
|
8950 <xsl:text> // base_point |
|
8951 </xsl:text> |
|
8952 <xsl:text> |
|
8953 </xsl:text> |
|
8954 <xsl:text> // move major marks and label to first positive mark position |
|
8955 </xsl:text> |
|
8956 <xsl:text> // let v = vectorscale(unit_vect, unit); |
|
8957 </xsl:text> |
|
8958 <xsl:text> // this.label_slide_transform.setTranslate(v.x, v.y); |
|
8959 </xsl:text> |
|
8960 <xsl:text> // this.major_slide_transform.setTranslate(v.x, v.y); |
|
8961 </xsl:text> |
|
8962 <xsl:text> // move minor mark to first half positive mark position |
|
8963 </xsl:text> |
|
8964 <xsl:text> let v = vectorscale(unit_vect, unit/2); |
|
8965 </xsl:text> |
|
8966 <xsl:text> this.minor_slide_transform.setTranslate(v.x, v.y); |
|
8967 </xsl:text> |
|
8968 <xsl:text> |
|
8969 </xsl:text> |
|
8970 <xsl:text> // duplicate marks and labels as needed |
|
8971 </xsl:text> |
|
8972 <xsl:text> let current_mark_count = this.duplicates.length; |
|
8973 </xsl:text> |
|
8974 <xsl:text> for(let i = current_mark_count; i <= mark_count; i++){ |
|
8975 </xsl:text> |
|
8976 <xsl:text> // cloneNode() label and add a svg:use of marks in a new group |
|
8977 </xsl:text> |
|
8978 <xsl:text> let newgroup = document.createElementNS(xmlns,"g"); |
|
8979 </xsl:text> |
|
8980 <xsl:text> let transform = svg_root.createSVGTransform(); |
|
8981 </xsl:text> |
|
8982 <xsl:text> let newlabel = this.label.cloneNode(true); |
|
8983 </xsl:text> |
|
8984 <xsl:text> let newuse = document.createElementNS(xmlns,"use"); |
|
8985 </xsl:text> |
|
8986 <xsl:text> let newuseAttr = document.createAttribute("href"); |
|
8987 </xsl:text> |
|
8988 <xsl:text> newuseAttr.value = "#"+this.marks_group.id; |
|
8989 </xsl:text> |
|
8990 <xsl:text> newuse.setAttributeNode(newuseAttr); |
|
8991 </xsl:text> |
|
8992 <xsl:text> newgroup.transform.baseVal.appendItem(transform); |
|
8993 </xsl:text> |
|
8994 <xsl:text> newgroup.appendChild(newlabel); |
|
8995 </xsl:text> |
|
8996 <xsl:text> newgroup.appendChild(newuse); |
|
8997 </xsl:text> |
|
8998 <xsl:text> this.duplicates.push([transform,newgroup]); |
|
8999 </xsl:text> |
|
9000 <xsl:text> } |
|
9001 </xsl:text> |
|
9002 <xsl:text> |
|
9003 </xsl:text> |
|
9004 <xsl:text> // move marks and labels, set labels |
|
9005 </xsl:text> |
|
9006 <xsl:text> // |
|
9007 </xsl:text> |
|
9008 <xsl:text> // min > 0, max > 0, offset = 0 |
|
9009 </xsl:text> |
|
9010 <xsl:text> // ^ |
|
9011 </xsl:text> |
|
9012 <xsl:text> // |________> |
|
9013 </xsl:text> |
|
9014 <xsl:text> // '| | ' | |
|
9015 </xsl:text> |
|
9016 <xsl:text> // | 6 7 |
|
9017 </xsl:text> |
|
9018 <xsl:text> // X |
|
9019 </xsl:text> |
|
9020 <xsl:text> // base_point |
|
9021 </xsl:text> |
|
9022 <xsl:text> // |
|
9023 </xsl:text> |
|
9024 <xsl:text> // min < 0, max > 0, offset = -min |
|
9025 </xsl:text> |
|
9026 <xsl:text> // ^ |
|
9027 </xsl:text> |
|
9028 <xsl:text> // _________|__________> |
|
9029 </xsl:text> |
|
9030 <xsl:text> // ' | ' | ' | ' |
|
9031 </xsl:text> |
|
9032 <xsl:text> // -1 | 1 |
|
9033 </xsl:text> |
|
9034 <xsl:text> // offset | |
|
9035 </xsl:text> |
|
9036 <xsl:text> // X<------>| |
|
9037 </xsl:text> |
|
9038 <xsl:text> // base_point |
|
9039 </xsl:text> |
|
9040 <xsl:text> // |
|
9041 </xsl:text> |
|
9042 <xsl:text> // min < 0, max < 0, offset = range |
|
9043 </xsl:text> |
|
9044 <xsl:text> // ^ |
|
9045 </xsl:text> |
|
9046 <xsl:text> // ____________| |
|
9047 </xsl:text> |
|
9048 <xsl:text> // ' | ' | |' |
|
9049 </xsl:text> |
|
9050 <xsl:text> // -5 -4 | |
|
9051 </xsl:text> |
|
9052 <xsl:text> // offset | |
|
9053 </xsl:text> |
|
9054 <xsl:text> // X<--------->| |
|
9055 </xsl:text> |
|
9056 <xsl:text> // base_point |
|
9057 </xsl:text> |
|
9058 <xsl:text> |
|
9059 </xsl:text> |
|
9060 <xsl:text> let duplicate_index = 0; |
|
9061 </xsl:text> |
|
9062 <xsl:text> for(let mark_index = 0; mark_index <= mark_count; mark_index++){ |
|
9063 </xsl:text> |
|
9064 <xsl:text> let val = (mark_min + mark_index) * unit; |
|
9065 </xsl:text> |
|
9066 <xsl:text> let vec = vectorscale(unit_vect, val - min); |
|
9067 </xsl:text> |
|
9068 <xsl:text> let text = this.format ? sprintf(this.format, val) : val.toString(); |
|
9069 </xsl:text> |
|
9070 <xsl:text> if(mark_index == mark_offset){ |
|
9071 </xsl:text> |
|
9072 <xsl:text> // apply offset to original marks and label groups |
|
9073 </xsl:text> |
|
9074 <xsl:text> this.marks_and_label_group_transform.setTranslate(vec.x, vec.y); |
|
9075 </xsl:text> |
|
9076 <xsl:text> |
|
9077 </xsl:text> |
|
9078 <xsl:text> // update original label text |
|
9079 </xsl:text> |
|
9080 <xsl:text> this.label.getElementsByTagName("tspan")[0].textContent = text; |
|
9081 </xsl:text> |
|
9082 <xsl:text> } else { |
|
9083 </xsl:text> |
|
9084 <xsl:text> let [transform,element] = this.duplicates[duplicate_index++]; |
|
9085 </xsl:text> |
|
9086 <xsl:text> |
|
9087 </xsl:text> |
|
9088 <xsl:text> // apply unit vector*N to marks and label groups |
|
9089 </xsl:text> |
|
9090 <xsl:text> transform.setTranslate(vec.x, vec.y); |
|
9091 </xsl:text> |
|
9092 <xsl:text> |
|
9093 </xsl:text> |
|
9094 <xsl:text> // update label text |
|
9095 </xsl:text> |
|
9096 <xsl:text> element.getElementsByTagName("tspan")[0].textContent = text; |
|
9097 </xsl:text> |
|
9098 <xsl:text> |
|
9099 </xsl:text> |
|
9100 <xsl:text> // Attach to group if not already |
|
9101 </xsl:text> |
|
9102 <xsl:text> if(element.parentElement == null){ |
|
9103 </xsl:text> |
|
9104 <xsl:text> this.group.appendChild(element); |
|
9105 </xsl:text> |
|
9106 <xsl:text> } |
|
9107 </xsl:text> |
|
9108 <xsl:text> } |
|
9109 </xsl:text> |
|
9110 <xsl:text> } |
|
9111 </xsl:text> |
|
9112 <xsl:text> |
|
9113 </xsl:text> |
|
9114 <xsl:text> let save_duplicate_index = duplicate_index; |
|
9115 </xsl:text> |
|
9116 <xsl:text> // dettach marks and label from group if not anymore visible |
|
9117 </xsl:text> |
|
9118 <xsl:text> for(;duplicate_index < this.last_duplicate_index; duplicate_index++){ |
|
9119 </xsl:text> |
|
9120 <xsl:text> let [transform,element] = this.duplicates[duplicate_index]; |
|
9121 </xsl:text> |
|
9122 <xsl:text> this.group.removeChild(element); |
|
9123 </xsl:text> |
|
9124 <xsl:text> } |
|
9125 </xsl:text> |
|
9126 <xsl:text> |
|
9127 </xsl:text> |
|
9128 <xsl:text> this.last_duplicate_index = save_duplicate_index; |
|
9129 </xsl:text> |
|
9130 <xsl:text> |
|
9131 </xsl:text> |
|
9132 <xsl:text> return vectorscale(unit_vect, offset); |
|
9133 </xsl:text> |
|
9134 <xsl:text> } |
|
9135 </xsl:text> |
|
9136 <xsl:text>} |
|
9137 </xsl:text> |
|
9138 <xsl:text> |
|
9139 </xsl:text> |
|
9140 </xsl:template> |
7675 <xsl:template match="/"> |
9141 <xsl:template match="/"> |
7676 <xsl:comment> |
9142 <xsl:comment> |
7677 <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text> |
9143 <xsl:text>Made with SVGHMI. https://beremiz.org</xsl:text> |
7678 </xsl:comment> |
9144 </xsl:comment> |
7679 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |
9145 <html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> |
8179 </xsl:text> |
9747 </xsl:text> |
8180 <xsl:text> /* eslint-enable quote-props */ |
9748 <xsl:text> /* eslint-enable quote-props */ |
8181 </xsl:text> |
9749 </xsl:text> |
8182 <xsl:text>}(); // eslint-disable-line |
9750 <xsl:text>}(); // eslint-disable-line |
8183 </xsl:text> |
9751 </xsl:text> |
|
9752 <xsl:text>/* |
|
9753 </xsl:text> |
|
9754 <xsl:text> |
|
9755 </xsl:text> |
|
9756 <xsl:text>From https://github.com/keyvan-m-sadeghi/pythonic |
|
9757 </xsl:text> |
|
9758 <xsl:text> |
|
9759 </xsl:text> |
|
9760 <xsl:text>Slightly modified in order to be usable in browser (i.e. not as a node.js module) |
|
9761 </xsl:text> |
|
9762 <xsl:text> |
|
9763 </xsl:text> |
|
9764 <xsl:text>The MIT License (MIT) |
|
9765 </xsl:text> |
|
9766 <xsl:text> |
|
9767 </xsl:text> |
|
9768 <xsl:text>Copyright (c) 2016 Assister.Ai |
|
9769 </xsl:text> |
|
9770 <xsl:text> |
|
9771 </xsl:text> |
|
9772 <xsl:text>Permission is hereby granted, free of charge, to any person obtaining a copy of |
|
9773 </xsl:text> |
|
9774 <xsl:text>this software and associated documentation files (the "Software"), to deal in |
|
9775 </xsl:text> |
|
9776 <xsl:text>the Software without restriction, including without limitation the rights to |
|
9777 </xsl:text> |
|
9778 <xsl:text>use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of |
|
9779 </xsl:text> |
|
9780 <xsl:text>the Software, and to permit persons to whom the Software is furnished to do so, |
|
9781 </xsl:text> |
|
9782 <xsl:text>subject to the following conditions: |
|
9783 </xsl:text> |
|
9784 <xsl:text> |
|
9785 </xsl:text> |
|
9786 <xsl:text>The above copyright notice and this permission notice shall be included in all |
|
9787 </xsl:text> |
|
9788 <xsl:text>copies or substantial portions of the Software. |
|
9789 </xsl:text> |
|
9790 <xsl:text> |
|
9791 </xsl:text> |
|
9792 <xsl:text>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
9793 </xsl:text> |
|
9794 <xsl:text>IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|
9795 </xsl:text> |
|
9796 <xsl:text>FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
9797 </xsl:text> |
|
9798 <xsl:text>COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|
9799 </xsl:text> |
|
9800 <xsl:text>IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
9801 </xsl:text> |
|
9802 <xsl:text>CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
9803 </xsl:text> |
|
9804 <xsl:text>*/ |
|
9805 </xsl:text> |
|
9806 <xsl:text> |
|
9807 </xsl:text> |
|
9808 <xsl:text>class Iterator { |
|
9809 </xsl:text> |
|
9810 <xsl:text> constructor(generator) { |
|
9811 </xsl:text> |
|
9812 <xsl:text> this[Symbol.iterator] = generator; |
|
9813 </xsl:text> |
|
9814 <xsl:text> } |
|
9815 </xsl:text> |
|
9816 <xsl:text> |
|
9817 </xsl:text> |
|
9818 <xsl:text> async * [Symbol.asyncIterator]() { |
|
9819 </xsl:text> |
|
9820 <xsl:text> for (const element of this) { |
|
9821 </xsl:text> |
|
9822 <xsl:text> yield await element; |
|
9823 </xsl:text> |
|
9824 <xsl:text> } |
|
9825 </xsl:text> |
|
9826 <xsl:text> } |
|
9827 </xsl:text> |
|
9828 <xsl:text> |
|
9829 </xsl:text> |
|
9830 <xsl:text> forEach(callback) { |
|
9831 </xsl:text> |
|
9832 <xsl:text> for (const element of this) { |
|
9833 </xsl:text> |
|
9834 <xsl:text> callback(element); |
|
9835 </xsl:text> |
|
9836 <xsl:text> } |
|
9837 </xsl:text> |
|
9838 <xsl:text> } |
|
9839 </xsl:text> |
|
9840 <xsl:text> |
|
9841 </xsl:text> |
|
9842 <xsl:text> map(callback) { |
|
9843 </xsl:text> |
|
9844 <xsl:text> const result = []; |
|
9845 </xsl:text> |
|
9846 <xsl:text> for (const element of this) { |
|
9847 </xsl:text> |
|
9848 <xsl:text> result.push(callback(element)); |
|
9849 </xsl:text> |
|
9850 <xsl:text> } |
|
9851 </xsl:text> |
|
9852 <xsl:text> |
|
9853 </xsl:text> |
|
9854 <xsl:text> return result; |
|
9855 </xsl:text> |
|
9856 <xsl:text> } |
|
9857 </xsl:text> |
|
9858 <xsl:text> |
|
9859 </xsl:text> |
|
9860 <xsl:text> filter(callback) { |
|
9861 </xsl:text> |
|
9862 <xsl:text> const result = []; |
|
9863 </xsl:text> |
|
9864 <xsl:text> for (const element of this) { |
|
9865 </xsl:text> |
|
9866 <xsl:text> if (callback(element)) { |
|
9867 </xsl:text> |
|
9868 <xsl:text> result.push(element); |
|
9869 </xsl:text> |
|
9870 <xsl:text> } |
|
9871 </xsl:text> |
|
9872 <xsl:text> } |
|
9873 </xsl:text> |
|
9874 <xsl:text> |
|
9875 </xsl:text> |
|
9876 <xsl:text> return result; |
|
9877 </xsl:text> |
|
9878 <xsl:text> } |
|
9879 </xsl:text> |
|
9880 <xsl:text> |
|
9881 </xsl:text> |
|
9882 <xsl:text> reduce(callback, initialValue) { |
|
9883 </xsl:text> |
|
9884 <xsl:text> let empty = typeof initialValue === 'undefined'; |
|
9885 </xsl:text> |
|
9886 <xsl:text> let accumulator = initialValue; |
|
9887 </xsl:text> |
|
9888 <xsl:text> let index = 0; |
|
9889 </xsl:text> |
|
9890 <xsl:text> for (const currentValue of this) { |
|
9891 </xsl:text> |
|
9892 <xsl:text> if (empty) { |
|
9893 </xsl:text> |
|
9894 <xsl:text> accumulator = currentValue; |
|
9895 </xsl:text> |
|
9896 <xsl:text> empty = false; |
|
9897 </xsl:text> |
|
9898 <xsl:text> continue; |
|
9899 </xsl:text> |
|
9900 <xsl:text> } |
|
9901 </xsl:text> |
|
9902 <xsl:text> |
|
9903 </xsl:text> |
|
9904 <xsl:text> accumulator = callback(accumulator, currentValue, index, this); |
|
9905 </xsl:text> |
|
9906 <xsl:text> index++; |
|
9907 </xsl:text> |
|
9908 <xsl:text> } |
|
9909 </xsl:text> |
|
9910 <xsl:text> |
|
9911 </xsl:text> |
|
9912 <xsl:text> if (empty) { |
|
9913 </xsl:text> |
|
9914 <xsl:text> throw new TypeError('Reduce of empty Iterator with no initial value'); |
|
9915 </xsl:text> |
|
9916 <xsl:text> } |
|
9917 </xsl:text> |
|
9918 <xsl:text> |
|
9919 </xsl:text> |
|
9920 <xsl:text> return accumulator; |
|
9921 </xsl:text> |
|
9922 <xsl:text> } |
|
9923 </xsl:text> |
|
9924 <xsl:text> |
|
9925 </xsl:text> |
|
9926 <xsl:text> some(callback) { |
|
9927 </xsl:text> |
|
9928 <xsl:text> for (const element of this) { |
|
9929 </xsl:text> |
|
9930 <xsl:text> if (callback(element)) { |
|
9931 </xsl:text> |
|
9932 <xsl:text> return true; |
|
9933 </xsl:text> |
|
9934 <xsl:text> } |
|
9935 </xsl:text> |
|
9936 <xsl:text> } |
|
9937 </xsl:text> |
|
9938 <xsl:text> |
|
9939 </xsl:text> |
|
9940 <xsl:text> return false; |
|
9941 </xsl:text> |
|
9942 <xsl:text> } |
|
9943 </xsl:text> |
|
9944 <xsl:text> |
|
9945 </xsl:text> |
|
9946 <xsl:text> every(callback) { |
|
9947 </xsl:text> |
|
9948 <xsl:text> for (const element of this) { |
|
9949 </xsl:text> |
|
9950 <xsl:text> if (!callback(element)) { |
|
9951 </xsl:text> |
|
9952 <xsl:text> return false; |
|
9953 </xsl:text> |
|
9954 <xsl:text> } |
|
9955 </xsl:text> |
|
9956 <xsl:text> } |
|
9957 </xsl:text> |
|
9958 <xsl:text> |
|
9959 </xsl:text> |
|
9960 <xsl:text> return true; |
|
9961 </xsl:text> |
|
9962 <xsl:text> } |
|
9963 </xsl:text> |
|
9964 <xsl:text> |
|
9965 </xsl:text> |
|
9966 <xsl:text> static fromIterable(iterable) { |
|
9967 </xsl:text> |
|
9968 <xsl:text> return new Iterator(function * () { |
|
9969 </xsl:text> |
|
9970 <xsl:text> for (const element of iterable) { |
|
9971 </xsl:text> |
|
9972 <xsl:text> yield element; |
|
9973 </xsl:text> |
|
9974 <xsl:text> } |
|
9975 </xsl:text> |
|
9976 <xsl:text> }); |
|
9977 </xsl:text> |
|
9978 <xsl:text> } |
|
9979 </xsl:text> |
|
9980 <xsl:text> |
|
9981 </xsl:text> |
|
9982 <xsl:text> toArray() { |
|
9983 </xsl:text> |
|
9984 <xsl:text> return Array.from(this); |
|
9985 </xsl:text> |
|
9986 <xsl:text> } |
|
9987 </xsl:text> |
|
9988 <xsl:text> |
|
9989 </xsl:text> |
|
9990 <xsl:text> next() { |
|
9991 </xsl:text> |
|
9992 <xsl:text> if (!this.currentInvokedGenerator) { |
|
9993 </xsl:text> |
|
9994 <xsl:text> this.currentInvokedGenerator = this[Symbol.iterator](); |
|
9995 </xsl:text> |
|
9996 <xsl:text> } |
|
9997 </xsl:text> |
|
9998 <xsl:text> |
|
9999 </xsl:text> |
|
10000 <xsl:text> return this.currentInvokedGenerator.next(); |
|
10001 </xsl:text> |
|
10002 <xsl:text> } |
|
10003 </xsl:text> |
|
10004 <xsl:text> |
|
10005 </xsl:text> |
|
10006 <xsl:text> reset() { |
|
10007 </xsl:text> |
|
10008 <xsl:text> delete this.currentInvokedGenerator; |
|
10009 </xsl:text> |
|
10010 <xsl:text> } |
|
10011 </xsl:text> |
|
10012 <xsl:text>} |
|
10013 </xsl:text> |
|
10014 <xsl:text> |
|
10015 </xsl:text> |
|
10016 <xsl:text>function rangeSimple(stop) { |
|
10017 </xsl:text> |
|
10018 <xsl:text> return new Iterator(function * () { |
|
10019 </xsl:text> |
|
10020 <xsl:text> for (let i = 0; i < stop; i++) { |
|
10021 </xsl:text> |
|
10022 <xsl:text> yield i; |
|
10023 </xsl:text> |
|
10024 <xsl:text> } |
|
10025 </xsl:text> |
|
10026 <xsl:text> }); |
|
10027 </xsl:text> |
|
10028 <xsl:text>} |
|
10029 </xsl:text> |
|
10030 <xsl:text> |
|
10031 </xsl:text> |
|
10032 <xsl:text>function rangeOverload(start, stop, step = 1) { |
|
10033 </xsl:text> |
|
10034 <xsl:text> return new Iterator(function * () { |
|
10035 </xsl:text> |
|
10036 <xsl:text> for (let i = start; i < stop; i += step) { |
|
10037 </xsl:text> |
|
10038 <xsl:text> yield i; |
|
10039 </xsl:text> |
|
10040 <xsl:text> } |
|
10041 </xsl:text> |
|
10042 <xsl:text> }); |
|
10043 </xsl:text> |
|
10044 <xsl:text>} |
|
10045 </xsl:text> |
|
10046 <xsl:text> |
|
10047 </xsl:text> |
|
10048 <xsl:text>function range(...args) { |
|
10049 </xsl:text> |
|
10050 <xsl:text> if (args.length < 2) { |
|
10051 </xsl:text> |
|
10052 <xsl:text> return rangeSimple(...args); |
|
10053 </xsl:text> |
|
10054 <xsl:text> } |
|
10055 </xsl:text> |
|
10056 <xsl:text> |
|
10057 </xsl:text> |
|
10058 <xsl:text> return rangeOverload(...args); |
|
10059 </xsl:text> |
|
10060 <xsl:text>} |
|
10061 </xsl:text> |
|
10062 <xsl:text> |
|
10063 </xsl:text> |
|
10064 <xsl:text>function enumerate(iterable) { |
|
10065 </xsl:text> |
|
10066 <xsl:text> return new Iterator(function * () { |
|
10067 </xsl:text> |
|
10068 <xsl:text> let index = 0; |
|
10069 </xsl:text> |
|
10070 <xsl:text> for (const element of iterable) { |
|
10071 </xsl:text> |
|
10072 <xsl:text> yield [index, element]; |
|
10073 </xsl:text> |
|
10074 <xsl:text> index++; |
|
10075 </xsl:text> |
|
10076 <xsl:text> } |
|
10077 </xsl:text> |
|
10078 <xsl:text> }); |
|
10079 </xsl:text> |
|
10080 <xsl:text>} |
|
10081 </xsl:text> |
|
10082 <xsl:text> |
|
10083 </xsl:text> |
|
10084 <xsl:text>const _zip = longest => (...iterables) => { |
|
10085 </xsl:text> |
|
10086 <xsl:text> if (iterables.length < 2) { |
|
10087 </xsl:text> |
|
10088 <xsl:text> throw new TypeError("zip takes 2 iterables at least, "+iterables.length+" given"); |
|
10089 </xsl:text> |
|
10090 <xsl:text> } |
|
10091 </xsl:text> |
|
10092 <xsl:text> |
|
10093 </xsl:text> |
|
10094 <xsl:text> return new Iterator(function * () { |
|
10095 </xsl:text> |
|
10096 <xsl:text> const iterators = iterables.map(iterable => Iterator.fromIterable(iterable)); |
|
10097 </xsl:text> |
|
10098 <xsl:text> while (true) { |
|
10099 </xsl:text> |
|
10100 <xsl:text> const row = iterators.map(iterator => iterator.next()); |
|
10101 </xsl:text> |
|
10102 <xsl:text> const check = longest ? row.every.bind(row) : row.some.bind(row); |
|
10103 </xsl:text> |
|
10104 <xsl:text> if (check(next => next.done)) { |
|
10105 </xsl:text> |
|
10106 <xsl:text> return; |
|
10107 </xsl:text> |
|
10108 <xsl:text> } |
|
10109 </xsl:text> |
|
10110 <xsl:text> |
|
10111 </xsl:text> |
|
10112 <xsl:text> yield row.map(next => next.value); |
|
10113 </xsl:text> |
|
10114 <xsl:text> } |
|
10115 </xsl:text> |
|
10116 <xsl:text> }); |
|
10117 </xsl:text> |
|
10118 <xsl:text>}; |
|
10119 </xsl:text> |
|
10120 <xsl:text> |
|
10121 </xsl:text> |
|
10122 <xsl:text>const zip = _zip(false), zipLongest= _zip(true); |
|
10123 </xsl:text> |
|
10124 <xsl:text> |
|
10125 </xsl:text> |
|
10126 <xsl:text>function items(obj) { |
|
10127 </xsl:text> |
|
10128 <xsl:text> let {keys, get} = obj; |
|
10129 </xsl:text> |
|
10130 <xsl:text> if (obj instanceof Map) { |
|
10131 </xsl:text> |
|
10132 <xsl:text> keys = keys.bind(obj); |
|
10133 </xsl:text> |
|
10134 <xsl:text> get = get.bind(obj); |
|
10135 </xsl:text> |
|
10136 <xsl:text> } else { |
|
10137 </xsl:text> |
|
10138 <xsl:text> keys = function () { |
|
10139 </xsl:text> |
|
10140 <xsl:text> return Object.keys(obj); |
|
10141 </xsl:text> |
|
10142 <xsl:text> }; |
|
10143 </xsl:text> |
|
10144 <xsl:text> |
|
10145 </xsl:text> |
|
10146 <xsl:text> get = function (key) { |
|
10147 </xsl:text> |
|
10148 <xsl:text> return obj[key]; |
|
10149 </xsl:text> |
|
10150 <xsl:text> }; |
|
10151 </xsl:text> |
|
10152 <xsl:text> } |
|
10153 </xsl:text> |
|
10154 <xsl:text> |
|
10155 </xsl:text> |
|
10156 <xsl:text> return new Iterator(function * () { |
|
10157 </xsl:text> |
|
10158 <xsl:text> for (const key of keys()) { |
|
10159 </xsl:text> |
|
10160 <xsl:text> yield [key, get(key)]; |
|
10161 </xsl:text> |
|
10162 <xsl:text> } |
|
10163 </xsl:text> |
|
10164 <xsl:text> }); |
|
10165 </xsl:text> |
|
10166 <xsl:text>} |
|
10167 </xsl:text> |
|
10168 <xsl:text> |
|
10169 </xsl:text> |
|
10170 <xsl:text>/* |
|
10171 </xsl:text> |
|
10172 <xsl:text>module.exports = {Iterator, range, enumerate, zip: _zip(false), zipLongest: _zip(true), items}; |
|
10173 </xsl:text> |
|
10174 <xsl:text>*/ |
|
10175 </xsl:text> |
8184 <xsl:text>// svghmi.js |
10176 <xsl:text>// svghmi.js |
8185 </xsl:text> |
10177 </xsl:text> |
8186 <xsl:text> |
10178 <xsl:text> |
8187 </xsl:text> |
10179 </xsl:text> |
8188 <xsl:text>var need_cache_apply = []; |
10180 <xsl:text>var need_cache_apply = []; |
8219 </xsl:text> |
10211 </xsl:text> |
8220 <xsl:text> Object.keys(hmi_widgets).forEach(function(id) { |
10212 <xsl:text> Object.keys(hmi_widgets).forEach(function(id) { |
8221 </xsl:text> |
10213 </xsl:text> |
8222 <xsl:text> let widget = hmi_widgets[id]; |
10214 <xsl:text> let widget = hmi_widgets[id]; |
8223 </xsl:text> |
10215 </xsl:text> |
8224 <xsl:text> let init = widget.init; |
10216 <xsl:text> widget.do_init(); |
8225 </xsl:text> |
10217 </xsl:text> |
8226 <xsl:text> if(typeof(init) == "function"){ |
10218 <xsl:text> }); |
8227 </xsl:text> |
10219 </xsl:text> |
8228 <xsl:text> try { |
10220 <xsl:text>}; |
8229 </xsl:text> |
10221 </xsl:text> |
8230 <xsl:text> init.call(widget); |
10222 <xsl:text> |
8231 </xsl:text> |
10223 </xsl:text> |
8232 <xsl:text> } catch(err) { |
10224 <xsl:text>// Open WebSocket to relative "/ws" address |
8233 </xsl:text> |
10225 </xsl:text> |
8234 <xsl:text> console.log(err); |
10226 <xsl:text>var has_watchdog = window.location.hash == "#watchdog"; |
|
10227 </xsl:text> |
|
10228 <xsl:text> |
|
10229 </xsl:text> |
|
10230 <xsl:text>var ws_url = |
|
10231 </xsl:text> |
|
10232 <xsl:text> window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') |
|
10233 </xsl:text> |
|
10234 <xsl:text> + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); |
|
10235 </xsl:text> |
|
10236 <xsl:text> |
|
10237 </xsl:text> |
|
10238 <xsl:text>var ws = new WebSocket(ws_url); |
|
10239 </xsl:text> |
|
10240 <xsl:text>ws.binaryType = 'arraybuffer'; |
|
10241 </xsl:text> |
|
10242 <xsl:text> |
|
10243 </xsl:text> |
|
10244 <xsl:text>const dvgetters = { |
|
10245 </xsl:text> |
|
10246 <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2], |
|
10247 </xsl:text> |
|
10248 <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
10249 </xsl:text> |
|
10250 <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
10251 </xsl:text> |
|
10252 <xsl:text> REAL: (dv,offset) => [dv.getFloat32(offset, true), 4], |
|
10253 </xsl:text> |
|
10254 <xsl:text> STRING: (dv, offset) => { |
|
10255 </xsl:text> |
|
10256 <xsl:text> const size = dv.getInt8(offset); |
|
10257 </xsl:text> |
|
10258 <xsl:text> return [ |
|
10259 </xsl:text> |
|
10260 <xsl:text> String.fromCharCode.apply(null, new Uint8Array( |
|
10261 </xsl:text> |
|
10262 <xsl:text> dv.buffer, /* original buffer */ |
|
10263 </xsl:text> |
|
10264 <xsl:text> offset + 1, /* string starts after size*/ |
|
10265 </xsl:text> |
|
10266 <xsl:text> size /* size of string */ |
|
10267 </xsl:text> |
|
10268 <xsl:text> )), size + 1]; /* total increment */ |
|
10269 </xsl:text> |
|
10270 <xsl:text> } |
|
10271 </xsl:text> |
|
10272 <xsl:text>}; |
|
10273 </xsl:text> |
|
10274 <xsl:text> |
|
10275 </xsl:text> |
|
10276 <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets |
|
10277 </xsl:text> |
|
10278 <xsl:text>function apply_updates() { |
|
10279 </xsl:text> |
|
10280 <xsl:text> updates.forEach((value, index) => { |
|
10281 </xsl:text> |
|
10282 <xsl:text> dispatch_value(index, value); |
|
10283 </xsl:text> |
|
10284 <xsl:text> }); |
|
10285 </xsl:text> |
|
10286 <xsl:text> updates.clear(); |
|
10287 </xsl:text> |
|
10288 <xsl:text>} |
|
10289 </xsl:text> |
|
10290 <xsl:text> |
|
10291 </xsl:text> |
|
10292 <xsl:text>// Called on requestAnimationFrame, modifies DOM |
|
10293 </xsl:text> |
|
10294 <xsl:text>var requestAnimationFrameID = null; |
|
10295 </xsl:text> |
|
10296 <xsl:text>function animate() { |
|
10297 </xsl:text> |
|
10298 <xsl:text> // Do the page swith if any one pending |
|
10299 </xsl:text> |
|
10300 <xsl:text> if(current_subscribed_page != current_visible_page){ |
|
10301 </xsl:text> |
|
10302 <xsl:text> switch_visible_page(current_subscribed_page); |
|
10303 </xsl:text> |
|
10304 <xsl:text> } |
|
10305 </xsl:text> |
|
10306 <xsl:text> |
|
10307 </xsl:text> |
|
10308 <xsl:text> while(widget = need_cache_apply.pop()){ |
|
10309 </xsl:text> |
|
10310 <xsl:text> widget.apply_cache(); |
|
10311 </xsl:text> |
|
10312 <xsl:text> } |
|
10313 </xsl:text> |
|
10314 <xsl:text> |
|
10315 </xsl:text> |
|
10316 <xsl:text> if(jumps_need_update) update_jumps(); |
|
10317 </xsl:text> |
|
10318 <xsl:text> |
|
10319 </xsl:text> |
|
10320 <xsl:text> apply_updates(); |
|
10321 </xsl:text> |
|
10322 <xsl:text> |
|
10323 </xsl:text> |
|
10324 <xsl:text> pending_widget_animates.forEach(widget => widget._animate()); |
|
10325 </xsl:text> |
|
10326 <xsl:text> pending_widget_animates = []; |
|
10327 </xsl:text> |
|
10328 <xsl:text> |
|
10329 </xsl:text> |
|
10330 <xsl:text> requestAnimationFrameID = null; |
|
10331 </xsl:text> |
|
10332 <xsl:text>} |
|
10333 </xsl:text> |
|
10334 <xsl:text> |
|
10335 </xsl:text> |
|
10336 <xsl:text>function requestHMIAnimation() { |
|
10337 </xsl:text> |
|
10338 <xsl:text> if(requestAnimationFrameID == null){ |
|
10339 </xsl:text> |
|
10340 <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate); |
|
10341 </xsl:text> |
|
10342 <xsl:text> } |
|
10343 </xsl:text> |
|
10344 <xsl:text>} |
|
10345 </xsl:text> |
|
10346 <xsl:text> |
|
10347 </xsl:text> |
|
10348 <xsl:text>// Message reception handler |
|
10349 </xsl:text> |
|
10350 <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing |
|
10351 </xsl:text> |
|
10352 <xsl:text>// are stored until browser can compute next frame, DOM is left untouched |
|
10353 </xsl:text> |
|
10354 <xsl:text>ws.onmessage = function (evt) { |
|
10355 </xsl:text> |
|
10356 <xsl:text> |
|
10357 </xsl:text> |
|
10358 <xsl:text> let data = evt.data; |
|
10359 </xsl:text> |
|
10360 <xsl:text> let dv = new DataView(data); |
|
10361 </xsl:text> |
|
10362 <xsl:text> let i = 0; |
|
10363 </xsl:text> |
|
10364 <xsl:text> try { |
|
10365 </xsl:text> |
|
10366 <xsl:text> for(let hash_int of hmi_hash) { |
|
10367 </xsl:text> |
|
10368 <xsl:text> if(hash_int != dv.getUint8(i)){ |
|
10369 </xsl:text> |
|
10370 <xsl:text> throw new Error("Hash doesn't match"); |
|
10371 </xsl:text> |
|
10372 <xsl:text> }; |
|
10373 </xsl:text> |
|
10374 <xsl:text> i++; |
|
10375 </xsl:text> |
|
10376 <xsl:text> }; |
|
10377 </xsl:text> |
|
10378 <xsl:text> |
|
10379 </xsl:text> |
|
10380 <xsl:text> while(i < data.byteLength){ |
|
10381 </xsl:text> |
|
10382 <xsl:text> let index = dv.getUint32(i, true); |
|
10383 </xsl:text> |
|
10384 <xsl:text> i += 4; |
|
10385 </xsl:text> |
|
10386 <xsl:text> let iectype = hmitree_types[index]; |
|
10387 </xsl:text> |
|
10388 <xsl:text> if(iectype != undefined){ |
|
10389 </xsl:text> |
|
10390 <xsl:text> let dvgetter = dvgetters[iectype]; |
|
10391 </xsl:text> |
|
10392 <xsl:text> let [value, bytesize] = dvgetter(dv,i); |
|
10393 </xsl:text> |
|
10394 <xsl:text> updates.set(index, value); |
|
10395 </xsl:text> |
|
10396 <xsl:text> i += bytesize; |
|
10397 </xsl:text> |
|
10398 <xsl:text> } else { |
|
10399 </xsl:text> |
|
10400 <xsl:text> throw new Error("Unknown index "+index); |
8235 </xsl:text> |
10401 </xsl:text> |
8236 <xsl:text> } |
10402 <xsl:text> } |
8237 </xsl:text> |
10403 </xsl:text> |
|
10404 <xsl:text> }; |
|
10405 </xsl:text> |
|
10406 <xsl:text> // register for rendering on next frame, since there are updates |
|
10407 </xsl:text> |
|
10408 <xsl:text> requestHMIAnimation(); |
|
10409 </xsl:text> |
|
10410 <xsl:text> } catch(err) { |
|
10411 </xsl:text> |
|
10412 <xsl:text> // 1003 is for "Unsupported Data" |
|
10413 </xsl:text> |
|
10414 <xsl:text> // ws.close(1003, err.message); |
|
10415 </xsl:text> |
|
10416 <xsl:text> |
|
10417 </xsl:text> |
|
10418 <xsl:text> // TODO : remove debug alert ? |
|
10419 </xsl:text> |
|
10420 <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded."); |
|
10421 </xsl:text> |
|
10422 <xsl:text> |
|
10423 </xsl:text> |
|
10424 <xsl:text> // force reload ignoring cache |
|
10425 </xsl:text> |
|
10426 <xsl:text> location.reload(true); |
|
10427 </xsl:text> |
|
10428 <xsl:text> } |
|
10429 </xsl:text> |
|
10430 <xsl:text>}; |
|
10431 </xsl:text> |
|
10432 <xsl:text> |
|
10433 </xsl:text> |
|
10434 <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash); |
|
10435 </xsl:text> |
|
10436 <xsl:text> |
|
10437 </xsl:text> |
|
10438 <xsl:text>function send_blob(data) { |
|
10439 </xsl:text> |
|
10440 <xsl:text> if(data.length > 0) { |
|
10441 </xsl:text> |
|
10442 <xsl:text> ws.send(new Blob([hmi_hash_u8].concat(data))); |
|
10443 </xsl:text> |
|
10444 <xsl:text> }; |
|
10445 </xsl:text> |
|
10446 <xsl:text>}; |
|
10447 </xsl:text> |
|
10448 <xsl:text> |
|
10449 </xsl:text> |
|
10450 <xsl:text>const typedarray_types = { |
|
10451 </xsl:text> |
|
10452 <xsl:text> INT: (number) => new Int16Array([number]), |
|
10453 </xsl:text> |
|
10454 <xsl:text> BOOL: (truth) => new Int16Array([truth]), |
|
10455 </xsl:text> |
|
10456 <xsl:text> NODE: (truth) => new Int16Array([truth]), |
|
10457 </xsl:text> |
|
10458 <xsl:text> REAL: (number) => new Float32Array([number]), |
|
10459 </xsl:text> |
|
10460 <xsl:text> STRING: (str) => { |
|
10461 </xsl:text> |
|
10462 <xsl:text> // beremiz default string max size is 128 |
|
10463 </xsl:text> |
|
10464 <xsl:text> str = str.slice(0,128); |
|
10465 </xsl:text> |
|
10466 <xsl:text> binary = new Uint8Array(str.length + 1); |
|
10467 </xsl:text> |
|
10468 <xsl:text> binary[0] = str.length; |
|
10469 </xsl:text> |
|
10470 <xsl:text> for(let i = 0; i < str.length; i++){ |
|
10471 </xsl:text> |
|
10472 <xsl:text> binary[i+1] = str.charCodeAt(i); |
|
10473 </xsl:text> |
8238 <xsl:text> } |
10474 <xsl:text> } |
8239 </xsl:text> |
10475 </xsl:text> |
8240 <xsl:text> if(widget.forced_frequency !== undefined) |
10476 <xsl:text> return binary; |
8241 </xsl:text> |
10477 </xsl:text> |
8242 <xsl:text> widget.frequency = widget.forced_frequency; |
10478 <xsl:text> } |
|
10479 </xsl:text> |
|
10480 <xsl:text> /* TODO */ |
|
10481 </xsl:text> |
|
10482 <xsl:text>}; |
|
10483 </xsl:text> |
|
10484 <xsl:text> |
|
10485 </xsl:text> |
|
10486 <xsl:text>function send_reset() { |
|
10487 </xsl:text> |
|
10488 <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */ |
|
10489 </xsl:text> |
|
10490 <xsl:text>}; |
|
10491 </xsl:text> |
|
10492 <xsl:text> |
|
10493 </xsl:text> |
|
10494 <xsl:text>var subscriptions = []; |
|
10495 </xsl:text> |
|
10496 <xsl:text> |
|
10497 </xsl:text> |
|
10498 <xsl:text>function subscribers(index) { |
|
10499 </xsl:text> |
|
10500 <xsl:text> let entry = subscriptions[index]; |
|
10501 </xsl:text> |
|
10502 <xsl:text> let res; |
|
10503 </xsl:text> |
|
10504 <xsl:text> if(entry == undefined){ |
|
10505 </xsl:text> |
|
10506 <xsl:text> res = new Set(); |
|
10507 </xsl:text> |
|
10508 <xsl:text> subscriptions[index] = [res,0]; |
|
10509 </xsl:text> |
|
10510 <xsl:text> }else{ |
|
10511 </xsl:text> |
|
10512 <xsl:text> [res, _ign] = entry; |
|
10513 </xsl:text> |
|
10514 <xsl:text> } |
|
10515 </xsl:text> |
|
10516 <xsl:text> return res |
|
10517 </xsl:text> |
|
10518 <xsl:text>} |
|
10519 </xsl:text> |
|
10520 <xsl:text> |
|
10521 </xsl:text> |
|
10522 <xsl:text>function get_subscription_period(index) { |
|
10523 </xsl:text> |
|
10524 <xsl:text> let entry = subscriptions[index]; |
|
10525 </xsl:text> |
|
10526 <xsl:text> if(entry == undefined) |
|
10527 </xsl:text> |
|
10528 <xsl:text> return 0; |
|
10529 </xsl:text> |
|
10530 <xsl:text> let [_ign, period] = entry; |
|
10531 </xsl:text> |
|
10532 <xsl:text> return period; |
|
10533 </xsl:text> |
|
10534 <xsl:text>} |
|
10535 </xsl:text> |
|
10536 <xsl:text> |
|
10537 </xsl:text> |
|
10538 <xsl:text>function set_subscription_period(index, period) { |
|
10539 </xsl:text> |
|
10540 <xsl:text> let entry = subscriptions[index]; |
|
10541 </xsl:text> |
|
10542 <xsl:text> if(entry == undefined){ |
|
10543 </xsl:text> |
|
10544 <xsl:text> subscriptions[index] = [new Set(), period]; |
|
10545 </xsl:text> |
|
10546 <xsl:text> } else { |
|
10547 </xsl:text> |
|
10548 <xsl:text> entry[1] = period; |
|
10549 </xsl:text> |
|
10550 <xsl:text> } |
|
10551 </xsl:text> |
|
10552 <xsl:text>} |
|
10553 </xsl:text> |
|
10554 <xsl:text> |
|
10555 </xsl:text> |
|
10556 <xsl:text>if(has_watchdog){ |
|
10557 </xsl:text> |
|
10558 <xsl:text> // artificially subscribe the watchdog widget to "/heartbeat" hmi variable |
|
10559 </xsl:text> |
|
10560 <xsl:text> // Since dispatch directly calls change_hmi_value, |
|
10561 </xsl:text> |
|
10562 <xsl:text> // PLC will periodically send variable at given frequency |
|
10563 </xsl:text> |
|
10564 <xsl:text> subscribers(heartbeat_index).add({ |
|
10565 </xsl:text> |
|
10566 <xsl:text> /* type: "Watchdog", */ |
|
10567 </xsl:text> |
|
10568 <xsl:text> frequency: 1, |
|
10569 </xsl:text> |
|
10570 <xsl:text> indexes: [heartbeat_index], |
|
10571 </xsl:text> |
|
10572 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
10573 </xsl:text> |
|
10574 <xsl:text> apply_hmi_value(heartbeat_index, value+1); |
|
10575 </xsl:text> |
|
10576 <xsl:text> } |
8243 </xsl:text> |
10577 </xsl:text> |
8244 <xsl:text> }); |
10578 <xsl:text> }); |
8245 </xsl:text> |
10579 </xsl:text> |
8246 <xsl:text>}; |
10580 <xsl:text>} |
8247 </xsl:text> |
10581 </xsl:text> |
8248 <xsl:text> |
10582 <xsl:text> |
8249 </xsl:text> |
10583 </xsl:text> |
8250 <xsl:text>// Open WebSocket to relative "/ws" address |
10584 <xsl:text>// subscribe to per instance current page hmi variable |
8251 </xsl:text> |
10585 </xsl:text> |
8252 <xsl:text>var has_watchdog = window.location.hash == "#watchdog"; |
10586 <xsl:text>// PLC must prefix page name with "!" for page switch to happen |
8253 </xsl:text> |
10587 </xsl:text> |
8254 <xsl:text> |
10588 <xsl:text>subscribers(current_page_var_index).add({ |
8255 </xsl:text> |
10589 </xsl:text> |
8256 <xsl:text>var ws_url = |
10590 <xsl:text> frequency: 1, |
8257 </xsl:text> |
10591 </xsl:text> |
8258 <xsl:text> window.location.href.replace(/^http(s?:\/\/[^\/]*)\/.*$/, 'ws$1/ws') |
10592 <xsl:text> indexes: [current_page_var_index], |
8259 </xsl:text> |
10593 </xsl:text> |
8260 <xsl:text> + '?mode=' + (has_watchdog ? "watchdog" : "multiclient"); |
10594 <xsl:text> new_hmi_value: function(index, value, oldval) { |
8261 </xsl:text> |
10595 </xsl:text> |
8262 <xsl:text> |
10596 <xsl:text> if(value.startsWith("!")) |
8263 </xsl:text> |
10597 </xsl:text> |
8264 <xsl:text>var ws = new WebSocket(ws_url); |
10598 <xsl:text> switch_page(value.slice(1)); |
8265 </xsl:text> |
|
8266 <xsl:text>ws.binaryType = 'arraybuffer'; |
|
8267 </xsl:text> |
|
8268 <xsl:text> |
|
8269 </xsl:text> |
|
8270 <xsl:text>const dvgetters = { |
|
8271 </xsl:text> |
|
8272 <xsl:text> INT: (dv,offset) => [dv.getInt16(offset, true), 2], |
|
8273 </xsl:text> |
|
8274 <xsl:text> BOOL: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
8275 </xsl:text> |
|
8276 <xsl:text> NODE: (dv,offset) => [dv.getInt8(offset, true), 1], |
|
8277 </xsl:text> |
|
8278 <xsl:text> REAL: (dv,offset) => [dv.getFloat32(offset, true), 4], |
|
8279 </xsl:text> |
|
8280 <xsl:text> STRING: (dv, offset) => { |
|
8281 </xsl:text> |
|
8282 <xsl:text> const size = dv.getInt8(offset); |
|
8283 </xsl:text> |
|
8284 <xsl:text> return [ |
|
8285 </xsl:text> |
|
8286 <xsl:text> String.fromCharCode.apply(null, new Uint8Array( |
|
8287 </xsl:text> |
|
8288 <xsl:text> dv.buffer, /* original buffer */ |
|
8289 </xsl:text> |
|
8290 <xsl:text> offset + 1, /* string starts after size*/ |
|
8291 </xsl:text> |
|
8292 <xsl:text> size /* size of string */ |
|
8293 </xsl:text> |
|
8294 <xsl:text> )), size + 1]; /* total increment */ |
|
8295 </xsl:text> |
10599 </xsl:text> |
8296 <xsl:text> } |
10600 <xsl:text> } |
8297 </xsl:text> |
10601 </xsl:text> |
8298 <xsl:text>}; |
10602 <xsl:text>}); |
8299 </xsl:text> |
10603 </xsl:text> |
8300 <xsl:text> |
10604 <xsl:text> |
8301 </xsl:text> |
10605 </xsl:text> |
8302 <xsl:text>// Apply updates recieved through ws.onmessage to subscribed widgets |
10606 <xsl:text>function svg_text_to_multiline(elt) { |
8303 </xsl:text> |
10607 </xsl:text> |
8304 <xsl:text>function apply_updates() { |
10608 <xsl:text> return(Array.prototype.map.call(elt.children, x=>x.textContent).join("\n")); |
8305 </xsl:text> |
|
8306 <xsl:text> updates.forEach((value, index) => { |
|
8307 </xsl:text> |
|
8308 <xsl:text> dispatch_value(index, value); |
|
8309 </xsl:text> |
|
8310 <xsl:text> }); |
|
8311 </xsl:text> |
|
8312 <xsl:text> updates.clear(); |
|
8313 </xsl:text> |
10609 </xsl:text> |
8314 <xsl:text>} |
10610 <xsl:text>} |
8315 </xsl:text> |
10611 </xsl:text> |
8316 <xsl:text> |
10612 <xsl:text> |
8317 </xsl:text> |
10613 </xsl:text> |
8318 <xsl:text>// Called on requestAnimationFrame, modifies DOM |
10614 <xsl:text>function multiline_to_svg_text(elt, str) { |
8319 </xsl:text> |
10615 </xsl:text> |
8320 <xsl:text>var requestAnimationFrameID = null; |
10616 <xsl:text> str.split('\n').map((line,i) => {elt.children[i].textContent = line;}); |
8321 </xsl:text> |
10617 </xsl:text> |
8322 <xsl:text>function animate() { |
10618 <xsl:text>} |
8323 </xsl:text> |
10619 </xsl:text> |
8324 <xsl:text> // Do the page swith if any one pending |
10620 <xsl:text> |
8325 </xsl:text> |
10621 </xsl:text> |
8326 <xsl:text> if(current_subscribed_page != current_visible_page){ |
10622 <xsl:text>function switch_langnum(langnum) { |
8327 </xsl:text> |
10623 </xsl:text> |
8328 <xsl:text> switch_visible_page(current_subscribed_page); |
10624 <xsl:text> langnum = Math.max(0, Math.min(langs.length - 1, langnum)); |
|
10625 </xsl:text> |
|
10626 <xsl:text> |
|
10627 </xsl:text> |
|
10628 <xsl:text> for (let translation of translations) { |
|
10629 </xsl:text> |
|
10630 <xsl:text> let [objs, msgs] = translation; |
|
10631 </xsl:text> |
|
10632 <xsl:text> let msg = msgs[langnum]; |
|
10633 </xsl:text> |
|
10634 <xsl:text> for (let obj of objs) { |
|
10635 </xsl:text> |
|
10636 <xsl:text> multiline_to_svg_text(obj, msg); |
|
10637 </xsl:text> |
|
10638 <xsl:text> obj.setAttribute("lang",langnum); |
|
10639 </xsl:text> |
|
10640 <xsl:text> } |
8329 </xsl:text> |
10641 </xsl:text> |
8330 <xsl:text> } |
10642 <xsl:text> } |
8331 </xsl:text> |
10643 </xsl:text> |
8332 <xsl:text> |
10644 <xsl:text> return langnum; |
8333 </xsl:text> |
10645 </xsl:text> |
8334 <xsl:text> while(widget = need_cache_apply.pop()){ |
10646 <xsl:text>} |
8335 </xsl:text> |
10647 </xsl:text> |
8336 <xsl:text> widget.apply_cache(); |
10648 <xsl:text> |
|
10649 </xsl:text> |
|
10650 <xsl:text>// backup original texts |
|
10651 </xsl:text> |
|
10652 <xsl:text>for (let translation of translations) { |
|
10653 </xsl:text> |
|
10654 <xsl:text> let [objs, msgs] = translation; |
|
10655 </xsl:text> |
|
10656 <xsl:text> msgs.unshift(svg_text_to_multiline(objs[0])); |
|
10657 </xsl:text> |
|
10658 <xsl:text>} |
|
10659 </xsl:text> |
|
10660 <xsl:text> |
|
10661 </xsl:text> |
|
10662 <xsl:text>var lang_local_index = hmi_local_index("lang"); |
|
10663 </xsl:text> |
|
10664 <xsl:text>var langcode_local_index = hmi_local_index("lang_code"); |
|
10665 </xsl:text> |
|
10666 <xsl:text>var langname_local_index = hmi_local_index("lang_name"); |
|
10667 </xsl:text> |
|
10668 <xsl:text>subscribers(lang_local_index).add({ |
|
10669 </xsl:text> |
|
10670 <xsl:text> indexes: [lang_local_index], |
|
10671 </xsl:text> |
|
10672 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
10673 </xsl:text> |
|
10674 <xsl:text> let current_lang = switch_langnum(value); |
|
10675 </xsl:text> |
|
10676 <xsl:text> let [langname,langcode] = langs[current_lang]; |
|
10677 </xsl:text> |
|
10678 <xsl:text> apply_hmi_value(langcode_local_index, langcode); |
|
10679 </xsl:text> |
|
10680 <xsl:text> apply_hmi_value(langname_local_index, langname); |
|
10681 </xsl:text> |
|
10682 <xsl:text> switch_page(); |
8337 </xsl:text> |
10683 </xsl:text> |
8338 <xsl:text> } |
10684 <xsl:text> } |
8339 </xsl:text> |
10685 </xsl:text> |
8340 <xsl:text> |
10686 <xsl:text>}); |
8341 </xsl:text> |
10687 </xsl:text> |
8342 <xsl:text> if(jumps_need_update) update_jumps(); |
10688 <xsl:text> |
8343 </xsl:text> |
10689 </xsl:text> |
8344 <xsl:text> |
10690 <xsl:text>// returns en_US, fr_FR or en_UK depending on selected language |
8345 </xsl:text> |
10691 </xsl:text> |
8346 <xsl:text> apply_updates(); |
10692 <xsl:text>function get_current_lang_code(){ |
8347 </xsl:text> |
10693 </xsl:text> |
8348 <xsl:text> |
10694 <xsl:text> return cache[langcode_local_index]; |
8349 </xsl:text> |
|
8350 <xsl:text> pending_widget_animates.forEach(widget => widget._animate()); |
|
8351 </xsl:text> |
|
8352 <xsl:text> pending_widget_animates = []; |
|
8353 </xsl:text> |
|
8354 <xsl:text> |
|
8355 </xsl:text> |
|
8356 <xsl:text> requestAnimationFrameID = null; |
|
8357 </xsl:text> |
10695 </xsl:text> |
8358 <xsl:text>} |
10696 <xsl:text>} |
8359 </xsl:text> |
|
8360 <xsl:text> |
|
8361 </xsl:text> |
|
8362 <xsl:text>function requestHMIAnimation() { |
|
8363 </xsl:text> |
|
8364 <xsl:text> if(requestAnimationFrameID == null){ |
|
8365 </xsl:text> |
|
8366 <xsl:text> requestAnimationFrameID = window.requestAnimationFrame(animate); |
|
8367 </xsl:text> |
|
8368 <xsl:text> } |
|
8369 </xsl:text> |
|
8370 <xsl:text>} |
|
8371 </xsl:text> |
|
8372 <xsl:text> |
|
8373 </xsl:text> |
|
8374 <xsl:text>// Message reception handler |
|
8375 </xsl:text> |
|
8376 <xsl:text>// Hash is verified and HMI values updates resulting from binary parsing |
|
8377 </xsl:text> |
|
8378 <xsl:text>// are stored until browser can compute next frame, DOM is left untouched |
|
8379 </xsl:text> |
|
8380 <xsl:text>ws.onmessage = function (evt) { |
|
8381 </xsl:text> |
|
8382 <xsl:text> |
|
8383 </xsl:text> |
|
8384 <xsl:text> let data = evt.data; |
|
8385 </xsl:text> |
|
8386 <xsl:text> let dv = new DataView(data); |
|
8387 </xsl:text> |
|
8388 <xsl:text> let i = 0; |
|
8389 </xsl:text> |
|
8390 <xsl:text> try { |
|
8391 </xsl:text> |
|
8392 <xsl:text> for(let hash_int of hmi_hash) { |
|
8393 </xsl:text> |
|
8394 <xsl:text> if(hash_int != dv.getUint8(i)){ |
|
8395 </xsl:text> |
|
8396 <xsl:text> throw new Error("Hash doesn't match"); |
|
8397 </xsl:text> |
|
8398 <xsl:text> }; |
|
8399 </xsl:text> |
|
8400 <xsl:text> i++; |
|
8401 </xsl:text> |
|
8402 <xsl:text> }; |
|
8403 </xsl:text> |
|
8404 <xsl:text> |
|
8405 </xsl:text> |
|
8406 <xsl:text> while(i < data.byteLength){ |
|
8407 </xsl:text> |
|
8408 <xsl:text> let index = dv.getUint32(i, true); |
|
8409 </xsl:text> |
|
8410 <xsl:text> i += 4; |
|
8411 </xsl:text> |
|
8412 <xsl:text> let iectype = hmitree_types[index]; |
|
8413 </xsl:text> |
|
8414 <xsl:text> if(iectype != undefined){ |
|
8415 </xsl:text> |
|
8416 <xsl:text> let dvgetter = dvgetters[iectype]; |
|
8417 </xsl:text> |
|
8418 <xsl:text> let [value, bytesize] = dvgetter(dv,i); |
|
8419 </xsl:text> |
|
8420 <xsl:text> updates.set(index, value); |
|
8421 </xsl:text> |
|
8422 <xsl:text> i += bytesize; |
|
8423 </xsl:text> |
|
8424 <xsl:text> } else { |
|
8425 </xsl:text> |
|
8426 <xsl:text> throw new Error("Unknown index "+index); |
|
8427 </xsl:text> |
|
8428 <xsl:text> } |
|
8429 </xsl:text> |
|
8430 <xsl:text> }; |
|
8431 </xsl:text> |
|
8432 <xsl:text> // register for rendering on next frame, since there are updates |
|
8433 </xsl:text> |
|
8434 <xsl:text> requestHMIAnimation(); |
|
8435 </xsl:text> |
|
8436 <xsl:text> } catch(err) { |
|
8437 </xsl:text> |
|
8438 <xsl:text> // 1003 is for "Unsupported Data" |
|
8439 </xsl:text> |
|
8440 <xsl:text> // ws.close(1003, err.message); |
|
8441 </xsl:text> |
|
8442 <xsl:text> |
|
8443 </xsl:text> |
|
8444 <xsl:text> // TODO : remove debug alert ? |
|
8445 </xsl:text> |
|
8446 <xsl:text> alert("Error : "+err.message+"\nHMI will be reloaded."); |
|
8447 </xsl:text> |
|
8448 <xsl:text> |
|
8449 </xsl:text> |
|
8450 <xsl:text> // force reload ignoring cache |
|
8451 </xsl:text> |
|
8452 <xsl:text> location.reload(true); |
|
8453 </xsl:text> |
|
8454 <xsl:text> } |
|
8455 </xsl:text> |
|
8456 <xsl:text>}; |
|
8457 </xsl:text> |
|
8458 <xsl:text> |
|
8459 </xsl:text> |
|
8460 <xsl:text>hmi_hash_u8 = new Uint8Array(hmi_hash); |
|
8461 </xsl:text> |
|
8462 <xsl:text> |
|
8463 </xsl:text> |
|
8464 <xsl:text>function send_blob(data) { |
|
8465 </xsl:text> |
|
8466 <xsl:text> if(data.length > 0) { |
|
8467 </xsl:text> |
|
8468 <xsl:text> ws.send(new Blob([hmi_hash_u8].concat(data))); |
|
8469 </xsl:text> |
|
8470 <xsl:text> }; |
|
8471 </xsl:text> |
|
8472 <xsl:text>}; |
|
8473 </xsl:text> |
|
8474 <xsl:text> |
|
8475 </xsl:text> |
|
8476 <xsl:text>const typedarray_types = { |
|
8477 </xsl:text> |
|
8478 <xsl:text> INT: (number) => new Int16Array([number]), |
|
8479 </xsl:text> |
|
8480 <xsl:text> BOOL: (truth) => new Int16Array([truth]), |
|
8481 </xsl:text> |
|
8482 <xsl:text> NODE: (truth) => new Int16Array([truth]), |
|
8483 </xsl:text> |
|
8484 <xsl:text> REAL: (number) => new Float32Array([number]), |
|
8485 </xsl:text> |
|
8486 <xsl:text> STRING: (str) => { |
|
8487 </xsl:text> |
|
8488 <xsl:text> // beremiz default string max size is 128 |
|
8489 </xsl:text> |
|
8490 <xsl:text> str = str.slice(0,128); |
|
8491 </xsl:text> |
|
8492 <xsl:text> binary = new Uint8Array(str.length + 1); |
|
8493 </xsl:text> |
|
8494 <xsl:text> binary[0] = str.length; |
|
8495 </xsl:text> |
|
8496 <xsl:text> for(let i = 0; i < str.length; i++){ |
|
8497 </xsl:text> |
|
8498 <xsl:text> binary[i+1] = str.charCodeAt(i); |
|
8499 </xsl:text> |
|
8500 <xsl:text> } |
|
8501 </xsl:text> |
|
8502 <xsl:text> return binary; |
|
8503 </xsl:text> |
|
8504 <xsl:text> } |
|
8505 </xsl:text> |
|
8506 <xsl:text> /* TODO */ |
|
8507 </xsl:text> |
|
8508 <xsl:text>}; |
|
8509 </xsl:text> |
|
8510 <xsl:text> |
|
8511 </xsl:text> |
|
8512 <xsl:text>function send_reset() { |
|
8513 </xsl:text> |
|
8514 <xsl:text> send_blob(new Uint8Array([1])); /* reset = 1 */ |
|
8515 </xsl:text> |
|
8516 <xsl:text>}; |
|
8517 </xsl:text> |
|
8518 <xsl:text> |
|
8519 </xsl:text> |
|
8520 <xsl:text>var subscriptions = []; |
|
8521 </xsl:text> |
|
8522 <xsl:text> |
|
8523 </xsl:text> |
|
8524 <xsl:text>function subscribers(index) { |
|
8525 </xsl:text> |
|
8526 <xsl:text> let entry = subscriptions[index]; |
|
8527 </xsl:text> |
|
8528 <xsl:text> let res; |
|
8529 </xsl:text> |
|
8530 <xsl:text> if(entry == undefined){ |
|
8531 </xsl:text> |
|
8532 <xsl:text> res = new Set(); |
|
8533 </xsl:text> |
|
8534 <xsl:text> subscriptions[index] = [res,0]; |
|
8535 </xsl:text> |
|
8536 <xsl:text> }else{ |
|
8537 </xsl:text> |
|
8538 <xsl:text> [res, _ign] = entry; |
|
8539 </xsl:text> |
|
8540 <xsl:text> } |
|
8541 </xsl:text> |
|
8542 <xsl:text> return res |
|
8543 </xsl:text> |
|
8544 <xsl:text>} |
|
8545 </xsl:text> |
|
8546 <xsl:text> |
|
8547 </xsl:text> |
|
8548 <xsl:text>function get_subscription_period(index) { |
|
8549 </xsl:text> |
|
8550 <xsl:text> let entry = subscriptions[index]; |
|
8551 </xsl:text> |
|
8552 <xsl:text> if(entry == undefined) |
|
8553 </xsl:text> |
|
8554 <xsl:text> return 0; |
|
8555 </xsl:text> |
|
8556 <xsl:text> let [_ign, period] = entry; |
|
8557 </xsl:text> |
|
8558 <xsl:text> return period; |
|
8559 </xsl:text> |
|
8560 <xsl:text>} |
|
8561 </xsl:text> |
|
8562 <xsl:text> |
|
8563 </xsl:text> |
|
8564 <xsl:text>function set_subscription_period(index, period) { |
|
8565 </xsl:text> |
|
8566 <xsl:text> let entry = subscriptions[index]; |
|
8567 </xsl:text> |
|
8568 <xsl:text> if(entry == undefined){ |
|
8569 </xsl:text> |
|
8570 <xsl:text> subscriptions[index] = [new Set(), period]; |
|
8571 </xsl:text> |
|
8572 <xsl:text> } else { |
|
8573 </xsl:text> |
|
8574 <xsl:text> entry[1] = period; |
|
8575 </xsl:text> |
|
8576 <xsl:text> } |
|
8577 </xsl:text> |
|
8578 <xsl:text>} |
|
8579 </xsl:text> |
|
8580 <xsl:text> |
|
8581 </xsl:text> |
|
8582 <xsl:text>if(has_watchdog){ |
|
8583 </xsl:text> |
|
8584 <xsl:text> // artificially subscribe the watchdog widget to "/heartbeat" hmi variable |
|
8585 </xsl:text> |
|
8586 <xsl:text> // Since dispatch directly calls change_hmi_value, |
|
8587 </xsl:text> |
|
8588 <xsl:text> // PLC will periodically send variable at given frequency |
|
8589 </xsl:text> |
|
8590 <xsl:text> subscribers(heartbeat_index).add({ |
|
8591 </xsl:text> |
|
8592 <xsl:text> /* type: "Watchdog", */ |
|
8593 </xsl:text> |
|
8594 <xsl:text> frequency: 1, |
|
8595 </xsl:text> |
|
8596 <xsl:text> indexes: [heartbeat_index], |
|
8597 </xsl:text> |
|
8598 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8599 </xsl:text> |
|
8600 <xsl:text> apply_hmi_value(heartbeat_index, value+1); |
|
8601 </xsl:text> |
|
8602 <xsl:text> } |
|
8603 </xsl:text> |
|
8604 <xsl:text> }); |
|
8605 </xsl:text> |
|
8606 <xsl:text>} |
|
8607 </xsl:text> |
|
8608 <xsl:text> |
|
8609 </xsl:text> |
|
8610 <xsl:text>// subscribe to per instance current page hmi variable |
|
8611 </xsl:text> |
|
8612 <xsl:text>// PLC must prefix page name with "!" for page switch to happen |
|
8613 </xsl:text> |
|
8614 <xsl:text>subscribers(current_page_var_index).add({ |
|
8615 </xsl:text> |
|
8616 <xsl:text> frequency: 1, |
|
8617 </xsl:text> |
|
8618 <xsl:text> indexes: [current_page_var_index], |
|
8619 </xsl:text> |
|
8620 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8621 </xsl:text> |
|
8622 <xsl:text> if(value.startsWith("!")) |
|
8623 </xsl:text> |
|
8624 <xsl:text> switch_page(value.slice(1)); |
|
8625 </xsl:text> |
|
8626 <xsl:text> } |
|
8627 </xsl:text> |
|
8628 <xsl:text>}); |
|
8629 </xsl:text> |
|
8630 <xsl:text> |
|
8631 </xsl:text> |
|
8632 <xsl:text>function svg_text_to_multiline(elt) { |
|
8633 </xsl:text> |
|
8634 <xsl:text> return(Array.prototype.map.call(elt.children, x=>x.textContent).join("\n")); |
|
8635 </xsl:text> |
|
8636 <xsl:text>} |
|
8637 </xsl:text> |
|
8638 <xsl:text> |
|
8639 </xsl:text> |
|
8640 <xsl:text>function multiline_to_svg_text(elt, str) { |
|
8641 </xsl:text> |
|
8642 <xsl:text> str.split('\n').map((line,i) => {elt.children[i].textContent = line;}); |
|
8643 </xsl:text> |
|
8644 <xsl:text>} |
|
8645 </xsl:text> |
|
8646 <xsl:text> |
|
8647 </xsl:text> |
|
8648 <xsl:text>function switch_langnum(langnum) { |
|
8649 </xsl:text> |
|
8650 <xsl:text> langnum = Math.max(0, Math.min(langs.length - 1, langnum)); |
|
8651 </xsl:text> |
|
8652 <xsl:text> |
|
8653 </xsl:text> |
|
8654 <xsl:text> for (let translation of translations) { |
|
8655 </xsl:text> |
|
8656 <xsl:text> let [objs, msgs] = translation; |
|
8657 </xsl:text> |
|
8658 <xsl:text> let msg = msgs[langnum]; |
|
8659 </xsl:text> |
|
8660 <xsl:text> for (let obj of objs) { |
|
8661 </xsl:text> |
|
8662 <xsl:text> multiline_to_svg_text(obj, msg); |
|
8663 </xsl:text> |
|
8664 <xsl:text> obj.setAttribute("lang",langnum); |
|
8665 </xsl:text> |
|
8666 <xsl:text> } |
|
8667 </xsl:text> |
|
8668 <xsl:text> } |
|
8669 </xsl:text> |
|
8670 <xsl:text> return langnum; |
|
8671 </xsl:text> |
|
8672 <xsl:text>} |
|
8673 </xsl:text> |
|
8674 <xsl:text> |
|
8675 </xsl:text> |
|
8676 <xsl:text>// backup original texts |
|
8677 </xsl:text> |
|
8678 <xsl:text>for (let translation of translations) { |
|
8679 </xsl:text> |
|
8680 <xsl:text> let [objs, msgs] = translation; |
|
8681 </xsl:text> |
|
8682 <xsl:text> msgs.unshift(svg_text_to_multiline(objs[0])); |
|
8683 </xsl:text> |
|
8684 <xsl:text>} |
|
8685 </xsl:text> |
|
8686 <xsl:text> |
|
8687 </xsl:text> |
|
8688 <xsl:text>var lang_local_index = hmi_local_index("lang"); |
|
8689 </xsl:text> |
|
8690 <xsl:text>var langcode_local_index = hmi_local_index("lang_code"); |
|
8691 </xsl:text> |
|
8692 <xsl:text>var langname_local_index = hmi_local_index("lang_name"); |
|
8693 </xsl:text> |
|
8694 <xsl:text>subscribers(lang_local_index).add({ |
|
8695 </xsl:text> |
|
8696 <xsl:text> indexes: [lang_local_index], |
|
8697 </xsl:text> |
|
8698 <xsl:text> new_hmi_value: function(index, value, oldval) { |
|
8699 </xsl:text> |
|
8700 <xsl:text> let current_lang = switch_langnum(value); |
|
8701 </xsl:text> |
|
8702 <xsl:text> let [langname,langcode] = langs[current_lang]; |
|
8703 </xsl:text> |
|
8704 <xsl:text> apply_hmi_value(langcode_local_index, langcode); |
|
8705 </xsl:text> |
|
8706 <xsl:text> apply_hmi_value(langname_local_index, langname); |
|
8707 </xsl:text> |
|
8708 <xsl:text> switch_page(); |
|
8709 </xsl:text> |
|
8710 <xsl:text> } |
|
8711 </xsl:text> |
|
8712 <xsl:text>}); |
|
8713 </xsl:text> |
10697 </xsl:text> |
8714 <xsl:text> |
10698 <xsl:text> |
8715 </xsl:text> |
10699 </xsl:text> |
8716 <xsl:text>function setup_lang(){ |
10700 <xsl:text>function setup_lang(){ |
8717 </xsl:text> |
10701 </xsl:text> |