best-gauge.vue 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <template>
  2. <view class="gauge-box" :style="{'background-color': bgColor}">
  3. <canvas :canvas-id="config.id" :style="{'width' : _width + 'px','height' : _width + 'px'}"></canvas>
  4. </view>
  5. </template>
  6. <script>
  7. const mainDefault = {
  8. bgColor: 'rgba(30,130,250,1)',
  9. startAngle: 0.75,
  10. endAngle: 0.25,
  11. width: uni.upx2px(350),
  12. padding: 10,
  13. min: 0,
  14. max: 100,
  15. value: 0,
  16. unit: '℃',
  17. name: 'CK01排风温度显示',
  18. detail: {},
  19. axisTick: []
  20. };
  21. const detailDefault = { //标题、数值、单位设置
  22. title: { //name字体位置设置
  23. offsetCenter: [0, uni.upx2px(160)], //距离圆心直径偏移
  24. color: '#A9CCFD',
  25. fontSize: uni.upx2px(28),
  26. fontWeight: 'normal',
  27. textAlign: 'center'
  28. },
  29. value: {
  30. color: '#FFFFFF',
  31. fontSize: uni.upx2px(48),
  32. fontWeight: 'bolder',
  33. offsetCenter: [-10, uni.upx2px(15)], //距离圆心直径偏移
  34. textAlign: 'center'
  35. },
  36. unit: {
  37. color: '#FFFFFF',
  38. fontSize: uni.upx2px(48),
  39. fontWeight: 'normal',
  40. offsetCenter: [0, uni.upx2px(15)], //距离圆心直径偏移
  41. textAlign: 'center'
  42. }
  43. };
  44. const axisTickDefault = { //轴刻度线
  45. width: uni.upx2px(25), //轴长
  46. number: 6, //轴数量(相当于几等分)
  47. color: '#33ff33,#ff9933', //轴颜色(第一个值--指针之前的颜色,之后的颜色)
  48. subNumber: 10, //一个大刻度分成几个小刻度
  49. subWidth: uni.upx2px(25),
  50. subHeight: 1,
  51. padding: uni.upx2px(10) //刻度距离边距
  52. };
  53. const axisTickDefaultSmall = { //轴刻度线(里圈)
  54. width: uni.upx2px(10), //轴长
  55. number: 6, //轴数量(相当于几等分)
  56. color: '#33ff33,#ff9933', //轴颜色(第一个值--指针之前的颜色,之后的颜色)
  57. subNumber: 10, //大刻度之间分成几个小刻度
  58. subWidth: uni.upx2px(6),
  59. subHeight: 1,
  60. padding: uni.upx2px(25) //刻度距离边距
  61. };
  62. export default {
  63. data: function() {
  64. return {
  65. gaugeOption: {}
  66. }
  67. },
  68. props: {
  69. config: {
  70. type: Object,
  71. default: () => {
  72. return {};
  73. }
  74. },
  75. bgColor: {
  76. type: String,
  77. default: 'linear-gradient(180deg, #2A8FFB 0%, #0467FB 100%)',
  78. }
  79. },
  80. computed: {
  81. _width: function() {
  82. return this.config.width || mainDefault.width
  83. },
  84. _status: function() {
  85. return String(this.config.status) !== 'false' ? true : false;
  86. },
  87. _dStatus: function() {
  88. return String(this.config.status) === 'true' || this.config.status === 1 ? 1 : 0;
  89. }
  90. },
  91. watch: {
  92. config: {
  93. handler(newVal, oldVal) {
  94. this.initCharts();
  95. },
  96. deep: true
  97. }
  98. },
  99. mounted: function() {
  100. this.initCharts();
  101. },
  102. methods: {
  103. initCharts: function() {
  104. // 自动填充数据
  105. var gaugeOption = this.fillInData();
  106. var uChartsGauge = null;
  107. uChartsGauge = uni.createCanvasContext(gaugeOption.id, this);
  108. uChartsGauge.fillStyle = gaugeOption.bgColor;
  109. uChartsGauge.fillRect(0, 0, gaugeOption.width, gaugeOption.width);
  110. uChartsGauge.save();
  111. this.drawGauge(gaugeOption, uChartsGauge);
  112. },
  113. drawGauge: function(options, ctx) {
  114. var centerPosition = {
  115. x: options.width / 2,
  116. y: options.width / 2
  117. };
  118. // 总角度数(为什么要加1)假装下图是个圆 0.75PI ~ 0.25PI 实际间隔1.5PI
  119. // 1.5PI
  120. // ——
  121. // 1.0PI | | 0PI
  122. // ——
  123. // 0.5PI
  124. var totalAngle = options.startAngle - options.endAngle + 1;
  125. // 计算半径
  126. var radius = Math.min(centerPosition.x, centerPosition.y);
  127. //画刻度线
  128. for (let idx = 0, len = options.axisTick.length; idx < len; idx++) {
  129. var gaugeOption = options.axisTick[idx];
  130. radius -= (gaugeOption.padding + gaugeOption.width / 2);
  131. var criticalPoint = Math.floor(options.value / (options.max / gaugeOption.number)); //大刻度临界点
  132. var subCriticalPoint = Math.floor(options.value / (options.max / gaugeOption.subNumber /
  133. gaugeOption.number)); //小刻度临界点
  134. if (options.value * 1 === 0) {
  135. criticalPoint = -1; //防止0的时候被上颜色
  136. subCriticalPoint = -1;
  137. }
  138. var colors = gaugeOption.color.split(',');
  139. colors[1] = colors[1] || colors[0];
  140. var splitAngle = totalAngle / gaugeOption.number;
  141. var childAngle = totalAngle / gaugeOption.number / gaugeOption.subNumber;
  142. var startX = -radius - gaugeOption.width * 0.5;
  143. var endX = -radius - gaugeOption.width * 0.5 + gaugeOption.width;
  144. var childEndX = -radius - gaugeOption.width * 0.5 + gaugeOption.subWidth;
  145. // 画大刻度
  146. let maxScaleData = {
  147. ...centerPosition,
  148. startX,
  149. endX,
  150. splitAngle,
  151. criticalPoint,
  152. colors,
  153. startAngle: options.startAngle,
  154. width: gaugeOption.subHeight,
  155. number: gaugeOption.number,
  156. }
  157. this.drawScale(ctx, maxScaleData);
  158. // 画小刻度
  159. let minScaleData = {
  160. ...centerPosition,
  161. startX,
  162. colors,
  163. criticalPoint: subCriticalPoint,
  164. endX: childEndX,
  165. splitAngle: childAngle,
  166. startAngle: options.startAngle,
  167. width: gaugeOption.subHeight,
  168. number: gaugeOption.number * gaugeOption.subNumber,
  169. }
  170. this.drawScale(ctx, minScaleData);
  171. }
  172. // 标题
  173. var titleObj = {
  174. ...options.detail.title,
  175. ...centerPosition,
  176. name: options.name
  177. }
  178. this.drawText(ctx, titleObj);
  179. // 数值
  180. var _fillText = (options.value * 1) === 0 ? 0 : (options.value * 1).toFixed(1);
  181. var valueObj = {
  182. ...options.detail.value,
  183. ...centerPosition,
  184. name: _fillText
  185. }
  186. this.drawText(ctx, valueObj);
  187. // 单位
  188. if (String(options.unit) !== 'false') {
  189. let unitObj = {
  190. ...options.detail.unit,
  191. ...centerPosition,
  192. name: options.unit
  193. }
  194. var valueLength = (options.value * 1).toFixed(1).length;
  195. var _oftX = ((valueLength - 1.5) / 2) * valueObj.fontSize; //需要保证单位在数值后面
  196. unitObj.offsetCenter[0] += _oftX;
  197. this.drawText(ctx, unitObj);
  198. }
  199. ctx.draw();
  200. },
  201. // 画字
  202. drawText: function(ctx, data) {
  203. let {
  204. fontSize,
  205. fontWeight,
  206. color,
  207. textAlign,
  208. offsetCenter,
  209. x,
  210. y,
  211. name
  212. } = data;
  213. // 画标题
  214. ctx.beginPath();
  215. // 设置字体
  216. ctx.font = fontWeight + " " + fontSize + "px MicrosoftYaHei";
  217. // 设置颜色
  218. ctx.fillStyle = color;
  219. // 设置水平对齐方式
  220. ctx.textAlign = textAlign;
  221. // 设置中心点
  222. ctx.translate(x, y);
  223. // 绘制文字(参数:要写的字,x坐标,y坐标)
  224. ctx.fillText(name, ...offsetCenter);
  225. ctx.closePath();
  226. ctx.stroke();
  227. ctx.restore();
  228. ctx.save();
  229. },
  230. // 画刻度
  231. drawScale: function(ctx, data) {
  232. let {
  233. number,
  234. criticalPoint,
  235. startX,
  236. endX,
  237. splitAngle,
  238. width,
  239. x,
  240. y,
  241. startAngle,
  242. colors
  243. } = data;
  244. // 设置中心点
  245. ctx.translate(x, y);
  246. ctx.rotate((startAngle - 1) * Math.PI);
  247. for (let i = 0; i <= number; i++) {
  248. ctx.beginPath();
  249. ctx.setStrokeStyle(i <= criticalPoint ? colors[0] : colors[1]);
  250. ctx.setLineWidth(width);
  251. ctx.moveTo(startX, 0);
  252. ctx.lineTo(endX, 0);
  253. ctx.stroke();
  254. ctx.rotate(splitAngle * Math.PI);
  255. }
  256. // 清除状态
  257. ctx.restore();
  258. ctx.save();
  259. },
  260. fillInData: function() {
  261. var gaugeOption = this.deepClone(mainDefault, this.config);
  262. for (var _k in detailDefault) {
  263. gaugeOption.detail[_k] = this.deepClone(detailDefault[_k], gaugeOption.detail[_k])
  264. }
  265. if (gaugeOption.axisTickLength === undefined || gaugeOption.axisTickLength === null) {
  266. if (!(gaugeOption.axisTick instanceof Array)) {
  267. gaugeOption.axisTick = [gaugeOption.axisTick]
  268. }
  269. } else {
  270. gaugeOption.axisTick = gaugeOption.axisTick || [];
  271. }
  272. var axisTickLength = gaugeOption.axisTickLength || gaugeOption.axisTick.length || 1;
  273. var axisTick = [].concat(gaugeOption.axisTick);
  274. for (var idx = 0, len = axisTickLength; idx < len; idx++) {
  275. var defaultData = idx == 0 ? axisTickDefault : axisTickDefaultSmall;
  276. axisTick[idx] = axisTick[idx] || {};
  277. if (idx > 0 && !axisTick[idx].padding) {
  278. axisTick[idx].padding = +axisTick[idx - 1].width;
  279. }
  280. axisTick[idx] = this.deepClone(defaultData, axisTick[idx]);
  281. }
  282. gaugeOption.axisTick = axisTick
  283. return gaugeOption;
  284. },
  285. deepClone: function(source, target) { //待优化代码
  286. var _obj = {};
  287. source = source || {};
  288. target = target || {};
  289. Object.assign(_obj, source, target);
  290. return JSON.parse(JSON.stringify(_obj));
  291. }
  292. }
  293. };
  294. </script>
  295. <style>
  296. .gauge-box {
  297. position: relative;
  298. width: 360rpx;
  299. height: 180rpx;
  300. display: flex;
  301. align-items: center;
  302. justify-content: center;
  303. text-align: center;
  304. }
  305. .gauge-box canvas {
  306. width: 100%;
  307. margin: 0 auto;
  308. height: auto;
  309. display: block;
  310. }
  311. </style>