-
Notifications
You must be signed in to change notification settings - Fork 47
Style customization
Ontodia library provides an ability to change a visual style of any element on canvas.
Not only color or icon can you change, but also any style or template of any element can be replaced by a new one.
If you want to change any of Ontodia's elements, there are three main things for you: LinkTemplate, ElementTemplate and CustomTypeStyle.
Applied LinkTemplate determines link markers and allows to override style for each link instance with LinkStyle. The LinkStyle object describes the way how the link with a specified type will be visualized on the graph. You can change the thickness, color, type or even routing of the link:
interface LinkTemplate {
markerSource?: LinkMarkerStyle;
markerTarget?: LinkMarkerStyle;
renderLink?(link: LinkModel): LinkStyle;
}
export interface LinkStyle {
connection?: {
fill?: string;
stroke?: string;
'stroke-width'?: number;
'stroke-dasharray'?: string;
};
label?: LinkLabel;
properties?: LinkLabel[];
connector?: { name?: string; args?: {}; };
}The ElementTemplate is the markup, which describes how a diagram element with a specified id will look like.
From the code perspective the ElementTemplate is a React component with a next object defined as a property of a react component:
interface TemplateProps {
types: string;
label: string;
color: any;
icon: string;
iri: string;
imgUrl?: string;
isExpanded?: boolean;
propsAsList?: PropArray;
props?: Dictionary<Property>;
}The simplest Element Template can looks like this:
/* template.tsx */
class CustomElementTemplate extends React.Component {
render() {
return (
<div className='example-template'
style={{borderColor: this.props.color}}>
<div className={this.props.icon} />
<div className='example-label'>{this.props.label}</div>
</div>
);
}
}/* template.css */
.template-1-first-class {
border-radius: 100px;
background-color: white;
border: 1px solid black;
width: 200px;
height: 200px;
}The CustomTypeStyle is an object, which contains two fields - a color and an icon:
const customTypeStyle = {color: '#eaac77', icon: 'ontodia-person-icon'};Both fields have string as type format. Color is represented as a value from css (for example: #aaffbb or rgb(100, 255, 50)). The icon field is represented as string as well, and actually it is a name of a CSS class, which can looks like the following:
.ontodia-person-icon::after {
content: "\1F464";
min-width: 10px;
min-height: 10px;
border-radius: 100px;
overflow: hidden;
font-style: normal;
}Ontodia already has a number of default LinksStyles, ElementTemplates, CustomTypeStyles, and there are special rules for describing, which template assigned to what types of elements. These rules are defined as Resolvers. The Resolver is a function, which obtains a number of types, represented as an array of strings, and returns one of the objects we were talking about earlier or can return undefined (the LinkStyle, the ElementTemplate, CustomTypeStyle).
Since we are have already mentioned the three things, including LinkTemplate, ElementTemplate and CustomTypeStyle, there are three types of resolvers:
function linkTemplateResolver(types: string[]) {
const LINK_TYPE_OF: LinkTemplate = {
markerTarget: {
fill: '#8cd965',
stroke: '#5b9a3b',
},
renderLink: () => ({
connection: {
stroke: '#8cd965',
'stroke-width': 2,
},
}),
};
if (types.indexOf('http://www.w3.org/1999/02/22-rdf-syntax-ns#type') !== -1) {
return LINK_TYPE_OF;
} else {
return undefined;
}
}function templateResolver(types: string[]) {
if (types.indexOf('http://www.w3.org/2000/01/rdf-schema#Class') !== -1) {
return CustomElementTemplate;
} else {
return undefined;
}
}function typeStyleResolver(types: string[]) {
if (types.indexOf('http://www.w3.org/2000/01/rdf-schema#Class') !== -1) {
return {color: '#eaac77', icon: 'ontodia-class-icon'};
} else {
return undefined;
}
}There are default bundles for each type of resolvers. They provide templates for every element on canvas.
When you place an element on the graph, Ontodia is trying to get an ElementTemplate and a CustomTypeStyle for the element, if it's a node, or LinkTemplate, if it's a link (edge). If there is no special rule for some specific type, the element obtains a basic template and a default icon, or if we are talking about links - a basic LinkTemplate.
When applying templates created by yourself you can chose two ways to go.
The first way is to override the whole default bundle of resolvers, whether it's templateResolvers bundle or any other resolver-bundles. You can do it on initialization stage of Ontodia, by passing the bundle as a part of viewOptions (see DiagramViewOptions) property for Workspace component.
/* link-and-element-style-example.css */
.ontodia-test-icon::after {
content: "+";
font-weight: bold;
min-width: 10px;
min-height: 10px;
border-radius: 100px;
overflow: hidden;
font-style: normal;
}/* link-and-element-style-example.js */
function onWorkspaceMounted(workspace) {
if (!workspace) { return; }
const model = workspace.getModel();
model.importLayout({
dataProvider: new Ontodia.SparqlDataProvider({
endpointUrl: 'https://library-ontodia-org.herokuapp.com/sparql',
imagePropertyUris: [
'http://xmlns.com/foaf/0.1/img',
],
queryMethod: Ontodia.SparqlQueryMethod.GET
}, Ontodia.OWLStatsSettings),
});
}
const CUSTOM_LINK_STYLE = {
markerSource: {
fill: '#4b4a67',
stroke: '#4b4a67',
d: 'M0,3a3,3 0 1,0 6,0a3,3 0 1,0 -6,0',
width: 6,
height: 6,
},
markerTarget: {
fill: '#4b4a67',
stroke: '#4b4a67',
d: 'm 20,5.88 -10.3,-5.95 0,5.6 -9.7,-5.6 0,11.82 9.7,-5.53 0,5.6 z',
width: 20,
height: 12,
},
renderLink: () => ({
connection: {
stroke: '#3c4260',
'stroke-width': 2,
},
label: {
attrs: {
rect: {},
text: {fill: '#3c4260'},
},
},
}),
};
const props = {
ref: onWorkspaceMounted,
viewOptions: {
typeStyleResolvers: [
types => {
return {icon: 'ontodia-test-icon', color: 'red'};
}
],
linkTemplateResolvers: [
types => {
return CUSTOM_LINK_STYLE;
}
],
templatesResolvers: [],
}
};
const container = document.createElement('div');
container.id = 'root';
document.body.appendChild(container);
ReactDOM.render(React.createElement(Ontodia.Workspace, props), container)/* element-template-example.css */
html, body, #root {
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
.ontodia-test-icon::after {
content: "+";
font-weight: bold;
min-width: 10px;
min-height: 10px;
border-radius: 100px;
overflow: hidden;
font-style: normal;
}
.example-template {
border-radius: 100px;
background-color: white;
border: 1px solid black;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
white-space: nowrap;
}
.example-template .example-label {
overflow: hidden;
text-overflow: ellipsis;
}/* element-template-example.js */
function onWorkspaceMounted(workspace) {
if (!workspace) { return; }
const model = workspace.getModel();
model.importLayout({
dataProvider: new Ontodia.SparqlDataProvider({
endpointUrl: 'https://library-ontodia-org.herokuapp.com/sparql',
imagePropertyUris: [
'http://xmlns.com/foaf/0.1/img',
],
queryMethod: Ontodia.SparqlQueryMethod.GET
}, Ontodia.OWLStatsSettings),
});
}
class ExampleTemplate extends React.Component {
render() {
return React.createElement('div', {className: 'example-template'},
React.createElement('label', {}, this.props.label)
);
}
}
const props = {
ref: onWorkspaceMounted,
viewOptions: {
typeStyleResolvers: [],
linkTemplateResolvers: [],
templatesResolvers: [
types => ExampleTemplate,
]
}
};
const container = document.createElement('div');
container.id = 'root';
document.body.appendChild(container);
ReactDOM.render(React.createElement(Ontodia.Workspace, props), container)The second way to extend a bundle, which Ontodia uses, is by creating new resolvers. Upon the initialization of Ontodia, (you can subscribe on specific events or just provide callBack into the workspace) use API of the DiagramView to extend the default bundles.
class Workspace {
...
getDiagram(): {
...
registerElementStyleResolver(resolver: TypeStyleResolver)
unregisterElementStyleResolver(resolver: TypeStyleResolver)
registerTemplateResolver(resolver: TemplateResolver)
unregisterTemplateResolver(resolver: TemplateResolver)
registerLinkTemplateResolver(resolver: LinkTemplateResolver)
unregisterLinkTemplateResolver(resolver: LinkTemplateResolver)
...
}
...
}/* register-element-template-example.css */
html, body, #root {
height: 100%;
overflow: hidden;
margin: 0;
padding: 0;
}
.ontodia-test-icon::after {
content: "+";
font-weight: bold;
min-width: 10px;
min-height: 10px;
border-radius: 100px;
overflow: hidden;
font-style: normal;
}
.example-template {
border-radius: 100px;
background-color: white;
border: 1px solid black;
width: 100px;
height: 100px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
white-space: nowrap;
}
.example-template .example-label {
overflow: hidden;
text-overflow: ellipsis;
}/* register-element-template-example.js */
function onWorkspaceMounted(workspace) {
if (!workspace) { return; }
const model = workspace.getModel();
model.importLayout({
dataProvider: new Ontodia.SparqlDataProvider({
endpointUrl: 'https://library-ontodia-org.herokuapp.com/sparql',
imagePropertyUris: [
'http://xmlns.com/foaf/0.1/img',
],
queryMethod: Ontodia.SparqlQueryMethod.GET
}, Ontodia.OWLStatsSettings),
});
workspace.diagram.registerTemplateResolver(types => {
return ExampleTemplate;
});
}
class ExampleTemplate extends React.Component {
render() {
return React.createElement('div', {className: 'example-template'},
React.createElement('div', {className: 'example-label'}, this.props.label)
);
}
}
const props = {
ref: onWorkspaceMounted
};
const container = document.createElement('div');
container.id = 'root';
document.body.appendChild(container);
ReactDOM.render(React.createElement(Ontodia.Workspace, props), container)