svg.js 12 KB

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