Subscribe:

Pages

Displaying Multi Column Data in wxRuby

This post is about using the ListCtrl widget within wxRuby to display data in a table format, using several columns naming the fields in the data. The most straightforward way to manage the data is to construct an underlying data model, and use the virtual listctrl style to have your data displayed dynamically. This method also efficiently displays large datasets.

To demonstrate the simplest case, I will display some data from a CSV text file. For example, the
letter recognition dataset from the UCI machine learning repository consists of two files. The file ending '.data' contains the raw data. Each line contains the class and attributes of an instance from the data set. Each instance can be represented using a Ruby struct. Taking the attribute names from the file ending '.names', I build the following struct:

LetterInstance = Struct.new(:letter, :x_box, :y_box, :width, :height,
:on_pixels, :x_bar, :y_bar, :x2bar, :y2bar,
:xybar, :x2ybar, :xy2bar, :x_ege, :xegvy,
:y_ege, :yegvx)

I define the Struct with the attribute names in the order that they occur in the CSV file, as this makes reading the CSV file simple. To read in the textfile to create an array of instances of this struct:


@@data = []

File.open("letter-recognition.data", "r") do |f|
f.each_line do |line|
@@data << LetterInstance.new(*line.strip.split(","))
end
end

Displaying the Data

Building the list control is almost trivial. I use two facts about Ruby Structs to help in constructing the table automatically. First, the method 'members' can be used to find all the field names within the Struct; these are used for the column headings.

For example: @@data[0].members.join(", ")
returns: "letter, x_box, y_box, width, height, on_pixels, x_bar, y_bar, x2bar, y2bar, xybar, x2ybar, xy2bar, x_ege, xegvy, y_ege, yegvx"

Second, fields within a Struct can be accessed using array indexes. So, @@data[0][0] returns the value in the 'letter' field of the first item.

The following few lines of code create a class for our own virtual list control, which extends Wx::ListCtrl. We are building a particular kind of list control, which uses 'report format' and a 'virtual list data model'. Other kinds exist, but are not covered here. The initialize method builds the columns automatically, and sets the number of items.
The method 'on_get_item_text' is the one which makes this virtual list control so easy to use. The method is called only when an item is to be displayed. It is given the index of the item to display, and the column number. Your method just has to return the string for this item and column: as our data is stored as an array of structs, the implementation is as simple as can be.
# -- show data in a ListCtrl

require 'wx'

class LetterDataList < Wx::ListCtrl
def initialize parent
super(parent, :style => Wx::LC_REPORT | Wx::LC_VIRTUAL)
return if @@data.nil? or @@data.empty?

# Directly retrieve field names in Struct to populate table with columns
@@data[0].members.each_with_index do |name, index|
insert_column(index, name.to_s)
end

set_item_count @@data.size
end

# use array like indexing of fields in Struct to
# directly retrieve data for display
def on_get_item_text(item, column)
@@data[item][column]
end
end

class LetterDisplay < Wx::Frame
def initialize
super(nil, :title => "Letter Recognition Data")

LetterDataList.new self
end
end

Wx::App.run do
LetterDisplay.new.show
end


A screenshot of the result:


Handling Events


The list control supports a number of events. The most useful are for catching double clicks on a row or change in the highlighted row. Double clicks are known as activating the item, and single clicks as selecting the item. The event evt_list_item_activated captures a double click, and the event evt_list_item_selected captures a change in the focussed item (through a single click or use of arrow keys). Both events are easy to capture and redirect to a method to handle them. The following code will just print out the item(s) now selected by walking through the list of selected indices obtained by calling selections:

class LetterDataList < Wx::ListCtrl
def initialize parent
super(parent, :style => Wx::LC_REPORT | Wx::LC_VIRTUAL)
return if @@data.nil? or @@data.empty?

# Directly retrieve field names in Struct to populate table with columns
@@data[0].members.each_with_index do |name, index|
insert_column(index, name.to_s)
end

evt_list_item_selected(self) { selected_item }

set_item_count @@data.size
end

# use array like indexing of fields in Struct to
# directly retrieve data for display
def on_get_item_text(item, column)
@@data[item][column]
end

def selected_item
get_selections.each do |selection|
puts @@data[selection]
end
end

Using jRuby from Netbeans

Netbeans is a great development platform which supports Ruby and jRuby. In this post, I have collected together the steps I needed to write and run a jRuby script with a library file, such as Weka or my own jChrest.

First, you must download and install the Netbeans platform. ( I started by downloading the version for Java SE alone.)


Second, you must add the plugin to use Ruby programs. To do this start up Netbeans, and select the 'plugins' option on the 'Tools' menu. Under the tab for 'Available plugins', select the 'Ruby' plugin and install it. (You may not need this step, if you downloaded a version with Ruby included in the previous step.)

Third, download and install jRuby. You need to download and unpack one of the 'binary' distributions. Take note of the 'bin' directory, as you need to know this to tell Netbeans about your jRuby distribution.

Fourth, start up Netbeans and create a new ruby project.

After clicking 'next' you need to specify the Ruby platform you want to use.

The first time we do this, we must tell Netbeans about our new jRuby installation, so click on 'Manage...' and select the 'jruby' executable in the 'bin' directory you installed in step 3. Once you've done this, Netbeans will remember your jruby installation for future projects.

Now, you can create some ruby code and click on 'Run Main Project' to see the output in the 'Output window'. Two things may need changing. First, to set a project to be the 'Main' project, the one which is run when you click the green triangle, you need to right click on the name of the project and select 'Set as Main Project'. Second, you may need to change the ruby platform or version. Again, right click on the name of the project and select 'Properties'. Click on the 'Run' option on the left hand side and you can change the Ruby Platform in the choice box, and add options to the interpreter. In the example below, you can see I have made jRuby use 1.9 mode:

Accessing Java Libraries

The final step in setting up the environment is to include a Java library within the project. For example, to work with Weka, you need to access the file weka.jar. To do this within Netbeans, go to the 'Java' option in the Properties dialog box shown above. Select 'Add JAR/Folder' and add the jar file to the classpath for this project. That's all there is to it. The only change I needed to make to my scripts was to remove the line 'require "weka"'; this is taken care of by adding the jar file to the project.