排队支付小程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

405 lines
13 KiB

  1. const utils = require('./util');
  2. const STYLE_DEFAULT = {
  3. TEXT_ALIGN: 'center',
  4. COLOR: '#000000',
  5. LIGHT_COLOR: '#fff',
  6. FONT_SIZE: 24,
  7. AXLE_COLOR: '#e5e5e5'
  8. };
  9. function CurvePainter(ctx, config) {
  10. this.ctx = ctx;
  11. this.windowWidth = config.windowWidth;
  12. this.canvasWidth = config.canvasWidth;
  13. this.canvasHeight = config.canvasHeight;
  14. this.marginTop = config.marginTop; // 画布上边留白
  15. this.marginBottom = config.marginBottom; // 画布底边留白
  16. this.marginLeft = config.marginLeft; // 画布左边留白
  17. this.marginRight = config.marginRight; // 画布右边留白
  18. this.drawWidth = this.canvasWidth - this.marginLeft - this.marginRight; // 绘制宽度
  19. this.drawHeight = this.canvasHeight - this.marginTop - this.marginBottom; // 绘制高度
  20. }
  21. CurvePainter.prototype = {
  22. setData(data) {
  23. this.hAxleData = data.hAxleData || []; // x轴刻度
  24. this.hAxleTitle = data.hAxleTitle || ''; // x轴标题
  25. this.vAxleData = data.vAxleData || []; // y轴刻度
  26. this.vAxleTitle = data.vAxleTitle || ''; // y轴标题
  27. this.originDataList = data.dataList || []; // 原始数据集
  28. this.selectedIndex = data.selectedIndex; // 被选中的序号
  29. this.reference = data.reference || 0; // 参考值
  30. this.unit = data.unit || ''; // 数值单位
  31. this.drawHAxle = data.drawHAxle || false; // 是否绘制横轴
  32. this.drawVAxle = data.drawVAxle || false; // 是否绘制纵轴
  33. this.drawCurveDecorate = data.drawCurveDecorate || false; // 是否绘制背景
  34. this.drawCurveDataPoint = data.drawCurveDataPoint || false; // 是否绘制数据点
  35. this.drawCurveDataText = data.drawCurveDataText || false; // 是否绘制数据值
  36. },
  37. setOffset(data) {
  38. this.topOffset = data.topOffset; // 最大数据点距画布顶部距离
  39. this.bottomOffset = data.bottomOffset; // 最小数据点距画布底部距离
  40. },
  41. setStyle(config) {
  42. this.curveWidth = config.curveWidth || this.toPx(6); // 曲线宽度
  43. this.curveColor = config.curveColor || STYLE_DEFAULT.COLOR; // 曲线颜色
  44. this.curveDecorateColors = config.curveDecorateColors || [STYLE_DEFAULT.LIGHT_COLOR, STYLE_DEFAULT.LIGHT_COLOR]; // 背景颜色
  45. this.scaleColor = config.scaleColor || STYLE_DEFAULT.COLOR; // 刻度字体颜色
  46. this.selectedScaleColor = config.selectedScaleColor || STYLE_DEFAULT.COLOR; // 选中刻度的字体颜色
  47. this.scaleFontSize = config.scaleFontSize || this.toPx(STYLE_DEFAULT.FONT_SIZE); // 刻度字体大小
  48. this.selectedScaleFontSize = config.selectedScaleFontSize || this.toPx(STYLE_DEFAULT.FONT_SIZE); // 选中的刻度字体大小
  49. this.dataPointFontColor = config.dataPointFontColor || STYLE_DEFAULT.COLOR; // 数据值颜色
  50. this.selectedDataPointFontColor = config.selectedDataPointFontColor || STYLE_DEFAULT.COLOR; // 选中的数据值颜色
  51. this.dataPointFontSize = config.dataPointFontSize || this.toPx(STYLE_DEFAULT.FONT_SIZE); // 数据值字体大小
  52. this.selectedDataPointFontSize = config.selectedDataPointFontSize || this.toPx(STYLE_DEFAULT.FONT_SIZE); // 选中的数据值字体大小
  53. this.textAlign = config.textAlign || 'center'; // 文字对其方式
  54. this.hAxleTitleColor = config.hAxleTitleColor || STYLE_DEFAULT.COLOR; // x轴标题颜色
  55. this.vAxleTitleColor = config.vAxleTitleColor || STYLE_DEFAULT.COLOR; // y轴标题颜色
  56. this.hAxleColor = config.hAxleColor || STYLE_DEFAULT.AXLE_COLOR; // x轴线颜色
  57. this.hAxleWidth = config.hAxleWidth || 1; // x轴宽度
  58. this.vAxleColor = config.vAxleColor || STYLE_DEFAULT.AXLE_COLOR; // y轴颜色
  59. this.vAxleWidth = config.vAxleWidth || 1; // y轴宽度
  60. this.pointMarginColor = config.pointMarginColor || STYLE_DEFAULT.COLOR; // 数据点外圆颜色
  61. this.pointMarginRadius = config.pointMarginRadius || this.toPx(24 / 2); // 数据点外缘半径
  62. this.pointColor = config.pointColor || STYLE_DEFAULT.COLOR;// 数据点颜色
  63. this.pointRadius = config.pointRadius || this.toPx(12 / 2); // 数据点半径
  64. },
  65. addAction(fn) {
  66. if (!utils.isFunction(fn)) return;
  67. if (!this.actions) {
  68. this.actions = [];
  69. }
  70. this.actions.push(fn);
  71. },
  72. prepare() {
  73. let self = this;
  74. let data = this.originDataList;
  75. dataToPos();
  76. function dataToPos() {
  77. if (!self.isValid(data)) {
  78. return;
  79. }
  80. let {max, min} = self.getMaxAndMin(data, self.reference);
  81. let range = max !== min ? max - min : 1; // 需要考虑最大值等于最小值,也就是所有值都一样的情况
  82. let drawWidth = self.drawWidth;
  83. let drawHeight = self.drawHeight;
  84. let dataPointMaxHeight = drawHeight - self.topOffset - self.bottomOffset; // 数据点最大高度
  85. let xOffset = drawWidth / (data.length - 1);
  86. let calcData = [];
  87. for (let i = 0; i < data.length; i++) {
  88. if (data[i]) {
  89. calcData.push({
  90. x: i * xOffset,
  91. y: self.topOffset + dataPointMaxHeight - self.calcRate(data, max, min, range, i) * dataPointMaxHeight,
  92. value: data[i],
  93. });
  94. } else {
  95. calcData.push({
  96. value: data[i]
  97. });
  98. }
  99. }
  100. self.calcData = calcData;
  101. }
  102. return this;
  103. },
  104. // 数据合法化处理
  105. calcRate(list, max, min, range, idx) {
  106. if(max === min) {
  107. return 1;
  108. }
  109. let diff = list[idx] - min;
  110. if (diff === 0) {
  111. return 0;
  112. } else {
  113. if (range === 0) {
  114. return list[idx] / max;
  115. } else {
  116. return diff / range;
  117. }
  118. }
  119. },
  120. getMaxAndMin(list, target) {
  121. let max = 0, min = 0, self = this;
  122. let listWithoutZero = this.filterZero(list);
  123. if (listWithoutZero.length > 1) {
  124. return {
  125. max: self.max(listWithoutZero),
  126. min: self.minWithoutZero(listWithoutZero)
  127. };
  128. } else {
  129. let only = listWithoutZero[0];
  130. max = only > target ? only : target;
  131. min = only > target ? target: only;
  132. return { max, min };
  133. }
  134. },
  135. max(obj) {
  136. if (!this.isValid(obj)) {
  137. return void 0;
  138. }
  139. let max = obj[0];
  140. obj.forEach(item => {
  141. if (item > max) {
  142. max = item;
  143. }
  144. });
  145. return max;
  146. },
  147. minWithoutZero(obj) {
  148. if (!this.isValid(obj)) {
  149. return void 0;
  150. }
  151. let min = obj[0];
  152. obj.forEach(item => {
  153. if (item > 0 && item <= min) {
  154. min = item;
  155. }
  156. });
  157. return min;
  158. },
  159. filterZero(list) {
  160. return list.filter(item => item);
  161. },
  162. draw(reDraw) {
  163. reDraw && this.clear();
  164. this.performDraw();
  165. },
  166. performDraw() {
  167. // 坐标轴变换方便计算
  168. this.resetCurvesCoordinate();
  169. // 绘制轴线
  170. this.drawAxle();
  171. // 绘制曲线
  172. this.drawCurve();
  173. this.ctx.draw();
  174. },
  175. clear() {
  176. this.ctx && this.ctx.clearRect(0, 0, this.drawHeight, this.drawHeight);
  177. },
  178. resetCurvesCoordinate() {
  179. let xCut = this.marginLeft;
  180. let yCut = this.marginTop;
  181. this.ctx.translate(xCut, yCut);
  182. },
  183. drawAxle() {
  184. let ctx = this.ctx;
  185. let drawWidth = this.drawWidth;
  186. let drawHeight = this.drawHeight;
  187. let defaultColor = STYLE_DEFAULT.COLOR;
  188. let drawHAxle = this.drawHAxle;
  189. if (drawHAxle) {
  190. let hAxleData = this.hAxleData;
  191. let dataLen = hAxleData.length;
  192. let selectedIndex = this.selectedIndex;
  193. let selectedScaleColor = this.selectedScaleColor;
  194. let scaleFontSize = this.scaleFontSize;
  195. let selectedScaleFontSize = this.selectedScaleFontSize;
  196. let hAxleTitle = this.hAxleTitle;
  197. let hAxleTitleColor = this.hAxleTitleColor;
  198. let hAxleWidth = this.hAxleWidth;
  199. let hAxleColor = this.hAxleColor;
  200. // 绘制刻度
  201. ctx.setFontSize(scaleFontSize);
  202. ctx.setTextAlign(this.textAlign);
  203. let item;
  204. let xOffset = dataLen - 1 ? drawWidth / (dataLen - 1) : 0;
  205. for (let i = 0, len = dataLen; i < len; i++) {
  206. item = hAxleData[i];
  207. ctx.setFillStyle(i === selectedIndex ? selectedScaleColor : defaultColor);
  208. ctx.setFontSize(i === selectedIndex ? selectedScaleFontSize : scaleFontSize);
  209. ctx.fillText(item, xOffset * i, drawHeight);
  210. }
  211. // 绘制刻度标题
  212. ctx.setFillStyle(hAxleTitleColor);
  213. ctx.setFontSize(scaleFontSize);
  214. ctx.fillText(hAxleTitle, drawWidth + this.toPx(45), drawHeight); // 45 = 偏离y轴的距离
  215. // 画轴线
  216. ctx.setLineWidth(hAxleWidth);
  217. ctx.setStrokeStyle(hAxleColor);
  218. ctx.beginPath();
  219. ctx.moveTo(0, drawHeight - this.toPx(36)); // 36 = 刻度文字与x轴的距离 + 文字大小
  220. ctx.lineTo(drawWidth + this.toPx(55), drawHeight - this.toPx(36));
  221. ctx.stroke();
  222. }
  223. },
  224. drawCurve() {
  225. let self = this;
  226. let ctx = this.ctx;
  227. let drawWidth = this.drawWidth;
  228. let drawHeight = this.drawHeight;
  229. let calcData = this.calcData;
  230. let selectedIndex = self.selectedIndex;
  231. if (calcData && calcData.length) {
  232. let curveWidth = this.curveWidth;
  233. let curveColor = this.curveColor;
  234. ctx.setLineWidth(curveWidth);
  235. let pos, lastPos = calcData[0];
  236. ctx.beginPath();
  237. ctx.moveTo(lastPos.x, lastPos.y);
  238. for (let i = 1, len = calcData.length; i < len; i++) {
  239. pos = calcData[i];
  240. if (!pos.value) continue;
  241. // 这里每次都得创建一个新的渐变
  242. // FIXME 这里是为了解决,android,在同一个canvas内,
  243. // FIXME 先用gradient绘制了线条后,无法setStrokeStyle设置为普通颜色的Bug
  244. let gd = ctx.createLinearGradient(0, 0, pos.x, pos.y);
  245. gd.addColorStop(0, curveColor);
  246. gd.addColorStop(1, curveColor);
  247. ctx.setStrokeStyle(gd);
  248. if (i === 1) {
  249. ctx.lineTo(pos.x, pos.y);
  250. } else {
  251. ctx.beginPath();
  252. ctx.moveTo(lastPos.x, lastPos.y);
  253. ctx.lineTo(pos.x, pos.y);
  254. }
  255. ctx.stroke();
  256. lastPos = pos;
  257. }
  258. drawCurveDecorate(); // 绘制曲线修饰
  259. drawCurveDataPoint(); // 绘制数据点
  260. drawCurveDataText(); // 绘制数据值
  261. drawCustom(); // 绘制自定义内容
  262. }
  263. function drawCurveDecorate() {
  264. let drawCurveDecorate = self.drawCurveDecorate;
  265. if (!drawCurveDecorate || calcData.length <= 1) return;
  266. let curveDecorateColors = self.curveDecorateColors;
  267. let lg = ctx.createLinearGradient(0, 0, 0, drawHeight);
  268. lg.addColorStop(0, curveDecorateColors[0]);
  269. lg.addColorStop(1, curveDecorateColors[1]);
  270. ctx.setFillStyle(lg);
  271. ctx.beginPath();
  272. ctx.moveTo(calcData[0].x || 0, calcData[0].y || 0);
  273. for(let i = 0, len = calcData.length; i < len; i++) {
  274. let pos = calcData[i];
  275. if (!pos.value) continue;
  276. ctx.lineTo(pos.x, pos.y);
  277. }
  278. ctx.lineTo(getNonZeroPos(calcData).x, drawHeight);
  279. ctx.lineTo(calcData[0].x || 0, drawHeight);
  280. ctx.lineTo(calcData[0].x || 0, calcData[0].y || 0);
  281. ctx.closePath();
  282. ctx.fill();
  283. }
  284. function getNonZeroPos(list) {
  285. let len = list.length;
  286. for (let i = len - 1, end = 0; i >= end; i--) {
  287. let temp = list[i];
  288. if (!temp.value) continue;
  289. return temp;
  290. }
  291. }
  292. function drawCurveDataPoint() {
  293. let drawCurveDataPoint = self.drawCurveDataPoint;
  294. if (!drawCurveDataPoint) return;
  295. let pointMarginColor = self.pointMarginColor;
  296. let pointColor = self.pointColor;
  297. for (let i = 0, len = calcData.length; i < len; i++) {
  298. let pos = calcData[i];
  299. if (!pos.value) continue;
  300. // 被选中的数据点有外边框
  301. if (i === selectedIndex) {
  302. ctx.setFillStyle(pointMarginColor);
  303. ctx.beginPath();
  304. ctx.arc(pos.x, pos.y, self.toPx(24 / 2), 0, 2 * Math.PI);
  305. ctx.fill();
  306. }
  307. // 再画颜色小圆
  308. ctx.setFillStyle(pointColor);
  309. ctx.beginPath();
  310. ctx.arc(pos.x, pos.y, self.toPx(12 / 2), 0, 2 * Math.PI);
  311. ctx.fill();
  312. }
  313. }
  314. function drawCurveDataText() {
  315. let drawCurveDataText = self.drawCurveDataText;
  316. if (!drawCurveDataText) return;
  317. let textAlign = self.textAlign;
  318. let dataPointFontSize = self.dataPointFontSize;
  319. let selectedDataPointFontSize = self.selectedDataPointFontSize;
  320. let dataPointFontColor = self.dataPointFontColor;
  321. let selectedDataPointFontColor = self.selectedDataPointFontColor;
  322. ctx.setTextAlign(textAlign);
  323. for (let i = 0, len = calcData.length; i < len; i++) {
  324. let pos = calcData[i];
  325. if (!pos.value) continue;
  326. ctx.setFontSize( i === selectedIndex ? selectedDataPointFontSize : dataPointFontSize);
  327. ctx.setFillStyle( i === selectedIndex ? selectedDataPointFontColor : dataPointFontColor);
  328. ctx.fillText(
  329. `${pos.value.toFixed(1)}`,
  330. pos.x,
  331. pos.y - self.toPx(25)); // 25 = 数据点和数据文字的距离
  332. }
  333. }
  334. function drawCustom() {
  335. let actions = self.actions;
  336. actions && actions.forEach(action => {
  337. action.apply(self);
  338. });
  339. }
  340. },
  341. toPx(rpx) {
  342. let windowWidth = this.windowWidth;
  343. return utils.rpx_to_px(rpx, windowWidth);
  344. },
  345. isValid(obj) {
  346. let isArray = utils.isArray(obj);
  347. if (isArray) {
  348. return obj.length > 0;
  349. } else {
  350. return isArray;
  351. }
  352. }
  353. };
  354. module.exports = CurvePainter;