<!DOCTYPE html>
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
<head>
|
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
|
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
<script>
|
angular.module("ngRadialGauge",[]).directive('ngRadialGauge', ['$window', '$timeout',
|
function ($window, $timeout) {
|
return {
|
restrict: 'EAC',
|
scope: {
|
data: '=',
|
lowerLimit: '=',
|
upperLimit: '=',
|
ranges: '=',
|
value: '=',
|
valueUnit: '=',
|
precision: '=',
|
majorGraduationPrecision: '=',
|
label: '=',// MODIFIED
|
onClick: '&'
|
},
|
link: function (scope, ele, attrs) {
|
var defaultUpperLimit = 100;
|
var defaultLowerLimit = 0;
|
var initialized = false;
|
|
var renderTimeout;
|
var gaugeAngle = parseInt(attrs.angle) || 120;
|
|
//New width variable, now works in conjunction with fixed viewBox sizing
|
var _width = attrs.width || "100%";
|
|
/* Colin Bester
|
Width and height are not really such an issue with SVG but choose these values as
|
width of 300 seems to be pretty baked into code.
|
I took the easy path seeing as size is not that relevant and hard coded width and height
|
as I was too lazy to dig deep into code.
|
May be the wrong call, but seems safe option.
|
*/
|
var view = {
|
width : 300,
|
height : 225
|
};
|
var innerRadius = Math.round((view.width * 130) / 300);
|
var outerRadius = Math.round((view.width * 145) / 300);
|
var majorGraduations = parseInt(attrs.majorGraduations - 1) || 5;
|
var minorGraduations = parseInt(attrs.minorGraduations) || 10;
|
var majorGraduationLength = Math.round((view.width * 16) / 300);
|
var minorGraduationLength = Math.round((view.width * 10) / 300);
|
var majorGraduationMarginTop = Math.round((view.width * 7) / 300);
|
var majorGraduationColor = attrs.majorGraduationColor || "#B0B0B0";
|
var minorGraduationColor = attrs.minorGraduationColor || "#D0D0D0";
|
var majorGraduationTextColor = attrs.majorGraduationTextColor || "#6C6C6C";
|
var needleColor = attrs.needleColor || "#416094";
|
var valueVerticalOffset = Math.round((view.width * 30) / 300);
|
var inactiveColor = "#D7D7D7";
|
var transitionMs = parseInt(attrs.transitionMs) || 750;
|
var majorGraduationTextSize = parseInt(attrs.majorGraduationTextSize);
|
var needleValueTextSize = parseInt(attrs.needleValueTextSize);
|
var needle = undefined;
|
|
//The scope.data object might contain the data we need, otherwise we fall back on the scope.xyz property
|
var extractData = function (prop) {
|
if (!scope.data) return scope[prop];
|
if (scope.data[prop] === undefined || scope.data[prop] == null) {
|
return scope[prop];
|
}
|
return scope.data[prop];
|
};
|
|
var maxLimit;
|
var minLimit;
|
var value;
|
var valueUnit;
|
var precision;
|
var majorGraduationPrecision;
|
var ranges;
|
var label;
|
|
var updateInternalData = function() {
|
maxLimit = extractData('upperLimit') ? extractData('upperLimit') : defaultUpperLimit;
|
minLimit = extractData('lowerLimit') ? extractData('lowerLimit') : defaultLowerLimit;
|
value = extractData('value');
|
valueUnit = extractData('valueUnit');
|
precision = extractData('precision');
|
majorGraduationPrecision = extractData('majorGraduationPrecision');
|
ranges = extractData('ranges');
|
label = extractData('label'); // MODIFIED
|
};
|
updateInternalData();
|
|
/* Colin Bester
|
Add viewBox and width attributes.
|
Used view.width and view.height in case it's decided that hardcoding these values is an issue.
|
Width can be specified as %, px etc and will scale image to fit.
|
*/
|
var svg = d3.select(ele[0])
|
.append('svg')
|
.attr('width', _width)
|
.attr('viewBox', '0 0 '+view.width+' '+view.height);
|
// .attr('view.width', view.width)
|
// .attr('height', view.width * 0.75);
|
var renderMajorGraduations = function (majorGraduationsAngles) {
|
var centerX = view.width / 2;
|
var centerY = view.width / 2;
|
//Render Major Graduations
|
majorGraduationsAngles.forEach(function (pValue, index) {
|
var cos1Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength));
|
var sin1Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength));
|
var cos2Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
var sin2Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
var x1 = centerX + cos1Adj;
|
var y1 = centerY + sin1Adj * -1;
|
var x2 = centerX + cos2Adj;
|
var y2 = centerY + sin2Adj * -1;
|
svg.append("svg:line")
|
.attr("x1", x1)
|
.attr("y1", y1)
|
.attr("x2", x2)
|
.attr("y2", y2)
|
.style("stroke", majorGraduationColor);
|
|
renderMinorGraduations(majorGraduationsAngles, index);
|
});
|
};
|
var renderMinorGraduations = function (majorGraduationsAngles, indexMajor) {
|
var minorGraduationsAngles = [];
|
|
if (indexMajor > 0) {
|
var minScale = majorGraduationsAngles[indexMajor - 1];
|
var maxScale = majorGraduationsAngles[indexMajor];
|
var scaleRange = maxScale - minScale;
|
|
for (var i = 1; i < minorGraduations; i++) {
|
var scaleValue = minScale + i * scaleRange / minorGraduations;
|
minorGraduationsAngles.push(scaleValue);
|
}
|
|
var centerX = view.width / 2;
|
var centerY = view.width / 2;
|
//Render Minor Graduations
|
minorGraduationsAngles.forEach(function (pValue, indexMinor) {
|
var cos1Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - minorGraduationLength));
|
var sin1Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - minorGraduationLength));
|
var cos2Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
var sin2Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
var x1 = centerX + cos1Adj;
|
var y1 = centerY + sin1Adj * -1;
|
var x2 = centerX + cos2Adj;
|
var y2 = centerY + sin2Adj * -1;
|
svg.append("svg:line")
|
.attr("x1", x1)
|
.attr("y1", y1)
|
.attr("x2", x2)
|
.attr("y2", y2)
|
.style("stroke", minorGraduationColor);
|
});
|
}
|
};
|
var getMajorGraduationValues = function (pMinLimit, pMaxLimit, pPrecision) {
|
var scaleRange = pMaxLimit - pMinLimit;
|
var majorGraduationValues = [];
|
for (var i = 0; i <= majorGraduations; i++) {
|
var scaleValue = pMinLimit + i * scaleRange / (majorGraduations);
|
majorGraduationValues.push(scaleValue.toFixed(pPrecision));
|
}
|
|
return majorGraduationValues;
|
};
|
var getMajorGraduationAngles = function () {
|
var scaleRange = 2 * gaugeAngle;
|
var minScale = -1 * gaugeAngle;
|
var graduationsAngles = [];
|
for (var i = 0; i <= majorGraduations; i++) {
|
var scaleValue = minScale + i * scaleRange / (majorGraduations);
|
graduationsAngles.push(scaleValue);
|
}
|
|
return graduationsAngles;
|
};
|
var getNewAngle = function(pValue) {
|
var scale = d3.scale.linear().range([0, 1]).domain([minLimit, maxLimit]);
|
var ratio = scale(pValue);
|
var scaleRange = 2 * gaugeAngle;
|
var minScale = -1 * gaugeAngle;
|
var newAngle = minScale + (ratio * scaleRange);
|
return newAngle;
|
};
|
var renderMajorGraduationTexts = function (majorGraduationsAngles, majorGraduationValues, pValueUnit) {
|
if (!ranges) return;
|
|
var centerX = view.width / 2;
|
var centerY = view.width / 2;
|
var textVerticalPadding = 5;
|
var textHorizontalPadding = 5;
|
|
var lastGraduationValue = majorGraduationValues[majorGraduationValues.length - 1];
|
var textSize = isNaN(majorGraduationTextSize) ? (view.width * 12) / 300 : majorGraduationTextSize;
|
var fontStyle = textSize + "px Courier";
|
|
var dummyText = svg.append("text")
|
.attr("x", centerX)
|
.attr("y", centerY)
|
.attr("fill", "transparent")
|
.attr("text-anchor", "middle")
|
.style("font", fontStyle)
|
.text(lastGraduationValue + pValueUnit);
|
|
var textWidth = dummyText.node().getBBox().width;
|
|
for (var i = 0; i < majorGraduationsAngles.length; i++) {
|
var angle = majorGraduationsAngles[i];
|
var cos1Adj = Math.round(Math.cos((90 - angle) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength - textHorizontalPadding));
|
var sin1Adj = Math.round(Math.sin((90 - angle) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength - textVerticalPadding));
|
|
var sin1Factor = 1;
|
if (sin1Adj < 0) sin1Factor = 1.1;
|
if (sin1Adj > 0) sin1Factor = 0.9;
|
if (cos1Adj > 0) {
|
if (angle > 0 && angle < 45) {
|
cos1Adj -= textWidth / 2;
|
} else {
|
cos1Adj -= textWidth;
|
}
|
}
|
if (cos1Adj < 0) {
|
if (angle < 0 && angle > -45) {
|
cos1Adj -= textWidth / 2;
|
}
|
}
|
if (cos1Adj == 0) {
|
cos1Adj -= angle == 0 ? textWidth / 4 : textWidth / 2;
|
}
|
|
var x1 = centerX + cos1Adj;
|
var y1 = centerY + sin1Adj * sin1Factor * -1;
|
|
svg.append("text")
|
.attr("class", "mtt-majorGraduationText")
|
.style("font", fontStyle)
|
.attr("text-align", "center")
|
.attr("x", x1)
|
.attr("dy", y1)
|
.attr("fill", majorGraduationTextColor)
|
.text(majorGraduationValues[i] + pValueUnit);
|
}
|
};
|
var renderGraduationNeedle = function (value, valueUnit, precision, minLimit, maxLimit) {
|
svg.selectAll('.mtt-graduation-needle').remove();
|
svg.selectAll('.mtt-graduationValueText').remove();
|
svg.selectAll('.mtt-graduation-needle-center').remove();
|
|
var centerX = view.width / 2;
|
var centerY = view.width / 2;
|
var centerColor;
|
|
if (typeof value === 'undefined') {
|
centerColor = inactiveColor;
|
} else {
|
centerColor = needleColor;
|
var needleAngle = getNewAngle(value);
|
var needleLen = innerRadius - majorGraduationLength - majorGraduationMarginTop;
|
var needleRadius = (view.width * 2.5) / 300;
|
var textSize = isNaN(needleValueTextSize) ? (view.width * 12) / 300 : needleValueTextSize;
|
var fontStyle = textSize + "px Courier";
|
|
if (value >= minLimit && value <= maxLimit) {
|
var lineData = [
|
[needleRadius, 0],
|
[0, -needleLen],
|
[-needleRadius, 0],
|
[needleRadius, 0]
|
];
|
var pointerLine = d3.svg.line().interpolate('monotone');
|
var pg = svg.append('g').data([lineData])
|
.attr('class', 'mtt-graduation-needle')
|
.style("fill", needleColor)
|
.attr('transform', 'translate(' + centerX + ',' + centerY + ')');
|
needle = pg.append('path')
|
.attr('d', pointerLine)
|
.attr('transform', 'rotate('+needleAngle+')');
|
}
|
|
svg.append("text")
|
.attr("x", centerX)
|
.attr("y", centerY + valueVerticalOffset)
|
.attr("class", "mtt-graduationValueText")
|
.attr("fill", needleColor)
|
.attr("text-anchor", "middle")
|
.attr("font-weight", "bold")
|
.style("font", fontStyle)
|
.text(value.toFixed(precision) + valueUnit);
|
|
// MODIFIED: Added a customizable label
|
svg.append("text")
|
.attr("x", centerX)
|
.attr("y", centerY + valueVerticalOffset + 10)
|
.attr("class", "mtt-graduationValueText")
|
.attr("fill", needleColor)
|
.attr("text-anchor", "middle")
|
.attr("font-weight", "bold")
|
.style("font", fontStyle)
|
.text(label);
|
}
|
|
var circleRadius = (view.width * 6) / 300;
|
|
svg.append("circle")
|
.attr("r", circleRadius)
|
.attr("cy", centerX)
|
.attr("cx", centerY)
|
.attr("fill", centerColor)
|
.attr("class", "mtt-graduation-needle-center");
|
};
|
$window.onresize = function () {
|
scope.$apply();
|
};
|
scope.$watch(function () {
|
return angular.element($window)[0].innerWidth;
|
}, function () {
|
scope.render();
|
});
|
|
/* Colin Bester
|
Removed watching of data.value as couldn't see reason for this, plus it's the cause of flicker when using
|
data=option mode of using directive.
|
I'm assuming that calling render function is not what was intended on every value update.
|
*/
|
// scope.$watchCollection('[ranges, data.ranges, data.value]', function () {
|
scope.$watchCollection('[ranges, data.ranges]', function () {
|
scope.render();
|
}, true);
|
|
|
scope.render = function () {
|
updateInternalData();
|
svg.selectAll('*').remove();
|
if (renderTimeout) clearTimeout(renderTimeout);
|
|
renderTimeout = $timeout(function () {
|
var d3DataSource = [];
|
|
if (typeof ranges === 'undefined') {
|
d3DataSource.push([minLimit, maxLimit, inactiveColor]);
|
} else {
|
//Data Generation
|
ranges.forEach(function (pValue, index) {
|
d3DataSource.push([pValue.min, pValue.max, pValue.color]);
|
});
|
}
|
|
//Render Gauge Color Area
|
var translate = "translate(" + view.width / 2 + "," + view.width / 2 + ")";
|
var cScale = d3.scale.linear().domain([minLimit, maxLimit]).range([-1 * gaugeAngle * (Math.PI / 180), gaugeAngle * (Math.PI / 180)]);
|
var arc = d3.svg.arc()
|
.innerRadius(innerRadius)
|
.outerRadius(outerRadius)
|
.startAngle(function (d) { return cScale(d[0]); })
|
.endAngle(function (d) { return cScale(d[1]); });
|
svg.selectAll("path")
|
.data(d3DataSource)
|
.enter()
|
.append("path")
|
.attr("d", arc)
|
.style("fill", function (d) { return d[2]; })
|
.attr("transform", translate);
|
|
var majorGraduationsAngles = getMajorGraduationAngles();
|
var majorGraduationValues = getMajorGraduationValues(minLimit, maxLimit, majorGraduationPrecision);
|
renderMajorGraduations(majorGraduationsAngles);
|
renderMajorGraduationTexts(majorGraduationsAngles, majorGraduationValues, valueUnit);
|
renderGraduationNeedle(value, valueUnit, precision, minLimit, maxLimit);
|
initialized = true;
|
}, 200);
|
|
};
|
var onValueChanged = function(pValue, pPrecision, pValueUnit) {
|
if (typeof pValue === 'undefined' || pValue == null) return;
|
|
if (needle && pValue >= minLimit && pValue <= maxLimit) {
|
var needleAngle = getNewAngle(pValue);
|
needle.transition()
|
.duration(transitionMs)
|
.ease('elastic')
|
.attr('transform', 'rotate('+needleAngle+')');
|
svg.selectAll('.mtt-graduationValueText')
|
.text('[ ' + pValue.toFixed(pPrecision) + pValueUnit + ' ]') ;
|
} else {
|
svg.selectAll('.mtt-graduation-needle').remove();
|
svg.selectAll('.mtt-graduationValueText').remove();
|
svg.selectAll('.mtt-graduation-needle-center').attr("fill", inactiveColor);
|
}
|
};
|
scope.$watchCollection('[value, data.value]', function () {
|
if (!initialized) return;
|
updateInternalData();
|
onValueChanged(value, precision, valueUnit);
|
}, true);
|
}
|
};
|
}]);
|
|
</script>
|
<script src=saveSvgAsPng.js></script>
|
<script>
|
angular.module('RadialGaugeDemo', [
|
'ngRadialGauge'
|
]);
|
|
angular.module('RadialGaugeDemo').controller('RadialGaugeDemoCtrl', ['$scope', '$timeout', function ($scope, $timeout) {
|
$scope.value = 1.5;
|
$scope.upperLimit = 6;
|
$scope.lowerLimit = 0;
|
$scope.unit = "kW";
|
$scope.precision = 2;
|
$scope.ranges = [
|
{
|
min: 0,
|
max: 1.5,
|
color: '#DEDEDE'
|
},
|
{
|
min: 1.5,
|
max: 2.5,
|
color: '#8DCA2F'
|
},
|
{
|
min: 2.5,
|
max: 3.5,
|
color: '#FDC702'
|
},
|
{
|
min: 3.5,
|
max: 4.5,
|
color: '#FF7700'
|
},
|
{
|
min: 4.5,
|
max: 6.0,
|
color: '#C50200'
|
}
|
];
|
$scope.OnClick = function() {
|
console.log("click");
|
svgAsDataUri(document.getElementsByTagName("svg")[0], null, function (uri) {
|
var img = '<img class="img-thumbnail" src="' + uri + '">';
|
d3.select("#svgpreview").html(img);
|
});
|
}
|
}]);
|
</script>
|
</head>
|
<body ng-app="RadialGaugeDemo">
|
<div ng-controller="RadialGaugeDemoCtrl">
|
<div width="10%" ng-radial-gauge ranges="ranges" value="value" value-unit="unit" precision="precision" lower-limit="lowerLimit" upper-limit="upperLimit"></div>
|
<a href="" ng-click="OnClick()">Click Here to show image Preview</a>
|
<div id="svgpreview"></div>
|
</div>
|
</body>
|
</html>
|