Skip to content

Views Predicates

Views are not static, they are generated from the model. Any changes in the model are applied immediately and update views. Two types of predicates define what is visible: element and relationship predicates.

Element predicates explicitly define which elements are visible. Each included element brings in its relationships with already visible elements.

view {
// Only backend is visible
include backend
// Add frontend to the view
// and its relationships with backend
include frontend
// Add authService to the view
// and its relationships with visible (backend and frontend)
include authService
// Add children of messageBroker,
// and their relationships among themselves and visible (backend, frontend and authService)
include messageBroker.*
// Add all descendants of messageBroker,
// and their relationships among themselves and visible (backend, frontend and authService)
include messageBroker.**
// Exclude emailsQueue and its relationships
exclude messageBroker.emailsQueue
}

Predicates can be combined. The following is the same as example above:

view {
include
backend,
frontend,
authService,
messageBroker.**
exclude messageBroker.emailsQueue
}

Wildcard predicates can be used to reference “everything” (but it differs for scoped/unscoped views).
Consider the following model:

model {
actor customer {
-> webApp 'uses in browser via HTTPS'
}
system cloud {
container backend {
component api
}
container ui {
component webApp {
-> api 'requests data'
}
}
}
}
views {
// Unscoped view - wildcard refers to top-level elements
view {
include *
// Visible top-level elements: customer, cloud
// and derived relationship customer -> cloud
}
// Scoped view - wildcard refers to element and its children
view of cloud.ui {
include *
// Visible:
// - cloud.ui
// - cloud.ui.webApp
// - customer
// - relationship customer -> cloud.ui.webApp
// - cloud.backend
// - cloud.ui.webApp -> cloud.backend, derived from cloud.ui.webApp -> cloud.backend.api
}
}

You can modify element properties specifically for the view:

// Include the element and override its properties
include cloud.backend with {
title 'Backend components'
description '...'
technology 'Java, Spring'
icon tech:java
color amber
shape browser
multiple true
}
// Include all nested elements, change color and textSize
include cloud.* with {
color amber
textSize small
}

with may be used only within include.

You can define custom navigation and links between views:

example.c4
view view2 {
include *
include cloud.backend with {
// navigate to 'view3' on click
navigateTo view3
}
}
view view3 {
include *
include cloud.backend with {
// navigate back to 'view2'
navigateTo view2
}
}
// elements by kind
include element.kind != system
exclude element.kind = container
// elements by tag
include element.tag != #V2
exclude element.tag = #next

The children selector includes element’s children and their relationships with visible elements.

include cloud.*
// Same as
include cloud.backend
include cloud.ui

The descendants selector includes element’s descendants IF they have a relationship with visible elements.

include cloud.**
// Same as
include cloud.backend
include cloud.ui
include cloud.ui.webApp

The expand selector includes element’s children IF they have a relationship with visible elements. All other children are omitted.

include cloud._
// Same as
include cloud
include -> cloud.* ->

Relationship predicates include elements only if they have relationships that meet the specified predicate conditions.

Include elements if they have directed relationships (or their nested elements):

// Include customer and cloud:
include customer -> cloud
// Include customer and nested elements of cloud (that have relationships):
include customer -> cloud.*

Include elements if they have any relationships:

include customer <-> cloud

Include elements if they have incoming relationships from already visible elements.
Here’s an example based on the model from the wildcard example:

incoming predicate.c4
view {
// visible element
include customer
// include nothing, customer has no relation to backend
include -> backend
// add ui,
// because customer has a relationship with nested ui.webApp
include -> ui
// add backend, because visible ui has a relationship to backend
// derived from ui.webApp -> backend.api
include -> backend
}
// This view includes customer and ui
view {
include
customer,
-> cloud.*
}

Include elements if only they have outgoing relationships to already visible elements:

include customer ->
include cloud.* ->

Include nested elements of cloud, that have any relationships with visible elements:

include -> cloud.* ->

Relationships can be customized inside view:

include
// Make lines red and solid
cloud.* <-> amazon.* with {
color red
line solid
},
// or only directed
customer -> cloud.* with {
// Override label
title 'Customer uses cloud'
navigateTo dynamicview1
},

To customize navigation from relationship:

include
webApp -> backend.api with {
navigateTo dashboardRequestFlow
}

where operator narrows down results by applying additional conditions:

// include only microservices from nested
include cloud.*
where kind is microservice
// only microservices and not deprecated
include cloud.*
where
kind == microservice and // possible to use 'is' or '=='
tag != #deprecated // possible to use 'is not' or '!='
// Use logical operators
include cloud.*
where
not (kind is microservice or kind is webapp)
and tag is not #legacy
and (tag is #v1 or tag is #v2)

Relationship predicates

When where is used with element predicates, it is applied to the elements.
When used with relationship predicates - to the relationships.

include
// only relationships with tag #messaging
cloud.* <-> amazon.*
where tag is #messaging,
// only incoming http-requests
-> backend
where kind is http-request
-[http-request]-> backend
.http-request backend

It is also possible to filter relations by tag or kind of its endpoints.

include
// only relationships outgoing from elements with with tag #next
cloud.* -> amazon.*
where source.tag is #next,
// only incoming relations of elements with kind microservice
-> *
where target.kind is microservice

Together with with

It is possible to use where together with with, but where should be defined first:

include *
where kind is microservice
with {
color amber
}

If you find yourself repeating the same predicates in multiple views, you can define them as global group:

global {
predicateGroup microservices {
include cloud.*
where kind is microservice
exclude *
where tag is #deprecated
}
}
views {
view of newServices {
include cloud.new.*
global predicate microservices
}
view of newBackendServices {
// Keep in mind that order is significant
global predicate microservices
include cloud.backend.*
}
}

It is possible to group elements, and this is rendered as a boundary around them:

view {
group {
include backend
}
// with title
group 'Frontend' {
include frontend.*
}
// with style
group 'Service Bus' {
color amber
opacity 20%
border solid
include messageBroker.*
}
}

Groups can be nested:

view {
group 'Third-parties' {
group 'Integrations' {
group 'Analytics' {}
group 'Marketing' {}
}
group 'Monitoring' {}
}
}

Style predicates define how elements are rendered, and applied in the order they are defined merging with previous ones:

view apiApp of internetBankingSystem.apiApplication {
include *
// apply to all elements
style * {
color muted
opacity 10%
}
// apply only to these elements
style singlePageApplication, mobileApp {
color secondary
size xlarge
}
// apply only to nested of apiApplication
style apiApplication.* {
color primary
multiple true
}
// apply to apiApplication and nested
style apiApplication._ {
color primary
}
// apply only to elements with specific tag
style element.tag = #deprecated {
color muted
}
// apply to elements not tagged
style element.tag != #deprecated {
opacity 20%
}
}

Styles can be shared within views block (“local styles”):

views {
// apply to all views in this block
style * {
color muted
opacity 10%
}
view of apiApp {
include *
style cloud.web.* {
color green
}
}
view of mobileApp {
include *
style cloud.ui.* {
color amber
}
}
}
views {
// Styles from previous block are not applied here
// ...
}

Styles can be shared globally.
Global styles must be named and defined in global block:

global {
// Format:
// style <name> <targets> { ... }
style mute_all * {
color muted
opacity 10%
}
style applications
singlePageApplication._,
mobileApp._ {
color secondary
}
style mute_deprecated
element.tag = #deprecated {
color muted
}
}
views {
view of singlePageApplication {
// Styles are applied in the order they are defined
// 1. Apply global style
global style mute_all
// 2. Then this
style cloud.* {
color green
}
// 3. and 4.
global style applications
global style mute_deprecated
}
}

Global styles can be grouped:

global {
// Define style group
styleGroup common_styles {
style singlePageApplication, mobileApp {
color secondary
}
style element.tag = #deprecated {
color muted
}
}
}
views {
view mobileApp of mobileApp {
include *
// Apply styles from group
global style common_styles
// Override
style mobileApp {
color primary
}
}
}
view {
include *
autoLayout LeftRight 120 110
}

Parameters are:

  • direction: possible values are TopBottom (default), BottomTop, LeftRight, RightLeft.
  • rank distance: optional, must be a positive number
  • node distance. optional, must be a positive number

Views can be extended to avoid duplication, to create a “baseline” or, for example, “slides” for a presentation:

views {
view view1 {
include *
}
view view2 extends view1 {
title 'Same as View1, but with more details'
style * {
color muted
}
include some.backend
}
// cascade inheritance
view view3 extends view2 {
title 'Same as View2, but with more details'
include * -> some.backend
}
}

The predicates and style rules of extended views applied after the ones from ancestors.

Extended view also inherits the scope:

views {
view view1 of cloud.backend {
title 'Backend components'
}
view view2 extends view1 {
include api // ✅ This is OK, references 'cloud.backend.api'
}
}