This screencast will give you a peek at where I am headed with these blog posts: the use of IronPython and Visio to create (nice-looking) Infographics

The previous posts (1, 2) were about getting started with the Python Tools for Visual Studio. Now we will do something productive: draw something useful.

WATCH THE SCREENCAST

 

 

TECHNICAL DISCUSSION

There are three pieces of code involved.

ironvisio.py – to hold the code I always need when talking to visio

import clr 
import System 
clr.AddReference("Microsoft.Office.Interop.Visio") 
import Microsoft.Office.Interop.Visio 
IVisio = Microsoft.Office.Interop.Visio

charting.py – this will draw some very basic charts. At a later point I am going to move the charting code to a C# project.  At that point it will be a good demonstration of how you can reuse an existing .NET assembly.

import math

class DataPoint :

    def __init__( self, value, label = None ) : 
        self.Value = value
        
        if (label==None) : 
            self.Label = str(value)
        else: 
            self.Label = label

class Size:

    def __init__( self, w, h) : 
        self.Width = w
        self.Height = h

class Point:

    def __init__( self, x, y) : 
        self.X = x
        self.Y = y

class VerticalBarChart :

    def __init__( self) : 
        self.DataPoints = []
        self.Categories = []
        self.MaxHeight = 3.0
        self.BarWidth=1.0
        self.BarDistance=0.5
        self.CategoryHeight = 0.5
        self.CategoryDistance=0.0
        self.Origin = Point(0,0)

    def Draw(self, page) :
        startx = self.Origin.X 
        starty = self.Origin.Y 

        numpoints = len(self.DataPoints)
        indices = xrange(numpoints )
        skip = self.BarWidth+self.BarDistance
        lefts = [ startx + i*skip for i in indices]
        heights = normalize_to( (p.Value for p in self.DataPoints), self.MaxHeight)
        bottom = starty + self.CategoryHeight + self.CategoryDistance
        barrects = [ (left,bottom,left+self.BarWidth,bottom+height) for (left,height) in zip(lefts,heights) ]
        catrects = [ (left,starty,left+self.BarWidth,starty+self.CategoryHeight) for left in lefts ]

        # draw bars
        barshapes = [ page.DrawRectangle(*r) for r in barrects ]

        # set bar text
        for (shape,p) in zip(barshapes,self.DataPoints) :
            shape.Text = p.Label

        # draw category textboxes
        catshapes = [ page.DrawRectangle(*r) for r in catrects ]

        # set category text
        for (shape,cattext) in zip(catshapes,self.Categories) :
            shape.Text = cattext


class CircleChart :

    def __init__( self) : 
        self.DataPoints = []
        self.Categories = []
        self.MaxRadius= 0.5
        self.CircleDistance=0.5
        self.CategoryHeight = 0.5
        self.CategoryDistance=0.0
        self.Origin = Point(0,0)

    def Draw(self, page) :
        startx = self.Origin.X 
        starty = self.Origin.Y 

        numpoints = len(self.DataPoints)
        indices = xrange(numpoints )
        maxv = max( ( p.Value for p in self.DataPoints) )
        skip = 2*self.MaxRadius + self.CircleDistance
        centerxs= [ startx + i*skip for i in indices]

                
        normalized_values = normalize( (p.Value for p in self.DataPoints) )
        radii = [ math.sqrt(v/math.pi) for v in normalized_values]
        radii = normalize_to( radii, self.MaxRadius )

        bottom = starty 
        centery = bottom + self.MaxRadius + self.CategoryHeight + self.CategoryDistance
        circlerects = [ (centerx-r,centery-r,centerx+r,centery+r) for (centerx,r) in zip(centerxs,radii) ]
        catrects = [ (centerx-self.MaxRadius,bottom,centerx+self.MaxRadius,bottom + self.CategoryHeight) for centerx in centerxs ]

        # draw circle
        circleshapes = [ page.DrawOval(*r) for r in circlerects]

        # set circle text
        for (shape,p) in zip(circleshapes,self.DataPoints) :
            shape.Text = p.Label

        # draw category textboxes
        catshapes = [ page.DrawRectangle(*r) for r in catrects ]

        # set category text
        for (shape,cattext) in zip(catshapes,self.Categories) :
            shape.Text = cattext


def normalize( seq ) :
    items = [v for v in seq]
    m = max( items )
    return [ float(v)/m for v in items ]

def normalize_to( seq , s) :
    items = [v for v in seq]
    m = max( items )
    return [ float(v)/m*s for v in items ]

 

demo.py – the “main” code that drives the demo

from ironvisio import *
import charting

app = IVisio.ApplicationClass()
docs = app.Documents
doc = docs.Add("")
page = app.ActivePage

values = [5,2,3,7,4]
category_labels = ["A", "B", "C", "D", "E"]

chart1= charting.VerticalBarChart()
chart1.DataPoints = [ charting.DataPoint(v) for v in values ]
chart1.Categories = category_labels 
chart1.Origin = charting.Point(0.5,0)

chart2= charting.CircleChart()
chart2.DataPoints = [ charting.DataPoint(v) for v in values ]
chart2.Categories = category_labels 
chart2.Origin = charting.Point(0.5,4)


chart1.Draw(page)
chart2.Draw(page)

 

As you can see in the screencast, all the demo does is draw two rather plain charts:

image

With a minute of formatting in Visio – as demonstrated in the screencast - we end up with this:

image

 

PYTHON TOOLS FOR VISUAL STUDIO

In the last blog post, I used the interactive shell, in this one I went back to making a project with three source code files. If you haven’t used the Python Tools for VS before, you should at least know how to tell VS which file to run when the project is executed.

Right-click on the project and select Properties

image

 

And in the General tab, set the Startup file to demo.py (in my example) and make sure that Interpeter is set to IronPython

 

image

 

SOURCE CODE

You can download the project here