.NET

Graphr: Additional Information

By Gigi and Saar Sayfan, May 06, 2011

A walk through of Graphr's architecture

Note: This article is an addendum to a lengthy article on Graphr, posted in the June digital issue of Dr. Dobb's Journal . That article should be consulted to understand the topics discussed here. That article also presents the location of the complete code for this application. — Ed.]

Managing the active graph

The active graph needs to be populated when the data is changed, and it needs to be updated when the canvas is resized or when the graph properties are modified. The GraphManager forwards the heavy lifting to the current graph helper.

The Update() method is called when the expression or data range have changed. It computes the data using the IronPython evaluator and then populates the canvas:

When a graph property is changed in an editor, the corresponding event handler extracts the value, updates the config dictionary, and calls the Update() method. Here is the _onColorChnaged() event handler. It takes the string out of the editor TextBox (the sender), and it tries to convert every two characters to a hex number (base 16). There should be 4 such numbers that correspond to alpha (transparency) value, red, green, and blue components (each should be 0..FF):

Note that it is OK for exceptions to occur here if the user is in the middle of modifying the value. The intermediate invalid value will just be ignored. If the user never manages to set a valid value, the last valid value will continue to be used.

The MainWindow

Finally, we get to the MainWindow. This class is responsible for all the general-purpose UI that is not specific to any graph type. In particular, it is responsible for the graph selector dropdown box, the expression and data range text boxes. When any of them change, it notifies the GraphManager, which takes it from there, dynamically populating the graph selector and the initial expression.

The graph selector dropdown needs to contain the names of all the graph plugins. Thanks to MEF, this is trivial. The GraphManager's Helpers property contains (in the Metadata.Name) all the information. In the constructor, the MainWindow iterates over the Helpers and populates the graph selector. It then selects the first graph and calls the GraphManager's SwitchGraphHelper() method. Finally, it sets an initial expression, so you the user can start playing right away.

Interacting with the GraphManager

The MainWindow interacts with the GraphManager in the following cases:

The visualize button is clicked

The size or layout of the MainWindow changes

The user selects a new graph type

When the visualize button is clicked it means the expression and/or the data has changed. This results in a call to the graph manager's Update(). You may ask why expression/range changes should require an explicit button click, while changes to the graph properties are reflected automatically. The answer is that it avoids a lot of intermediate results while the user is still working on the expression or the range. It is a design choice that may change in the future.

Anatomy of a Graphr plugin

You have seen the Graphr side of things. Now, it's time to look at the plugins themselves. The plugins should implement the IGraph plugin (of course) and they should respond properly for data and graph property changes that will be communicated by calls to their PopulateCanvas() method. Let's take a detailed look at the BarGraph plugin, which does a good job.

The BarGraph class is annotated with [Export] and [ExportMetadata] attributes. They enable MEF discovery. The class also implements the IGraph interface:

The config dictionary contains values of type 'object.' The PopulateCanvas() method knows that StrokeThickness is a number that can be converted to a double, but there is no direct way to convert it. By assigning it to a C# 4.0 dynamic variable, it can be assigned directly to r.StrokeThickness and the conversion takes place automatically. These two lines replaced the following ugly piece of code:

The next interesting method is DoGraphLayout(). This is where all the rectangles are placed properly on the canvas. The BarGraph fits all the bars exactly in the canvas (with some padding). The space between bars is 20% of each bar's width. Note the nice SQL-like LINQ queries to find the minimum and maximum heights:

An interesting WPF nugget in this method is how the bars are positioned in the canvas. A rectangle has a Width and Height property, but it doesn't have a Left, Top, or Bottom property. Instead, you position it by calling the SetValue() method and passing Canvas.LeftProperty and Canvas.BottomProperty, which are attached properties. This odd design is actually very smart and allows setting various properties on objects that apply to their relation with their container. Check out http://msdn.microsoft.com/en-us/library/ms749011.aspx to learn more on attached properties.

Finally, the BarGraph provides the ConfigSpec property. This is a pretty simple dictionary that contains the BarGraph-specific graph properties:

Taking Graphr to the next level

There are a few natural directions to evolve Graphr. These include:

More General Services: It would be very useful to add axis, grid, labels, and legend as well scrolling and zooming. Each plugin may implement all these capabilities itself, but it would be a lot of work and will have to be repeated for each plugin. Coming up with some centralized infrastructure will keep plugins relatively simple and yet allow them to benefit from it.

Type-specific graph properties UI: The current graph properties UI is just a text box for each property. It would be much nicer to be able pick color using a cool color picker and select values and ranges using sliders, spin controls, etc.

Load existing data sets: Currently, Graphr only supports data that can be defined with equations of one variable. Adding the capability to load arbitrary data sets will make it applicable in many real-world domains.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!