概要 实例 介绍 源码

液体灌装仪

源文件:index.html

  1. <!DOCTYPE html>
  2. <html>
  3. <head lang="en">
  4. <meta charset="UTF-8">
  5. <title></title>
  6. <script src="http://d3js.org/d3.v3.min.js" language="JavaScript"></script>
  7. <script src="liquidFillGauge.js" language="JavaScript"></script>
  8. <style>
  9. .liquidFillGaugeText { font-family: Helvetica; font-weight: bold; }
  10. </style>
  11. </head>
  12. <body>
  13. <svg id="fillgauge1" width="97%" height="250" onclick="gauge1.update(NewValue());"></svg>
  14. <svg id="fillgauge2" width="19%" height="200" onclick="gauge2.update(NewValue());"></svg>
  15. <svg id="fillgauge3" width="19%" height="200" onclick="gauge3.update(NewValue());"></svg>
  16. <svg id="fillgauge4" width="19%" height="200" onclick="gauge4.update(NewValue());"></svg>
  17. <svg id="fillgauge5" width="19%" height="200" onclick="gauge5.update(NewValue());"></svg>
  18. <svg id="fillgauge6" width="19%" height="200" onclick="gauge6.update(NewValue());"></svg>
  19. <script language="JavaScript">
  20. var gauge1 = loadLiquidFillGauge("fillgauge1", 55);
  21. var config1 = liquidFillGaugeDefaultSettings();
  22. config1.circleColor = "#FF7777";
  23. config1.textColor = "#FF4444";
  24. config1.waveTextColor = "#FFAAAA";
  25. config1.waveColor = "#FFDDDD";
  26. config1.circleThickness = 0.2;
  27. config1.textVertPosition = 0.2;
  28. config1.waveAnimateTime = 1000;
  29. var gauge2= loadLiquidFillGauge("fillgauge2", 28, config1);
  30. var config2 = liquidFillGaugeDefaultSettings();
  31. config2.circleColor = "#D4AB6A";
  32. config2.textColor = "#553300";
  33. config2.waveTextColor = "#805615";
  34. config2.waveColor = "#AA7D39";
  35. config2.circleThickness = 0.1;
  36. config2.circleFillGap = 0.2;
  37. config2.textVertPosition = 0.8;
  38. config2.waveAnimateTime = 2000;
  39. config2.waveHeight = 0.3;
  40. config2.waveCount = 1;
  41. var gauge3 = loadLiquidFillGauge("fillgauge3", 60.1, config2);
  42. var config3 = liquidFillGaugeDefaultSettings();
  43. config3.textVertPosition = 0.8;
  44. config3.waveAnimateTime = 5000;
  45. config3.waveHeight = 0.15;
  46. config3.waveAnimate = false;
  47. config3.waveOffset = 0.25;
  48. config3.valueCountUp = false;
  49. config3.displayPercent = false;
  50. var gauge4 = loadLiquidFillGauge("fillgauge4", 50, config3);
  51. var config4 = liquidFillGaugeDefaultSettings();
  52. config4.circleThickness = 0.15;
  53. config4.circleColor = "#808015";
  54. config4.textColor = "#555500";
  55. config4.waveTextColor = "#FFFFAA";
  56. config4.waveColor = "#AAAA39";
  57. config4.textVertPosition = 0.8;
  58. config4.waveAnimateTime = 1000;
  59. config4.waveHeight = 0.05;
  60. config4.waveAnimate = true;
  61. config4.waveRise = false;
  62. config4.waveHeightScaling = false;
  63. config4.waveOffset = 0.25;
  64. config4.textSize = 0.75;
  65. config4.waveCount = 3;
  66. var gauge5 = loadLiquidFillGauge("fillgauge5", 60.44, config4);
  67. var config5 = liquidFillGaugeDefaultSettings();
  68. config5.circleThickness = 0.4;
  69. config5.circleColor = "#6DA398";
  70. config5.textColor = "#0E5144";
  71. config5.waveTextColor = "#6DA398";
  72. config5.waveColor = "#246D5F";
  73. config5.textVertPosition = 0.52;
  74. config5.waveAnimateTime = 5000;
  75. config5.waveHeight = 0;
  76. config5.waveAnimate = false;
  77. config5.waveCount = 2;
  78. config5.waveOffset = 0.25;
  79. config5.textSize = 1.2;
  80. config5.minValue = 30;
  81. config5.maxValue = 150
  82. config5.displayPercent = false;
  83. var gauge6 = loadLiquidFillGauge("fillgauge6", 120, config5);
  84. function NewValue(){
  85. if(Math.random() > .5){
  86. return Math.round(Math.random()*100);
  87. } else {
  88. return (Math.random()*100).toFixed(1);
  89. }
  90. }
  91. </script>
  92. </body>
  93. </html>

源文件:liquidFillGauge.js

  1. /*!
  2. * @license Open source under BSD 2-clause (http://choosealicense.com/licenses/bsd-2-clause/)
  3. * Copyright (c) 2015, Curtis Bratton
  4. * All rights reserved.
  5. *
  6. * Liquid Fill Gauge v1.1
  7. */
  8. function liquidFillGaugeDefaultSettings(){
  9. return {
  10. minValue: 0, // The gauge minimum value.
  11. maxValue: 100, // The gauge maximum value.
  12. circleThickness: 0.05, // The outer circle thickness as a percentage of it's radius.
  13. circleFillGap: 0.05, // The size of the gap between the outer circle and wave circle as a percentage of the outer circles radius.
  14. circleColor: "#178BCA", // The color of the outer circle.
  15. waveHeight: 0.05, // The wave height as a percentage of the radius of the wave circle.
  16. waveCount: 1, // The number of full waves per width of the wave circle.
  17. waveRiseTime: 1000, // The amount of time in milliseconds for the wave to rise from 0 to it's final height.
  18. waveAnimateTime: 18000, // The amount of time in milliseconds for a full wave to enter the wave circle.
  19. waveRise: true, // Control if the wave should rise from 0 to it's full height, or start at it's full height.
  20. waveHeightScaling: true, // Controls wave size scaling at low and high fill percentages. When true, wave height reaches it's maximum at 50% fill, and minimum at 0% and 100% fill. This helps to prevent the wave from making the wave circle from appear totally full or empty when near it's minimum or maximum fill.
  21. waveAnimate: true, // Controls if the wave scrolls or is static.
  22. waveColor: "#178BCA", // The color of the fill wave.
  23. waveOffset: 0, // The amount to initially offset the wave. 0 = no offset. 1 = offset of one full wave.
  24. textVertPosition: .5, // The height at which to display the percentage text withing the wave circle. 0 = bottom, 1 = top.
  25. textSize: 1, // The relative height of the text to display in the wave circle. 1 = 50%
  26. valueCountUp: true, // If true, the displayed value counts up from 0 to it's final value upon loading. If false, the final value is displayed.
  27. displayPercent: true, // If true, a % symbol is displayed after the value.
  28. textColor: "#045681", // The color of the value text when the wave does not overlap it.
  29. waveTextColor: "#A4DBf8" // The color of the value text when the wave overlaps it.
  30. };
  31. }
  32. function loadLiquidFillGauge(elementId, value, config) {
  33. if(config == null) config = liquidFillGaugeDefaultSettings();
  34. var gauge = d3.select("#" + elementId);
  35. var radius = Math.min(parseInt(gauge.style("width")), parseInt(gauge.style("height")))/2;
  36. var locationX = parseInt(gauge.style("width"))/2 - radius;
  37. var locationY = parseInt(gauge.style("height"))/2 - radius;
  38. var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
  39. var waveHeightScale;
  40. if(config.waveHeightScaling){
  41. waveHeightScale = d3.scale.linear()
  42. .range([0,config.waveHeight,0])
  43. .domain([0,50,100]);
  44. } else {
  45. waveHeightScale = d3.scale.linear()
  46. .range([config.waveHeight,config.waveHeight])
  47. .domain([0,100]);
  48. }
  49. var textPixels = (config.textSize*radius/2);
  50. var textFinalValue = parseFloat(value).toFixed(2);
  51. var textStartValue = config.valueCountUp?config.minValue:textFinalValue;
  52. var percentText = config.displayPercent?"%":"";
  53. var circleThickness = config.circleThickness * radius;
  54. var circleFillGap = config.circleFillGap * radius;
  55. var fillCircleMargin = circleThickness + circleFillGap;
  56. var fillCircleRadius = radius - fillCircleMargin;
  57. var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
  58. var waveLength = fillCircleRadius*2/config.waveCount;
  59. var waveClipCount = 1+config.waveCount;
  60. var waveClipWidth = waveLength*waveClipCount;
  61. // Rounding functions so that the correct number of decimal places is always displayed as the value counts up.
  62. var textRounder = function(value){ return Math.round(value); };
  63. if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
  64. textRounder = function(value){ return parseFloat(value).toFixed(1); };
  65. }
  66. if(parseFloat(textFinalValue) != parseFloat(textRounder(textFinalValue))){
  67. textRounder = function(value){ return parseFloat(value).toFixed(2); };
  68. }
  69. // Data for building the clip wave area.
  70. var data = [];
  71. for(var i = 0; i <= 40*waveClipCount; i++){
  72. data.push({x: i/(40*waveClipCount), y: (i/(40))});
  73. }
  74. // Scales for drawing the outer circle.
  75. var gaugeCircleX = d3.scale.linear().range([0,2*Math.PI]).domain([0,1]);
  76. var gaugeCircleY = d3.scale.linear().range([0,radius]).domain([0,radius]);
  77. // Scales for controlling the size of the clipping path.
  78. var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
  79. var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
  80. // Scales for controlling the position of the clipping path.
  81. var waveRiseScale = d3.scale.linear()
  82. // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
  83. // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
  84. // circle at 100%.
  85. .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
  86. .domain([0,1]);
  87. var waveAnimateScale = d3.scale.linear()
  88. .range([0, waveClipWidth-fillCircleRadius*2]) // Push the clip area one full wave then snap back.
  89. .domain([0,1]);
  90. // Scale for controlling the position of the text within the gauge.
  91. var textRiseScaleY = d3.scale.linear()
  92. .range([fillCircleMargin+fillCircleRadius*2,(fillCircleMargin+textPixels*0.7)])
  93. .domain([0,1]);
  94. // Center the gauge within the parent SVG.
  95. var gaugeGroup = gauge.append("g")
  96. .attr('transform','translate('+locationX+','+locationY+')');
  97. // Draw the outer circle.
  98. var gaugeCircleArc = d3.svg.arc()
  99. .startAngle(gaugeCircleX(0))
  100. .endAngle(gaugeCircleX(1))
  101. .outerRadius(gaugeCircleY(radius))
  102. .innerRadius(gaugeCircleY(radius-circleThickness));
  103. gaugeGroup.append("path")
  104. .attr("d", gaugeCircleArc)
  105. .style("fill", config.circleColor)
  106. .attr('transform','translate('+radius+','+radius+')');
  107. // Text where the wave does not overlap.
  108. var text1 = gaugeGroup.append("text")
  109. .text(textRounder(textStartValue) + percentText)
  110. .attr("class", "liquidFillGaugeText")
  111. .attr("text-anchor", "middle")
  112. .attr("font-size", textPixels + "px")
  113. .style("fill", config.textColor)
  114. .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
  115. // The clipping wave area.
  116. var clipArea = d3.svg.area()
  117. .x(function(d) { return waveScaleX(d.x); } )
  118. .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
  119. .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
  120. var waveGroup = gaugeGroup.append("defs")
  121. .append("clipPath")
  122. .attr("id", "clipWave" + elementId);
  123. var wave = waveGroup.append("path")
  124. .datum(data)
  125. .attr("d", clipArea)
  126. .attr("T", 0);
  127. // The inner circle with the clipping wave attached.
  128. var fillCircleGroup = gaugeGroup.append("g")
  129. .attr("clip-path", "url(#clipWave" + elementId + ")");
  130. fillCircleGroup.append("circle")
  131. .attr("cx", radius)
  132. .attr("cy", radius)
  133. .attr("r", fillCircleRadius)
  134. .style("fill", config.waveColor);
  135. // Text where the wave does overlap.
  136. var text2 = fillCircleGroup.append("text")
  137. .text(textRounder(textStartValue) + percentText)
  138. .attr("class", "liquidFillGaugeText")
  139. .attr("text-anchor", "middle")
  140. .attr("font-size", textPixels + "px")
  141. .style("fill", config.waveTextColor)
  142. .attr('transform','translate('+radius+','+textRiseScaleY(config.textVertPosition)+')');
  143. // Make the value count up.
  144. if(config.valueCountUp){
  145. var textTween = function(){
  146. var i = d3.interpolate(this.textContent, textFinalValue);
  147. return function(t) { this.textContent = textRounder(i(t)) + percentText; }
  148. };
  149. text1.transition()
  150. .duration(config.waveRiseTime)
  151. .tween("text", textTween);
  152. text2.transition()
  153. .duration(config.waveRiseTime)
  154. .tween("text", textTween);
  155. }
  156. // Make the wave rise. wave and waveGroup are separate so that horizontal and vertical movement can be controlled independently.
  157. var waveGroupXPosition = fillCircleMargin+fillCircleRadius*2-waveClipWidth;
  158. if(config.waveRise){
  159. waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(0)+')')
  160. .transition()
  161. .duration(config.waveRiseTime)
  162. .attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')')
  163. .each("start", function(){ wave.attr('transform','translate(1,0)'); }); // This transform is necessary to get the clip wave positioned correctly when waveRise=true and waveAnimate=false. The wave will not position correctly without this, but it's not clear why this is actually necessary.
  164. } else {
  165. waveGroup.attr('transform','translate('+waveGroupXPosition+','+waveRiseScale(fillPercent)+')');
  166. }
  167. if(config.waveAnimate) animateWave();
  168. function animateWave() {
  169. wave.attr('transform','translate('+waveAnimateScale(wave.attr('T'))+',0)');
  170. wave.transition()
  171. .duration(config.waveAnimateTime * (1-wave.attr('T')))
  172. .ease('linear')
  173. .attr('transform','translate('+waveAnimateScale(1)+',0)')
  174. .attr('T', 1)
  175. .each('end', function(){
  176. wave.attr('T', 0);
  177. animateWave(config.waveAnimateTime);
  178. });
  179. }
  180. function GaugeUpdater(){
  181. this.update = function(value){
  182. var newFinalValue = parseFloat(value).toFixed(2);
  183. var textRounderUpdater = function(value){ return Math.round(value); };
  184. if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
  185. textRounderUpdater = function(value){ return parseFloat(value).toFixed(1); };
  186. }
  187. if(parseFloat(newFinalValue) != parseFloat(textRounderUpdater(newFinalValue))){
  188. textRounderUpdater = function(value){ return parseFloat(value).toFixed(2); };
  189. }
  190. var textTween = function(){
  191. var i = d3.interpolate(this.textContent, parseFloat(value).toFixed(2));
  192. return function(t) { this.textContent = textRounderUpdater(i(t)) + percentText; }
  193. };
  194. text1.transition()
  195. .duration(config.waveRiseTime)
  196. .tween("text", textTween);
  197. text2.transition()
  198. .duration(config.waveRiseTime)
  199. .tween("text", textTween);
  200. var fillPercent = Math.max(config.minValue, Math.min(config.maxValue, value))/config.maxValue;
  201. var waveHeight = fillCircleRadius*waveHeightScale(fillPercent*100);
  202. var waveRiseScale = d3.scale.linear()
  203. // The clipping area size is the height of the fill circle + the wave height, so we position the clip wave
  204. // such that the it will overlap the fill circle at all when at 0%, and will totally cover the fill
  205. // circle at 100%.
  206. .range([(fillCircleMargin+fillCircleRadius*2+waveHeight),(fillCircleMargin-waveHeight)])
  207. .domain([0,1]);
  208. var newHeight = waveRiseScale(fillPercent);
  209. var waveScaleX = d3.scale.linear().range([0,waveClipWidth]).domain([0,1]);
  210. var waveScaleY = d3.scale.linear().range([0,waveHeight]).domain([0,1]);
  211. var newClipArea;
  212. if(config.waveHeightScaling){
  213. newClipArea = d3.svg.area()
  214. .x(function(d) { return waveScaleX(d.x); } )
  215. .y0(function(d) { return waveScaleY(Math.sin(Math.PI*2*config.waveOffset*-1 + Math.PI*2*(1-config.waveCount) + d.y*2*Math.PI));} )
  216. .y1(function(d) { return (fillCircleRadius*2 + waveHeight); } );
  217. } else {
  218. newClipArea = clipArea;
  219. }
  220. var newWavePosition = config.waveAnimate?waveAnimateScale(1):0;
  221. wave.transition()
  222. .duration(0)
  223. .transition()
  224. .duration(config.waveAnimate?(config.waveAnimateTime * (1-wave.attr('T'))):(config.waveRiseTime))
  225. .ease('linear')
  226. .attr('d', newClipArea)
  227. .attr('transform','translate('+newWavePosition+',0)')
  228. .attr('T','1')
  229. .each("end", function(){
  230. if(config.waveAnimate){
  231. wave.attr('transform','translate('+waveAnimateScale(0)+',0)');
  232. animateWave(config.waveAnimateTime);
  233. }
  234. });
  235. waveGroup.transition()
  236. .duration(config.waveRiseTime)
  237. .attr('transform','translate('+waveGroupXPosition+','+newHeight+')')
  238. }
  239. }
  240. return new GaugeUpdater();
  241. }