Thursday, December 22, 2016

Visualize your ideas using Rasem

A major part of a researchers' work is to write papers and articles that describe their work and make posters and presentations to better communicate their ideas. We all believe that "A picture is worth a thousand words" and we are always looking for better ways to visualize our ideas. In this blog article, I present Rasem, a library that I built as I started my PhD and used it in many of my papers and presentation to build nice visualizations like the ones shown below.






The figures shown above are generated from hundreds or thousands of records and are formatted based on some logic related to the algorithm being illustrated in the relevant papers. Furthermore, although not shown in this blog article, these figures are vector images which would be very nice to include in articles that will be printed. These two features, i.e., thousands of elements and user-defined logic, make it impractical to draw these figures manually using any image editor such as Inkscape. This blog article describes a simple tool that makes it very easy to generate all these figures.

Overview and Usage

Rasem uses Ruby code to describe images and generates standard SVG images that can be further edited or converted to EPS or PDF by popular applications including the open source Inkscape and Adobe Illustrator. Even if you're not a ruby developer, you can easily use Rasem as it's very simple to use.

Installation steps

  1. Install the Ruby compiler at https://www.ruby-lang.org/en/
  2. Install Rasem gem by running the command
    gem install rasem

A simple example

Create a text file named 'test.rasem' with the following content
self.width = 100
self.height = 100
circle 20, 20, 5
circle 50, 50, 5
line 20, 20, 50, 50
The contents of the file are self explanatory. Now to generate an image out of this figure, all you need to do is to type the following command.
rasem test.rasem
or simply type
rasem
It will generate an SVG file named 'test.svg' which you can simply display in your Internet browser and is shown below.


You can find more examples at https://github.com/aseldawy/rasem
The best feature of Rasem is that the file shown above is a fully functioning Ruby code. Which means you can include additional Ruby constructs and integrate it seamlessly with the visualized image. This is further shown in the following example which visualizes a simple graph.
nodes = [[10,10], [20,20], [10,20]]
radius = 3
with_style :fill=>"white", :stroke=>"black" do
  for node in nodes
    circle node.first, node.last, radius
  end
end

line nodes[0].first, nodes[0].last, nodes[1].first, nodes[1].last,
  :stroke=>"blue"
The generated image is shown below.


How it works

Rasem relies on the dynamic features of Ruby which allows it to compile and run user-defined code on-the-fly. The line and circle commands shown above are nothing but function calls which are part of the API of Rasem. There are more functions in Rasem including rectangle, polygon, and polyline. These functions generate XML elements that conform to the SVG standard to draw the corresponding elements. That's how you can easily integrate your Ruby code into your figure to add user-defined logic which can affect both the generated figure elements and their style. You can also write a code that opens an external file, parses it, and generates the figure from this data. Rasem is open source and you're welcome to use it or contribute to it.

A Voronoi Diagram example

In this part, we show a more sophisticated example that we used to draw a Voronoi Diagram figure similar to the one shown below. We describe some tricks that can be used to generate similar figures.
The input file was generated using the Voronoi diagram operation in SpatialHadoop and it looks like the following snippet.
189.05078396875297,319.3714811786753    POLYGON ((194.43089260205164 325.5334210386081, 170.08384007573167 321.1403988020476, 198.24373337246874 315.0171135767282, 194.43089260205164 325.5334210386081))
3.678985871399587,429.815627730571    LINESTRING (-46.06193792012205 322.23283446689334, -46.06193792012205 322.23283446689334)
Each line has X and Y coordinates for the site followed by a WKT representation of a POLYGON or a LINESTRING that represents a closed or an open Voronoi region, respectively.
We start by providing the code that reads and parses those lines.
def read_regions(filename)
  lines = File.readlines(filename)
  regions = lines.map do |l|
    if l =~ /(.*),(.*)\t(.*)/
      {:x => $1.to_f, :y => $2.to_f, :wkt => $3}
    end
  end
end

To draw each entry, we provide the following simple code that draws the Voronoi region as either a polygon or a polyline.

def draw_region(svg, region)
  vertices = region[:wkt].scan(/[\-\d\.]+\s+[\-\d\.]+/).map{|coords| coords.split(" ").map(&:to_f)}
  if region[:wkt].index 'POLYGON'
    svg.polygon vertices
  elsif region[:wkt].index 'LINESTRING'
    svg.polyline vertices
  end
end

Finally, the following function takes a file and draws all the regions in it. It also draws all sites as small circles.
def draw_vd(in_file, out_file, mbr)
  outs = File.open(out_file, "w")
  mbr_width = mbr[2] - mbr[0]
  mbr_height = mbr[3] - mbr[1]
  regions = read_regions(in_file)
  Rasem::SVGImage.new({:width=>mbr_width, :height => mbr_height}, outs) do
    rectangle mbr[0], mbr[1], mbr_width, mbr_height, :stroke=>"black", :fill=>:none
    # Sub-group 1: Points
    for region in regions
      circle region[:x], region[:y], 1
      draw_region svg, region
    end
  end
  outs.close
end

draw_vd "vd", "vd.svg", [0, 0, 500, 500] 

Now, let's make it more interesting. We would like to give different colors, as shown above, for safe and unsafe regions as described in the SpatialHadoop Voronoi diagram blog post. We define the following function that decides whether a site is safe or not.

def is_safe(region, mbr)
  site = [region[:x], region[:y]]
  vertices = region[:wkt].scan(/[\-\d\.]+\s+[\-\d\.]+/).map{|coords| coords.split(" ").map(&:to_f)}
  for vertex in vertices
    # Distance from the vertex to the site
    d_site = distance_to(site, vertex)
    # Minimum distance from the vertex to the MBR boundary
    d_mbr_min = [vertex[0] - mbr[0],
             mbr[2] - vertex[0],
             vertex[1] - mbr[1],
             mbr[3] - vertex[1]].min
    return false if d_mbr_min < d_site
  end
  return true
end

def distance_to(p1, p2)
  dx = p1[0] - p2[0]
  dy = p1[1] - p2[1]
  return Math::sqrt(dx * dx + dy * dy)
end


We can now easily make a test before drawing each region to decide the color we use to draw each region. However, as a part of this tutorial we will do it in a different way. Instead of applying the styles in the Rasem code, we will apply them in Inkscape. We will use Rasem only to separate safe and unsafe regions. We will use the "group" feature in Rasem and SVG to put each set of regions together as shown in the following code.

def draw_vd(in_file, out_file, mbr)
  outs = File.open(out_file, "w")
  mbr_width = mbr[2] - mbr[0]
  mbr_height = mbr[3] - mbr[1]

  regions = read_regions(in_file)

  Rasem::SVGImage.new({:width=>mbr_width, :height => mbr_height}, outs) do
    rectangle mbr[0], mbr[1], mbr_width, mbr_height, :stroke=>"black", :fill=>:none
    # Group 1: Safe regions
    group do
      # Sub-group 1: Points
      group do
        for region in regions
          if is_safe(region, mbr)
            circle region[:x], region[:y], 1
          end
        end
      end
      # Sub-group 2: Regions
      group(:fill => :none) do
        for region in regions
          if is_safe(region, mbr)
            draw_region svg, region
          end
        end
      end
    end

    # Group 2: non-safe regions
    group do
      # Sub-group 1: Points
      group do
        for region in regions
          if !is_safe(region, mbr)
            circle region[:x], region[:y], 1
          end
        end
      end
      # Sub-group 2: Regions
      group(:fill => :none) do
        for region in regions
          if !is_safe(region, mbr)
            draw_region svg, region
          end
        end
      end
    end
  end
  #svg.write(outs)
  outs.close
end


The code is much longer than before but it is very simple. We create two big groups for safe and unsafe regions. Then, we create two subgroups within each one for sites and regions. This makes a total of four subgroups, namely, safe sites, safe regions, unsafe sites, and unsafe regions. This makes it easier to select apply a style to each individual group in Inkscape. This technique is preferable than applying the styles in the code because you can easily choose the most appealing colors in a GUI and get an instant feedback on your choice rather than modifying the code and regenerating the image.

Exporting standard SVG images is a very powerful feature in Rasem as it allows users to further edit the generated images in their favorite image editor such as Inkscape or Adobe Illustrator. This way, you can easily integrate different figures together, add some text, or some other annotations.

As usual the source code and sample files are provided on the following link.
https://sites.google.com/site/aseldawyblogfiles/rasem_files

Further readings

Rasem project page on Github
Voronoi diagram and Delaunay triangulation in SpatialHadoop
Example files

No comments:

Post a Comment