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
Product and Topic Expert
Product and Topic Expert
0 Kudos
1,025

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

In the last installment, we introduce an indicator needle into our gauge component.  Now we're ready to start working on refinement.  One of the refinements that we're going to add to our component is animation.  In the next set of installments, we'll animate the gauge arc and needle in the sandbox html file and then enable the animations in the SDK extension itself.

But first, we have to re-examine how we've defined our geometry.

The "Data" in Data Driven Documents

Up until now, we've been doing something that works under normal circumstances, but is not conformant to the philosophy behind D3 and will break animations.  We've been defining geometry attributes directly.  Let's take a look at the gauge arc as we originally defined it; all the way back in Part 2b.


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);




In the above code, we define an svg arc and then assign it to the "d" attribute of the path that we add to our visualization.  Note that we are setting .startAngle and .endAngle directly.  This runs counter to the D3 philosophy and letting the data do the talking.   So what's wrong with the above code?  It works after all and it has so for the past 18 installments and why should we care if it does not follow the D3 philosophy?  It is blocking off some key capabilities of D3, capabilities that we've not investigated up until now.  Let's take a look at the following statement from the D3 documentation.


Computed properties often refer to bound data. Data is specified as an array of values, and each value is passed as the first argument (d) to selection functions. With the default join-by-index, the first element in the data array is passed to the first node in the selection, the second element to the second node, and so on. For example, if you bind an array of numbers to paragraph elements, you can use these numbers to compute dynamic font sizes:



d3.selectAll("p")


    .data([4, 8, 15, 16, 23, 42])


    .style("font-size", function(d) { return d + "px"; });




So by adding data, we can then make our properties - or even the total count of svg elements - data driven.  In the above snippet, we see that we are setting the font size, based on the data.  In the next installments, we're going to be altering existing svg element properties, using D3's transition machinery, but in order to make this possible, we're going to need to remove our direct angle property assignments and replace them with data.  This way, D3 can use the data to make dynamic property value assignments.  So we're going to make a change to what it should have looked like:


var arcDef = d3.svg.arc()


  .innerRadius(innerRad)


  .outerRadius(outerRad);



// Add the foreground arc in orange, currently showing 12.7%.


var guageArc = vis.append("path")


  .datum({endAngle: startAngleDeg * (pi/180), startAngle: startAngleDeg * (pi/180)})


  .style("fill", "orange")


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


  .attr("d", arcDef);




Note that we no longer define start and end in the arc definition, but rather leave it malleable.  Instead, we define the .datum element and set it to contain our data.  We'll be leaving the direct definitions of the radii in place, though in principle, we could have moved them to data as well.  One line of code is all that need sot change.  The SVG element is now data bound.  The base pin of the needle is also an arc, so we can follow the same pattern for it as well.

Instead of:


//Base Pin



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", needleColorCode)


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




We have:


//Base Pin



var pinArcDefinition = d3.svg.arc()


  .innerRadius(needleIBasennerRadius)


  .outerRadius(needleBaseOuterRadius);



var pinArc = vis.append("path")


  .datum({endAngle: nbTransformedEndAngle * (pi/180), startAngle: nbTransformedStartAngle * (pi/180)})


  .attr("d", pinArcDefinition)


  .attr("fill", needleColorCode)


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




The needle itself is a bit different.  Whereas with the arcs, we're defining  a D3 arc separately and then assigning it to the SVG element as a property of the path, with the line, we're drawing the line when we declare it.

Instead of:


//needleWaypoints is defined with positive y axis being up


var needleWaypoints = [{x: 0,y: 100}, {x: 10,y: 0}, {x: 0,y: -10}, {x: -10,y: 0}, {x: 0,y: 100}]



//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");



var needle = vis


  .append("g")


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


  .append("path")


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


  .attr("stroke", ringColorCode)


  .attr("stroke-width", bracketThickness)


  .attr("fill", ringColorCode)


  .attr("transform", "rotate(" + startAngleDeg + ")");




We have:


//needleWaypoints is defined with positive y axis being up


var needleWaypoints = [{x: 0,y: 100}, {x: 10,y: 0}, {x: 0,y: -10}, {x: -10,y: 0}, {x: 0,y: 100}]



//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");



var needle = vis


  .append("g")


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


  .append("path")


  .data(needleWaypoints)


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


  .attr("stroke", ringColorCode)


  .attr("stroke-width", bracketThickness)


  .attr("fill", ringColorCode)


  .attr("transform", "rotate(" + startAngleDeg + ")");




Datum vs Data

You might have noticed something above.  With the arc definitions, we used .datum and with the needle, we used .data.  What's the difference and then do you use datum vs data?  Here is the simple rule:

  • datum is unchanging, static data, tied to a single SVG element.  .datum is just fine for our gauge arc.
  • data can be updated and assigned to multiple SVG elements.  You can also use it wherever you would use datum, if you want.  We could have used .data for our gauge arc, if we wanted.  If we were defining a scatterplot, we'd have to use .data.

If you use data, a whole new set of lifecycle methods become available, allowing you chances to manually handle the "entering" and "exiting" content.  In this tutorial, we are sticking with binding our data to single SVG elements and we are completely redrawing from scratch on refresh.  If we were creating elements dynamically, or had to design for a mobile device, then we'd want to go deeply into the element/data lifecycle and come to grips with joins.

We now understand the basics behind binding data to SVG paths in D3.  next time, we'll use transitions to alter this data and start animating our gauge.

1 Comment