svg.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  1. "use strict";
  2. FW_version["svg.js"] = "$Id: svg.js 15896 2018-01-14 21:35:42Z rudolfkoenig $";
  3. var svgCallback={};
  4. if(!svgNS) {
  5. var svgNS = "http://www.w3.org/2000/svg";
  6. var svg_initialized={}, lastHidden;
  7. var svg_db, svg_dbtbl = "SVG_KEYVALUE", svg_pastedata, svgCounter=0;
  8. }
  9. function
  10. svg_initDb(nextFn)
  11. {
  12. if(window.indexedDB == undefined)
  13. return;
  14. var dbreq = indexedDB.open("FHEM", 1);
  15. dbreq.onsuccess = function(op) { svg_db = op.target.result; nextFn() }
  16. dbreq.onerror = function(op) {
  17. var oldfn = window.onerror;
  18. window.onerror = undefined; // stupid FireFox private mode (Forum #64541)
  19. log("indexedDB.open Error: " + op.message);
  20. setTimeout(function(){window.onerror = oldfn;}, 100);
  21. }
  22. dbreq.onupgradeneeded = function(op) {
  23. svg_db = op.target.result;
  24. svg_db.createObjectStore(svg_dbtbl, { keyPath:"key" });
  25. };
  26. }
  27. function
  28. svg_save(key, value)
  29. {
  30. if(!svg_db)
  31. return;
  32. var os = svg_db.transaction([svg_dbtbl],"readwrite")
  33. .objectStore(svg_dbtbl);
  34. os.put({key:key, val:value});
  35. }
  36. function
  37. svg_load(key, nextFn)
  38. {
  39. if(!svg_db)
  40. return;
  41. var req = svg_db.transaction([svg_dbtbl],"readonly")
  42. .objectStore(svg_dbtbl)
  43. .get(key);
  44. req.onsuccess = function(e) { if(req.result) nextFn(req.result.val); }
  45. }
  46. function
  47. svg_prepareHash(el)
  48. {
  49. var obj = { y_mul:0,y_h:0,y_min:0, decimals:0,
  50. t_mul:0,x_off:0,x_min:0, x_mul:0, log_scale:undefined };
  51. for(var name in obj)
  52. obj[name] = parseFloat($(el).attr(name));
  53. return obj;
  54. }
  55. function
  56. svg_click(evt)
  57. {
  58. var t = evt.target;
  59. var o = svg_prepareHash(t);
  60. var y_org = (((o.y_h-evt.clientY)/o.y_mul)+o.y_min).toFixed(o.decimals);
  61. var d = new Date((((evt.clientX-o.x_min)/o.t_mul)+o.x_off) * 1000);
  62. var ts = (d.getHours() < 10 ? '0' : '') + d.getHours() + ":"+
  63. (d.getMinutes() < 10 ? '0' : '') + d.getMinutes();
  64. var tl = t.ownerDocument.getElementById('svg_title');
  65. tl.firstChild.nodeValue = t.getAttribute("title")+": "+y_org+" ("+ts+")";
  66. }
  67. function
  68. sv_menu(evt, embed)
  69. {
  70. var label = evt.target;
  71. var svg = $(label).closest("svg");
  72. var svgNode = $(svg).get(0);
  73. var lid = $(label).attr("line_id");
  74. var sel = $(svg).find("#"+lid);
  75. var selNode = $(sel).get(0);
  76. var tl = $(svg).find("#svg_title");
  77. var par = svgNode.par;
  78. var lines = $(svg).find("[line_id]");
  79. var hidden = $(svg).find(".hidden");
  80. function
  81. myPathSegList(node) // chrome 48+ removed the pathSegList interface
  82. {
  83. this.arr = $(node).attr("d").split(/ */);
  84. this.arr.splice(0,1); // remove M
  85. this.arr.splice(1,1); // remove L/Q/etc
  86. this.numberOfItems = this.arr.length;
  87. this.getItem = function(pos)
  88. {
  89. var xy = this.arr[pos].split(",");
  90. return { x:parseFloat(xy[0]), y:parseFloat(xy[1]) };
  91. }
  92. }
  93. function
  94. showValOff() {
  95. $(svg).find("[id]").each(function(){delete($(this).get(0).showVal)});
  96. $(svg).off("mousemove");
  97. if(par && par.circle) {
  98. $(par.circle).remove();
  99. $(par.div).remove();
  100. }
  101. }
  102. var sn = selNode.nodeName,
  103. pn = (sn=="path" ? "d" : "points"),
  104. arrName = (sn=="path" ? "pathSegList" : "points");
  105. FW_menu(evt, label,
  106. ["Copy", "Paste",
  107. selNode.isHidden ? "Show line" : "Hide line",
  108. "Hide other lines",
  109. "Show all lines",
  110. selNode.showVal ? "Stop displaying values" : "Display plot values" ],
  111. [undefined, svg_pastedata == undefined,
  112. !selNode.isHidden && (lines.length - hidden.length) == 1,
  113. !selNode.isHidden && (lines.length - hidden.length) == 1,
  114. hidden.length==0,
  115. selNode.isHidden || (sn!="polyline" && sn!="path") ],
  116. function(arg) {
  117. //////////////////////////////////// copy
  118. if(arg == 0) {
  119. svg_pastedata = {
  120. key:"svg_pastedata",
  121. tag:sn, attr:pn,
  122. y_min:$(sel).attr("y_min"),
  123. y_mul:$(sel).attr("y_mul"),
  124. datapoints:$(sel).attr(pn)
  125. };
  126. svg_save("svg_pastedata", svg_pastedata);
  127. }
  128. //////////////////////////////////// paste
  129. if(arg == 1) {
  130. var doc = $(svg).get(0).ownerDocument;
  131. var o=doc.createElementNS(svgNS, svg_pastedata.tag);
  132. o.setAttribute("class", "SVGplot pasted");
  133. o.setAttribute(svg_pastedata.attr, svg_pastedata.datapoints);
  134. var h = parseFloat($(sel).attr("y_h"));
  135. var ny_mul = parseFloat(svg_pastedata.y_mul);
  136. var ny_min = parseInt(svg_pastedata.y_min);
  137. var y_mul = parseFloat($(sel).attr("y_mul"));
  138. var y_min = parseInt($(sel).attr("y_min"));
  139. var tr =
  140. "translate(0,"+ (h/y_mul+y_min-h/ny_mul-ny_min)*y_mul +") "+
  141. "scale(1, "+ (y_mul/ny_mul) +") ";
  142. o.setAttribute("transform", tr);
  143. svgNode.appendChild(o);
  144. }
  145. //show/hide
  146. if(arg == 2) {
  147. setVisibility(lid, selNode.isHidden?1:0);
  148. $(label).attr("opacity", selNode.isHidden?0.4:1);
  149. }
  150. //hide other
  151. if(arg == 3) {
  152. $(svg).find("[id]").each(function(){
  153. var id = $(this).attr("id");
  154. if(id.indexOf("line_") != 0 )
  155. return;
  156. var label = $(svg).find('[line_id="'+id+'"]');
  157. if( !label.length ) // ignore lines with label none
  158. return;
  159. var sel = $(svg).find("#"+id);
  160. var selNode = $(sel).get(0);
  161. if( (selNode.isHidden?false:true) != (id == lid) )
  162. setVisibility(id, id == lid);
  163. label.attr("opacity", id == lid?1:0.4);
  164. } );
  165. }
  166. //show all
  167. if(arg == 4) {
  168. $(svg).find("[line_id]").attr("opacity",1);
  169. $(svg).find("[id]").each(function(){
  170. var id = $(this).attr("id");
  171. if(id.indexOf("line_") != 0 )
  172. return;
  173. var sel = $(svg).find("#"+id);
  174. var selNode = $(sel).get(0);
  175. if( !selNode.isHidden )
  176. return;
  177. setVisibility(id, 1);
  178. //$(svg).find('[line_id="'+id+'"]').attr("opacity",1);
  179. } );
  180. }
  181. //////////////////////////////////// value display
  182. if(arg == 5) {
  183. var hadShowVal = selNode.showVal;
  184. showValOff();
  185. if(!hadShowVal) {
  186. selNode.showVal = true;
  187. $(svg).mousemove(mousemove);
  188. svgNode.par = par = svg_prepareHash(selNode);
  189. par.circle =
  190. $(svg).get(0).ownerDocument.createElementNS(svgNS, "circle");
  191. $(par.circle).attr("id", "svgmarker").attr("r", "8");
  192. $(svg).append(par.circle);
  193. par.div = $('<div id="svgmarker">');
  194. par.divoffY = $(embed ? embed : svg).offset().top -
  195. $("#content").offset().top-50;
  196. $("#content").append(par.div);
  197. var pl = selNode[arrName];
  198. if(!pl)
  199. selNode[arrName] = pl = new myPathSegList(selNode);
  200. if(pl.numberOfItems > 2)
  201. mousemove({pageX:pl.getItem(pl.numberOfItems-2).x});
  202. }
  203. }
  204. if( arg >= 2 && arg <= 4 ) {
  205. var hidden = $(svg).find(".hidden");
  206. if( lines.length - hidden.length == 1 ) {
  207. $(tl).attr("hiddentitle", $(tl).text());
  208. if($(sel).attr(pn) != null)
  209. $(tl).text($(label).attr("title"));
  210. } else if( $(tl).attr("hiddentitle") ) {
  211. $(tl).text($(tl).attr("hiddentitle"));
  212. $(tl).removeAttr("hiddentitle")
  213. }
  214. }
  215. }, embed);
  216. function pad0(v) { return (v < 10 ? '0'+v :v); }
  217. function
  218. mousemove(e)
  219. {
  220. var xRaw = e.pageX, pl = selNode[arrName], l = pl.numberOfItems, i1;
  221. if(!embed)
  222. xRaw -= $(svg).offset().left;
  223. for(i1=0; i1<l; i1++)
  224. if(pl.getItem(i1).x > xRaw)
  225. break;
  226. if(i1==l || i1==0)
  227. return;
  228. var pp=pl.getItem(i1-1), pn=pl.getItem(i1);
  229. var xR = (xRaw-pp.x)/(pn.x-pp.x); // Compute interim values
  230. var yRaw = pp.y+xR*(pn.y-pp.y);
  231. var y = (((par.y_h-yRaw)/par.y_mul)+par.y_min);
  232. if( par.log_scale ) {
  233. y *= par.log_scale;
  234. y = Math.pow(10,y) - 1;
  235. }
  236. y = y.toFixed(par.decimals);
  237. if( par.x_mul ) {
  238. ts = (((xRaw-par.x_min)/par.x_mul)+par.x_off).toFixed(par.decimals);
  239. } else {
  240. var d = new Date((((xRaw-par.x_min)/par.t_mul)+par.x_off) * 1000), ts;
  241. if(par.t_mul < 0.0001) { // Year
  242. ts =(pad0(d.getMonth()+1))+"."+pad0(d.getDate()+"."+(d.getYear()+1900));
  243. } else if(par.t_mul < 0.001) { // Month
  244. ts = (pad0(d.getMonth()+1))+"."+pad0(d.getDate())+
  245. ". "+pad0(d.getHours())+":"+pad0(d.getMinutes());
  246. } else {
  247. ts = pad0(d.getHours())+":"+pad0(d.getMinutes());
  248. }
  249. }
  250. $(par.circle).attr("cx", xRaw).attr("cy", yRaw);
  251. var yd = Math.floor((yRaw+par.divoffY) / 20)*20;
  252. $(par.div).html(ts+" "+y)
  253. .css({ left:xRaw-20, top:yd });
  254. }
  255. function
  256. setVisibility(id,visible)
  257. {
  258. var sel = $(svg).find("#"+id);
  259. var selNode = $(sel).get(0);
  260. var currval = visible?1:0;
  261. var h = parseFloat(sel.attr("y_h"));
  262. //sel.attr("transform", "translate(0,"+h*(1-currval)+") "
  263. //+ "scale(1,"+currval+")");
  264. if( !visible
  265. && selNode.showVal )
  266. showValOff();
  267. animateVisibility(sel,visible?0:1, visible?1:0);
  268. if( visible ) {
  269. delete(selNode.isHidden);
  270. sel.attr("class", sel.attr("class").replace(/ hidden/, "" ) );
  271. } else {
  272. selNode.isHidden = true;
  273. sel.attr("class", sel.attr("class") + " hidden" );
  274. }
  275. }
  276. function
  277. animateVisibility(sel, currval, maxval)
  278. {
  279. var h = parseFloat(sel.attr("y_h"));
  280. sel.attr("transform", "translate(0,"+h*(1-currval)+") "+
  281. "scale(1,"+currval+")");
  282. if(currval != maxval) {
  283. currval += (currval<maxval ? 0.02 : -0.02);
  284. currval = Math.round(currval*100)/100;
  285. setTimeout(function(){ animateVisibility(sel,currval,maxval) }, 10);
  286. }
  287. }
  288. }
  289. function
  290. svg_init_one(embed, svg)
  291. {
  292. var sid = $(svg).attr("id");
  293. if(svg_initialized[sid])
  294. return;
  295. svg_initialized[sid] = true;
  296. $("text.legend", svg).click(function(e){sv_menu(e, embed)});
  297. for(var i in svgCallback)
  298. svgCallback[i](svg);
  299. }
  300. function
  301. svg_init(par) // also called directly from perl, in race condition
  302. {
  303. $("embed").each(function(){
  304. var e = this;
  305. var src = $(e).attr("src");
  306. var ed = FW_getSVG(e);
  307. if(!src || src.indexOf("SVG_showLog") < 0 || !ed)
  308. return;
  309. var sTag = $("svg", ed)[0]; // "not well-formed" warning in FireFox
  310. if((par && $(sTag).attr("id") != par))
  311. return;
  312. svg_init_one(e, sTag);
  313. });
  314. }
  315. $(document).ready(function(){
  316. if(svgCounter++ > 0) // if svg.js is included twice, e.g. by Dashboard
  317. return;
  318. svg_init(); // <embed><svg>
  319. svg_initDb(function(){
  320. svg_load("svg_pastedata", function(val) {svg_pastedata = val} );
  321. });
  322. $("svg[id]").each(function(){ // <svg> (direct)
  323. if($(this).attr("id").indexOf("SVGPLOT") == 0)
  324. svg_init_one(undefined, this);
  325. });
  326. });
  327. // longpollSVG code below
  328. function
  329. FW_svgUpdateDevs(devs)
  330. {
  331. // if matches, refresh the SVG by removing and readding the embed tag
  332. var embArr = document.getElementsByTagName("embed");
  333. for(var i = 0; i < embArr.length; i++) {
  334. var svg = FW_getSVG(embArr[i]);
  335. if(!svg || !svg.firstChild || !svg.firstChild.nextSibling)
  336. continue;
  337. if(svg.contentType != "image/svg+xml" &&
  338. typeof embArr[i].getSVGDocument != "function")
  339. continue;
  340. svg = svg.firstChild.nextSibling;
  341. var flog = svg.getAttribute("flog");
  342. if(!flog)
  343. continue;
  344. flog = flog.replace(/\\x3a/g, ".");
  345. log("longpollSVG filter:"+flog);
  346. for(var j=0; j < devs.length; j++) {
  347. var d = devs[j];
  348. var ev = d[0]+":"+d[1];
  349. if(ev.match(flog)) {
  350. log("longpollSVG: reload SVG");
  351. delete(svg_initialized[$(svg).attr("id")]);
  352. var e = embArr[i];
  353. var newE = document.createElement("embed");
  354. for(var k=0; k<e.attributes.length; k++)
  355. newE.setAttribute(e.attributes[k].name, e.attributes[k].value);
  356. e.parentNode.insertBefore(newE, e);
  357. e.parentNode.removeChild(e);
  358. break;
  359. }
  360. }
  361. }
  362. }
  363. FW_widgets.SVG = { updateDevs:FW_svgUpdateDevs };