guicho271828 / eazy-gnuplot

Super Duper Doopa Booka Lispy Gnuplot library
http://guicho271828.github.io/eazy-gnuplot/
63 stars 8 forks source link

additional commands gp-replot gp-format #24

Closed mmaul closed 8 years ago

mmaul commented 9 years ago

The replot command is somewhat common, I propose gp-replot.

Also I propose a gp-format command, which takes a format string and arguments and renders to the plot-stream.

guicho271828 commented 9 years ago

Thanks for the suggestion, but I'm not really excited about the idea. replot is one thing I tried to avoid in eazy-gnuplot, and is the very motivation/reason I wrote eazy-gnuplot.

When I used to write a gnuplot script by hand, replot often causes incorrect plots. For example, on svg terminal, replot plots two overlapping graphs in one place, with different sizes and dimensions depending on the auto adjustments. This is probably because it does not erase the old plot.

You would then see why I implemented eazy-gnuplot's plot so that it reads from the inline data, not as a sequence of replot. Yes, this is one way to completely eliminate the use of replot while plotting multiple data.

Re: gp-format, I think the meaning of plot stream is well defined and does not need additional abstraction. (with-plots (s ...) ...) offers an environment where s is bound to a stream dedicated to emitting a gnuplot script. I'd like to make it as transparent as possible so that no one gets scarred if there were some magic under the abstraction.

guicho271828 commented 9 years ago

This is also relevant to the fact that I am not so active in supporting multiplots. (not against it, though.) It introduces problems like #22 , and makes the code much more complicated.

The primary cause of #22 is in the sequential/imperative nature of multiplot: It requires set and unset before plot in order to render the figure appropriately. If further abstraction is possible, I rather want it to be more declarative. Unlike (format s ...) vs (gp-format ...), this kind of abstraction helps the user and is in fact necessary.

In terms of personal needs, I mostly use eazy-gnuplot for single plots and then organize the results with latex (for writing a paper), so currently I am not using multiplots.

My point is, gnuplot team is extending gnuplot too much, going beyond its minimalistic, original concept of plotting something. It even tries to increase the support for control structure like for (in much less complete form, compared to lisp). We should be careful about this, and I won't support those features. However, I'm not sure if multiplot is one of them.

mmaul commented 9 years ago

Regarding gp-format, it was not so much the need for a new abstraction so much as the need to render to plot-stream instead of user-stream, which relates to the ordering of statements. Which can be important especially in multiplot. For some stuff like variables or declaring functions in gnuplot it doesn't matter so much, other things it might. Another way of solving this problem is just set everything to render to plot-stream (Well except for the data).

Regarding replot, it is pretty much meant to be used interactively, to effect a change to a graph displayed in a terminal as opposed to constructing a final product. This is most likely why it does odd things in non-interactive terminals.

mmaul commented 9 years ago

I propose removing gp-set, gp-unset, gp-clear, withdrawing my request for gp-replot and gp-format and replacing them with two functions gp-stmt and gp-zstmt Below are the proposed implementations, descriptions in the docstrings. Look at the example below the implementation to see them in action.

(defun gp-zstmt (left-side &rest right-sides &key &allow-other-keys)
  "Zipped statement ,render gnuplot `left-side` as string infront of each plist pair in `right-sides` rendering a gnuplot statement for each key-value pair in right sides.
   For example:
      (gp-zstmt :set :param '()
               :lmargin 10)
   Generates
     set param
     set lmargin 10
- Arguments:
  - left-side : first word in gnuplot statement
  - right-sides : Keyword arguments with gnuplot assignments, quoted by gp-quote.
- Return:
  NIL
"
  (if right-sides
      (map-plist right-sides
                 (lambda (key val)
                   (format *plot-stream* "~&~a ~a ~a~%"
                           left-side key (gp-quote val))))
      (format *plot-stream* "~&~a~%" left-side)))

(defun gp-stmt (left-side &rest right-side)
  "Single statement ,render gnuplot `left-side` as string infront of gp-quoted contents of ars
   For example:
     Command:
       (gp-stmt :set :param)
     Generates:
       set param
       set lmargin 10
     Command
       (gp-stmt :style :line 1 :lc :rgb '(\"'#999999'\") :lt 1 '(\"#border\"))
     Generates:
- Arguments:
  - left-side : first word in gnuplot statement
  - right-side : arguments remaining in the argument list rendered as parameters to left-side command
- Return:
  NIL
"
  (format *plot-stream* "~&~A ~A~%" left-side (gp-quote right-side))
  )

And an example of their use:


(defun labeled-contour-plot ()
  (eazy-gnuplot:with-plots (*standard-output* :debug t)
    (eazy-gnuplot:gp-setup :output "-"
                           :terminal '(:wxt :persist))
    (gp-stmt :clear)
    (gp-stmt :unset :key)
    (gp-stmt :unset :surf)
    (gp-stmt :unset :clabel)
    (gp-zstmt :set
              :view :map
              :contour :base
              :xrange '("[0:5]")
              :yrange '("[0:5]")
              :style '( fill solid)
              :for '("[n = 1:4]" cntrparam levels discrete ("n**2"))
              :for '("[n = 1:4]" object n circle at n #\, n size 0.2 front fillcolor rgb ("'#ffffff'") lw 0)
              :for '("[n = 1:4]" label n ("sprintf(\"%d\", n**2)") at n #\, n center front)
              )
    (gp-stmt "f(x,y)" := '("x < 5 ? sin(x) + (1+1/y): NaN"))
    (func-splot "x*y" :with '(:lines :lt 2 :lw 4))
    (func-splot "f(x,y)" :with '(:points :pt 7 :ps 2 ))
    (gp-stmt :save "\"labeled-contour-plot.gp\"")
    (gp-zstmt :set :title "Labeled Contour Plot")
    (gp-stmt :replot)
    ))
guicho271828 commented 9 years ago

sounds better than gp-set/unset or other specialized variations. I'd like it if it is a single interface, i.e., could you remove one of gp-stmt / gp-zstmt ? If so, the more appropriate name would be just gp, which is just shorter.

by the way, I don't recommend using for statements in gnuplot unless necessary. you can instead do

(apply #'gp-zstmt :set :cntrparam :levels :discrete (loop for n from 1 to 4 collect (expt n 2)))

One related idea is emulating a dynamic binding, i.e.,

(gp-let ((:view :map))
   (xxx-plot yyy zzz))
;; unset :view when exiting the construct
mmaul commented 9 years ago

So lets go with: Change gp-stmt to gp. Drop gp-zstmt

I like the gp-let concept, It is a little more complicated than just issuing a unset on everything that was set in gp-set since the values you change could been have set to other values previously as opposed to being unset. What would be necessary is to save state by calling the show command for each property set in gp-let and then restoring the state when gp-let is done.

mmaul commented 9 years ago

Okay so, I'm sending a pull request with gp-set,gp-unset,gp-replot, gp-clear removed and replaced by gp <command> &rest args. Tests for issue 8 and 21 have been updated and should cover these changes. Also including the Ipython notebook and HTML output in the docs directory.