Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
david_stocker
Advisor
Advisor
0 Kudos

This is part of a tutorial series on creating extension components for Design Studio.

We can set the start and end angles via the property pane and via script.  What's missing is setting those angles via an important methodology, first introduced in DS 1.5; property binding.  We could simply add property binding to the angle properties and be done with it.  It is unlikely, however, that any data sources will have measures with degrees as units.  So before we actually add property binding, we're going to make some overhauls to our gauge, so that it can work with generic measure values.  That's the focus of this installment.  What follows is either concepts already covered in this tutorial, or generic Design Studio app building concepts (Grid and Text components, some basic scripting, etc.) that are out of scope for this tutorial.  Therefore, we'll rush along and pack everything into a single instalment so that we can get back to the main narrative. Brace yourself for a long entry.

As always, the completed Part 6 extension is available on Github if you feel like breezing over it and speeding ahead.  Just download the zip from Github or clone the repo and you'll find everything covered in this instalment, already prebuilt.

Requirements (aka what we need)

Previously, we simply entered an end angle.  What we're going to need now is some sort of min/max range for the gauge.  Think of a speedometer on a car.  It runs from 0 to <maxspeed>, with the needle showing the current speed.  That's what we need out gauge to have.  So we'll keep endAngleDeg as the current angle and add a new property, endAngleDegMax as the end value of the gauge.

When we decide to use measures, we'll be calculating endAngleDeg.  So we'll need to add a new property to tell the component to ignore the entered value of endAngleDeg and calculate it.  We'll need to add a "floor value" for the measure; the value at which (or below which) the gauge sits at its start angle.  This might be zero and we'll default it to zero, but the designer may have reasons for using some other starting value.  We'll need a "ceiling value", at which the gauge is pegged.

Once we have a min and max range for our measure, we'll need the actual measure value that we want to plot and a normalization algorithm, to convert this measure value into endAngleDeg .

The new Properties:

  • useMeasures - This boolean value will determing whether we directly assign endAngleDeg, or calculate it.
  • endAngleDegMax - This determins the maximum range of the gauge; the angle that it is maxed out at
  • measureMax - The measure value, at which the gauge will be maxed out (endAngleDeg = endAngleDegMax)  increasing the measure value beyond this will have no effect.
  • measureMin - he measure value, at which the gauge will be at it's start valueout (endAngleDeg = startAngleDeg)  decreasingthe measure value beyond this will have no effect.
  • measureVal - The actual value of the measure.

While we're at it, we clean up the properties pane a bit and group the new properties into their own group in the properties pane.  First, we add the new group to contribution.xml.  It goes between two existing elements, the license and the component.:


  <license>license</license>


  <group


  id="SCNGaugeAngleSettings"


  title="Angle Settings"


  tooltip="Gauge Angle Settings"/>


  <component


  databound="false"


              ...








Then the add the new property entries; which look like this:


<property


  id="useMeasures"s


  title="Use Measures"


  type="boolean"


  group="SCNGaugeAngleSettings"/>


<property


  id="endAngleDegMax"


  title="End Angle Max"


  type="float"


  group="SCNGaugeAngleSettings"/>


<property


  id="measureMax"


  title="Measure Max Value"


  type="int"


  group="SCNGaugeAngleSettings"/>


<property


  id="measureMin"


  title="Measure Min Value"


  type="int"


  group="SCNGaugeAngleSettings"/>


<property


  id="measureVal"


  title="Measure"


  type="int"


  group="SCNGaugeAngleSettings"/>








They will also need entries in contribution.ztl, so that they can be scriptable:


/* Returns whether or not gauge angles cab be indirectly set, based on measure values. */


boolean getUseMeasures() {*


  return this.useMeasures;


*}



/* Enables or disables the ability to indirectly set gauge angles, based on measure values. */


void setUseMeasures(/* New boolean */ boolean useMeasures) {*


  this.useMeasures = useMeasures;


*}



/* Returns the maximum end angle of the gauge. */


float getEndAngleDegMax() {*


  return this.endAngleDegMax;


*}



/* Sets the maximum end angle of the gauge. */


void setEndAngleDegMax(/* New angleMax */ float newAngleMax) {*


  this.endAngleDegMax = newAngleMax;


*}



/* Returns the measure value at which the gauge will peg (read its maximum value). */


float getMeasureMax() {*


  return this.measureMax;


*}



/* Sets the measure value at which the gauge will peg (read its maximum value). */


void setMeasureMax(/* New measureMax */ float measureMax) {*


  this.measureMax = measureMax;


*}



/* Returns the measure value at which the gauge will read its minimum value. */


float getMeasureMin() {*


  return this.measureMin;


*}



/* Sets the measure value at which the gauge will read its minimum value. */


void setMeasureMin(/* New measureMin */ float measureMin) {*


  this.measureMin = measureMin;


*}



/* Returns the measure value from which the gauge will determine its angle. */


float getMeasureVal() {*


  return this.measureVal;


*}



/* Sets the measure value from which the gauge will determine its angle. */


void setMeasureVal(/* New measureVal */ float measureVal) {*


  this.measureVal = measureVal;


*}








Each of these will also need getter/setter functions in compoent.js.  We'll cover them below.

Introducing recalculateCurrentAngle()

In component.js, we won't need to make any changes wherever we're touching properties that only affect the sizing and positioning of the gauge.  This means that we can leave the radii and padding properties alone.  All of the angle affecting properties will need to check and recompute the angles before calling redraw().  We'll introduce a new function with this algorithm, called recalculateCurrentAngle() and these properties will call recalculateCurrentAngle(), instead of redraw(). This new function will act as a wrapper for redraw(), that first implements the angle recalculation algorithm.

The logic flowchart for this algorithm is below:

And the JavaScript code, needed to implement it looks like this:


//New with Part 6


me.recalculateCurrentAngle = function(){


  if (me._useMeasures == true){


  //Firstly, ensure that we can turn in a clockwise manner to get from startAngleDeg to endAngleDegMax


  while (me._endAngleDeg < me._startAngleDeg){


  me._endAngleDegMax = me.me._endAngleDegMax + 360.0;


  }



  var currEnd = 0.0;


  if (me._measureVal > me._measureMax){


  currEnd = me._endAngleDegMax;


  }


  else if (me._measureVal  < me._measureMin){


  currEnd = me._startAngleDeg;


  } else{


  var measureDelta = me._measureMax - me._measureMin;


  var measureValNormalized = 0.0;


  if (measureDelta >  measureValNormalized){


  var measureValNormalized = me._measureVal / measureDelta;


  }


  currEnd = me._startAngleDeg + (measureValNormalized * (me._endAngleDegMax - me._startAngleDeg))


  }



  if (currEnd >  me._endAngleDegMax){


  currEnd = me._endAngleDegMax;


  }




  //Now set me._endAngleDeg


  me._endAngleDeg = currEnd;


  }


  else {


  //Right now, this gauge is hardcoded to turn in a clockwise manner.


  //  Ensure that the arc can turn in a clockwise direction to get to the end angles


  while (me._endAngleDeg < me._startAngleDeg){


  me._endAngleDeg = me._endAngleDeg + 360.0;


  }



  //Ensure that endAngleDeg falls within the range from startAngleDeg to endAngleDegMax


  while (me._endAngleDeg > me._endAngleDegMax){


  me._endAngleDegMax = me._endAngleDegMax + 360.0;


  }


  }


  me.redraw();


};







So now that we've have recalculateCurrentAngle() in hand, we can add the component.js side getter/setter functions for the new properties.  We're not explicitly showing the code block here, but don't forget to refactor me.startAngleDeg() and me.endAngleDeg() to call recalculateCurrentAngle(), instead of redraw().


//New with Part 6


me.useMeasures = function(value) {


  if (value === undefined) {


  return me._useMeasures;


  } else {


  me._useMeasures = value;


  me.recalculateCurrentAngle();


  return this;


  }


};



me.endAngleDegMax = function(value) {


  if (value === undefined) {


  return me._endAngleDegMax;


  } else {


  me._endAngleDegMax = value;


  me.recalculateCurrentAngle();


  return this;


  }


};



me.measureMax = function(value) {


  if (value === undefined) {


  return me._measureMax;


  } else {


  if (value >= me._measureMin){


  me._measureMax = value;


  me.recalculateCurrentAngle();


  }


  else{


  alert("The maximum displayed value of the measure must be greater then the minimum!");


  }


  return this;


  }


};



me.measureMin = function(value) {


  if (value === undefined) {


  return me._measureMin;


  } else {


  if (value <= me._measureMax){


  me._measureMin = value;


  me.recalculateCurrentAngle();


  }


  else{


  alert("The maximum displayed value of the measure must be greater then the minimum!");


  }


  return this;


  }


};



me.measureVal = function(value) {


  if (value === undefined) {


  return me._measureVal;


  } else {


  me._measureVal = value;


  me.recalculateCurrentAngle();


  return this;


  }


};







We should now be ready to copnstruct a test app!  :smile:

Test App

Let's put together a test app, that allows us to modify existing angle properties, as well as the new ones; to test and demonstrate the new properties at runtime.  We can modify the test app that we've been using by adding a grid layout with 7 rows and two columns.  If you don't want to build a test app yourself, you can use the one from the Github repository.  In the left column, we have the label for each row and in the right, the value.  We can use a text component to display and change these values.  For now, we'll just use scripting to modify all values.

In the application startup event script (called "On Startup"), we can fill these text fields:


INPUTFIELD_STARTANGLE.setValue("" + SCNGAUGE_1.getStartAngleDeg());


INPUTFIELD_MAXENDANGLE.setValue("" + SCNGAUGE_1.getEndAngleDegMax());


INPUTFIELD_ENDANGLE.setValue("" + SCNGAUGE_1.getEndAngleDeg());


INPUTFIELD_MEASUREMINVAL.setValue("" + SCNGAUGE_1.getMeasureMin());


INPUTFIELD_MEASUREMAXVAL.setValue("" + SCNGAUGE_1.getMeasureMax());


INPUTFIELD_MEASUREVAL.setValue("" + SCNGAUGE_1.getMeasureVal());



var useMeasures = SCNGAUGE_1.getUseMeasures();



if (useMeasures == true){


  RADIOBUTTONGROUP_1.setSelectedValue("1");


} else {


  RADIOBUTTONGROUP_1.setSelectedValue("0");


}






We also want the gauge to be updated when the user edits a field within the grid.  Let's take one of these text elements - the start angle - as an example.  When the user changes the value, we'll execute the following script:


var asInt = Convert.stringToFloat(INPUTFIELD_STARTANGLE.getValue());


SCNGAUGE_1.setStartAngleDeg(asInt);






As the rest of the fields are similar, we'll not explicitly show the repetitive scripts here.

At runtime, we should be able to do the following with the app.  If it is set to *not* use measures, the pie chart should use the angles that we set directly.

When we switch to use measures, then the end angle that we directly input before is ignore and instead, a normalized end angle is calculated.

Next time, we'll actually add data binding.

1 Comment