Many applications aim to provide multiple themes, especially light and dark modes to support user preferences. GoJS provides functionality to define and manage themes to achieve this goal.
Various GoJS templates are themed, such as the default node, group and link template, tool adornments, background grid, and more. This makes it easy to quickly change the way certain objects appear. For example, you can change the selection adornment color/thickness without providing a Part.selectionAdornmentTemplate, or change the temporary link color when drawing a new link without providing a LinkingBaseTool.temporaryLink.
// The ThemeManager.currentTheme defaults to 'light'
diagram.themeManager.set('light', { // modify some property values of the "light" Theme
colors: {
selection: '#ec4899', // pink selection adornment
tempLink: '#14b8a6' // teal temporary link while linking
},
numbers: {
selection: 6 // increased selection adornment thickness
}
});
diagram.nodeTemplate =
new go.Node('Auto')
.add(new go.Shape('RoundedRectangle',
{ fill: 'white', strokeWidth: 0,
portId: '', fromLinkable: true, toLinkable: true, cursor: 'pointer' })
)
.add(new go.TextBlock({ margin: 8 })
.bind('text')
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha' },
{ key: 2, text: 'Beta' }
]);
diagram.select(diagram.nodes.first());
For a complete list of themed properties on predefined templates, see Templates.js in the Extensions directory.
The simplest way to theme your diagram is to use the predefined Themes available and call GraphObject.theme or create a new ThemeBinding when constructing templates. By default, a light and dark theme are provided in the ThemeManager. These are the Themes.Light and Themes.Dark objects, respectively.
To change themes, simply set ThemeManager.currentTheme to a theme name.
Note that the special value 'system'
will pick either the
'light'
or 'dark'
theme depending on the end user's browser preference.
diagram.themeManager.currentTheme = document.getElementById('themeSelect1').value
// Update the div background color when theme changes;
// in most applications, the div background will be transparent,
// and the page background should be updated.
diagram.themeManager.changesDivBackground = true;
diagram.nodeTemplate =
new go.Node('Auto')
.add(new go.Shape('RoundedRectangle', { strokeWidth: 0 })
.theme('fill', 'group') // fill color is a semi-transparent gray
)
.add(new go.TextBlock({ margin: 8 })
.bind('text')
.theme('stroke', 'text') // stroke color is a dark or light gray
);
// the default link template changes color based on the theme's 'link' color
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha' },
{ key: 2, text: 'Beta' }
],
[
{ from: 1, to: 2 }
]);
changeTheme1 = () => {
diagram.themeManager.currentTheme = document.getElementById('themeSelect1').value;
};
In most scenarios, you'll want custom themes, either by modifying the predefined themes or creating your own.
This can be done by calling ThemeManager.set, passing a theme name and a partial (or complete) Theme object.
Passing an empty string as the theme name will modify the
ThemeManager.defaultTheme, which is initially 'light'
.
If the theme name exists in the ThemeManger.themeMap, that theme will be updated by merging the partial theme object into it. If the theme name does not exist, the theme will be added to the theme map.
diagram.themeManager.currentTheme = document.getElementById('themeSelect2').value
diagram.themeManager.changesDivBackground = true;
// update the light theme and dark theme with some additional colors
diagram.themeManager.set('light', {
colors: {
primary: '#a5b4fc',
border: '#4f46e5'
}
});
diagram.themeManager.set('dark', {
colors: {
primary: '#4338ca'
// border comes from default theme
}
});
diagram.nodeTemplate =
new go.Node('Auto').add(
new go.Shape('RoundedRectangle', { strokeWidth: 2 })
.theme('fill', 'primary') // Shape.fill is set to the current theme's colors.primary
.theme('stroke', 'border'), // Shape.stroke is set to the current theme's colors.border
new go.TextBlock({ margin: 8 })
.bind('text')
.theme('stroke', 'text') // stroke color is a dark or light gray
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha' },
{ key: 2, text: 'Beta' }
],
[
{ from: 1, to: 2 }
]);
changeTheme2 = () => {
diagram.themeManager.currentTheme = document.getElementById('themeSelect2').value;
};
The typical case for theming a GraphObject will be getting a value directly from a Theme. It is also possible to use values from other sources, such as a bound data object, a GraphObject in the binding Panel, or the shared model data object.
These different sources are specified by calling GraphObject.themeData, GraphObject.themeObject, or GraphObject.themeModel. In addition to specifying the source, the Binding.converter function can be used to convert from some value to a theme property name.
diagram.themeManager.currentTheme = document.getElementById('themeSelect3').value
diagram.themeManager.changesDivBackground = true;
diagram.themeManager.set('', {
colors: {
red: '#dc2626',
blue: '#2563eb',
selected: '#0ea5e9'
}
});
// converted from Part.isSelected to a theme property name
const convertSelectedToThemeProp = s => s ? 'selected' : 'text';
diagram.nodeTemplate =
new go.Node('Auto').add(
new go.Shape('RoundedRectangle', { strokeWidth: 0 })
.themeData('fill', 'color'), // from data
new go.TextBlock({ margin: 8 })
.bind('text')
.themeObject('stroke', 'isSelected', null, convertSelectedToThemeProp) // from object
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha', color: 'red' },
{ key: 2, text: 'Beta', color: 'blue' }
],
[
{ from: 1, to: 2 }
]);
changeTheme3 = () => {
diagram.themeManager.currentTheme = document.getElementById('themeSelect3').value;
};
In addition to a converter function to determine the theme property name, a Binding.themeConverter can be provided to perform a conversion on the resulting theme value before assigning it to the target property.
diagram.themeManager.currentTheme = document.getElementById('themeSelect4').value
diagram.themeManager.changesDivBackground = true;
diagram.themeManager.set('', {
colors: {
red: '#dc2626',
blue: '#2563eb'
}
});
diagram.nodeTemplate =
new go.Node('Auto').add(
new go.Shape('RoundedRectangle', { strokeWidth: 2 })
.themeData('fill', 'color') // from data
.themeData('stroke', 'color', null, null, go.Brush.darken), // from data, converted to a darker color
new go.TextBlock({ margin: 8 })
.bind('text')
.theme('stroke', 'text')
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha', color: 'red' },
{ key: 2, text: 'Beta', color: 'blue' }
],
[
{ from: 1, to: 2 }
]);
changeTheme4 = () => {
diagram.themeManager.currentTheme = document.getElementById('themeSelect4').value;
};
Discussion on this page has focused on colors, but theming can also be used for fonts, stroke widths, sizes, or any other property type. You must ensure that the returned theme value matches the type of the target property.
To access an arbitrary property value in your theme, you can use a '.' separated path,
like 'colors.primary'
or 'icons.mode'
.
diagram.themeManager.currentTheme = document.getElementById('themeSelect5').value
diagram.themeManager.changesDivBackground = true;
diagram.themeManager.set('', {
colors: {
primary: '#a5b4fc'
},
icons: {
mode: 'M12 3v2.25m6.364.386l-1.591 1.591M21 12h-2.25m-.386 6.364l-1.591-1.591M12 18.75V21m-4.773-4.227l-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0z'
}
});
diagram.themeManager.set('dark', {
icons: {
mode: 'M21.752 15.002A9.718 9.718 0 0118 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 003 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 009.002-5.998z'
}
});
diagram.nodeTemplate =
new go.Node('Auto').add(
new go.Shape('RoundedRectangle', { strokeWidth: 0 })
.theme('fill', 'primary'), // colors.primary
new go.Panel('Horizontal', { margin: 8 }).add(
new go.TextBlock({ margin: new go.Margin(0, 8, 0, 0), stretch: go.Stretch.Vertical, verticalAlignment: go.Spot.Center })
.bind('text')
.theme('stroke', 'text') // colors.text
.theme('font', 'bold'), // fonts.bold
new go.Shape({ width: 16, height: 16, strokeWidth: 2 })
.theme('geometryString', 'icons.mode') // looks in the theme's 'icons' object
.theme('stroke', 'text') // colors.text
)
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha' },
{ key: 2, text: 'Beta' }
],
[
{ from: 1, to: 2 }
]);
changeTheme5 = () => {
diagram.themeManager.currentTheme = document.getElementById('themeSelect5').value;
};
To keep the builder objects ("Button", "ToolTip", "ContextMenu", etc) simple, they are not themed by default.
Creating themed versions of these objects is relatively simple given the predefined definitions available at Buttons.js. Here we demonstrate theming the "Button" and "ToolTip" builders.
See the intro pages on Buttons, ToolTips, and Context Menus to change appearances without theming.
diagram.themeManager.currentTheme = document.getElementById('themeSelect6').value
diagram.themeManager.changesDivBackground = true;
// update the light theme and dark theme with some additional colors
diagram.themeManager.set('light', {
colors: {
primary: '#a5b4fc',
button: '#f9a8d4',
buttonHover: '#f472b6',
toolTip: '#fafafa'
}
});
diagram.themeManager.set('dark', {
colors: {
primary: '#4338ca',
button: '#be185d',
buttonHover: '#db2777',
toolTip: '#262626'
}
});
// theme the predefined "Button", modifying the initial fill and _button properties via theming
go.GraphObject.defineBuilder('ThemedButton', (args) => {
return go.GraphObject.build('Button', { 'ButtonBorder.strokeWidth': 0 })
.theme('ButtonBorder.fill', 'button')
.theme('_buttonFillOver', 'buttonHover');
});
// theme the predefined "ToolTip", modifying the fill via theming
go.GraphObject.defineBuilder('ThemedToolTip', (args) => {
return go.GraphObject.build('ToolTip').theme('Border.fill', 'toolTip');
});
diagram.nodeTemplate =
new go.Node('Auto', {
toolTip:
go.GraphObject.build('ThemedToolTip').add(
new go.TextBlock('tooltip', { margin: 5 })
.bind('text', 'text', k => `${k}'s tooltip`)
.theme('stroke', 'text')
)
}).add(
new go.Shape('RoundedRectangle', { strokeWidth: 0 })
.theme('fill', 'primary'),
new go.Panel('Vertical', { margin: 8 }).add(
new go.TextBlock({ margin: new go.Margin(0, 0, 8, 0) })
.bind('text', 'key')
.theme('stroke', 'text')
.theme('font', 'bold'),
go.GraphObject.build('ThemedButton').add(
new go.TextBlock('Button', { margin: 5 }).theme('stroke', 'text')
)
)
);
diagram.model = new go.GraphLinksModel(
[
{ key: 1, text: 'Alpha' },
{ key: 2, text: 'Beta' }
],
[
{ from: 1, to: 2 }
]);
changeTheme6 = () => {
diagram.themeManager.currentTheme = document.getElementById('themeSelect6').value;
};