From 94fb6a0c06d0ccc71a9b6faeb68cbcf1f0f882b6 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 10:22:25 -0700 Subject: [PATCH 01/14] Add plot subcommand --- docs/Subcommand/Plot.md | 99 ++++++++++ gldcore/scripts/Makefile.mk | 1 + gldcore/scripts/autotest/test_plot.glm | 31 ++++ gldcore/scripts/gridlabd-plot | 246 +++++++++++++++++++++++++ 4 files changed, 377 insertions(+) create mode 100644 docs/Subcommand/Plot.md create mode 100644 gldcore/scripts/autotest/test_plot.glm create mode 100755 gldcore/scripts/gridlabd-plot diff --git a/docs/Subcommand/Plot.md b/docs/Subcommand/Plot.md new file mode 100644 index 000000000..8ff581c7b --- /dev/null +++ b/docs/Subcommand/Plot.md @@ -0,0 +1,99 @@ +[[/Subcommand/Plot]] -- GridLAB-D plot subcommand + +# Synopsis + +Shell: + +~~~ +$ gridlabd plot OPTIONS +~~~ + +GLM: + +~~~ +#plot OPTIONS +#on_exit CODE gridlabd plot OPTIONS +~~~ + +# Description + +The 'gridlabd plot' command uses pandas and matplotlib to create PNG files from + CSV files. + +## Options + +The following options are available: + +### `-d|--debug` + +Enable debug output (if any) + +### `--figure:OPTIONS[=VALUE]` + +Specify matplotlib pyplot figure option + +### `-h|--help|help` + +Display this help information + +### `-i|--input=PATH` + +Specify the input CSV pathname + +### `-o|--output=PATH` + +Specify the output PNG pathname (default is input with ".png") + +### `-q|--quiet` + +Disable error output (if any) + +### `-s|--show` + +Show the output + +### `--plot:OPTIONS[=VALUE]` + +Specify pandas DataFrame plot option + +### `-v|--verbose` + +Enable verbose output (if any) + +### `-w|--warning` + +Disable warning output (if any) + +# Example + +The following example illustrates how to generate a plot of two fields generated +by a recorder. + +~~~ +module tape +{ + csv_header_type NAME; +} +clock +{ + timezone "PST+8PDT"; + starttime "2020-01-01 00:00:00 PST"; + stoptime "2020-01-02 00:00:00 PST"; +} +class test { + randomvar x[MW]; + randomvar y[MWh]; +} +object test +{ + x "type:normal(100,10); min:0; max:200; refresh:1h"; + y "type:normal(100,10); min:0; max:200; refresh:1h"; + object recorder + { + property "x,y"; + interval -1; + file ${modelname/glm/csv}; + }; +} +#on_exit 0 gridlabd plot -i=${modelname/glm/csv} --plot:x=timestamp --plot:y=x,y --plot:rot=90 --plot:grid --plot:legend --figure:tight_layout=True +~~~ diff --git a/gldcore/scripts/Makefile.mk b/gldcore/scripts/Makefile.mk index 023015b5c..7a65d154d 100644 --- a/gldcore/scripts/Makefile.mk +++ b/gldcore/scripts/Makefile.mk @@ -12,6 +12,7 @@ bin_SCRIPTS += gldcore/scripts/gridlabd-library bin_SCRIPTS += gldcore/scripts/gridlabd-manual bin_SCRIPTS += gldcore/scripts/gridlabd-openfido bin_SCRIPTS += gldcore/scripts/gridlabd-pandas +bin_SCRIPTS += gldcore/scripts/gridlabd-plot bin_SCRIPTS += gldcore/scripts/gridlabd-python bin_SCRIPTS += gldcore/scripts/gridlabd-require bin_SCRIPTS += gldcore/scripts/gridlabd-requirements diff --git a/gldcore/scripts/autotest/test_plot.glm b/gldcore/scripts/autotest/test_plot.glm new file mode 100644 index 000000000..a3f9c21c7 --- /dev/null +++ b/gldcore/scripts/autotest/test_plot.glm @@ -0,0 +1,31 @@ +module tape +{ + csv_header_type NAME; +} + +clock +{ + timezone "PST+8PDT"; + starttime "2020-01-01 00:00:00 PST"; + stoptime "2020-01-02 00:00:00 PST"; +} + +class test { + randomvar x[MW]; + randomvar y[MWh]; +} + +#set randomseed=1 +object test +{ + x "type:normal(100,10); min:0; max:200; refresh:1h"; + y "type:normal(100,10); min:0; max:200; refresh:1h"; + object recorder + { + property "x,y"; + interval -1; + file ${modelname/glm/csv}; + }; +} + +#on_exit 0 gridlabd plot -i=${modelname/glm/csv} --plot:x=timestamp --plot:y=x,y --plot:rot=90 --plot:grid --plot:legend --figure:tight_layout=True diff --git a/gldcore/scripts/gridlabd-plot b/gldcore/scripts/gridlabd-plot new file mode 100755 index 000000000..93b9342cb --- /dev/null +++ b/gldcore/scripts/gridlabd-plot @@ -0,0 +1,246 @@ +#!/usr/local/bin/python3 +"""GridLAB-D plot subcommand + +SYNOPSIS + +Shell: + $ gridlabd plot OPTIONS + +GLM: + + #plot OPTIONS + +DESCRIPTION + + The 'gridlabd plot' command uses pandas and matplotlib to create PNG files from + CSV files. + +OPTIONS + + -d|--debug Enable debug output and exception traceback + --figure:OPTIONS[=VALUE] Specify matplotlib pyplot figure option (see matplot + figure for details) + -h|--help|help Display this help information + -i|--input=PATH Specify the input CSV pathname + -o|--output=PATH Specify the output PNG pathname (default is input with + ".png") + --plot:OPTIONS[=VALUE] Specify pandas DataFrame plot option (see pandas + DataFrame plot for details) + -q|--quiet Disable error output + -s|--show Show the output + -v|--verbose Enable verbose output + -w|--warning Disable warning output + +EXAMPLE + +The following example illustrates how to generate a plot of two fields generated +by a recorder. + + module tape + { + csv_header_type NAME; + } + clock + { + timezone "PST+8PDT"; + starttime "2020-01-01 00:00:00 PST"; + stoptime "2020-01-02 00:00:00 PST"; + } + class test { + randomvar x[MW]; + randomvar y[MWh]; + } + object test + { + x "type:normal(100,10); min:0; max:200; refresh:1h"; + y "type:normal(100,10); min:0; max:200; refresh:1h"; + object recorder + { + property "x,y"; + interval -1; + file ${modelname/glm/csv}; + }; + } + #on_exit 0 gridlabd plot -i=${modelname/glm/csv} --plot:x=timestamp --plot:y=x,y --plot:rot=90 --plot:grid --plot:legend --figure:tight_layout=True +""" + +import sys, os +import pandas +import json + +def exception(msg,code=None): + print(f"EXCEPTION [gridlabd-plot]: {msg}",file=sys.stderr) + if type(code) is int: + exit(code) + +def error(msg,code=None): + if not config["quiet"]: + print(f"ERROR [gridlabd-plot]: {msg}",file=sys.stderr) + if type(code) is int: + exit(code) + +def warning(msg): + if config["warning"]: + print(f"WARNING [gridlabd-plot]: {msg}",file=sys.stderr) + +def verbose(msg): + if config["verbose"]: + print(f"VERBOSE [gridlabd-plot]: {msg}",file=sys.stderr) + +def debug(msg,level=0): + if config["debug"] and level <= config["debug"]: + print(f"DEBUG [gridlabd-plot]: (level {level}) {msg}",file=sys.stderr) + +def output(msg,code=None): + print(msg,file=sys.stdout) + if type(code) is int: + exit(code) + +try: + with open(sys.argv[0].replace(".py",".conf"),"r") as f: + config = json.load(f) +except: + config = dict( + workdir = os.getenv("PWD"), + show = False, + quiet = False, + warning = True, + debug = None, + verbose = False, + open_command = "open", + input_pathname = None, + output_pathname = None, + ) + +os.chdir(config["workdir"]) + +plot_options = dict( + x = "", + y = [], + kind = 'line', + subplots = False, + sharex = True, + sharey = False, + # layout = None, + # figsize = None, + use_index = True, + title = "", + grid = False, + legend = False, + style = None, + logx = False, + logy = False, + loglog = False, + # xticks = None, + # yticks = None, + # xlim = None, + # ylim = None, + xlabel = "", + ylabel = "", + rot = 0, + fontsize = None, + # colormap = None, + # colorbar = None, + # position = 0.5, + table = False, + yerr = None, + xerr = None, + stacked = False, + sort_columns = False, + secondary_y = False, + mark_right = True, + include_bool = False, + backend = None, + ) +figure_options = dict( + tight_layout = False, + facecolor = 'white', + edgecolor = 'white', + frameon = True, + # dpi = 100.0, + ) + +validate = { + "plot:y" : lambda x: x.split(","), + } + +# if __name__ == "__main__": +# sys.argv = f"{sys.argv[0]} -i /Users/david/Downloads/test_line.csv -x timestamp -y p630:wind_speed -s".split() + +if len(sys.argv) == 1: + print(__doc__) + exit(1) +for arg in sys.argv[1:]: + if arg in ["-h","--help","help"]: + if sys.stdout.isatty(): + help(__name__) + else: + output(__doc__,0) + if "=" in arg: + args = arg.split("=") + arg = args[0] + arg1 = "=".join(args[1:]) + if arg.startswith("--") and arg[2:] in validate.keys(): + arg1 = validate[arg[2:]](arg1) + debug(f"validating {'='.join(args)} --> {arg1}") + else: + arg1 = None + if arg in ["-i","--input"] and arg1: + config['input_pathname'] = arg1 + elif arg in ["-o","--output"] and arg1: + config['output_pathname'] = arg1 + elif arg in ["-s","--show"]: + config['show'] = True + elif arg in ["-d","--debug"]: + config['debug'] = True + elif arg in ["-v","--verbose"]: + config['verbose'] = True + elif arg in ["-q","--quiet"]: + config['quiet'] = True + elif arg in ["-w","--warning"]: + config['warning'] = False + elif arg.startswith("--plot:"): + if arg1: + plot_options[arg[7:]] = arg1 + else: + plot_options[arg[7:]] = not plot_options[arg[7:]] + elif arg.startswith("--figure:"): + if arg1: + figure_options[arg[9:]] = arg1 + else: + figure_options[arg[9:]] = not figure_options[arg[9:]] + else: + error(f"option {arg} is not valid",1) + +if not config['output_pathname']: + config['output_pathname'] = config['input_pathname'].replace(".csv",".png") +figure_calls = dict( + savefig = config['output_pathname'], + ) + +if config['debug']: + debug("config = "+json.dumps(config,indent=4),level=1) + debug("plot_options = "+json.dumps(plot_options,indent=4),level=1) + debug("figure_options = "+json.dumps(figure_options,indent=4),level=1) +try: + data = pandas.read_csv(config['input_pathname']) + plt = data.plot(**plot_options) + if plt: + for key, value in figure_options.items(): + getattr(plt.figure,"set_"+key)(value) + # plt.figure.set_tight_layout(True) + # debug(f"plt.figure.{key} = {getattr(plt.figure,key)}") + for key, value in figure_calls.items(): + getattr(plt.figure,key)(value) + if config['show']: + os.system(f"{config['open_command']} {config['output_pathname']}") + else: + error("nothing to plot",2) +except: + e_type, e_value, e_trace = sys.exc_info() + if config["debug"]: + import traceback + exception("".join(traceback.format_exception(e_type,e_value,e_trace)),3) + else: + exception(e_value,3) + From 7afb3e574418bcd5f0e00c99565e4d8f145836ac Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 10:26:08 -0700 Subject: [PATCH 02/14] Update Plot.md --- docs/Subcommand/Plot.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/Subcommand/Plot.md b/docs/Subcommand/Plot.md index 8ff581c7b..315f297a6 100644 --- a/docs/Subcommand/Plot.md +++ b/docs/Subcommand/Plot.md @@ -12,6 +12,11 @@ GLM: ~~~ #plot OPTIONS +~~~ + +or + +~~~ #on_exit CODE gridlabd plot OPTIONS ~~~ From 4b4251df97b55fb618e8c90cc96c9933d4c9343f Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 10:39:22 -0700 Subject: [PATCH 03/14] Update test_plot.glm --- gldcore/scripts/autotest/test_plot.glm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gldcore/scripts/autotest/test_plot.glm b/gldcore/scripts/autotest/test_plot.glm index a3f9c21c7..fe72a9050 100644 --- a/gldcore/scripts/autotest/test_plot.glm +++ b/gldcore/scripts/autotest/test_plot.glm @@ -29,3 +29,7 @@ object test } #on_exit 0 gridlabd plot -i=${modelname/glm/csv} --plot:x=timestamp --plot:y=x,y --plot:rot=90 --plot:grid --plot:legend --figure:tight_layout=True + +#ifexist ../${FILENAME $modelname}.png +#on_exit 0 diff ../${FILENAME $modelname}.png ${FILENAME $modelname}.png +#endif From c0d96f8fda69a6066a1e6d21a163b63bda15e8f6 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 10:39:24 -0700 Subject: [PATCH 04/14] Create test_plot.png --- gldcore/scripts/autotest/test_plot.png | Bin 0 -> 46411 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 gldcore/scripts/autotest/test_plot.png diff --git a/gldcore/scripts/autotest/test_plot.png b/gldcore/scripts/autotest/test_plot.png new file mode 100644 index 0000000000000000000000000000000000000000..8b000e8898bbc69fc2c88b1f2c6a28713d453e78 GIT binary patch literal 46411 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

0-^TeO`xCy|)HZmpQ1RR1> zSHltBy$yO(E8Wh*k)~J?9YD`e3*Nvxpj6_1&@lP7sVQ}LZfV!?w;zd*l}!K0sp%LvR9c^GJ1FSfJR$k zV{D37dwN;FgihRnHQC{SDW!Oe-#AL?|MFt%!;}fd_*#EsjlW<2B7l@sxmN zdHs%>?z<4oh1s49!Pa#?+mTu>r2yq$H`-4?U_rq6ake6m0HTFwk(#(a4oZ0_9z&S6 zB*-V9(Vojm@zcOpg&e1zf^(9N@39uZ+JGFIAaCv?GoqoBW>!`jdU`R82=t=bL*NM) zcyo!HW+X3cwi1fi5*gybyC`AZ)9Tl*Jt)<;u51AWXN3-;|9xZMVIY z)d-Sq0E7KCPMu2J|AmkL93OyFqW=nrA@Y7c2lH3lx*@1fK)R+d7-dgS&q_zK-`=^8 zcmEh0Kr6z8{CvQ*8SQiZCD@`uV(^|8(71El+-+k=Z7)TEKR5wdGz&C5@S{wuQhZoF z&w@%iyGl`r|NG|0sVmcOsH?w%qEsS+n|bK>yw~o{!w3K#{IfETT0yq?N{g)tm?hzb z9)Of6i4iGqOB87kkN0j8EJ>xNrTsh0ZFBOM?ZNp7!6z$yu#wb+NH$4B_-#K;ocr+u z>I#FHFj`52g@I)@K z8ScTn{Xy06c{AviI;(hX_6_ElcXL8m$c2Mi^18HC7_yoN#EYYgq;BXhYcNm%jY9TT zd|O&~0Q}YOI-Nl!P*{8w?US|Oa42so1atZ!S%l%^DE@-;?-XBe7mu0 zOw7!*b|{l1(aXo1SA?z67w-zIhK`O{;mD$jKd3~#l*<_Jruvi9SRZOfCN zfvFyvi`7?ANq~}V07=MoCZ`@)>K{={QDnDyhv2JC4N)_!P&}HpE?aB zNE_T%dca>tj-76WWkU*w?b=|n;<4vC4m(@xTuO)}co5rrPE8@^Sqk_+Wcn`_eqcN< zB=rG)S65R@ZcCCi|Mw{y*8bE2jDtgdsh`2>-uL7}|3BOTGAcNWcDZ|z)BFI<0!}Z~ zv@T$Ke4w=4J0kr@_N~hPrQGZntiXjX9qT)AlPGTEKoE+syQrwC`S#cmqJ$lk6DckT zS13qCB5+y-m4H<=Xj_QQfdRd8>j&X2J*(W#_Rq+w`dFqzvTCiL)$Ov@f3JjYz9*-K z62QY!`}GT#LPrGw&xh8J+L{X=miXldhQ~)&aq}vKT)rp6fi1;!6JukOqiC@%;A@DY z0ARhhY?$Q4uTl9SAq8-C8X*3$AI|O|oQm%JFh*1*q)55dFtGk@_`>bgzI?0xV*M7k zGJSOojk`dMIOYE;h={_owbWuhSneUwU5BR2f*89AN1;jlNBa7TN42L!;ARtpgwY9S zrn!1}Gy}r{3i?NtKon95SWH1SNg_;$l9Ce3AYM;LCmo?`a9tn3^?w(!$OQ%xKgb|Z zu&;wKsS4{Yu;Q2`;|)7R17{*SITDBQZby8CL=uc{9j{7*4*p#i{lBAgUA9n2Ej3VBB_ zdQVgS>9BT7ZkUZ+P-q3Ao;ZRgN`tN+t*x)W3`_)>WB4S-Woi5pIj648zVh*_81s31 zAVMcdtcRc;+Kxy=8Z8#4l>eP)IqQD{3q!n#mbh+kf;1q^tJ)=?1H;CQ$!aw`km?U| z69enLL7%w_#c09dM-<%E2f6MEM2tJ=Fa6U8UiY`B`tx|>A~FY*><-YU$ygN4hAMrE z!B^CEbf)(goPIv*Fd%1GExbG3l>yc5IU`Ufn5t=nE5L~_$N~|&h=?BONc(#CpEhrf z6U&N>jGU`iQBgrc6)<($<+(zsUDz6@7rnO{^)sE+E^o(4zqTE%n}q!%swO5Tl}ioJ zIi4^=C29)EA^D@+BQOuygu4yUlBgJ@f{RVjmsCsL1}c81y-1sVBsxq8mH)pi0R0D6 h{c~;T|M#mM;`FwT{+>IMrvYD} Date: Wed, 25 Aug 2021 11:06:30 -0700 Subject: [PATCH 05/14] Update gridlabd-plot --- gldcore/scripts/gridlabd-plot | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/gldcore/scripts/gridlabd-plot b/gldcore/scripts/gridlabd-plot index 93b9342cb..879975245 100755 --- a/gldcore/scripts/gridlabd-plot +++ b/gldcore/scripts/gridlabd-plot @@ -121,8 +121,8 @@ plot_options = dict( subplots = False, sharex = True, sharey = False, - # layout = None, - # figsize = None, + # layout = [], + # figsize = [], use_index = True, title = "", grid = False, @@ -157,11 +157,15 @@ figure_options = dict( facecolor = 'white', edgecolor = 'white', frameon = True, - # dpi = 100.0, + dpi = 100.0, ) validate = { "plot:y" : lambda x: x.split(","), + "plot:figsize" : lambda x: list(map(int,x.split(','))), + "plot:fontsize" : int, + "plot:position" : float, + "figure:dpi" : float, } # if __name__ == "__main__": From 59601e1ddc7374077385d7d7abab16166f8dcefe Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 12:55:25 -0700 Subject: [PATCH 06/14] Update gridlabd-plot --- gldcore/scripts/gridlabd-plot | 367 ++++++++++++++++++++-------------- 1 file changed, 222 insertions(+), 145 deletions(-) diff --git a/gldcore/scripts/gridlabd-plot b/gldcore/scripts/gridlabd-plot index 879975245..bfe1c4cca 100755 --- a/gldcore/scripts/gridlabd-plot +++ b/gldcore/scripts/gridlabd-plot @@ -18,6 +18,7 @@ DESCRIPTION OPTIONS -d|--debug Enable debug output and exception traceback + -e|--exception Enable exception raising --figure:OPTIONS[=VALUE] Specify matplotlib pyplot figure option (see matplot figure for details) -h|--help|help Display this help information @@ -68,183 +69,259 @@ import sys, os import pandas import json +E_OK = 0 +E_INVALID = 1 +E_NODATA = 2 +E_EXCEPTION = 3 + def exception(msg,code=None): + """Print exception message + + Exception messages are printed only if the configuration parameter `exception` is false. + Otherwise, the exception is raised. Use the '-e|--exception' option to toggle this setting. + + PARAMETERS + + msg (None or str) Exception message (None to get last exception) + code (None or int) Exit code. None does not exit. Default is None) + """ + if config["exception"]: + raise + if not msg: + e_type, e_value, e_trace = sys.exc_info() + if config["debug"]: + import traceback + msg = "".join(traceback.format_exception(e_type,e_value,e_trace)) + else: + msg = e_value + code = E_EXCEPTION print(f"EXCEPTION [gridlabd-plot]: {msg}",file=sys.stderr) if type(code) is int: exit(code) def error(msg,code=None): + """Print error message + + Error messages are printed only if the configuration parameters `quiet` is false. + Otherwise, the error message is suppressed. Use the '-q|--quiet' option to toggle this setting. + + PARAMETERS + + msg (str) Error message + code (None or int) Exit code. None does not exit. Default is None) + """ if not config["quiet"]: print(f"ERROR [gridlabd-plot]: {msg}",file=sys.stderr) if type(code) is int: exit(code) def warning(msg): + """Print warning message + + Warning messages are printed only if the configuration parameter `warning` is true. + Otherwise the warning message is suppressed. Use the '-w|--warning' option to toggle this setting. + + PARAMETERS + + msg (str) Warning message + """ if config["warning"]: print(f"WARNING [gridlabd-plot]: {msg}",file=sys.stderr) def verbose(msg): + """Print verbose message + + Verbose messages are printed only if the configuration parameter `verbose` is true. + Otherwise the verbose message is suppressed. Use the '-v|--verbose' option to toggle this setting. + + PARAMETERS + + msg (str) Verbose message + """ if config["verbose"]: print(f"VERBOSE [gridlabd-plot]: {msg}",file=sys.stderr) def debug(msg,level=0): + """Print debug message + + Debug messages are printed only if the configuration parameter `debug` is an integer and the + value of level is less than or equal to the `debug` value. Otherwise the debug message is + suppressed. Use the '-d|--debug' option to toggle this setting. + + PARAMETERS + + msg (str) Debug message + level (int) Debug message level (default is 0) + """ if config["debug"] and level <= config["debug"]: print(f"DEBUG [gridlabd-plot]: (level {level}) {msg}",file=sys.stderr) def output(msg,code=None): + """Print output message + + Output messages are printed only if the configuration parameter `quiet` is false. + Otherwise the output message is suppressed. Use the '-q|--quiet' option to toggle this setting. + + PARAMETERS + + msg (str) Output message + code (int) Exit code to use after message is output (use `None` to not exit) + """ print(msg,file=sys.stdout) if type(code) is int: exit(code) -try: - with open(sys.argv[0].replace(".py",".conf"),"r") as f: - config = json.load(f) -except: - config = dict( - workdir = os.getenv("PWD"), - show = False, - quiet = False, - warning = True, - debug = None, - verbose = False, - open_command = "open", - input_pathname = None, - output_pathname = None, - ) +def main(): + global config + try: + with open(sys.argv[0].replace(".py",".conf"),"r") as input_file: + config = json.load(input_file) + except: + config = dict( + workdir = os.getenv("PWD"), + show = False, + quiet = False, + warning = True, + debug = None, + verbose = False, + exception = False, + open_command = "open", + input_pathname = None, + output_pathname = None, + ) -os.chdir(config["workdir"]) - -plot_options = dict( - x = "", - y = [], - kind = 'line', - subplots = False, - sharex = True, - sharey = False, - # layout = [], - # figsize = [], - use_index = True, - title = "", - grid = False, - legend = False, - style = None, - logx = False, - logy = False, - loglog = False, - # xticks = None, - # yticks = None, - # xlim = None, - # ylim = None, - xlabel = "", - ylabel = "", - rot = 0, - fontsize = None, - # colormap = None, - # colorbar = None, - # position = 0.5, - table = False, - yerr = None, - xerr = None, - stacked = False, - sort_columns = False, - secondary_y = False, - mark_right = True, - include_bool = False, - backend = None, - ) -figure_options = dict( - tight_layout = False, - facecolor = 'white', - edgecolor = 'white', - frameon = True, - dpi = 100.0, - ) - -validate = { - "plot:y" : lambda x: x.split(","), - "plot:figsize" : lambda x: list(map(int,x.split(','))), - "plot:fontsize" : int, - "plot:position" : float, - "figure:dpi" : float, - } + os.chdir(config["workdir"]) + + plot_options = dict( + x = "", + y = [], + kind = 'line', + subplots = False, + sharex = True, + sharey = False, + # layout = [], + # figsize = [], + use_index = True, + title = "", + grid = False, + legend = False, + style = None, + logx = False, + logy = False, + loglog = False, + # xticks = None, + # yticks = None, + # xlim = None, + # ylim = None, + xlabel = "", + ylabel = "", + rot = 0, + fontsize = None, + # colormap = None, + # colorbar = None, + # position = 0.5, + table = False, + yerr = None, + xerr = None, + stacked = False, + sort_columns = False, + secondary_y = False, + mark_right = True, + include_bool = False, + backend = None, + ) + figure_options = dict( + tight_layout = False, + facecolor = 'white', + edgecolor = 'white', + frameon = True, + dpi = 100.0, + ) -# if __name__ == "__main__": -# sys.argv = f"{sys.argv[0]} -i /Users/david/Downloads/test_line.csv -x timestamp -y p630:wind_speed -s".split() + validate = { + "plot:y" : lambda x: x.split(","), + "plot:figsize" : lambda x: list(map(int,x.split(','))), + "plot:fontsize" : int, + "plot:position" : float, + "figure:dpi" : float, + } -if len(sys.argv) == 1: - print(__doc__) - exit(1) -for arg in sys.argv[1:]: - if arg in ["-h","--help","help"]: - if sys.stdout.isatty(): - help(__name__) + if len(sys.argv) == 1: + print(__doc__) + exit(1) + for arg in sys.argv[1:]: + if arg in ["-h","--help","help"]: + if sys.stdout.isatty(): + help(__name__) + else: + output(__doc__,E_OK) + if "=" in arg: + args = arg.split("=") + arg = args[0] + arg1 = "=".join(args[1:]) + if arg.startswith("--") and arg[2:] in validate.keys(): + arg1 = validate[arg[2:]](arg1) + debug(f"validating {'='.join(args)} --> {arg1}") else: - output(__doc__,0) - if "=" in arg: - args = arg.split("=") - arg = args[0] - arg1 = "=".join(args[1:]) - if arg.startswith("--") and arg[2:] in validate.keys(): - arg1 = validate[arg[2:]](arg1) - debug(f"validating {'='.join(args)} --> {arg1}") - else: - arg1 = None - if arg in ["-i","--input"] and arg1: - config['input_pathname'] = arg1 - elif arg in ["-o","--output"] and arg1: - config['output_pathname'] = arg1 - elif arg in ["-s","--show"]: - config['show'] = True - elif arg in ["-d","--debug"]: - config['debug'] = True - elif arg in ["-v","--verbose"]: - config['verbose'] = True - elif arg in ["-q","--quiet"]: - config['quiet'] = True - elif arg in ["-w","--warning"]: - config['warning'] = False - elif arg.startswith("--plot:"): - if arg1: - plot_options[arg[7:]] = arg1 + arg1 = None + if arg in ["-e","--exception"]: + config["exception"] = not config["exception"] + elif arg in ["-i","--input"] and arg1: + config['input_pathname'] = arg1 + elif arg in ["-o","--output"] and arg1: + config['output_pathname'] = arg1 + elif arg in ["-s","--show"]: + config['show'] = True + elif arg in ["-d","--debug"]: + if arg1: + config['debug'] = int(arg1) + else: + config['debug'] = not config['debug'] + elif arg in ["-v","--verbose"]: + config['verbose'] = not config['verbose'] + elif arg in ["-q","--quiet"]: + config['quiet'] = True + elif arg in ["-w","--warning"]: + config['warning'] = not config['warning'] + elif arg.startswith("--plot:"): + if arg1: + plot_options[arg[7:]] = arg1 + else: + plot_options[arg[7:]] = not plot_options[arg[7:]] + elif arg.startswith("--figure:"): + if arg1: + figure_options[arg[9:]] = arg1 + else: + figure_options[arg[9:]] = not figure_options[arg[9:]] else: - plot_options[arg[7:]] = not plot_options[arg[7:]] - elif arg.startswith("--figure:"): - if arg1: - figure_options[arg[9:]] = arg1 + error(f"option {arg} is not valid",E_INVALID) + + if not config['output_pathname']: + config['output_pathname'] = config['input_pathname'].replace(".csv",".png") + figure_calls = dict( + savefig = config['output_pathname'], + ) + + if config['debug']: + debug("config = "+json.dumps(config,indent=4),level=1) + debug("plot_options = "+json.dumps(plot_options,indent=4),level=1) + debug("figure_options = "+json.dumps(figure_options,indent=4),level=1) + try: + data = pandas.read_csv(config['input_pathname']) + plt = data.plot(**plot_options) + if plt: + for key, value in figure_options.items(): + getattr(plt.figure,"set_"+key)(value) + # plt.figure.set_tight_layout(True) + # debug(f"plt.figure.{key} = {getattr(plt.figure,key)}") + for key, value in figure_calls.items(): + getattr(plt.figure,key)(value) + if config['show']: + os.system(f"{config['open_command']} {config['output_pathname']}") else: - figure_options[arg[9:]] = not figure_options[arg[9:]] - else: - error(f"option {arg} is not valid",1) - -if not config['output_pathname']: - config['output_pathname'] = config['input_pathname'].replace(".csv",".png") -figure_calls = dict( - savefig = config['output_pathname'], - ) - -if config['debug']: - debug("config = "+json.dumps(config,indent=4),level=1) - debug("plot_options = "+json.dumps(plot_options,indent=4),level=1) - debug("figure_options = "+json.dumps(figure_options,indent=4),level=1) -try: - data = pandas.read_csv(config['input_pathname']) - plt = data.plot(**plot_options) - if plt: - for key, value in figure_options.items(): - getattr(plt.figure,"set_"+key)(value) - # plt.figure.set_tight_layout(True) - # debug(f"plt.figure.{key} = {getattr(plt.figure,key)}") - for key, value in figure_calls.items(): - getattr(plt.figure,key)(value) - if config['show']: - os.system(f"{config['open_command']} {config['output_pathname']}") - else: - error("nothing to plot",2) -except: - e_type, e_value, e_trace = sys.exc_info() - if config["debug"]: - import traceback - exception("".join(traceback.format_exception(e_type,e_value,e_trace)),3) - else: - exception(e_value,3) + error("nothing to plot",E_NODATA) + except: + exception() +if __name__ == "__main__": + main() \ No newline at end of file From 1ed943665f2d3e8fdce838e12be9bce6d6429bbb Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 12:55:32 -0700 Subject: [PATCH 07/14] Create .gitignore --- gldcore/scripts/autotest/.gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 gldcore/scripts/autotest/.gitignore diff --git a/gldcore/scripts/autotest/.gitignore b/gldcore/scripts/autotest/.gitignore new file mode 100644 index 000000000..2d34dfea3 --- /dev/null +++ b/gldcore/scripts/autotest/.gitignore @@ -0,0 +1 @@ +test_plot.csv From d5dfaa4f79a38f4c8355e12f17b1ebd343961f4d Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 13:08:31 -0700 Subject: [PATCH 08/14] Update test_plot.glm --- gldcore/scripts/autotest/test_plot.glm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gldcore/scripts/autotest/test_plot.glm b/gldcore/scripts/autotest/test_plot.glm index fe72a9050..d1de6c107 100644 --- a/gldcore/scripts/autotest/test_plot.glm +++ b/gldcore/scripts/autotest/test_plot.glm @@ -28,7 +28,7 @@ object test }; } -#on_exit 0 gridlabd plot -i=${modelname/glm/csv} --plot:x=timestamp --plot:y=x,y --plot:rot=90 --plot:grid --plot:legend --figure:tight_layout=True +#on_exit 0 gridlabd plot --input=${modelname/glm/csv} --plot:x=timestamp --plot:y=x,y --plot:rot=90 --plot:grid --plot:legend --figure:tight_layout=True #ifexist ../${FILENAME $modelname}.png #on_exit 0 diff ../${FILENAME $modelname}.png ${FILENAME $modelname}.png From 96ce0a8d0bd07099a5e550df7f1a69139082fc4c Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 13:08:34 -0700 Subject: [PATCH 09/14] Update gridlabd-plot --- gldcore/scripts/gridlabd-plot | 185 +++++++++++++++++----------------- 1 file changed, 91 insertions(+), 94 deletions(-) diff --git a/gldcore/scripts/gridlabd-plot b/gldcore/scripts/gridlabd-plot index bfe1c4cca..03e75dc9e 100755 --- a/gldcore/scripts/gridlabd-plot +++ b/gldcore/scripts/gridlabd-plot @@ -69,112 +69,112 @@ import sys, os import pandas import json -E_OK = 0 -E_INVALID = 1 -E_NODATA = 2 -E_EXCEPTION = 3 - -def exception(msg,code=None): - """Print exception message - - Exception messages are printed only if the configuration parameter `exception` is false. - Otherwise, the exception is raised. Use the '-e|--exception' option to toggle this setting. - - PARAMETERS - - msg (None or str) Exception message (None to get last exception) - code (None or int) Exit code. None does not exit. Default is None) - """ - if config["exception"]: - raise - if not msg: - e_type, e_value, e_trace = sys.exc_info() - if config["debug"]: - import traceback - msg = "".join(traceback.format_exception(e_type,e_value,e_trace)) - else: - msg = e_value - code = E_EXCEPTION - print(f"EXCEPTION [gridlabd-plot]: {msg}",file=sys.stderr) - if type(code) is int: - exit(code) +def main(args): + E_OK = 0 + E_INVALID = 1 + E_NODATA = 2 + E_EXCEPTION = 3 + + def exception(msg,code=None): + """Print exception message + + Exception messages are printed only if the configuration parameter `exception` is false. + Otherwise, the exception is raised. Use the '-e|--exception' option to toggle this setting. + + PARAMETERS + + msg (None or str) Exception message (None to get last exception) + code (None or int) Exit code. None does not exit. Default is None) + """ + if config["exception"]: + raise + if not msg: + e_type, e_value, e_trace = sys.exc_info() + if config["debug"]: + import traceback + msg = "".join(traceback.format_exception(e_type,e_value,e_trace)) + else: + msg = e_value + code = E_EXCEPTION + print(f"EXCEPTION [gridlabd-plot]: {msg}",file=sys.stderr) + if type(code) is int: + exit(code) -def error(msg,code=None): - """Print error message + def error(msg,code=None): + """Print error message - Error messages are printed only if the configuration parameters `quiet` is false. - Otherwise, the error message is suppressed. Use the '-q|--quiet' option to toggle this setting. + Error messages are printed only if the configuration parameters `quiet` is false. + Otherwise, the error message is suppressed. Use the '-q|--quiet' option to toggle this setting. - PARAMETERS + PARAMETERS - msg (str) Error message - code (None or int) Exit code. None does not exit. Default is None) - """ - if not config["quiet"]: - print(f"ERROR [gridlabd-plot]: {msg}",file=sys.stderr) - if type(code) is int: - exit(code) + msg (str) Error message + code (None or int) Exit code. None does not exit. Default is None) + """ + if not config["quiet"]: + print(f"ERROR [gridlabd-plot]: {msg}",file=sys.stderr) + if type(code) is int: + exit(code) -def warning(msg): - """Print warning message + def warning(msg): + """Print warning message - Warning messages are printed only if the configuration parameter `warning` is true. - Otherwise the warning message is suppressed. Use the '-w|--warning' option to toggle this setting. + Warning messages are printed only if the configuration parameter `warning` is true. + Otherwise the warning message is suppressed. Use the '-w|--warning' option to toggle this setting. - PARAMETERS + PARAMETERS - msg (str) Warning message - """ - if config["warning"]: - print(f"WARNING [gridlabd-plot]: {msg}",file=sys.stderr) + msg (str) Warning message + """ + if config["warning"]: + print(f"WARNING [gridlabd-plot]: {msg}",file=sys.stderr) -def verbose(msg): - """Print verbose message + def verbose(msg): + """Print verbose message - Verbose messages are printed only if the configuration parameter `verbose` is true. - Otherwise the verbose message is suppressed. Use the '-v|--verbose' option to toggle this setting. + Verbose messages are printed only if the configuration parameter `verbose` is true. + Otherwise the verbose message is suppressed. Use the '-v|--verbose' option to toggle this setting. - PARAMETERS + PARAMETERS - msg (str) Verbose message - """ - if config["verbose"]: - print(f"VERBOSE [gridlabd-plot]: {msg}",file=sys.stderr) + msg (str) Verbose message + """ + if config["verbose"]: + print(f"VERBOSE [gridlabd-plot]: {msg}",file=sys.stderr) -def debug(msg,level=0): - """Print debug message + def debug(msg,level=0): + """Print debug message - Debug messages are printed only if the configuration parameter `debug` is an integer and the - value of level is less than or equal to the `debug` value. Otherwise the debug message is - suppressed. Use the '-d|--debug' option to toggle this setting. + Debug messages are printed only if the configuration parameter `debug` is an integer and the + value of level is less than or equal to the `debug` value. Otherwise the debug message is + suppressed. Use the '-d|--debug' option to toggle this setting. - PARAMETERS + PARAMETERS - msg (str) Debug message - level (int) Debug message level (default is 0) - """ - if config["debug"] and level <= config["debug"]: - print(f"DEBUG [gridlabd-plot]: (level {level}) {msg}",file=sys.stderr) + msg (str) Debug message + level (int) Debug message level (default is 0) + """ + if config["debug"] and level <= config["debug"]: + print(f"DEBUG [gridlabd-plot]: (level {level}) {msg}",file=sys.stderr) -def output(msg,code=None): - """Print output message + def output(msg,code=None): + """Print output message - Output messages are printed only if the configuration parameter `quiet` is false. - Otherwise the output message is suppressed. Use the '-q|--quiet' option to toggle this setting. + Output messages are printed only if the configuration parameter `quiet` is false. + Otherwise the output message is suppressed. Use the '-q|--quiet' option to toggle this setting. - PARAMETERS + PARAMETERS - msg (str) Output message - code (int) Exit code to use after message is output (use `None` to not exit) - """ - print(msg,file=sys.stdout) - if type(code) is int: - exit(code) + msg (str) Output message + code (int) Exit code to use after message is output (use `None` to not exit) + """ + print(msg,file=sys.stdout) + if type(code) is int: + exit(code) -def main(): global config try: - with open(sys.argv[0].replace(".py",".conf"),"r") as input_file: + with open(args[0].replace(".py",".conf"),"r") as input_file: config = json.load(input_file) except: config = dict( @@ -246,22 +246,19 @@ def main(): "figure:dpi" : float, } - if len(sys.argv) == 1: + if len(args) == 1: print(__doc__) exit(1) - for arg in sys.argv[1:]: + for arg in args[1:]: if arg in ["-h","--help","help"]: - if sys.stdout.isatty(): - help(__name__) - else: - output(__doc__,E_OK) + output(__doc__,E_OK) if "=" in arg: - args = arg.split("=") - arg = args[0] - arg1 = "=".join(args[1:]) + argn = arg.split("=") + arg = argn[0] + arg1 = "=".join(argn[1:]) if arg.startswith("--") and arg[2:] in validate.keys(): arg1 = validate[arg[2:]](arg1) - debug(f"validating {'='.join(args)} --> {arg1}") + debug(f"validating {'='.join(argn)} --> {arg1}") else: arg1 = None if arg in ["-e","--exception"]: @@ -324,4 +321,4 @@ def main(): exception() if __name__ == "__main__": - main() \ No newline at end of file + main(sys.argv) From 05760fd438b253ae14cf57b369ecad1ecebcc875 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Wed, 25 Aug 2021 13:12:37 -0700 Subject: [PATCH 10/14] Update Plot.md --- docs/Subcommand/Plot.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/Subcommand/Plot.md b/docs/Subcommand/Plot.md index 315f297a6..06d7fdf7c 100644 --- a/docs/Subcommand/Plot.md +++ b/docs/Subcommand/Plot.md @@ -33,6 +33,10 @@ The following options are available: Enable debug output (if any) +### `-e|--exception` + +Enable raising of exception instead of printing exception message and exiting with error code 3. + ### `--figure:OPTIONS[=VALUE]` Specify matplotlib pyplot figure option From 2ec54a6604ed254b414c2d4a4ffca23ef9dd5a89 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 26 Aug 2021 06:15:11 -0700 Subject: [PATCH 11/14] Update Plot.md --- docs/Subcommand/Plot.md | 45 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/docs/Subcommand/Plot.md b/docs/Subcommand/Plot.md index 06d7fdf7c..f4c70f4e1 100644 --- a/docs/Subcommand/Plot.md +++ b/docs/Subcommand/Plot.md @@ -39,7 +39,14 @@ Enable raising of exception instead of printing exception message and exiting wi ### `--figure:OPTIONS[=VALUE]` -Specify matplotlib pyplot figure option +Specify matplotlib pyplot figure option. See [Matplotlib *figure*](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.figure.html) for details. + +- *figsize* (tuple): Specify figure width and height in inches. Default is `6.4,4.8`. +- *dpi* (float): Specify plot resolution in dots per inch. Default is `100.0`. +- *facecolor* (str): Specify the background color. Default is `white`. +- *edgecolor* (str): Specify the border color. Default is `white`. +- *frameon* (bool): Enable visibility of the figure frame. Default is `True`. +- *tight_layout* (bool): Enable adjustment of the padding to fit plot elements. Default is `False`. ### `-h|--help|help` @@ -63,7 +70,41 @@ Show the output ### `--plot:OPTIONS[=VALUE]` -Specify pandas DataFrame plot option +The following [Pandas DataFrame *plot*](https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.html) options are available. In addition, many [Matplotlib *plot*](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.plot.html) options may be used. + +- *x* (str or int): Specify the x data. Default is `None`. +- *y* (str, int, of list of str or int): Specify the y data. Default is `None` +- *kind* (str): specify the kind of plot, i.e., "line", "bar", "barh", "hist", "box", "kde", "density", "area", "pie", "scatter", "hexbin". Default is "line". +- *subplots* (bool): Plot y data on separate subplots. Default is `False`. +- *sharex* (bool): Share x axis when using `subplots`. +- *sharey* (bool): Share y axis when using `subplots`. +- *layout* (tuple): Specify `(rows,columns)` when using `subplots`. +- *figsize* (tuple): Specify `(width,height)` of figure in inches. +- *use_index* (bool): Specify the use of the row index as ticks. Default is `True`. +- *title* (str or list of str): Specify the (sub)plot title(s). Defualt is `None`. +- *grid* (bool): Enable plotting of the grid. Default is `False`. +- *legend* (bool): Enable plotting of the legend. Default is `False`. +- *style* (str): Specify the list style. Default is `-`. +- *logx* (bool): Enable plotting *x* axis on a logarithmic scale. Default is `False`. +- *logy* (bool): Enable plotting *y* axis on a logarithmic scale. Default is `False`. +- *loglog* (bool): Enable plotting both axes on a logarithmic scale. Default is `False`. +- *xticks* (sequence): Specify *x*-tick values. Default is automatic. +- *yticks* (sequence): Specify *y*-tick values. Default is automatic. +- *xlim* (tuple): Specify *x* limits. Default is `None`. +- *ylim* (tuple): Specify *y* limits. Default is `None`. +- *xlabel* (str): Specify the *x* axis label. Default is *x* field name. +- *ylabel* (str): Specify the *y* axis label. Default is *y* field name. +- *rot* (int): Specify the rotation for tick labels. Default is 0. +- *fontsize* (int): Specify the tick label font size. Default is 10. +- *position* (float): Specify the relative alignment of bars, i.e., 0 for left/bottom to 1 for top/right. Default is 0.5 (center). +- *colormap* (str): Specify the color map to use when *colorbar* is enabled. +- *colorbar* (bool): Enable the colorbar (only for *scatter* and *hexbin* plots). +- *table* (bool): Enable placement of a data table below the plot. Default is `False`. +- *stacked* (bool): Enable stacked *line*, *area* and *bar* plots. Default is `False` for *line* and *bar* plots, and `True` for *area* plots. +- *sort_columns* (bool): Enable sorting of column names when ordering the plot. Default is `False`. +- *secondary_y* (bool): Enable plotting of *y* values on secondary axis if multiple *y* fields are specified. Default is `False`. +- *mark_right* (bool): Enable marking with `(right)` secondary *y*-axis fields. Default is `False`. +- *include_bool* (bool): Enable inclusion of Boolean values in plot. Default is `False`. ### `-v|--verbose` From a0bf033e32a22faebed5fc863316b79dda6f4370 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 26 Aug 2021 06:16:27 -0700 Subject: [PATCH 12/14] Update Plot.md --- docs/Subcommand/Plot.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/Subcommand/Plot.md b/docs/Subcommand/Plot.md index f4c70f4e1..d05384e77 100644 --- a/docs/Subcommand/Plot.md +++ b/docs/Subcommand/Plot.md @@ -43,8 +43,8 @@ Specify matplotlib pyplot figure option. See [Matplotlib *figure*](https://matpl - *figsize* (tuple): Specify figure width and height in inches. Default is `6.4,4.8`. - *dpi* (float): Specify plot resolution in dots per inch. Default is `100.0`. -- *facecolor* (str): Specify the background color. Default is `white`. -- *edgecolor* (str): Specify the border color. Default is `white`. +- *facecolor* (str): Specify the background color. See [Matplotlib colors](https://matplotlib.org/stable/gallery/color/named_colors.html) for details. Default is `white`. +- *edgecolor* (str): Specify the border color. See [Matplotlib colors](https://matplotlib.org/stable/gallery/color/named_colors.html) for details. Default is `white`. - *frameon* (bool): Enable visibility of the figure frame. Default is `True`. - *tight_layout* (bool): Enable adjustment of the padding to fit plot elements. Default is `False`. From a6b49b90907789310a323b9494084ed596ebb0e8 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 26 Aug 2021 07:35:09 -0700 Subject: [PATCH 13/14] Update Plot.md --- docs/Subcommand/Plot.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/Subcommand/Plot.md b/docs/Subcommand/Plot.md index d05384e77..81756b3a3 100644 --- a/docs/Subcommand/Plot.md +++ b/docs/Subcommand/Plot.md @@ -100,6 +100,8 @@ The following [Pandas DataFrame *plot*](https://pandas.pydata.org/pandas-docs/st - *colormap* (str): Specify the color map to use when *colorbar* is enabled. - *colorbar* (bool): Enable the colorbar (only for *scatter* and *hexbin* plots). - *table* (bool): Enable placement of a data table below the plot. Default is `False`. +- *xerr* (str): Field to use for *x* error bars. Default is `None`. +- *yerr* (str): Field to use for *y* error bars. Default is `None`. - *stacked* (bool): Enable stacked *line*, *area* and *bar* plots. Default is `False` for *line* and *bar* plots, and `True` for *area* plots. - *sort_columns* (bool): Enable sorting of column names when ordering the plot. Default is `False`. - *secondary_y* (bool): Enable plotting of *y* values on secondary axis if multiple *y* fields are specified. Default is `False`. From 1733795b84341c4c9bbb90a9e0fa725fbebc54d4 Mon Sep 17 00:00:00 2001 From: "David P. Chassin" Date: Thu, 26 Aug 2021 07:35:10 -0700 Subject: [PATCH 14/14] Update gridlabd-plot --- gldcore/scripts/gridlabd-plot | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gldcore/scripts/gridlabd-plot b/gldcore/scripts/gridlabd-plot index 03e75dc9e..1ca6106c5 100755 --- a/gldcore/scripts/gridlabd-plot +++ b/gldcore/scripts/gridlabd-plot @@ -240,7 +240,13 @@ def main(args): validate = { "plot:y" : lambda x: x.split(","), + "plot:layout" : lambda x: list(map(int,x.split(','))), "plot:figsize" : lambda x: list(map(int,x.split(','))), + "plot:xtick" : lambda x: list(map(str,x.split(','))), + "plot:ytick" : lambda x: list(map(str,x.split(','))), + "plot:xlim" : lambda x: list(map(float,x.split(','))), + "plot:ylim" : lambda x: list(map(float,x.split(','))), + "plot:secondary_y" : lambda x: list(map(str,x.split(','))), "plot:fontsize" : int, "plot:position" : float, "figure:dpi" : float,