# Graph Style Script language

Graph Style Script (GSS) is a language created by Memgraph for customizing graph
visualization. It is composed of two [main
elements](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/main-building-blocks):
[expressions](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/main-building-blocks#expressions)
and
[directives](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/main-building-blocks#directives).
Each directive includes a set of configurable
[properties](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/directive-properties),
along with their expected data types.

A GSS file is essentially a sequence of the main building blocks and it includes several
[built-in elements](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/built-in-elements) to help define styles:
- [Colors](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/built-in-elements#colors)
- [Functions](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/built-in-elements#functions)
- [Variables](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/built-in-elements#variables)

The following example uses a graph of European countries and cities to illustrate GSS basics.  
You can find the dataset [here](https://memgraph.com/docs/querying/exploring-datasets/backpacking-through-europe).

- Countries are labeled `Country`, and cities are labeled `City`.  
- All nodes have a `name` property.  
- City nodes also include properties like `country` (the country they belong to) and `drinks_USD` (average drink price).

## Setting graph labels

We want to label country nodes with country names, and city nodes with city
names and containing country names. To achieve that we can use two directives.
The first one selects countries and the second one selects cities.

```cpp
@NodeStyle HasLabel(node, "Country") {
  label: Property(node, "name")
}

@NodeStyle HasLabel(node, "City") {
    label: Format("{cityName}, {countryName}",
                  Property(node, "name"),
                  Property(node, "country"))
}
```

In the case of the [`Format`](https://memgraph.com/docs/memgraph-lab/features/graph-style-script/built-in-elements#formatformatstring-value)
function, content inside the curly braces is ignored but can be helpful for
clarity.

## Setting node images

It would be nice to display flags in the country nodes. This can be achieved
using URLs of flag images. There is a [website](https://cdn.countryflags.com)
that hosts many world flags so we can use images from there. Their API expects a
country name as a part of the URL path so we will make the following directive.

```cpp
@NodeStyle HasLabel(node, "Country") {
  image-url: Format("https://cdn.countryflags.com/thumbs/{}/flag-800.png",
                    LowerCase(Property(node, "name")))
}
```

Unfortunately, this won't work for all countries. Flags for England and Scotland
cannot be found on the website because they aren't real countries. So we can get
around that by providing custom directives below the general one above.

```cpp
@NodeStyle Equals(Property(node, "name"), "England") {
   image-url: "https://upload.wikimedia.org/wikipedia/en/thumb/b/be/Flag_of_England.svg/2560px-Flag_of_England.svg.png"
}

@NodeStyle Equals(Property(node, "name"), "Scotland") {
  image-url: "https://upload.wikimedia.org/wikipedia/commons/thumb/1/10/Flag_of_Scotland.svg/1200px-Flag_of_Scotland.svg.png"
 }
```

URLs with country names containing spaces don’t work, so we provide custom URLs
for the Czech Republic and Bosnia and Herzegovina.

```cpp
@NodeStyle Equals(Property(node, "name"), "Bosnia and Herzegovina") {
   image-url: "https://upload.wikimedia.org/wikipedia/commons/thumb/b/bf/Flag_of_Bosnia_and_Herzegovina.svg/1200px-Flag_of_Bosnia_and_Herzegovina.svg.png"
 }

 @NodeStyle Equals(Property(node, "name"), "Czech Republic") {
   image-url: "https://upload.wikimedia.org/wikipedia/commons/thumb/c/cb/Flag_of_the_Czech_Republic.svg/2560px-Flag_of_the_Czech_Republic.svg.png"
 }
```

Now all the country nodes have their flags displayed.

## Highlighting interesting nodes

We can highlight nodes with low drink prices in the following way:

```cpp
@NodeStyle And(
     HasLabel(node, "City"),
     Less(Property(node, "drinks_USD"), 5)) {
  size: 50
  image-url: "https://www.sciencenews.org/wp-content/uploads/2020/05/050620_mt_beer_feat-1028x579.jpg"
  shadow-color: red
 }
```

The code above will use a beer image and a bigger size along with a red shadow.

## Normalizing values

To normalize some value, for example, the size or width of all the
nodes or relationships in the graph, find the minimum and maximum values
of all nodes.
For example, a node labeled "Person"` has the property `age` that holds the age information
of a particular person. We want the node property `size` to be 5 for the youngest person
and 20 for the oldest one in the presented graph. All other node sizes should be normalized
within that range.

One of the solutions could look like this:

```cpp
// Size range min/max variables
Define(MIN_SIZE, 5)
Define(MAX_SIZE, 20)
Define(PROP_NAME, "age")
Define(SIZE_RANGE, Sub(MAX_SIZE, MIN_SIZE))

// A set of utility functions
// Create a new array of property values from an array of nodes
Define(GetProperties, Function(nodes, propName,
  Map(nodes, Function(singleNode, Property(singleNode, propName)))
))
// Keep only the numeric values from an array of values
Define(KeepNumericValues, Function(values,
  Filter(values, Function(value, IsNumber(value)))
))

// Functions to find min and max value in the input nodes
Define(GetMaxValue, Function(nodes,
  Max(KeepNumericValues(GetProperties(nodes, PROP_NAME)))
))
Define(GetMinValue, Function(nodes,
  Min(KeepNumericValues(GetProperties(nodes, PROP_NAME)))
))

// Normalize function that receives two inputs: node (n) and
// graph (g) and returns normalized value into a range
// [MIN_SIZE, MAX_SIZE]
Define(Normalize, Function(n, g,
  Add(
    MIN_SIZE,
    Mul(
      SIZE_RANGE,
      Div(
        Sub(Property(n, PROP_NAME), GetMinValue(Nodes(g))),
        Sub(GetMaxValue(Nodes(g)), GetMinValue(Nodes(g)))
      )
    )
  )
))

// For all nodes with the label "Person" and numeric property "age"
@NodeStyle And(HasLabel(node, "Person"), IsNumber(Property(node, PROP_NAME))) {
  color: white
  size: Normalize(node, graph)
  width: Div(Normalize(node, graph), 5)
  label: Format("Age: {}", AsText(Property(node, PROP_NAME)))
}
```

![Using Graph Style Script to style different nodes by its size](https://memgraph.com/docs/pages/data-visualization/graph-style-script/gss.png)

## Caching results for faster performance

The problem with the [example for normalizing values](#normalizing-values) is
slow performance. The `Normalize` function is called twice for each node in the
graph view. Each `Normalize` call iterates through all nodes three times: two
times for `GetMinValue` and once for `GetMaxValue`. For small graphs, you won't
see a difference in performance but as the number of nodes rises the performance
issues will follow. 

To solve this issue, **cache the results** by calculating outside of `@NodeStyle` and
`@EdgeStyle` directives where the variable `graph` is also available.
Inside the `@NodeStyle` directive, a local variable can be used to store the normalized
value and use it with `size` and `width` properties thus calling the `Normalize` function only once.

Check the improved GSS code below:

```cpp
// Size range min/max variables
Define(MIN_SIZE, 5)
Define(MAX_SIZE, 20)
Define(PROP_NAME, "age")
Define(SIZE_RANGE, Sub(MAX_SIZE, MIN_SIZE))

// A set of utility functions
// Create a new array of property values from an array of nodes
Define(GetProperties, Function(nodes, propName,
  Map(nodes, Function(singleNode, Property(singleNode, propName)))
))
// Keep only the numeric values from an array of values
Define(KeepNumericValues, Function(values,
  Filter(values, Function(value, IsNumber(value)))
))

// Variables MAX_VALUE and MIN_VALUE will hold the max and min
// values of all node properties.
// The If statement is used to handle errors when there are no values to calculate
// min and max from.
Define(MAX_VALUE, If(
  Greater(NodeCount(graph), 0),
  Max(KeepNumericValues(GetProperties(Nodes(graph), PROP_NAME))),
  0
))
Define(MIN_VALUE, If(
  Greater(NodeCount(graph), 0),
  Min(KeepNumericValues(GetProperties(Nodes(graph), PROP_NAME))),
  0
))

// Normalize function that receives one inputs: node and
// returns normalized value into a range [MIN_SIZE, MAX_SIZE]
Define(Normalize, Function(n,
  Add(
    MIN_SIZE,
    Mul(
      SIZE_RANGE,
      Div(
        Sub(Property(n, PROP_NAME), MIN_VALUE),
        Sub(MAX_VALUE, MIN_VALUE)
      )
    )
  )
))

// For all the nodes with label "Person" and numeric property "age"
@NodeStyle And(HasLabel(node, "Person"), IsNumber(Property(node, PROP_NAME))) {
  // Local variable used to cache a result from function Normalize
  Define(NORM, Normalize(node))

  color: white
  size: NORM
  width: Div(NORM, 5)
  label: Format("Age: {}", AsText(Property(node, PROP_NAME)))
}
```
