traffic-diagram 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/.trae/documents/Update README.md for Traffic Lane Component.md +37 -0
  2. package/.trae/documents/npm Publish Plan.md +27 -0
  3. package/.trae/documents/plan_20251222_071731.md +64 -0
  4. package/.trae/documents/plan_20251222_074833.md +23 -0
  5. package/.trae/documents/plan_20251222_081357.md +23 -0
  6. package/README.md +311 -0
  7. package/dist/demo.html +10 -0
  8. package/dist/static/css/chunk-src.895fe3a6.css +1 -0
  9. package/dist/static/img/demo.160f90e8.png +0 -0
  10. package/dist/static/img/demo.18a57edc.gif +0 -0
  11. package/dist/static/img/demo.c7910627.gif +0 -0
  12. package/dist/traffic-diagram.common.chunk-lodash.js +17212 -0
  13. package/dist/traffic-diagram.common.chunk-src.js +2015 -0
  14. package/dist/traffic-diagram.common.js +33788 -0
  15. package/dist/traffic-diagram.umd.chunk-lodash.js +17212 -0
  16. package/dist/traffic-diagram.umd.chunk-src.js +2015 -0
  17. package/dist/traffic-diagram.umd.js +33798 -0
  18. package/dist/traffic-diagram.umd.min.chunk-lodash.js +9 -0
  19. package/dist/traffic-diagram.umd.min.chunk-src.js +1 -0
  20. package/dist/traffic-diagram.umd.min.js +12 -0
  21. package/package.json +46 -0
  22. package/src/App.vue +269 -0
  23. package/src/assets/images/green-wave/demo.gif +0 -0
  24. package/src/assets/images/traffic-lane/demo.gif +0 -0
  25. package/src/assets/images/traffic-lane/demo.png +0 -0
  26. package/src/assets/images/traffic-lane/directon.png +0 -0
  27. package/src/assets/images/traffic-lane/left-ui.png +0 -0
  28. package/src/assets/images/traffic-lane/left.png +0 -0
  29. package/src/assets/images/traffic-lane/leftRight-ui.png +0 -0
  30. package/src/assets/images/traffic-lane/leftRight.png +0 -0
  31. package/src/assets/images/traffic-lane/otherPic-ui.png +0 -0
  32. package/src/assets/images/traffic-lane/otherPic.png +0 -0
  33. package/src/assets/images/traffic-lane/right-ui.png +0 -0
  34. package/src/assets/images/traffic-lane/right.png +0 -0
  35. package/src/assets/images/traffic-lane/straight-ui.png +0 -0
  36. package/src/assets/images/traffic-lane/straight.png +0 -0
  37. package/src/assets/images/traffic-lane/straightLeft-ui.png +0 -0
  38. package/src/assets/images/traffic-lane/straightLeft.png +0 -0
  39. package/src/assets/images/traffic-lane/straightLeftRight-ui.png +0 -0
  40. package/src/assets/images/traffic-lane/straightLeftRight.png +0 -0
  41. package/src/assets/images/traffic-lane/straightRight-ui.png +0 -0
  42. package/src/assets/images/traffic-lane/straightRight.png +0 -0
  43. package/src/assets/images/traffic-lane/turnAround-ui.png +0 -0
  44. package/src/assets/images/traffic-lane/turnAround.png +0 -0
  45. package/src/components/green-wave/index.vue +671 -0
  46. package/src/components/traffic-lane/index.vue +1020 -0
  47. package/src/index.js +32 -0
  48. package/src/libs/bus.js +24 -0
  49. package/src/libs/pics-load-tool.js +23 -0
  50. package/src/main.js +14 -0
  51. package/vue.config.js +279 -0
@@ -0,0 +1,1020 @@
1
+ <template>
2
+ <div id="canvasWrapper">
3
+ <canvas class="crossing-box" ref="road"></canvas>
4
+ <transition enter-active-class="animate__animated animate__bounceInLeft">
5
+ <div class="legend-box" ref="legendBox" v-show="showLegend">
6
+ <div
7
+ class="legend-item"
8
+ v-for="(item, index) in this.laneDirections"
9
+ :key="index"
10
+ :style="{background: item.laneDirName === currentLaneDirName ? laneActiveColor : 'rgba(31, 200, 255, 10%)'}"
11
+ @click="chooseImg(item)"
12
+ >
13
+ <img :src="item.img" alt="" />
14
+ <div style="color: #fff">{{ item.laneDirName }}</div>
15
+ </div>
16
+ </div>
17
+ </transition>
18
+ </div>
19
+ </template>
20
+
21
+ <script>
22
+ import _ from 'lodash';
23
+ import { fabric } from 'fabric';
24
+ // 方向对应的图标
25
+ import leftDirIcon from '@/assets/images/traffic-lane/left.png';
26
+ import straightDirIcon from '@/assets/images/traffic-lane/straight.png';
27
+ import rightDirIcon from '@/assets/images/traffic-lane/right.png';
28
+ import straightLeftDirIcon from '@/assets/images/traffic-lane/straightLeft.png';
29
+ import leftRightDirIcon from '@/assets/images/traffic-lane/leftRight.png';
30
+ import straightRightDirIcon from '@/assets/images/traffic-lane/straightRight.png';
31
+ import straightLeftRightDirIcon from '@/assets/images/traffic-lane/straightLeftRight.png';
32
+ import turnAroundDirIcon from '@/assets/images/traffic-lane/turnAround.png';
33
+ import otherDirIcon from '@/assets/images/traffic-lane/otherPic.png';
34
+ export default {
35
+ name: 'traffic-lane',
36
+ props: {
37
+ /**
38
+ * 画布数据
39
+ * - Possible values:
40
+ * - Array
41
+ * 字段描述:
42
+ * - id: string - 车道ID
43
+ * - laneDirCode: string - 车道方向编码
44
+ * - laneDirName: string - 车道方向名称
45
+ * - inDirection: string - 车道入口方向
46
+ * - laneNo: number - 车道编号
47
+ * - congestionFactor: number - 拥堵值
48
+ */
49
+ laneDiagramData: {
50
+ type: Array,
51
+ default: () => [],
52
+ },
53
+ /**
54
+ * 画布宽度
55
+ */
56
+ canvasHeight: {
57
+ type: Number,
58
+ default: 300,
59
+ },
60
+ /**
61
+ * 配置项(只有进行车道-流向配置时可在canvas图上点击替换方向图标
62
+ * - Possible values:
63
+ * - LaneConfig
64
+ */
65
+ configType: {
66
+ type: String,
67
+ default: '',
68
+ },
69
+ /**
70
+ * 车道宽度
71
+ */
72
+ laneWidth: {
73
+ // 车道宽度
74
+ type: Number,
75
+ default: 22,
76
+ },
77
+ /**
78
+ * 车道分隔线长度
79
+ */
80
+ laneDividerLength: {
81
+ type: Number,
82
+ default: 80,
83
+ },
84
+ /**
85
+ * 车道-流向配置项
86
+ */
87
+ laneDirections: {
88
+ type: Array,
89
+ default: () => [
90
+ { img: leftDirIcon, laneDirName: '左转' },
91
+ { img: straightDirIcon, laneDirName: '直行' },
92
+ { img: rightDirIcon, laneDirName: '右转' },
93
+ { img: straightLeftDirIcon, laneDirName: '直左' },
94
+ { img: leftRightDirIcon, laneDirName: '左右' },
95
+ { img: straightRightDirIcon, laneDirName: '直右' },
96
+ { img: straightLeftRightDirIcon, laneDirName: '直左右' },
97
+ { img: turnAroundDirIcon, laneDirName: '掉头' },
98
+ { img: otherDirIcon, laneDirName: '其他' },
99
+ ],
100
+ },
101
+ /**
102
+ * 最大拥堵值
103
+ */
104
+ maxCongestionValue: {
105
+ type: Number,
106
+ default: 1,
107
+ },
108
+ /**
109
+ * 是否可点击
110
+ */
111
+ clickEvent: {
112
+ // 是否添加点击事件
113
+ type: Boolean,
114
+ default: false,
115
+ },
116
+ /**
117
+ * 是否显示拥堵指标值
118
+ */
119
+ congestionFactorVisible: {
120
+ // 是否在车道上显示指标值
121
+ type: Boolean,
122
+ default: false,
123
+ },
124
+ /**
125
+ * 是否可多选车道
126
+ */
127
+ multiple: {
128
+ // 是否可多选车道
129
+ type: Boolean,
130
+ default: false,
131
+ },
132
+ // 已被选择的车道
133
+ selectedLane:{
134
+ type:String,
135
+ default:''
136
+ },
137
+ // 线段激活颜色
138
+ laneActiveColor: {
139
+ type: String,
140
+ default: '#27ffd5',
141
+ }
142
+ },
143
+ data() {
144
+ return {
145
+ data: [],
146
+ // laneWidth: 22, // 车道宽度
147
+ canvas: null,
148
+ laneDirCodeOption: [
149
+ { laneDirCode: '101', img: 'left', laneDirName: '左转' },
150
+ { laneDirCode: '102', img: 'straight', laneDirName: '直行' },
151
+ { laneDirCode: '103', img: 'right', laneDirName: '右转' },
152
+ { laneDirCode: '104', img: 'straightLeft', laneDirName: '直左' },
153
+ { laneDirCode: '109', img: 'leftRight', laneDirName: '左右' },
154
+ { laneDirCode: '105', img: 'straightRight', laneDirName: '直右' },
155
+ { laneDirCode: '106', img: 'straightLeftRight', laneDirName: '直左右' },
156
+ { laneDirCode: '107', img: 'turnAround', laneDirName: '掉头' },
157
+ { laneDirCode: '108', img: 'otherPic', laneDirName: '其他' },
158
+ ],
159
+ currentLaneDirName: '',
160
+ currentGroup: null,
161
+ currentGroupBK: null,
162
+ showLegend: false,
163
+ canvasWidth: 300,
164
+ chooseGroup: [],
165
+ resizeHandler: null,
166
+ };
167
+ },
168
+ components: {},
169
+ computed: {},
170
+ created() {
171
+ // 使用lodash的debounce函数添加防抖处理,限制resize事件触发频率
172
+ this.resizeHandler = _.debounce(() => {
173
+ if (this.canvas) {
174
+ this.canvas.dispose();
175
+ this.canvas = null;
176
+ this.$nextTick(() => {
177
+ this.init();
178
+ });
179
+ }
180
+ }, 200);
181
+
182
+ // 保存事件处理函数引用,以便后续移除
183
+ window.addEventListener('resize', this.resizeHandler);
184
+ },
185
+ mounted() {
186
+ this.init();
187
+ },
188
+ beforeDestroy() {
189
+ // 移除resize事件监听器,防止内存泄漏
190
+ window.removeEventListener('resize', this.resizeHandler);
191
+
192
+ // 取消防抖函数
193
+ this.resizeHandler.cancel();
194
+
195
+ // 销毁canvas实例
196
+ if (this.canvas) {
197
+ this.canvas.dispose();
198
+ this.canvas = null;
199
+ }
200
+ },
201
+ watch: {
202
+ laneDiagramData: {
203
+ handler(val) {
204
+ if (this.canvas) {
205
+ this.canvas.dispose();
206
+ this.canvas = null;
207
+ }
208
+ this.$nextTick(() => {
209
+ this.init();
210
+ });
211
+ },
212
+ deep: true,
213
+ },
214
+ },
215
+ methods: {
216
+ init() {
217
+ const el = document.getElementById('canvasWrapper');
218
+ this.canvasWidth = el.offsetWidth;
219
+ this.canvas = new fabric.Canvas(this.$refs.road, {
220
+ backgroundColor: 'rgba(17,94,7,0.4)',
221
+ selection: false,
222
+ width: this.canvasWidth,
223
+ height: this.canvasHeight,
224
+ });
225
+
226
+ // 找到各方向进口车道数
227
+ const nLaneNum = this.laneDiagramData.filter(item => item.inDirection === '北').length;
228
+ const wLaneNum = this.laneDiagramData.filter(item => item.inDirection === '西').length;
229
+ const sLaneNum = this.laneDiagramData.filter(item => item.inDirection === '南').length;
230
+ const eLaneNum = this.laneDiagramData.filter(item => item.inDirection === '东').length;
231
+
232
+ // 路口底图点位,从北与西交叉点开始(标准十字路口)
233
+ const point1 = {
234
+ x: this.canvasWidth / 2 - this.laneWidth * nLaneNum,
235
+ y: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
236
+ };
237
+ const point2 = {
238
+ x: 0,
239
+ y: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
240
+ };
241
+ const point3 = {
242
+ x: 0,
243
+ y: this.canvasHeight / 2 + this.laneWidth * wLaneNum,
244
+ };
245
+ const point4 = {
246
+ x: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
247
+ y: this.canvasHeight / 2 + this.laneWidth * wLaneNum,
248
+ };
249
+ const point5 = {
250
+ x: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
251
+ y: this.canvasHeight,
252
+ };
253
+ const point6 = {
254
+ x: this.canvasWidth / 2 + this.laneWidth * sLaneNum,
255
+ y: this.canvasHeight,
256
+ };
257
+ const point7 = {
258
+ x: this.canvasWidth / 2 + this.laneWidth * sLaneNum,
259
+ y: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
260
+ };
261
+ const point8 = {
262
+ x: this.canvasWidth,
263
+ y: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
264
+ };
265
+ const point9 = {
266
+ x: this.canvasWidth,
267
+ y: this.canvasHeight / 2 - this.laneWidth * eLaneNum,
268
+ };
269
+ const point10 = {
270
+ x: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
271
+ y: this.canvasHeight / 2 - this.laneWidth * eLaneNum,
272
+ };
273
+ const point11 = {
274
+ x: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
275
+ y: 0,
276
+ };
277
+ const point12 = {
278
+ x: this.canvasWidth / 2 - this.laneWidth * nLaneNum,
279
+ y: 0,
280
+ };
281
+ // 画路口图连线
282
+ var raodBase = new fabric.Polygon(
283
+ [
284
+ point1,
285
+ wLaneNum !== 0 ? point2 : {},
286
+ wLaneNum !== 0 ? point3 : {},
287
+ point4,
288
+ sLaneNum !== 0 ? point5 : {},
289
+ sLaneNum !== 0 ? point6 : {},
290
+ point7,
291
+ eLaneNum !== 0 ? point8 : {},
292
+ eLaneNum !== 0 ? point9 : {},
293
+ point10,
294
+ nLaneNum !== 0 ? point11 : {},
295
+ nLaneNum !== 0 ? point12 : point1,
296
+ ],
297
+ {
298
+ fill: '#989898',
299
+ stroke: '#fff',
300
+ strokeWidth: 2,
301
+ selectable: false,
302
+ },
303
+ );
304
+ this.canvas.add(raodBase);
305
+
306
+ raodBase.on('mousedown', () => {
307
+ // 点击路口灰色底图部分,关闭图例弹窗
308
+ this.showLegend = false;
309
+ });
310
+ // 绘制黄实线
311
+ const nYellowLine = {
312
+ x1: this.canvasWidth / 2,
313
+ y1: 0,
314
+ x2: this.canvasWidth / 2,
315
+ y2: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
316
+ }; // 北方向
317
+ const wYellowLine = {
318
+ x1: 0,
319
+ y1: this.canvasHeight / 2,
320
+ x2: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
321
+ y2: this.canvasHeight / 2,
322
+ }; // 西方向
323
+ const sYellowLine = {
324
+ x1: this.canvasWidth / 2,
325
+ y1: this.canvasHeight,
326
+ x2: this.canvasWidth / 2,
327
+ y2: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
328
+ }; // 南方向
329
+ const eYellowLine = {
330
+ x1: this.canvasWidth,
331
+ y1: this.canvasHeight / 2,
332
+ x2: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
333
+ y2: this.canvasHeight / 2,
334
+ }; // 东方向
335
+ const lines = [
336
+ nLaneNum ? nYellowLine : {},
337
+ wLaneNum ? wYellowLine : {},
338
+ sLaneNum ? sYellowLine : {},
339
+ eLaneNum ? eYellowLine : {}, // 东方向
340
+ ];
341
+ lines.forEach(line => {
342
+ const fabricLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
343
+ stroke: '#e6c130', // 线条颜色
344
+ strokeWidth: 2, // 线条宽度
345
+ });
346
+ this.canvas.add(fabricLine);
347
+ });
348
+
349
+ // 绘制车道线
350
+ const laneLineLength = this.laneDividerLength; // 车道线长度
351
+ // 绘制各方向车道组(北西南东)
352
+ if (nLaneNum > 0) {
353
+ const nData = this.laneDiagramData.filter(item => item.inDirection === '北');
354
+ for (let i = 0; i < nData.length; i++) {
355
+ const line = {
356
+ x1: this.canvasWidth / 2 - this.laneWidth * (i + 1),
357
+ y1: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
358
+ x2: this.canvasWidth / 2 - this.laneWidth * (i + 1),
359
+ y2: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum) - laneLineLength,
360
+ };
361
+
362
+ // 添加图标
363
+ const temp = nData[i];
364
+ const value = Math.round(temp.congestionFactor * 10) / 10;
365
+ const laneDirItem = this.laneDirections.find(item => item.laneDirName === temp.laneDirName);
366
+ if (laneDirItem) {
367
+ fabric.Image.fromURL(
368
+ laneDirItem.img,
369
+ img => {
370
+ img.scaleToWidth(this.laneWidth);
371
+ // 绘制车道白线
372
+ const fabricLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
373
+ stroke: '#fff', // 线条颜色
374
+ strokeWidth: 1, // 线条宽度
375
+ });
376
+ // 绘制车道矩形
377
+ var rect = new fabric.Rect({
378
+ width: this.laneWidth,
379
+ height: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
380
+ fill: this.calcBgColor(value),
381
+ left: this.canvasWidth / 2 - this.laneWidth * (i + 1),
382
+ top: 0,
383
+ });
384
+ if (this.currentGroup && this.currentGroup.name === `N_L${temp.laneNo}`) {
385
+ rect.set('fill', this.laneActiveColor);
386
+ }
387
+ // 绘制文本
388
+ var text = new fabric.Textbox(`${Number(temp.laneNo)}`, {
389
+ fontSize: 18,
390
+ left: this.canvasWidth / 2 - this.laneWidth * (i + 1),
391
+ top:
392
+ (this.canvasHeight -
393
+ this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum) -
394
+ this.laneWidth * wLaneNum) /
395
+ 2 -
396
+ 22,
397
+ textAlign: 'center',
398
+ width: this.laneWidth,
399
+ fill: '#fff',
400
+ angle: 180,
401
+ originX: 'right',
402
+ originY: 'bottom',
403
+ });
404
+ let text2 = new fabric.Textbox(`${value ? value : '0'}`, {
405
+ fontSize: 12,
406
+ left: this.canvasWidth / 2 - this.laneWidth * (i + 1 - 0.25),
407
+ top: 5,
408
+ textAlign: 'left',
409
+ width: this.laneWidth,
410
+ fill: '#fff',
411
+ angle: 90,
412
+ originX: 'left',
413
+ originY: 'bottom',
414
+ });
415
+ let groupList = [];
416
+ if (this.congestionFactorVisible) {
417
+ groupList = [rect, text, text2, img, fabricLine];
418
+ } else {
419
+ groupList = [rect, text, img, fabricLine];
420
+ }
421
+ var group = new fabric.Group(groupList, {
422
+ name: `N_L${temp.laneNo}`,
423
+ objectCaching: false,
424
+ selectable: false,
425
+ });
426
+ this.canvas.add(group);
427
+ if (this.clickEvent) {
428
+ group.on('mousedown', e => {
429
+ if (this.configType === 'LaneConfig') {
430
+ this.showLegend = true;
431
+ }
432
+ this.mousedownFun(e);
433
+ });
434
+ group.on('mousemove', e => {
435
+ this.canvas.setCursor('pointer');
436
+ });
437
+ }
438
+ },
439
+ {
440
+ name: laneDirItem.laneDirName,
441
+ left: this.canvasWidth / 2 - this.laneWidth * (i + 1),
442
+ top:
443
+ (this.canvasHeight - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum) - this.laneWidth * wLaneNum) /
444
+ 2 -
445
+ 50,
446
+ angle: 180,
447
+ originX: 'right',
448
+ originY: 'bottom',
449
+ },
450
+ );
451
+ } else {
452
+ console.error(`${temp.inDirection}向 - ${temp.laneDirName}车道,图标缺少!`);
453
+ }
454
+ }
455
+ }
456
+
457
+ if (wLaneNum > 0) {
458
+ const wData = this.laneDiagramData.filter(item => item.inDirection === '西');
459
+ for (let i = 0; i < wData.length; i++) {
460
+ const line = {
461
+ x1: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
462
+ y1: this.canvasHeight / 2 + this.laneWidth * (i + 1),
463
+ x2: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum) - laneLineLength,
464
+ y2: this.canvasHeight / 2 + this.laneWidth * (i + 1),
465
+ };
466
+
467
+ // 添加图标
468
+ const temp = wData[i];
469
+ const value = Math.round(temp.congestionFactor * 10) / 10;
470
+ const laneDirItem = this.laneDirections.find(item => item.laneDirName === temp.laneDirName);
471
+ if (laneDirItem) {
472
+ fabric.Image.fromURL(
473
+ laneDirItem.img,
474
+ img => {
475
+ img.scaleToWidth(this.laneWidth);
476
+ // 绘制车道白线
477
+ const fabricLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
478
+ stroke: '#fff', // 线条颜色
479
+ strokeWidth: 1, // 线条宽度
480
+ });
481
+
482
+ // 绘制车道矩形
483
+ var rect = new fabric.Rect({
484
+ // width: laneLineLength,
485
+ width: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
486
+ height: this.laneWidth,
487
+ fill: this.calcBgColor(value),
488
+ left: 0,
489
+ top: this.canvasHeight / 2 + this.laneWidth * i,
490
+ });
491
+ if (this.currentGroup && this.currentGroup.name === `W_L${temp.laneNo}`) {
492
+ rect.set('fill', this.laneActiveColor);
493
+ }
494
+ var text = new fabric.Textbox(`${Number(temp.laneNo)}`, {
495
+ // 绘制文本
496
+ fontSize: 18,
497
+ left: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
498
+ top: this.canvasHeight / 2 + this.laneWidth * i,
499
+ textAlign: 'center',
500
+ width: this.laneWidth,
501
+ fill: '#fff',
502
+ angle: 90,
503
+ originX: 'left',
504
+ originY: 'top',
505
+ });
506
+ let text2 = new fabric.Textbox(`${value ? value : '0'}`, {
507
+ // 绘制文本
508
+ fontSize: 12,
509
+ left: 5,
510
+ top: this.canvasHeight / 2 + this.laneWidth * (i + 0.25),
511
+ textAlign: 'left',
512
+ width: this.laneWidth,
513
+ fill: '#fff',
514
+ originX: 'left',
515
+ originY: 'top',
516
+ });
517
+ let groupList = [];
518
+ if (this.congestionFactorVisible) {
519
+ groupList = [rect, text, text2, img, fabricLine];
520
+ } else {
521
+ groupList = [rect, text, img, fabricLine];
522
+ }
523
+ var group = new fabric.Group(groupList, {
524
+ name: `W_L${temp.laneNo}`,
525
+ objectCaching: false,
526
+ selectable: false,
527
+ });
528
+ this.canvas.add(group);
529
+
530
+ if (this.clickEvent) {
531
+ group.on('mousedown', e => {
532
+ if (this.configType === 'LaneConfig') {
533
+ this.showLegend = true;
534
+ }
535
+ this.mousedownFun(e);
536
+ });
537
+ group.on('mousemove', e => {
538
+ this.canvas.setCursor('pointer');
539
+ });
540
+ }
541
+ },
542
+ {
543
+ name: laneDirItem.laneDirName,
544
+ left: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum) - 30,
545
+ top: this.canvasHeight / 2 + this.laneWidth * i,
546
+ angle: 90,
547
+ originX: 'left',
548
+ originY: 'top',
549
+ },
550
+ );
551
+ } else {
552
+ console.error(`${temp.inDirection}向 - ${temp.laneDirName}车道,图标缺少!`);
553
+ }
554
+ }
555
+ }
556
+
557
+ if (sLaneNum > 0) {
558
+ const sData = this.laneDiagramData.filter(item => item.inDirection === '南');
559
+ for (let i = 0; i < sData.length; i++) {
560
+ const line = {
561
+ x1: this.canvasWidth / 2 + this.laneWidth * (i + 1),
562
+ y1: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
563
+ x2: this.canvasWidth / 2 + this.laneWidth * (i + 1),
564
+ y2: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum) + laneLineLength,
565
+ };
566
+
567
+ // 添加图标
568
+ const temp = sData[i];
569
+ const value = Math.round(temp.congestionFactor * 10) / 10;
570
+ const laneDirItem = this.laneDirections.find(item => item.laneDirName === temp.laneDirName);
571
+ if (laneDirItem) {
572
+ fabric.Image.fromURL(
573
+ laneDirItem.img,
574
+ img => {
575
+ img.scaleToWidth(this.laneWidth);
576
+ // 绘制车道白线
577
+ const fabricLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
578
+ stroke: '#fff', // 线条颜色
579
+ strokeWidth: 1, // 线条宽度
580
+ });
581
+
582
+ // 绘制车道矩形
583
+ var rect = new fabric.Rect({
584
+ width: this.laneWidth,
585
+ // height: laneLineLength,
586
+ height: this.canvasHeight / 2,
587
+ fill: this.calcBgColor(value),
588
+ left: this.canvasWidth / 2 + this.laneWidth * i,
589
+ top: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
590
+ });
591
+ if (this.currentGroup && this.currentGroup.name === `S_L${temp.laneNo}`) {
592
+ rect.set('fill', this.laneActiveColor);
593
+ }
594
+ var text = new fabric.Textbox(`${Number(temp.laneNo)}`, {
595
+ // 绘制文本
596
+ fontSize: 18,
597
+ left: this.canvasWidth / 2 + this.laneWidth * i,
598
+ top: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
599
+ textAlign: 'center',
600
+ width: this.laneWidth,
601
+ fill: '#fff',
602
+ });
603
+ let text2 = new fabric.Textbox(`${value ? value : '0'}`, {
604
+ // 绘制文本
605
+ fontSize: 12,
606
+ left: this.canvasWidth / 2 + this.laneWidth * (i + 0.25),
607
+ top: this.canvasHeight - 5,
608
+ textAlign: 'left',
609
+ width: this.laneWidth,
610
+ fill: '#fff',
611
+ angle: -90,
612
+ });
613
+ let groupList = [];
614
+ if (this.congestionFactorVisible) {
615
+ groupList = [rect, text, text2, img, fabricLine];
616
+ } else {
617
+ groupList = [rect, text, img, fabricLine];
618
+ }
619
+ var group = new fabric.Group(groupList, {
620
+ name: `S_L${temp.laneNo}`,
621
+ objectCaching: false,
622
+ selectable: false,
623
+ });
624
+ this.canvas.add(group);
625
+ if (this.clickEvent) {
626
+ group.on('mousedown', e => {
627
+ if (this.configType === 'LaneConfig') {
628
+ this.showLegend = true;
629
+ }
630
+ this.mousedownFun(e);
631
+ });
632
+ group.on('mousemove', e => {
633
+ this.canvas.setCursor('pointer');
634
+ });
635
+ }
636
+ },
637
+ {
638
+ name: laneDirItem.laneDirName,
639
+ left: this.canvasWidth / 2 + this.laneWidth * i,
640
+ top: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum) + 30,
641
+ },
642
+ );
643
+ }
644
+ }
645
+ }
646
+
647
+ if (eLaneNum > 0) {
648
+ const eData = this.laneDiagramData.filter(item => item.inDirection === '东');
649
+ for (let i = 0; i < eData.length; i++) {
650
+ const line = {
651
+ x1: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
652
+ y1: this.canvasHeight / 2 - this.laneWidth * (i + 1),
653
+ x2: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum) + laneLineLength,
654
+ y2: this.canvasHeight / 2 - this.laneWidth * (i + 1),
655
+ };
656
+
657
+ // 添加图标
658
+ const temp = eData[i];
659
+ const value = Math.round(temp.congestionFactor * 10) / 10;
660
+ const laneDirItem = this.laneDirections.find(item => item.laneDirName === temp.laneDirName);
661
+ if (laneDirItem) {
662
+ fabric.Image.fromURL(
663
+ laneDirItem.img,
664
+ img => {
665
+ img.scaleToWidth(this.laneWidth);
666
+ // 绘制车道白线
667
+ const fabricLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
668
+ stroke: '#fff', // 线条颜色
669
+ strokeWidth: 1, // 线条宽度
670
+ });
671
+ // 绘制车道矩形
672
+ var rect = new fabric.Rect({
673
+ width: this.canvasWidth / 2,
674
+ height: this.laneWidth,
675
+ fill: this.calcBgColor(value),
676
+ left: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
677
+ top: this.canvasHeight / 2 - this.laneWidth * (i + 1),
678
+ });
679
+ if (this.currentGroup && this.currentGroup.name === `E_L${temp.laneNo}`) {
680
+ rect.set('fill', this.laneActiveColor);
681
+ }
682
+ var text = new fabric.Textbox(`${Number(temp.laneNo)}`, {
683
+ // 绘制文本
684
+ fontSize: 18,
685
+ left: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
686
+ top: this.canvasHeight / 2 - this.laneWidth * (i + 1),
687
+ textAlign: 'center',
688
+ width: this.laneWidth,
689
+ fill: '#fff',
690
+ angle: -90,
691
+ originX: 'right',
692
+ originY: 'top',
693
+ });
694
+ let text2 = new fabric.Textbox(`${value ? value : '0'}`, {
695
+ // 绘制文本
696
+ fontSize: 12,
697
+ left: this.canvasWidth - 5,
698
+ top: this.canvasHeight / 2 - this.laneWidth * (i + 1 - 0.25),
699
+ textAlign: 'center',
700
+ width: this.laneWidth,
701
+ fill: '#fff',
702
+ // angle: -90,
703
+ originX: 'right',
704
+ originY: 'top',
705
+ });
706
+ let groupList = [];
707
+ if (this.congestionFactorVisible) {
708
+ groupList = [rect, text, text2, img, fabricLine];
709
+ } else {
710
+ groupList = [rect, text, img, fabricLine];
711
+ }
712
+ var group = new fabric.Group(groupList, {
713
+ name: `E_L${temp.laneNo}`,
714
+ objectCaching: false,
715
+ selectable: false,
716
+ });
717
+
718
+ this.canvas.add(group);
719
+ if (this.clickEvent) {
720
+ group.on('mousedown', e => {
721
+ if (this.configType === 'LaneConfig') {
722
+ this.showLegend = true;
723
+ }
724
+ this.mousedownFun(e);
725
+ });
726
+ group.on('mousemove', e => {
727
+ this.canvas.setCursor('pointer');
728
+ });
729
+ }
730
+ },
731
+ {
732
+ name: laneDirItem.laneDirName,
733
+ left: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum) + 30,
734
+ top: this.canvasHeight / 2 - this.laneWidth * (i + 1),
735
+ angle: -90,
736
+ originX: 'right',
737
+ originY: 'top',
738
+ },
739
+ );
740
+ }
741
+ }
742
+ }
743
+ // 北停止线
744
+ const nStopLine = {
745
+ x1: this.canvasWidth / 2 - this.laneWidth * nLaneNum,
746
+ y1: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
747
+ x2: this.canvasWidth / 2,
748
+ y2: this.canvasHeight / 2 - this.laneWidth * (eLaneNum ? eLaneNum : wLaneNum),
749
+ };
750
+ // 西停止线
751
+ const wStopLine = {
752
+ x1: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
753
+ y1: this.canvasHeight / 2,
754
+ x2: this.canvasWidth / 2 - this.laneWidth * (nLaneNum ? nLaneNum : sLaneNum),
755
+ y2: this.canvasHeight / 2 + this.laneWidth * wLaneNum,
756
+ };
757
+
758
+ // 南停止线
759
+ const sStopLine = {
760
+ x1: this.canvasWidth / 2,
761
+ y1: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
762
+ x2: this.canvasWidth / 2 + this.laneWidth * sLaneNum,
763
+ y2: this.canvasHeight / 2 + this.laneWidth * (wLaneNum ? wLaneNum : eLaneNum),
764
+ };
765
+
766
+ // 东停止线
767
+ const eStopLine = {
768
+ x1: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
769
+ y1: this.canvasHeight / 2 - this.laneWidth * eLaneNum,
770
+ x2: this.canvasWidth / 2 + this.laneWidth * (sLaneNum ? sLaneNum : nLaneNum),
771
+ y2: this.canvasHeight / 2,
772
+ };
773
+ let laneLines = [];
774
+ laneLines.push(wStopLine);
775
+ laneLines.push(nStopLine);
776
+ laneLines.push(sStopLine);
777
+ laneLines.push(eStopLine);
778
+ laneLines.forEach(line => {
779
+ this.addFabricLine(line, '#fff', 1);
780
+ });
781
+ },
782
+ changeLane(laneDirCodes) {
783
+ if (laneDirCodes) {
784
+ if (this.canvas) {
785
+ laneDirCodes.forEach(item => {
786
+ // 根据车道编号获取canvas上指定group
787
+ const currentGroup = this.canvas.getObjects().find(o => o.type === 'group' && o.name === item);
788
+ // 获取当前group下车道对象
789
+ if (currentGroup) {
790
+ var rect = currentGroup.getObjects().find(o => {
791
+ return o.type === 'rect';
792
+ });
793
+ rect.set('fill', this.laneActiveColor);
794
+ }
795
+ });
796
+ this.canvas.getObjects().forEach(o => {
797
+ if (o.type === 'group' && !laneDirCodes.includes(o.name)) {
798
+ var rect = o.getObjects().find(o => {
799
+ return o.type === 'rect';
800
+ });
801
+ rect.set('fill', 'transparent');
802
+ }
803
+ });
804
+ this.canvas.renderAll();
805
+ }
806
+ }
807
+ },
808
+ // 更改车道流向(canvas对应更新)
809
+ changeLaneDir({ laneDirCode, laneDirName }) {
810
+ // 根据车道编号获取canvas上指定group
811
+ if (this.canvas) {
812
+ const currentGroup = this.canvas.getObjects().find(o => o.type === 'group' && o.name === laneDirCode);
813
+ // 获取当前group下车道对象
814
+ var rect = currentGroup.getObjects().find(o => {
815
+ return o.type === 'rect';
816
+ });
817
+ // 获取当前group下图标
818
+ var image = currentGroup.getObjects().find(o => {
819
+ return o.type === 'image';
820
+ });
821
+ const laneDirItem = this.laneDirections.find(item => item.laneDirName === laneDirName);
822
+ const imgSrc = laneDirItem.img;
823
+ image.setSrc(imgSrc, () => {
824
+ rect.set('fill', this.laneActiveColor);
825
+ this.canvas.renderAll();
826
+ });
827
+ this.canvas.getObjects().forEach(o => {
828
+ if (o.type === 'group' && o.name !== laneDirCode) {
829
+ var rect = o.getObjects().find(o => {
830
+ return o.type === 'rect';
831
+ });
832
+ rect.set('fill', 'transparent');
833
+ }
834
+ });
835
+
836
+ this.canvas.renderAll();
837
+ }
838
+ },
839
+ addFabricLine(line, color, strokeWidth = 1) {
840
+ // 绘制线
841
+ const fabricLine = new fabric.Line([line.x1, line.y1, line.x2, line.y2], {
842
+ stroke: color, // 线条颜色
843
+ strokeWidth: strokeWidth, // 线条宽度
844
+ });
845
+ this.canvas.add(fabricLine);
846
+ },
847
+ mousedownFun(e) {
848
+ // 鼠标点击事件
849
+ // 根据车道编号获取canvas上指定group
850
+ const currentGroup = this.canvas.getObjects().find(o => o.type === 'group' && o.name === e.target.name);
851
+ // 获取当前group下车道对象
852
+ var rect = currentGroup.getObjects().find(o => {
853
+ return o.type === 'rect';
854
+ });
855
+
856
+ if (this.multiple) {
857
+ // 车道可多选
858
+ if (rect.choosen) {
859
+ rect.set('fill', 'transparent');
860
+ rect.set('choosen', false);
861
+ let index = this.chooseGroup.findIndex(item => item === currentGroup.name);
862
+ this.chooseGroup.splice(index, 1);
863
+ } else {
864
+ rect.set('choosen', true);
865
+ rect.set('fill', this.laneActiveColor);
866
+ this.chooseGroup.push(currentGroup.name);
867
+ }
868
+ this.$emit('chooseLane', this.chooseGroup);
869
+ } else {
870
+ rect.set('fill', this.laneActiveColor);
871
+ this.currentGroup = currentGroup;
872
+ this.currentGroupBK = _.cloneDeep(currentGroup);
873
+ this.canvas.getObjects().forEach(o => {
874
+ if (o.type === 'group' && o.name !== e.target.name) {
875
+ var rect = o.getObjects().find(o => {
876
+ return o.type === 'rect';
877
+ });
878
+ rect.set('fill', 'transparent');
879
+ }
880
+ });
881
+ this.$nextTick(() => {
882
+ const ele = this.$refs.legendBox;
883
+ ele.style.top = e.target.top + 'px';
884
+ ele.style.left = e.target.left + 80 + 'px';
885
+ this.currentLaneDirName = e.target.getObjects().filter(item => item.type === 'image')[0].name;
886
+ // 选择右侧表格一行数据
887
+ this.$emit('selectLane', {
888
+ laneDirCode: this.currentGroup.name,
889
+ });
890
+ });
891
+ }
892
+ this.canvas.renderAll();
893
+ },
894
+
895
+ chooseImg(item) {
896
+ // 选择图片
897
+ this.currentLaneDirName = item.laneDirName;
898
+ // 获取当前group下图标
899
+ var image = this.currentGroup.getObjects().find(o => {
900
+ return o.type === 'image';
901
+ });
902
+ const imgSrc = item.img;
903
+ image.setSrc(imgSrc, () => {
904
+ this.currentGroup = _.cloneDeep(this.currentGroupBK);
905
+ });
906
+ this.$emit('changeLaneDir', {
907
+ laneDirCode: this.currentGroup.name,
908
+ laneDirName: item.laneDirName,
909
+ });
910
+ },
911
+ calcBgColor(value) {
912
+ if (value || value == 0) {
913
+ const colorList = [
914
+ { r: 0, g: 104, b: 55 },
915
+ { r: 26, g: 152, b: 80 },
916
+ { r: 102, g: 189, b: 99 },
917
+ { r: 166, g: 217, b: 106 },
918
+ { r: 217, g: 239, b: 139 },
919
+ { r: 255, g: 255, b: 191 },
920
+ { r: 254, g: 224, b: 139 },
921
+ { r: 253, g: 174, b: 97 },
922
+ { r: 244, g: 109, b: 67 },
923
+ { r: 215, g: 48, b: 39 },
924
+ { r: 165, g: 0, b: 38 },
925
+ ];
926
+ let startColor = '';
927
+ let endColor = '';
928
+ const percentage = value / this.maxCongestionValue; // 获取百分比
929
+ if (percentage >= 0 && percentage <= 0.1) {
930
+ startColor = colorList[0];
931
+ endColor = colorList[1];
932
+ } else if (percentage > 0.1 && percentage <= 0.2) {
933
+ startColor = colorList[1];
934
+ endColor = colorList[2];
935
+ } else if (percentage > 0.2 && percentage <= 0.3) {
936
+ startColor = colorList[2];
937
+ endColor = colorList[3];
938
+ } else if (percentage > 0.3 && percentage <= 0.4) {
939
+ startColor = colorList[3];
940
+ endColor = colorList[4];
941
+ } else if (percentage > 0.4 && percentage <= 0.5) {
942
+ startColor = colorList[4];
943
+ endColor = colorList[5];
944
+ } else if (percentage > 0.5 && percentage <= 0.6) {
945
+ startColor = colorList[5];
946
+ endColor = colorList[6];
947
+ } else if (percentage > 0.6 && percentage <= 0.7) {
948
+ startColor = colorList[6];
949
+ endColor = colorList[7];
950
+ } else if (percentage > 0.7 && percentage <= 0.8) {
951
+ startColor = colorList[7];
952
+ endColor = colorList[8];
953
+ } else if (percentage > 0.8 && percentage <= 0.9) {
954
+ startColor = colorList[8];
955
+ endColor = colorList[9];
956
+ } else if (percentage > 0.9 && percentage <= 1) {
957
+ startColor = colorList[9];
958
+ endColor = colorList[10];
959
+ } else if (percentage > 1) {
960
+ startColor = colorList[10];
961
+ endColor = colorList[10];
962
+ }
963
+ // 插值计算 RGB 值
964
+ const r = Math.floor(startColor.r + (endColor.r - startColor.r) * percentage);
965
+ const g = Math.floor(startColor.g + (endColor.g - startColor.g) * percentage);
966
+ const b = Math.floor(startColor.b + (endColor.b - startColor.b) * percentage);
967
+
968
+ return `rgb(${r}, ${g}, ${b})`;
969
+ } else {
970
+ return 'transparent';
971
+ }
972
+ },
973
+ },
974
+ };
975
+ </script>
976
+
977
+ <style scoped lang="scss">
978
+ .crossing-box {
979
+ margin: auto;
980
+ position: relative;
981
+ }
982
+
983
+ .legend-box {
984
+ display: flex;
985
+ justify-content: space-between;
986
+ margin-top: 20px;
987
+ flex-wrap: wrap;
988
+ width: 180px;
989
+ background: rgba(13, 43, 80, 90%);
990
+ padding: 10px;
991
+ z-index: 999;
992
+ position: absolute;
993
+ left: 0;
994
+ top: 0;
995
+ transition: all 0.3s ease;
996
+
997
+ .legend-item {
998
+ width: 40px;
999
+ border-radius: 8px;
1000
+ padding: 8px;
1001
+ margin-bottom: 5px;
1002
+ font-size: 12px;
1003
+ text-align: center;
1004
+ cursor: pointer;
1005
+
1006
+ &:hover {
1007
+ background: rgba(39, 255, 213, 70%);
1008
+ }
1009
+
1010
+ img {
1011
+ height: 20px;
1012
+ margin-bottom: 5px;
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ ::v-deep .canvas-container {
1018
+ margin: auto;
1019
+ }
1020
+ </style>