This is part of a tutorial series on creating extension components for Design Studio.
Last time, we added guide lines to our raw html sandbox gauge. Now we're going to bring it into our component. We have a few things that we'll need to take care of:
- Since we won't always want to display the radial guide lines and guide arc, so we'll need to make them optional.
- In part 9, we'll introduce an indicator needle. When we do that, the colored arc won't be our only display option, so while we're at it, we'll add the main arc to the optional list.
- The guide ring has the same clockwise orientation as the main gauge arc and if the end angle is smaller than the start angle, it will need to be increased in 360 degree increments until we get a clockwise arc. You may recall from Part 6a how we solved this. We won't simply re-use the recalculateCurrentAngle() that we created there, as most of ist code is about measure handling. Instead, we introduce a new function, recalculateGuideRingAngles, that simply does the while (end < start) {//Increment;} part.
- The guide lines and arc need a color setting, as we may not want them to be the same color as the main gauge arc.
- The guide ring (arc) will need its own start and end angles.
- The guide ring and lines will need their own thickness setting.
- For the sake of simplicity, we'll presume that these new settings are the sort of thing that designers want to have precise control over at design time and don't need to be dynamic. Therefore, the new properties won't be added to the Design Studio script language and we won't need to change contribution.ztl.
- We'll add a new "Guide Lines" group in the properties pane, where we'll cluster the new properties.
Adding the Properties
We'll add the new group to contribution.xml
<group
id="SCNGaugeLineSettings"
title="Guide Lines"
tooltip="Guide Line Ring and Line Settings"/>
We'll add the 8 properties, that we defined above as requirements:
<property id="enableGuideLines" title="Enable Guide Lines" type="boolean" group="SCNGaugeLineSettings"/>
<property id="guideColorCode" title="Guide Line Color" type="Color" group="SCNGaugeLineSettings"/>
<property id="bracketThickness" title="Guide Line Thickness" type="int" group="SCNGaugeLineSettings"/>
<property id="enableGuideRing" title="Enable Guide Ring" type="boolean" group="SCNGaugeLineSettings"/>
<property id="ringColorCode" title="Guide Ring Color" type="Color" group="SCNGaugeLineSettings"/>
<property id="ringThickness" title="Guide Ring Thickness" type="int" group="SCNGaugeLineSettings"/>
<property id="ringStartAngleDeg" title="Guide Ring Start Angle" type="float" group="SCNGaugeLineSettings"/>
<property id="ringEndAngleDeg" title="Guide Ring End Angle" type="float" group="SCNGaugeLineSettings"/>
If this component were for production use, we'd be very defensive, checking for the presence of intiialized property values whenever we need them, etc. For the sake of brevity, we'll give the new properties default values. This is less reliable than actively checking for undefined property values, but also a lot less verbose. We'll default to guide ring/lines being blue, not being displayed, being 2 pixels wide and being a full circle (from 0 to 360 degrees).
<initialization>
...
<defaultValue property="enableGuideLines">false</defaultValue>
<defaultValue property="guideColorCode">blue</defaultValue>
<defaultValue property="bracketThickness">2</defaultValue>
<defaultValue property="enableGuideRing">false</defaultValue>
<defaultValue property="ringColorCode">blue</defaultValue>
<defaultValue property="ringThickness">2</defaultValue>
<defaultValue property="ringStartAngleDeg">0.0</defaultValue>
<defaultValue property="ringEndAngleDeg">360.0</defaultValue>
</initialization>
Adding the Guides to the Canvas (updating component.js)
Now comes the time for the rubber to meet the road. We've brought properties into canvas components many timas already, but just to review at a high level; we'll need:
- Local proxy copies of the new properties, initialized to the same values as what we initialize them to in contribution.xml, because property synchronization between the server and canvas/client only occurs when values are changed. ( http://scn.sap.com/community/businessobjects-design-studio/blog/2015/11/11/3c--the-dark-art-of-property-synchronization )
- Getter/setter functions for all of our new properties.
The local Proxies
me._enableArc = true;
//Part 8 Guide Lines
me._enableGuideLines = false;
me._enableGuideRing = false;
me._ringColorCode = 'blue';
me._ringThickness = 2;
me._bracketThickness = 2;
me._ringStartAngleDeg = 0.0;
me._ringEndAngleDeg = 360.0;
The new getter/setter functions:
me.enableArc = function(value) {
if (value === undefined) {
return me._enableArc;
} else {
me._enableArc = value;
me.redraw();
return me;
}
};
// Part 8
me.enableGuideLines = function(value) {
if (value === undefined) {
return me._enableGuideLines;
} else {
me._enableGuideLines = value;
me.redraw();
return this;
}
};
me.bracketThickness = function(value) {
if (value === undefined) {
return me._bracketThickness;
} else {
me._bracketThickness = value;
me.redraw();
return this;
}
};
me.guideColorCode = function(value) {
if (value === undefined) {
return me._guideColorCode;
} else {
me._guideColorCode = value;
me.redraw();
return this;
}
};
me.enableGuideRing = function(value) {
if (value === undefined) {
return me._enableGuideRing;
} else {
me._enableGuideRing = value;
me.redraw();
return this;
}
};
me.ringColorCode = function(value) {
if (value === undefined) {
return me._ringColorCode;
} else {
me._ringColorCode = value;
me.redraw();
return this;
}
};
me.ringThickness = function(value) {
if (value === undefined) {
return me._ringThickness;
} else {
me._ringThickness = value;
me.redraw();
return this;
}
};
me.ringStartAngleDeg = function(value) {
if (value === undefined) {
return me._ringStartAngleDeg;
} else {
me._ringStartAngleDeg = value;
me.redraw();
return this;
}
};
me.ringEndAngleDeg = function(value) {
if (value === undefined) {
return me._ringEndAngleDeg;
} else {
me._ringEndAngleDeg = value;
me.redraw();
return this;
}
};
// End Part 8 Properties
Specific to the new feature, we'll need:
- recalculateGuideRingAngles() moves into component.js and gets renamed me.recalculateGuideRingAngles()
- The drawing code moves into the me.redraw() function and we'll refactor the variables to work with the existing code in component.js. Variables from the html sandbox file that correspond to properties will get a "me._" prefix, width becomes me.$().width(), etc.
- The newly migrated and refactored code blocks for drawing the guides get wrapped inside if statements and only executed when the designer has decided to enable them.
The refactored recalculateGuideRingAngles () function:
//New with Part 8
me.recalculateGuideRingAngles = function(){
//The ring has no max angle or measures, so it is trivial to recalculate.
//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._ringEndAngleDeg < me._ringStartAngleDeg){
me._ringEndAngleDeg = me._ringEndAngleDeg + 360.0;
}
};
The me.redraw() function, after the newly refactored guide drawing code goes in:
//Part 8 - The guide lines
///////////////////////////////////////////
//Lets build a border ring around the gauge
///////////////////////////////////////////
if (me._enableGuideLines == true){
var visRing = d3.select(myDiv).append("svg:svg").attr("width", "100%").attr("height", "100%");
var ringOuterRad = me._outerRad + ( -1 * me._ringThickness); //Outer ring starts at the outer radius of the inner arc
var ringArcDefinition = d3.svg.arc()
.innerRadius(me._outerRad)
.outerRadius(ringOuterRad)
.startAngle(me._ringStartAngleDeg * (pi/180)) //converting from degs to radians
.endAngle(me._ringEndAngleDeg * (pi/180)) //converting from degs to radians
var ringArc = vis
.append("path")
.attr("d", ringArcDefinition)
.attr("fill", me._ringColorCode)
.attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")");
}
///////////////////////////////////////////
//Lets build a the start and end lines
///////////////////////////////////////////
if (me._enableGuideRing == true){
var visStartBracket = d3.select(myDiv).append("svg:svg").attr("width", "100%").attr("height", "100%");
var lineData = [endPoints (me._outerRad, me._ringStartAngleDeg), {x:me._offsetLeft, y:me._offsetDown}, endPoints (me._outerRad, me._ringEndAngleDeg)];
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
var borderLines = vis
.attr("width", me.$().width()).attr("height", me.$().height()) // Added height and width so line is visible
.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", me._ringColorCode)
.attr("stroke-width", me._bracketThickness)
.attr("fill", "none");
}
The Guide Lines and Arc in Action
As always, the extension project and test application are in a repository on Github. Next, time, we'll start adding an indicator needle.