Skip to content
This repository was archived by the owner on Mar 28, 2026. It is now read-only.

Commit 8d57131

Browse files
Improved support for themes (e.g. when exporting to PlantUML), which now works the same as described at https://structurizr.com/help/themes
1 parent bd48c2a commit 8d57131

11 files changed

Lines changed: 451 additions & 237 deletions

File tree

docs/changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Adds support for adding individual infrastructure nodes, software system instances, and container instances to a deployment view.
66
- Adds support for removing software system instances from deployment views.
7+
- Improved support for themes (e.g. when exporting to PlantUML), which now works the same as described at [Structurizr - Themes](https://structurizr.com/help/themes).
78

89
## 1.8.0 (20th February 2021)
910

structurizr-client/src/com/structurizr/util/ThemeUtils.java renamed to structurizr-client/src/com/structurizr/view/ThemeUtils.java

Lines changed: 3 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
package com.structurizr.util;
1+
package com.structurizr.view;
22

3-
import com.fasterxml.jackson.annotation.JsonGetter;
43
import com.fasterxml.jackson.annotation.JsonInclude;
54
import com.fasterxml.jackson.databind.DeserializationFeature;
65
import com.fasterxml.jackson.databind.ObjectMapper;
76
import com.fasterxml.jackson.databind.SerializationFeature;
87
import com.structurizr.Workspace;
98
import com.structurizr.io.WorkspaceWriterException;
10-
import com.structurizr.view.ElementStyle;
11-
import com.structurizr.view.RelationshipStyle;
129
import org.apache.hc.client5.http.classic.methods.HttpGet;
1310
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
1411
import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse;
@@ -17,8 +14,6 @@
1714

1815
import java.io.*;
1916
import java.nio.charset.StandardCharsets;
20-
import java.util.Collection;
21-
import java.util.LinkedList;
2217

2318
/**
2419
* Some utility methods for exporting themes to JSON.
@@ -70,7 +65,7 @@ public static String toJson(Workspace workspace) throws Exception {
7065
* @param workspace a Workspace object
7166
* @throws Exception if something goes wrong
7267
*/
73-
public static void loadStylesFromThemes(Workspace workspace) throws Exception {
68+
public static void loadThemes(Workspace workspace) throws Exception {
7469
if (workspace.getViews().getConfiguration().getThemes() != null) {
7570
for (String url : workspace.getViews().getConfiguration().getThemes()) {
7671
CloseableHttpClient httpClient = HttpClients.createSystem();
@@ -86,13 +81,7 @@ public static void loadStylesFromThemes(Workspace workspace) throws Exception {
8681

8782
Theme theme = objectMapper.readValue(json, Theme.class);
8883

89-
for (ElementStyle elementStyle : theme.getElements()) {
90-
workspace.getViews().getConfiguration().getStyles().add(elementStyle);
91-
}
92-
93-
for (RelationshipStyle relationshipStyle : theme.getRelationships()) {
94-
workspace.getViews().getConfiguration().getStyles().add(relationshipStyle);
95-
}
84+
workspace.getViews().getConfiguration().getStyles().addStylesFromTheme(url, theme.getElements(), theme.getRelationships());
9685
}
9786

9887
httpClient.close();
@@ -123,57 +112,4 @@ private static void write(Workspace workspace, Writer writer) throws Exception {
123112
writer.close();
124113
}
125114

126-
static class Theme {
127-
128-
private String name;
129-
private String description;
130-
private Collection<ElementStyle> elements = new LinkedList<>();
131-
private Collection<RelationshipStyle> relationships = new LinkedList<>();
132-
133-
Theme() {
134-
}
135-
136-
Theme(String name, String description, Collection<ElementStyle> elements, Collection<RelationshipStyle> relationships) {
137-
this.name = name;
138-
this.description = description;
139-
this.elements = elements;
140-
this.relationships = relationships;
141-
}
142-
143-
public String getName() {
144-
return name;
145-
}
146-
147-
void setName(String name) {
148-
this.name = name;
149-
}
150-
151-
public String getDescription() {
152-
return description;
153-
}
154-
155-
void setDescription(String description) {
156-
this.description = description;
157-
}
158-
159-
@JsonGetter
160-
Collection<ElementStyle> getElements() {
161-
return elements;
162-
}
163-
164-
void setElements(Collection<ElementStyle> elements) {
165-
this.elements = elements;
166-
}
167-
168-
@JsonGetter
169-
Collection<RelationshipStyle> getRelationships() {
170-
return relationships;
171-
}
172-
173-
void setRelationships(Collection<RelationshipStyle> relationships) {
174-
this.relationships = relationships;
175-
}
176-
177-
}
178-
179115
}

structurizr-client/test/unit/com/structurizr/util/ThemeUtilsTests.java

Lines changed: 0 additions & 47 deletions
This file was deleted.
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package com.structurizr.view;
2+
3+
import com.structurizr.Workspace;
4+
import com.structurizr.model.Relationship;
5+
import com.structurizr.model.SoftwareSystem;
6+
import com.structurizr.model.Tags;
7+
import org.junit.Assert;
8+
import org.junit.Test;
9+
10+
import java.util.ArrayList;
11+
import java.util.Collection;
12+
13+
import static org.junit.Assert.*;
14+
15+
public class ThemeUtilsTests {
16+
17+
@Test
18+
public void test_loadThemes_DoesNothingWhenNoThemesAreDefined() throws Exception {
19+
Workspace workspace = new Workspace("Name", "Description");
20+
ThemeUtils.loadThemes(workspace);
21+
22+
// there should still be zero styles in the workspace
23+
assertEquals(0, workspace.getViews().getConfiguration().getStyles().getElements().size());
24+
}
25+
26+
@Test
27+
public void test_loadThemes_LoadsThemesWhenThemesAreDefined() throws Exception {
28+
Workspace workspace = new Workspace("Name", "Description");
29+
SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name");
30+
softwareSystem.addTags("Amazon Web Services - Alexa For Business");
31+
workspace.getViews().getConfiguration().setThemes("https://static.structurizr.com/themes/amazon-web-services-2020.04.30/theme.json");
32+
33+
ThemeUtils.loadThemes(workspace);
34+
35+
// there should still be zero styles in the workspace
36+
assertEquals(0, workspace.getViews().getConfiguration().getStyles().getElements().size());
37+
38+
// but we should be able to find a style included in the theme
39+
ElementStyle style = workspace.getViews().getConfiguration().getStyles().findElementStyle(softwareSystem);
40+
assertNotNull(style);
41+
assertEquals("#d6242d", style.getStroke());
42+
assertEquals("#d6242d", style.getColor());
43+
assertNotNull(style.getIcon());
44+
}
45+
46+
@Test
47+
public void test_toJson() throws Exception {
48+
Workspace workspace = new Workspace("Name", "Description");
49+
assertEquals("{\n" +
50+
" \"name\" : \"Name\",\n" +
51+
" \"description\" : \"Description\"\n" +
52+
"}", ThemeUtils.toJson(workspace));
53+
54+
workspace.getViews().getConfiguration().getStyles().addElementStyle(Tags.ELEMENT).background("#ff0000");
55+
workspace.getViews().getConfiguration().getStyles().addRelationshipStyle(Tags.RELATIONSHIP).color("#ff0000");
56+
assertEquals("{\n" +
57+
" \"name\" : \"Name\",\n" +
58+
" \"description\" : \"Description\",\n" +
59+
" \"elements\" : [ {\n" +
60+
" \"tag\" : \"Element\",\n" +
61+
" \"background\" : \"#ff0000\"\n" +
62+
" } ],\n" +
63+
" \"relationships\" : [ {\n" +
64+
" \"tag\" : \"Relationship\",\n" +
65+
" \"color\" : \"#ff0000\"\n" +
66+
" } ]\n" +
67+
"}", ThemeUtils.toJson(workspace));
68+
}
69+
70+
@Test
71+
public void test_findElementStyle_WithThemes() {
72+
Workspace workspace = new Workspace("Name", "Description");
73+
SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name");
74+
workspace.getViews().getConfiguration().getStyles().addElementStyle("Element").shape(Shape.RoundedBox);
75+
76+
// theme 1
77+
Collection<ElementStyle> elementStyles = new ArrayList<>();
78+
Collection<RelationshipStyle> relationshipStyles = new ArrayList<>();
79+
elementStyles.add(new ElementStyle("Element").shape(Shape.Box).background("#000000").color("#ffffff"));
80+
workspace.getViews().getConfiguration().getStyles().addStylesFromTheme("url1", elementStyles, relationshipStyles);
81+
82+
// theme 2
83+
elementStyles = new ArrayList<>();
84+
relationshipStyles = new ArrayList<>();
85+
elementStyles.add(new ElementStyle("Element").background("#ff0000"));
86+
workspace.getViews().getConfiguration().getStyles().addStylesFromTheme("url2", elementStyles, relationshipStyles);
87+
88+
ElementStyle style = workspace.getViews().getConfiguration().getStyles().findElementStyle(softwareSystem);
89+
assertEquals(new Integer(450), style.getWidth());
90+
assertEquals(new Integer(300), style.getHeight());
91+
assertEquals("#ff0000", style.getBackground()); // from theme 2
92+
assertEquals("#ffffff", style.getColor()); // from theme 1
93+
assertEquals(new Integer(24), style.getFontSize());
94+
assertEquals(Shape.RoundedBox, style.getShape()); // from workspace
95+
assertNull(style.getIcon());
96+
assertEquals(Border.Solid, style.getBorder());
97+
assertEquals("#dddddd", style.getStroke());
98+
assertEquals(new Integer(100), style.getOpacity());
99+
assertEquals(true, style.getMetadata());
100+
assertEquals(true, style.getDescription());
101+
}
102+
103+
@Test
104+
public void test_findRelationshipStyle_WithThemes() {
105+
Workspace workspace = new Workspace("Name", "Description");
106+
SoftwareSystem softwareSystem = workspace.getModel().addSoftwareSystem("Name");
107+
Relationship relationship = softwareSystem.uses(softwareSystem, "Uses");
108+
workspace.getViews().getConfiguration().getStyles().addRelationshipStyle("Relationship").dashed(false);
109+
110+
// theme 1
111+
Collection<ElementStyle> elementStyles = new ArrayList<>();
112+
Collection<RelationshipStyle> relationshipStyles = new ArrayList<>();
113+
relationshipStyles.add(new RelationshipStyle("Relationship").color("#ff0000").thickness(4));
114+
workspace.getViews().getConfiguration().getStyles().addStylesFromTheme("url1", elementStyles, relationshipStyles);
115+
116+
// theme 2
117+
elementStyles = new ArrayList<>();
118+
relationshipStyles = new ArrayList<>();
119+
relationshipStyles.add(new RelationshipStyle("Relationship").color("#0000ff"));
120+
workspace.getViews().getConfiguration().getStyles().addStylesFromTheme("url2", elementStyles, relationshipStyles);
121+
122+
RelationshipStyle style = workspace.getViews().getConfiguration().getStyles().findRelationshipStyle(relationship);
123+
assertEquals(new Integer(4), style.getThickness()); // from theme 1
124+
assertEquals("#0000ff", style.getColor()); // from theme 2
125+
Assert.assertFalse(style.getDashed()); // from workspace
126+
assertEquals(Routing.Direct, style.getRouting());
127+
assertEquals(new Integer(24), style.getFontSize());
128+
assertEquals(new Integer(200), style.getWidth());
129+
assertEquals(new Integer(50), style.getPosition());
130+
assertEquals(new Integer(100), style.getOpacity());
131+
}
132+
133+
}

structurizr-core/src/com/structurizr/view/Configuration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ public String getTheme() {
4949
* @param url the URL of theme
5050
*/
5151
@JsonSetter
52-
public void setTheme(String url) {
52+
void setTheme(String url) {
5353
setThemes(url);
5454
}
5555

structurizr-core/src/com/structurizr/view/ElementStyle.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.structurizr.view;
22

33
import com.fasterxml.jackson.annotation.JsonInclude;
4+
import com.structurizr.util.StringUtils;
45
import com.structurizr.util.Url;
56

67
/**
@@ -335,4 +336,54 @@ public ElementStyle description(boolean description) {
335336
return this;
336337
}
337338

339+
void copyFrom(ElementStyle elementStyle) {
340+
if (elementStyle.getWidth() != null) {
341+
this.setWidth(elementStyle.getWidth());
342+
}
343+
344+
if (elementStyle.getHeight() != null) {
345+
this.setHeight(elementStyle.getHeight());
346+
}
347+
348+
if (!StringUtils.isNullOrEmpty(elementStyle.getBackground())) {
349+
this.setBackground(elementStyle.getBackground());
350+
}
351+
352+
if (!StringUtils.isNullOrEmpty(elementStyle.getStroke())) {
353+
this.setStroke(elementStyle.getStroke());
354+
}
355+
356+
if (!StringUtils.isNullOrEmpty(elementStyle.getColor())) {
357+
this.setColor(elementStyle.getColor());
358+
}
359+
360+
if (elementStyle.getFontSize() != null) {
361+
this.setFontSize(elementStyle.getFontSize());
362+
}
363+
364+
if (elementStyle.getShape() != null) {
365+
this.setShape(elementStyle.getShape());
366+
}
367+
368+
if (!StringUtils.isNullOrEmpty(elementStyle.getIcon())) {
369+
this.setIcon(elementStyle.getIcon());
370+
}
371+
372+
if (elementStyle.getBorder() != null) {
373+
this.setBorder(elementStyle.getBorder());
374+
}
375+
376+
if (elementStyle.getOpacity() != null) {
377+
this.setOpacity(elementStyle.getOpacity());
378+
}
379+
380+
if (elementStyle.getMetadata() != null) {
381+
this.setMetadata(elementStyle.getMetadata());
382+
}
383+
384+
if (elementStyle.getDescription() != null) {
385+
this.setDescription(elementStyle.getDescription());
386+
}
387+
}
388+
338389
}

0 commit comments

Comments
 (0)