base_ui/components/charts/ChartGraph.vue
2025-02-01 13:04:55 +03:30

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>