This is part of a tutorial series on creating extension components for Design Studio.
Last time, we looked at D3 transitions and used them to animate our gauge in the sandbox html file. In this installment, we're going to bring those transitions into the component. There are a few things that we want to keep in mind:
Looking through the above bullet points, we have 9 new properties:
Property | Type | Default Value |
---|---|---|
gaugeOpacity | float | 1.0 |
guideOpacity | float | 1.0 |
animationEnable | boolean | false |
animationDelay | int | 500 |
animationDuration | int | 1000 |
animationEase | String | "linear" |
animationEnableOpacity | boolean | false |
animationDelayOpacity | int | 500 |
animationDurationOpacity | int | 500 |
The above properties translate into the following property elements:
<property
id="gaugeOpacity"
title="Opacity"
type="float"
group="SCNGaugeAngleSettings"/>
<property id="guideOpacity"
title="Guide Opacity"
type="float"
group="SCNGaugeLineSettings"/>
<property id="animationEnable"
title="Enable Animations"
type="boolean"
tooltip="Are the gauge arc and needle animated?"
group="SCNGaugeAnimationSettings"/>
<property id="animationDelay"
title="Animation Delay"
type="int"
tooltip="Delay time (in miliseconds), before the animation starts"
group="SCNGaugeAnimationSettings"/>
<property id="animationDuration"
title="Animation Duration"
type="int"
tooltip="Duration time (in miliseconds) of the animation. Includes dampening oscillation time if ease type is elastic."
group="SCNGaugeAnimationSettings"/>
<property id="animationEase"
title="Ease Type"
type="String"
tooltip="Delay time (in miliseconds), before the animation starts"
group="SCNGaugeAnimationSettings">
<possibleValue>linear</possibleValue>
<possibleValue>quad</possibleValue>
<possibleValue>cubic</possibleValue>
<possibleValue>sin</possibleValue>
<possibleValue>exp</possibleValue>
<possibleValue>circle</possibleValue>
<possibleValue>elastic</possibleValue>
<possibleValue>back</possibleValue>
<possibleValue>bounce</possibleValue>
</property>
<property id="animationEnableOpacity"
title="Enable Opacity Fade"
type="boolean"
tooltip="Enable Animated Opacity Fade?"
group="SCNGaugeAnimationSettings"/>
<property id="animationDelayOpacity"
title="Opacity Fade Delay"
type="int"
tooltip="Delay time (in miliseconds), before the opacity fade starts starts"
group="SCNGaugeAnimationSettings"/>
<property id="animationDurationOpacity"
title="Opacity Fade Duration"
type="int"
tooltip="Duration time (in miliseconds) of opacity fade."
group="SCNGaugeAnimationSettings"/>
And initialization is also straightforward:
<initialization>
...
<defaultValue property="gaugeOpacity">1.0</defaultValue>
<defaultValue property="guideOpacity">1.0</defaultValue>
<defaultValue property="animationEnable">false</defaultValue>
<defaultValue property="animationDelay">500</defaultValue>
<defaultValue property="animationDuration">1000</defaultValue>
<defaultValue property="animationEase">linear</defaultValue>
<defaultValue property="animationEnableOpacity">false</defaultValue>
<defaultValue property="animationDelayOpacity">500</defaultValue>
<defaultValue property="animationDurationOpacity">500</defaultValue>
</initialization>
In the component.js file, we're going to do three things:
We follow the usual conventions for the client side delegate for all properties:
me._gaugeOpacity = 1.0,
me._guideOpacity = 1.0;
me._animationEnable = false;
me._animationDelay = 500;
me._animationDuration = 1000;
me._animationEase = "linear";
me._animationEnableOpacity = false;
me._animationDelayOpacity = 500;
me._animationDurationOpacity = 500;
And the getter/setters:
me.gaugeOpacity = function(value) {
if (value === undefined) {
return me._gaugeOpacity;
} else {
me._gaugeOpacity = value;
return me;
}
};
me.guideOpacity = function(value) {
if (value === undefined) {
return me._guideOpacity;
} else {
me._guideOpacity = value;
return this;
}
};
me.animationEnable = function(value) {
if (value === undefined) {
return me._animationEnable;
} else {
me._animationEnable = value;
return me;
}
};
me.animationDelay = function(value) {
if (value === undefined) {
return me._animationDelay;
} else {
me._animationDelay = value;
return me;
}
};
me.animationDuration = function(value) {
if (value === undefined) {
return me._animationDuration;
} else {
me._animationDuration = value;
return me;
}
};
me.animationEase = function(value) {
if (value === undefined) {
return me._animationEase;
} else {
me._animationEase = value;
return me;
}
};
me.animationEnableOpacity = function(value) {
if (value === undefined) {
return me._animationEnableOpacity;
} else {
me._animationEnableOpacity = value;
return me;
}
};
me.animationDelayOpacity = function(value) {
if (value === undefined) {
return me._animationDelayOpacity;
} else {
me._animationDelayOpacity = value;
return me;
}
};
me.animationDurationOpacity = function(value) {
if (value === undefined) {
return me._animationDurationOpacity;
} else {
me._animationDurationOpacity = value;
return me;
}
};
The new gauge arc, with opacity and the new starting data:
if (me._enableArc == true){
var arcDef = d3.svg.arc()
.innerRadius(me._innerRad)
.outerRadius(me._outerRad);
var guageArc = vis.append("path")
.datum({endAngle: me._endAngleDeg * (pi/180), startAngle: me._startAngleDeg * (pi/180)})
.style("fill", me._displayedColor)
.attr("width", me.$().width()).attr("height", me.$().height()) // Added height and width so arc is visible
.attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")")
.attr("d", arcDef)
.attr( "fill-opacity", me._gaugeOpacity );
}
The reworked indicator needle and base pin:
///////////////////////////////////////////
//Lets add the indicator needle
///////////////////////////////////////////
if (me._enableIndicatorNeedle == true){
var needleWaypointOffset = me._needleWidth/2;
//needleWaypoints is defined with positive y axis being up
//The initial definition of needleWaypoints is for a full diamond, but if me._enableIndicatorNeedleTail is false, we'll abbreviate to a chevron
var needleWaypoints = [{x: 0,y: me._needleHeadLength}, {x: needleWaypointOffset,y: 0}, {x: 0,y: (-1*me._needleTailLength)}, {x: (-1*needleWaypointOffset),y: 0}, {x: 0,y: me._needleHeadLength}]
if (me._enableIndicatorNeedleTail == false){
if (me._fillNeedle == false){
//If we have no tail and no fill then there is no need to close the shape.
//Leave it as an open chevron
needleWaypoints = [{x: needleWaypointOffset,y: 0}, {x: 0,y: me._needleHeadLength}, {x: (-1*needleWaypointOffset),y: 0}];
}
else {
//There is no tail, but we are filling the needle.
//In this case, draw it as a triangle
needleWaypoints = [{x: 0,y: me._needleHeadLength}, {x: needleWaypointOffset,y: 0}, {x: (-1*needleWaypointOffset),y: 0}, {x: 0,y: me._needleHeadLength}]
}
}
//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)*(me._outerRad/100); })
.y(function(d) { return -1*(d.y)*(me._outerRad/100); })
.interpolate("linear");
//Draw the needle, either filling it in, or not
var needleFillColorCode = me._needleColorCode;
if (me._fillNeedle == false){
needleFillColorCode = "none";
}
var needle = vis
.append("g")
.attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")")
.append("path")
.datum(needleWaypoints)
.attr("class", "tri")
.attr("d", needleFunction(needleWaypoints))
.attr("stroke", me._needleColorCode)
.attr("stroke-width", me._needleLineThickness)
.attr("fill", needleFillColorCode)
.attr("transform", "rotate(" + me._startAngleDeg + ")");;
}
///////////////////////////////////////////
//Lets add a needle base pin
///////////////////////////////////////////
if (me._enableIndicatorNeedleBase == true){
// Like the rest of the needle, the size of the pin is defined relative to the main arc, as a % value
var needleIBasennerRadius = (me._needleBaseWidth/2)*(me._outerRad/100) - (me._needleLineThickness/2);
var needleBaseOuterRadius = needleIBasennerRadius + me._needleLineThickness;
if (me._fillNeedlaBasePin == true){
needleIBasennerRadius = 0.0;
}
// The pin will either be a 180 degree arc, or a 360 degree ring; starting from the 9 O'clock position.
var needleBaseStartAngle = 90.0;
var needleBaseEndAngle = 270.0;
if (me._fullBasePinRing == true){
needleBaseEndAngle = 450.0;
}
//Don't let the arc have a negative length
if (needleBaseEndAngle < needleBaseStartAngle){
needleBaseEndAngle = needleBaseStartAngle;
alert("End angle of outer ring may not be less than start angle!");
}
//Transfomation for the Pin Ring
// We won't apply it just yet
var nbpTransformedStartAngle = needleBaseStartAngle + me._startAngleDeg;
var nbpTransformedEndAngle = needleBaseEndAngle + me._startAngleDeg;
var nbTransformedStartAngle = needleBaseStartAngle + me._endAngleDeg;
var nbTransformedEndAngle = needleBaseEndAngle + me._endAngleDeg;
var pinArcDefinition = d3.svg.arc()
.innerRadius(needleIBasennerRadius)
.outerRadius(needleBaseOuterRadius);
var pinArc = vis.append("path")
.datum({endAngle: nbpTransformedEndAngle * (pi/180), startAngle: nbpTransformedStartAngle * (pi/180)})
.attr("d", pinArcDefinition)
.attr("fill", me._needleColorCode)
.attr("transform", "translate(" + me._offsetLeft + "," + me._offsetDown + ")");
}
Somewhere in the me.redraw() function, prior to the transitions, we need to check to see whether or not animations have been enabled and set the timing to 0 if required:
//Prepare the animation settings
// If me._animationEnable is false, then we'll act as if me._animationDelay and me._animationDuration
// are both 0, without actually altering their values.
var tempAnimationDelay = 0;
var tempAnimationDuration = 0;
if (me._animationEnable == true){
tempAnimationDelay = me._animationDelay;
tempAnimationDuration = me._animationDuration;
}
//Guide Ring and Lines
var localFadeDelay = me._animationDelayOpacity;
var localFadeDuration = me._animationDurationOpacity;
if (me._animationEnableOpacity == false){
localFadeDelay = 0;
localFadeDuration = 0;
}
We'll pretty much just leave the sandbox transition code untouched, except for refactoring it into the component:
///////////////////////////////////////////
//Lets add our animations
///////////////////////////////////////////
//This blog post explains using attrTween for arcs: http://bl.ocks.org/mbostock/5100636
// Function adapted from this example
// Creates a tween on the specified transition's "d" attribute, transitioning
// any selected arcs from their current angle to the specified new angle.
if (me._enableArc == true){
guageArc.transition()
.duration(tempAnimationDuration)
.delay(tempAnimationDelay)
.ease(me._animationEase)
.attrTween("d", function(d) {
var interpolate = d3.interpolate(me._startAngleDeg * (pi/180), d.endAngle);
return function(t) {
d.endAngle = interpolate(t);
return arcDef(d);
};
});
}
//Arcs are in radians, but rotation transformations are in degrees. Kudos to D3 for consistency
if (me._enableIndicatorNeedle == true){
needle.transition()
.attr("transform", "rotate(" + me._endAngleDeg + ")")
.duration(tempAnimationDuration)
.delay(tempAnimationDelay)
.ease(me._animationEase);
}
if (me._enableIndicatorNeedleBase == true){
pinArc.transition()
.duration(tempAnimationDuration)
.delay(tempAnimationDelay)
.attrTween("d", function(d) {
var interpolateEnd = d3.interpolate(nbpTransformedEndAngle * (pi/180), nbTransformedEndAngle * (pi/180));
var interpolateStart = d3.interpolate(nbpTransformedStartAngle * (pi/180), nbTransformedStartAngle * (pi/180));
return function(t) {
d.endAngle = interpolateEnd(t);
d.startAngle = interpolateStart(t);
return pinArcDefinition(d);
};
});
}
//Guide Ring and Lines
if (me._enableGuideRing == true){
ringArc.transition()
.attr( "fill-opacity", 0 )
.transition()
.delay( localFadeDelay )
.duration(localFadeDuration)
.attr( "fill-opacity", me._guideOpacity );
}
if (me._enableGuideLines == true){
borderLines.transition()
.attr( "stroke-opacity", 0 )
.transition()
.delay( localFadeDelay )
.duration(localFadeDuration)
.attr( "stroke-opacity", me._guideOpacity );
}
Note that I created the video before adding the opacity fade.
We now have animated gauge angles and guide opacity. As usual, the current version of the project is available in a project on Github.
Next time, we'll begin looking at adding dynamic text callouts.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
User | Count |
---|---|
10 | |
10 | |
10 | |
9 | |
8 | |
8 | |
6 | |
6 | |
5 | |
5 |