Resolver Hacks contains information and code for doing fun and useful things with Resolver One. This site is created and maintained by Michael Foord, not by Resolver Systems. Please read the disclaimer before using any of the code on this site.
Charting with ZedGraph: A Basic Introduction

Introduction
ZedGraph is a powerful and free charting solution for .NET. It can produce a great range of attractive graphs and charts, which can easily be integrated into Resolver through 'ImageWorksheets'.
ImageWorksheets display an image instead of a grid. At some point (soon) Resolver will support images in the grid, but personally I like ImageWorksheets.
This article shows you how to use some of the basic features of ZedGraph with Resolver, for producing a few different types of graphs. The disadvantage of this approach is that it uses the ZedGraph objects directly - you have to take care of all the details yourself. As I experiment with ZedGraph, I will probably be able to produce a much 'higher level' interface that is easy to use.
The advantage of this approach, is that you have complete control over the graphs you create and aren't restricted in any way. For the moment, this article is most useful to those familiar with tinkering in IronPython - or willing to give it a try.
Getting Started
In order to use ZedGraph you will need the zedgraph.dll. It can be downloaded from the sourceforge project.
You should place the dll in the bin folder in your Resolver installation [1].
To get the best from ZedGraph you will also want the documentation, which is a separate download.
ZedGraph provides a Windows Forms control for use in desktop applications and a Web control for ASP applications. We will actually be using it to directly produce images, as described in this article.
ZedGraph can produce quite a range of different graphs, and we will only be demonstrating a couple of them here. To see more of the possibilities, go to the Sample Graphs page, which has screenshots and example code.
Simple Example - Plotting a Sine Curve

This first example plots a sine wave curve, as a line graph with diamond symbols. This example is adapted from the Codeproject Tutorial.
The graph is drawn into a GraphPane, which must first be initialised [2]:
title = "Graph Title"
xAxisTitle = "X Title"
yAxisTitle = "Y Title"
pane = GraphPane(RectangleF(0, 0, 480, 320), title, xAxisTitle, yAxisTitle)
The data to be graphed is placed in a PointPairList: a collection class containing the set of points to be displayed on the curve. In this example we plot a simple curve by passing the numbers 0 to 63 (divided by 10) [3] to the System.Math.Sin function. For each value, the (x, y) co-ordinates are added to our PointPairList:
ppl = PointPairList()
# A sine-wave with 64 data points
for x in range(64):
x = float(x)
y = Math.Sin(x / 10.0)
ppl.Add(x, y)
The final steps are to create the graph, extract the image and place it in an ImageWorksheet. :
pane.AddCurve("Sine Wave", ppl, Color.Blue, SymbolType.Diamond)
# A hack because the axis change needs a real
# image if we aren't using a control
bm = Bitmap( 1, 1 )
g = Graphics.FromImage(bm)
pane.AxisChange(g)
# Create the ImageWorksheet
imageSheetName = "Sine Wave"
workbook.AddImageWorksheet(imageSheetName, pane.GetImage())
- The created is created by calling pane.AddCurve
- SymbolType.Diamond is used to specify the markers that appear on the curve.
- pane.AxisChange is called because we are auto-scaling the axes and they must be updated [4].
Multi-Colored Bar Chart

The last chart used a formula to create the curve. In this example we use data from a CellRange and create a bar chart. The bar chart is multicolored, using a gradient fill to make it attractive. This example is adapted from the ZedGraph Multi-Colored Bar Demo.
In the Pre-constants user code, a CellRange is created and formatted.

The CellRange here is populated with bar numbers from 1 to 18, each with a randomly assigned value of 0 to a thousand (this time using the random function from the Python standard library random module:
from random import random
for y in range(2, chartData.MaxRow+1):
chartData['Bar', y] = y -1
chartData['Value', y] = random() * 1000
As the CellRange has a header row (the top one), it can be indexed with the column names rather than index numbers, which makes the code a bit nicer to read. (The call to range starts at 2 to skip out the header row.)
The GraphPane is created in the same way as last time, but this time we have three co-ordinates to include in each member of the PointPairList (rather than two which we had last time):
colorStep = 4.0 / (chartData.MaxRow-1)
for i in range(2, chartData.MaxRow+1):
x = int(chartData['Bar', i])
y = chartData['Value', i]
z = x * colorStep
ppl.Add(x, y, z)
To specify the 'point' in the gradient of each bar we give it a 'Z co-ordinate' which is a number from 0.0 to 4.0. We divide 4.0 by the number of rows of data (which is the number of rows in the CellRange - 1, because the first row is the header row). The Z co-ordinate is then the bar number multiplied by the step value.
Next we define the color array we will use for the gradient fill and a pale blue color that is used for a background gradient file on the chart:
colorList = [Color.Red, Color.Yellow, Color.Green, Color.Blue, Color.Purple]
colors = Array[Color](colorList)
Instead of calling 'AddCurve' to create the graph, we call AddBar:
curve = pane.AddBar("Multi-Colored Bars", ppl, Color.Blue)
curve.Bar.Fill = Fill(colors)
curve.Bar.Fill.Type = FillType.GradientByZ
curve.Bar.Fill.RangeMin = 0
curve.Bar.Fill.RangeMax = 4
- Fill creates the gradient fill
- FillType.GradientByZ specifies that it is a gradient on the Z-co-ordinate
- Setting RangeMin and RangeMax specifies the gradient range (which is why we made the Z co-ordinate values between 0 and 4 earlier).
We also create two more fills, to give the chart background a nice gradient from white to pale blue:
pane.Chart.Fill = Fill(Color.White, paleBlue, 45)
pane.Fill = Fill(Color.White, paleBlue, 45)
Now that the hard work is done, the rest of the code is identical to the last example.
Pie Chart

This example is adapted from the ZedGraph Pie Chart Demo. It starts by creating a CellRange very similar to the last one. Instead of numbers in the first column it has countries:

In this example we create the GraphPane slightly differently. Instead of passing in a rectangle and titles, we construct the GraphPane without arguments and configure it as a separate step:
title = "Resolver Sales by Region\n$ Millions"
pane = GraphPane()
pane.Title.Text = title
pane.Title.FontSpec.IsItalic = True
pane.Title.FontSpec.Size = 24.0
pane.Title.FontSpec.Family = "Times New Roman"
pane.Rect = RectangleF(0, 0, 640, 480)
We also configure where the 'Legend' (the colour key) appears:
pane.Legend.Position = LegendPos.Float
pane.Legend.Location = Location(0.95, 0.15, CoordType.PaneFraction,
AlignH.Right, AlignV.Top)
pane.Legend.FontSpec.Size = 10
pane.Legend.IsHStack = False
The important step though is creating the 'Pie slices':
total = 0
for i in range(chartData.MaxRow-1):
offset = 0
label = chartData['Location', i + 2].Value
sales = chartData['Sales $M', i + 2].Value
if sales < 150:
# offset some of the slices
offset = 0.2
total += sales
# Create the pie slices
segment = pane.AddPieSlice(20, GetRandomColor(), Color.White, 45, offset, label)
segment.LabelDetail.FontSpec.FontColor = GetRandomColor()
The value and the region name are pulled out of the CellRange. The slice is actually created with a call to pane.AddPieSlice. The slice and label colour are set randomly and values below 200 are offset a bit. The total value is accumulated to use in the label.
A text label, highlighting the total value and with a gradient fill, is constructed:
text = TextObj("Total World Sales\n$%sM" % total,
0.18, 0.40, CoordType.PaneFraction)
text.Location.AlignH = AlignH.Center
text.Location.AlignV = AlignV.Bottom
text.FontSpec.Border.IsVisible = False
text.FontSpec.Fill = Fill(Color.White, Color.FromArgb(255, 100, 100), 45.0)
text.FontSpec.StringAlignment = StringAlignment.Center
pane.GraphObjList.Add(text)
The text label is a TextObj. Another TextObj (slightly offset) is created to create a drop shadow for the label:
text2 = TextObj(text)
text2.FontSpec.Fill = Fill(Color.Black)
text2.Location.X += 0.008
text2.Location.Y += 0.01
pane.GraphObjList.Add(text2)
Saving the Chart Image
This spreadsheet has an added bonus, a button to save the chart image:

It uses the Windows Forms SaveFileDialog.
def SaveChart():
try:
import clr
clr.AddReference('System.Windows.Forms')
from System.Windows.Forms import DialogResult, SaveFileDialog
dialog = SaveFileDialog()
dialog.Title = 'Save Chart as Jpg Image'
dialog.Filter = 'Jpg Image (*.jpg)|*.JPG;|All files (*.*)|*.*'
if dialog.ShowDialog() == DialogResult.OK:
image.Save(dialog.FileName, ImageFormat.Jpeg)
except Exception, e:
print '%s: %s' % e.__class__.__name__, e
b.Click += SaveChart
sheet.E5 = b
This isn't a hack, we don't do anything 'clever' like triggering a recalc so it is a perfectly valid use of the button. The ImageWorksheet is always constructed with an Image object, so this approach can be used with any ZedGraph chart.
| [1] | Usually this directory will be located at C:\Program Files\Resolver\bin. |
| [2] | I haven't shown all the import code (which includes adding a reference to the ZedGraph asembly). It is included in the spreadsheets for download, and is of course needed if you want to use ZedGraph in your own spreadsheets. |
| [3] | Don't forget that the range function produces a list where the last values is one less than the stop value you give it. |
| [4] | Because we are using a GraphPane directly we have to provide an image to AxisChange. |
Last edited Mon Dec 10 17:08:18 2007.

IronPython in Action