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
Contributor
0 Kudos

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

In the last instalment, we investigated adding a two component indicator needle to our gauge; at least in the sandbox html file.  Now we're going to update the component.  As usual, the process follows the following steps:

  1. Determine which variables from our sandbox code need to become properties (either visible in the properties pane or hidden) and which can remain as local variables in the component.js file.
  2. Add those properties to contribution.xml
  3. If we are adding a group to the properties pane, we need to declare it in contribution.xml and make sure that the relevant properties get added to it.  To keep our properties pane organized, we be adding a new "Indicator Needle" group and all of the new properties will go there.
  4. Create getters and setters for those properties
  5. Create ztl functions for anything that we're adding to the Design Studio script API.  In this case, we'll leave the properties as design time only.  Conceivably, we could make the needle and base pin shapes flexible and capable of being manipulated via scripting, but the use case is not compelling enough to add the additional complexity to the tutorial.
  6. The actual drawing code gets copied over and refactored to use the property variables, where applicable.

We added  large number of variables in Part 9a.  Of these, a dozen make sense as properties.  We also won't want to leave any of these properties uninitialized.

Contribution.xml

In the contribution.xml file, we're adding the "Indicator Needle" group, the properties in the table above and their initialization values.

PropertyTypeDefault Value
enableIndicatorNeedlebooleanfalse
enableIndicatorNeedleTailbooleanfalse
fillNeedlebooleanfalse
needleColorCodestring'black'
needleWidthint10
needleHeadLengthint100
needleTailLengthint10
needleLineThicknessint2
enableIndicatorNeedleBasebooleanfalse
fullBasePinRingbooleanfalse
fillNeedlaBasePinbooleanfalse
needleBaseRadiusbooleanfalse

The new group declaration:


  <group


  id="SCNGaugeNeedleSettings"


  title="Indicator Needle"


  tooltip="Gauge Indicator Needle Settings"/>







The property declarations:


<property id="enableIndicatorNeedle" title="Enable Indicator Needle" type="boolean" group="SCNGaugeNeedleSettings"/>


<property id="enableIndicatorNeedleTail"


  title="Enable Indicator Needle Tail"


  type="boolean"


  tooltip="Enable the tail on the the indicator needle and make it a diamond"


  group="SCNGaugeNeedleSettings"/>


<property id="fillNeedle"


  title="Fill Indicator Needle"


  type="boolean"


  tooltip="Enable color fill on the indicator needle"


  group="SCNGaugeNeedleSettings"/>


<property id="needleColorCode"


  title="Needle Color"


  type="Color"


  tooltip="Needle Color (outline and fill of indicator needle and base pin)"


  group="SCNGaugeNeedleSettings"/>


<property id="needleWidth"


  title="Indicator Needle Width"


  type="int"


  tooltip="Base width of the indicator needle, as a percentage of the gauge radius"


  group="SCNGaugeNeedleSettings"/>


<property id="needleHeadLength"


  title="Indicator Needle Length"


  type="int"


  tooltip="Length of the indicator needle, as a percentage of the gauge radius"


  group="SCNGaugeNeedleSettings">


  <!-- <possibleValue>1</possibleValue>  -->


</property>


<property id="needleTailLength"


  title="Indicator Needle Tail Length"


  type="int"


  tooltip="Tail Length of the indicator needle, as a percentage of the gauge radius"


  group="SCNGaugeNeedleSettings"/>


<property id="needleLineThickness"


  title="Indicator Line Thickness"


  type="int"


  tooltip="Thickness of the lines used to draw the indicator needle and base pin"


  group="SCNGaugeNeedleSettings"/>


<property id="enableIndicatorNeedleBase"


  title="Enable Base Pin"


  type="boolean"


  tooltip="Enable the base pin (circle) on the indicator needle"


  group="SCNGaugeNeedleSettings"/>


<property id="fullBasePinRing"


  title="360° Base Pin"


  type="boolean"


  tooltip="Enable a full 360 degree base pin circle.  Disabling this results in a 180 degree arc on the needle tail"


  group="SCNGaugeNeedleSettings"/>


<property id="fillNeedlaBasePin"


  title="Fill Base Pin"


  type="boolean"


  tooltip="Fill the base pin, with the indicator needle fill color"


  group="SCNGaugeNeedleSettings"/>


<property id="needleBaseWidth"


  title="Base Pin Width"


  type="int"


  tooltip="Diameter (as a % of main arc radius) of the base pin"


  group="SCNGaugeNeedleSettings"/>







The new initialization default values:


<initialization>


        …


        <defaultValue property="enableIndicatorNeedle">false</defaultValue>


        <defaultValue property="enableIndicatorNeedleTail">false</defaultValue>


        <defaultValue property="fillNeedle">false</defaultValue>


        <defaultValue property="needleColorCode">black</defaultValue>


        <defaultValue property="needleWidth">10</defaultValue>


        <defaultValue property="needleHeadLength">100</defaultValue>


        <defaultValue property="needleTailLength">10</defaultValue>


        <defaultValue property="needleLineThickness">2</defaultValue>


        <defaultValue property="enableIndicatorNeedleBase">false</defaultValue>


        <defaultValue property="fullBasePinRing">false</defaultValue>


        <defaultValue property="fillNeedlaBasePin">false</defaultValue>


        <defaultValue property="needleBaseWidth">20</defaultValue>


</initialization>







Property Proxies and Getter/Setters

As usual, our property proxy values ( the "me._" variables) get declared at the head of the component.js file.


me._enableIndicatorNeedle = false;


me._enableIndicatorNeedleTail = false;


me._fillNeedle = false;


me._needleColorCode = 'black';


me._needleWidth = 10;


me._needleHeadLength = 100;


me._needleTailLength = 10;


me._needleLineThickness = 2;


me._enableIndicatorNeedleBase = false;


me._fullBasePinRing = false;


me._fillNeedlaBasePin = false;


me._needleBaseRadius = false;







And each of these will need a getter/setter for property synchronization to work.  Don't forget that all of these properties affect display and if any is changed, we need to trigger a redraw:


//Step 9


me.enableIndicatorNeedle = function(value) {


  if (value === undefined) {


  return me._enableIndicatorNeedle;


  } else {


  me._enableIndicatorNeedle = value;


  me.redraw();


  return me;


  }


};




me.enableIndicatorNeedleTail = function(value) {


  if (value === undefined) {


  return me._enableIndicatorNeedleTail;


  } else {


  me._enableIndicatorNeedleTail = value;


  me.redraw();


  return me;


  }


};




me.fillNeedle = function(value) {


  if (value === undefined) {


  return me._fillNeedle;


  } else {


  me._fillNeedle = value;


  me.redraw();


  return me;


  }


};




me.needleColorCode = function(value) {


  if (value === undefined) {


  return me._needleColorCode;


  } else {


  me._needleColorCode = value;


  me.redraw();


  return me;


  }


};




me.needleWidth = function(value) {


  if (value === undefined) {


  return me._needleWidth;


  } else {


  me._needleWidth = value;


  me.redraw();


  return me;


  }


};




me.needleHeadLength = function(value) {


  if (value === undefined) {


  return me._needleHeadLength;


  } else {


  me._needleHeadLength = value;


  me.redraw();


  return me;


  }


};




me.needleTailLength = function(value) {


  if (value === undefined) {


  return me._needleTailLength;


  } else {


  me._needleTailLength = value;


  me.redraw();


  return me;


  }


};




me.needleLineThickness = function(value) {


  if (value === undefined) {


  return me._needleLineThickness;


  } else {


  me._needleLineThickness = value;


  me.redraw();


  return me;


  }


};




me.enableIndicatorNeedleBase = function(value) {


  if (value === undefined) {


  return me._enableIndicatorNeedleBase;


  } else {


  me._enableIndicatorNeedleBase = value;


  me.redraw();


  return me;


  }


};




me.fullBasePinRing = function(value) {


  if (value === undefined) {


  return me._fullBasePinRing;


  } else {


  me._fullBasePinRing = value;


  me.redraw();


  return me;


  }


};




me.fillNeedlaBasePin = function(value) {


  if (value === undefined) {


  return me._fillNeedlaBasePin;


  } else {


  me._fillNeedlaBasePin = value;


  me.redraw();


  return me;


  }


};




me.needleBaseRadius = function(value) {


  if (value === undefined) {


  return me._needleBaseRadius;


  } else {


  me._needleBaseRadius = value;


  me.redraw();


  return me;


  }


};







The Drawing Code

Once we have everything else in place, we can copy/paste the 110 lines of drawing code to componet.js's redraw() function and refactor the variables that are properties to use the proxy values.


///////////////////////////////////////////


//Lets add the indicator needle


///////////////////////////////////////////




if (me._enableIndicatorNeedle == true){


  var needleWaypointOffset = me._needleWidth/2;




  //needleWaypoints is defined with positive y axis being up


  //The initial definition of needleWaypoints is for a full diamond, but if me._enableIndicatorNeedleTail is false, we'll abbreviate to a chevron


  var needleWaypoints = [{x: 0,y: me._needleHeadLength}, {x: needleWaypointOffset,y: 0}, {x: 0,y: (-1*me._needleTailLength)}, {x: (-1*needleWaypointOffset),y: 0}, {x: 0,y: me._needleHeadLength}]


  if (me._enableIndicatorNeedleTail == false){


  if (me._fillNeedle == false){


  //If we have no tail and no fill then there is no need to close the shape.


  //Leave it as an open chevron


  needleWaypoints = [{x: needleWaypointOffset,y: 0}, {x: 0,y: me._needleHeadLength}, {x: (-1*needleWaypointOffset),y: 0}];


  }


  else {


  //There is no tail, but we are filling the needle.


  //In this case, draw it as a triangle


  needleWaypoints = [{x: 0,y: me._needleHeadLength}, {x: needleWaypointOffset,y: 0}, {x: (-1*needleWaypointOffset),y: 0}, {x: 0,y: me._needleHeadLength}]


  }




  }




  //we need to invert the y-axis and scale the indicator to the gauge.


  //  If Y = 100, then that is 100% of outer radius.  So of Y = 100 and outerRad = 70, then the scaled Y will be 70.


  var needleFunction = d3.svg.line()


  .x(function(d) { return (d.x)*(outerRad/100); })


  .y(function(d) { return -1*(d.y)*(outerRad/100); })


  .interpolate("linear");




  //Draw the needle, either filling it in, or not


  var needleFillColorCode = me._needleColorCode;


  if (me._fillNeedle == false){


  needleFillColorCode = "none";


  }



  //Draw the needle


  var needle = vis


  .append("g")


  .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")


  .append("path")


  .attr("class", "tri")


  .attr("d", needleFunction(needleWaypoints))


  .attr("stroke", me._needleColorCode)


  .attr("stroke-width", me._needleLineThickness)


  .attr("fill", needleFillColorCode);






  //Arcs are in radians, but rotation transformations are in degrees.  Kudos to D3 for consistency


  needle.attr("transform", "rotate(" + endAngleDeg + ")");


}






///////////////////////////////////////////


//Lets add a needle base pin


///////////////////////////////////////////




var me._enableIndicatorNeedleBase = true;


var me._fullBasePinRing = true;


var me._fillNeedlaBasePin = true;


var me._needleBaseRadius = 20;




if (me._enableIndicatorNeedleBase == true){


  // Like the rest of the needle, the size of the pin is defined relative to the main arc, as a % value


  var needleIBasennerRadius = (me._needleBaseRadius/2)*(outerRad/100) - (me._needleLineThickness/2);


  var needleBaseOuterRadius = needleIBasennerRadius + me._needleLineThickness;


  if (me._fillNeedlaBasePin == true){


  needleIBasennerRadius = 0.0;


  }





  // The pin will either be a 180 degree arc, or a 360 degree ring; starting from the 9 O'clock position.


  var needleBaseStartAngle = 90.0;


  var needleBaseEndAngle = 270.0;


  if (me._fullBasePinRing == true){


  needleBaseEndAngle = 450.0;


  }




  //Don't let the arc have a negative length


  if (needleBaseEndAngle < needleBaseStartAngle){


  needleBaseEndAngle = needleBaseStartAngle;


  alert("End angle of outer ring may not be less than start angle!");


  }




  //Transform the pin ring


  var nbTransformedStartAngle = needleBaseStartAngle + endAngleDeg;


  var nbTransformedEndAngle = needleBaseEndAngle + endAngleDeg;




  var pinArcDefinition = d3.svg.arc()


  .innerRadius(needleIBasennerRadius)


  .outerRadius(needleBaseOuterRadius)


  .startAngle(nbTransformedStartAngle * (pi/180)) //converting from degs to radians


  .endAngle(nbTransformedEndAngle * (pi/180)) //converting from degs to radians




  var pinArc = vis


  .append("path")


  .attr("d", pinArcDefinition)


  .attr("fill", me._needleColorCode)


  .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")");


}







Once these go into place, you can use the properties pane to enable the indicator needle and configure it.

As usual, the current state of the component and a test app are in a Github repository.