Back when I was a structural engineer in the 80s and 90s, we had a mantra:
"If it can't be drawn, it can't be built".
As a result we were taught to sketch all our ideas out on paper until we could demonstrate an idea's viability.
Now that i'm older (and debatebly wiser) and am no longer a practising structural engineer, I nevertheless continue to sketch my ideas because I've found that old mantra is right on the nose, although now i'm a software engineer I would probably say:
"If I can't draw it, it's probably because I don't fully understand it".
What prompted this article was one of those fortunate moments when you call on a collegue to help you out in a given situation (as fraught with danger as asking a collegue for help is) and you actually learn something useful.
In this case I was struggling to untangle some logic that relied on 3 or 4 relatively unrelated variables in order to implement some specific business rules for my current employer.
So I called in a collegue and asked him to let me explain the logic to him so that he might help me untangle it.
To assist me I drew a picture, and then another, and then another, before finally giving up and bringing up the relevant source code (what a cop out!).
This at least allowed me to explain the complexity of the problem, and it was at that point that Ed (my collegue) said, "This needs a state diagram".
To which I said, "Sounds good to me. Off you go then".
So as I explained, Ed drew the state diagram and within 10 minutes we had it all sorted out and found another logical bug to boot.
Then I said, "That's the fundamental problem with high level logic that relies for its input on the result of source code residing in a variety of different source files, its just about impossible to comment (or something equally succinct!).
"What would be great would be a way of putting diagrams like this in the code".
And then it all fell into place.
ASCII art (if that's what it can be called) has been around for ever and a day, and whilst it has been developed into UML diagrams I was unable to find freeware Windows tools that would maintain such diagrams in an easy and quick manner.
CodePlotter is a graphical tool for creating and maintaining generic 'box-connection' diagrams in your code.
By 'generic' I mean that CodePlotter can be used to express a range of organisational structures, such as:
- parent-child relationships
- class library hierarchies
- Flow diagrams
- state diagrams
The boxes can be labelled and the diagrams can also have a multi-line title.
However, CodePlotter is definitely not a UML modelling tool (although it can be used to express similar diagrams).
If you think of it as such then you may be disappointed by this initial implementation.
Then again you might not, but instead engage with me on a merry rollercoaster ride of new features and bug fixes.
Installing CodePlotter is as easy as...
- drop the dll into your "...\program files\microsoft visual studio\common\msdev98\addins" folder
- restart Visual Studio
- Enable CodePlotter from the 'Tools|Customize...|Macros and Addins' tab
The following simple tutorial will create you two linked boxes, after which it's really just more of the same.
- Open a text file
- Select an appropriate place to insert a diagram
- Click the CodePlotter toolbar button to display the main editing dialog
- Click the 'New Rect' button in the lower left to create the first box
- Type in some text on the far right to identify it [optional]
- Repeat for the second box
- Select the box (using the mouse) from which the connector should start
- Click the 'New Link' button to display the 'New Relationship' dialog
- Select the side from which the connector should emerge
- Select the target box from the drop-down list (you should only have one box in the list)
- Click 'Ok'
- Finally Click 'Done' in the main editing dialog and the diagram should get inserted into your text file
- To edit the diagram, simply position the cursor/caret on any of the diagram lines and click the CodePlotter toolbar button
Then simply drag the boxes around to see the connections zig and zag as new paths are generated.
Two things to be aware of:
- CodePlotter will disable the 'Done' button if it determines that it would not be able to correctly parse the resulting diagram.
In this case all you need to do is adjust the positions of the boxes until the button becomes enabled.
- CodePlotter can very occasionally end up mightily confused (!) when trying to figure out the best path from one box to another.
This is handled by catching paths exceeding a maximum number of segments at which point the path will be abandoned. When/If this happens, CodePlotter restores the last 'good' diagram and prompts you to try again.
As diagrams get more and more connections, you may begin to notice a lag as each editing change it made. So please don't keep reminding me of this unless its a small diagram and its still really slow.
CodePlotter comprises 3 main elements:
TextDiagram engine that does all the hard stuff such as figuring out the path from one box to another
CWnd derived control which renders the text diagram in an easier to read form than its ASCII equivalent and which provides (rudimentary) user interface manipulation
CodePlotter dialog which provides higher level logic
Of these only the first really warrants close attention since the other two are either not fully implemented or fairly trivial in their complexity.
The TextDiagram Engine
This is a fairly grand title for the component that has the following basic functions:
- Parsing a block of input text to generate a logical model of the diagram comprising labelled boxes and their connections
- Rechecking the boxes and rebuilding connection paths whenever an addition, modification or deletion is made
- Rebuilding the ASCII diagram when editing is complete.
The last of these was the most straightforward since, given a valid model, it is entirely deterministic. The only slightly tricky bit was ensuring that the output format precisely matched the required input format.
The parsing of the model was also fairly simple (in hindsight) once I realised that supporting multiple box styles with only subtle differences (rect, roundrect and round) made it extremely difficult to figure out with any great degree of certainty a given arrangement of characters.
ie. once I reduced the problem domain to a single box style with connectors having only simple start and end characters, things got alot simpler.
The connection path finding, however, was definitely both the most fun and the most painful part of the design (and its not over yet either).
Indeed, until Ed suggested that I require the user to specify the side of the source box from which a given connection was to start, I struggled to figure out what approach to take. (ps. I know that path finding is a well worn problem domain, but most of the fun of these articles is doing it yourself so that you know exactly how it is behaving and why)
Once the start side is specified, I first thought out a simple set of rules that might be used to get from A to B, taking into account that the initial direction might be in one of four possible directions relative to the target.
These are the following rules I came up with:
- The first move must be orthoganal to the side we start from and away from the source
- The second move must test to see if we are pointing in the opposite direction from the target
- If we are then we end the current segment (so we can change direction) else we continue on
- Continue incrementing a given segment until either we hit something (the target box, another box or a connection running in the same direction) or until we intersect with the target verticially or horizontally.
- A new segment must always be in an orthogonal direction to the prior segment
- The direction of the new segment is chosen to match the current vertical or horizontal relationship between the source and target boxes ie. we continually try to inch our way towards the target.
- If we hit something we attempt to backtrack to the previous segment and move on, else if we can't we move back one space and change direction
And that, with a certain degree of tweaking, is about it.
When I started this project I approached it from the perspective that all I had to do was find the magical set of rules that covered all eventualities and it would be done.
And, in a sense I still believe that.
However, what I definitely was not expecting was to see the simple set of rules outlined above actually fix a problem that I had not anticipated, and I'm still not exactly clear how it occurred even after debugging it.
What I observed was the path doubling back on itself to find an alternative route after it hit a dead-end. The surprising thing was not so much that it doubled back, given I had specifically added backtracking as a means of achieving this goal; what was surprising was that it seemed to do so via some other means - definitely creepy!
- Standalone exe version for users of .NET, C++ Builder, Delphi, etc
- Make it faster - the portion of the MFC class hierarchy I modelled in the screenshot was way too slow (a side effect of re-analysing all the connection paths when any modification is made)
- Better path finding when starting from the side opposite to the target box (the current implementation produces some very counter-intuitive solutions)
- Diamond shaped boxes
The code is supplied here for you to use and abuse without restriction, except that you may not modify it and pass it off as your own.
- 1.0 Initial Release
- 1.1 Bug Fix relating to creating diagrams in empty pages (thanks to ReorX)
- Crash Fix relating to deleting newly created boxes (thanks to sunil_g7)
- drag and drop added to allow easy placement and resizing of boxes
- use of cursor keys for simple box placement
- double click connections for quick editing
- project and dll renamed to 'CodePlotterAddin'
- In-place editing of box text (thanks to =[ Abin ]=)
- Improved path finding (thanks to to_be_unknown)
- fixed bug relating to boxes resizing when moved (thanks to to_be_unknown)
- improved diagram verification (thanks to sunil_g7)
- context menus added for all non-drag'n'drop editing
- Enter/Escape keys can be used when editing box text
- reworked UI
- resizeable (and remembered between sessions) (thanks to Anonymous)
- 'Flip Connecton' added to context menu to reverse a connection's direction (thanks to Ralph Wetzel)
- press <F2> to edit the selected box's text (thanks to Anonymous)
- Alternative comment styles for coder's wishing to working VB, NSBasic or C (thanks to Adrian Nicolaiev)
- 'Copy' button bug fixed (thanks to Robert Etheridge)
- 'Resize box to fit' command removed for now (thanks to Robert Etheridge)
- '*' and '#' replaced by arrows at both the start and end of a connection (suggested by Robert Etheridge)
- user can define visual page width for guidance on line length (automatically takes any preceding comment length into account) (suggested by Robert Etheridge)