Skip to Content

This is the second article about the network graph SAP UI5 control. The first article, you can find it here.

In this article, we will discuss custom rendering. As of SAP UI5 version 1.58, we allow UI5 developers to completely customize node visualization by changing the positions of icons and nodes titles , as well as applying custom colors and  different node shapes.

 

We will take a look at three types of custom nodes:

  • Custom box node — We will change the default locations of text and icons.
  • Custom circular node — We will put the text into the circle where the node icon is placed by default.
  • Custom-shaped node.

 

The Architecture of Custom Rendering

To enable  custom rendering, you need to override some of the node’s methods. Although you can override methods for each node’s instance, I recommend that you create your own custom node class and inherit it from the default node.

Here, as you can see, we are creating our own class with the method calculateSizes. We will discuss this method later.

 

<custom:NetworkGraphCustomNode key="{key}"
     title="{title}"
     icon="{icon}"
     shape="{shape}"
     width="{width}"
     height="{height}"
     titleLineSize="{titleLineSize}"
     status="{status}">
</custom:NetworkGraphCustomNode>

sap.ui.define([
   "sap/ui/core/mvc/Controller",
   "sap/ui/model/json/JSONModel",
   "sap/suite/ui/commons/networkgraph/Node"
], function (Controller, JSONModel, Node) {
   var oCustomNode = Node.extend("sap.suite.ui.commons.sample.NetworkGraphCustomRendering.NetworkGraphCustomNode", {
      oCustomTitleSize: null,
      oCustomTrimmedTextSize: null,
      sBigFont: "BIG FONT TRIMMED TEXT",
      calculateSizes: function () {

 

Text Wrapping

Before we start customization, there is a very important topic we need to discuss. For now,  SVG doesn’t support wrapping text into multiple lines (the CSS equivalent is white-space: normal). That’s why we have provided two methods:

  • calculateSizes — a method that calculates the number of lines of text.
  • renderText — a method for rendering such calculated lines of text.

In the network graph control, this is unfortunately more complicated, as we need to know the  node’s dimensions before the nodes are sent to the layout rendering processor.

That being said, without getting too deep into technical details, the node has a method calculateSizes which by default calculates and positions all texts when the default rendering algorithm is used.

If you want to use  custom rendering, you can override this function (best in your own class inheriting from the node) and do the calculation for your text yourself. There is another method that helps you calculate text size,computeTextDimensions –  you only need to specify the width of the text, the maximum number of lines (0 for unlimited), and, of course, the text itself.

 

calculateSizes: function () {
   this.oCustomTitleSize = this.computeTextDimmensions({
      text: this.getTitle(),
      width: this.getWidth() - 10,
      maxLines: this.getTitleLineSize()
   });

   this.oCustomTrimmedTextSize = this.computeTextDimmensions({
      text: this.sBigFont,
      width: this.getWidth() - 10,
      attributes: {
         "font-size": "20px"
      },
      maxLines: 1
   }); 
}

 

The node title is split into multiple lines to fit the node’s width.The return parameter of this method is an object with two properties:

 

return {
   text: // an array of strings that contains the text split into lines  
   lineHeight: // the height of a single line (different line sizes in  are not supported 

 

You can store this value (for example, in a closure) and then pass it to the renderText method described below.

If you want to change the node’s height or width, now is the best moment for that. Since the height property is used for the initial height of the node, we don’t want to change it directly, as it would grow indefinitely in multiple calls. Instead, you can use the method setSize, which sets internal variables and doesn’t change the height and width properties.

 

this.setSize({
   height: Math.floor(oTitle.lineHeight * oTitle.text.length + oTrimmedText.lineHeight + 25 + 50 /*icon*/)
});

 

Other Methods Used for Rendering 

To streamline the creation of SVGs, you can use several methods that are available for network graph nodes.

But, of course, you can always  create your own custom SVGs without using these methods.

  • computeTextDimensions – this method helps you split the text into lines. As mentioned above, SVG doesn’t support text wrapping,  so you need to split it manually, specifying the text to be displayed on each line.
    Important: This method   should be called from the calculateSizes method.
  • renderText – if you have already split your text into lines or if you just want to render one single line, you can use this method to render text as SVG. Please note, though,  that this method won’t split text into   
  • renderIcon – this method provides an easier way to render an icon, using the SAP icon font. 
  • renderElement this method provides a simple function wrapper for rendering a single SVG element.

We support two basic approaches to  handling node customization. We start with the easier one – node’s content customization.

 

Partial Customization: Node’s Content Customization 

Nodes with customized content may look like this. The basic shape is predefined but you can change the rest to fit your needs.

 

 

The Network Graph will still render the node’s wrappers that allow the network graph to handle status changes, hover events, and selection events as well as keyboard navigation.  Which means you can focus on proper content positioning only.

To customize the node’s content, you need to override the method renderItemContent for each node you want to customize.

renderItemContent: function (mArguments) {
    if (this.getShape() === "Custom") {
      return Node.prototype.renderContent.call(this);
   }

   var sHtml = "";

   if (this.getShape() === "Box") {
      var y = 20;
      if (this.oCustomTitleSize) {
         sHtml += this.renderText({
            x: 5,
            y: y,
            lineSize: this.oCustomTitleSize.lineHeight,
            lines: this.oCustomTitleSize.text
         });

         y += this.oCustomTitleSize.lineHeight * this.oCustomTitleSize.text.length + 20;

         if (this.oCustomTrimmedTextSize) {
            // solo lane text
            sHtml += this.renderText({
               x: 5,
               y: y,
               lines: this.oCustomTrimmedTextSize.text,
               attributes: {
                  "font-size": "20px"
               }
            });

            y += this.oCustomTrimmedTextSize.lineHeight;
         }

         sHtml += this.renderIcon({
            icon: this.getIcon(),
            y: y + 30,
            x: this.getWidth() / 2,
            attributes: {
               "text-anchor": "middle",
               "font-size": "50px"
            }
         });
      }
   } else {
      if (this.oCustomTitleSize) {
         sHtml += this.renderText({
            x: 30,
            y: 35,
            lineSize: this.oCustomTitleSize.lineHeight,
            lines: this.oCustomTitleSize.text
         });
      }
   }

   return sHtml;
}

 

Then we can create an SVG string for the title, using the data we collected in the attached event calculateSizes.

As you can see, we can create different content, based on  whether the node is of the circle or box type. We can use helper methods to render  SVG strings and adjust the text size,  usinge the object we created with the calculateSizes method.

This method requires a return string that represents an SVG element.

 

Complete Customization: Adjusting the Node’s Shape and Content 

You can, however, customize your node completely, exploring other shapes beyond the standard box and circle. How about a diamond shape like the one pictured

 

 

If you want tocustomize both the node’s shape and its content, follow these steps:

  1. Set the node’s shape property to Custom
  2. Override the method renderContent for the node you want to customize.

 

renderContent: function (mArguments) {
   // size determination rendering is for default nodes using max-width or multiline titles
   // there is no need to waste time rendering in this cycle as its destroyed anyway
   if (mArguments.sizeDetermination) {
      return;
   }

 

Important: The rendering function is called even for calculating the text length  (with parameter sizeDetermination). Since we usually perform all text length calculations in a special event, it’s recommended that  you  skip the rendering in this case and save some computation time.

If you want the network graph to handle events for color changes, state changes, and hover states, you can do so by settings correct classes. For the top shape element, it’s sapSuiteUiCommonsNetworkInnerRect.

 

var FOCUS_OFFSET = 3;
var sHtml = "",
   x = this.getX(),
   y = this.getY(),
   iHeight = 80,
   iWidth = this.getWidth();

// set default classes if you want to have focus, hover and selected state handled by default
// otherwise you need to handle it yourself
sHtml += this.renderElement("path", {
   stroke: "red",
   "class": "sapSuiteUiCommonsNetworkInnerRect",
   d: "M" + (x + iWidth / 2) + " " + y + " L " + (x + iWidth) + " " + ( y + iHeight / 2) +
   " L " + (x + iWidth / 2) + " " + ( y + iHeight) +
   " L " + (x) + " " + ( y + iHeight / 2) +
   " L " + (x + iWidth / 2) + " " + ( y) + "Z"
});

 

This shape will be used for focus handling and keyboard navigation.  If you want the network graph to take care of these,  set the class to sapSuiteUiCommonsNetworkBoxFocus.

 

sHtml += this.renderElement("path", {
   stroke: "red",
   "class": "sapSuiteUiCommonsNetworkBoxFocus",
   d: "M" + (x + iWidth / 2) + " " + (y + FOCUS_OFFSET) + " L " + (x + iWidth - FOCUS_OFFSET) + " " + ( y + iHeight / 2) +
   " L " + (x + iWidth / 2) + " " + ( y + iHeight - FOCUS_OFFSET) +
   " L " + (x + FOCUS_OFFSET) + " " + ( y + iHeight / 2) +
   " L " + (x + iWidth / 2) + " " + ( y + FOCUS_OFFSET) + "Z"
});

 

Alternatively, you have can do  this all in a different way, if it  fits your needs better, in which case you don’t need to add the classes.

Finally, we can render the icon and the text that we split into lines earlier.

 

sHtml += this.renderIcon({
   icon: this.getIcon(),
   y: 65,
   x: this.getWidth() / 2,
   attributes: {
      "text-anchor": "middle",
      "font-size": "50px"
   }
});

if (this.getTitle()) {
   sHtml += this.renderText({
      x: this.getWidth() / 2,
      attributes: {
         "text-anchor": "middle",
         "pointer-events": "none"
      },
      y: iHeight + 20,
      lineSize: oTitle.lineHeight,
      lines: oTitle.text
   });
}

 

Rendering of the Nodes Status Icon

Status icon is a part of the node’s shape.

If you override the renderItemContent method to change only the inner content of the node, the status icon will be rendered automatically, which means you needn’t do anything yourself. In case you still want to change the status icon, you can override the method  renderStatusIcon.  Please keep in mind  that you will also have to take care of the colors for different states, such as node colors on select  and on hover.

// render info box
sHtml += this.renderStatusIcon({
	x: this.getWidth() / 2,
	y: 0,
	size: 15,
        iconSize: 25
});

On the other hand, when you use complete customization, using the renderContent method, the status icon is not rendered.  You can use the already mentioned method renderStatusIcon and call it directly (see the API reference for the available parameters) or create a completely different shape for this status icon. However,  in this case you will again need to take care of the colors used for different node states.

 

In this tutorial, you’ve learned how to customize node shapes and their content in a network graph.  For a more detailed example, see our sample page.

 

To report this post you need to login first.

3 Comments

You must be Logged on to comment or reply to a post.

  1. Cockx Tizian

    Great post! Just one question: Is it also possible to adjust the lines? I am looking for a way to add a label to each line.

    Regards

    Tizian

    (0) 
    1. Libor Pisa Post author

      Hello, so far it is not. We will consider this in the future.

      Just one suggestion: If it is only about adding some stuff (label etc) to the line, you may consider editing directly svg (i think best place would be in event graphReady). You can grab coordinate for every line, that can help you position your label properly.

      (0) 

Leave a Reply