1 | <!DOCTYPE html>
|
2 | <html xmlns="http://www.w3.org/1999/xhtml">
|
3 | <head>
|
4 | <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
|
5 | <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
|
6 | <script>
|
7 | angular.module("ngRadialGauge",[]).directive('ngRadialGauge', ['$window', '$timeout',
|
8 | function ($window, $timeout) {
|
9 | return {
|
10 | restrict: 'EAC',
|
11 | scope: {
|
12 | data: '=',
|
13 | lowerLimit: '=',
|
14 | upperLimit: '=',
|
15 | ranges: '=',
|
16 | value: '=',
|
17 | valueUnit: '=',
|
18 | precision: '=',
|
19 | majorGraduationPrecision: '=',
|
20 | label: '=',
|
21 | onClick: '&'
|
22 | },
|
23 | link: function (scope, ele, attrs) {
|
24 | var defaultUpperLimit = 100;
|
25 | var defaultLowerLimit = 0;
|
26 | var initialized = false;
|
27 |
|
28 | var renderTimeout;
|
29 | var gaugeAngle = parseInt(attrs.angle) || 120;
|
30 |
|
31 |
|
32 | var _width = attrs.width || "100%";
|
33 |
|
34 | /* Colin Bester
|
35 | Width and height are not really such an issue with SVG but choose these values as
|
36 | width of 300 seems to be pretty baked into code.
|
37 | I took the easy path seeing as size is not that relevant and hard coded width and height
|
38 | as I was too lazy to dig deep into code.
|
39 | May be the wrong call, but seems safe option.
|
40 | */
|
41 | var view = {
|
42 | width : 300,
|
43 | height : 225
|
44 | };
|
45 | var innerRadius = Math.round((view.width * 130) / 300);
|
46 | var outerRadius = Math.round((view.width * 145) / 300);
|
47 | var majorGraduations = parseInt(attrs.majorGraduations - 1) || 5;
|
48 | var minorGraduations = parseInt(attrs.minorGraduations) || 10;
|
49 | var majorGraduationLength = Math.round((view.width * 16) / 300);
|
50 | var minorGraduationLength = Math.round((view.width * 10) / 300);
|
51 | var majorGraduationMarginTop = Math.round((view.width * 7) / 300);
|
52 | var majorGraduationColor = attrs.majorGraduationColor || "#B0B0B0";
|
53 | var minorGraduationColor = attrs.minorGraduationColor || "#D0D0D0";
|
54 | var majorGraduationTextColor = attrs.majorGraduationTextColor || "#6C6C6C";
|
55 | var needleColor = attrs.needleColor || "#416094";
|
56 | var valueVerticalOffset = Math.round((view.width * 30) / 300);
|
57 | var inactiveColor = "#D7D7D7";
|
58 | var transitionMs = parseInt(attrs.transitionMs) || 750;
|
59 | var majorGraduationTextSize = parseInt(attrs.majorGraduationTextSize);
|
60 | var needleValueTextSize = parseInt(attrs.needleValueTextSize);
|
61 | var needle = undefined;
|
62 |
|
63 |
|
64 | var extractData = function (prop) {
|
65 | if (!scope.data) return scope[prop];
|
66 | if (scope.data[prop] === undefined || scope.data[prop] == null) {
|
67 | return scope[prop];
|
68 | }
|
69 | return scope.data[prop];
|
70 | };
|
71 |
|
72 | var maxLimit;
|
73 | var minLimit;
|
74 | var value;
|
75 | var valueUnit;
|
76 | var precision;
|
77 | var majorGraduationPrecision;
|
78 | var ranges;
|
79 | var label;
|
80 |
|
81 | var updateInternalData = function() {
|
82 | maxLimit = extractData('upperLimit') ? extractData('upperLimit') : defaultUpperLimit;
|
83 | minLimit = extractData('lowerLimit') ? extractData('lowerLimit') : defaultLowerLimit;
|
84 | value = extractData('value');
|
85 | valueUnit = extractData('valueUnit');
|
86 | precision = extractData('precision');
|
87 | majorGraduationPrecision = extractData('majorGraduationPrecision');
|
88 | ranges = extractData('ranges');
|
89 | label = extractData('label');
|
90 | };
|
91 | updateInternalData();
|
92 |
|
93 | /* Colin Bester
|
94 | Add viewBox and width attributes.
|
95 | Used view.width and view.height in case it's decided that hardcoding these values is an issue.
|
96 | Width can be specified as %, px etc and will scale image to fit.
|
97 | */
|
98 | var svg = d3.select(ele[0])
|
99 | .append('svg')
|
100 | .attr('width', _width)
|
101 | .attr('viewBox', '0 0 '+view.width+' '+view.height);
|
102 |
|
103 |
|
104 | var renderMajorGraduations = function (majorGraduationsAngles) {
|
105 | var centerX = view.width / 2;
|
106 | var centerY = view.width / 2;
|
107 |
|
108 | majorGraduationsAngles.forEach(function (pValue, index) {
|
109 | var cos1Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength));
|
110 | var sin1Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength));
|
111 | var cos2Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
112 | var sin2Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
113 | var x1 = centerX + cos1Adj;
|
114 | var y1 = centerY + sin1Adj * -1;
|
115 | var x2 = centerX + cos2Adj;
|
116 | var y2 = centerY + sin2Adj * -1;
|
117 | svg.append("svg:line")
|
118 | .attr("x1", x1)
|
119 | .attr("y1", y1)
|
120 | .attr("x2", x2)
|
121 | .attr("y2", y2)
|
122 | .style("stroke", majorGraduationColor);
|
123 |
|
124 | renderMinorGraduations(majorGraduationsAngles, index);
|
125 | });
|
126 | };
|
127 | var renderMinorGraduations = function (majorGraduationsAngles, indexMajor) {
|
128 | var minorGraduationsAngles = [];
|
129 |
|
130 | if (indexMajor > 0) {
|
131 | var minScale = majorGraduationsAngles[indexMajor - 1];
|
132 | var maxScale = majorGraduationsAngles[indexMajor];
|
133 | var scaleRange = maxScale - minScale;
|
134 |
|
135 | for (var i = 1; i < minorGraduations; i++) {
|
136 | var scaleValue = minScale + i * scaleRange / minorGraduations;
|
137 | minorGraduationsAngles.push(scaleValue);
|
138 | }
|
139 |
|
140 | var centerX = view.width / 2;
|
141 | var centerY = view.width / 2;
|
142 |
|
143 | minorGraduationsAngles.forEach(function (pValue, indexMinor) {
|
144 | var cos1Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - minorGraduationLength));
|
145 | var sin1Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - minorGraduationLength));
|
146 | var cos2Adj = Math.round(Math.cos((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
147 | var sin2Adj = Math.round(Math.sin((90 - pValue) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop));
|
148 | var x1 = centerX + cos1Adj;
|
149 | var y1 = centerY + sin1Adj * -1;
|
150 | var x2 = centerX + cos2Adj;
|
151 | var y2 = centerY + sin2Adj * -1;
|
152 | svg.append("svg:line")
|
153 | .attr("x1", x1)
|
154 | .attr("y1", y1)
|
155 | .attr("x2", x2)
|
156 | .attr("y2", y2)
|
157 | .style("stroke", minorGraduationColor);
|
158 | });
|
159 | }
|
160 | };
|
161 | var getMajorGraduationValues = function (pMinLimit, pMaxLimit, pPrecision) {
|
162 | var scaleRange = pMaxLimit - pMinLimit;
|
163 | var majorGraduationValues = [];
|
164 | for (var i = 0; i <= majorGraduations; i++) {
|
165 | var scaleValue = pMinLimit + i * scaleRange / (majorGraduations);
|
166 | majorGraduationValues.push(scaleValue.toFixed(pPrecision));
|
167 | }
|
168 |
|
169 | return majorGraduationValues;
|
170 | };
|
171 | var getMajorGraduationAngles = function () {
|
172 | var scaleRange = 2 * gaugeAngle;
|
173 | var minScale = -1 * gaugeAngle;
|
174 | var graduationsAngles = [];
|
175 | for (var i = 0; i <= majorGraduations; i++) {
|
176 | var scaleValue = minScale + i * scaleRange / (majorGraduations);
|
177 | graduationsAngles.push(scaleValue);
|
178 | }
|
179 |
|
180 | return graduationsAngles;
|
181 | };
|
182 | var getNewAngle = function(pValue) {
|
183 | var scale = d3.scale.linear().range([0, 1]).domain([minLimit, maxLimit]);
|
184 | var ratio = scale(pValue);
|
185 | var scaleRange = 2 * gaugeAngle;
|
186 | var minScale = -1 * gaugeAngle;
|
187 | var newAngle = minScale + (ratio * scaleRange);
|
188 | return newAngle;
|
189 | };
|
190 | var renderMajorGraduationTexts = function (majorGraduationsAngles, majorGraduationValues, pValueUnit) {
|
191 | if (!ranges) return;
|
192 |
|
193 | var centerX = view.width / 2;
|
194 | var centerY = view.width / 2;
|
195 | var textVerticalPadding = 5;
|
196 | var textHorizontalPadding = 5;
|
197 |
|
198 | var lastGraduationValue = majorGraduationValues[majorGraduationValues.length - 1];
|
199 | var textSize = isNaN(majorGraduationTextSize) ? (view.width * 12) / 300 : majorGraduationTextSize;
|
200 | var fontStyle = textSize + "px Courier";
|
201 |
|
202 | var dummyText = svg.append("text")
|
203 | .attr("x", centerX)
|
204 | .attr("y", centerY)
|
205 | .attr("fill", "transparent")
|
206 | .attr("text-anchor", "middle")
|
207 | .style("font", fontStyle)
|
208 | .text(lastGraduationValue + pValueUnit);
|
209 |
|
210 | var textWidth = dummyText.node().getBBox().width;
|
211 |
|
212 | for (var i = 0; i < majorGraduationsAngles.length; i++) {
|
213 | var angle = majorGraduationsAngles[i];
|
214 | var cos1Adj = Math.round(Math.cos((90 - angle) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength - textHorizontalPadding));
|
215 | var sin1Adj = Math.round(Math.sin((90 - angle) * Math.PI / 180) * (innerRadius - majorGraduationMarginTop - majorGraduationLength - textVerticalPadding));
|
216 |
|
217 | var sin1Factor = 1;
|
218 | if (sin1Adj < 0) sin1Factor = 1.1;
|
219 | if (sin1Adj > 0) sin1Factor = 0.9;
|
220 | if (cos1Adj > 0) {
|
221 | if (angle > 0 && angle < 45) {
|
222 | cos1Adj -= textWidth / 2;
|
223 | } else {
|
224 | cos1Adj -= textWidth;
|
225 | }
|
226 | }
|
227 | if (cos1Adj < 0) {
|
228 | if (angle < 0 && angle > -45) {
|
229 | cos1Adj -= textWidth / 2;
|
230 | }
|
231 | }
|
232 | if (cos1Adj == 0) {
|
233 | cos1Adj -= angle == 0 ? textWidth / 4 : textWidth / 2;
|
234 | }
|
235 |
|
236 | var x1 = centerX + cos1Adj;
|
237 | var y1 = centerY + sin1Adj * sin1Factor * -1;
|
238 |
|
239 | svg.append("text")
|
240 | .attr("class", "mtt-majorGraduationText")
|
241 | .style("font", fontStyle)
|
242 | .attr("text-align", "center")
|
243 | .attr("x", x1)
|
244 | .attr("dy", y1)
|
245 | .attr("fill", majorGraduationTextColor)
|
246 | .text(majorGraduationValues[i] + pValueUnit);
|
247 | }
|
248 | };
|
249 | var renderGraduationNeedle = function (value, valueUnit, precision, minLimit, maxLimit) {
|
250 | svg.selectAll('.mtt-graduation-needle').remove();
|
251 | svg.selectAll('.mtt-graduationValueText').remove();
|
252 | svg.selectAll('.mtt-graduation-needle-center').remove();
|
253 |
|
254 | var centerX = view.width / 2;
|
255 | var centerY = view.width / 2;
|
256 | var centerColor;
|
257 |
|
258 | if (typeof value === 'undefined') {
|
259 | centerColor = inactiveColor;
|
260 | } else {
|
261 | centerColor = needleColor;
|
262 | var needleAngle = getNewAngle(value);
|
263 | var needleLen = innerRadius - majorGraduationLength - majorGraduationMarginTop;
|
264 | var needleRadius = (view.width * 2.5) / 300;
|
265 | var textSize = isNaN(needleValueTextSize) ? (view.width * 12) / 300 : needleValueTextSize;
|
266 | var fontStyle = textSize + "px Courier";
|
267 |
|
268 | if (value >= minLimit && value <= maxLimit) {
|
269 | var lineData = [
|
270 | [needleRadius, 0],
|
271 | [0, -needleLen],
|
272 | [-needleRadius, 0],
|
273 | [needleRadius, 0]
|
274 | ];
|
275 | var pointerLine = d3.svg.line().interpolate('monotone');
|
276 | var pg = svg.append('g').data([lineData])
|
277 | .attr('class', 'mtt-graduation-needle')
|
278 | .style("fill", needleColor)
|
279 | .attr('transform', 'translate(' + centerX + ',' + centerY + ')');
|
280 | needle = pg.append('path')
|
281 | .attr('d', pointerLine)
|
282 | .attr('transform', 'rotate('+needleAngle+')');
|
283 | }
|
284 |
|
285 | svg.append("text")
|
286 | .attr("x", centerX)
|
287 | .attr("y", centerY + valueVerticalOffset)
|
288 | .attr("class", "mtt-graduationValueText")
|
289 | .attr("fill", needleColor)
|
290 | .attr("text-anchor", "middle")
|
291 | .attr("font-weight", "bold")
|
292 | .style("font", fontStyle)
|
293 | .text(value.toFixed(precision) + valueUnit);
|
294 |
|
295 |
|
296 | svg.append("text")
|
297 | .attr("x", centerX)
|
298 | .attr("y", centerY + valueVerticalOffset + 10)
|
299 | .attr("class", "mtt-graduationValueText")
|
300 | .attr("fill", needleColor)
|
301 | .attr("text-anchor", "middle")
|
302 | .attr("font-weight", "bold")
|
303 | .style("font", fontStyle)
|
304 | .text(label);
|
305 | }
|
306 |
|
307 | var circleRadius = (view.width * 6) / 300;
|
308 |
|
309 | svg.append("circle")
|
310 | .attr("r", circleRadius)
|
311 | .attr("cy", centerX)
|
312 | .attr("cx", centerY)
|
313 | .attr("fill", centerColor)
|
314 | .attr("class", "mtt-graduation-needle-center");
|
315 | };
|
316 | $window.onresize = function () {
|
317 | scope.$apply();
|
318 | };
|
319 | scope.$watch(function () {
|
320 | return angular.element($window)[0].innerWidth;
|
321 | }, function () {
|
322 | scope.render();
|
323 | });
|
324 |
|
325 | /* Colin Bester
|
326 | Removed watching of data.value as couldn't see reason for this, plus it's the cause of flicker when using
|
327 | data=option mode of using directive.
|
328 | I'm assuming that calling render function is not what was intended on every value update.
|
329 | */
|
330 |
|
331 | scope.$watchCollection('[ranges, data.ranges]', function () {
|
332 | scope.render();
|
333 | }, true);
|
334 |
|
335 |
|
336 | scope.render = function () {
|
337 | updateInternalData();
|
338 | svg.selectAll('*').remove();
|
339 | if (renderTimeout) clearTimeout(renderTimeout);
|
340 |
|
341 | renderTimeout = $timeout(function () {
|
342 | var d3DataSource = [];
|
343 |
|
344 | if (typeof ranges === 'undefined') {
|
345 | d3DataSource.push([minLimit, maxLimit, inactiveColor]);
|
346 | } else {
|
347 |
|
348 | ranges.forEach(function (pValue, index) {
|
349 | d3DataSource.push([pValue.min, pValue.max, pValue.color]);
|
350 | });
|
351 | }
|
352 |
|
353 |
|
354 | var translate = "translate(" + view.width / 2 + "," + view.width / 2 + ")";
|
355 | var cScale = d3.scale.linear().domain([minLimit, maxLimit]).range([-1 * gaugeAngle * (Math.PI / 180), gaugeAngle * (Math.PI / 180)]);
|
356 | var arc = d3.svg.arc()
|
357 | .innerRadius(innerRadius)
|
358 | .outerRadius(outerRadius)
|
359 | .startAngle(function (d) { return cScale(d[0]); })
|
360 | .endAngle(function (d) { return cScale(d[1]); });
|
361 | svg.selectAll("path")
|
362 | .data(d3DataSource)
|
363 | .enter()
|
364 | .append("path")
|
365 | .attr("d", arc)
|
366 | .style("fill", function (d) { return d[2]; })
|
367 | .attr("transform", translate);
|
368 |
|
369 | var majorGraduationsAngles = getMajorGraduationAngles();
|
370 | var majorGraduationValues = getMajorGraduationValues(minLimit, maxLimit, majorGraduationPrecision);
|
371 | renderMajorGraduations(majorGraduationsAngles);
|
372 | renderMajorGraduationTexts(majorGraduationsAngles, majorGraduationValues, valueUnit);
|
373 | renderGraduationNeedle(value, valueUnit, precision, minLimit, maxLimit);
|
374 | initialized = true;
|
375 | }, 200);
|
376 |
|
377 | };
|
378 | var onValueChanged = function(pValue, pPrecision, pValueUnit) {
|
379 | if (typeof pValue === 'undefined' || pValue == null) return;
|
380 |
|
381 | if (needle && pValue >= minLimit && pValue <= maxLimit) {
|
382 | var needleAngle = getNewAngle(pValue);
|
383 | needle.transition()
|
384 | .duration(transitionMs)
|
385 | .ease('elastic')
|
386 | .attr('transform', 'rotate('+needleAngle+')');
|
387 | svg.selectAll('.mtt-graduationValueText')
|
388 | .text('[ ' + pValue.toFixed(pPrecision) + pValueUnit + ' ]') ;
|
389 | } else {
|
390 | svg.selectAll('.mtt-graduation-needle').remove();
|
391 | svg.selectAll('.mtt-graduationValueText').remove();
|
392 | svg.selectAll('.mtt-graduation-needle-center').attr("fill", inactiveColor);
|
393 | }
|
394 | };
|
395 | scope.$watchCollection('[value, data.value]', function () {
|
396 | if (!initialized) return;
|
397 | updateInternalData();
|
398 | onValueChanged(value, precision, valueUnit);
|
399 | }, true);
|
400 | }
|
401 | };
|
402 | }]);
|
403 |
|
404 | </script>
|
405 | <script src=saveSvgAsPng.js></script>
|
406 | <script>
|
407 | angular.module('RadialGaugeDemo', [
|
408 | 'ngRadialGauge'
|
409 | ]);
|
410 |
|
411 | angular.module('RadialGaugeDemo').controller('RadialGaugeDemoCtrl', ['$scope', '$timeout', function ($scope, $timeout) {
|
412 | $scope.value = 1.5;
|
413 | $scope.upperLimit = 6;
|
414 | $scope.lowerLimit = 0;
|
415 | $scope.unit = "kW";
|
416 | $scope.precision = 2;
|
417 | $scope.ranges = [
|
418 | {
|
419 | min: 0,
|
420 | max: 1.5,
|
421 | color: '#DEDEDE'
|
422 | },
|
423 | {
|
424 | min: 1.5,
|
425 | max: 2.5,
|
426 | color: '#8DCA2F'
|
427 | },
|
428 | {
|
429 | min: 2.5,
|
430 | max: 3.5,
|
431 | color: '#FDC702'
|
432 | },
|
433 | {
|
434 | min: 3.5,
|
435 | max: 4.5,
|
436 | color: '#FF7700'
|
437 | },
|
438 | {
|
439 | min: 4.5,
|
440 | max: 6.0,
|
441 | color: '#C50200'
|
442 | }
|
443 | ];
|
444 | $scope.OnClick = function() {
|
445 | console.log("click");
|
446 | svgAsDataUri(document.getElementsByTagName("svg")[0], null, function (uri) {
|
447 | var img = '<img class="img-thumbnail" src="' + uri + '">';
|
448 | d3.select("#svgpreview").html(img);
|
449 | });
|
450 | }
|
451 | }]);
|
452 | </script>
|
453 | </head>
|
454 | <body ng-app="RadialGaugeDemo">
|
455 | <div ng-controller="RadialGaugeDemoCtrl">
|
456 | <div width="10%" ng-radial-gauge ranges="ranges" value="value" value-unit="unit" precision="precision" lower-limit="lowerLimit" upper-limit="upperLimit"></div>
|
457 | <a href="" ng-click="OnClick()">Click Here to show image Preview</a>
|
458 | <div id="svgpreview"></div>
|
459 | </div>
|
460 | </body>
|
461 | </html> |
\ | No newline at end of file |