index.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. const PI_3_2 = Math.PI * 1.5;
  2. const PI_1_2 = Math.PI * 0.5;
  3. const mergeProps = function(newVal, oldVal) {
  4. return { ...oldVal,
  5. ...newVal
  6. }
  7. }
  8. const defaultObjectProps = {
  9. indicatorTextStyle: {
  10. type: Object,
  11. value: {
  12. show: false,
  13. size: 12,
  14. color: '#666',
  15. text: ''
  16. }
  17. },
  18. indicatorValueStyle: {
  19. type: Object,
  20. value: {
  21. show: false,
  22. size: 18,
  23. color: '#4575e8'
  24. }
  25. },
  26. indicatorCircleStyle: {
  27. type: Object,
  28. value: {
  29. show: false,
  30. bgColor: '#00b800',
  31. r: 9,
  32. borderRadius: 3,
  33. borderColor: '#fff'
  34. }
  35. },
  36. scaleTextStyle: {
  37. type: Object,
  38. value: {
  39. show: false,
  40. size: 16,
  41. color: '#f0f0f0'
  42. }
  43. }
  44. }
  45. // components/gauge.js
  46. Component({
  47. /**
  48. * 组件的属性列表
  49. */
  50. properties: {
  51. width: {
  52. type: Number,
  53. value: 750
  54. },
  55. height: {
  56. type: Number,
  57. value: 350
  58. },
  59. gaugeid: {
  60. type: String,
  61. value: 'gauge' + Math.random()
  62. },
  63. r: {
  64. type: Number,
  65. value: 95
  66. },
  67. startAngle: {
  68. type: Number,
  69. // value: 90 / 90 * Math.PI,
  70. value: 80 / 90 * Math.PI,
  71. },
  72. endAngle: {
  73. type: Number,
  74. // value: 180 / 90 * Math.PI,
  75. value: 10 / 90 * Math.PI,
  76. },
  77. indicatorBgColor: {
  78. type: [Array, String],
  79. value: [{
  80. progress: 0,
  81. value: null
  82. },
  83. {
  84. progress: 0.6,
  85. value: null
  86. },
  87. {
  88. progress: 1,
  89. value: null
  90. }
  91. ]
  92. },
  93. bgColor: {
  94. type: String,
  95. value: null,
  96. },
  97. bgWidth: {
  98. type: Number,
  99. value: 15,
  100. },
  101. min: {
  102. type: Number,
  103. value: 0,
  104. },
  105. max: {
  106. type: Number,
  107. value: 100,
  108. },
  109. value: {
  110. type: Number,
  111. value: null,
  112. observer: function(newVal, oldVal, changedPath) {
  113. this.setData({
  114. value: newVal
  115. },
  116. function() {
  117. this.drawGauge(this.canvasId, this.x, this.y)
  118. }
  119. )
  120. }
  121. },
  122. animateMsec: {
  123. type: Number,
  124. value: 0,
  125. },
  126. indicatorText: {
  127. type: String,
  128. value: ''
  129. },
  130. scale: {
  131. type: Array,
  132. value: [
  133. 0, 100
  134. ]
  135. },
  136. ...defaultObjectProps
  137. },
  138. /**
  139. * 组件的初始数据
  140. */
  141. data: {
  142. currentValue: 0
  143. },
  144. /**
  145. * 组件的方法列表
  146. */
  147. methods: {
  148. getPoint: function(x, y, r, angle) {
  149. const x1 = x + r * Math.cos(angle);
  150. const y1 = y + r * Math.sin(angle);
  151. return {
  152. x: x1,
  153. y: y1
  154. }
  155. // }
  156. },
  157. /**
  158. * 绘制圆圈
  159. * @params {CanvasContext} canvas context
  160. * @params {Object} 组件配置文件
  161. */
  162. _drawCircle: function(ctx, cfg) {
  163. ctx.beginPath()
  164. const config = cfg
  165. const innerCircleRadius = config.r - config.bgWidth;
  166. // ctx.moveTo(config.x,config.y)
  167. // 外圈
  168. ctx.arc(config.x, config.y, config.r, config.startAngle, config.endAngle)
  169. // 外圈结束坐标
  170. const outEndPoint = this.getPoint(config.x, config.y, config.r, config.endAngle)
  171. // 内圈结束坐标
  172. const innerEndPoint = this.getPoint(config.x, config.y, innerCircleRadius, config.endAngle)
  173. // 结束位置小圆圈坐标
  174. const endCirclePoint = {
  175. x: (outEndPoint.x + innerEndPoint.x) / 2,
  176. y: (outEndPoint.y + innerEndPoint.y) / 2,
  177. }
  178. // 结束位置圆弧
  179. ctx.arc(endCirclePoint.x, endCirclePoint.y, config.bgWidth / 2, config.endAngle, config.endAngle + Math.PI)
  180. // 内圈圆弧
  181. ctx.arc(config.x, config.y, innerCircleRadius, config.endAngle, config.startAngle, true)
  182. // 外圈开始位置坐标
  183. const outStartPoint = this.getPoint(config.x, config.y, config.r, config.startAngle)
  184. // 内圈开始位置坐标
  185. const innerStartPoint = this.getPoint(config.x, config.y, innerCircleRadius, config.startAngle)
  186. // 开始位置小圆圈坐标
  187. const startCirclePoint = {
  188. x: (outStartPoint.x + innerStartPoint.x) / 2,
  189. y: (outStartPoint.y + innerStartPoint.y) / 2,
  190. }
  191. // 开始位置小圆圈坐标
  192. // 开始位置圆弧
  193. ctx.moveTo(outStartPoint.x, outStartPoint.y)
  194. ctx.arc(startCirclePoint.x, startCirclePoint.y, config.bgWidth / 2, config.startAngle, config.startAngle - Math.PI, true)
  195. // 设置填充渐变色
  196. // 当背景颜色是数组时,设置为渐变色
  197. const {
  198. backgroundColor
  199. } = config
  200. if (Array.isArray(backgroundColor)) {
  201. const fillGrd = ctx.createLinearGradient(innerStartPoint.x, innerStartPoint.y, innerEndPoint.x, innerEndPoint.y);
  202. const {
  203. length
  204. } = config.backgroundColor
  205. for (let i = 0; i < length; i++) {
  206. const bg = backgroundColor[i]
  207. fillGrd.addColorStop(bg.progress, bg.value || '#32d900')
  208. }
  209. ctx.setFillStyle(fillGrd)
  210. } else {
  211. ctx.setFillStyle(config.backgroundColor)
  212. }
  213. ctx.closePath()
  214. ctx.fill()
  215. },
  216. _animate: function(func) {
  217. const {
  218. animateMsec
  219. } = this.data
  220. if (animateMsec === 0) {
  221. return func(1)
  222. }
  223. const startTime = Date.now();
  224. const endTime = startTime + animateMsec;
  225. // const traceTime = endTime - startTime;
  226. let timeOutId;
  227. const animateFunc = function() {
  228. const curTime = Date.now();
  229. const percent = (curTime - startTime) / animateMsec;
  230. if (percent >= 1) {
  231. func(1);
  232. // timeOutId && clearTimeout(timeOutId);
  233. return
  234. }
  235. func(percent)
  236. timeOutId = setTimeout(function() {
  237. animateFunc();
  238. }, 16)
  239. }
  240. animateFunc();
  241. },
  242. _drawBackground: function(ctx, config) {
  243. const newCfg = {
  244. ...config,
  245. backgroundColor: config.bgColor
  246. }
  247. delete newCfg.bgColor
  248. this._drawCircle(ctx, newCfg)
  249. },
  250. /**
  251. * 绘制指示器圆圈
  252. */
  253. _drawIndicator: function(ctx, value = 0, config) {
  254. let {
  255. startAngle,
  256. endAngle,
  257. min,
  258. max
  259. } = config
  260. if (endAngle <= startAngle) {
  261. endAngle += 2 * Math.PI
  262. }
  263. const currentAngle = (value / (max - min)) * (endAngle - startAngle) + startAngle
  264. const newCfg = {
  265. ...config,
  266. backgroundColor: config.indicatorBgColor,
  267. endAngle: currentAngle,
  268. }
  269. this._drawCircle(ctx, newCfg)
  270. },
  271. _drawIndicatorValue: function(ctx, text, config) {
  272. const {
  273. x,
  274. y,
  275. indicatorValueStyle
  276. } = config
  277. const {
  278. size = 25,
  279. color = '#1AAD16'
  280. } = indicatorValueStyle
  281. ctx.setFillStyle(color)
  282. // 以下精度可以加接口控制
  283. ctx.setFontSize(size)
  284. ctx.setTextAlign('center')
  285. ctx.fillText((!text||text==0)?'':Number.prototype.toFixed.call(text, 0), x, y)
  286. },
  287. _drawIndicatorText: function(ctx, config) {
  288. const {
  289. x,
  290. y,
  291. indicatorTextStyle
  292. } = config
  293. const {
  294. size,
  295. color,
  296. text
  297. } = mergeProps(indicatorTextStyle, defaultObjectProps.indicatorTextStyle.value)
  298. ctx.save()
  299. ctx.setFillStyle(color)
  300. // 以下精度可以加接口控制
  301. ctx.setFontSize(size)
  302. ctx.setTextAlign('center')
  303. const mergedIndicatorValueStyle = mergeProps(config.indicatorValueStyle, defaultObjectProps.indicatorValueStyle.value)
  304. ctx.fillText(text, x, y - 5 - mergedIndicatorValueStyle.size)
  305. },
  306. _drawIndicatorScale: function(ctx, config) {
  307. const {
  308. bgWidth,
  309. scale,
  310. r,
  311. x,
  312. y,
  313. min,
  314. max,
  315. scaleTextStyle
  316. } = config;
  317. let {
  318. startAngle,
  319. endAngle,
  320. } = config
  321. if (endAngle <= startAngle) {
  322. endAngle += Math.PI * 2
  323. }
  324. const len = scale.length;
  325. const {
  326. size,
  327. color
  328. } = mergeProps(scaleTextStyle, defaultObjectProps.scaleTextStyle.value);
  329. ctx.setFillStyle(color)
  330. // 以下精度可以加接口控制
  331. ctx.setFontSize(size)
  332. ctx.setTextAlign('center')
  333. for (let i = 0; i < len; i++) {
  334. const value = scale[i]
  335. let angle = (value / (max - min)) * (endAngle - startAngle) + startAngle
  336. if (angle >= Math.PI * 2) {
  337. angle = angle - Math.PI * 2
  338. }
  339. const point = this.getPoint(x, y, r - bgWidth - size - 5, angle);
  340. ctx.save()
  341. ctx.translate(point.x, point.y)
  342. const rotateDegrees = angle >= PI_3_2 ? (angle - PI_3_2) : (angle + PI_1_2);
  343. ctx.rotate(rotateDegrees)
  344. ctx.fillText(value, 0, 0)
  345. ctx.restore()
  346. }
  347. },
  348. /**
  349. * 绘制终点圆圈指示器
  350. */
  351. _drawIndicatorCircle: function(ctx, value = 0, config) {
  352. ctx.beginPath()
  353. const {
  354. indicatorCircleStyle,
  355. x,
  356. y,
  357. r,
  358. max,
  359. min,
  360. bgWidth
  361. } = config;
  362. let {
  363. startAngle,
  364. endAngle,
  365. } = config
  366. if (endAngle <= startAngle) {
  367. endAngle += 2 * Math.PI
  368. }
  369. const currentAngle = (value / (max - min)) * (endAngle - startAngle) + startAngle
  370. const outPoint = this.getPoint(x, y, r, currentAngle);
  371. const innerPoint = this.getPoint(x, y, r - bgWidth, currentAngle);
  372. const point = {
  373. x: (outPoint.x + innerPoint.x) / 2,
  374. y: (outPoint.y + innerPoint.y) / 2,
  375. }
  376. ctx.moveTo(point.x, point.y)
  377. let mergedIndicatorCircleStyle
  378. const {
  379. bgColor,
  380. borderRadius,
  381. borderColor
  382. } = mergedIndicatorCircleStyle = mergeProps(indicatorCircleStyle, defaultObjectProps.indicatorCircleStyle.value)
  383. // 边框
  384. if (!isNaN(borderRadius) && borderRadius !== 0) {
  385. const outR = mergedIndicatorCircleStyle.r + borderRadius
  386. ctx.arc(point.x, point.y, outR, 0, 2 * Math.PI);
  387. if (Array.isArray(borderColor)) {
  388. const fillGrd = ctx.createCircularGradient(point.x, point.y, outR);
  389. const {
  390. length
  391. } = borderColor
  392. for (let i = 0; i < length; i++) {
  393. const bg = borderColor[i]
  394. fillGrd.addColorStop(bg.progress, bg.value || '#32d900')
  395. }
  396. ctx.setFillStyle(fillGrd)
  397. } else {
  398. ctx.setFillStyle(borderColor)
  399. }
  400. ctx.closePath()
  401. ctx.fill()
  402. }
  403. // 内圈指示器
  404. ctx.beginPath()
  405. ctx.arc(point.x, point.y, mergedIndicatorCircleStyle.r, 0, 2 * Math.PI);
  406. ctx.setFillStyle(bgColor)
  407. ctx.closePath()
  408. ctx.fill()
  409. },
  410. drawGauge: function(canvasId, x, y) {
  411. const ctx = wx.createCanvasContext(canvasId, this);
  412. this.ctx = ctx;
  413. const config = {
  414. x,
  415. y,
  416. ...this.properties
  417. }
  418. this._animate(this._drawGaugeWithAnimate.bind(this, config))
  419. },
  420. _drawGaugeWithAnimate: function(config, percent) {
  421. const {
  422. ctx
  423. } = this
  424. const {
  425. value,
  426. min
  427. } = this.data;
  428. const {
  429. indicatorTextStyle,
  430. indicatorValueStyle,
  431. indicatorCircleStyle,
  432. scaleTextStyle
  433. } = this.properties
  434. const animateValue = min + (value - min) * percent;
  435. this._drawBackground(ctx, config)
  436. this._drawIndicator(ctx, animateValue, config)
  437. if (scaleTextStyle.show) {
  438. this._drawIndicatorScale(ctx, config)
  439. }
  440. if (indicatorTextStyle.show) {
  441. this._drawIndicatorText(ctx, config)
  442. }
  443. if (indicatorCircleStyle.show) {
  444. this._drawIndicatorCircle(ctx, animateValue, config)
  445. }
  446. if (indicatorValueStyle.show) {
  447. this._drawIndicatorValue(ctx, animateValue, config)
  448. }
  449. ctx.draw()
  450. }
  451. },
  452. ready: function(opt) {
  453. const canvasId = 'gauge_' + this.data.gaugeid;
  454. const that = this;
  455. let x = 187;
  456. let y = 187;
  457. wx.getSystemInfo({
  458. success: function(res) {
  459. const {
  460. screenWidth,
  461. screenHeight
  462. } = res;
  463. const rpxTopx = screenWidth / 750;
  464. const {
  465. width,
  466. height
  467. } = that.data
  468. that.x = x = width * rpxTopx / 2;
  469. that.y = y = height * rpxTopx / 2;
  470. that.canvasId = canvasId
  471. that.drawGauge(canvasId, x, y)
  472. },
  473. })
  474. }
  475. })