« Notes on Using Client Applications to Generate Server-Side Graphics | Main | Visio Trick: One-Color and Two-Color Glows »
Monday
Apr142008

Speeding up Visio automation by batching via SetFormulas(), GetFormulas() and VisDOM

If you've read this post by Bill Morein and this one by Mai-lan, you know that using page.SetFormulas() and page.GetFormulas() can really speed things up when drawing via Automation.

In this post I'm going to show a range of techniques to use SetFormulas() and GetFormulas() to achieve this performance win. The code here is on the latest release (3.0.2) of AutoVisioExt on CodePlex.

First, let's use the VisioAutoExt library and some basic code that draws a 15x15 grid of squares and sets them to have a circular fill gradient with different starting and ending transparencies (something that can't be done in the Vision 2007) UI.

The output should look like this:

image

The first attempt without any batching

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IVisio = Microsoft.Office.Interop.Visio;
using VisioAutomation;

namespace VisioAutomationSamples
{

    public partial class Demo
    {

        public static void sample3_a()
        {
            IVisio.Application visapp = new Microsoft.Office.Interop.Visio.ApplicationClass();
            IVisio.Document doc = visapp.Documents.Add(new Isotope.Drawing.Size(10, 10));

            IVisio.Page page = doc.Pages[1];

            int num_rows = 15;
            int num_cols = 15;
            Isotope.Drawing.LayoutGrid grid = new Isotope.Drawing.LayoutGrid( page.GetRect(), num_rows,num_cols,false);

            var cells = grid.EnumCells(num_rows, num_cols);
            var shapes = cells.Select(c => page.DrawRectangle(c.rect)).ToList();

            format_shapes_no_batching(page, shapes);
        }

        public static void format_shapes_no_batching( IVisio.Page page, IEnumerable<IVisio.Shape> shapes)
        {
            // no batching
            foreach (IVisio.Shape shape in shapes)
            {
                shape.SetFillPattern(FillPatternType.RadialCenter);
                shape.SetFillForegroundColor(System.Drawing.Color.Red);
                shape.SetFillForegroundTransparency(0);
                shape.SetFillBackgroundColor(System.Drawing.Color.Black);
                shape.SetFillBackgroundTransparency(50);
            }
        }

    }

}

This example uses the VisioAutoExt library so the code is in general a bit simpler than "pure" Visio automation code. If you run the code, you'll notice the delay as each shape is drawn and then an even greater delay as the fill properties are set on each shape.

The second attempt using SetFormulas() and GetFormulas()

Now let's take the performance advice and use SetFormulas() and GetFormulas(). We'll replace format_shapes_no_batching() with this function format_shapes_batching_1()

public static void format_shapes_batching_1(IVisio.Page page, List<IVisio.Shape> shapes)
{

    List<short> shapesheet_info = new List<short>();

    foreach (IVisio.Shape shape in shapes)
    {
        shapesheet_info.Add((short)shape.ID);
        shapesheet_info.Add((short)IVisio.VisSectionIndices.visSectionObject);
        shapesheet_info.Add((short)IVisio.VisRowIndices.visRowFill);
        shapesheet_info.Add((short)IVisio.VisCellIndices.visFillPattern);
        shapesheet_info.Add((short)shape.ID);
        shapesheet_info.Add((short)IVisio.VisSectionIndices.visSectionObject);
        shapesheet_info.Add((short)IVisio.VisRowIndices.visRowFill);
        shapesheet_info.Add((short)IVisio.VisCellIndices.visFillForegnd);
        shapesheet_info.Add((short)shape.ID);
        shapesheet_info.Add((short)IVisio.VisSectionIndices.visSectionObject);
        shapesheet_info.Add((short)IVisio.VisRowIndices.visRowFill);
        shapesheet_info.Add((short)IVisio.VisCellIndices.visFillForegndTrans);
        shapesheet_info.Add((short)shape.ID);
        shapesheet_info.Add((short)IVisio.VisSectionIndices.visSectionObject);
        shapesheet_info.Add((short)IVisio.VisRowIndices.visRowFill);
        shapesheet_info.Add((short)IVisio.VisCellIndices.visFillBkgnd);
        shapesheet_info.Add((short)shape.ID);
        shapesheet_info.Add((short)IVisio.VisSectionIndices.visSectionObject);
        shapesheet_info.Add((short)IVisio.VisRowIndices.visRowFill);
        shapesheet_info.Add((short)IVisio.VisCellIndices.visFillBkgndTrans);
    }

    System.Array shapesheet_info_array = (System.Array)shapesheet_info.ToArray();

    System.Array formula_array;
    page.GetFormulas(ref shapesheet_info_array, out formula_array);

    int i=0;
    foreach (var shape in shapes)
    {
        formula_array.SetValue("40", i++);
        formula_array.SetValue("rgb(255,0,0)", i++);
        formula_array.SetValue( "0%", i++);
        formula_array.SetValue("rgb(0,0,0)", i++);
        formula_array.SetValue("50%", i++);
    }
    short flags = 0;
    page.SetFormulas(ref shapesheet_info_array, ref formula_array, flags);

}

How wonderfully painful. At least it's really fast compared to the first example.

The third example: Using VisBatchFormulaApplier

We can get much simpler code and all the performance benefits if we use the VisBatchFormulaApplier class found in the VisioAutoExt library.

public static void format_shapes_batching_2(IVisio.Page page, IEnumerable<IVisio.Shape> shapes)
{
    VisioAutomation.VisBatchFormulaApplier batch = new VisBatchFormulaApplier();
    foreach (IVisio.Shape shape in shapes)
    {
        batch.SetFormula( shape, VisProps.FillPattern, (int) FillPatternType.RadialCenter );
        batch.SetFormula( shape, VisProps.FillForegroundColor , System.Drawing.Color.Red );
        batch.SetFormula( shape, VisProps.FillForegroundTransparency, 0, VisUnit.Percent);
        batch.SetFormula( shape, VisProps.FillBackgroundColor, System.Drawing.Color.Black);
        batch.SetFormula( shape, VisProps.FillBackgroundTransparency, 50, VisUnit.Percent);

    }
    batch.ApplyFormulas(page);
}

It isn't quite as readable as the first example, but it's much better then the second example, but it is very fast.

VisBatchFormulaApplier will generally make things much simpler.

Some issues:

  • it will only with shapes on the same page
  • you have to write your automation code to create all the objects first and then work use the applier.
  • you have to remember that some properties require you to specify the unit to work (for example VisUnit.Percent for transparencies)

The fourth example: using VisDOM

And now, we use the new experimental VisDOM support. VisDOM is essentially a small set of classes that will automatically draw shapes and apply them in batch. As an extra benefit, things are much more readable and strongly typed than any of the examples above. Here's the full example:

public static void sample3_b()
{

    VisDOM.Document doc = new VisDOM.Document();
    VisDOM.Page page1 = new VisDOM.Page(10, 10);
    doc.Pages.Add(page1);

    int num_rows = 15;
    int num_cols = 15;
    Isotope.Drawing.LayoutGrid grid = new Isotope.Drawing.LayoutGrid(page1.Rect, num_rows, num_cols, false);

    var cells = grid.EnumCells(num_rows, num_cols);
    var shapes = cells.Select(c => page1.DrawRectangle(c.rect)).ToList();

    foreach (VisDOM.Shape shape in shapes)
    {
        shape.FillPattern.Value = FillPatternType.RadialCenter;
        shape.FillForegroundColor.Value = System.Drawing.Color.Red;
        shape.FillBackgroundColor.Value = System.Drawing.Color.Black;
        shape.FillForegroundTransparency.Value = 0;
        shape.FillBackgroundTransparency.Value = 50;

    }

    IVisio.Application visapp = new Microsoft.Office.Interop.Visio.ApplicationClass();
    VisDOM.VisDOMRenderer.RenderToVisio(doc, visapp,false,false);

}

VisDOM constructs a document in memory and then the VisDOMRenderer.RenderToVisio() method does all the Visio magic for batching.

Notice how the properties like FillForegroundTransparency are strongly typed, will work with Intellisense, and don't require knowledge of the VisUnit codes.

PrintView Printer Friendly Version

EmailEmail Article to Friend

Reader Comments

There are no comments for this journal entry. To create a new comment, use the form below.

PostPost a New Comment

Enter your information below to add a new comment.
Author Email (optional):
Author URL (optional):
Post:
 
Some HTML allowed: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <code> <em> <i> <strike> <strong>