This is part of a tutorial series on creating extension components for Design Studio.
In this installment, we'll be constructing the basic positioning visualizer JavaScript code. As we did with the initial arc definition in Part 2a and the initial variable controlled arc definition in Part 3a, we'll first use raw html5 as a sandbox. The positioning visualizer that we're going to build for our gauge component will consist of the following:
The visualizer will look something like this:
In order to construct it in D3, we'll break the visualizer down into its constituent components. In terms of raw shapes, we have the following:
Constructing the basic Javascript for drawing the positioning visualizer
For our APS Javascript, we'll be using the four padding properties. In the actual component, we'll be following the pattern of me._<propertyName> for the local copies of these four properties. In in sandbox, we'll dispense with the me. Prefix, but we'll keep the underscore. The padding sizes are defined as follows:
//Outer Dimensions & Positioning
_paddingTop = 0;
_paddingBottom = 0;
_paddingLeft = 0;
_paddingRight = 0
We'll define the stroke thicknesses and set a variable for the height and width. We're not following the underscore convention, even though height and width are properties. They are not accessible via the normal methods however. We'll cover synching them later, but for now we'll just define variables for height and width.
//Viz definitiions
var lineThickness = 2;
//Height and Width Proxies
widthProxy = 200;
heightProxy = 200;
Next, we clear any SVG elements from the HTML file's content div and re-insert one. While we're at it, we'll declare the PI variable
// Clear any existing content. We'll redraw from scratch
d3.select("#content").selectAll("*").remove();
var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var pi = Math.PI;
Drawing the Gauge Dummy
Now we'll actually start drawing the visualizer itself, starting with the gauge dummy. The code should be familiar by now. We determine what the largest padding value is and combine with the smaller of height and width to calculate the outer radius. When we draw the "gauge", we'll draw a black 360° circle (from -180° to +180°).
//Determing the position of the gauge dummy (black circle)
// Find the larger left/right padding
var lrPadding = _paddingLeft + _paddingRight;
var tbPadding = _paddingTop + _paddingBottom;
var maxPadding = lrPadding;
if (maxPadding < tbPadding){
maxPadding = tbPadding
}
//Do the same with the overall height and width
var smallerAxis = heightProxy;
if (widthProxy < smallerAxis){
smallerAxis = widthProxy
}
var outerRad = (smallerAxis - 2*(maxPadding))/2;
//The offset will determine where the center of the arc shall be
var offsetLeft = outerRad + _paddingLeft;
var offsetDown = outerRad + _paddingTop;
//The black Circle
var arcDef = d3.svg.arc()
.innerRadius(0)
.outerRadius(outerRad)
.startAngle(-180 * (pi/180)) //converting from degs to radians
.endAngle(180 * (pi/180)); //converting from degs to radians
var guageDummy = vis.append("path")
.style("fill", "black")
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so arc is visible
.attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")
.attr("d", arcDef);
Drawing the Line Strokes
When we define our paths, we'll be defining them via a set of x/y coordinates and then D3 will draw its "pencil stokes" between them. The waypoint "data" of a line is a list of Javascript Objects; each with a pair of x and y coordinate properties. Strictly speaking, the properties don't have to be called "x" and "y". We could also call them "fred" and "frank", but "x" and "y" are self descriptive, and we'll stick with them.
Below is an example Line Data list. It defines an outer box - the component outline - for the component. It:
[
{"x":0, "y":0},
{"x": widthProxy, "y":0},
{"x": widthProxy, "y":heightProxy},
{"x":0, "y":heightProxy},
{"x":0, "y":0}
];
We can now define all of our lines. When we draw the padding boxes, keep in mind that each is anchored on the appropriate side of the component outline. The crosshairs a s defined by two strokes, one from the top center of the circle, down to the bottom center. The other goes from the left middle, to the right middle.
var lineDataOuter = [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];
var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":_paddingLeft, "y":0}, {"x":_paddingLeft, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];
var lineDataPaddingRight = [{"x":( widthProxy - _paddingRight), "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":0}];
var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":_paddingTop}, {"x":0, "y":_paddingTop}, {"x":0, "y":0}];
var lineDataPaddingLower = [{"x":0, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":(heightProxy - _paddingBottom)}];
var lineDataCrosshairsHorizontal = [{"x":_paddingLeft, "y":(_paddingTop + outerRad) }, {"x":(_paddingLeft + 2*outerRad), "y":(_paddingTop + outerRad) }];
var lineDataCrosshairsVertical = [{"x":(_paddingLeft + outerRad), "y":_paddingTop }, {"x":(_paddingLeft + outerRad), "y":(_paddingTop + 2*outerRad) }];
The Line Accessor
In between the coordinates that we just defined, D3 is going to do something called interpolation; effectively filling in the blanks to convert your waypoint coordinates to SVG paths. There is an excellent overview of SVG paths in D3 at DashingD3.com if you are interested. We'll simply use the "cookbook version" of a linear interpolator; one that consumes waypoint data defined in terms of X and Y coordinates and draws straight lines between them.
//Line Accessor Function
var lineAccessor = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
Adding the boxes
The lines of the boxes are drawn just like the gauge arc, by appending the path defined by the data and line accessor to the vis svg element. There are a couple of minor differences:
To add the component outline to the svg element, our code would look like this:
var borderLinesOuter = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataOuter))
.attr("stroke", "black")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
For all lines, it would look like this:
var borderLinesPaddingLeft = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingLeft))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesPaddingRight = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingRight))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesPaddingUpper = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingUpper))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesPaddingLower = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingLower))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesOuter = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataOuter))
.attr("stroke", "black")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesCrosshairHorizontal = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataCrosshairsHorizontal))
.attr("stroke", "white")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesCrosshairVertical = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataCrosshairsVertical))
.attr("stroke", "white")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
The completed html file look like this:
<!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>
//Outer Dimensions & Positioning
_paddingTop = 0;
_paddingBottom = 0;
_paddingLeft = 0;
_paddingRight = 0;
//Viz definitiions
var lineThickness = 2;
//Height and Width Proxies
widthProxy = 200;
heightProxy = 200;
// Clear any existing content. We'll redraw from scratch
d3.select("#content").selectAll("*").remove();
var vis = d3.select("#content").append("svg:svg").attr("width", "100%").attr("height", "100%");
var pi = Math.PI;
///////////////////////////////////////////
//Gauge Dummy
///////////////////////////////////////////
//Determing the position of the gauge dummy (black circle)
// Find the larger left/right padding
var lrPadding = _paddingLeft + _paddingRight;
var tbPadding = _paddingTop + _paddingBottom;
var maxPadding = lrPadding;
if (maxPadding < tbPadding){
maxPadding = tbPadding
}
//Do the same with the overall height and width
var smallerAxis = heightProxy;
if (widthProxy < smallerAxis){
smallerAxis = widthProxy
}
var outerRad = (smallerAxis - 2*(maxPadding))/2;
//The offset will determine where the center of the arc shall be
var offsetLeft = outerRad + _paddingLeft;
var offsetDown = outerRad + _paddingTop;
//The black Circle
var arcDef = d3.svg.arc()
.innerRadius(0)
.outerRadius(outerRad)
.startAngle(-180 * (pi/180)) //converting from degs to radians
.endAngle(180 * (pi/180)); //converting from degs to radians
var guageDummy = vis.append("path")
.style("fill", "black")
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so arc is visible
.attr("transform", "translate(" + offsetLeft + "," + offsetDown + ")")
.attr("d", arcDef);
///////////////////////////////////////////
//Line Data
///////////////////////////////////////////
var lineDataOuter = [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];
var lineDataPaddingLeft = [{"x":0, "y":0}, {"x":_paddingLeft, "y":0}, {"x":_paddingLeft, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":0}];
var lineDataPaddingRight = [{"x":( widthProxy - _paddingRight), "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":heightProxy}, {"x":( widthProxy - _paddingRight), "y":0}];
var lineDataPaddingUpper= [{"x":0, "y":0}, {"x": widthProxy, "y":0}, {"x": widthProxy, "y":_paddingTop}, {"x":0, "y":_paddingTop}, {"x":0, "y":0}];
var lineDataPaddingLower = [{"x":0, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":(heightProxy - _paddingBottom)}, {"x": widthProxy, "y":heightProxy}, {"x":0, "y":heightProxy}, {"x":0, "y":(heightProxy - _paddingBottom)}];
var lineDataCrosshairsHorizontal = [{"x":_paddingLeft, "y":(_paddingTop + outerRad) }, {"x":(_paddingLeft + 2*outerRad), "y":(_paddingTop + outerRad) }];
var lineDataCrosshairsVertical = [{"x":(_paddingLeft + outerRad), "y":_paddingTop }, {"x":(_paddingLeft + outerRad), "y":(_paddingTop + 2*outerRad) }];
//Line Accessor Function
var lineAccessor = d3.svg.line()
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.interpolate("linear");
var borderLinesPaddingLeft = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingLeft))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesPaddingRight = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingRight))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesPaddingUpper = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingUpper))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesPaddingLower = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataPaddingLower))
.attr("stroke", "blue")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesOuter = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataOuter))
.attr("stroke", "black")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesCrosshairHorizontal = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataCrosshairsHorizontal))
.attr("stroke", "white")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
var borderLinesCrosshairVertical = vis
.attr("width", widthProxy).attr("height", heightProxy) // Added height and width so line is visible
.append("path")
.attr("d", lineAccessor(lineDataCrosshairsVertical))
.attr("stroke", "white")
.attr("stroke-width", lineThickness)
.attr("fill", "none");
</script>
</head>
<body class='sapUiBody'>
</body>
</html>
And at this is what we see in the browser, if the padding values are all zero.
As always, the completed extension (as of part 4) is available as a Github repository.
Next time, we'll put this into the APS window.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
24 | |
11 | |
10 | |
8 | |
8 | |
7 | |
6 | |
6 | |
5 | |
5 |