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
- Install the Ruby compiler at https://www.ruby-lang.org/en/
- 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, 50The 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.
To draw each entry, we provide the following simple code that draws the Voronoi region as either a polygon or a polyline.
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 GithubVoronoi diagram and Delaunay triangulation in SpatialHadoop
Example files
No comments:
Post a Comment