Quantcast
Channel: SAP BusinessObjects Design Studio
Viewing all articles
Browse latest Browse all 662

Your First Extension: Part 4b - The Positioning Visualizer

$
0
0

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:

 

  • It will represent the component at a 1:1 scale.

 

  • It will draw a black rectangle, indicating the borders of the component

 

  • It will draw the padding margins as blue lines, within the component.

 

  • It will represent the potential arc of the gauge (from -180° to +180°) as a black circle.

 

  • This circle will be positioned as the actual gauge within the canvas, allowing the designer to see where the padding margins are and how these padding margins affect the size and positioning of the gauge.

 

  • There will be white crosshairs, centered on the centroid of the circle, allowing the designer to easily see there the origin of the arc is.

 

 

The visualizer will look something like this:

4a.1.png

 

 

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:

 

  • 4 blue rectangles.  D3 allows us to draw a rectangle, but we won't use that feature, as it is filled in by default.  Instead, we'll use paths and draw each rectangle by tracing through each of the corners and returning to the original, as if we were drawing with a pencil.

 

  • 1 black rectangle, drawn the same way.

 

  • The crosshairs, which will be drawn as two lines.

 

  • The circle, which will be drawn the same way as we've been drawing the gauge; as an arc.  The color will be fixed to black and the start/end angles will be -180° and 180° respectively.

 

  • We'll use a consistent line stroke thickness of two pixels.

 

4a.2.png

 

 

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:

  1. Starts at the upper left; 0,0
  2. Moves across to the upper right corner; widthProxy,0
  3. Moves down to the lower right corner; widthProxy, heightProxy
  4. Moves across to the lower left corner; 0, heightProxy
  5. Returns to the starting position in the upper left; 0,0

[

  {"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:

  • When we defined the path for the gauge arc, we created a d3.svg.arc() instance.  This time, we'll be filling the "d" attribute with the results of a lineAccessor() function.  The input property of the accessor will be our lne data.

 

  • We'll use an empty fill.

 

  • Paths can have stroke colors and widts attributes.  We will assign values to these attributes.

 

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.

4b.1.png

 

 

 

 

Next time, we'll put this into the APS window.


Viewing all articles
Browse latest Browse all 662

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>