478 lines
15 KiB
Vue
478 lines
15 KiB
Vue
|
<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>
|