Skip to content

Ruby Grant 2019 Proposal

Alish Dipani edited this page Oct 21, 2019 · 10 revisions

Contact Information

Name: Alish Dipani

E-mail: [email protected]

Github: alishdipani

Twitter: Alu57202

Blog: https://alishdipani.github.io/

Brief Biography

I am a final year computer science student at Birla Institute of Technology and Science, Pilani, K.K. Birla Goa Campus, currently pursuing my undergraduate thesis at Institute of Molecular Genetics of Montpellier, CNRS, France working on Applications of Artificial Intelligence in Genetics.

I have been contributing to Rubyplot as a part of Google Summer of Code(GSoC) 2019 under the Ruby Science Foundation, during this period apart from minor improvements I have added ImageMagick backend to Rubyplot and integrated it with IRuby notebook. My final report for GSoC 2019 can be found here

Project Title

Rubyplot: An advanced plotting library for Ruby

Project Details

Background

Rubyplot is a plotting library for Ruby inspired from the library Matplotlib for Python. The aim of creating such a library is to create a platform-independent data visualization library to be used with Ruby for scientific computing and web development. For this library to have ruby-like behaviour the front-end is back-end agnostic while having multiple back-ends. The advantage of such an architecture is that it enforces ruby-like behaviour rather than backend-like behaviour while being compatible with multiple platforms.
Currently, Rubyplot supports two back-ends: GR and ImageMagick

Overview

Rubyplot is a library with which a user can visualize data easily with just a few lines of code while having full control over every aspect of the plot.

Any plot in Rubyplot can be created in 4 easy steps:

  1. Importing Rubyplot and set up important properties for Rubyplot i.e. backend, inline show, etc.
  2. Create a Figure(i.e. Canvas) on which the graphs will be plotted
  3. Choose the types of graphs and set their properties like data, title, colour, etc.
  4. Display the graph or save the Figure

An example of Rubyplot is :

require 'rubyplot'
Rubyplot.set_backend :magick

figure = Rubyplot::Figure.new(width: 30, height: 30)

axes00 = figure.add_subplot! 0,0
axes00.plot! do |p|
  d = (0..360).step(5).to_a
  p.data d, d.map { |a| Math.sin(a * Math::PI / 180) }
  p.fmt = 'ok-'
  p.marker_fill_color = :white
  p.marker_size = 0.5
  p.line_width = 2
  p.label = "sine"
end

axes00.title = "A plot function example"
axes00.square_axes = false

figure.write('example1.png')

The output of the code is: Example1

Tutorial for Rubyplot can be found here

History

Rubyplot started as two GSoC 2018 projects by Pranav Garg(@pgtgrly) and Arafat Dad Khan(@Arafatk) and the mentors from The Ruby Science Foundation(SciRuby), Sameer Deshmukh(@v0dro), John Woods(@mohawkjohn) and Pjotr Prins(@pjotrp). Pranav Garg worked on the GRRuby which had the GR backend and Arafat Dad Khan worked on Ruby Matplotlib which had the ImageMagick backend. The ultimate goal of combining both and creating Rubyplot. After GSoC 2018, Sameer Deshmukh combined both projects and created Rubyplot and he has maintained it ever since. Around May 2019, I started working on Rubyplot as a part of GSoC 2019.

GSoC 2019

I worked on Rubyplot as a part of GSoC 2019. Details of my contributions are as follows:

Adding ImageMagick backend

Before my GSoC project, Rubyplot only supported the GR backend. During the first phase of my GSoC, I implemented ImaheMagick backend by using rmagick library which has Ruby bindings for ImageMagick.

I added the support of the following plots for the ImageMagick backend: Scatter plot, Area plot, Bar plot, Bubble plot, Candle-stick plot, Histogram, Line plot, Error-bar plot, Box plot, Multi Bar plot, Multi-Box plot, Multi Candle-stick plot, Multi Stacked-bar plot.
Apart from implementing plots, I implemented X and Y ticks and tackled the following issues: Scaling of Figure according to the unit given by the user, conversion of ImageMagick coordinates to Rubyplot coordinates, scaling and rotation of text in a Figure, configuring ImageMagick backend for subplots within the Figure.

The show and plot functions

During the second phase of my GSoC, I implemented two features:

  1. The show function: show function is called upon the Figure to show the Figure in a pop-up window without saving it, this helps the user to quickly debug the program. It is similar to the show function in Matplotlib.

  2. The plot function: Previously Line and Scatter plots existed as two different kinds of plots. However, the crux of plotting both of these is exactly the same, the only difference being that in one kind of plot we have straight lines connected to co-ordinates while in the other the co-ordinates are simply 'decorated'. Thus can be many combinations of these plots, which can be combined together under a single 'plot' interface.
    So, the plot function can plot combinations of Scatter and Line plots. One special feature of plot function is that the user can specify the fmt argument which takes input a string of 3-4 characters to set the scatter plot marker type, line type and the colour of the plot.

IRuby integration

During the third phase of my GSoC, I integrated Rubyplot to IRuby notebooks. This allows the user to write and debug programs easily and showing the plots inside the IRuby notebook.
To decide whether to save the Figure, show Figure in a pop-up or in an IRuby notebook, Rubyplot has the output_device variable which is set according to the choice of the user. The user has to call the Rubyplot.inline function to change the output_device variable to show the Figure inside the notebook.

Tutorial

Finally, I created a tutorial for Rubyplot in an IRuby notebook through which any user can quickly get familiar with Rubyplot and can also see the examples of different types of plots. The tutorial can be viewed online here.

Related Work

There a lot of data visualization libraries for Ruby like matplitlib.rb, gruff, nyaplot, plotrb, gnuplotrb, numo-gnuplot, etc.
Each of these libraries have their own advantage like plotting interactive and 3D graphs, being compatible with numo, etc.

Rubyplot aims to club together all these functions in one place by providing easy to code, good looking plots with a support for a large variety of plots, providing support for Numerical arrays and providing Image processing functionality.
While having these advantages over the other libraries, Rubyplot will have a ruby-like interface with support for multiple back-ends and supporting different platforms.

Implementation Details

Front-end

An overview of how the code works is that it first creates the canvas(figure) on which everything will be drawn, then the space for subplots is created in this figure and finally, the subplots are added. When a subplot is added, the type of the subplot (i.e. the plot in this particular subplot like bar plot, line plot, etc.), the properties of the subplot, the data for the plots in this subplot, the title for the X-axis, Y-axis and the subplot itself etc. are defined. After setting the properties of all the subplots, the write or show function is called to actually draw and save or show the figure. During this final step, everything is drawn including the Legendbox of the subplots.

Rubyplot has a back-end agnostic front-end and since the front-end of Rubyplot is set-up new back-ends can be easily added. It is also integrated with IRuby notebooks to allow easy debugging.

An explanation of the example code according to the steps mentioned is:

  1. First, we import Rubyplot and set the back-end as ImageMagick:
require 'rubyplot'
Rubyplot.set_backend :magick
  1. Now the Figure(i.e. Canvas) is created on which everything will be drawn, the user can choose the size of the Figure and the unit of measurements too, here the Figure is 30cm x 30cm:
figure = Rubyplot::Figure.new(width: 30, height: 30)
  1. Now we define the properties of the subplots. By default, the Figure has only one subplot but the user can set any number of subplots. To set the properties of the subplots, first, we choose the subplot and then define the properties of the subplot. The user has control over every aspect of the Figure.
axes00 = figure.add_subplot! 0,0
axes00.plot! do |p|
  d = (0..360).step(5).to_a
  p.data d, d.map { |a| Math.sin(a * Math::PI / 180) }
  p.fmt = 'ok-'
  p.marker_fill_color = :white
  p.marker_size = 0.5
  p.line_width = 2
  p.label = "sine"
end

axes00.title = "A plot function example"
axes00.square_axes = false
  1. After setting all the properties, we finally call the write or the show function which saves or shows the Figure. All the actual drawing is done when the write or show function is called:
figure.write('example1.png')

Thus, in just a few lines of code, the user can draw plots. A detailed explanation of the code can be found here.

Artist Layer

The front-end interacts with a layer called the artist layer which creates the Objects for different shapes, here in the example, a Rubyplot::Artist::Line2D Object is created for drawing the line. This artist layer calls the back-end functions which actually draw the shapes on the Figure.

The advantage of having the Artist layer is that we can decide the behaviour of plots without being dependent on the back-end, for example, we can add new plots just by adding more objects of shapes and then configuring the back-end functions accordingly. This layer makes the configuration of back-end according to the front-end easy and also keeps the front-end, back-end agnostic.

Back-end

The back-end of Rubyplot is set by the user, the front-end functions call the back-end functions and create the Figure, draw the X and Y axes, draw the legends and draw the shapes as decided by the user, here in this example, lines and circles are drawn. Finally, the back-end saves or shows the Figure after drawing it.

Here, the Artist layer for the plot function calls the back-end functions 'draw_markers' and the Rubyplot::Artist::Line2D calls the draw_lines function. These functions are:

def draw_markers(x:, y:, type: :default, fill_color: :default, border_color: :default, size: nil)
  y.each_with_index do |iy, idx_y|
    ix = transform_x(x: x[idx_y],abs: false)
    iy = transform_y(y: iy, abs: false)
    # in GR backend size is multiplied by
    # nominal size generated on the graphics device
    # so set the nominal_factor to 15
    within_window do
      size[idx_y] *= NOMINAL_FACTOR_MARKERS
      MARKER_TYPES[type].call(@draw, ix, iy, fill_color, border_color, size[idx_y])
    end
  end
end

def draw_lines(x:, y:, width: 2.0, type: :default, color: :default, opacity: 1.0)
  within_window do
    y.each_with_index do |_, idx_y|
      if idx_y < (y.length - 1)
        x1 = transform_x(x: x[idx_y], abs: false)
        y1 = transform_y(y: y[idx_y], abs: false)
        x2 = transform_x(x: x[idx_y + 1], abs: false)
        y2 = transform_y(y: y[idx_y + 1], abs: false)
        LINE_TYPES[type].call(@draw, x1, y1, x2, y2, width, color, opacity)
      end
    end
  end
end

These functions use the rmagick functions to actually draw the shapes. But, before drawing the shapes, the within_window function is called which configures the ImageMagick space to Rubyplot space i.e. the sizes, the coordinates, etc.
For the Scatter and Line plot, there are hashes of lambdas which draw different shapes according to the type of marker or the line, for example for a circle marker, a circle is drawn by drawing the shape using rmagick's function (which uses ImageMagick).

Similarly, different plots call different back-end functions to draw different shapes and hence draw the Figure as the user decides.

Codebase

https:/SciRuby/rubyplot

Project deliverables

The main aim is to develop Rubyplot so that it can be directly used by the users in any application. Since Rubyplot is inspired by Matplotlib, the goal is to add every functionality of Matplotlib to Rubyplot and even more functionality. Rubyplot should serve the users who would like to use it for data visualization by providing easy and gook-looking plots as well as for scientific computing and applications like Machine Learning by providing image loading, saving and processing. Rubyplot should also support other array types like Nmatrix and Numo for faster computation.

Major Deliverables

The Major deliverables are:

1. Improving the appearance of the plots:

Currently, Rubyplot can draw rudimentary plots but for it to be used in real-world applications, the appearance of plots needs an improvement. These improvements include fixing bugs as well as enhancements, some of them are listed below:

  • Adding the option to change opacity for GR back-end.
  • Fixing Line plot bug for ImageMagick back-end: When two lines of different colour are drawn, both lines are drawn of the same colour as the last colour declared.
  • Intelligently determining the location of title for Axes objects.
  • Fixing the multiple subplot overlap bug: When multiple subplots are drawn in a Figure, the X-axis title of a plot overlaps with the title of a plot directly below it.
  • Improving the X and Y axes titles for ImageMagick backend, currently the position of the titles are static and the text sizes too. These will be intelligently determined and the text will be scaled according to the size of the Figure.
  • ImageMagick back-end is missing some marker types and line types for Scatter and Line plots, these will be implemented.
    Specifically, the marker types are: star, solid_star, asterisk, tri_up_down, pentagon, hexagon, heptagon, octagon, star_4, star_5, star_6, star_7 and star_8.
    The line types are: dashed, dotted, dashed-dotted, dash-2-dot, dash-3-dot, long dashed, longs short dashed, spaced dash, spaced dot, double dot and triple dot.

This is not an exhaustive list and any other required enhancements and improvements will be done.

2. Improving legends:

Currently, the legends in Rubyplot are static i.e. their position, sizes, text sizes are fixed but they need to adjust themselves according to the plots. The improvements that will be done are:

  • Intelligently determining the position of the legend box.
  • Adding a border to the colour boxes in the legend box.
  • Intelligently determining the size of the legend box.
  • Intelligently determining the size of the text in the legend box.

3. Improving ticks:

The X and Y axes ticks need major improvements for both the back-ends as well as the Front-end. So, the Front-end will be improved allowing the user to control every aspect of the ticks like the titles of the ticks, the positions, the number of ticks, the sizes, the opacity, etc. The back-end functions will be implemented accordingly.

4. Adding documentation:

Currently, the documentation is not complete. So, the documentation will be completed and description and some examples will be added for every function.

5. Improving tests:

Currently, Rubyplot uses rspec for testing but it has a bug which causes the test to fail when the plots are of a darker colour, the tests should not be colour dependent but they should test the actual plot. This bug will be fixed and new tests will be added for functionality which is missing them like the fmt argument for the plot function does not have tests yet.

6. Adding new types of plots:

Rubyplot currently supports these plots: Scatter plot, Area plot, Bar plot, Bubble plot, Candle-stick plot, Histogram, Line plot, Error-bar plot, Box plot, Multi Bar plot, Multi-Box plot, Multi Candle-stick plot, Multi Stacked-bar plot.
New plots will be added which are pie chart plot, radar plot, dot plot, violin plot and a guide will be set-up on how to add new plots for future developers.

7. Integrate Rubyplot with XND arrays:

For fast and flexible computations and graph plotting, the XND arrays will be used as the storage and algebra backend. This will also help Rubyplot become a better library for scientific computing.

8. Add support for Nmatrix and Numo:

Currently, Rubyplot only supports simple Ruby arrays. Support for other arrays types is required for scientific computing applications. This will allow the users to perform any computations on the data with Rubyplot providing the support for visualizing the data.

9. Add Image saving and loading functionality:

For Rubyplot to be used as a Machine Learning library, the option for Image loading and saving will be added. This will be similar to the Matplotlib functions imread and imshow which read images from a file and show it.

10. Conversion of Loaded images into different array types:

To perform various computations on the images, the loaded images will have an option to be converted into Nmatrix or Numo arrays so that it can be further used for applications like Machine Learning.

11. Adding Image processing options(Optional):

ImageMagick back-end supports image processing functionality, for example, the minimagick library provides Ruby bindings with quick computations for such functions, rmagick also supports image processing functions but it is slower and requires more menory than minimagick. A list of Image processing function provided by minimagick is here. An option for performing these computations on Images will be added to Rubyplot for scientific computing and Machine Learning applications.

12. Adding support for interactive plots and 3D plots(Optional):

Currently, Rubyplot only supports static 2D plots, but interactive plots and 3D plots are required for better visualization and so research will be done on how to add these functionalities to Rubyplot.

Timeline

A timeline with deliverables as mentioned above is as follows:

24 October - 3 November

  • Improving ticks

4 November - 17 November

  • Improving legends

18 November - 1 December

  • Improving the appearance of plots

2 December - 15 December

  • Improving tests

16 December - 29 December

  • Adding documentation

30 December - 14 January

  • Adding support for new plots
  • Completing any remaining work and writing intermediate reports

15 January - 26 January

  • Integrating Rubyplot with XND arrays

27 January - 9 February

  • Adding support for Nmatrix and Numo

10 February - 23 February

  • Adding support for Nmatrix and Numo
  • Adding Image loading and saving functionality

24 February - 13 March

  • Adding support for conversion of Loaded images into different array types
  • Completing any remaining work and writing the final report

Future work

  • Adding image processing functionality
  • Adding interactive plotting
  • Adding 3D plotting

Mentorship

Sameer Deshmukh, my GSoC mentor has agreed to mentor me during the grant period. He is the co-author of Rubyplot and a part of Ruby Science Foundation, he is a two-time recipient of Ruby Grant. Contact details - email, website.