This is part of a tutorial series on creating extension components for Design Studio.
Up to now, our gauge has been a colored arc and that's it. Our emphasis has been on Design Studio specific concepts. With this set of installments, we're going to start cleaning up and refining the component for professional use. We'll be adding guide lines, a pointer, animations and text decorations.
Part 8 will be about the guide lines. As in the early parts of this series, we're going to step out of Design Studio and investigate drawing the guide lines with raw HTML and D3, before we add them into our component.
What you see above is the goal of this installment. We're going to draw three guide lines. The first is a circular line at the outer radius; having its own start and end angles. This could be used to outline the gauge, even when no arc is present (e.g. if it were empty or we were using only a pointer. The other two start at the origin and follow the start angle and end angle-max.
Drawing the Ring Guide Line
What we're going to do here is draw a second arc, starting at outerRad and with a given thickness. It will have its own start and end angles. Effectively, it's a rehash of what we did waaay back, in Part 2a. ( http://scn.sap.com/community/businessobjects-design-studio/blog/2015/10/06/part-2a--your-first-steps-with-d3 )
/////////////////////////////////////////// //Lets build a border ring around the gauge
///////////////////////////////////////////
var visRing = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var ringThickness = 2;
var ringOuterRad = outerRad + ringThickness; //Outer ring starts at the outer radius of the inner arc
var ringColorCode = "black";
var ringStartAngleDeg = 0;
var ringEndAngleDeg = 360;
//Don't let the arc have a negative length
if (ringEndAngleDeg < ringStartAngleDeg){
ringEndAngleDeg = ringStartAngleDeg; alert("End angle of outer ring may not be less than start angle!"); }
var ringArcDefinition = d3.svg.arc()
.innerRadius(outerRad) .outerRadius(ringOuterRad) .startAngle(ringStartAngleDeg * (pi/180)) //converting from degs to radians .endAngle(ringEndAngleDeg * (pi/180)) //converting from degs to radians
var ringArc = vis
.append("path") .attr("d", ringArcDefinition) .attr("fill", ringColorCode) .attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")");
Drawing the Start and End Guide Lines
Again, we are rehashing what we already covered in Part4b ( http://scn.sap.com/community/businessobjects-design-studio/blog/2015/12/08/your-first-extension-part-4b--the-positioning-visualizer ). There, we used a line accessor function to visualize the padding boxes by drawing them. We'll do the same here. What's different is that the start and end points will need to be calculated, so rather than the lineData being a simple array of x/y value pairs, it will be built dynamically. It will use the line length (i.e. the radius of the gauge; outerRad) and the angle (either start or end) and use a bit of trigonometry to get the x and y points.
The line data is defined by the counterpoint and the points on the outer radius, where radial lines from the angle would intersect. We'll use a helper function, endPoints(), to to the trigonometric calculation. The looking at the function, you see that the brush stroke of the radial line on start angle actually starts at the ring and goes to the centre and then goes back out to the ring at end angle.
var lineData = [endPoints (outerRad, startAngleDeg), {x:offsetLeft, y:offsetDown}, endPoints (outerRad, endAngleDeg)];
//Helper function
function endPoints (lineLength, lineAngle){
var endX = offsetLeft - (lineLength * Math.sin(lineAngle * (pi/180)));
var endY = offsetDown - (lineLength * Math.cos(lineAngle * (pi/180)));
return {x:endX, y:endY}
}
Using these two to help draw the radial lines:
///////////////////////////////////////////
//Lets build a the start and end lines
///////////////////////////////////////////
var bracketThickness = 2;
var lineData = [endPoints (outerRad, startAngleDeg), {x:offsetLeft, y:offsetDown}, endPoints (outerRad, endAngleDeg)];
var visStartBracket = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
var borderLines = vis
.attr("width", width).attr("height", height) // Added height and width so line is visible
.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", ringColorCode)
.attr("stroke-width", bracketThickness)
.attr("fill", "none");
//Helper function
function endPoints (lineLength, lineAngle){
var endX = offsetLeft - (lineLength * Math.sin(lineAngle * (pi/180)));
var endY = offsetDown - (lineLength * Math.cos(lineAngle * (pi/180)));
return {x:endX, y:endY}
Putting it all together in a test webpage
That's it. Next time, we'll adapt these features to the gauge component as it now stands.
<!DOCTYPE html>
<html>
<head>
<meta http-equiv='X-UA-Compatible' content='IE=edge' />
<title>Part 4</title>
<div id='content'></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js" charset="utf-8"></script>
<script>
var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var pi = Math.PI;
//Viz definitiions
var innerRad = 0;
//var outerRad = 70;
var width = 200;
var height = 200;
var startAngleDeg = -45;
var endAngleDeg = 45;
var colorCode = "red";
//Outer Dimensions & Positioning
var paddingTop = 10;
var paddingBottom = 10;
var paddingLeft = 10;
var paddingRight = 10;
//The total size of the component is calculated from its parts
// Find the larger left/right padding
var lrPadding = paddingLeft + paddingRight;
var tbPadding = paddingTop + paddingBottom;
var maxPadding = lrPadding;
if (maxPadding < tbPadding){
maxPadding = tbPadding
}
var outerRad = (width - 2*(maxPadding))/2;
//var width = (outerRad * 2) + paddingLeft + paddingRight;
//var height = (outerRad * 2) + paddingTop + paddingBottom;
//The offset will determine where the center of the arc shall be
var offsetLeft = outerRad + paddingLeft;
var offsetDown = outerRad + paddingTop;
//Don't let the arc have a negative length
if (endAngleDeg < startAngleDeg){
endAngleDeg = startAngleDeg;
alert("End angle may not be less than start angle!");
}
var arcDef = d3.svg.arc()
.innerRadius(innerRad)
.outerRadius(outerRad)
.startAngle(startAngleDeg * (pi/180)) //converting from degs to radians
.endAngle(endAngleDeg * (pi/180)); //converting from degs to radians
var guageArc = vis.append("path")
.style("fill", colorCode)
.attr("width", width).attr("height", height) // Added height and width so arc is visible
.attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")
.attr("d", arcDef);
///////////////////////////////////////////
//Lets build a border ring around the gauge
///////////////////////////////////////////
var visRing = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var ringThickness = 2;
var ringOuterRad = outerRad + ringThickness; //Outer ring starts at the outer radius of the inner arc
var ringColorCode = "black";
var ringStartAngleDeg = 0;
var ringEndAngleDeg = 360;
//Don't let the arc have a negative length
if (ringEndAngleDeg < ringStartAngleDeg){
ringEndAngleDeg = ringStartAngleDeg;
alert("End angle of outer ring may not be less than start angle!");
}
var ringArcDefinition = d3.svg.arc()
.innerRadius(outerRad)
.outerRadius(ringOuterRad)
.startAngle(ringStartAngleDeg * (pi/180)) //converting from degs to radians
.endAngle(ringEndAngleDeg * (pi/180)) //converting from degs to radians
var ringArc = vis
.append("path")
.attr("d", ringArcDefinition)
.attr("fill", ringColorCode)
.attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")");
///////////////////////////////////////////
//Lets build a the start and end lines
///////////////////////////////////////////
var bracketThickness = 2;
var lineData = [endPoints (outerRad, startAngleDeg), {x:offsetLeft, y:offsetDown}, endPoints (outerRad, endAngleDeg)];
var visStartBracket = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var lineFunction = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
var borderLines = vis
.attr("width", width).attr("height", height) // Added height and width so line is visible
.append("path")
.attr("d", lineFunction(lineData))
.attr("stroke", ringColorCode)
.attr("stroke-width", bracketThickness)
.attr("fill", "none");
//Helper function
function endPoints (lineLength, lineAngle){
var endX = offsetLeft - (lineLength * Math.sin(lineAngle * (pi/180)));
var endY = offsetDown - (lineLength * Math.cos(lineAngle * (pi/180)));
return {x:endX, y:endY}
}
</script>
</head>
<body class='sapUiBody'>
<div id='content'></div>
</body>
</html>