Skip to content

Commit a4559b4

Browse files
author
Jelle Peters
committed
Add legends to diagrams
1 parent 99244a8 commit a4559b4

17 files changed

+200
-44
lines changed

src/main/kotlin/nl/avisi/structurizr/site/generatr/site/DiagramGenerator.kt

Lines changed: 41 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.structurizr.view.View
88
import net.sourceforge.plantuml.FileFormat
99
import net.sourceforge.plantuml.FileFormatOption
1010
import net.sourceforge.plantuml.SourceStringReader
11+
import nl.avisi.structurizr.site.generatr.site.model.DiagramSvgs
1112
import java.io.ByteArrayOutputStream
1213
import java.io.File
1314
import java.net.URI
@@ -23,11 +24,27 @@ fun generateDiagrams(workspace: Workspace, exportDir: File) {
2324
plantUMLDiagrams.parallelStream()
2425
.forEach { diagram ->
2526
val plantUMLFile = File(pumlDir, "${diagram.key}.puml")
26-
if (!plantUMLFile.exists() || plantUMLFile.readText() != diagram.definition) {
27+
val plantUMLLegendFile = File(pumlDir, "${diagram.key}.legend.puml")
28+
29+
val diagramNeedsUpdating = !plantUMLFile.exists() || plantUMLFile.readText() != diagram.definition
30+
val legendNeedsUpdating = !plantUMLLegendFile.exists() || plantUMLLegendFile.readText() != diagram.legend?.definition
31+
32+
if (diagramNeedsUpdating || legendNeedsUpdating) {
2733
println("${diagram.key}...")
28-
saveAsSvg(diagram, svgDir)
29-
saveAsPng(diagram, pngDir)
30-
saveAsPUML(diagram, plantUMLFile)
34+
if (diagramNeedsUpdating) {
35+
saveAsSvg(diagram.withCachedIncludes().definition, svgDir, diagram.key)
36+
saveAsPng(diagram.withCachedIncludes().definition, pngDir, diagram.key)
37+
saveAsPUML(diagram.definition, plantUMLFile)
38+
}
39+
if (legendNeedsUpdating) {
40+
val diagramLegendDefinition = diagram.legend?.definition
41+
42+
if (diagramLegendDefinition != null) {
43+
saveAsSvg(diagramLegendDefinition, svgDir,diagram.key + ".legend")
44+
saveAsPng(diagramLegendDefinition, pngDir, diagram.key + ".legend")
45+
saveAsPUML(diagramLegendDefinition, plantUMLLegendFile)
46+
}
47+
}
3148
} else {
3249
println("${diagram.key} UP-TO-DATE")
3350
}
@@ -38,18 +55,23 @@ fun generateDiagramWithElementLinks(
3855
workspace: Workspace,
3956
view: View,
4057
url: String,
41-
diagramCache: ConcurrentHashMap<String, String>
42-
): String {
58+
diagramCache: ConcurrentHashMap<String, DiagramSvgs>
59+
): DiagramSvgs {
4360
val diagram = generatePlantUMLDiagramWithElementLinks(workspace, view, url)
4461

4562
val name = "${diagram.key}-${view.key}"
46-
return diagramCache.getOrPut(name) {
47-
val reader = SourceStringReader(diagram.withCachedIncludes().definition)
48-
val stream = ByteArrayOutputStream()
63+
return diagramCache.getOrPut(name) { DiagramSvgs(
64+
convertDefinitionToSvg(diagram.withCachedIncludes().definition),
65+
diagram.legend?.definition?.let { convertDefinitionToSvg(it) }
66+
) }
67+
}
4968

50-
reader.outputImage(stream, FileFormatOption(FileFormat.SVG, false))
51-
stream.toString(Charsets.UTF_8)
52-
}
69+
private fun convertDefinitionToSvg(definition: String): String {
70+
val reader = SourceStringReader(definition)
71+
val stream = ByteArrayOutputStream()
72+
73+
reader.outputImage(stream, FileFormatOption(FileFormat.SVG, false))
74+
return stream.toString(Charsets.UTF_8)
5375
}
5476

5577
private fun generatePlantUMLDiagrams(workspace: Workspace): Collection<Diagram> {
@@ -58,22 +80,22 @@ private fun generatePlantUMLDiagrams(workspace: Workspace): Collection<Diagram>
5880
return plantUMLExporter.export()
5981
}
6082

61-
private fun saveAsPUML(diagram: Diagram, plantUMLFile: File) {
62-
plantUMLFile.writeText(diagram.definition)
83+
private fun saveAsPUML(definition: String, plantUMLFile: File) {
84+
plantUMLFile.writeText(definition)
6385
}
6486

65-
private fun saveAsSvg(diagram: Diagram, svgDir: File, name: String = diagram.key) {
66-
val reader = SourceStringReader(diagram.withCachedIncludes().definition)
87+
private fun saveAsSvg(definition: String, svgDir: File, name: String) {
88+
val reader = SourceStringReader(definition)
6789
val svgFile = File(svgDir, "$name.svg")
6890

6991
svgFile.outputStream().use {
7092
reader.outputImage(it, FileFormatOption(FileFormat.SVG, false))
7193
}
7294
}
7395

74-
private fun saveAsPng(diagram: Diagram, pngDir: File) {
75-
val reader = SourceStringReader(diagram.withCachedIncludes().definition)
76-
val pngFile = File(pngDir, "${diagram.key}.png")
96+
private fun saveAsPng(definition: String, pngDir: File, name: String) {
97+
val reader = SourceStringReader(definition)
98+
val pngFile = File(pngDir, "$name.png")
7799

78100
pngFile.outputStream().use {
79101
reader.outputImage(it)
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package nl.avisi.structurizr.site.generatr.site
22

33
import com.structurizr.Workspace
4+
import nl.avisi.structurizr.site.generatr.site.model.DiagramSvgs
45

56
data class GeneratorContext(
67
val version: String,
78
val workspace: Workspace,
89
val branches: List<String>,
910
val currentBranch: String,
1011
val serving: Boolean,
11-
val svgFactory: (key: String, url: String) -> String?
12+
val svgFactory: (key: String, url: String) -> DiagramSvgs?
1213
)

src/main/kotlin/nl/avisi/structurizr/site/generatr/site/SiteGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ fun generateSite(
7373
serving: Boolean = false
7474
) {
7575
val generatorContext = GeneratorContext(version, workspace, branches, currentBranch, serving) { key, url ->
76-
val diagramCache = ConcurrentHashMap<String, String>()
76+
val diagramCache = ConcurrentHashMap<String, DiagramSvgs>()
7777
workspace.views.views.singleOrNull { view -> view.key == key }
7878
?.let { generateDiagramWithElementLinks(workspace, it, url, diagramCache) }
7979
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package nl.avisi.structurizr.site.generatr.site.model
2+
3+
data class DiagramSvgs(
4+
val svg: String,
5+
val legendSvg: String?,
6+
)

src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/DiagramViewModel.kt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ data class DiagramViewModel(
1010
val diagramWidthInPixels: Int?,
1111
val svgLocation: ImageViewModel,
1212
val pngLocation: ImageViewModel,
13-
val pumlLocation: ImageViewModel
13+
val pumlLocation: ImageViewModel,
14+
val legend: LegendViewModel?,
1415
) {
1516
companion object {
16-
fun forView(pageViewModel: PageViewModel, view: View, svgFactory: (key: String, url: String) -> String?) =
17+
fun forView(pageViewModel: PageViewModel, view: View, svgFactory: (key: String, url: String) -> DiagramSvgs?) =
1718
forView(pageViewModel, view.key, view.name, view.title, view.description.ifBlank { null }, svgFactory)
1819

1920
fun forView(
@@ -22,9 +23,11 @@ data class DiagramViewModel(
2223
name: String,
2324
title: String?,
2425
description: String?,
25-
svgFactory: (key: String, url: String) -> String?
26+
svgFactory: (key: String, url: String) -> DiagramSvgs?
2627
): DiagramViewModel {
27-
val svg = svgFactory(key, pageViewModel.url)
28+
val (svg, legendSvg) = svgFactory(key, pageViewModel.url)?.let {
29+
it.svg to it.legendSvg
30+
} ?: (null to null)
2831
return DiagramViewModel(
2932
key,
3033
title ?: name,
@@ -33,7 +36,14 @@ data class DiagramViewModel(
3336
extractDiagramWidthInPixels(svg),
3437
ImageViewModel(pageViewModel, "/svg/$key.svg"),
3538
ImageViewModel(pageViewModel, "/png/$key.png"),
36-
ImageViewModel(pageViewModel, "/puml/$key.puml")
39+
ImageViewModel(pageViewModel, "/puml/$key.puml"),
40+
legendSvg?.let { LegendViewModel(
41+
legendSvg,
42+
extractDiagramWidthInPixels(legendSvg),
43+
ImageViewModel(pageViewModel, "/svg/$key.legend.svg"),
44+
ImageViewModel(pageViewModel, "/png/$key.legend.png"),
45+
ImageViewModel(pageViewModel, "/puml/$key.legend.puml"),
46+
) }
3747
)
3848
}
3949

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package nl.avisi.structurizr.site.generatr.site.model
2+
3+
data class LegendViewModel(
4+
val svg: String?,
5+
val widthInPixels: Int?,
6+
val svgLocation: ImageViewModel,
7+
val pngLocation: ImageViewModel,
8+
val pumlLocation: ImageViewModel,
9+
)

src/main/kotlin/nl/avisi/structurizr/site/generatr/site/model/ToHtml.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package nl.avisi.structurizr.site.generatr.site.model
22

33
import com.structurizr.documentation.Format
4+
import com.structurizr.export.Diagram
45
import com.vladsch.flexmark.ast.FencedCodeBlock
56
import com.vladsch.flexmark.html.HtmlRenderer
67
import com.vladsch.flexmark.html.HtmlWriter
@@ -29,7 +30,7 @@ fun toHtml(
2930
pageViewModel: PageViewModel,
3031
content: String,
3132
format: Format,
32-
svgFactory: (key: String, url: String) -> String?
33+
svgFactory: (key: String, url: String) -> DiagramSvgs?
3334
): String = when (format) {
3435
Format.Markdown -> markdownToHtml(pageViewModel, content, svgFactory)
3536
Format.AsciiDoc -> asciidocToHtml(pageViewModel, content, svgFactory)
@@ -38,7 +39,7 @@ fun toHtml(
3839
private fun markdownToHtml(
3940
pageViewModel: PageViewModel,
4041
markdown: String,
41-
svgFactory: (key: String, url: String) -> String?
42+
svgFactory: (key: String, url: String) -> DiagramSvgs?
4243
): String {
4344
val flexmarkConfig = pageViewModel.flexmarkConfig
4445
val options = flexmarkConfig.flexmarkOptions
@@ -77,7 +78,7 @@ private class FencedCodeBlockRenderer : NodeRenderer {
7778
private fun asciidocToHtml(
7879
pageViewModel: PageViewModel,
7980
asciidoc: String,
80-
svgFactory: (key: String, url: String) -> String?
81+
svgFactory: (key: String, url: String) -> DiagramSvgs?
8182
): String {
8283
val options = Options.builder()
8384
.safe(SafeMode.SERVER)
@@ -159,7 +160,7 @@ private class CustomLinkResolver(private val pageViewModel: PageViewModel) : Lin
159160

160161
fun Element.transformEmbeddedDiagramElements(
161162
pageViewModel: PageViewModel,
162-
svgFactory: (key: String, url: String) -> String?
163+
svgFactory: (key: String, url: String) -> DiagramSvgs?
163164
) = this.allElements
164165
.toList()
165166
.filter { it.tag().name == "img" && it.attr("src").startsWith(embedPrefix) }

src/main/kotlin/nl/avisi/structurizr/site/generatr/site/views/Diagram.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ fun FlowContent.diagram(viewModel: DiagramViewModel) {
3535
+"|"
3636
a(href = viewModel.pumlLocation.relativeHref, target = "_blank") { +"puml" }
3737
+"]"
38+
if (viewModel.legend != null) {
39+
+" Legend ["
40+
a(href = viewModel.legend.svgLocation.relativeHref, target = "_blank") { +"svg" }
41+
+"|"
42+
a(href = viewModel.legend.pngLocation.relativeHref, target = "_blank") { +"png" }
43+
+"|"
44+
a(href = viewModel.legend.pumlLocation.relativeHref, target = "_blank") { +"puml" }
45+
+"]"
46+
}
3847
}
3948
}
4049
} else

src/test/kotlin/nl/avisi/structurizr/site/generatr/site/StructurizrUtilitiesTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import kotlin.test.Test
88

99
class StructurizrUtilitiesTest {
1010

11-
private val svgFactory = { _: String, _: String -> "" }
11+
private val svgFactory = { _: String, _: String -> "" to "" }
1212

1313
private fun generatorContext(
1414
workspaceName: String = "Workspace name",

src/test/kotlin/nl/avisi/structurizr/site/generatr/site/model/AsciidocToHtmlTest.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,11 @@ class AsciidocToHtmlTest : ViewModelTest() {
211211
<div>
212212
<svg viewBox="0 0 800 900"></svg>
213213
</div>
214+
<div style="width: min(100%, 800px);">
215+
<div>
216+
<svg viewBox="0 0 800 900"></svg>
217+
</div>
218+
</div>
214219
<figcaption>
215220
<a onclick="openSvgModal('SystemLandscape-modal', 'SystemLandscape-svg')">System Landscape Diagram</a>
216221
</figcaption>
@@ -224,6 +229,8 @@ class AsciidocToHtmlTest : ViewModelTest() {
224229
</div>
225230
<div class="has-text-centered">
226231
System Landscape Diagram [<a href="svg/SystemLandscape.svg" target="_blank">svg</a>|<a href="png/SystemLandscape.png" target="_blank">png</a>|<a href="puml/SystemLandscape.puml" target="_blank">puml</a>]
232+
<br>
233+
Legend [<a href="svg/SystemLandscape.legend.svg" target="_blank">svg</a>|<a href="png/SystemLandscape.legend.png" target="_blank">png</a>|<a href="svg/SystemLandscape.legend.svg" target="_blank">puml</a>]
227234
</div>
228235
</div>
229236
</div>

0 commit comments

Comments
 (0)