Following on from my previous post, I thought I'd move on and look at some interesting aspects of the Congress shapes from a Visio construction perspective.
I think you can look at the building of these shapes as consisting of two stages.
- General layout of the seat shapes
- The application of logic to drive the shape's behavior
General Layout
The first part really requires code and although I'm going to focus this post on the ShapeSheet, broadly, I started off by laying out the shapes in bands. Each band carries a set number of shapes and from this you can calculate an equal increment in degrees and then repeat the process with an incremented radius for each band.
Once the shapes are on the page you can group the items and this takes care of setting PinX/Y cells with relative formulas and if you set the LockAspect
cell to true then none of the sub-shapes get distorted.
Logic
The first thing to do now the shapes are down is to ensure that each shape is indexed correctly as this drives seat allocation from left to right. To do this you run through the sub-shapes once more building a list of shape IDs, angle (relative to the center) and distance from the center. This list can then be sorted by angle and distance. Since you have the shape ID you can then run through the list adding a User cell (User.Idx
) with the respective index to each shape. This results in a radial layout that can be populated in a clockwise manner.
Multiple formatting entries
In Visio, allowing the user to be able to set formatting on a single selected shape is straight forward, but as soon as you want to extend this to multiple formatting choices things get a little more complex. For color, one option is to include a range of Shape Data rows that allow the setting of RGB's - this works but, of course, you lose the benefit of the easy UI controls.
To try and work around this I've elected to use the legend items to set formatting, by allowing each legend item to be sub-selected and then carrying the applied formatting through to the group shape. There are three parts to this technique:
- Shape Text
- Fill
- all other character formatting
For the shape text I've mapped the Shape Data row label cells to the text of the legend items and for the fill color I'm using a SETATREF
function to allow both the setting of the legend item FillForegnd cell and, to forward on that value into the group shape's User cell for the respective range.
As far as the Character
section's concerned I've chosen to make any change, applied to a Legend item, to be propagated on to all of the other Legend items. Again, this uses the SETATREF
function and any changes are pushed back to the group shape and are in turn read back by the items. This means that you can apply text formatting to either a single Legend item or, to the group shape and in each case, all of the Legend items will change together. Here's a model of how this works:
...and here's what it looks like in the ShapeSheet where you can see each cell is just mirrored back up to the same cell in the group shape:
Dealing with ranges
The shape supports up to five ranges and the data for each range is set in the Shape Data
section of the group shape Prop.Range1-5
. So any given seat needs to be able to find out in which range it's sitting and then, what the color should be used for that range. The range colors are set as described above with the group shape holding five User cells: User.RangeNColor. So the final part of the puzzle is a single function cell that each seat sub-shape can call:
User.fxGetColor:
IF(ARG("Idx",500)<User.UpTo1,
User.Range1Color,
IF(AND(ARG("Idx",500)<User.UpTo2,Prop.RangeCount>=2),
User.Range2Color,
IF(AND(ARG("Idx",500)<User.UpTo3,Prop.RangeCount>=3),
User.Range3Color,
IF(AND(ARG("Idx",500)<User.UpTo4,Prop.RangeCount>=4),
User.Range4Color,
IF(AND(ARG("Idx",500)<User.UpTo5,Prop.RangeCount>=5),
User.Range5Color,
User.UnallocatedColor)))))
With this function cell in place the seat sub-shape can call it using the EVALCELL
function:
Making changes and sub-shape iteration
As I mentioned earlier, once you start to deal with a shape with more than a few sub-shapes, you will find it much easier to use code to do the work. These days I find LINQPad absolutely invaluable when working with Visio, but VBA is also very useful as it is, of course, fully baked into the product.
To make things a little simpler I also added a User.ShpType
cell to all of the sub-shapes and set the value to either "Seat" or "LegendItem". This means you can simply foreach over all of the sub-shapes targeting the type required for any changes you'd like to make:
C#
void Main()
{
var vApp = MyExtensions.GetRunningVisio();
var grp = vApp.ActiveWindow.Selection.PrimaryItem;
const string ShpTypeCellName = "User.ShpType";
foreach (Visio.Shape s in grp.Shapes)
{
if (s.CellExistsU[ShpTypeCellName, (short)Visio.VisExistsFlags.visExistsAnywhere] != 0)
{
if (s.CellsU[ShpTypeCellName].ResultStr[""] == "Seat") //or "LegendItem"
{
//Do something to this sub-shape
}
}
}
}
VBA
Public Sub LoopCongressSubShapes()
Dim grp As Visio.Shape
Dim s As Visio.Shape
Set grp = ActiveWindow.Selection.PrimaryItem
Const ShpTypeCellName As String = "User.ShpType"
For Each s In grp.Shapes
If s.CellExistsU(ShpTypeCellName, Visio.VisExistsFlags.visExistsAnywhere) <> 0 Then
If s.CellsU(ShpTypeCellName).ResultStr("") = "Seat" Then 'or "LegendItem"
'Do something to this sub-shape
End If
End If
Next
End Sub
Of course, as with many things in Visio, there's no one answer to solving these problems, but I hope that above helps you think about your own Visio shape building design choices.