svg.js 12 KB

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