98_SVG.pm 93 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923
  1. ##############################################
  2. # $Id: 98_SVG.pm 17457 2018-10-04 12:33:53Z rudolfkoenig $
  3. package main;
  4. use strict;
  5. use warnings;
  6. use POSIX;
  7. use HttpUtils;
  8. #use Devel::Size qw(size total_size);
  9. # This block is only needed when SVG is loaded bevore FHEMWEB
  10. sub FW_pO(@);
  11. use vars qw($FW_ME); # webname (default is fhem), needed by 97_GROUP
  12. use vars qw($FW_RET); # Returned data (html)
  13. use vars qw($FW_RETTYPE); # image/png or the like
  14. use vars qw($FW_cssdir); # css directory
  15. use vars qw($FW_detail); # currently selected device for detail view
  16. use vars qw($FW_dir); # base directory for web server
  17. use vars qw($FW_gplotdir);# gplot directory for web server: the first
  18. use vars qw($FW_plotmode);# Global plot mode (WEB attribute), used by SVG
  19. use vars qw($FW_plotsize);# Global plot size (WEB attribute), used by SVG
  20. use vars qw($FW_room); # currently selected room
  21. use vars qw($FW_subdir); # Sub-path in URL, used by FLOORPLAN/weblink
  22. use vars qw($FW_wname); # Web instance
  23. use vars qw(%FW_hiddenroom); # hash of hidden rooms, used by weblink
  24. use vars qw(%FW_pos); # scroll position
  25. use vars qw(%FW_webArgs); # all arguments specified in the GET
  26. use vars qw($FW_formmethod);
  27. use vars qw($FW_userAgent);
  28. use vars qw($FW_hiddenroom);
  29. use vars qw($FW_CSRF);
  30. my $SVG_RET; # Returned data (SVG)
  31. sub SVG_calcOffsets($$);
  32. sub SVG_doround($$$);
  33. sub SVG_fmtTime($$);
  34. sub SVG_pO($);
  35. sub SVG_readgplotfile($$$);
  36. sub SVG_render($$$$$$$$$$);
  37. sub SVG_showLog($);
  38. sub SVG_substcfg($$$$$$);
  39. sub SVG_time_align($$);
  40. sub SVG_time_to_sec($);
  41. sub SVG_openFile($$$);
  42. sub SVG_doShowLog($$$$;$);
  43. sub SVG_getData($$$$$);
  44. sub SVG_sel($$$;$$);
  45. sub SVG_getControlPoints($);
  46. sub SVG_calcControlPoints($$$$$$);
  47. my %SVG_devs; # hash of from/to entries per device
  48. #####################################
  49. sub
  50. SVG_Initialize($)
  51. {
  52. my ($hash) = @_;
  53. $hash->{DefFn} = "SVG_Define";
  54. no warnings 'qw';
  55. my @attrList = qw(
  56. captionLeft:1,0"
  57. captionPos:right,left,auto
  58. endPlotNow
  59. endPlotToday
  60. fixedoffset
  61. fixedrange
  62. label
  63. nrAxis
  64. plotWeekStartDay:0,1,2,3,4,5,6
  65. plotfunction
  66. plotmode:gnuplot-scroll,gnuplot-scroll-svg,SVG
  67. plotsize
  68. plotReplace:textField-long
  69. startDate
  70. title
  71. );
  72. use warnings 'qw';
  73. $hash->{AttrList} = join(" ", @attrList);
  74. $hash->{SetFn} = "SVG_Set";
  75. $hash->{AttrFn} = "SVG_AttrFn";
  76. $hash->{RenameFn} = "SVG_Rename";
  77. $hash->{FW_summaryFn} = "SVG_FwFn";
  78. $hash->{FW_detailFn} = "SVG_FwFn";
  79. $hash->{FW_atPageEnd} = 1;
  80. $data{FWEXT}{"/SVG_WriteGplot"}{CONTENTFUNC} = "SVG_WriteGplot";
  81. $data{FWEXT}{"/SVG_showLog"}{FUNC} = "SVG_showLog";
  82. $data{FWEXT}{"/SVG_showLog"}{FORKABLE} = 1;
  83. }
  84. #####################################
  85. sub
  86. SVG_Define($$)
  87. {
  88. my ($hash, $def) = @_;
  89. my ($name, $type, $arg) = split("[ \t]+", $def, 3);
  90. if(!$arg ||
  91. !($arg =~ m/^(.*):(.*):(.*)$/ || $arg =~ m/^(.*):(.*)$/)) {
  92. return "Usage: define <name> SVG <logdevice>:<gnuplot-file>:<logfile>";
  93. }
  94. $hash->{LOGDEVICE} = $1;
  95. $hash->{GPLOTFILE} = $2;
  96. $hash->{LOGFILE} = ($3 ? $3 : "CURRENT");
  97. $hash->{STATE} = "initialized";
  98. $hash->{LOGDEVICE} =~ s/^fileplot //; # Autocreate bug.
  99. notifyRegexpChanged($hash, "global");
  100. return undef;
  101. }
  102. ##################
  103. sub
  104. SVG_Set($@)
  105. {
  106. my ($hash, @a) = @_;
  107. my $me = $hash->{NAME};
  108. return "no set argument specified" if(int(@a) < 2);
  109. my $cmd = $a[1];
  110. return "Unknown argument $cmd, choose one of copyGplotFile:noArg"
  111. if($cmd ne "copyGplotFile");
  112. my $srcName = "$FW_gplotdir/$hash->{GPLOTFILE}.gplot";
  113. my ($og,$od) = ($hash->{GPLOTFILE}, $hash->{DEF});
  114. $hash->{GPLOTFILE} = $hash->{NAME};
  115. my $dstName = "$FW_gplotdir/$hash->{GPLOTFILE}.gplot";
  116. return "this is already a unique gplot file" if($srcName eq $dstName);
  117. $hash->{DEF} = $hash->{LOGDEVICE} . ":".
  118. $hash->{GPLOTFILE} . ":".
  119. $hash->{LOGFILE};
  120. my ($err,@rows) = FileRead($srcName);
  121. return $err if($err);
  122. $err = FileWrite($dstName, @rows);
  123. if($err) {
  124. $hash->{DEF} = $od; $hash->{GPLOTFILE} = $og;
  125. } else {
  126. addStructChange("modify", $me, "$me $hash->{DEF}")
  127. }
  128. return $err;
  129. }
  130. sub
  131. SVG_AttrFn(@)
  132. {
  133. my ($cmd,$name,$aName,$aVal) = @_;
  134. if($aName eq "captionLeft" && $cmd eq "set") {
  135. my $dir = (!defined($aVal) || $aVal) ? "left" : "right";
  136. AnalyzeCommand(undef, "attr $name captionPos $dir");
  137. return "attr $name captionLeft converted to attr $name captionPos $dir";
  138. }
  139. return undef;
  140. }
  141. sub
  142. SVG_Attr($$$$)
  143. {
  144. my ($parent, $dev, $attr, $default) = @_;
  145. my $val = AttrVal($dev, $attr, undef);
  146. return $val if(defined($val));
  147. return AttrVal($parent, $attr, $default);
  148. }
  149. sub
  150. SVG_Rename($$)
  151. {
  152. my ($new, $old) = @_;
  153. my $hash = $defs{$new};
  154. return if($hash->{GPLOTFILE} ne $old);
  155. SVG_Set($hash, $new, "copyGplotFile"); # Forum #59786
  156. }
  157. sub
  158. SVG_getplotsize($)
  159. {
  160. my ($d) = @_;
  161. return $FW_webArgs{plotsize} ?
  162. $FW_webArgs{plotsize} : AttrVal($d,"plotsize",$FW_plotsize);
  163. }
  164. sub
  165. SVG_isEmbed($)
  166. {
  167. return AttrVal($FW_wname, "plotEmbed", 0);
  168. }
  169. sub
  170. SVG_log10($)
  171. {
  172. my ($n) = @_;
  173. return 0.0000000001 if( $n <= 0 );
  174. return log(1+$n)/log(10);
  175. }
  176. ##################
  177. sub
  178. SVG_FwFn($$$$)
  179. {
  180. my ($FW_wname, $d, $room, $pageHash) = @_; # pageHash is set for summaryFn.
  181. my $hash = $defs{$d};
  182. my $ret = "";
  183. my $isFirst = (!$pageHash || !$pageHash->{svgLoaded});
  184. $isFirst = 0 if($pageHash && $pageHash->{svgIdx} && $pageHash->{svgIdx} != 1);
  185. $pageHash->{svgLoaded} = 1 if($pageHash);
  186. $ret .= "<script type='text/javascript' src='$FW_ME/pgm2/svg.js'></script>"
  187. if($isFirst);
  188. # plots navigation buttons
  189. my $pm = AttrVal($d,"plotmode",$FW_plotmode);
  190. if($isFirst) {
  191. $ret .= '<div class="SVGlabel" data-name="svgZoomControl">';
  192. $ret .= SVG_zoomLink("zoom=-1", "Zoom-in", "zoom in");
  193. $ret .= SVG_zoomLink("zoom=1", "Zoom-out","zoom out");
  194. $ret .= SVG_zoomLink("off=-1", "Prev", "prev");
  195. $ret .= SVG_zoomLink("off=1", "Next", "next");
  196. $ret .= '</div>';
  197. $pageHash->{buttons} = 1 if($pageHash);
  198. $ret .= "<br>";
  199. }
  200. my $arg="$FW_ME/SVG_showLog?dev=$d".
  201. "&logdev=$hash->{LOGDEVICE}".
  202. "&gplotfile=$hash->{GPLOTFILE}".
  203. "&logfile=$hash->{LOGFILE}".
  204. "&pos=" . join(";", map {"$_=$FW_pos{$_}"} keys %FW_pos);
  205. if($pm eq "SVG") {
  206. $ret .= "<div class=\"SVGplot SVG_$d\">";
  207. if(SVG_isEmbed($FW_wname)) {
  208. my ($w, $h) = split(",", SVG_getplotsize($d));
  209. $ret .= "<embed src=\"$arg\" type=\"image/svg+xml\" " .
  210. "width=\"$w\" height=\"$h\" name=\"$d\"/>\n";
  211. } else {
  212. my $oret=$FW_RET; $FW_RET="";
  213. my ($type, $data) = SVG_doShowLog($d, $hash->{LOGDEVICE},
  214. $hash->{GPLOTFILE}, $hash->{LOGFILE}, 1);
  215. $FW_RET=$oret;
  216. $ret .= $data;
  217. }
  218. $ret .= "</div>";
  219. } elsif($pm eq "gnuplot-scroll") {
  220. $ret .= "<img src=\"$arg\"/>";
  221. } elsif($pm eq "gnuplot-scroll-svg") {
  222. $ret .= "<object type=\"image/svg+xml\" ".
  223. "data=\"$arg\">Your browser does not support SVG.</object>";
  224. }
  225. if(!$pageHash) {
  226. if($FW_plotmode eq "SVG") {
  227. $ret .= SVG_PEdit($FW_wname,$d,$room,$pageHash) . "<br>";
  228. }
  229. } else {
  230. if(!AttrVal($d, "group", "") && !$FW_subdir) {
  231. my $alias = AttrVal($d, "alias", $d);
  232. my $clAdd = "\" data-name=\"$d";
  233. $clAdd .= "\" style=\"display:none;" if($FW_hiddenroom{detail});
  234. $ret .= FW_pH("detail=$d", $alias, 0, "SVGlabel SVG_$d $clAdd", 1, 0);
  235. $ret .= "<br>";
  236. }
  237. }
  238. return $ret;
  239. }
  240. sub
  241. SVG_cb($$$)
  242. {
  243. my ($v,$t,$c) = @_;
  244. $c = ($c ? " checked" : "");
  245. return "$t&nbsp;<input type=\"checkbox\" name=\"$v\" value=\"$v\"$c>";
  246. }
  247. sub
  248. SVG_txt($$$$)
  249. {
  250. my ($v,$t,$c,$sz) = @_;
  251. $c = "" if(!defined($c));
  252. $c =~ s/&/\&amp;/g;
  253. $c =~ s/"/\&quot;/g;
  254. return "$t&nbsp;<input type=\"text\" name=\"$v\" size=\"$sz\" ".
  255. "value=\"$c\"/>";
  256. }
  257. sub
  258. SVG_sel($$$;$$)
  259. {
  260. my ($v,$l,$c,$fnData,$class) = @_;
  261. my @al = split(",",$l);
  262. $c =~ s/\\x3a/:/g if($c);
  263. return FW_select(undef,$v,\@al,$c, $class?$class:"set", $fnData);
  264. }
  265. ############################
  266. # gnuplot file "editor"
  267. sub
  268. SVG_PEdit($$$$)
  269. {
  270. my ($FW_wname,$d,$room,$pageHash) = @_;
  271. my $pe = AttrVal($FW_wname, "ploteditor", "always");
  272. return "" if( $pe eq 'never' );
  273. my $gpf = $defs{$d}{GPLOTFILE};
  274. my $gpfEsc = $gpf;
  275. $gpfEsc =~ s,\.,\\\\.,g;
  276. my $link = "$FW_ME?cmd=style edit $gpf.gplot".
  277. (configDBUsed() ? " configDB" : "").$FW_CSRF;
  278. my $gp = "$FW_gplotdir/$gpf.gplot";
  279. my $pm = AttrVal($d,"plotmode",$FW_plotmode);
  280. my ($err, $cfg, $plot, $srcDesc) = SVG_readgplotfile($d, $gp, $pm);
  281. my %conf = SVG_digestConf($cfg, $plot);
  282. my $ret = "<br>";
  283. my $pestyle = "";
  284. if( $pe eq 'onClick' ) {
  285. my $pgm = "Javascript:" .
  286. "s=document.getElementById('pedit').style;".
  287. "s.display = s.display=='none' ? 'block' : 'none';".
  288. "s=document.getElementById('pdisp').style;".
  289. "s.display = s.display=='none' ? 'block' : 'none';";
  290. $ret .= "<a id=\"pdisp\" style=\"cursor:pointer\" onClick=\"$pgm\">".
  291. "Show Plot Editor</a>";
  292. $pestyle = 'style="display:none"';
  293. }
  294. $ret.="<form $pestyle id='pedit' method='$FW_formmethod' autocomplete='off' ".
  295. "action='$FW_ME/SVG_WriteGplot'>";
  296. $ret .= FW_hidden("detail", $d); # go to detail after save
  297. if(defined($FW_pos{zoom}) && defined($FW_pos{off})) { # for showData
  298. $ret .= FW_hidden("pos", "zoom=$FW_pos{zoom};off=$FW_pos{off}");
  299. }
  300. $ret .= "<div class='makeTable wide'><span>Plot Editor</span>";
  301. $ret .= "<table class=\"block wide plotEditor\">";
  302. $ret .= "<tr class=\"even\">";
  303. $ret .= "<td>Plot title</td>";
  304. $ret .= "<td>".SVG_txt("title", "", $conf{title}, 32)."</td>";
  305. $ret .= "</tr>";
  306. $ret .= "<tr class=\"odd\">";
  307. $ret .= "<td>Y-Axis label</td>";
  308. $conf{ylabel} =~ s/"//g if($conf{ylabel});
  309. $ret .= "<td>".SVG_txt("ylabel", "left", $conf{ylabel}, 16)."</td>";
  310. $conf{y2label} =~ s/"//g if($conf{y2label});
  311. $ret .= "<td>".SVG_txt("y2label","right", $conf{y2label}, 16)."</td>";
  312. $ret .= "</tr>";
  313. $ret .= "<tr class=\"even\">";
  314. $ret .= "<td>Grid aligned</td>";
  315. $ret .= "<td>".SVG_cb("gridy", "left", $conf{hasygrid})."</td>";
  316. $ret .= "<td>".SVG_cb("gridy2","right",$conf{hasy2grid})."</td>";
  317. $ret .= "</tr>";
  318. $ret .= "<tr class=\"odd\">";
  319. $ret .= "<td>Range as [min:max]</td>";
  320. $ret .= "<td>".SVG_txt("yrange", "left", $conf{yrange}, 16);
  321. $ret .= "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;".
  322. SVG_cb("yscale", "log", $conf{yscale})."</td>";
  323. $ret .= "<td>".SVG_txt("y2range", "right", $conf{y2range}, 16);
  324. $ret .= "&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;".
  325. SVG_cb("y2scale", "log", $conf{y2scale})."</td>";
  326. $ret .= "</tr>";
  327. if( $conf{xrange} ) {
  328. $ret .= "<tr class=\"odd\"><td/><td>";
  329. $ret .= SVG_txt("xrange", "x&nbsp;&nbsp;&nbsp;",$conf{xrange},16)."</td>";
  330. $ret .= "<td/></tr>";
  331. }
  332. $ret .= "<tr class=\"even\">";
  333. $ret .= "<td>Tics as (\"Txt\" val, ...)</td>";
  334. $ret .= "<td>".SVG_txt("ytics", "left", $conf{ytics}, 16)."</td>";
  335. $ret .= "<td>".SVG_txt("y2tics","right", $conf{y2tics}, 16)."</td>";
  336. $ret .= "</tr>";
  337. if( $conf{xtics} ) {
  338. $ret .= "<tr class=\"even\"><td/><td>";
  339. $ret .= SVG_txt("xtics", "x&nbsp;&nbsp;&nbsp;", $conf{xtics}, 16)."</td>";
  340. $ret .= "<td/></tr>";
  341. }
  342. my ($desc, $cnt) = ("Spec", 0);
  343. my (@srcHtml, @paramHtml, @exampleHtml, @revIdx);
  344. my @srcNames = grep { $modules{$defs{$_}{TYPE}}{SVG_sampleDataFn} }
  345. sort keys %defs;
  346. foreach my $src (@{$srcDesc->{order}}) {
  347. my $lmax = $srcDesc->{src}{$src}{idx}+1;
  348. my $fn = $modules{$defs{$src}{TYPE}}{SVG_sampleDataFn};
  349. my @argArr = split(" ", $srcDesc->{src}{$src}{arg});
  350. if($fn) {
  351. no strict "refs";
  352. my ($ldesc, $paramHtml, $example) =
  353. &{$fn}($src, \@argArr, $lmax,\%conf, $FW_wname);
  354. use strict "refs";
  355. $desc = $ldesc;
  356. push @paramHtml, @{$paramHtml} if($paramHtml);
  357. map { push @exampleHtml, $example } (0..$lmax-1);
  358. } else {
  359. push @paramHtml, map { SVG_txt("par_${_}_0","",$_,20) } @argArr;
  360. map { push @exampleHtml, "" } (0..$lmax-1);
  361. }
  362. push @srcHtml,
  363. map { FW_select(undef,"src_$_",\@srcNames,$src,"svgSrc");} (0..$lmax-1);
  364. map { push @revIdx,$srcDesc->{rev}{$cnt}{$_}; } (0..$lmax-1);
  365. $cnt++;
  366. }
  367. # Last, empty line
  368. push @revIdx,int(@revIdx);
  369. push @srcHtml, FW_select(undef,"src_".int(@srcHtml),\@srcNames,"","svgSrc");
  370. push @paramHtml, (@paramHtml==0 ?
  371. SVG_txt("par_0_0", "", "parameter", 10) : $paramHtml[0]);
  372. push @exampleHtml, "Set the label and 'Write .gplot file' first in order to ".
  373. "get example data and correct parameter choice";
  374. my @lineStyles;
  375. if(SVG_openFile($FW_cssdir,
  376. AttrVal($FW_wname,"stylesheetPrefix",""), "svg_style.css")) {
  377. map { push(@lineStyles,$1) if($_ =~ m/^\.SVGplot.(l[^{ ]*)/) } <FH>; # }
  378. close(FH);
  379. }
  380. $ret .= "<tr class=\"odd\"><td>Diagram label, Source</td>";
  381. $ret .= "<td>$desc</td>";
  382. $ret .=" <td>Y-Axis,Plot-Type,Style,Width</td></tr>";
  383. my ($r, $example, @output) = (0, "");
  384. my $max = int(@srcHtml);
  385. for($r=0; $r < $max; $r++) {
  386. my $idx = $revIdx[$r];
  387. $example .= "<div class='ex ex_$idx' style='display:".($idx?"none":"block").
  388. "'>$exampleHtml[$r]</div>";
  389. my $o = "<tr row='$idx' class=\"".(($r&1)?"odd":"even")."\"><td>";
  390. $o .= SVG_txt("title_$idx", "", !$conf{lTitle}[$idx]&&$idx<($max-1) ?
  391. "notitle" : $conf{lTitle}[$idx], 12);
  392. my $sh = $srcHtml[$r]; $sh =~ s/src_\d+/src_$idx/g;
  393. $o .= $sh;
  394. $o .= "</td><td>";
  395. my $ph = $paramHtml[$r]; $ph =~ s/par_\d+_/par_${idx}_/g;
  396. $o .= $ph;
  397. $o .= "</td><td>";
  398. my $v = $conf{lAxis}[$idx];
  399. my $sel = ($v && $v eq "x1y1") ? "left" : "right";
  400. $o .= SVG_sel("axes_${idx}", "left,right,left log,right log", $sel );
  401. $o .= SVG_sel("type_${idx}",
  402. "lines,points,steps,fsteps,histeps,bars,ibars,".
  403. "horizontalLineFrom,horizontalLineTo,".
  404. "cubic,quadratic,quadraticSmooth",
  405. $conf{lType}[$idx]);
  406. my $ls = $conf{lStyle}[$idx];
  407. if($ls) {
  408. $ls =~ s/class=.* //g;
  409. $ls =~ s/"//g;
  410. }
  411. $o .= SVG_sel("style_$idx", join(",", @lineStyles), $ls);
  412. my $lw = $conf{lWidth}[$idx];
  413. if($lw) {
  414. $lw =~ s/.*stroke-width://g;
  415. $lw =~ s/"//g;
  416. }
  417. $o .= SVG_sel("width_$idx", "0.2,0.5,1,1.5,2,3,4,8,12,16,24",($lw ? $lw:1));
  418. $o .= "</td></tr>";
  419. $output[$idx] = $o;
  420. }
  421. $ret .= join("", @output);
  422. $ret .= "<tr class=\"".(($r++&1)?"odd":"even")."\"><td colspan=\"3\">";
  423. $ret .= "Example lines for input:<br>$example</td></tr>";
  424. my %gpf;
  425. map {
  426. $gpf{$defs{$_}{GPLOTFILE}}{$_} = 1 if($defs{$_}{TYPE} eq "SVG");
  427. } sort keys %defs;
  428. if(int(keys %{$gpf{$defs{$d}{GPLOTFILE}}}) > 1) {
  429. $ret .= "<tr class='".(($r++&1)?"odd":"even")."'><td colspan='3'>".
  430. "<b>Note:</b>".
  431. "The .gplot file ($defs{$d}{GPLOTFILE}) is used by multiple SVG ".
  432. "devices (".join(",", sort keys %{$gpf{$defs{$d}{GPLOTFILE}}})."), ".
  433. "writing probably will corrupt the other SVGs. ".
  434. "Remedy: execute set $d copyGplotFile".
  435. "</td></tr>";
  436. }
  437. $ret .= "<tr class=\"".(($r++&1)?"odd":"even")."\"><td colspan=\"3\">";
  438. $ret .= FW_submit("submit", "Write .gplot file")."&nbsp;".
  439. FW_submit("showFileLogData", "Show preprocessed input").
  440. "</td></tr>";
  441. $ret .= "</table></div></form>";
  442. my $sl = "$FW_ME/SVG_WriteGplot?detail=$d&showFileLogData=1";
  443. if(defined($FW_pos{zoom}) && defined($FW_pos{off})) {
  444. $sl .= "&pos=zoom=$FW_pos{zoom};off=$FW_pos{off}";
  445. }
  446. $ret .= <<'EOF';
  447. <script type="text/javascript">
  448. var sel = "table.plotEditor tr[row] ";
  449. $(sel+"input,"+sel+"select").focus(function(){
  450. var row = $(this).closest("tr").attr("row");
  451. $("table.plotEditor div.ex").css("display","none");
  452. $("table.plotEditor div.ex_"+row).css("display","block");
  453. });
  454. $("table.plotEditor input[name=title_0]").focus();
  455. $("table.plotEditor input[name=showFileLogData]").click(function(e){
  456. e.preventDefault();
  457. EOF
  458. $ret .=
  459. "FW_cmd('$sl', function(arg){" .<<"EOF";
  460. FW_okDialog(arg);
  461. });
  462. });
  463. setTimeout(function(){
  464. \$("table.internals div[informid=$gpfEsc-GPLOTFILE]")
  465. .html("<a href='$link'>$gpf</a>");
  466. }, 10);
  467. </script>
  468. EOF
  469. return $ret;
  470. }
  471. ##################
  472. # Generate the zoom and scroll images with links if appropriate
  473. sub
  474. SVG_zoomLink($$$)
  475. {
  476. my ($cmd, $img, $alt) = @_;
  477. my $prf;
  478. $cmd =~ m/^(.*);([^;]*)$/;
  479. if($2) {
  480. ($prf, $cmd) = ($1, $2);
  481. $prf =~ s/&pos=.*//;
  482. }
  483. my ($d,$off) = split("=", $cmd, 2);
  484. my $val = $FW_pos{$d};
  485. $cmd = ($FW_detail ? "detail=".urlEncode($FW_detail):
  486. ($prf ? $prf : "room=".urlEncode($FW_room))) . "&pos=";
  487. if($d eq "zoom") {
  488. my $n = 0;
  489. my @FW_zoom=("hour","qday","day","week","month","year","10years","20years");
  490. my %FW_zoom = map { $_, $n++ } @FW_zoom;
  491. $val = "day" if(!$val);
  492. $val = $FW_zoom{$val};
  493. return "" if(!defined($val) || $val+$off < 0 || $val+$off >= int(@FW_zoom));
  494. $val = $FW_zoom[$val+$off];
  495. return "" if(!$val);
  496. # Approximation of the next offset.
  497. my $w_off = $FW_pos{off};
  498. $w_off = 0 if(!$w_off);
  499. if ($val eq "hour") {
  500. $w_off = $w_off*6;
  501. } elsif($val eq "qday") {
  502. $w_off = ($off < 0) ? $w_off*4 : int($w_off/6);
  503. } elsif($val eq "day") {
  504. $w_off = ($off < 0) ? $w_off*7 : int($w_off/4);
  505. } elsif($val eq "week") {
  506. $w_off = ($off < 0) ? $w_off*4 : int($w_off/7);
  507. } elsif($val eq "month") {
  508. $w_off = ($off < 0) ? $w_off*12: int($w_off/4);
  509. } elsif($val eq "year") {
  510. $w_off = int($w_off/12);
  511. } elsif($val eq "10years") {
  512. $w_off = int($w_off/120);
  513. } elsif($val eq "20years") {
  514. $w_off = int($w_off/240);
  515. }
  516. $cmd .= "zoom=$val;off=$w_off";
  517. } else {
  518. return "" if((!$val && $off > 0) || ($val && $val+$off > 0)); # no future
  519. $off=($val ? $val+$off : $off);
  520. my $zoom=$FW_pos{zoom};
  521. $zoom = 0 if(!$zoom);
  522. $cmd .= "zoom=$zoom;off=$off";
  523. }
  524. return "&nbsp;&nbsp;".FW_pHPlain("$cmd", FW_makeImage($img, $alt));
  525. }
  526. # Debugging: show the data received from GET
  527. sub
  528. SVG_showData()
  529. {
  530. my $wl = $FW_webArgs{detail};
  531. my $hash = $defs{$wl};
  532. my ($d, $gplotfile, $file) = split(":", $hash->{DEF});
  533. $gplotfile = "$FW_gplotdir/$gplotfile.gplot";
  534. my $pm = AttrVal($d,"plotmode",$FW_plotmode);
  535. my ($err, $cfg, $plot, $srcDesc) = SVG_readgplotfile($wl, $gplotfile, $pm);
  536. if($err) {
  537. $FW_RET=$err;
  538. return 1;
  539. }
  540. SVG_calcOffsets($d, $wl);
  541. $FW_RET = SVG_getData($wl,$SVG_devs{$d}{from}, $SVG_devs{$d}{to}, $srcDesc,1);
  542. $FW_RET =~ s/\n/<br>/gs;
  543. return 1;
  544. }
  545. sub
  546. SVG_WriteGplot($)
  547. {
  548. my ($arg) = @_;
  549. FW_digestCgi($arg);
  550. return if($FW_hiddenroom{detail});
  551. return SVG_showData() if($FW_webArgs{showFileLogData});
  552. if(!defined($FW_webArgs{par_0_0})) {
  553. $FW_RET .=
  554. '<div id="errmsg">'.
  555. "missing data in logfile: won't write incomplete .gplot definition".
  556. '</div>';
  557. return 0;
  558. }
  559. my $maxLines = 0;
  560. foreach my $i (keys %FW_webArgs) {
  561. next if($i !~ m/^title_(.*)$/);
  562. $maxLines = $1 if($1 > $maxLines);
  563. }
  564. my @rows;
  565. push @rows, "# Created by FHEM/98_SVG.pm, ".TimeNow();
  566. push @rows, "set terminal png transparent size <SIZE> crop";
  567. push @rows, "set output '<OUT>.png'";
  568. push @rows, "set xdata time";
  569. push @rows, "set timefmt \"%Y-%m-%d_%H:%M:%S\"";
  570. push @rows, "set xlabel \" \"";
  571. push @rows, "set title '$FW_webArgs{title}'";
  572. push @rows, "set xtics ".$FW_webArgs{xtics} if($FW_webArgs{xtics});
  573. push @rows, "set ytics ".$FW_webArgs{ytics};
  574. push @rows, "set y2tics ".$FW_webArgs{y2tics};
  575. push @rows, "set grid".($FW_webArgs{gridy} ? " ytics" :"").
  576. ($FW_webArgs{gridy2} ? " y2tics":"")."";
  577. push @rows, "set ylabel \"$FW_webArgs{ylabel}\"";
  578. push @rows, "set y2label \"$FW_webArgs{y2label}\"";
  579. push @rows, "set xrange $FW_webArgs{xrange}" if($FW_webArgs{xrange});
  580. push @rows, "set yrange $FW_webArgs{yrange}" if($FW_webArgs{yrange});
  581. push @rows, "set y2range $FW_webArgs{y2range}" if($FW_webArgs{y2range});
  582. push @rows, "set yscale log" if($FW_webArgs{yscale});
  583. push @rows, "set y2scale log" if($FW_webArgs{y2scale});
  584. push @rows, "";
  585. my @plot;
  586. for(my $i=0; $i <= $maxLines; $i++) {
  587. next if(!$FW_webArgs{"title_$i"});
  588. my $prf = "par_${i}_";
  589. my @v = map {$FW_webArgs{"$prf$_"}}
  590. grep {defined($FW_webArgs{"$prf$_"})} (0..9);
  591. my $r = @v > 1 ?
  592. join(":", map { $v[$_] =~ s/:/\\x3a/g if($_<$#v); $v[$_] } 0..$#v) :
  593. $v[0];
  594. my $src = $FW_webArgs{"src_$i"};
  595. push @rows, "#$src $r";
  596. push @plot, "\"<IN>\" using 1:2 axes ".
  597. ($FW_webArgs{"axes_$i"} eq "right" ? "x1y2" : "x1y1").
  598. ($FW_webArgs{"title_$i"} eq "notitle" ? " notitle" :
  599. " title '".$FW_webArgs{"title_$i"} ."'").
  600. " ls " .$FW_webArgs{"style_$i"} .
  601. " lw " .$FW_webArgs{"width_$i"} .
  602. " with " .$FW_webArgs{"type_$i"};
  603. }
  604. push @rows, "";
  605. for(my $i=0; $i < @plot; $i++) {
  606. my $r = $plot[$i];
  607. $r = "plot $r" if($i == 0);
  608. $r = " $r" if($i > 0);
  609. $r = "$r,\\" if($i+1 < @plot);
  610. push @rows, $r;
  611. }
  612. my $hash = $defs{$FW_webArgs{detail}};
  613. my $err = FileWrite("$FW_gplotdir/$hash->{GPLOTFILE}.gplot", @rows);
  614. $FW_RET .= "<div id='errmsg'>SVG_WriteGplot: $err</div>" if($err);
  615. return 0;
  616. }
  617. #######################################################
  618. # srcDesc:
  619. # - {all} : space separated plot arguments, in the file order, without devname
  620. # - {order}: unique name of the devs (FileLog,etc) in the .gplot order
  621. # - {src}{X}: hash (X is an order element), consisting of
  622. # {arg}: plot arguments for one dev, space separated
  623. # {idx}: number of lines requested from the same source
  624. # {num}: number of this src in the order array
  625. # - {rev}{orderIdx}{localIdx} = N: reverse lookup of the plot argument index,
  626. # using {src}{X}{num} as orderIdx and {src}{X}{idx} as localIdx
  627. sub
  628. SVG_readgplotfile($$$)
  629. {
  630. my ($wl, $gplot_pgm, $plotmode) = @_;
  631. ############################
  632. # Read in the template gnuplot file. Digest the #FileLog lines. Replace
  633. # the plot directive with our own, as we offer a file for each line
  634. my (%srcDesc, @data, $plot);
  635. my ($err1, $err2, @svgplotfile);
  636. return ("Nonexisting device $wl specified", @svgplotfile)
  637. if(!$defs{$wl});
  638. my $ld = $defs{$wl}{LOGDEVICE}
  639. if($defs{$wl}{LOGDEVICE});
  640. my $ldType = $defs{$defs{$wl}{LOGDEVICE}}{TYPE}
  641. if($ld && $defs{$ld});
  642. if(!$ldType) {
  643. $ldType = $defs{$wl}{TYPE};
  644. $ld = $wl;
  645. }
  646. ($err1, @svgplotfile) = FileRead($gplot_pgm);
  647. ($err2, @svgplotfile) = FileRead("$FW_gplotdir/template.gplot") if($err1);
  648. return ($err1, undef) if($err2);
  649. my ($plotfnCnt, $srcNum) = (0,0);
  650. my @empty;
  651. $srcDesc{all} = "";
  652. $srcDesc{order} = \@empty;
  653. my $specval = AttrVal($wl, "plotfunction", undef);
  654. my $plotReplace = AttrVal($wl, "plotReplace", undef);
  655. my $pr;
  656. (undef, $pr) = parseParams($plotReplace,"\\s"," ") if($plotReplace);
  657. my $prSubst = sub($)
  658. {
  659. return "%$_[0]%" if(!$pr);
  660. my $v = $pr->{$_[0]};
  661. return "%$_[0]%" if(!$v);
  662. if($v =~ m/^{.*}$/) {
  663. $cmdFromAnalyze = $v;
  664. return eval $v;
  665. } else {
  666. return $v;
  667. }
  668. };
  669. foreach my $l (@svgplotfile) {
  670. $l = "$l\n" unless $l =~ m/\n$/;
  671. map { $l =~ s/%($_)%/&$prSubst($1)/ge } keys %$pr if($plotReplace);
  672. my ($src, $plotfn) = (undef, undef);
  673. if($l =~ m/^#([^ ]*) (.*)$/) {
  674. if($1 eq $ldType) {
  675. $src = $ld; $plotfn = $2;
  676. } elsif($1 && $defs{$1}) {
  677. $src = $1; $plotfn = $2;
  678. }
  679. } elsif($l =~ "^plot" || $plot) {
  680. $plot .= $l;
  681. } else {
  682. push(@data, $l);
  683. }
  684. if($plotfn) {
  685. Log 3, "$wl: space is not allowed in >$ldType< definition: $plotfn"
  686. if($plotfn =~ m/\s/);
  687. if ($specval) {
  688. my @spec = split(" ",$specval);
  689. my $spec_count=1;
  690. foreach (@spec) {
  691. $plotfn =~ s/<SPEC$spec_count>/$_/g;
  692. $spec_count++;
  693. }
  694. }
  695. my $p = $srcDesc{src}{$src};
  696. if(!$p) {
  697. $p = { arg => $plotfn, idx=>0, num=>$srcNum++ };
  698. $srcDesc{src}{$src} = $p;
  699. push(@{$srcDesc{order}}, $src);
  700. } else {
  701. $p->{arg} .= " $plotfn";
  702. $p->{idx}++;
  703. }
  704. $srcDesc{rev}{$p->{num}}{$p->{idx}} = $plotfnCnt++;
  705. $srcDesc{all} .= " $plotfn";
  706. }
  707. }
  708. return (undef, \@data, $plot, \%srcDesc);
  709. }
  710. sub
  711. SVG_substcfg($$$$$$)
  712. {
  713. my ($splitret, $wl, $cfg, $plot, $file, $tmpfile) = @_;
  714. # interpret title and label as a perl command and make
  715. # to all internal values e.g. $value.
  716. my $ldt = $defs{$defs{$wl}{LOGDEVICE}}{TYPE}
  717. if($defs{$wl} && $defs{$wl}{LOGDEVICE});
  718. $ldt = "" if(!defined($ldt));
  719. if($file eq "CURRENT" && $ldt eq "FileLog") {
  720. $file = $defs{$defs{$wl}{LOGDEVICE}}{currentlogfile};
  721. $file =~ s+.*/++;
  722. }
  723. my $fileesc = $file;
  724. $fileesc =~ s/\\/\\\\/g; # For Windows, by MarkusRR
  725. my $title = AttrVal($wl, "title", "\"$fileesc\"");
  726. $title = AnalyzeCommand(undef, "{ $title }");
  727. my $label = AttrVal($wl, "label", undef);
  728. my @g_label;
  729. if ($label) {
  730. @g_label = split("::",$label);
  731. foreach (@g_label) {
  732. $_ = AnalyzeCommand(undef, "{ $_ }");
  733. }
  734. }
  735. my $gplot_script = join("", @{$cfg});
  736. $gplot_script .= $plot if(!$splitret);
  737. my $plotReplace = AttrVal($wl, "plotReplace", undef);
  738. if($plotReplace) {
  739. my ($list, $pr) = parseParams($plotReplace, "\\s"," ");
  740. for my $k (keys %$pr) {
  741. if($pr->{$k} =~ m/^{.*}$/) {
  742. $cmdFromAnalyze = $pr->{$k};
  743. $pr->{$k} = eval $cmdFromAnalyze;
  744. }
  745. $gplot_script =~ s/<$k>/$pr->{$k}/g;
  746. }
  747. }
  748. $gplot_script =~ s/<OUT>/$tmpfile/g;
  749. $gplot_script =~ s/<IN>/$file/g;
  750. my $ps = SVG_getplotsize($wl);
  751. $gplot_script =~ s/<SIZE>/$ps/g;
  752. $gplot_script =~ s/<TL>/$title/g;
  753. my $g_count=1;
  754. if ($label) {
  755. foreach (@g_label) {
  756. $gplot_script =~ s/<L$g_count>/$_/g;
  757. $plot =~ s/<L$g_count>/$_/g;
  758. $g_count++;
  759. }
  760. }
  761. $plot =~ s/\r//g; # For our windows friends...
  762. $gplot_script =~ s/\r//g;
  763. if($splitret == 1) {
  764. my @ret = split("\n", $gplot_script);
  765. return (\@ret, $plot);
  766. } else {
  767. return $gplot_script;
  768. }
  769. }
  770. sub
  771. SVG_tspec(@)
  772. {
  773. return sprintf("%04d-%02d-%02d_%02d:%02d:%02d",
  774. $_[5]+1900,$_[4]+1,$_[3],$_[2],$_[1],$_[0]);
  775. }
  776. ##################
  777. # Calculate either the number of scrollable SVGs (for $d = undef) or
  778. # for the device the valid from and to dates for the given zoom and offset
  779. sub
  780. SVG_calcOffsets($$)
  781. {
  782. my ($d,$wl) = @_;
  783. my $pm = AttrVal($wl,"plotmode",$FW_plotmode);
  784. my ($fr, $fo);
  785. my $frx; #fixedrange with offset
  786. if($defs{$wl}) {
  787. $fr = AttrVal($wl, "fixedrange", undef);
  788. if($fr) {
  789. if($fr =~ "^(hour|qday|day|week|month|year)" ||
  790. $fr =~ m/^\d+hour/ || #fixedrange with offset
  791. $fr =~ m/^\d+day/ ||
  792. $fr =~ m/^\d+year/ ) {
  793. $frx=$fr; #fixedrange with offset
  794. } else {
  795. my @range = split(" ", $fr);
  796. my @t = localtime;
  797. $SVG_devs{$d}{from} = ResolveDateWildcards($range[0], @t);
  798. $SVG_devs{$d}{to} = ResolveDateWildcards($range[1], @t);
  799. return;
  800. }
  801. }
  802. $fo = AttrVal( $wl, "fixedoffset", undef);
  803. }
  804. my $off = 0;
  805. $off += $FW_pos{off} if($FW_pos{off});
  806. $off = $fo if(defined($fo) && $fo =~ m/^[+-]?\d+$/);
  807. my $now;
  808. my $st = AttrVal($wl, "startDate", undef);
  809. if($st) {
  810. $now = mktime(0,0,12,$3,$2-1,$1-1900,0,0,-1)
  811. if($st =~ m/(\d\d\d\d)-(\d\d)-(\d\d)/);
  812. }
  813. $now = time() if(!$now);
  814. my $zoom = $FW_pos{zoom};
  815. $zoom = "day" if(!$zoom);
  816. $zoom = $fr if(defined($fr));
  817. $zoom = $frx if ($frx); #fixedrange with offset
  818. my @zrange = split(" ", $zoom); #fixedrange with offset
  819. if(defined($zrange[1])) { $off += $zrange[1]; $zoom=$zrange[0]; } #fixedrange with offset
  820. my $endPlotNow = (SVG_Attr($FW_wname, $wl, "endPlotNow", undef) && !$st);
  821. if($zoom =~ m/^(\d+)?hour/) {
  822. my $nHours = $1 ? ($1-1) : 0;
  823. if($endPlotNow) {
  824. my $t = int(($now + ($off-$nHours-1)*3600)/300.0)*300 + 300;
  825. my @l = localtime($t);
  826. $SVG_devs{$d}{from} = SVG_tspec(@l);
  827. @l = localtime($t+$nHours*3600+3599);
  828. $SVG_devs{$d}{to} = SVG_tspec(@l);
  829. } else {
  830. my $t = int($now/3600)*3600 + ($off-$nHours)*3600;
  831. my @l = localtime($t);
  832. $SVG_devs{$d}{from} = SVG_tspec(@l);
  833. @l = localtime($t+($nHours+1)*3600-1);
  834. $SVG_devs{$d}{to} = SVG_tspec(@l);
  835. }
  836. } elsif($zoom eq "qday") {
  837. if($endPlotNow) {
  838. my $t = int($now/300)*300+300 + ($off-1)*21600;
  839. my @l = localtime($t);
  840. $SVG_devs{$d}{from} = SVG_tspec( 0,$l[1],$l[2],$l[3],$l[4],$l[5]);
  841. @l = localtime($t+21600-1);
  842. $SVG_devs{$d}{to} = SVG_tspec(59,$l[1],$l[2],$l[3],$l[4],$l[5]);
  843. } else {
  844. my $t = int($now/3600)*3600 + $off*21600;
  845. my @l = localtime($t);
  846. $l[2] = int($l[2]/6)*6;
  847. $SVG_devs{$d}{from} = SVG_tspec( 0, 0,$l[2],$l[3],$l[4],$l[5]);
  848. $l[2] += 5;
  849. $SVG_devs{$d}{to} = SVG_tspec(59,59,$l[2],$l[3],$l[4],$l[5]);
  850. }
  851. } elsif($zoom =~ m/^(\d+)?day/) {
  852. my $nDays = $1 ? ($1-1) : 0;
  853. if($endPlotNow) {
  854. my $t = int($now/300)*300+300 + ($off-$nDays-1)*86400;
  855. my @l = localtime($t);
  856. $SVG_devs{$d}{from} = SVG_tspec(0,$l[1],$l[2],$l[3],$l[4],$l[5]);
  857. @l = localtime($t+(1+$nDays)*86400-1);
  858. $SVG_devs{$d}{to} = SVG_tspec(59,$l[1],$l[2],$l[3],$l[4],$l[5]);
  859. } else {
  860. my $t = $now + ($off-$nDays)*86400;
  861. my @l = localtime($t);
  862. $SVG_devs{$d}{from} = SVG_tspec( 0, 0, 0,$l[3],$l[4],$l[5]);
  863. @l = localtime($t+$nDays*86400);
  864. $SVG_devs{$d}{to} = SVG_tspec(59,59,23,$l[3],$l[4],$l[5]);
  865. }
  866. } elsif($zoom eq "week") {
  867. my @l = localtime($now);
  868. my $start = (SVG_Attr($FW_wname, $wl, "endPlotToday", undef) ?
  869. 6 : $l[6] - SVG_Attr($FW_wname, $wl, "plotWeekStartDay", 0));
  870. $start += 7 if($start < 0);
  871. my $t = $now - ($start*86400) + ($off*86400)*7;
  872. @l = localtime($t);
  873. $SVG_devs{$d}{from} = SVG_tspec( 0, 0, 0,$l[3],$l[4],$l[5]);
  874. @l = localtime($t+6*86400);
  875. $SVG_devs{$d}{to} = SVG_tspec(59,59,23,$l[3],$l[4],$l[5]);
  876. } elsif($zoom eq "month") {
  877. my ($sd,$ed,$sm,$em,$sy,$ey);
  878. my @l = localtime($now);
  879. while($off < -12) { # Correct the year
  880. $off += 12; $l[5]--;
  881. }
  882. $l[4] += $off;
  883. $l[4] += 12, $l[5]-- if($l[4] < 0);
  884. my @me = (31,28,31,30,31,30,31,31,30,31,30,31);
  885. if(SVG_Attr($FW_wname, $wl, "endPlotToday", undef)) {
  886. $sy = $ey = $l[5];
  887. $sm = $l[4]-1; $em = $l[4];
  888. $sm += 12, $sy-- if($sm < 0);
  889. $sd = $l[3]+1; $ed = $l[3];
  890. $sd=1, $sm=$em, $sy=$ey if($sd > $me[$sm]);
  891. } else {
  892. $sy = $ey = $l[5];
  893. $sm = $em = $l[4];
  894. $sd = 1; $ed = $me[$l[4]];
  895. $ed++ if($l[4]==1 && !(($sy+1900)%4)); # leap year
  896. }
  897. $SVG_devs{$d}{from} = SVG_tspec( 0, 0, 0,$sd,$sm,$sy);
  898. $SVG_devs{$d}{to} = SVG_tspec(59,59,23,$ed,$em,$ey);
  899. } elsif($zoom =~ m/^(\d+)?year/) {
  900. my $nyear = $1 ? ($1-1) : 0;
  901. my @l = localtime($now);
  902. $l[5] += ($off-1);
  903. if ($st) { #
  904. $l[5]++;
  905. $SVG_devs{$d}{from} = SVG_tspec( 0, 0, 0, 1, 0,$l[5] ); #Jan01 00:00:00
  906. $SVG_devs{$d}{to} = SVG_tspec(59,59,23,31,11,$l[5]+$nyear);
  907. } elsif(SVG_Attr($FW_wname, $wl, "endPlotToday", undef)) {
  908. $SVG_devs{$d}{from} = SVG_tspec( 0, 0, 0,$l[3],$l[4],$l[5] - $nyear);
  909. $l[5]++; # today, 23:59
  910. $SVG_devs{$d}{to} = SVG_tspec(59,59,23,$l[3],$l[4],$l[5]);
  911. } elsif(SVG_Attr($FW_wname, $wl, "endPlotNow", undef)) {
  912. #$SVG_devs{$d}{from} = SVG_tspec(0, $l[0], @l);
  913. $SVG_devs{$d}{from} = SVG_tspec($l[0], $l[1], $l[2],$l[3],$l[4],
  914. $l[5] - $nyear);
  915. #$SVG_devs{$d}{from} = SVG_tspec(@l);
  916. $l[5]++; # now
  917. $SVG_devs{$d}{to} = SVG_tspec(@l);
  918. } else {
  919. $l[5]++;
  920. $SVG_devs{$d}{from} = SVG_tspec( 0, 0, 0, 1, 0,$l[5]-$nyear);
  921. $SVG_devs{$d}{to} = SVG_tspec(59,59,23,31,11,$l[5]); #Dec31 23:59:59
  922. }
  923. }
  924. }
  925. ######################
  926. # Generate an image from the log via gnuplot or SVG
  927. sub
  928. SVG_showLog($)
  929. {
  930. return SVG_doShowLog($FW_webArgs{dev},
  931. $FW_webArgs{logdev},
  932. $FW_webArgs{gplotfile},
  933. $FW_webArgs{logfile});
  934. }
  935. sub
  936. SVG_doShowLog($$$$;$)
  937. {
  938. my ($wl, $d, $type, $file, $noHeader) = @_;
  939. my $pm = AttrVal($wl,"plotmode",$FW_plotmode);
  940. my $gplot_pgm = "$FW_gplotdir/$type.gplot";
  941. my ($err, $cfg, $plot, $srcDesc) = SVG_readgplotfile($wl, $gplot_pgm, $pm);
  942. if($err || !$defs{$d}) {
  943. my $msg = ($err ? $err : "No Logdevice >$d<");
  944. Log3 $FW_wname, 1, $msg;
  945. if($pm && $pm =~ m/SVG/) { # FW_fatal for SVG:
  946. $FW_RETTYPE = "image/svg+xml";
  947. FW_pO '<svg xmlns="http://www.w3.org/2000/svg">';
  948. FW_pO '<text x="20" y="20">'.$msg.'</text>';
  949. FW_pO '</svg>';
  950. return ($FW_RETTYPE, $FW_RET);
  951. } else {
  952. return ($FW_RETTYPE, $msg);
  953. }
  954. }
  955. SVG_calcOffsets($d,$wl);
  956. my ($f,$t)=($SVG_devs{$d}{from}, $SVG_devs{$d}{to});
  957. $f = 0 if(!$f); # From the beginning of time...
  958. $t = 9 if(!$t); # till the end
  959. if($pm =~ m/gnuplot/) {
  960. my $tmpfile = "/tmp/file.$$";
  961. my $errfile = "/tmp/gnuplot.err";
  962. my $da = SVG_getData($wl, $f, $t, $srcDesc, 0); # substcfg needs it(!)
  963. my $tmpstring = "";
  964. open(FH, ">$tmpfile");
  965. for(my $dIdx=0; $dIdx<@{$da}; $dIdx++) {
  966. if (${$da->[$dIdx]}) {
  967. $tmpstring = ${$da->[$dIdx]};
  968. $tmpstring =~ s/#.*/\n/g;
  969. } else {
  970. $tmpstring = "$f 0\n\n";
  971. }
  972. print FH "$tmpstring";
  973. }
  974. close(FH);
  975. # put in the filename of the temporary data file into the plot file string
  976. my $i = 0;
  977. $plot =~ s/\".*?using 1:[^ ]+ /"\"$tmpfile\" i " . $i++ . " using 1:2 "/gse;
  978. $plot = "set xrange [\"$f\":\"$t\"]\n\n$plot" if($SVG_devs{$d}{from});
  979. my $gplot_script = SVG_substcfg(0, $wl, $cfg, $plot, $file, $tmpfile);
  980. $gplot_script =~ s/<TMPFILE>/$tmpfile/g;
  981. my $ext;
  982. if($pm eq "gnuplot-scroll") {
  983. $FW_RETTYPE = "image/png";
  984. $ext = "png";
  985. }
  986. else {
  987. $gplot_script =~ s/set terminal png transparent/set terminal svg/;
  988. $gplot_script =~ s/set terminal (.*) crop/set terminal $1/;
  989. $gplot_script =~ s/set output (.*).png'/set output $1.svg'/;
  990. $FW_RETTYPE = "image/svg+xml";
  991. $ext = "svg";
  992. }
  993. $gplot_script =~ s/ls \w+//g;
  994. open(FH, "|gnuplot >> $errfile 2>&1");# feed it to gnuplot
  995. print FH $gplot_script;
  996. close(FH);
  997. unlink($tmpfile);
  998. open(FH, "$tmpfile.$ext"); # read in the result and send it
  999. binmode (FH); # necessary for Windows
  1000. FW_pO join("", <FH>);
  1001. close(FH);
  1002. unlink("$tmpfile.$ext");
  1003. } elsif($pm eq "SVG") {
  1004. my ($f,$t)=($SVG_devs{$d}{from}, $SVG_devs{$d}{to});
  1005. $f = 0 if(!$f); # From the beginning of time...
  1006. $t = 9 if(!$t); # till the end
  1007. Log3 $FW_wname, 5, "plotcommand: get $d $file INT $f $t ".$srcDesc->{all};
  1008. $FW_RETTYPE = "image/svg+xml";
  1009. (my $cachedate = TimeNow()) =~ s/ /_/g;
  1010. my $SVGcache = (AttrVal($FW_wname, "SVGcache", undef) && $t lt $cachedate);
  1011. my $cDir = "$FW_dir/SVGcache";
  1012. my $cFile = "$wl-$f-$t.svg";
  1013. $cFile =~ s/:/-/g; # For Windows / #11053
  1014. my $cPath = "$cDir/$cFile";
  1015. if($SVGcache && open(CFH, $cPath)) {
  1016. FW_pO join("", <CFH>);
  1017. close(CFH);
  1018. } else {
  1019. my $da = SVG_getData($wl, $f, $t, $srcDesc, 0); # substcfg needs it(!)
  1020. ($cfg, $plot) = SVG_substcfg(1, $wl, $cfg, $plot, $file, "<OuT>");
  1021. my $ret = SVG_render($wl, $f, $t, $cfg, $da,
  1022. $plot, $FW_wname, $FW_cssdir, $srcDesc, $noHeader);
  1023. $internal_data = "";
  1024. FW_pO $ret;
  1025. if($SVGcache) {
  1026. mkdir($cDir) if(! -d $cDir);
  1027. if(open(CFH, ">$cPath")) {
  1028. print CFH $ret;
  1029. close(CFH);
  1030. }
  1031. }
  1032. }
  1033. }
  1034. return ($FW_RETTYPE, $FW_RET);
  1035. }
  1036. sub
  1037. SVG_getData($$$$$)
  1038. {
  1039. my ($d, $f,$t,$srcDesc,$showData) = @_;
  1040. my (@da, $ret, @vals);
  1041. my @keys = ("min","mindate","max","maxdate","currval","currdate",
  1042. "firstval","firstdate","avg","cnt","lastraw","sum");
  1043. foreach my $src (@{$srcDesc->{order}}) {
  1044. my $s = $srcDesc->{src}{$src};
  1045. my $fname = ($src eq $defs{$d}{LOGDEVICE} ? $defs{$d}{LOGFILE} : "CURRENT");
  1046. my $cmd = "get $src $fname INT $f $t ".$s->{arg};
  1047. FW_fC($cmd, 1);
  1048. if($showData) {
  1049. $ret .= "\n$cmd\n\n";
  1050. $ret .= $$internal_data if(ref $internal_data eq "SCALAR");
  1051. } else {
  1052. push(@da, $internal_data);
  1053. for(my $i = 0; $i<=$s->{idx}; $i++) {
  1054. my %h;
  1055. foreach my $k (@keys) {
  1056. $h{$k} = $data{$k.($i+1)};
  1057. }
  1058. push @vals, \%h;
  1059. }
  1060. }
  1061. }
  1062. # Reorder the $data{maxX} stuff
  1063. my ($min, $max) = (999999, -999999);
  1064. my $no = int(keys %{$srcDesc->{rev}});
  1065. for(my $oi = 0; $oi < $no; $oi++) {
  1066. my $nl = int(keys %{$srcDesc->{rev}{$oi}});
  1067. for(my $li = 0; $li < $nl; $li++) {
  1068. my $r = $srcDesc->{rev}{$oi}{$li}+1;
  1069. my $val = shift @vals;
  1070. foreach my $k (@keys) {
  1071. $min = $val->{$k} if($k eq "min" && defined($val->{$k}) &&
  1072. $val->{$k} =~ m/[-+]?\d*\.?\d+/ && $val->{$k} < $min);
  1073. $max = $val->{$k} if($k eq "max" && defined($val->{$k}) &&
  1074. $val->{$k} =~ m/[-+]?\d*\.?\d+/ && $val->{$k} > $max);
  1075. $data{"$k$r"} = $val->{$k};
  1076. }
  1077. }
  1078. }
  1079. $data{maxAll} = $max;
  1080. $data{minAll} = $min;
  1081. return $ret if($showData);
  1082. return \@da;
  1083. }
  1084. ######################
  1085. # Convert the configuration to a "readable" form -> array to hash
  1086. sub
  1087. SVG_digestConf($$)
  1088. {
  1089. my ($confp,$plot) = @_;
  1090. my %conf;
  1091. map { chomp; my @a=split(" ",$_, 3);
  1092. if($a[0] && $a[0] eq "set") { $conf{lc($a[1])} = $a[2]; }
  1093. } @{$confp};
  1094. $conf{title} = "" if(!defined($conf{title}));
  1095. $conf{title} =~ s/'//g;
  1096. ######################
  1097. # Digest grid
  1098. my $t = ($conf{grid} ? $conf{grid} : "");
  1099. #$conf{hasxgrid} = ( $t =~ /.*xtics.*/ ? 1 : 0); # Unused
  1100. $conf{hasygrid} = ( $t =~ /.*ytics.*/ ? 1 : 0);
  1101. $conf{hasy2grid}= ( $t =~ /.*y2tics.*/ ? 1 : 0);
  1102. # Digest axes/title/etc from $plot (gnuplot) and draw the line-titles
  1103. my (@lAxis,@lTitle,@lType,@lStyle,@lWidth);
  1104. my ($i, $pTemp);
  1105. $pTemp = $plot; $i = 0; $pTemp =~ s/ axes (\w+)/$lAxis[$i++]=$1/gse;
  1106. $pTemp = $plot; $i = 0;
  1107. $pTemp =~ s/title( '([^']*)')?/$lTitle[$i++]=(defined($2)?$2:"")/gse;
  1108. $pTemp = $plot; $i = 0; $pTemp =~ s/ with (\w+)/$lType[$i++]=$1/gse;
  1109. $pTemp = $plot; $i = 0; $pTemp =~ s/ ls (\w+)/$lStyle[$i++]=$1/gse;
  1110. $pTemp = $plot; $i = 0; $pTemp =~ s/ lw ([\w.]+)/$lWidth[$i++]=$1/gse;
  1111. for my $i (0..int(@lType)-1) { # lAxis is optional
  1112. $lAxis[$i] = "x1y2" if(!$lAxis[$i]);
  1113. $lStyle[$i] = "class=\"SVGplot ".
  1114. (defined($lStyle[$i]) ? $lStyle[$i] : "l$i")."\"";
  1115. $lWidth[$i] = (defined($lWidth[$i]) ? "stroke-width:$lWidth[$i]" :"");
  1116. }
  1117. $conf{lAxis} = \@lAxis;
  1118. $conf{lTitle} = \@lTitle;
  1119. $conf{lType} = \@lType;
  1120. $conf{lStyle} = \@lStyle;
  1121. $conf{lWidth} = \@lWidth;
  1122. return %conf;
  1123. }
  1124. sub
  1125. SVG_openFile($$$)
  1126. {
  1127. my ($dir, $prf, $fName) = @_;
  1128. my $baseStyle = $prf;
  1129. $baseStyle =~ s/(touchpad|smallscreen)//;
  1130. if(open(FH, "$dir/${baseStyle}$fName") || # Forum #32530
  1131. open(FH, "$dir/$fName")) {
  1132. return 1;
  1133. }
  1134. return 0;
  1135. }
  1136. #####################################
  1137. sub
  1138. SVG_getSteps($$$)
  1139. {
  1140. my ($range,$min,$max) = @_;
  1141. my $dh = $max - $min;
  1142. my ($step, $mi, $ma) = (1, 1, 1);
  1143. my @limit = (0.001, 0.01, 0.02, 0.05, 0.1, 0.2, 0.5, 1, 2, 5, 10, 20, 50,
  1144. 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000,
  1145. 200000, 500000, 1000000, 2000000);
  1146. for my $li (0..$#limit-1) {
  1147. my $l = $limit[$li];
  1148. next if($dh > $l*10);
  1149. $ma = $range ? $max : SVG_doround($max, $l, 1);
  1150. $mi = $range ? $min : SVG_doround($min, $l, 0);
  1151. if(($ma-$mi)/$l >= 7) { # If more then 7 steps, then choose next
  1152. $l = $limit[$li+1];
  1153. $ma = $range ? $max : SVG_doround($max, $l, 1);
  1154. $mi = $range ? $min : SVG_doround($min, $l, 0);
  1155. }
  1156. $step = $l;
  1157. last;
  1158. }
  1159. if($step==0.001 && $max==$min) { # Don't want 0.001 range for nil
  1160. $step = 1;
  1161. $ma = $mi + $step;
  1162. }
  1163. return ($step, $mi, $ma);
  1164. }
  1165. sub
  1166. SVG_render($$$$$$$$$$)
  1167. {
  1168. my $name = shift; # e.g. wl_8
  1169. my $from = shift; # e.g. 2008-01-01
  1170. my $to = shift; # e.g. 2009-01-01
  1171. my $confp = shift; # lines from the .gplot file, w/o FileLog and plot
  1172. my $da = shift; # data pointer array
  1173. my $plot = shift; # Plot lines from the .gplot file
  1174. my $parent_name = shift; # e.g. FHEMWEB instance name
  1175. my $parent_dir = shift; # FW_dir
  1176. my $srcDesc = shift; # #FileLog lines, as array pointer
  1177. my $noHeader = shift;
  1178. $SVG_RET="";
  1179. my $SVG_ss = AttrVal($parent_name, "smallscreen", 0);
  1180. my ($nr_left_axis,$nr_right_axis,$use_left_axis,$use_right_axis) =
  1181. split(",", SVG_Attr($parent_name, $name,"nrAxis","1,1"));
  1182. $use_left_axis = $nr_left_axis if( !defined($use_left_axis) );
  1183. $use_right_axis = $nr_right_axis if( !defined($use_right_axis) );
  1184. my $th = 16; # "Font" height
  1185. my $axis_width = ($SVG_ss ? 2 : 3)*$th;
  1186. my ($x, $y) = ($axis_width*$nr_left_axis, 1.2*$th); # Rect offset
  1187. ######################
  1188. # Convert the configuration to a "readable" form -> array to hash
  1189. my %conf = SVG_digestConf($confp, $plot);
  1190. my $ps = "800,400";
  1191. $ps = $1 if($conf{terminal} =~ m/.*size[ ]*([^ ]*)/);
  1192. my ($ow,$oh) = split(",", $ps); # Original width
  1193. my $w = $ow-$nr_left_axis*$axis_width-$nr_right_axis*$axis_width;
  1194. my $h = $oh-2*$y; # Rect size
  1195. my @filter;
  1196. foreach my $src (keys %{$srcDesc->{src}}) {
  1197. my $f = CallFn($src, "SVG_regexpFn", $src, $srcDesc->{src}{$src}{arg});
  1198. push(@filter, $f) if($f);
  1199. }
  1200. my $filter = join("|", @filter);
  1201. $filter =~ s/"/./g;
  1202. $filter = AttrVal($parent_name, "longpollSVG", 0) ? "flog=\"$filter\"" : "";
  1203. my %dataIdx; # Build a reverse Index for the dataSource
  1204. ######################
  1205. # SVG Header
  1206. my $svghdr = 'version="1.1" xmlns="http://www.w3.org/2000/svg" '.
  1207. 'xmlns:xlink="http://www.w3.org/1999/xlink" '.
  1208. "id='SVGPLOT_$name' $filter data-origin='FHEM'";
  1209. if(!$noHeader) {
  1210. SVG_pO '<?xml version="1.0" encoding="UTF-8"?>';
  1211. SVG_pO '<!DOCTYPE svg>';
  1212. SVG_pO "<svg $svghdr width=\"${ow}px\" height=\"${oh}px\">";
  1213. } else {
  1214. SVG_pO "<svg $svghdr style='width:${ow}px; height:${oh}px;'>";
  1215. }
  1216. my $prf = AttrVal($parent_name, "stylesheetPrefix", "");
  1217. SVG_pO "<style type=\"text/css\"><![CDATA[";
  1218. if(SVG_openFile($parent_dir, $prf, "svg_style.css")) {
  1219. SVG_pO join("", <FH>);
  1220. close(FH);
  1221. } else {
  1222. Log3 $name, 0, "Can't open $parent_dir/svg_style.css"
  1223. }
  1224. SVG_pO "]]></style>";
  1225. ######################
  1226. # gradient definitions
  1227. if(SVG_openFile($parent_dir, $prf, "svg_defs.svg")) {
  1228. SVG_pO join("", <FH>);
  1229. close(FH);
  1230. } else {
  1231. Log3 $name, 0, "Can't open $parent_dir/svg_defs.svg"
  1232. }
  1233. ######################
  1234. # Mask:
  1235. SVG_pO "<defs>".
  1236. "<mask id='mask_$name' maskUnits='userSpaceOnUse' ".
  1237. "x='0' y='0' width ='$ow' height ='$oh'>".
  1238. sprintf("<rect x='%d' y='%d' width='%d' height='%d' ",
  1239. $x-2, $y-1, $w+2, $h+1).
  1240. "style='stroke:none; fill:#ffffff'/>".
  1241. "</mask>".
  1242. "</defs>";
  1243. ######################
  1244. # Rectangle
  1245. SVG_pO "<rect x='$x' y='$y' width ='$w' height ='$h' rx='8' ry='8' ".
  1246. "fill='none' class='border'/>";
  1247. my ($off1,$off2) = ($x+$w/2, 3*$y/4);
  1248. my $title = ($conf{title} ? $conf{title} : " ");
  1249. $title =~ s/</&lt;/g;
  1250. $title =~ s/>/&gt;/g;
  1251. SVG_pO "<text id='svg_title' x='$off1' y='$off2' " .
  1252. "class='title' text-anchor='middle'>$title</text>";
  1253. ######################
  1254. # Left label = ylabel and right label = y2label
  1255. if(!$SVG_ss) {
  1256. for my $idx (1..$use_left_axis) {
  1257. my $name = "y".($idx)."label";
  1258. $name = "ylabel" if( $idx == 1 );
  1259. my $t = ($conf{$name} ? $conf{$name} : "");
  1260. $t =~ s/"//g;
  1261. ($off1,$off2) = ($x-($idx)*$axis_width+3*$th/4, $oh/2);
  1262. SVG_pO "<text x=\"$off1\" y=\"$off2\" text-anchor=\"middle\" " .
  1263. "class=\"ylabel\" transform=\"rotate(270,$off1,$off2)\">$t</text>";
  1264. }
  1265. for my $idx ($use_left_axis+1..$use_left_axis+$use_right_axis) {
  1266. my $name = "y".($idx)."label";
  1267. $name = "ylabel" if( $idx == 1 );
  1268. my $t = ($conf{$name} ? $conf{$name} : "");
  1269. $t =~ s/"//g;
  1270. ($off1,$off2) = ($x+$w+($idx-$use_left_axis)*$axis_width-$th/4, $oh/2);
  1271. SVG_pO "<text x=\"$off1\" y=\"$off2\" text-anchor=\"middle\" " .
  1272. "class=\"y2label\" transform=\"rotate(270,$off1,$off2)\">$t</text>";
  1273. }
  1274. }
  1275. ######################
  1276. ($off1,$off2) = ($ow-$nr_right_axis*$axis_width-$th, $y+$th);
  1277. my ($xmin, $xmax, $xtics)= (99999999, -99999999, "");
  1278. if(defined($conf{xrange})) {
  1279. my $idx= 1;
  1280. while(defined($data{"xmin$idx"})) {
  1281. $xmin= $data{"xmin$idx"} if($data{"xmin$idx"}< $xmin);
  1282. $xmax= $data{"xmax$idx"} if($data{"xmax$idx"}> $xmax);
  1283. $idx++;
  1284. }
  1285. #main::Debug "xmin= $xmin xmax=$xmax";
  1286. $conf{xrange} = AnalyzeCommand(undef, $1) if($conf{xrange} =~ /^({.*})$/);
  1287. if($conf{xrange} =~ /\[(.*):(.*)\]/) {
  1288. $xmin = $1 if($1 ne "");
  1289. $xmax = $2 if($2 ne "");
  1290. }
  1291. }
  1292. $xtics = defined($conf{xtics}) ? $conf{xtics} : "";
  1293. ######################
  1294. # Loop over the input, digest dates, calculate min/max values
  1295. my ($fromsec, $tosec);
  1296. $fromsec = SVG_time_to_sec($from) if($from ne "0"); # 0 is special
  1297. $tosec = SVG_time_to_sec($to) if($to ne "9"); # 9 is special
  1298. my $tmul;
  1299. $tmul = $w/($tosec-$fromsec) if($tosec && $fromsec && $tosec != $fromsec);
  1300. my ($min, $max, $idx) = (99999999, -99999999, 0);
  1301. my (%hmin, %hmax, @hdx, @hdy);
  1302. my ($dxp, $dyp) = (\(), \());
  1303. my ($d, $v, $ld, $lv) = ("","","","");
  1304. for(my $dIdx=0; $dIdx<@{$da}; $dIdx++) {
  1305. my $lIdx = 0;
  1306. $idx = $srcDesc->{rev}{$dIdx}{$lIdx};
  1307. my $dp = $da->[$dIdx];
  1308. next if(ref $dp ne "SCALAR"); # Avoid Crash, Forum #34523
  1309. my ($dpl,$dpoff,$l) = (length($$dp), 0, "");
  1310. while($dpoff < $dpl) { # using split instead is memory hog
  1311. my $ndpoff = index($$dp, "\n", $dpoff);
  1312. if($ndpoff == -1) {
  1313. $l = substr($$dp, $dpoff);
  1314. } else {
  1315. $l = substr($$dp, $dpoff, $ndpoff-$dpoff);
  1316. }
  1317. $dpoff = $ndpoff+1;
  1318. if($l =~ m/^#/) {
  1319. my $a = $conf{lAxis}[$idx];
  1320. if(defined($a)) {
  1321. $hmin{$a} = $min if(!defined($hmin{$a}) || $hmin{$a} > $min);
  1322. $hmax{$a} = $max if(!defined($hmax{$a}) || $hmax{$a} < $max);
  1323. }
  1324. ($min, $max) = (99999999, -99999999);
  1325. $hdx[$idx] = $dxp; $hdy[$idx] = $dyp;
  1326. ($dxp, $dyp) = (\(), \());
  1327. $lIdx++;
  1328. $idx = $srcDesc->{rev}{$dIdx}{$lIdx};
  1329. last if(!$idx);
  1330. } elsif( $l =~ /^;/ ) { #allow ;special lines
  1331. if( $l =~ m/^;p (\S+)\s(\S+)/ ) {# point
  1332. my $xmul = $w/($xmax-$xmin) if($xmax-$xmin > 0 );
  1333. my $x1;
  1334. if( $conf{xrange} ) {
  1335. $x1 = int(($1-$xmin)*$xmul);
  1336. } else {
  1337. $x1 = $x1;
  1338. }
  1339. my $y1 = $2;
  1340. push @{$dxp}, $x1;
  1341. push @{$dyp}, $y1;
  1342. $min = $y1 if($min > $y1);
  1343. $max = $y1 if($max < $y1);
  1344. } elsif( $conf{lType}[$idx] eq "lines" ) {
  1345. push @{$dxp}, undef;
  1346. push @{$dyp}, $l;
  1347. }
  1348. } else {
  1349. ($d, $v) = split(" ", $l);
  1350. $d = ($tmul ? int((SVG_time_to_sec($d)-$fromsec)*$tmul) : $d);
  1351. $d = 0 if($tmul && $d < 0); # Forum #40358
  1352. if($ld ne $d || $lv ne $v) { # Saves a lot on year zoomlevel
  1353. $ld = $d; $lv = $v;
  1354. push @{$dxp}, $d;
  1355. push @{$dyp}, $v;
  1356. $min = $v if($min > $v);
  1357. $max = $v if($max < $v);
  1358. }
  1359. }
  1360. last if($ndpoff == -1);
  1361. }
  1362. }
  1363. $dxp = $hdx[0];
  1364. if(($dxp && int(@{$dxp}) < 2 && !$tosec) || # not enough data and no range...
  1365. (!$tmul && !$dxp)) {
  1366. SVG_pO "</svg>";
  1367. return $SVG_RET;
  1368. }
  1369. if(!$tmul) { # recompute the x data if no range sepcified
  1370. $fromsec = SVG_time_to_sec($dxp->[0]) if(!$fromsec);
  1371. $tosec = SVG_time_to_sec($dxp->[int(@{$dxp})-1]) if(!$tosec);
  1372. $tmul = $w/($tosec-$fromsec) if($tosec != $fromsec);
  1373. for my $i (0..@hdx-1) {
  1374. $dxp = $hdx[$i];
  1375. for my $i (0..@{$dxp}-1) {
  1376. $dxp->[$i] = int((SVG_time_to_sec($dxp->[$i])-$fromsec)*$tmul);
  1377. }
  1378. }
  1379. }
  1380. ######################
  1381. # Compute & draw vertical tics, grid and labels
  1382. my $ddur = ($tosec-$fromsec)/86400;
  1383. my ($first_tag, $tag, $step, $tstep, $aligntext, $aligntics);
  1384. if($ddur <= 0.05) {
  1385. $first_tag=". 2 1"; $tag=": 3 4"; $step = 300; $tstep = 60;
  1386. } elsif($ddur <= 0.2) {
  1387. $first_tag=". 2 1"; $tag=": 3 4"; $step = 1200; $tstep = 300;
  1388. } elsif($ddur <= 0.5) {
  1389. $first_tag=". 2 1"; $tag=": 3 4"; $step = 3600; $tstep = 900;
  1390. } elsif($ddur <= 1.1) { # +0.1 -> DST
  1391. $first_tag=". 2 1"; $tag=": 3 4"; $step = 4*3600; $tstep = 3600;
  1392. } elsif ($ddur <= 7.1) {
  1393. $first_tag=". 6"; $tag=". 2 1"; $step = 24*3600; $tstep = 6*3600;
  1394. } elsif ($ddur <= 31.1) {
  1395. $first_tag=". 6"; $tag=". 2 1"; $step = 7*24*3600; $tstep = 24*3600;
  1396. $aligntext = 1;
  1397. } elsif ($ddur <= 732.1) {
  1398. $first_tag=". 6"; $tag=". 1"; $step = 28*24*3600; $tstep = 28*24*3600;
  1399. $aligntext = 2; $aligntics = 2;
  1400. } else {
  1401. $step = (($ddur / 365.2425) / 20) * 365 * 86400;
  1402. if($step < 365 * 86400) {
  1403. $step = 365 * 86400;
  1404. }
  1405. $tstep = $step;
  1406. $first_tag=""; $tag=". 6";
  1407. $aligntext = 2; $aligntics = 2;
  1408. }
  1409. my $barwidth = $tstep;
  1410. ######################
  1411. # First the tics
  1412. $off2 = $y+4;
  1413. my ($off3, $off4) = ($y+$h-4, $y+$h);
  1414. my $initoffset = $tstep;
  1415. if( $conf{xrange} ) { # user defined range
  1416. if( !$xtics || $xtics ne "()" ) { # auto tics
  1417. my $xmul = $w/($xmax-$xmin);
  1418. my ($step,$mi,$ma) = SVG_getSteps( $conf{xrange}, $xmin, $xmax );
  1419. $step /= 5 if( $step > 50 );
  1420. $step /= 2 if( $step > 10 );
  1421. for(my $i = $mi; $i <= $ma; $i += $step) {
  1422. $off1 = int($x+($i-$xmin)*$xmul);
  1423. SVG_pO "<polyline class='SVGplot' points='$off1,$y $off1,$off2'/>";
  1424. SVG_pO "<polyline class='SVGplot' points='$off1,$off3 $off1,$off4'/>";
  1425. }
  1426. }
  1427. } else { # times
  1428. $initoffset = int(($tstep/2)/86400)*86400 if($aligntics);
  1429. for(my $i = $fromsec+$initoffset; $i < $tosec; $i += $tstep) {
  1430. $i = SVG_time_align($i,$aligntics);
  1431. $off1 = int($x+($i-$fromsec)*$tmul);
  1432. SVG_pO "<polyline class='SVGplot' points='$off1,$y $off1,$off2'/>";
  1433. SVG_pO "<polyline class='SVGplot' points='$off1,$off3 $off1,$off4'/>";
  1434. }
  1435. }
  1436. ######################
  1437. # then the text and the grid
  1438. $off1 = $x;
  1439. $off2 = $y+$h+$th;
  1440. my $t = SVG_fmtTime($first_tag, $fromsec);
  1441. SVG_pO "<text x=\"0\" y=\"$off2\" class=\"ylabel\">$t</text>"
  1442. if(!$conf{xrange});
  1443. $initoffset = $step;
  1444. if(SVG_Attr($parent_name,$name,"endPlotNow",undef) && $ddur>1.1 && $ddur<6.9){
  1445. my $now = time();
  1446. $initoffset -= ($now+fhemTzOffset($now))%86400; # Forum #25768
  1447. }
  1448. if( $conf{xrange} ) { # user defined range
  1449. my $xmul = $w/($xmax-$xmin);
  1450. if( $xtics ) { #user tics and grid
  1451. my $tic = $xtics;
  1452. $tic =~ s/^\((.*)\)$/$1/; # Strip ()
  1453. foreach my $onetic (split(",", $tic)) {
  1454. $onetic =~ s/^ *(.*) *$/$1/;
  1455. my ($tlabel, $tvalue) = split(" ", $onetic);
  1456. $tlabel =~ s/^"(.*)"$/$1/;
  1457. $tvalue = 0 if( !$tvalue );
  1458. $off1 = int($x+($tvalue-$xmin)*$xmul);
  1459. $t = $tvalue;
  1460. SVG_pO "<text x=\"$off1\" y=\"$off2\" class=\"ylabel\" " .
  1461. "text-anchor=\"middle\">$t</text>";
  1462. SVG_pO " <polyline points=\"$off1,$y $off1,$off4\" class=\"hgrid\"/>";
  1463. }
  1464. } else { # auto grid
  1465. my ($step,$mi,$ma) = SVG_getSteps( $conf{xrange}, $xmin, $xmax );
  1466. for(my $i = $mi; $i <= $ma; $i += $step) {
  1467. $off1 = int($x+($i-$xmin)*$xmul);
  1468. $t = $i;
  1469. SVG_pO "<text x=\"$off1\" y=\"$off2\" class=\"ylabel\" " .
  1470. "text-anchor=\"middle\">$t</text>";
  1471. SVG_pO " <polyline points=\"$off1,$y $off1,$off4\" class=\"hgrid\"/>"
  1472. if( $i != $mi && $i != $ma );
  1473. }
  1474. }
  1475. } else { # times
  1476. $initoffset = int(($step/2)/86400)*86400 if($aligntext);
  1477. for(my $i = $fromsec+$initoffset; $i < $tosec; $i += $step) {
  1478. $i = SVG_time_align($i,$aligntext);
  1479. $off1 = int($x+($i-$fromsec)*$tmul);
  1480. $t = SVG_fmtTime($tag, $i);
  1481. SVG_pO "<text x=\"$off1\" y=\"$off2\" class=\"ylabel\" " .
  1482. "text-anchor=\"middle\">$t</text>";
  1483. SVG_pO " <polyline points=\"$off1,$y $off1,$off4\" class=\"hgrid\"/>";
  1484. }
  1485. }
  1486. ######################
  1487. # Left and right axis tics / text / grid
  1488. #-- just in case we have only one data line, but want to draw both axes
  1489. $hmin{x1y1}=$hmin{x1y2}, $hmax{x1y1}=$hmax{x1y2} if(!defined($hmin{x1y1}));
  1490. $hmin{x1y2}=$hmin{x1y1}, $hmax{x1y2}=$hmax{x1y1} if(!defined($hmin{x1y2}));
  1491. my (%hstep,%htics);
  1492. #-- yrange handling for axes x1y1..x1y8
  1493. for my $idx (0..7) {
  1494. my $a = "x1y".($idx+1);
  1495. next if( !defined($hmax{$a}) || !defined($hmin{$a}) );
  1496. my $yra="y".($idx+1)."range";
  1497. $yra="yrange" if ($yra eq "y1range");
  1498. #-- yrange is specified in plotfile
  1499. if($conf{$yra}) {
  1500. $conf{$yra} = AnalyzeCommand(undef, $1)
  1501. if($conf{$yra} =~ /^({.*})$/);
  1502. if($conf{$yra} =~ /\[(.*):(.*)\]/) {
  1503. $hmin{$a} = $1 if($1 ne "");
  1504. $hmax{$a} = $2 if($2 ne "");
  1505. }
  1506. }
  1507. #-- tics handling
  1508. my $yt="y".($idx+1)."tics";
  1509. $yt="ytics" if ($yt eq"y1tics");
  1510. $htics{$a} = defined($conf{$yt}) ? $conf{$yt} : "";
  1511. #-- Round values, compute a nice step
  1512. ($hstep{$a}, $hmin{$a}, $hmax{$a}) =
  1513. SVG_getSteps($conf{$yra},$hmin{$a},$hmax{$a});
  1514. #Log3 $name, 2, "Axis $a has interval [$hmin{$a},$hmax{$a}],
  1515. # step $hstep{$a}, tics $htics{$a}\n";
  1516. }
  1517. #-- run through all axes for drawing (each only once !)
  1518. foreach my $a (sort keys %hmin) {
  1519. next if(!defined($hmin{$a})); # Bogus case
  1520. #-- safeguarding against pathological data
  1521. if( !$hstep{$a} ){
  1522. $hmax{$a} = $hmin{$a}+1;
  1523. $hstep{$a} = 1;
  1524. }
  1525. #-- Draw the y-axis values and grid
  1526. my $dh = $hmax{$a} - $hmin{$a};
  1527. my $hmul = $dh>0 ? $h/$dh : $h;
  1528. my $axis = 1;
  1529. $axis = $1 if( $a =~ m/x\d+y(\d+)/ );
  1530. my $scale = "y".($axis)."scale"; $scale = "yscale" if( $axis == 1 );
  1531. my $log = ""; $log = $conf{$scale} if( $conf{$scale} );
  1532. my $f_log = int($hmax{$a}) ? (SVG_log10($hmax{$a}) / $hmax{$a}) : 1;
  1533. # offsets
  1534. my ($align,$display,$cll);
  1535. if( $axis <= $use_left_axis ) {
  1536. $off1 = $x - ($axis-1)*$axis_width-4-$th*0.3;
  1537. $off3 = $x - ($axis-1)*$axis_width-4;
  1538. $off4 = $off3+5;
  1539. $align = " text-anchor=\"end\"";
  1540. $display = "";
  1541. $cll = "";
  1542. } elsif( $axis <= $use_left_axis+$use_right_axis ) {
  1543. $off1 = $x+4+$w+($axis-1-$use_left_axis)*$axis_width+$th*0.3;
  1544. $off3 = $x+4+$w+($axis-1-$use_left_axis)*$axis_width-5;
  1545. $off4 = $off3+5;
  1546. $align = "";
  1547. $display = "";
  1548. $cll = "";
  1549. } else {
  1550. $off1 = $x-$th*0.3+30;
  1551. $off3 = $x+30;
  1552. $off4 = $off3+5;
  1553. $align = " text-anchor=\"end\"";
  1554. $display = " display=\"none\" id=\"hline_$axis\"";
  1555. $cll = " class=\"SVGplot l$axis\"";
  1556. }
  1557. #-- grouping
  1558. SVG_pO "<g$display>";
  1559. my $yp = $y + $h;
  1560. #-- axis if not left or right axis
  1561. SVG_pO "<polyline points=\"$off3,$y $off3,$yp\" $cll/>"
  1562. if($a ne "x1y1" && $a ne "x1y2");
  1563. #-- tics handling
  1564. my $tic = $htics{$a};
  1565. #-- tics as in the config-file
  1566. if($tic && $tic !~ m/mirror/) {
  1567. $tic =~ s/^\((.*)\)$/$1/; # Strip ()
  1568. for(my $decimal = 0;
  1569. $decimal < ($log eq 'log'?SVG_log10($hmax{$a}):1);
  1570. $decimal++ ) {
  1571. foreach my $onetic (split(",", $tic)) {
  1572. $onetic =~ s/^ *(.*) *$/$1/;
  1573. my ($tlabel, $tvalue) = split(" ", $onetic);
  1574. $tlabel =~ s/^"(.*)"$/$1/;
  1575. $tvalue = 0 if( !$tvalue );
  1576. $tvalue /= 10 ** $decimal;
  1577. $tlabel = $tvalue if( !$tlabel );
  1578. $off2 = int($y+($hmax{$a}-$tvalue)*$hmul);
  1579. $off2 = int($y+($hmax{$a}-SVG_log10($tvalue)/$f_log)*$hmul)
  1580. if( $log eq 'log' );
  1581. #-- tics
  1582. SVG_pO "<polyline points=\"$off3,$off2 $off4,$off2\" $cll/>";
  1583. #--grids
  1584. my $off6 = $x+$w;
  1585. if( ($a eq "x1y1") && $conf{hasygrid} ) {
  1586. SVG_pO "<polyline points=\"$x,$off2 $off6,$off2\" class=\"vgrid\"/>"
  1587. if($tvalue > $hmin{$a} && $tvalue < $hmax{$a});
  1588. }elsif( ($a eq "x1y2") && $conf{hasy2grid} ) {
  1589. SVG_pO " <polyline points=\"$x,$off2 $off6,$off2\" class=\"vgrid\"/>"
  1590. if($tvalue > $hmin{$a} && $tvalue < $hmax{$a});
  1591. }
  1592. $off2 += $th/4;
  1593. #-- text
  1594. SVG_pO
  1595. "<text x=\"$off1\" y=\"$off2\" class=\"ylabel\"$align>$tlabel</text>";
  1596. }
  1597. }
  1598. #-- tics automatically
  1599. } elsif( $hstep{$a}>0 ) {
  1600. for(my $decimal = 0;
  1601. $decimal < ($log eq 'log'?SVG_log10($hmax{$a}):1);
  1602. $decimal++ ) {
  1603. for(my $i = $hmin{$a}; $i <= $hmax{$a}; $i += $hstep{$a}) {
  1604. my $i = $i / 10 ** $decimal;
  1605. $off2 = int($y+($hmax{$a}-$i)*$hmul);
  1606. $off2 = int($y+($hmax{$a}-SVG_log10($i)/$f_log)*$hmul)
  1607. if( $log eq 'log' );
  1608. #-- tics
  1609. SVG_pO " <polyline points=\"$off3,$off2 $off4,$off2\" $cll/>";
  1610. #--grids
  1611. my $off6 = $x+$w;
  1612. if( ($a eq "x1y1") && $conf{hasygrid} ) {
  1613. my $off6 = $x+$w;
  1614. SVG_pO " <polyline points=\"$x,$off2 $off6,$off2\" class=\"vgrid\"/>"
  1615. if($i > $hmin{$a} && $i < $hmax{$a});
  1616. }elsif( ($a eq "x1y2") && $conf{hasy2grid} ) {
  1617. SVG_pO " <polyline points=\"$x,$off2 $off6,$off2\" class=\"vgrid\"/>"
  1618. if($i > $hmin{$a} && $i < $hmax{$a});
  1619. }
  1620. $off2 += $th/4;
  1621. #-- text
  1622. my $name = ($axis==1 ? "y":"y$axis")."sprintf"; # Forum #88460
  1623. my $txt = sprintf($conf{$name} ? $conf{$name} : "%g", $i);
  1624. SVG_pO
  1625. "<text x=\"$off1\" y=\"$off2\" class=\"ylabel\"$align>$txt</text>";
  1626. }
  1627. }
  1628. }
  1629. SVG_pO "</g>";
  1630. }
  1631. ######################
  1632. # Second loop over the data: draw the measured points
  1633. for(my $idx=$#hdx; $idx >= 0; $idx--) {
  1634. my $a = $conf{lAxis}[$idx];
  1635. SVG_pO "<!-- Warning: No axis for data item $idx defined -->"
  1636. if(!defined($a));
  1637. next if(!defined($a));
  1638. my $axis = 1; $axis = $1 if( $a =~ m/x\d+y(\d+)/ );
  1639. my $scale = "y".($axis)."scale"; $scale = "yscale" if( $axis == 1 );
  1640. my $log = ""; $log = $conf{$scale} if( $conf{$scale} );
  1641. $min = $hmin{$a};
  1642. $hmax{$a} += 1 if($min == $hmax{$a}); # Else division by 0 in the next line
  1643. my $xmul;
  1644. $xmul = $w/($xmax-$xmin) if( $conf{xrange} );
  1645. my $hmul = $h/($hmax{$a}-$min);
  1646. my $ret = "";
  1647. my ($dxp, $dyp) = ($hdx[$idx], $hdy[$idx]);
  1648. SVG_pO "<!-- Warning: No data item $idx defined -->" if(!defined($dxp));
  1649. next if(!defined($dxp));
  1650. my $f_log = int($hmax{$a}) ? (SVG_log10($hmax{$a}) / $hmax{$a}) : 1;
  1651. if( $log eq 'log' ) {
  1652. foreach my $i (1..int(@{$dxp})-1) {
  1653. $dyp->[$i] = SVG_log10($dyp->[$i]) / $f_log;
  1654. }
  1655. }
  1656. my $yh = $y+$h;
  1657. #-- Title attributes
  1658. my $tl = $conf{lTitle}[$idx] ? $conf{lTitle}[$idx] : "";
  1659. #my $dec = int(log($hmul*3)/log(10)); # perl can be compiled without log() !
  1660. my $dec = length(sprintf("%d",$hmul*3))-1;
  1661. $dec = 0 if($dec < 0);
  1662. my $attributes = "id=\"line_$idx\" decimals=\"$dec\" ".
  1663. "style='mask:url(#mask_$name); $conf{lWidth}[$idx]' ".
  1664. "x_min=\"$x\" ".
  1665. ($conf{xrange}?"x_off=\"$xmin\" ":"x_off=\"$fromsec\" ").
  1666. ($conf{xrange}?"x_mul=\"$xmul\" ":"t_mul=\"$tmul\" ").
  1667. "y_h=\"$yh\" y_min=\"$min\" y_mul=\"$hmul\" title=\"$tl\" ".
  1668. ($log eq 'log'?"log_scale=\"$f_log\" ":"").
  1669. "onclick=\"parent.svg_click(evt)\"";
  1670. my $lStyle = $conf{lStyle}[$idx];
  1671. my $isFill = ($conf{lStyle}[$idx] =~ m/fill/);
  1672. my $doClose = $isFill;
  1673. my ($lx, $ly) = (-1,-1);
  1674. my $lType = $conf{lType}[$idx];
  1675. if($lType eq "points" ) {
  1676. foreach my $i (0..int(@{$dxp})-1) {
  1677. my ($x1, $y1) = (int($x+$dxp->[$i]),
  1678. int($y+$h-($dyp->[$i]-$min)*$hmul));
  1679. next if($x1 == $lx && $y1 == $ly);
  1680. $ly = $x1; $ly = $y1;
  1681. $ret = sprintf(" %d,%d %d,%d %d,%d %d,%d %d,%d",
  1682. $x1-3,$y1, $x1,$y1-3, $x1+3,$y1, $x1,$y1+3, $x1-3,$y1);
  1683. SVG_pO "<polyline $attributes $lStyle points=\"$ret\"/>";
  1684. }
  1685. } elsif($lType eq "steps" || $lType eq "fsteps" ) {
  1686. $ret .= sprintf(" %d,%d", $x+$dxp->[0], $y+$h) if($isFill && @{$dxp});
  1687. if(@{$dxp} == 1) {
  1688. my $y1 = $y+$h-($dyp->[0]-$min)*$hmul;
  1689. $ret .= sprintf(" %d,%d %d,%d %d,%d %d,%d",
  1690. $x,$y+$h, $x,$y1, $x+$w,$y1, $x+$w,$y+$h);
  1691. } else {
  1692. my $nEl = int(@{$dxp})-1;
  1693. foreach my $i (1..$nEl) {
  1694. my ($x1, $y1) = ($x+$dxp->[$i-1], $y+$h-($dyp->[$i-1]-$min)*$hmul);
  1695. my ($x2, $y2) = ($x+$dxp->[$i], $y+$h-($dyp->[$i] -$min)*$hmul);
  1696. next if(int($x2) == $lx && int($y2) == $ly);
  1697. $lx = int($x2); $ly = int($y2);
  1698. if($i == $nEl) {
  1699. if($lType eq "steps") {
  1700. $ret .= sprintf(" %d,%d %d,%d %d,%d", $x1,$y1, $x2,$y1, $x2,$y2);
  1701. } else {
  1702. $ret .= sprintf(" %d,%d %d,%d %d,%d", $x1,$y1, $x1,$y2, $x2,$y2);
  1703. }
  1704. } else {
  1705. if($lType eq "steps") {
  1706. $ret .= sprintf(" %d,%d %d,%d", $x1,$y1, $x2,$y1);
  1707. } else {
  1708. $ret .= sprintf(" %d,%d %d,%d", $x1,$y1, $x1,$y2);
  1709. }
  1710. }
  1711. }
  1712. }
  1713. $ret .= sprintf(" %d,%d", $lx, $y+$h) if($isFill && $lx > -1);
  1714. SVG_pO "<polyline $attributes $lStyle points=\"$ret\"/>";
  1715. } elsif($lType eq "histeps" ) {
  1716. $ret .= sprintf(" %d,%d", $x+$dxp->[0], $y+$h) if($isFill && @{$dxp});
  1717. if(@{$dxp} == 1) {
  1718. my $y1 = $y+$h-($dyp->[0]-$min)*$hmul;
  1719. $ret .= sprintf(" %d,%d %d,%d %d,%d %d,%d",
  1720. $x,$y+$h, $x,$y1, $x+$w,$y1, $x+$w,$y+$h);
  1721. } else {
  1722. foreach my $i (1..int(@{$dxp})-1) {
  1723. my ($x1, $y1) = ($x+$dxp->[$i-1], $y+$h-($dyp->[$i-1]-$min)*$hmul);
  1724. my ($x2, $y2) = ($x+$dxp->[$i], $y+$h-($dyp->[$i] -$min)*$hmul);
  1725. next if(int($x2) == $lx && int($y1) == $ly);
  1726. $lx = int($x2); $ly = int($y2);
  1727. $ret .= sprintf(" %d,%d %d,%d %d,%d %d,%d",
  1728. $x1,$y1, ($x1+$x2)/2,$y1, ($x1+$x2)/2,$y2, $x2,$y2);
  1729. }
  1730. }
  1731. $ret .= sprintf(" %d,%d", $lx, $y+$h) if($isFill && $lx > -1);
  1732. SVG_pO "<polyline $attributes $lStyle points=\"$ret\"/>";
  1733. } elsif( $lType eq "bars" ) {
  1734. my $bw = $barwidth*$tmul;
  1735. # bars are all of equal width (see far above !),
  1736. # position rounded to integer multiples of bar width
  1737. foreach my $i (0..int(@{$dxp})-1) {
  1738. my ($x1, $y1) = ( $x + $dxp->[$i] - $bw,
  1739. $y +$h-($dyp->[$i]-$min)*$hmul);
  1740. my $curBw = $bw;
  1741. if($x1 < $x) {
  1742. $curBw -= $x - $x1;
  1743. $x1 = $x;
  1744. }
  1745. my ($x2, $y2) = ($curBw, ($dyp->[$i]-$min)*$hmul);
  1746. SVG_pO "<rect $attributes $lStyle x=\"$x1\" y=\"$y1\" ".
  1747. "width=\"$x2\" height=\"$y2\"/>";
  1748. }
  1749. } elsif( $lType eq "ibars" ) { # Forum #35268
  1750. if(@{$dxp} == 1) {
  1751. my $y1 = $y+$h-($dyp->[0]-$min)*$hmul;
  1752. $ret .= sprintf(" %d,%d %d,%d %d,%d %d,%d",
  1753. $x,$y+$h, $x,$y1, $x+$w,$y1, $x+$w,$y+$h);
  1754. } else {
  1755. # interconnected bars (ibars):
  1756. # these bars will connect all datapoints. so the width of the bars
  1757. # might vary depending on the distance between data points
  1758. foreach my $i (1..int(@{$dxp})-1) {
  1759. my $x1 = $x + $dxp->[$i-1];
  1760. my $y1 = $y +$h-($dyp->[$i]-$min)*$hmul;
  1761. my $x2 = $x + $dxp->[$i]; # used to calculate bar width later
  1762. my $height = ($dyp->[$i]-$min)*$hmul;
  1763. my $bw = $x2 - $x1;
  1764. SVG_pO "<rect $attributes $lStyle x=\"$x1\" y=\"$y1\" ".
  1765. "width=\"$bw\" height=\"$height\"/>";
  1766. }
  1767. }
  1768. } elsif($lType =~ m/^horizontalLine(From|To)$/) {
  1769. $attributes =~ s/id=\"line_[^\"]+\"//; # no id for each line
  1770. foreach my $i (0..int(@{$dxp})-2) {
  1771. my ($x1, $y1) = ($x+$dxp->[$i], $y+$h-($dyp->[$i] -$min)*$hmul);
  1772. my ($x2, $y2) = ($x+$dxp->[$i+1], $y+$h-($dyp->[$i+1]-$min)*$hmul);
  1773. $x2 += 0.2 if($x1 == $x2);
  1774. my $Y = ($lType eq "horizontalLineFrom" ? $y1 : $y2);
  1775. SVG_pO "<line $attributes $lStyle x1='$x1' y1='$Y' x2='$x2' y2='$Y'/>";
  1776. }
  1777. } else { # lines and everything else
  1778. my ($ymin, $ymax) = (99999999, -99999999);
  1779. my %lt =(cubic=>"C",quadratic=>"Q",quadraticSmooth=>"T");
  1780. my ($x1, $y1);
  1781. my $lt = ($lt{$lType} ? $lt{$lType} : "L"); # defaults to line
  1782. my $maxIdx = int(@{$dxp})-1;
  1783. foreach my $i (0..$maxIdx) {
  1784. if( !defined($dxp->[$i]) ) { # specials
  1785. if( $dyp->[$i] =~ m/^;$/ ) { # new line segment after newline
  1786. my @tvals = split("[ ,]", $ret);
  1787. if (@tvals > 2) {
  1788. if ($tvals[0] ne "M") { # just points, no M/L
  1789. $ret = sprintf("M %d,%d $lt $ret", $tvals[1],$tvals[2]);
  1790. }
  1791. }
  1792. SVG_pO "<path $attributes $lStyle d=\"$ret\"/>";
  1793. $ret = "";
  1794. } elsif( $dyp->[$i] =~ m/^;c (.*)/ ) {# close polyline ?
  1795. $doClose = $1;
  1796. } elsif( $dyp->[$i] =~ m/^;ls (\w+)?/ ) {# line style
  1797. if( $1 ) {
  1798. $lStyle = "class='SVGplot $1'";
  1799. } else {
  1800. $lStyle = $conf{lStyle}[$idx];
  1801. }
  1802. # marker with optional text
  1803. } elsif( $dyp->[$i] =~ m/^;m (\S+)\s(\S+)(\s(\S+)\s(.*))?/ ) {
  1804. if( defined($xmin) ) {
  1805. $x1 = int($x+($1-$xmin)*$xmul);
  1806. } else {
  1807. $x1 = ($tmul ? int((SVG_time_to_sec($1)-$fromsec)*$tmul) : $x);
  1808. }
  1809. $y1 = int($y+$h-($2-$min)*$hmul);
  1810. my $ret = sprintf("%d,%d %d,%d %d,%d %d,%d %d,%d",
  1811. $x1-3,$y1, $x1,$y1-3, $x1+3,$y1, $x1,$y1+3, $x1-3,$y1);
  1812. SVG_pO "<polyline $attributes $lStyle points=\"$ret\"/>";
  1813. SVG_pO "<text x=\"$x1\" y=\"$y1\" $lStyle text-anchor=\"$4\">$5".
  1814. "</text>" if( $3 );
  1815. } elsif( $dyp->[$i] =~ m/^;t (\S+)\s(\S+)\s(\S+)\s(.*)/ ) {# text
  1816. if( defined($xmin) ) {
  1817. $x1 = int($x+($1-$xmin)*$xmul);
  1818. } else {
  1819. $x1 = ($tmul ? int((SVG_time_to_sec($1)-$fromsec)*$tmul) : $x);
  1820. }
  1821. $y1 = int($y+$h-($2-$min)*$hmul);
  1822. SVG_pO "<text x=\"$x1\" y=\"$y1\" $lStyle text-anchor=\"$3\">$4".
  1823. "</text>";
  1824. } else {
  1825. Log3 $name, 2, "unknown special $dyp->[$i]"
  1826. }
  1827. next;
  1828. }
  1829. ($x1, $y1) = (int($x+$dxp->[$i]),
  1830. int($y+$h-($dyp->[$i]-$min)*$hmul));
  1831. next if($x1 == $lx && $y1 == $ly);
  1832. # calc ymin/ymax for points with the same x coordinates
  1833. if($x1 == $lx && $i < $maxIdx) {
  1834. $ymin = $y1 if($y1 < $ymin);
  1835. $ymax = $y1 if($y1 > $ymax);
  1836. $ly = $y1;
  1837. next;
  1838. }
  1839. if($i == 0) {
  1840. if($doClose) {
  1841. $ret .= sprintf("M %d,%d L %d,%d $lt", $x1,$y+$h, $x1,$y1);
  1842. } else {
  1843. $ret .= sprintf("M %d,%d $lt", $x1,$y1);
  1844. }
  1845. $lx = $x1; $ly = $y1;
  1846. next;
  1847. }
  1848. # plot ymin/ymax range for points with the same x coordinates
  1849. if( $ymin != 99999999 ) {
  1850. $ret .= sprintf(" %d,%d", $lx, $ymin);
  1851. $ret .= sprintf(" %d,%d", $lx, $ymax);
  1852. $ret .= sprintf(" %d,%d", $lx, $ly);
  1853. ($ymin, $ymax) = (99999999, -99999999);
  1854. }
  1855. $ret .= sprintf(" %d,%d", $x1, $y1) if ($lt ne "T");
  1856. $ret .= sprintf(" %.1f,%.1f", (($lx+$x1)/2.0), (($ly+$y1)/2.0))
  1857. if (($lt eq "T") && ($lx > -1));
  1858. $lx = $x1; $ly = $y1;
  1859. }
  1860. #-- calculate control points for interpolation
  1861. $ret = SVG_getControlPoints($ret) if (($lt eq "C") || ($lt eq "Q"));
  1862. #-- insert last point for filled line
  1863. $ret .= sprintf(" %.1f,%.1f", $x1, $y1) if(($lt eq "T") && defined($x1));
  1864. $ret .= sprintf(" L %d,%d Z", $x1, $y+$h) if($doClose && defined($x1));
  1865. if($ret =~ m/^ (\d+),(\d+)/) { # just points, no M/L
  1866. $ret = sprintf("M %d,%d $lt ", $1, $2).$ret;
  1867. }
  1868. $ret = "" if($maxIdx == 0);
  1869. SVG_pO "<path $attributes $lStyle d=\"$ret\"/>";
  1870. }
  1871. }
  1872. ######################
  1873. # Plot caption (title) at the end, should be draw on top of the lines
  1874. my $caption_pos = SVG_Attr($parent_name, $name, "captionPos", 'right');
  1875. my( $li,$ri ) = (0,0);
  1876. for my $i (0..int(@{$conf{lTitle}})-1) {
  1877. my $caption_anchor = "end";
  1878. if( $caption_pos eq 'auto' ) {
  1879. my $a = $conf{lAxis}[$i];
  1880. my $axis = 1; $axis = $1 if( $a && $a =~ m/x\d+y(\d+)/ );
  1881. if( $axis <= $use_left_axis ) {
  1882. $caption_anchor = "beginning";
  1883. } else {
  1884. $caption_anchor = "end";
  1885. }
  1886. } elsif( $caption_pos eq 'left' ) {
  1887. $caption_anchor = "beginning";
  1888. }
  1889. my $txtoff1 = $nr_left_axis*$axis_width + $w - $th/2;
  1890. $txtoff1 = $nr_left_axis*$axis_width+$th/2
  1891. if($caption_anchor eq 'beginning');
  1892. my $j = $i+1;
  1893. my $t = $conf{lTitle}[$i];
  1894. next if( !$t );
  1895. my $txtoff2;
  1896. if( $caption_anchor eq 'beginning' ) {
  1897. $txtoff2 = $y + 3 + $th/1.3 + $th * $li;
  1898. ++$li;
  1899. } else {
  1900. $txtoff2 = $y + 3 + $th/1.3 + $th * $ri;
  1901. ++$ri;
  1902. }
  1903. my $desc = "";
  1904. if(defined($data{"min$j"}) && $data{"min$j"} ne "undef" &&
  1905. defined($data{"currval$j"}) && $data{"currval$j"} ne "undef") {
  1906. $desc = sprintf("%s: Min:%g Max:%g Last:%g",
  1907. $t, $data{"min$j"}, $data{"max$j"}, $data{"currval$j"});
  1908. }
  1909. my $style = $conf{lStyle}[$i];
  1910. $style =~ s/class="/class="legend /;
  1911. SVG_pO "<text line_id=\"line_$i\" x=\"$txtoff1\" y=\"$txtoff2\" ".
  1912. "text-anchor=\"$caption_anchor\" $style>$t<title>$desc</title></text>";
  1913. $txtoff2 += $th;
  1914. }
  1915. my $fnName = SVG_isEmbed($FW_wname) ? "parent.window.svg_init" : "svg_init";
  1916. SVG_pO "<script type='text/javascript'>if(typeof $fnName == 'function') ".
  1917. "$fnName('SVGPLOT_$name')</script>";
  1918. SVG_pO "</svg>";
  1919. return $SVG_RET;
  1920. }
  1921. ######################
  1922. # Derives control points for interpolation of bezier curves for SVG "path"
  1923. sub
  1924. SVG_getControlPoints($)
  1925. {
  1926. my ($ret) = @_;
  1927. my (@xa, @ya);
  1928. my (@vals) = split("[ ,]", $ret);
  1929. my (@xcp1, @xcp2, @ycp1, @ycp2);
  1930. my $header = "";
  1931. foreach my $i (0..int(@vals)-1) {
  1932. $header .= $vals[$i] . ($i == 1 ? "," : " ");
  1933. if ($vals[$i] eq "C" || $vals[$i] eq "Q") {
  1934. my $lt = $vals[$i];
  1935. $i++;
  1936. my $ii = 0;
  1937. while (defined($vals[$i])) {
  1938. ($xa[$ii], $ya[$ii]) = ($vals[$i], $vals[$i+1]);
  1939. $i += 2;
  1940. $ii++;
  1941. }
  1942. return(1) if (@xa < 2);
  1943. SVG_calcControlPoints(\@xcp1, \@xcp2, \@ycp1, \@ycp2, \@xa, \@ya);
  1944. $ret = $header;
  1945. foreach my $i (1..int(@xa)-1) {
  1946. $ret .= sprintf(" %d,%d,%d,%d,%d,%d", $xcp1[$i-1], $ycp1[$i-1], $xcp2[$i-1], $ycp2[$i-1], $xa[$i], $ya[$i]) if ($lt eq "C");
  1947. $ret .= sprintf(" %d,%d,%d,%d", $xcp2[$i-1], $ycp2[$i-1], $xa[$i], $ya[$i]) if ($lt eq "Q");
  1948. }
  1949. }
  1950. }
  1951. return($ret);
  1952. }
  1953. ######################
  1954. # Calculate control points for interpolation of bezier curves for SVG "path"
  1955. sub
  1956. SVG_calcControlPoints($$$$$$)
  1957. {
  1958. my ($px1, $px2, $py1, $py2, $inputx, $inputy) = @_;
  1959. my $n = @{$inputx};
  1960. # Loop over all Points in Input arrays
  1961. for (my $i=0; $i<$n-1; $i++) {
  1962. my (@lxp, @lyp);
  1963. # Loop over 4 Points around actual Point to calculate
  1964. my $iloc = 0;
  1965. for (my $ii=$i-1; $ii<=$i+2; $ii++) {
  1966. my $icorr = $ii;
  1967. $icorr = 0 if ($icorr < 0);
  1968. $icorr = $n-1 if ($icorr > $n-1);
  1969. $lxp[$iloc] = $inputx->[$icorr];
  1970. $lyp[$iloc] = $inputy->[$icorr];
  1971. $iloc++;
  1972. }
  1973. # Calulcation of first control Point using first 3 Points around actual
  1974. # Point
  1975. my $m1x = ($lxp[0]+$lxp[1])/2.0;
  1976. my $m1y = ($lyp[0]+$lyp[1])/2.0;
  1977. my $m2x = ($lxp[1]+$lxp[2])/2.0;
  1978. my $m2y = ($lyp[1]+$lyp[2])/2.0;
  1979. my $l1 = sqrt(($lxp[0]-$lxp[1])*($lxp[0]-$lxp[1]) +
  1980. ($lyp[0]-$lyp[1])*($lyp[0]-$lyp[1]));
  1981. my $l2 = sqrt(($lxp[1]-$lxp[2])*($lxp[1]-$lxp[2]) +
  1982. ($lyp[1]-$lyp[2])*($lyp[1]-$lyp[2]));
  1983. my $dxm = ($m1x - $m2x);
  1984. my $dym = ($m1y - $m2y);
  1985. my $k = 0;
  1986. $k = $l2/($l1+$l2) if (($l1+$l2) != 0);
  1987. my $tx = $lxp[1] - ($m2x + $dxm*$k);
  1988. my $ty = $lyp[1] - ($m2y + $dym*$k);
  1989. $px1->[$i] = $m2x + $tx;
  1990. $py1->[$i] = $m2y + $ty;
  1991. # Calulcation of second control Point using last 3 Points around actual
  1992. # Point
  1993. $m1x = ($lxp[1]+$lxp[2])/2.0;
  1994. $m1y = ($lyp[1]+$lyp[2])/2.0;
  1995. $m2x = ($lxp[2]+$lxp[3])/2.0;
  1996. $m2y = ($lyp[2]+$lyp[3])/2.0;
  1997. $l1 = sqrt(($lxp[1]-$lxp[2])*($lxp[1]-$lxp[2])+
  1998. ($lyp[1]-$lyp[2])*($lyp[1]-$lyp[2]));
  1999. $l2 = sqrt(($lxp[2]-$lxp[3])*($lxp[2]-$lxp[3])+
  2000. ($lyp[2]-$lyp[3])*($lyp[2]-$lyp[3]));
  2001. $dxm = ($m1x - $m2x);
  2002. $dym = ($m1y - $m2y);
  2003. $k=0;
  2004. $k = $l2/($l1+$l2) if (($l1+$l2) != 0);
  2005. $tx = $lxp[2] - ($m2x + $dxm*$k);
  2006. $ty = $lyp[2] - ($m2y + $dym*$k);
  2007. $px2->[$i] = $m1x + $tx;
  2008. $py2->[$i] = $m1y + $ty;
  2009. }
  2010. return (1);
  2011. }
  2012. sub
  2013. SVG_fmtTime($$)
  2014. {
  2015. my ($sepfmt, $sec) = @_;
  2016. my @tarr = split("[ :]+", localtime($sec));
  2017. my ($sep, $fmt) = split(" ", $sepfmt, 2);
  2018. my $ret = "";
  2019. $fmt = "" if(!defined($fmt));
  2020. for my $f (split(" ", $fmt)) {
  2021. $ret .= $sep if($ret);
  2022. $ret .= $tarr[$f];
  2023. }
  2024. return $ret;
  2025. }
  2026. sub
  2027. SVG_time_align($$)
  2028. {
  2029. my ($v,$align) = @_;
  2030. return $v if(!$align);
  2031. if($align == 1) { # Look for the beginning of the week
  2032. for(;;) {
  2033. my @a = localtime($v);
  2034. return $v if($a[6] == 0);
  2035. $v += 86400;
  2036. }
  2037. }
  2038. if($align == 2) { # Look for the beginning of the month
  2039. for(;;) {
  2040. my @a = localtime($v);
  2041. return $v if($a[3] == 1);
  2042. $v += 86400;
  2043. }
  2044. }
  2045. }
  2046. sub
  2047. SVG_doround($$$)
  2048. {
  2049. my ($v, $step, $isup) = @_;
  2050. $step = 1 if(!$step); # Avoid division by zero
  2051. my $d = $v/$step;
  2052. my $dr = int($d);
  2053. return $v if($d == $dr);
  2054. if($v >= 0) {
  2055. return int($v/$step)*$step+($isup ? $step : 0);
  2056. } else {
  2057. return int($v/$step)*$step+($isup ? 0 : -$step);
  2058. }
  2059. }
  2060. ##################
  2061. # print (append) to output
  2062. sub
  2063. SVG_pO($)
  2064. {
  2065. my $arg = shift;
  2066. return if(!defined($arg));
  2067. $SVG_RET .= $arg;
  2068. $SVG_RET .= "\n";
  2069. }
  2070. ##################
  2071. # this is a helper function which creates a PNG image from a given plot
  2072. sub
  2073. plotAsPng(@)
  2074. {
  2075. my (@plotName) = @_;
  2076. my (@webs, $mimetype, $svgdata, $rsvg, $pngImg);
  2077. @webs=devspec2array("TYPE=FHEMWEB");
  2078. foreach(@webs) {
  2079. if(!InternalVal($_,'TEMPORARY',undef)) {
  2080. $FW_wname=InternalVal($_,'NAME','');
  2081. last;
  2082. }
  2083. }
  2084. #Debug "FW_wname= $FW_wname, plotName= $plotName[0]";
  2085. $FW_RET = undef;
  2086. $FW_webArgs{dev} = $plotName[0];
  2087. $FW_webArgs{logdev} = InternalVal($plotName[0], "LOGDEVICE", "");
  2088. $FW_webArgs{gplotfile} = InternalVal($plotName[0], "GPLOTFILE", "");
  2089. $FW_webArgs{logfile} = InternalVal($plotName[0], "LOGFILE", "CURRENT");
  2090. $FW_pos{zoom} = $plotName[1] if $plotName[1];
  2091. $FW_pos{off} = $plotName[2] if $plotName[2];
  2092. ($mimetype, $svgdata) = SVG_showLog("unused");
  2093. #Debug "MIME type= $mimetype";
  2094. #Debug "SVG= $svgdata";
  2095. my ($w, $h) = split(",", AttrVal($plotName[0],"plotsize","800,160"));
  2096. $svgdata =~ s/<\/svg>/<polyline opacity="0" points="0,0 $w,$h"\/><\/svg>/;
  2097. $svgdata =~ s/\.SVGplot\./\./g;
  2098. eval {
  2099. require Image::LibRSVG;
  2100. $rsvg = new Image::LibRSVG();
  2101. $rsvg->loadImageFromString($svgdata);
  2102. $pngImg = $rsvg->getImageBitmap();
  2103. };
  2104. Log3 $FW_wname, 1,
  2105. "plotAsPng(): Cannot create plot as png image for \"" .
  2106. join(" ", @plotName) . "\": $@"
  2107. if($@ or !defined($pngImg) or ($pngImg eq ""));
  2108. return $pngImg if $pngImg;
  2109. return;
  2110. }
  2111. ##################
  2112. 1;
  2113. ##################
  2114. =pod
  2115. =item helper
  2116. =item summary draw an SVG-Plot based on FileLog or DbLog data
  2117. =item summary_DE malt ein SVG-Plot aus FileLog oder DbLog Daten
  2118. =begin html
  2119. <a name="SVG"></a>
  2120. <h3>SVG</h3>
  2121. <ul>
  2122. <a name="SVGlinkdefine"></a>
  2123. <b>Define</b>
  2124. <ul>
  2125. <code>define &lt;name&gt; SVG
  2126. &lt;logDevice&gt;:&lt;gplotfile&gt;:&lt;logfile&gt;</code>
  2127. <br><br>
  2128. This is the Plotting/Charting device of FHEMWEB
  2129. Examples:
  2130. <ul>
  2131. <code>define MyPlot SVG inlog:temp4hum4:CURRENT</code><br>
  2132. </ul>
  2133. <br>
  2134. Notes:
  2135. <ul>
  2136. <li>Normally you won't define an SVG device manually, as
  2137. FHEMWEB makes it easy for you, just plot a logfile (see <a
  2138. href="#logtype">logtype</a>) and click on "Create SVG instance".
  2139. Specifying CURRENT as a logfilename will always access the current
  2140. logfile, even if its name changes regularly.</li>
  2141. <li>For historic reasons this module uses a Gnuplot file description
  2142. to store different attributes. Some special commands (beginning with
  2143. #FileLog or #DbLog) are used additionally, and not all gnuplot
  2144. attribtues are implemented.</li>
  2145. </ul>
  2146. </ul>
  2147. <a name="SVGset"></a>
  2148. <b>Set</b>
  2149. <ul>
  2150. <li>copyGplotFile<br>
  2151. Copy the currently specified gplot file to a new file, which is named
  2152. after the SVG device, existing files will be overwritten.
  2153. This operation is needed in order to use the plot editor (see below)
  2154. without affecting other SVG instances using the same gplot file.
  2155. Creating the SVG instance from the FileLog detail menu will also
  2156. create a unique gplot file, in this case this operation is not needed.
  2157. </li>
  2158. </ul><br>
  2159. <a name="SVGget"></a>
  2160. <b>Get</b> <ul>N/A</ul><br>
  2161. <a name="SVGattr"></a>
  2162. <b>Attributes</b>
  2163. <ul>
  2164. <li><a href="#endPlotNow">endPlotNow</a></li><br>
  2165. <li><a href="#endPlotToday">endPlotToday</a></li><br>
  2166. <a name="captionLeft"></a>
  2167. <li>captionLeft<br>
  2168. Show the legend on the left side (deprecated, will be autoconverted to
  2169. captionPos)
  2170. </li><br>
  2171. <a name="captionPos"></a>
  2172. <li>captionPos<br>
  2173. right - Show the legend on the right side (default)<br>
  2174. left - Show the legend on the left side<br>
  2175. auto - Show the legend labels on the left or on the right side depending
  2176. on the axis it belongs to<br>
  2177. </li><br>
  2178. <a name="fixedrange"></a>
  2179. <li>fixedrange [offset]<br>
  2180. Contains two time specs in the form YYYY-MM-DD separated by a space.
  2181. In plotmode gnuplot-scroll(-svg) or SVG the given time-range will be
  2182. used, and no scrolling for this SVG will be possible. Needed e.g. for
  2183. looking at last-years data without scrolling.<br><br> If the value is
  2184. one of hour, &lt;N&gt;hours, day, &lt;N&gt;days, week, month, year,
  2185. &lt;N&gt;years then set the zoom level for this SVG independently of
  2186. the user specified zoom-level. This is useful for pages with multiple
  2187. plots: one of the plots is best viewed in with the default (day) zoom,
  2188. the other one with a week zoom.
  2189. <br>
  2190. If given, the optional integer parameter offset refers to a different
  2191. period (e.g. last year: fixedrange year -1, 2 days ago: fixedrange day
  2192. -2).
  2193. </li><br>
  2194. <a name="fixedoffset"></a>
  2195. <li>fixedoffset &lt;nDays&gt;<br>
  2196. Set an fixed offset (in days) for the plot.
  2197. </li><br>
  2198. <a name="label"></a>
  2199. <li>label<br>
  2200. Double-Colon separated list of values. The values will be used to replace
  2201. &lt;L#&gt; type of strings in the .gplot file, with # beginning at 1
  2202. (&lt;L1&gt;, &lt;L2&gt;, etc.). Each value will be evaluated as a perl
  2203. expression, so you have access e.g. to the Value functions.<br><br>
  2204. If the plotmode is gnuplot-scroll(-svg) or SVG, you can also use the min,
  2205. max, mindate, maxdate, avg, cnt, sum, firstval, firstdate, currval (last
  2206. value) and currdate (last date) values of the individual curves, by
  2207. accessing the corresponding values from the data hash, see the example
  2208. below:<br>
  2209. <ul>
  2210. <li>Fixed text for the right and left axis:<br>
  2211. <ul>
  2212. <li>Fhem config:<br>
  2213. attr wl_1 label "Temperature"::"Humidity"</li>
  2214. <li>.gplot file entry:<br>
  2215. set ylabel &lt;L1&gt;<br>
  2216. set y2label &lt;L2&gt;</li>
  2217. </ul></li>
  2218. <li>Title with maximum and current values of the 1st curve (FileLog)
  2219. <ul>
  2220. <li>Fhem config:<br>
  2221. attr wl_1 label "Max $data{max1}, Current $data{currval1}"</li>
  2222. <li>.gplot file entry:<br>
  2223. set title &lt;L1&gt;<br></li>
  2224. </ul></li>
  2225. </ul>
  2226. The value minAll and maxAll (representing the minimum/maximum over all
  2227. values) is also available from the data hash.
  2228. <br>Deprecated, see plotReplace.
  2229. </li><br>
  2230. <li><a href="#nrAxis">nrAxis</a></li><br>
  2231. <a name="plotfunction"></a>
  2232. <li>plotfunction<br>
  2233. Space value separated list of values. The value will be used to replace
  2234. &lt;SPEC#&gt; type of strings in the .gplot file, with # beginning at 1
  2235. (&lt;SPEC1&gt;, &lt;SPEC2&gt;, etc.) in the #FileLog or #DbLog directive.
  2236. With this attribute you can use the same .gplot file for multiple devices
  2237. with the same logdevice.
  2238. <ul><b>Example:</b><br>
  2239. <li>#FileLog &lt;SPEC1&gt;<br>
  2240. with: attr &lt;SVGdevice&gt; plotfunction "4:IR\x3a:0:"<br>
  2241. instead of<br>
  2242. #FileLog 4:IR\x3a:0:
  2243. </li>
  2244. <li>#DbLog &lt;SPEC1&gt;<br>
  2245. with: attr &lt;SVGdevice&gt; plotfunction
  2246. "Garage_Raumtemp:temperature::"<br> instead of<br>
  2247. #DbLog Garage_Raumtemp:temperature::
  2248. </li>
  2249. </ul>
  2250. Deprecated, see plotReplace.
  2251. </li><br>
  2252. <li><a href="#plotmode">plotmode</a></li><br>
  2253. <a name="plotReplace"></a>
  2254. <li>plotReplace<br>
  2255. space separated list of key=value pairs. value may contain spaces if
  2256. enclosed in "" or {}. value will be evaluated as a perl expression, if it
  2257. is enclosed in {}.
  2258. <br>
  2259. In the .gplot file &lt;key&gt; is replaced with the corresponding value,
  2260. the evaluation of {} takes place <i>after</i> the input file is
  2261. processed, so $data{min1} etc can be used.
  2262. <br>
  2263. %key% will be repaced <i>before</i> the input file is processed, this
  2264. expression can be used to replace parameters for the input processing.
  2265. </li><br>
  2266. <li><a href="#plotsize">plotsize</a></li><br>
  2267. <li><a href="#plotWeekStartDay">plotWeekStartDay</a></li><br>
  2268. <a name="startDate"></a>
  2269. <li>startDate<br>
  2270. Set the start date for the plot. Used for demo installations.
  2271. </li><br>
  2272. <a name="title"></a>
  2273. <li>title<br>
  2274. A special form of label (see above), which replaces the string &lt;TL&gt;
  2275. in the .gplot file. It defaults to the filename of the logfile.
  2276. <br>Deprecated, see plotReplace.
  2277. </li><br>
  2278. </ul>
  2279. <br>
  2280. <a name="plotEditor"></a>
  2281. <b>Plot-Editor</b>
  2282. <br>
  2283. This editor is visible on the detail screen of the SVG instance.
  2284. Most features are obvious here, up to some exceptions:
  2285. <ul>
  2286. <li>if you want to omit the title for a Diagram label, enter notitle in the
  2287. input field.</li>
  2288. <li>if you want to specify a fixed value (not taken from a column) if a
  2289. string found (e.g. 1 if the FS20 switch is on and 0 if it is off), then
  2290. you have to specify the Tics first, and write the .gplot file, before you
  2291. can select this value from the dropdown.<br>
  2292. Example:
  2293. <ul>
  2294. Enter in the Tics field: ("On" 1, "Off" 0)<br>
  2295. Write .gplot file<br>
  2296. Select "1" from the column dropdown (note the double quote!) for the
  2297. regexp switch.on, and "0" for the regexp switch.off.<br>
  2298. Write .gplot file again<br>
  2299. </ul></li>
  2300. <li>If the range is of the form {...}, then it will be evaluated with perl.
  2301. The result is a string, and must have the form [min:max]
  2302. </li>
  2303. </ul>
  2304. The visibility of the ploteditor can be configured with the FHEMWEB attribute
  2305. <a href="#ploteditor">ploteditor</a>.
  2306. <br>
  2307. </ul>
  2308. =end html
  2309. =begin html_DE
  2310. <a name="SVG"></a>
  2311. <h3>SVG</h3>
  2312. <ul>
  2313. <a name="SVGlinkdefine"></a>
  2314. <b>Define</b>
  2315. <ul>
  2316. <code>define &lt;name&gt; SVG &lt;logDevice&gt;:&lt;gplotfile&gt;:&lt;logfile&gt;</code>
  2317. <br><br>
  2318. Dies ist das Zeichenmodul von FHEMWEB, mit dem Vektorgrafiken (SVG) erzeugt
  2319. werden. <br><br>
  2320. Beispiel:
  2321. <ul>
  2322. <code>define MyPlot SVG inlog:temp4hum4:CURRENT</code><br>
  2323. </ul>
  2324. <br>
  2325. Hinweise:
  2326. <ul>
  2327. <li>Normalerweise m&uuml;ssen SVG-Ger&auml;te nicht manuell erzeugt
  2328. werden, da FHEMWEB es f&uuml;r den Nutzer einfach macht: man muss in
  2329. der Detailansicht eines FileLogs wechseln und auf "Create SVG instance"
  2330. klicken.</li>
  2331. <li>CURRENT als &lt;logfile&gt; wird immer das aktuelle Logfile
  2332. benutzen, selbst dann, wenn der Name des Logfiles sich
  2333. regelm&auml;&szlig;ig &auml;ndert. </li>
  2334. <li>Aus historischen Gr&uuml;nden ben&ouml;tigt jede SVG-Instanz eine
  2335. sog. .gplot Datei, die auch als Input f&uuml;r das gnuplot Programm
  2336. verwendet werden kann. Einige besondere Zeilen (welche mit #FileLog
  2337. oder #DbLog beginnen) werden zus&auml;tzlich benutzt, diese werden von
  2338. gnuplot als Kommentar betrachtet. Auf der anderen Seite implementiert
  2339. dieses Modul nicht alle gnuplot-Attribute.</li>
  2340. </ul>
  2341. </ul>
  2342. <br>
  2343. <a name="SVGset"></a>
  2344. <b>Set</b>
  2345. <ul>
  2346. <li>copyGplotFile<br>
  2347. Kopiert die aktuell ausgew&auml;hlte .gplot Datei in eine neue Datei, die
  2348. den Namen der SVG Instanz tr&auml;gt; bereits bestehende Dateien mit
  2349. gleichem Namen werden &uuml;berschrieben. Diese Vorgehensweise ist
  2350. notwendig, wenn man den Ploteditor benutzt. Erzeugt man aus der
  2351. Detailansicht des FileLogs die SVG Instanz, wird eine eindeutige
  2352. .gplot-Datei erzeugt. In diesem Fall ist dieses Befehl nicht
  2353. erforderlich.</li>
  2354. </ul><br>
  2355. <a name="SVGget"></a>
  2356. <b>Get</b> <ul>N/A</ul><br>
  2357. <a name="SVGattr"></a>
  2358. <b>Attribute</b>
  2359. <ul>
  2360. <a name="captionLeft"></a>
  2361. <li>captionLeft<br>
  2362. Anzeigen der Legende auf der linken Seite. &Uuml;berholt, wird
  2363. automatisch nach captionPos konvertiert.
  2364. </li><br>
  2365. <a name="captionPos"></a>
  2366. <li>captionPos<br>
  2367. right - Anzeigen der Legende auf der rechten Seite (default)<br>
  2368. left - Anzeigen der Legende auf der linken Seite<br>
  2369. auto - Anzeigen der Labels der Legende auf der linken oder rechten Seite
  2370. je nach Achsenzugeh&ouml;rigkeit<br>
  2371. </li><br>
  2372. <li><a href="#endPlotNow">endPlotNow</a></li><br>
  2373. <li><a href="#endPlotToday">endPlotToday</a></li><br>
  2374. <a name="fixedoffset"></a>
  2375. <li>fixedoffset &lt;nTage&gt;<br>
  2376. Verschiebt den Plot-Offset um einen festen Wert (in Tagen).
  2377. </li><br>
  2378. <a name="fixedrange"></a>
  2379. <li>fixedrange [offset]<br>
  2380. Erste Alternative:<br>
  2381. Enth&auml;lt zwei Zeit-Spezifikationen in der Schreibweise YYYY-MM-DD,
  2382. getrennt durch ein Leerzeichen. scrollen der Zeitachse ist nicht
  2383. m&ouml;glich, es wird z.B. verwendet, um sich die Daten verschiedener
  2384. Jahre auf eine Seite anzusehen.<br><br>
  2385. Zweite Alternative:<br>
  2386. Wenn der Wert entweder hour, &lt;N&gt;hours, day, &lt;N&gt;days, week,
  2387. month, year oder &lt;N&gt;years lautet, kann der Zoom-Level f&uuml;r
  2388. dieses SVG unabh&auml;ngig vom User-spezifischen Zoom eingestellt werden.
  2389. Diese Einstellung ist n&uuml;tzlich f&uuml;r mehrere Plots auf einer
  2390. Seite: Eine Grafik ist mit dem Standard-Zoom am aussagekr&auml;ftigsten,
  2391. die anderen mit einem Zoom &uuml;ber eine Woche. Der optionale
  2392. ganzzahlige Parameter [offset] setzt ein anderes Zeitintervall (z.B.
  2393. letztes Jahr: <code>fixedrange year -1</code>, vorgestern: <code>
  2394. fixedrange day -2</code>).
  2395. </li><br>
  2396. <a name="label"></a>
  2397. <li>label<br>
  2398. Eine Liste, bei der die einzelnen Werte mit einem zweifachen Doppelpunkt
  2399. voneinander getrennt werden. Diese Liste wird verwendet um die &lt;L#&gt;
  2400. Zeichenfolgen in der .gplot-Datei zu ersetzen. Dabei steht das # f&uuml;r
  2401. eine laufende Ziffer beginnend mit 1 (&lt;L1&gt;, &lt;L2&gt;, usw.).
  2402. Jeder Wert wird als Perl-Ausdruck bewertet, deshalb hat man Zugriff z.B.
  2403. auf die hinterlegten Funktionen. <br><br>
  2404. Egal, ob es sich bei der Plotart um gnuplot-scroll(-svg) oder SVG
  2405. handelt, es k&ouml;nnen ebenfalls die Werte der individuellen Kurve
  2406. f&uuml;r min, max, mindate, maxdate, avg, cnt, sum, currval (letzter
  2407. Wert) und currdate (letztes Datum) durch Zugriff der entsprechenden Werte
  2408. &uuml;ber das data Hash verwendet werden. Siehe untenstehendes
  2409. Beispiel:<br>
  2410. <ul>
  2411. <li>Beschriftunng der rechten und linken y-Achse:<br>
  2412. <ul>
  2413. <li>Fhem config:<br>
  2414. <code>attr wl_1 label "Temperature"::"Humidity"</code></li>
  2415. <li>Eintrag in der .gplot-Datei:<br>
  2416. <code>set ylabel &lt;L1&gt;<br>
  2417. set y2label &lt;L2&gt;</code></li>
  2418. </ul>
  2419. </li>
  2420. <li>&Uuml;berschrift aus Maximum und dem letzten Wert der ersten
  2421. Kurve(FileLog)
  2422. <ul>
  2423. <li>Fhem config:<br>
  2424. <code>attr wl_1 label "Max $data{max1}, Current
  2425. $data{currval1}"</code></li>
  2426. <li>Eintrag in der .gplot-Datei:<br>
  2427. <code>set title &lt;L1&gt;</code><br></li>
  2428. </ul>
  2429. </li>
  2430. </ul>
  2431. Die Werte minAll und maxAll (die das Minimum/Maximum aller Werte
  2432. repr&auml;sentieren) sind ebenfals im data hash vorhanden.
  2433. <br>&Uuml;berholt, wird durch das plotReplace Attribut abgel&ouml;st.
  2434. </li><br>
  2435. <li><a href="#nrAxis">nrAxis</a></li><br>
  2436. <a name="plotfunction"></a>
  2437. <li>plotfunction<br>
  2438. Eine Liste, deren Werte durch Leerzeichen voneinander getrennt sind.
  2439. Diese Liste wird verwendet um die &lt;SPEC#&gt; Zeichenfolgen in der
  2440. .gplot-Datei zu ersetzen. Dabei steht das # f&uuml;r eine laufende Ziffer
  2441. beginnend mit 1 (&lt;SPEC1&gt;, &lt;SPEC2&gt;, usw.) in der #FileLog oder
  2442. #DBLog Anweisung. Mit diesem Attrbute ist es m&ouml;glich eine
  2443. .gplot-Datei f&uuml;r mehrere Ger&auml;te mit einem einzigen logdevice zu
  2444. verwenden. <br><br>
  2445. <ul><b>Beispiel:</b><br>
  2446. <li>#FileLog &lt;SPEC1&gt;<br>
  2447. mit:<br>
  2448. <code>attr &lt;SVGdevice&gt; plotfunction "4:IR\x3a:0:"</code><br>
  2449. anstelle von:<br>
  2450. <code>#FileLog 4:IR\x3a:0:</code>
  2451. </li>
  2452. <li>#DbLog &lt;SPEC1&gt;<br>
  2453. mit:<br>
  2454. <code>attr &lt;SVGdevice&gt; plotfunction
  2455. "Garage_Raumtemp:temperature::"</code><br>
  2456. anstelle von:<br>
  2457. <code>#DbLog Garage_Raumtemp:temperature::</code>
  2458. </li>
  2459. </ul>
  2460. &Uuml;berholt, wird durch das plotReplace Attribut abgel&ouml;st.
  2461. </li><br>
  2462. <li><a href="#plotmode">plotmode</a></li><br>
  2463. <a name="plotReplace"></a>
  2464. <li>plotReplace<br>
  2465. Leerzeichen getrennte Liste von Name=Wert Paaren. Wert kann Leerzeichen
  2466. enthalten, falls es in "" oder {} eingeschlossen ist. Wert wird als
  2467. perl-Ausdruck ausgewertet, falls es in {} eingeschlossen ist.
  2468. <br>
  2469. In der .gplot Datei werden &lt;Name&gt; Zeichenketten durch den
  2470. zugehoerigen Wert ersetzt, die Auswertung von {} Ausdr&uuml;cken erfolgt
  2471. <i>nach</i> dem die Daten ausgewertet wurden, d.h. man kann hier
  2472. $data{min1},etc verwenden.
  2473. <br>
  2474. Bei %Name% erfolgt die Ersetzung <i>vor</i> der Datenauswertung, das kann
  2475. man verwenden, um Parameter f&uuml;r die Auswertung zu ersetzen.
  2476. </li><br>
  2477. <li><a href="#plotsize">plotsize</a></li><br>
  2478. <li><a href="#plotWeekStartDay">plotWeekStartDay</a></li><br>
  2479. <a name="startDate"></a>
  2480. <li>startDate<br>
  2481. Setzt das Startdatum f&uuml;r den Plot. Wird f&uuml;r Demo-Installationen
  2482. verwendet.
  2483. </li><br>
  2484. <a name="title"></a>
  2485. <li>title<br>
  2486. Eine besondere Form der &Uuml;berschrift (siehe oben), bei der die
  2487. Zeichenfolge &lt;TL&gt; in der .gplot-Datei ersetzt wird.
  2488. Standardm&auml;&szlig;ig wird als &lt;TL&gt; der Dateiname des Logfiles
  2489. eingesetzt.
  2490. <br>&Uuml;berholt, wird durch das plotReplace Attribut abgel&ouml;st.
  2491. </li><br>
  2492. </ul>
  2493. <br>
  2494. <a name="plotEditor"></a>
  2495. <b>Plot-Editor</b>
  2496. <br>
  2497. Dieser Editor ist in der Detailansicht der SVG-Instanz zu sehen. Die
  2498. meisten Features sind hier einleuchtend und bekannt, es gibt aber auch
  2499. einige Ausnahmen:
  2500. <ul>
  2501. <li>wenn f&uuml;r ein Diagramm die &Uuml;berschrift unterdr&uuml;ckt werden
  2502. soll, muss im Eingabefeld <code>notitle</code> eingetragen werden.
  2503. </li>
  2504. <li>wenn ein fester Wert (nicht aus einer Wertespalte) definiert werden
  2505. soll, f&uuml;r den Fall, das eine Zeichenfoge gefunden wurde (z.B. 1
  2506. f&uuml;r eine FS20 Schalter, der AN ist und 0 f&uuml;r den AUS-Zustand),
  2507. muss zuerst das Tics-Feld gef&uuml;llt, und die .gplot-Datei
  2508. gespeichert werden, bevor der Wert &uuml;ber die Dropdownliste erreichbar
  2509. ist.
  2510. <ul><b>Beispiel:</b><br>
  2511. Eingabe im Tics-Feld: ("On" 1, "Off" 0)<br>
  2512. .gplot-Datei speichern<br>
  2513. "1" als Regexp switch.on und "0" f&uuml;r den Regexp switch.off vom
  2514. Spalten-Dropdown ausw&auml;hlen (auf die G&auml;nsef&uuml;&szlig;chen
  2515. achten!).<br>
  2516. .gplot-Datei erneut speichern<br>
  2517. </ul>
  2518. </li>
  2519. <li>Falls Range der Form {...} entspricht, dann wird sie als Perl -
  2520. Expression ausgewertet. Das Ergebnis muss in der Form [min:max] sein.
  2521. </li>
  2522. </ul>
  2523. Die Sichtbarkeit des Plot-Editors kann mit dem FHEMWEB Attribut <a
  2524. href="#ploteditor">ploteditor</a> konfiguriert werden.
  2525. <br>
  2526. </ul>
  2527. =end html_DE
  2528. =cut