@spip I'll answer your question below aswell.

> Is this compatible with other libraries, such as arraymancer, etc? I think 
> that one of the biggest strengths of the python numerical ecosystem is the 
> good inter-operability of most plotting libraries with numpy. So if that is 
> not already the case I would suggest making that your highest priority.

The answer to that is "sort of". I'll need to explain a little to answer the 
why and what I mean by "sort of".

**The long answer**

Originally when I started the library I never planned to write a data frame 
library to go with this. I quickly realized however that (at least with a 
library like `ggplot2`) one doesn't work well without the other. In a normal 
plotting library every plotting function is a special case. Essentially each 
kind of plot wants data in a specific form / of a specific data type.

So in the beginning I specifically didn't want to use arraymancer internally. I 
love that library, but given that all I wanted to write was a "plotting 
library", this meant two things specifically for me:

  * The library is essentially a sink for the user's data. It doesn't return 
anything, so there's no reason for the internal data type to conform to any 
standards
  * If a user wants to create a plot, performance will **not** be an important 
consideration (which does not imply performance of a plotting library does not 
matter!). Creating a plot will always be slow (compared to pure number 
crunching anyways). There are use cases for libraries, which can create plots 
at several hundred fps. But to be honest, if I need to create a huge number of 
plots and am thus performance sensitive, the question is if a plotting library 
is the best tool in the first place.



For this reason I decided to avoid having arraymancer as a dependency, because 
all its strengths are mostly useless for the intended purpose, but would mean I 
introduce an unnecessary dependency.

If a user is using arraymancer for calculations, it's easy to convert the 
required data to ggplotnim's data types. I felt the overhead of copying the 
data was not a big deal under the assumption mentioned above.

**But** , things did somewhat change when I started to write the data frame.

My first idea was actually to use `NimData`, since I really like library. 
However, the (depending on viewpoint) advantage / disadvantage that their type 
is entirely defined via a schema at compile time, didn't appeal to me. I didn't 
want to end up with a ggplot2 clone that was super restrictive, because 
everything had to be known at compile time.

I was actually hoping that @bluenote would pick up his development of Kadro 
again:

[https://github.com/bluenote10/kadro](https://github.com/bluenote10/kadro)

That sounded perfectly suited. But since he didn't, I simply started to hack 
together something that suits the needs of the library.

Originally in fact the `DataFrame` type was generic and my goal was to write 
the code in such a way that the underlying type does not matter. This made 
things complicated though. In fact I even thought about an arraymancer backend 
from the start:

[https://github.com/Vindaar/ggplotnim/blob/master/playground/arraymancer_backend.nim](https://github.com/Vindaar/ggplotnim/blob/master/playground/arraymancer_backend.nim)

which however never progressed from there. Mainly because I couldn't figure out 
how to make use of arraymancer's performance, when majority of data frame 
operations I did ended up copying around data. Which is how I ended up with 
@PMunch's persistent vector from Clojure. It kind of allowed me to "copy as 
much as I want" without the performance penalty.

This is how we got to the current situation. The data frame is okayish fast for 
simple things to prepare a plot. Anything else, I can't recommend it (also 
because it's extremely lenient on types!).

**tl;dr**

Compatibility with the "rest of the ecosystem" isn't there for practical 
reasons.

The thing is I'd love to profit from @mratsim's amazing work on arraymancer and 
laser!

Once I go back and reconsider performance of the data frame, I hope I will end 
up using as much of arraymancer as I can to be honest. I just need to figure 
out how to do it. :)

> Other than that, I didn't see mention of support for contour plots in the 
> docs. It is surprising how often those come in handy in many scenarios so I'd 
> like for you to add that if it is not available yet. Another thing I like to 
> do is to combine line plots with histograms and/or kernel density plots on 
> the X and Y axis (to get a quick idea of the distribution of the values, 
> particularly in time series). It would be neat to support for that too.

Good point. Contour plots are something I simply didn't think about.

I've never actually thought about how those are implemented before. I guess 
it's just a 2 dimensional KDE, right?

Since I will be implementing `geom_density`, for which I need KDEs anyways, I 
might as well implement N dimensional KDEs. That makes performance an even 
bigger issue though.

This is a case where implementing this in arraymancer would defintely be 
helpful and then just pass the required data from a DF to arraymancer. Maybe in 
a few months time, we can just write:
    
    
    import ggplotnim, arraymaner
    let df: DataFrame = someDataFrame()
    let dfKde = df.kde("x", "y", "z")
    
    Run

where `kde` would be an arraymancer proc; or something along those lines…

> Finally, in signal processing work you are often working with complex 
> samples. In that context it is often handy to plot the I and Q components vs 
> time, placing 2 subplots on top of each other, and linking the X (time) 
> zoom/pan of the two subplots. It would be really nice if that were supported.

When you say "on top of each other". Do you mean essentially a plot with a 
secondary axis? These are already supported, but are somewhat limited right now.

There's no recipe of these at the moment though. An example:

[https://gist.github.com/Vindaar/5292d4d9b8fb667e3eb27061627dbbfe#gistcomment-3225761](https://gist.github.com/Vindaar/5292d4d9b8fb667e3eb27061627dbbfe#gistcomment-3225761)

The downside of secondary axes at the moment is that it is just a fake axis. It 
is ticks and labels on the RHS of the plot, but the underlying data is still 
drawn into the coordinate system defined by the main axis.

I know that `ggplot2` explicitly does not allow completely independent axes 
(only those which can be calculated from one another, e.g. unit conversions), 
because [Hadley Wickham thinks other cases are easily 
misleading](https://stackoverflow.com/a/3101876). And to an extent I agree. 
However, I **do** think there is a place for them, so I will provide better 
support for them in the future.

**real subplots**

Or do you mean a normal subplot consisting of several (in principle not 
connected) plots in a single graphic? Those are also supported, but their use 
is not perfectly nice yet. One has to make use of `ginger` functionality 
directly.

An example inspired by: 
[https://staff.fnwi.uva.nl/r.vandenboomgaard/SP20162017/SystemsSignals/plottingsignals.html](https://staff.fnwi.uva.nl/r.vandenboomgaard/SP20162017/SystemsSignals/plottingsignals.html)
    
    
    import ggplotnim, seqmath, math, sequtils, complex, ginger
    let t = linspace(-0.02, 0.05, 1000)
    let y1 = t.mapIt(exp(im(2'f64) * Pi * 50 * it).re)
    let y2 = t.mapIt(exp(im(2'f64) * Pi * 50 * it).im)
    let df = seqsToDf({ "t" : t,
                        "Re x(t)" : y1,
                        "Im x(t)" : y2 })
    let plt1 = ggcreate(
      ggplot(df, aes("t", "Re x(t)")) +
        geom_line() +
        xlim(-0.02, 0.05) +
        ggtitle("Real part of x(t)=e^{j 100 π t}"),
      width = 800, height = 300
    )
    let plt2 = ggcreate(
      ggplot(df, aes("t", "Im x(t)")) +
        geom_line() +
        xlim(-0.02, 0.05) +
        ggtitle("Imaginary part of x(t)=e^{j 100 π t}"),
      width = 800, height = 300
    )
    # combine both into a single viewport to draw as one image
    var plt = initViewport(wImg = 800, hImg = 600)#wImg = 800.0, hImg = 800)
    plt.layout(1, rows = 2)
    # embed the finished plots into the the new viewport
    plt.embedAt(0, plt1.view)
    plt.embedAt(1, plt2.view)
    plt.draw("real_imag_subplot.pdf")
    
    Run

Which produces the following plot: 
[https://gist.github.com/Vindaar/5292d4d9b8fb667e3eb27061627dbbfe#gistcomment-3225762](https://gist.github.com/Vindaar/5292d4d9b8fb667e3eb27061627dbbfe#gistcomment-3225762)

Another example can be found here: 
[https://gist.github.com/Vindaar/fc158afbc75627260aed90264398e473](https://gist.github.com/Vindaar/fc158afbc75627260aed90264398e473)

If you have something else in mind, let me know! 

Reply via email to