viewboxtest.html 21 KB

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