Optimizing the links from the Glamorous Toolkit book for first time readers

Writing isolated pieces of documentation can be entertaining, but sometimes we want to enhance the findability of those pieces. Linking them with other pieces can be a good path to achieve this.

We actually had this very same problem when writing the Glamorous Toolkit book: We wanted to make it possible for people to start at the "Get started" page and have the chance to reach as many pages as possible by following direct links.

To ease the finding of new linking opportunities, we can visualize the situation. We start by collecting all the pages from the book. We leave out the table of contents page because that one links everything.

database := LeDatabasesRegistry default currentLoadedDefaultLogicalDatabase
		databaseNamed: 'Glamorous Toolkit Book'.
pages := database pages
		reject: [ :each | each title = 'Glamorous Toolkit Book' ].
  

We find the Get started page:

getStartedPage := pages detect: [:each | each title = 'Get started'].
  

We collect the pages that are reachable from by traversing deeply all the outgoing links:

allReachedPages := getStartedPage deepCollectAsSet: [:each | 
	each allChildOutgoingTextualLinks collect: [:x | x target ifNotNil: #page] ].
  

Then we take a quick look at the graph:

m := GtMondrian new.
m nodes
	stencil: [ :each | 
		BlShrinkingTextElement new 
			text: each title asRopedText glamorousRegularFont;
			background: (Color white alpha: 0.5);
			constraintsDo: [:c | c horizontal exact: 70. c vertical fitContent ];
			when: BlClickEvent
				do: [ :e | e currentTarget phlow spawnTool: each asPhlowTool ] ];
	with: (allReachedPages).
m edges
	stencil: [ BlLineElement new
			border: Color veryLightGray;
			toHead: (BlArrowheadSimpleArrow new border: Color veryLightGray);
			zIndex: -1 ];
	connectToAll: [ :page | page allOutgoingTextualLinks collectAsSet: #target ].
m layout custom: (GtGraphHorizontalTreeLayout new levelDistance: 20).
m
  
The graph all pages reachable from the "Get started" page

Ok, but a better view would be to see these pages in the context of the entire graph:

m := GtMondrian new.
m nodes
	stencil: [ :each | 
		| color size |
		color := (allReachedPages includes: each)
				ifTrue: [ Color red ]
				ifFalse: [ Color black ].
		size := 5 @ 5.
		each = getStartedPage ifTrue: [ color := Color blue. size := 10 @ 10 ].
		BlElement new background: color; size: size;
			when: BlClickEvent
				do: [ :e | e currentTarget phlow spawnTool: each asPhlowTool ] ];
	with: pages.
m edges
	stencil: [ BlLineElement new
			border: Color veryLightGray;
			toHead: (BlArrowheadSimpleArrow new border: Color veryLightGray);
			zIndex: -1 ];
	connectToAll: [ :page | page allOutgoingTextualLinks collectAsSet: #target ].
m layout force nbIterations: 30; charge: -50.
m
  

And we get this:

A visualization of pages and their interconnections from the Glamorous Toolkit book. The node in blue is the get started page. The nodes in red are all the pages reachable from the get started page.

So, now we have an idea of the parts of the book that are not reachable. But would it not be better to show the pages as they are in the book table of contents instead of as a graph of links? Let's try.

First, we assemble the data structures. Here we traverse the table of contents page itself and get the links from each snippet.

tocPage := database pages
		detect: [ :each | each title = 'Glamorous Toolkit Book' ].
allTocSnippets := tocPage allChildrenBreadthFirst.
  

And then we put it together in a simple visualization of the snippets that uses allReachedPages computed above to highlight the corresponding snippets:

m := GtMondrian new.
m nodes 
	stencil: [ :each | 
		| title color |
		title := (each contentAsString removePrefix: '[[') removeSuffix: ']]'.
		color := Color gray alpha: 0.2.
		(allReachedPages anySatisfy: [ :aPage | aPage title = title ])
				ifTrue: [ color := Color red alpha: 0.4 ].
		title = 'Get started' ifTrue: [ color := Color blue alpha: 0.6 ].
		BlShrinkingTextElement new
			constraintsDo: [:c |c horizontal exact: 300. c vertical fitContent];
			background: color;
			text: title asRopedText glamorousRegularFont;
			when: BlClickEvent do: [:e | e consumed: true. e target phlow spawnObject: (each outgoingExplicitLinks anyOne target)] ];
	with: allTocSnippets.
m edges fromRightCenter; toLeftCenter; connectToAll: #children.
m layout custom: (GtGraphHorizontalTreeLayout new levelDistance: 150).
m
  

This now gives us a view that is closer to the mental model of a book.

The table of contents of the Glamorous Toolkit book with the pages reachable from "Get started" highlighted in red

The tree shows pages. But some pages are longer than others, and it can be useful sometimes to also show the size of pages.

To this end, we can count the words from the text representation of a page:

sizeCounter := [ :aPage | 
	((String cr join: (aPage allChildrenBreadthFirst collect: #contentAsString))
		piecesCutWhere: [ :a :b | a isSeparator ]) size ].
maxSize := pages max: sizeCounter.
  

And then we enhance the visualization:

m := GtMondrian new.
m nodes
	stencil: [ :each | 
		| title color linkedPage |
		linkedPage := each outgoingExplicitLinks anyOne target.
		title := linkedPage title.
		color := Color gray alpha: 0.2.
		(allReachedPages anySatisfy: [ :aPage | aPage title = title ])
			ifTrue: [ color := Color red alpha: 0.4 ].
		title = 'Get started' ifTrue: [ color := Color blue alpha: 0.6 ].
		BlShrinkingTextElement new
			constraintsDo: [ :c | 
				c horizontal exact: (((sizeCounter value: linkedPage) / maxSize * 500) max: 30).
				c vertical fitContent ];
			background: color;
			text: title asRopedText glamorousRegularFont;
			padding: (BlInsets all: 2);
			when: BlClickEvent
				do: [ :e | 
					e consumed: true.
					e target phlow spawnObject: linkedPage ] ];
	with: allTocSnippets.
m edges
	fromRightCenter;
	toLeftCenter;
	connectToAll: #children.
m layout custom: (GtGraphHorizontalTreeLayout new levelDistance: 100; layered).
m
  
The table of contents of the Glamorous Toolkit book with the pages reachable from "Get started" highlighted in red

Of course, in a world of executable snippets, we can find other interesting measurements than words. For example, we can also consider the amount of snippets in a page.

sizeCounter := [ :aPage | aPage allChildrenBreadthFirst size ].
maxSize := pages max: snippetsCounter.
  

We used these visualizations actively while working on the Glamorous Toolkit book. We've done it from inside the environment in which the book is delivered. And you can even find this page as a case study in the book itself.