<template> <v-chart class="chart" :option="option" style="height: 100%; width: 100%" ref="chart" @click="handleNodeClick" lazy /> </template> <script> import searchApis from "~/apis/searchApi"; import { use } from "echarts/core"; import { CanvasRenderer } from "echarts/renderers"; import { GraphChart } from "echarts/charts"; import { TitleComponent, TooltipComponent, LegendComponent, } from "echarts/components"; // import VChart from "vue-echarts"; use([ CanvasRenderer, GraphChart, TitleComponent, TooltipComponent, LegendComponent, ]); export default { props: { dataChart: { type: Object, default() { return {}; }, }, selectedFilter: { type: Object, default() { return {}; }, }, }, data() { return { option: { title: {}, tooltip: { position: "bottom", extraCssText: 'max-width: 400px; max-height:100px; white-space: pre-wrap;text-align: justify;font-family: "sahel"; ', }, animationDurationUpdate: 1500, animationEasingUpdate: "quadraticIn", series: [ { type: "graph", layout: "force", symbolSize: [250, 60], itemStyle: { color: "rgb(25, 183, 207)", shadowBlur: 10, shadowColor: "rgba(25, 100, 150, 0.5)", shadowOffsetY: 5, }, emphasis: { focus: "adjacency", }, force: { repulsion: 3000, gravity: 0.1, edgeLength: 250, layoutAnimation: true, }, axisPointer: { type: "cross", label: { backgroundColor: "#6a7985", fontSize: 14, fontFamily: "sahel", }, }, // roam: true, zoom: 0.6, label: { show: true, position: "inside", fontSize: 14, color: "#fff", fontFamily: "sahel", formatter: function (params) { const maxLength = 30; const text = params.name.length > maxLength ? params.name.substring(0, maxLength) + "..." : params.name; return text; }, }, draggable: true, edgeSymbol: ["circle", "arrow"], edgeSymbolSize: [8, 12], edgeLabel: { fontSize: 12, }, data: [], links: [], lineStyle: { opacity: 0.8, width: 2, curveness: 0, }, }, ], }, selectedNodeChildren: [], graphCountLimit: 1000, }; }, watch: { dataChart: { handler(newValue) { if (Object.keys(newValue).length) { this.$nextTick(() => { this.updateChart(newValue); }); } }, immediate: true, }, selectedFilter: { handler(newFilter) { // بهروزرسانی نمودار با توجه به فیلتر جدید if (this.dataChart && Object.keys(this.dataChart).length) { this.$nextTick(() => { this.updateChart(this.dataChart); }); } }, deep: true, }, }, methods: { /** * بهروزرسانی دادههای نمودار با استفاده از دادههای جدید. * @param {Object} data - دادههای جدید برای بهروزرسانی نمودار */ updateChart(data) { // console.log(this.selectedFilter.relation_type.title); // ایجاد یک Map برای پیگیری محتویات نودهای دیده شده const seenNodesMap = new Map(); // تابع برای تولید کلید منحصر به فرد از محتویات نود const generateNodeKey = (node) => { return `${node.name}-${node.symbol}-${node.itemStyle.color}-${node.to_type}`; }; const addNodeIfValid = (node) => { if (node && node.name && node.name.trim() !== "") { //اگر نودی خالی باشد حذفش میکند تا نود خالی نمایش داده نشود const key = generateNodeKey(node); //بررسی میکند که اگر متن تکراری باشد حذفش میکند if (!seenNodesMap.has(key)) { seenNodesMap.set(key, node); return node; } } return null; }; const nodesData = [ addNodeIfValid({ id: data.id, name: data.title, symbol: "rect", itemStyle: { color: "#21325e", }, }), ...(this.selectedFilter?.relation_from?.title === "قانون جاری" && this.selectedFilter?.relation_to?.title === "رای و نظر" ? data.opinion_relations .filter((relation) => { if (this.selectedFilter.relation_type.title === "همه موارد") { return true; } else if ( this.selectedFilter.relation_type.title === "ارتباط عادی" ) { return relation.rel_type === "عادی"; } else if ( this.selectedFilter.relation_type.title === "استناد دهی" ) { return relation.rel_type === "استناد"; } return false; }) .map((relation, index) => addNodeIfValid({ id: relation.from_section_id, name: relation.from_section_title, symbol: "circle", key: relation.to_type, itemStyle: { color: "#f3708f", }, }) ) .filter((node) => node !== null) // حذف نودهای null : []), ...(this.selectedFilter?.relation_from?.title === "قانون جاری" && this.selectedFilter?.relation_to?.title === "مقررات دیگر" ? data.qanon_relations .filter((relation) => { if (this.selectedFilter.relation_type.title === "همه موارد") { return true; } else if ( this.selectedFilter.relation_type.title === "ارتباط عادی" ) { return relation.rel_type === "عادی"; } else if ( this.selectedFilter.relation_type.title === "استناد دهی" ) { return relation.rel_type === "استناد"; } return false; }) .map((relation, index) => addNodeIfValid({ id: relation.from_section_id, name: relation.from_section_title, symbol: "roundRect", key: relation.to_type, itemStyle: { color: "#3a9ff5", }, }) ) .filter((node) => node !== null) // حذف نودهای null : []), ]; const linksData = [ ...(this.selectedFilter?.relation_to?.title === "رای و نظر" ? data.opinion_relations.map((relation, index) => ({ source: data.id, target: relation.from_section_id, })) : []), ...(this.selectedFilter?.relation_to?.title === "مقررات دیگر" ? data.qanon_relations.map((relation, index) => ({ source: data.id, target: relation.from_section_id, })) : []), ]; // تنظیم دادهها به گزینههای نمودار this.option.series[0].data = nodesData; this.option.series[0].links = linksData; this.$refs?.chart?.setOption(this.option); }, /** * پردازش کلیک روی یک گره در نمودار. * @param {object} params - اطلاعات مربوط به گره کلیک شده . */ handleNodeClick(params) { // console.log("params", params.data); const dataNode = params.data; // if (dataNode.children && dataNode.children.length > 0) { // this.handleNodeWithChildren(dataNode); // } try { this.getChildrenNode(dataNode.id, dataNode.key).then((list) => { if (!list) return; list.forEach((element, index) => { // console.log(element); var node = { name: element.title, id: element.id, children: [], // label: { // backgroundColor: dataColor, // }, itemStyle: {}, }; dataNode.children.push(node); if (dataNode.id === 0) { this.option.series[0].data[0].children.push(node); } else { this.addChildToParent( this.option.series[0].data[0], dataNode.id, node ); } }); }); } catch (error) { console.error("Error fetching children nodes:", error); } }, /** * این تابع مسئول دریافت لیست آیتمهای زیر یک آیتم پدر از سرور است. * @param {string} parentId - شناسه آیتم پدر * @returns {Promise} - لیست آیتمهای زیر آیتم پدر */ async getChildrenNode(id, key) { let index_key = this.$route.params.key; // if ( // key === "نشست قضایی" || // key === "رای وحدت رویه" || // key === "رای دیوان عدالت اداری" || // key === "نظر ریاست مجلس" // ) { // index_key = "qaopinion"; // } else if (key === "قانون") { // index_key = "qasection"; // } else if (key === "مقرره") { // index_key = "rgsection"; // } const payload = { id: id, key: index_key, //type }; let url = searchApis.chart.graph; url = url.replace("{{to_key}}", index_key); // ارسال درخواست به سرور و بازگشت پاسخ به صورت Promise try { const { $api } = useNuxtApp(); const res = await $api(url, { method: "post", baseURL: repoUrl(), body: payload, }); return res.data; } catch (err) {} }, /** * این تابع مسئول اضافه کردن یک آیتم فرزند به آیتم پدر میباشد. * @param {Object} node - آیتم پدر * @param {string} parentId - شناسه آیتم پدر * @param {Object} newChild - آیتم فرزند جدید * @returns {boolean} - مقدار منطقی True در صورت موفقیت آمیز بودن اضافه کردن، در غیر این صورت False */ addChildToParent(node, parentId, newChild) { if (node.id === parentId) { node.children.push(newChild); return true; } if (node.children) { for (let child of node.children) { if (this.addChildToParent(child, parentId, newChild)) { return true; } } } return false; }, showModalTree(event) { const itemSelected = { name: event.name, y: event.value }; this.$emit("list", itemSelected); }, /** * پردازش کلیک روی یک گره که دارای فرزندان است. * @param {object} dataNode - اطلاعات گرهای که کلیک شده است. */ handleNodeWithChildren(dataNode) { if (this.selectedNodeChildren.length > 0) { this.selectedNodeChildren = []; this.removeAllChildren(dataNode); } else { this.selectedNodeChildren = dataNode.children; this.addNodesAndLinks(dataNode.children, dataNode.id); } this.$refs?.chart?.setOption(this.option); }, /** * حذف گرههای فرزند فرزندان. * @param {object} node - اطلاعات گرهای که کلیک شده است. */ removeAllChildren(node) { if (node.children && node.children.length > 0) { node.children.forEach((child) => { this.removeAllChildren(child); }); this.removeNodesAndLinks(node.children); } }, /** * حذف گرهها و لینکهای مربوط به لیست فرزندان. * @param {array} children - لیست فرزندان برای حذف گرهها و لینکها. */ removeNodesAndLinks(children) { this.option.series[0].data = this.option.series[0].data.filter( (node) => !children.find((child) => child.id === node.id) ); this.option.series[0].links = this.option.series[0].links.filter( (link) => !children.find((child) => child.id === link.target) ); }, /** * اضافه کردن گرهها و لینکهای مربوط به لیست فرزندان به همراه گره والد. * @param {array} children - لیست فرزندان برای اضافه کردن گرهها و لینکها. * @param {string} parentNodeId - نام گره والد. */ addNodesAndLinks(children, parentNodeId) { if (this.option.series[0].data.length < this.graphCountLimit) { children.forEach((child) => { if ( !this.option.series[0].data.find((node) => node.id === child.id) ) { this.option.series[0].data.push(child); } const sourceId = parentNodeId; const targetId = child.id; const isLink = this.option.series[0].links.find( (link) => link.source === sourceId && link.target === targetId ); if (!isLink) { this.option.series[0].links.push({ source: sourceId, target: targetId, }); } }); } else { // alert("حداکثر تعداد نمایش گرافها!"); } }, //با کلیک بر روی هر نود نود جدید میساازد // handleNodeClick(params) { // console.log("نود کلیک شده",params.data) // console.log("رای",this.dataChart.qanon_relations[2].from_section_id) // const clickedNode = params.data; // const newNode = { // id: `new_${Date.now()}`, // name: "نود جدید", // symbol: "triangle", // itemStyle: { // color: "#ffa500", // }, // }; // const newLink = { // source: clickedNode.id, // target: newNode.id, // }; // // اضافه کردن نود و لینک جدید به دادههای موجود // this.option.series[0].data.push(newNode); // this.option.series[0].links.push(newLink); // // بهروزرسانی نمودار // this.$refs.chart.setOption(this.option); // }, }, }; </script> <style scoped> .chart { height: 850px; } </style>