graph.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  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. lineArr: [],
  43. //文本数组
  44. labelArr: [],
  45. //存放图形用的一些信息点
  46. canvasProObj: {
  47. //基于canvas画布的中心点坐标,需要动态计算得到
  48. centerPointerPx: { x: 0, y: 0 },
  49. //原始缩放比例,动态计算得到的,因为图形默认显示时是要适应容器大小的,所以相比原始坐标会有比例变化
  50. originalScale: 1,
  51. //x轴差值,通过此值让x轴左右两端对称
  52. xJiaValue: 0,
  53. //y轴差值,通过此值让y轴上下两端对称
  54. yJiaValue: 0,
  55. //缓存纹理对象
  56. textureSource: {},
  57. //缩放系数
  58. pixiScale: 1,
  59. //原点X坐标
  60. positionPxX: 0,
  61. //原点Y坐标
  62. positionPxY: 0,
  63. //缩放时每次变化的递进值
  64. deep: 0.1,
  65. //鼠标状态,0 普通状态;-1 鼠标按下
  66. mouseState: 0,
  67. //上一个鼠标位置信息
  68. prevMousePosition: {},
  69. },
  70. };
  71. },
  72. computed: {},
  73. methods: {
  74. //绘图入口
  75. drawEntry: function (graphInfo) {
  76. //把原始数据转为易于绘图的数据
  77. var graphDataObj = this.parseDataToDraw(graphInfo);
  78. this.nodeArr = graphDataObj.nodeArr;
  79. this.lineArr = graphDataObj.lineArr;
  80. this.labelArr = graphDataObj.labelArr;
  81. this.drawGraph();
  82. },
  83. //重绘
  84. resetDraw: function () {
  85. this.drawGraph();
  86. },
  87. //绘图
  88. drawGraph: function () {
  89. var _this = this;
  90. // let type = "WebGL";
  91. // if (!PIXI.utils.isWebGLSupported()) {
  92. // type = "canvas";
  93. // }
  94. var pixiContainer = document.getElementById(this.maxContainerId);
  95. //--------------------------------------------创建舞台
  96. //存在舞台时,先清空舞台内的所有元素
  97. if (!!_this.pixiApp) {
  98. while (_this.pixiApp.stage.children.length > 0) {
  99. _this.pixiApp.stage.removeChildAt(0);
  100. }
  101. }
  102. var pixiApp;
  103. if (_this.pixiApp == null) {
  104. pixiApp = new PIXI.Application({
  105. width: pixiContainer.clientWidth, // default: 800 宽度
  106. height: pixiContainer.clientHeight, // default: 600 高度
  107. antialias: true, // default: false 反锯齿
  108. transparent: true, // default: false 透明度
  109. resolution: 1, // default: 1 分辨率
  110. //backgroundColor: "0xffffff",
  111. });
  112. pixiApp.renderer.autoResize = true;
  113. pixiContainer.appendChild(pixiApp.view);
  114. _this.pixiApp = pixiApp;
  115. } else pixiApp = _this.pixiApp;
  116. _this.computeOriginZuobPosition();
  117. //图例
  118. _this.nodeArr.forEach((_dataObj) => {
  119. //2022.1.30版,图例用矩形框代替
  120. var graphicsRect = new PIXI.Graphics();
  121. graphicsRect.lineStyle({
  122. width: 1,
  123. color: "0x0091ff",
  124. alpha: 1,
  125. alignment: 0.5,
  126. });
  127. let newCordObj = _this.convertCoordToLeftOrigina({
  128. x: _dataObj.absolutePosition.x,
  129. y: _dataObj.absolutePosition.y,
  130. });
  131. var newWidth =
  132. _dataObj.legendObj.size.width * _this.canvasProObj.originalScale;
  133. var newHeight =
  134. _dataObj.legendObj.size.height * _this.canvasProObj.originalScale;
  135. graphicsRect.drawRect(newCordObj.x, newCordObj.y, newWidth, newHeight);
  136. graphicsRect.hitArea = graphicsRect.getBounds();
  137. graphicsRect.name = _dataObj.id;
  138. //启用事件
  139. graphicsRect.interactive = true;
  140. graphicsRect.on("click", (event) => {
  141. _this.clickEventCall(event, 1);
  142. });
  143. pixiApp.stage.addChild(graphicsRect);
  144. return;
  145. var _legendObj = _dataObj.legendObj;
  146. // 创建一个纹理
  147. if (!_this.canvasProObj.textureSource[_legendObj.image.content]) {
  148. _this.canvasProObj.textureSource[_legendObj.image.content] =
  149. PIXI.Texture.from(_legendObj.image.content);
  150. }
  151. //加载纹理
  152. var spriteInstanceCoord = _this.convertCoordToLeftOrigina({
  153. x: _dataObj.position.x,
  154. y: _dataObj.position.y,
  155. });
  156. var spriteWidth =
  157. _legendObj.defaultSize.width * _this.canvasProObj.originalScale;
  158. var spriteHeight =
  159. _legendObj.defaultSize.height * _this.canvasProObj.originalScale;
  160. var legendSprite = new PIXI.Sprite(
  161. _this.canvasProObj.textureSource[_legendObj.image.content]
  162. );
  163. legendSprite.x = spriteInstanceCoord.x;
  164. legendSprite.y = spriteInstanceCoord.y;
  165. legendSprite.width = spriteWidth;
  166. legendSprite.height = spriteHeight;
  167. pixiApp.stage.addChild(legendSprite);
  168. });
  169. return;
  170. //画线
  171. _this.dataSource.view.diagram.lines.forEach((_cLine) => {
  172. var lineColor = "0x" + _cLine.style.lineColor.substring(1);
  173. var lineInstance = new PIXI.Graphics();
  174. lineInstance.lineStyle(_cLine.style.lineWidth, lineColor, 1, 0, false);
  175. var _newStart = _this.convertCoordToLeftOrigina({
  176. x: _cLine.path[0].x,
  177. y: _cLine.path[0].y,
  178. });
  179. var _newEnd = _this.convertCoordToLeftOrigina({
  180. x: _cLine.path[1].x,
  181. y: _cLine.path[1].y,
  182. });
  183. lineInstance.moveTo(_newStart.x, _newStart.y);
  184. lineInstance.lineTo(_newEnd.x, _newEnd.y);
  185. pixiApp.stage.addChild(lineInstance);
  186. });
  187. },
  188. /**
  189. * 解析数据成易于图形使用的格式
  190. */
  191. parseDataToDraw: function (graphInfo) {
  192. var nodeArr = [];
  193. //线数组
  194. var lineArr = [];
  195. //文本数组
  196. var labelArr = [];
  197. var rootContainerArr = graphInfo.template.frame.children;
  198. parseNode(rootContainerArr, graphInfo.template.frame.location);
  199. return {
  200. nodeArr,
  201. lineArr,
  202. labelArr,
  203. };
  204. /**
  205. * 递归解析设备(node)、文本(label)
  206. * dataArr 参照后台接口返回的diagram对象里的graphInfo.template.frame.children
  207. * absolutePosition 绝对位置{x:1,y:1} 其值是compType为"container"的每一级的location相加得出的值
  208. */
  209. function parseNode(dataArr, absolutePosition) {
  210. dataArr.forEach((_dataObj) => {
  211. var newAbsolutePosition = {
  212. x: _dataObj.location.x + absolutePosition.x,
  213. y: _dataObj.location.y + absolutePosition.y,
  214. };
  215. switch (_dataObj.compType) {
  216. //容器
  217. case "container":
  218. parseNode(_dataObj.children, newAbsolutePosition);
  219. break;
  220. //设备
  221. case "equipmentNode":
  222. let anchorArr = [];
  223. let anchorKeys = Object.keys(_dataObj.anchorLocations || {});
  224. anchorKeys.forEach((_anchorKey) => {
  225. anchorArr.push({
  226. name: _anchorKey,
  227. //锚点相对于设备图例的位置
  228. relativePosition: _dataObj.anchorLocations[_anchorKey],
  229. });
  230. });
  231. nodeArr.push({
  232. id: _dataObj.id,
  233. name: _dataObj.dataObject.localName,
  234. //2022.1.30版本图例以矩形框代替
  235. legendObj: {
  236. content: "图的地址或base64串",
  237. size: {
  238. width: _dataObj.size.x,
  239. height: _dataObj.size.y,
  240. },
  241. },
  242. //设备的绝对位置
  243. absolutePosition: newAbsolutePosition,
  244. anchorArr: anchorArr,
  245. });
  246. break;
  247. }
  248. });
  249. }
  250. },
  251. /**
  252. * 1、X轴的原始坐标两端可能是不对称的;Y轴的原始坐标两端也可能是不对称的,所以计算各自的差值,以便把两端对称起来,以此把图形显示到正中间
  253. * 2、计算原始坐标值的缩放系数,因为原始坐标和像素坐标是不对应的,所以要根据最大原始坐标(每一个坐标的绝对值进行比较)和最大像素坐标做比率缩放
  254. * 3、计算中心点像素坐标
  255. */
  256. computeOriginZuobPosition: function () {
  257. var _this = this;
  258. var pixiContainer = document.getElementById(this.maxContainerId);
  259. var maxPxX = pixiContainer.clientWidth;
  260. var maxPxY = pixiContainer.clientHeight;
  261. //基于canvas画布的中心点坐标
  262. _this.canvasProObj.centerPointerPx = {
  263. x: maxPxX / 2,
  264. y: maxPxY / 2,
  265. };
  266. var originaMinX = null, //原始最小坐标X
  267. originaMaxX = null, //原始最大坐标X
  268. originaMinY = null, //原始最小坐标Y
  269. originaMaxY = null; //原始最大坐标Y
  270. /**
  271. * 取得X轴的原始最小坐标和最大坐标、
  272. * 取得Y轴的原始最小坐标和最大坐标、
  273. */
  274. compute();
  275. //计算X轴差值
  276. var xJiaValue = (maxPxX - originaMaxX - originaMinX) / 2;
  277. xJiaValue = originaMinX + xJiaValue < 0 ? 0 - originaMinX : xJiaValue;
  278. //计算Y轴差值
  279. var yJiaValue = (maxPxY - originaMaxY - originaMinY) / 2;
  280. yJiaValue = originaMinY + yJiaValue < 0 ? 0 - originaMinY : yJiaValue;
  281. _this.canvasProObj.xJiaValue = xJiaValue;
  282. _this.canvasProObj.yJiaValue = yJiaValue;
  283. //让每一个的X、Y坐标加上差值,改为在画图时再加差值,这样就减少了此处的循环
  284. setScale();
  285. /**
  286. * 取得X轴的原始最小坐标和最大坐标、
  287. * 取得Y轴的原始最小坐标和最大坐标、
  288. */
  289. function compute() {
  290. _this.nodeArr.forEach((_c) => {
  291. var _legendObj = _c.legendObj;
  292. originaMinX =
  293. originaMinX == null
  294. ? _c.absolutePosition.x
  295. : Math.min(originaMinX, _c.absolutePosition.x);
  296. originaMaxX =
  297. originaMaxX == null
  298. ? _c.absolutePosition.x + _legendObj.size.width
  299. : Math.max(
  300. originaMaxX,
  301. _c.absolutePosition.x + _legendObj.size.width
  302. );
  303. originaMinY =
  304. originaMinY == null ? _c.absolutePosition.y : Math.min(originaMinY);
  305. originaMaxY =
  306. originaMaxY == null
  307. ? _c.absolutePosition.y + _legendObj.size.height
  308. : Math.max(
  309. originaMaxY,
  310. _c.absolutePosition.y + _legendObj.size.height
  311. );
  312. });
  313. _this.lineArr.forEach((_cLine) => {
  314. _cLine.path.forEach((_c) => {
  315. originaMinX =
  316. originaMinX == null ? _c.x : Math.min(originaMinX, _c.x);
  317. originaMaxX =
  318. originaMaxX == null ? _c.x : Math.max(originaMaxX, _c.x);
  319. originaMinY =
  320. originaMinY == null ? _c.y : Math.min(originaMinY, _c.y);
  321. originaMaxY =
  322. originaMaxY == null ? _c.y : Math.max(originaMaxY, _c.y);
  323. });
  324. });
  325. }
  326. //计算缩放系数
  327. function setScale() {
  328. var xScale = maxPxX / (originaMaxX + xJiaValue);
  329. var yScale = maxPxY / (originaMaxY + yJiaValue);
  330. _this.canvasProObj.originalScale = Math.min(xScale, yScale);
  331. //当图形区域能显示全的时候不再进行缩放
  332. _this.canvasProObj.originalScale = Math.min(
  333. 1,
  334. _this.canvasProObj.originalScale
  335. );
  336. }
  337. },
  338. /**
  339. * 把坐标转为缩放后的坐标
  340. * _obj:{x:1,y:1}
  341. */
  342. convertCoordToLeftOrigina: function (_obj) {
  343. _obj.x =
  344. (_obj.x + this.canvasProObj.xJiaValue) *
  345. this.canvasProObj.originalScale;
  346. _obj.y =
  347. (_obj.y + this.canvasProObj.yJiaValue) *
  348. this.canvasProObj.originalScale;
  349. return _obj;
  350. },
  351. /**
  352. * 设备、线、文本点击事件的回调
  353. * type 1 设备; 2 线; 3 文本
  354. */
  355. clickEventCall: function (event, type) {
  356. var graphInstance = event.target || event.currentTarget;
  357. var dataId = graphInstance.name;
  358. var dataArr =
  359. type == 1
  360. ? this.nodeArr
  361. : type == 2
  362. ? this.lineArr
  363. : type == 3
  364. ? this.labelArr
  365. : [];
  366. var dataObj = dataArr.filter((_data) => {
  367. return _data.id == dataId;
  368. })[0];
  369. this.$emit("click", dataObj, type);
  370. },
  371. },
  372. created() {},
  373. mounted() {},
  374. components: {},
  375. };
  376. </script>
  377. <style scoped>
  378. .graphParentContainer {
  379. width: 100%;
  380. height: 100%;
  381. background: #ffffff;
  382. }
  383. </style>