graph.vue 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659
  1. <template>
  2. <div :id="maxContainerId" class="graphParentContainer"></div>
  3. </template>
  4. <script>
  5. import * as PIXI from "pixi.js";
  6. export default {
  7. props: [],
  8. data() {
  9. return {
  10. maxContainerId:
  11. new Date().getTime().toString() +
  12. "_" +
  13. Math.random().toString().substring(2),
  14. /*
  15. * 设备数组格式如下
  16. * {
  17. id: "",
  18. name:'',
  19. legendObj: {
  20. content: "图的地址或base64串",
  21. size: {
  22. width: 12,
  23. height: 12,
  24. },
  25. },
  26. //设备的绝对位置
  27. absolutePosition: {
  28. x: 1,
  29. y: 1,
  30. },
  31. anchorArr:[{
  32. name:'锚点名称',
  33. //锚点相对于设备图例的位置
  34. relativePosition:{
  35. x:1,y:1
  36. }
  37. }]
  38. }
  39. */
  40. nodeArr: [],
  41. /**
  42. * 线数组,格式如下:
  43. * lines:[{
  44. id: '',
  45. path:[{x:1,y:1}],
  46. style: {
  47. lineWidth: 4, //线宽
  48. lineColor: '#8D9399', //线的颜色
  49. }
  50. }]
  51. */
  52. lineArr: [],
  53. /**
  54. * 文本数组,格式如下:
  55. * labels: [{
  56. id:'',
  57. text:'',
  58. style:{
  59. color: '',
  60. fontSize: '',
  61. backGround:'',
  62. fontWeight:1
  63. },
  64. //文本的绝对位置
  65. absolutePosition: {
  66. x: 1,
  67. y: 1,
  68. },
  69. }]
  70. */
  71. labelArr: [],
  72. //存放图形用的一些信息点
  73. canvasProObj: {
  74. //基于canvas画布的中心点坐标,需要动态计算得到
  75. centerPointerPx: { x: 0, y: 0 },
  76. //原始缩放比例,动态计算得到的,因为图形默认显示时是要适应容器大小的,所以相比原始坐标会有比例变化
  77. originalScale: 1,
  78. //x轴差值,通过此值让x轴左右两端对称
  79. xJiaValue: 0,
  80. //y轴差值,通过此值让y轴上下两端对称
  81. yJiaValue: 0,
  82. //缓存纹理对象
  83. textureSource: {},
  84. //缩放系数
  85. pixiScale: 1,
  86. //原点X坐标
  87. positionPxX: 0,
  88. //原点Y坐标
  89. positionPxY: 0,
  90. //缩放时每次变化的递进值
  91. deep: 0.1,
  92. //鼠标状态,0 普通状态;-1 鼠标按下
  93. mouseState: 0,
  94. //上一个鼠标位置信息
  95. prevMousePosition: {},
  96. },
  97. };
  98. },
  99. computed: {},
  100. methods: {
  101. //绘图入口
  102. drawEntry: function (graphInfo) {
  103. if (!graphInfo) return;
  104. //把原始数据转为易于绘图的数据
  105. var graphDataObj = this.parseDataToDraw(graphInfo);
  106. this.nodeArr = graphDataObj.nodeArr;
  107. this.lineArr = graphDataObj.lineArr;
  108. this.labelArr = graphDataObj.labelArr;
  109. this.drawGraph();
  110. },
  111. //重绘
  112. resetDraw: function () {
  113. this.drawGraph();
  114. },
  115. //绘图
  116. drawGraph: function () {
  117. var _this = this;
  118. // let type = "WebGL";
  119. // if (!PIXI.utils.isWebGLSupported()) {
  120. // type = "canvas";
  121. // }
  122. var pixiContainer = document.getElementById(this.maxContainerId);
  123. //--------------------------------------------创建舞台
  124. //存在舞台时,先清空舞台内的所有元素
  125. if (!!_this.pixiApp) {
  126. while (_this.pixiApp.stage.children.length > 0) {
  127. _this.pixiApp.stage.removeChildAt(0);
  128. }
  129. }
  130. var pixiApp;
  131. if (_this.pixiApp == null) {
  132. pixiApp = new PIXI.Application({
  133. width: pixiContainer.clientWidth, // default: 800 宽度
  134. height: pixiContainer.clientHeight, // default: 600 高度
  135. antialias: true, // default: false 反锯齿
  136. transparent: true, // default: false 透明度
  137. resolution: 1, // default: 1 分辨率
  138. //backgroundColor: "0xffffff",
  139. });
  140. pixiApp.renderer.autoResize = true;
  141. pixiContainer.appendChild(pixiApp.view);
  142. _this.pixiApp = pixiApp;
  143. } else pixiApp = _this.pixiApp;
  144. _this.computeOriginZuobPosition();
  145. //图例
  146. _this.nodeArr.forEach((_dataObj) => {
  147. //2022.1.30版,图例用矩形框代替
  148. var graphicsRect = new PIXI.Graphics();
  149. graphicsRect.lineStyle({
  150. width: 1,
  151. color: "0x0091ff",
  152. alpha: 1,
  153. alignment: 0.5,
  154. });
  155. let newCordObj = _this.convertCoordToLeftOrigina({
  156. x: _dataObj.absolutePosition.x,
  157. y: _dataObj.absolutePosition.y,
  158. });
  159. var newWidth =
  160. _dataObj.legendObj.size.width * _this.canvasProObj.originalScale;
  161. var newHeight =
  162. _dataObj.legendObj.size.height * _this.canvasProObj.originalScale;
  163. graphicsRect.drawRect(newCordObj.x, newCordObj.y, newWidth, newHeight);
  164. //设置点击范围,不然click事件不会触发
  165. graphicsRect.hitArea = graphicsRect.getBounds();
  166. graphicsRect.name = _dataObj.id;
  167. //启用事件
  168. graphicsRect.interactive = true;
  169. graphicsRect.on("click", (event) => {
  170. _this.clickEventCall(event, 1);
  171. });
  172. pixiApp.stage.addChild(graphicsRect);
  173. return;
  174. var _legendObj = _dataObj.legendObj;
  175. // 创建一个纹理
  176. if (!_this.canvasProObj.textureSource[_legendObj.image.content]) {
  177. _this.canvasProObj.textureSource[_legendObj.image.content] =
  178. PIXI.Texture.from(_legendObj.image.content);
  179. }
  180. //加载纹理
  181. var spriteInstanceCoord = _this.convertCoordToLeftOrigina({
  182. x: _dataObj.position.x,
  183. y: _dataObj.position.y,
  184. });
  185. var spriteWidth =
  186. _legendObj.defaultSize.width * _this.canvasProObj.originalScale;
  187. var spriteHeight =
  188. _legendObj.defaultSize.height * _this.canvasProObj.originalScale;
  189. var legendSprite = new PIXI.Sprite(
  190. _this.canvasProObj.textureSource[_legendObj.image.content]
  191. );
  192. legendSprite.x = spriteInstanceCoord.x;
  193. legendSprite.y = spriteInstanceCoord.y;
  194. legendSprite.width = spriteWidth;
  195. legendSprite.height = spriteHeight;
  196. pixiApp.stage.addChild(legendSprite);
  197. });
  198. //文本
  199. _this.labelArr.forEach((_labelObj) => {
  200. var pixiTextStyle = new PIXI.TextStyle({
  201. fill: _labelObj.style.color,
  202. fontSize: _labelObj.style.fontSize,
  203. fontWeight: _labelObj.style.fontWeight,
  204. // stroke: "#ffffff",
  205. // strokeThickness: _this.defaultStyle.text.fontStrokeWidth,
  206. });
  207. var textInstance = new PIXI.Text(_labelObj.text, pixiTextStyle);
  208. textInstance.name = _labelObj.id;
  209. let newCordObj = _this.convertCoordToLeftOrigina({
  210. x: _labelObj.absolutePosition.x,
  211. y: _labelObj.absolutePosition.y,
  212. });
  213. textInstance.position.set(newCordObj.x, newCordObj.y);
  214. // console.log(textInstance.width, "-", textInstance.height);
  215. //启用事件
  216. textInstance.interactive = true;
  217. textInstance.on("click", (event) => {
  218. _this.clickEventCall(event, 3);
  219. });
  220. pixiApp.stage.addChild(textInstance);
  221. });
  222. //画线
  223. _this.lineArr.forEach((_cLine) => {
  224. var lineColor = "0x" + _cLine.style.lineColor.substring(1);
  225. var lineInstance = new PIXI.Graphics();
  226. lineInstance.lineStyle(_cLine.style.lineWidth, lineColor, 1, 0, false);
  227. var _newStart = _this.convertCoordToLeftOrigina({
  228. x: _cLine.path[0].x,
  229. y: _cLine.path[0].y,
  230. });
  231. var _newEnd = _this.convertCoordToLeftOrigina({
  232. x: _cLine.path[1].x,
  233. y: _cLine.path[1].y,
  234. });
  235. lineInstance.moveTo(_newStart.x, _newStart.y);
  236. lineInstance.lineTo(_newEnd.x, _newEnd.y);
  237. pixiApp.stage.addChild(lineInstance);
  238. });
  239. },
  240. /**
  241. * 解析数据成易于图形使用的格式
  242. */
  243. parseDataToDraw: function (graphInfo) {
  244. var nodeArr = [];
  245. //线数组
  246. var lineArr = [];
  247. //文本数组
  248. var labelArr = [];
  249. if (graphInfo.template) {
  250. var rootContainerArr = graphInfo.template.frame.children;
  251. parseNode(rootContainerArr, graphInfo.template.frame.location);
  252. parseLine(graphInfo.lines || []);
  253. parseMainPies(graphInfo.template.mainPipes);
  254. }
  255. return {
  256. nodeArr,
  257. lineArr,
  258. labelArr,
  259. };
  260. /**
  261. * 递归解析设备(node)、文本(label)
  262. * dataArr 参照后台接口返回的diagram对象里的graphInfo.template.frame.children
  263. * absolutePosition 绝对位置{x:1,y:1} 其值是compType为"container"的每一级的location相加得出的值
  264. */
  265. function parseNode(dataArr, absolutePosition) {
  266. dataArr.forEach((_dataObj) => {
  267. var newAbsolutePosition = {
  268. x: _dataObj.location.x + absolutePosition.x,
  269. y: _dataObj.location.y + absolutePosition.y,
  270. };
  271. //文本
  272. if (_dataObj.label && _dataObj.label.content) {
  273. let _labelObj = _dataObj.label;
  274. _labelObj.id = _labelObj.id || Math.random().toString();
  275. let _style = _labelObj.style || {};
  276. _style.color =
  277. _style.color ||
  278. window.__systemConf.systemGraph.peiDian.text.color;
  279. _style.fontSize =
  280. _style.fontSize ||
  281. window.__systemConf.systemGraph.peiDian.text.size;
  282. _style.backGround =
  283. _style.backGround ||
  284. window.__systemConf.systemGraph.peiDian.text.backGround;
  285. _style.fontWeight =
  286. _style.fontWeight ||
  287. window.__systemConf.systemGraph.peiDian.text.weight;
  288. var textNewAbsolutePosition = {
  289. x: _labelObj.location.x + newAbsolutePosition.x,
  290. y: _labelObj.location.y + newAbsolutePosition.y,
  291. };
  292. labelArr.push({
  293. id: _labelObj.id,
  294. text: _labelObj.content,
  295. style: {
  296. color: _style.color,
  297. fontSize: _style.fontSize,
  298. backGround: _style.backGround,
  299. fontWeight: _style.fontWeight,
  300. },
  301. //文本的绝对位置
  302. absolutePosition: textNewAbsolutePosition,
  303. });
  304. _labelObj.style = _style;
  305. _dataObj.label = _labelObj;
  306. }
  307. switch (_dataObj.compType) {
  308. //容器
  309. case "container":
  310. parseNode(_dataObj.children, newAbsolutePosition);
  311. break;
  312. //设备
  313. case "equipmentNode":
  314. let anchorArr = [];
  315. let anchorKeys = Object.keys(_dataObj.anchorLocations || {});
  316. anchorKeys.forEach((_anchorKey) => {
  317. anchorArr.push({
  318. name: _anchorKey,
  319. //锚点相对于设备图例的位置
  320. relativePosition: _dataObj.anchorLocations[_anchorKey],
  321. });
  322. });
  323. nodeArr.push({
  324. id: _dataObj.id,
  325. name: _dataObj.dataObject.localName,
  326. //2022.1.30版本图例以矩形框代替
  327. legendObj: {
  328. content: "图的地址或base64串",
  329. size: {
  330. width: _dataObj.size.x,
  331. height: _dataObj.size.y,
  332. },
  333. },
  334. //设备的绝对位置
  335. absolutePosition: newAbsolutePosition,
  336. anchorArr: anchorArr,
  337. });
  338. break;
  339. }
  340. });
  341. }
  342. /**
  343. * 解析线
  344. */
  345. function parseLine(lines) {
  346. lines.forEach((_line) => {
  347. if (_line.flag != "duplicate") {
  348. _line.id = _line.id || _line.dataObjectId;
  349. let lineDefaultStyle = window.__systemConf.systemGraph.peiDian.line;
  350. lineArr.push({
  351. id: _line.id,
  352. path: _line.locationPath,
  353. style: {
  354. lineWidth: lineDefaultStyle.width,
  355. lineColor: lineDefaultStyle.color,
  356. },
  357. });
  358. }
  359. });
  360. }
  361. /**
  362. * 解析干管,当做线处理
  363. */
  364. function parseMainPies(_dataArr) {
  365. _dataArr.forEach((_data) => {
  366. _data.id = _data.id || _data.dataObjectId;
  367. let lineDefaultStyle = window.__systemConf.systemGraph.peiDian.line;
  368. var pathArr = eachPathArr(_data.locationPath || [], []);
  369. lineArr.push({
  370. id: _data.id,
  371. path: pathArr,
  372. style: {
  373. lineWidth: lineDefaultStyle.width,
  374. lineColor: lineDefaultStyle.color,
  375. },
  376. });
  377. });
  378. }
  379. /**
  380. * 递归解析path数组
  381. */
  382. function eachPathArr(_arr, returnArr) {
  383. _arr.forEach((_data) => {
  384. if (_data instanceof Array) {
  385. eachPathArr(_data, returnArr);
  386. } else
  387. returnArr.push({
  388. x: _data.x,
  389. y: _data.y,
  390. });
  391. });
  392. return returnArr;
  393. }
  394. },
  395. /**
  396. * 1、X轴的原始坐标两端可能是不对称的;Y轴的原始坐标两端也可能是不对称的,所以计算各自的差值,以便把两端对称起来,以此把图形显示到正中间
  397. * 2、计算原始坐标值的缩放系数,因为原始坐标和像素坐标是不对应的,所以要根据最大原始坐标(每一个坐标的绝对值进行比较)和最大像素坐标做比率缩放
  398. * 3、计算中心点像素坐标
  399. */
  400. computeOriginZuobPosition: function () {
  401. var _this = this;
  402. var pixiContainer = document.getElementById(this.maxContainerId);
  403. var maxPxX = pixiContainer.clientWidth;
  404. var maxPxY = pixiContainer.clientHeight;
  405. //基于canvas画布的中心点坐标
  406. _this.canvasProObj.centerPointerPx = {
  407. x: maxPxX / 2,
  408. y: maxPxY / 2,
  409. };
  410. var originaMinX = null, //原始最小坐标X
  411. originaMaxX = null, //原始最大坐标X
  412. originaMinY = null, //原始最小坐标Y
  413. originaMaxY = null; //原始最大坐标Y
  414. /**
  415. * 取得X轴的原始最小坐标和最大坐标、
  416. * 取得Y轴的原始最小坐标和最大坐标、
  417. */
  418. compute();
  419. //计算X轴差值
  420. var xJiaValue = (maxPxX - originaMaxX - originaMinX) / 2;
  421. xJiaValue = originaMinX + xJiaValue < 0 ? 0 - originaMinX : xJiaValue;
  422. //计算Y轴差值
  423. var yJiaValue = (maxPxY - originaMaxY - originaMinY) / 2;
  424. yJiaValue = originaMinY + yJiaValue < 0 ? 0 - originaMinY : yJiaValue;
  425. _this.canvasProObj.xJiaValue = xJiaValue;
  426. _this.canvasProObj.yJiaValue = yJiaValue;
  427. //让每一个的X、Y坐标加上差值,改为在画图时再加差值,这样就减少了此处的循环
  428. setScale();
  429. /**
  430. * 取得X轴的原始最小坐标和最大坐标、
  431. * 取得Y轴的原始最小坐标和最大坐标、
  432. */
  433. function compute() {
  434. _this.nodeArr.forEach((_c) => {
  435. var _legendObj = _c.legendObj;
  436. originaMinX =
  437. originaMinX == null
  438. ? _c.absolutePosition.x
  439. : Math.min(originaMinX, _c.absolutePosition.x);
  440. originaMaxX =
  441. originaMaxX == null
  442. ? _c.absolutePosition.x + _legendObj.size.width
  443. : Math.max(
  444. originaMaxX,
  445. _c.absolutePosition.x + _legendObj.size.width
  446. );
  447. originaMinY =
  448. originaMinY == null ? _c.absolutePosition.y : Math.min(originaMinY);
  449. originaMaxY =
  450. originaMaxY == null
  451. ? _c.absolutePosition.y + _legendObj.size.height
  452. : Math.max(
  453. originaMaxY,
  454. _c.absolutePosition.y + _legendObj.size.height
  455. );
  456. });
  457. _this.lineArr.forEach((_cLine) => {
  458. _cLine.path.forEach((_c) => {
  459. originaMinX =
  460. originaMinX == null ? _c.x : Math.min(originaMinX, _c.x);
  461. originaMaxX =
  462. originaMaxX == null ? _c.x : Math.max(originaMaxX, _c.x);
  463. originaMinY =
  464. originaMinY == null ? _c.y : Math.min(originaMinY, _c.y);
  465. originaMaxY =
  466. originaMaxY == null ? _c.y : Math.max(originaMaxY, _c.y);
  467. });
  468. });
  469. }
  470. //计算缩放系数
  471. function setScale() {
  472. var xScale = maxPxX / (originaMaxX + xJiaValue);
  473. var yScale = maxPxY / (originaMaxY + yJiaValue);
  474. _this.canvasProObj.originalScale = Math.min(xScale, yScale);
  475. //当图形区域能显示全的时候不再进行缩放
  476. _this.canvasProObj.originalScale = Math.min(
  477. 1,
  478. _this.canvasProObj.originalScale
  479. );
  480. }
  481. },
  482. /**
  483. * 把坐标转为缩放后的坐标
  484. * _obj:{x:1,y:1}
  485. */
  486. convertCoordToLeftOrigina: function (_obj) {
  487. _obj.x =
  488. (_obj.x + this.canvasProObj.xJiaValue) *
  489. this.canvasProObj.originalScale;
  490. _obj.y =
  491. (_obj.y + this.canvasProObj.yJiaValue) *
  492. this.canvasProObj.originalScale;
  493. return _obj;
  494. },
  495. /**
  496. * 设备、线、文本点击事件的回调
  497. * type 1 设备; 2 线; 3 文本
  498. */
  499. clickEventCall: function (event, type) {
  500. var graphInstance = event.target || event.currentTarget;
  501. var dataId = graphInstance.name;
  502. var dataArr =
  503. type == 1
  504. ? this.nodeArr
  505. : type == 2
  506. ? this.lineArr
  507. : type == 3
  508. ? this.labelArr
  509. : [];
  510. var dataObj = dataArr.filter((_data) => {
  511. return _data.id == dataId;
  512. })[0];
  513. this.$emit("click", dataObj, type);
  514. },
  515. //图形缩放比例递进计算
  516. graphScaleCompute: function (deepVal) {
  517. return this.canvasProObj.pixiScale + deepVal;
  518. },
  519. //图形缩放
  520. graphScale: function (newScale) {
  521. newScale = Math.min(5, newScale);
  522. newScale = Math.max(0.25, newScale);
  523. //设置X、Y轴的缩放系数
  524. this.pixiApp.stage.scale.x = newScale;
  525. this.pixiApp.stage.scale.y = newScale;
  526. //保存缩放系数
  527. this.canvasProObj.pixiScale = newScale;
  528. //重新计算原点
  529. this.canvasProObj.positionPxX =
  530. (1 - newScale) * this.canvasProObj.centerPointerPx.x;
  531. this.canvasProObj.positionPxY =
  532. (1 - newScale) * this.canvasProObj.centerPointerPx.y;
  533. this.pixiApp.stage.position.set(
  534. this.canvasProObj.positionPxX,
  535. this.canvasProObj.positionPxY
  536. );
  537. },
  538. /**
  539. * 从舞台上移出元素:图例、线、文本
  540. * id 数据ID,即元素的name
  541. * type 1 设备; 2 线; 3 文本
  542. */
  543. /**
  544. * 更新graphInfo中的数据
  545. * dataType 1 设备; 2 线; 3 文本
  546. * operType 1 移除设备节点; 2 更新文本字体大小; 3 更新文本颜色; 4 更新文本背景色; 5 移除文本节点
  547. * exprObj 扩展数据,支持:{id:'数据id',fontSize:'字体大小',fontColor:'文本颜色',fontBackColor:'文本背景色'}
  548. */
  549. updateDataAndGraph: function (dataType, operType, exprObj) {
  550. // fill: _labelObj.style.color,
  551. // fontSize: _labelObj.style.fontSize,
  552. // fontWeight: _labelObj.style.fontWeight,
  553. var dataArr =
  554. dataType == 1
  555. ? this.nodeArr
  556. : dataType == 2
  557. ? this.lineArr
  558. : dataType == 3
  559. ? this.labelArr
  560. : [];
  561. for (let i = 0; i < dataArr.length; i++) {
  562. let _dataObj = dataArr[i];
  563. if (_dataObj.id == exprObj.id) {
  564. var stageChild = this.pixiApp.stage.getChildByName(exprObj.id);
  565. var _stageStyle = stageChild.style;
  566. switch (operType) {
  567. case 1:
  568. case 5:
  569. dataArr.splice(i, 1);
  570. this.pixiApp.stage.removeChild(stageChild);
  571. break;
  572. case 2:
  573. _dataObj.style.fontSize = exprObj.fontSize;
  574. _stageStyle.fontSize = exprObj.fontSize;
  575. stageChild.style = _stageStyle;
  576. break;
  577. case 3:
  578. _dataObj.style.color = exprObj.fontColor;
  579. _stageStyle.fill = exprObj.fontColor;
  580. stageChild.style = _stageStyle;
  581. break;
  582. case 4:
  583. _dataObj.style.backGround = exprObj.fontBackColor;
  584. // _stageStyle.fontSize = exprObj.fontSize;
  585. // stageChild.style = _stageStyle;
  586. break;
  587. }
  588. break;
  589. }
  590. }
  591. },
  592. },
  593. created() {},
  594. mounted() {},
  595. components: {},
  596. };
  597. </script>
  598. <style scoped>
  599. .graphParentContainer {
  600. width: 100%;
  601. height: 100%;
  602. background: #ffffff;
  603. }
  604. </style>