<template><div class="page-container"><div class="header-title">美国产品图谱(ECharts Radial Tree)</div><RadialTreeView :chart-data="radialData" background-color="#E6E6FA"/></div></template><script setup lang="ts">import RadialTreeView from"@/views/knowledgeGraph/radialTreeView/RadialTreeView.vue";import{mockRadialData,TreeNode,}from"@/views/knowledgeGraph/radialTreeView/mockRadialTreeData";// 确保路径正确import{ ref }from"vue";const radialData = ref<TreeNode>(mockRadialData);// If you need to update data later:// setTimeout(() => {// const newData = JSON.parse(JSON.stringify(mockRadialData)); // Deep copy// newData.children[0].name = 'Updated Customers';// newData.children[0].children.push({ name: 'New Customer Segment', id: 'new_cust', nodeType: 'customer' });// radialData.value = newData;// }, 5000);</script><style scoped>.page-container {width:100%;height: 90vh;/* Adjust as needed */display: flex;flex-direction: column;padding: 20px;box-sizing: border-box;background-color: #f0f2f5;/* A light background for the page */}.header-title {font-size: 24px;font-weight: bold;text-align: center;margin-bottom: 20px;color: #333;}/* RadialTreeView will take full width/height of its parent if not constrained *//* Ensure its container in this page has defined dimensions or it fills the space */</style>
子组件-创建RadialTreeView
<template><div class="radial-tree-view-container"><div ref="chartRef"class="chart-container"></div></div></template><script setup lang="ts">import{ ref, onMounted, onBeforeUnmount, watch, nextTick }from"vue";import*as echarts from"echarts/core";import{ TreeChart, TreeSeriesOption }from"echarts/charts";import{TooltipComponent,TooltipComponentOption,LegendComponent,LegendComponentOption,}from"echarts/components";import{ CanvasRenderer }from"echarts/renderers";import type { EChartsOption, ECBasicOption }from"echarts/types/dist/shared";echarts.use([TreeChart, TooltipComponent, LegendComponent, CanvasRenderer]);type ECOption = EChartsOption &ECBasicOption &{series?: TreeSeriesOption | TreeSeriesOption[];}& TooltipComponentOption &LegendComponentOption;interfaceTreeNode{name: string;id: string;value?: number;// Can be used for sizing or other metricsitemStyle?:{color?: string;borderColor?: string;borderWidth?: number;};label?:{show?: boolean;position?: any;formatter?: string |((_params: any)=> string);color?: string;fontSize?: number;overflow?: string;width?: number;ellipsis?: string;};lineStyle?:{color?: string;width?: number;curveness?: number;};children?: TreeNode[];// Custom propertiesnodeType:|"country"|"category"|"brand"|"product"|"regulation"|"customer"|"patent"|"contributor";isCollapsed?: boolean;// For manual collapse/expand if needed beyond ECharts defaultsymbol?: string;// e.g., 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow', 'none' or 'image://path'symbolSize?: number | number[];emphasis?:{focus?: string;scale?: boolean | number;itemStyle?:{color?: string;borderColor?: string;borderWidth?: number;};label?:{color?: string;fontSize?: number;};lineStyle?:{color?: string;width?: number;};};}const props = defineProps<{chartData: TreeNode;backgroundColor?: string;}>();const chartRef = ref<HTMLDivElement |null>(null);letchartInstance: echarts.ECharts |null=null;// --- Style Definitions (based on UI) ---constCOLORS={country:"#FFD700",// Yellow for country (中心节点)categoryPurple:"#DA70D6",// Purple for main categories (一级节点)brandRed:"#FF6347",// Red for brands (二级节点)productBlue:"#87CEFA",// Light blue for products (二级节点)contributorGreen:"#32CD32",// Green for contributors (特殊二级节点)defaultNode:"#A9A9A9",// Default colorconnector:"#B0B0B0",// Connector line colortextPrimary:"#333333",textSecondary:"#FFFFFF",};constSYMBOL_SIZES={country:80,category:60,item:45,contributor:50,};functiongetNodeStyle(node: TreeNode){let color =COLORS.defaultNode;let symbolSize =SYMBOL_SIZES.item;let labelColor =COLORS.textPrimary;let borderColor ="rgba(0,0,0,0)";let borderWidth =0;switch(node.nodeType){case"country":color =COLORS.country;symbolSize =SYMBOL_SIZES.country;labelColor =COLORS.textPrimary;// Text color for country nodebreak;case"category":case"regulation":// Assuming regulation, customer, patent are like categoriescase"customer":case"patent":color =COLORS.categoryPurple;symbolSize =SYMBOL_SIZES.category;labelColor =COLORS.textSecondary;// White text for purple nodesbreak;case"brand":color =COLORS.brandRed;symbolSize =SYMBOL_SIZES.item;labelColor =COLORS.textSecondary;break;case"product":color =COLORS.productBlue;symbolSize =SYMBOL_SIZES.item;labelColor =COLORS.textPrimary;// Darker text for light blue nodesbreak;case"contributor":color =COLORS.contributorGreen;symbolSize =SYMBOL_SIZES.contributor;labelColor =COLORS.textSecondary;borderColor ="#FF4500";// Special border for contributorborderWidth =2;break;}return{itemStyle:{ color, borderColor, borderWidth },symbolSize,label:{show:true,color: labelColor,fontSize:node.nodeType ==="country"?16: node.nodeType ==="category"?14:12,formatter:(params: any)=>{// Basic word wrapping for long namesconst name = params.name;const maxLength =10;// Max characters per lineif(name.length > maxLength){let result ="";for(let i =0; i < name.length; i += maxLength){result += name.substring(i, i + maxLength)+"\n";}return result.trim();}return name;},// position: ["center", "center"], // 修改此处为数组形式,避免类型冲突overflow:"break",// Allow breaking lineswidth: symbolSize *0.8,// Label width related to symbol size},emphasis:{focus:"descendant",// Highlight node and its descendantsscale:1.1,itemStyle:{borderColor:"#FFF",borderWidth:2,},label:{fontSize:node.nodeType ==="country"?18: node.nodeType ==="category"?16:14,},},};}functionprocessDataForEcharts(node: TreeNode): TreeNode {const style =getNodeStyle(node);constprocessedNode: TreeNode ={...node,...style,// Apply generated stylessymbol:"circle",// All nodes are circles as per UI};if(node.children && node.children.length >0){processedNode.children = node.children.map((child)=>processDataForEcharts(child));}return processedNode;}constinitChart=()=>{if(chartRef.value){chartInstance = echarts.init(chartRef.value);setChartOptions();}};constsetChartOptions=()=>{if(!chartInstance ||!props.chartData)return;constprocessedData: any =processDataForEcharts(props.chartData);constoption: ECOption ={backgroundColor: props.backgroundColor ||"transparent",// Or a light blue like in the UItooltip:{trigger:"item",triggerOn:"mousemove",formatter:(params: any)=>{// params.data should be the original TreeNode data before ECharts processing if neededreturn`${params.data.nodeType}: ${params.name}`;},},series:[{type:"tree",data:[processedData],layout:"radial",// Key for radial layoutsymbol:"circle",// Default symbol// symbolSize: (value: any, params: any) => {// // Dynamic symbol size based on nodeType or value if needed// // This is now handled by getNodeStyle and processDataForEcharts// return params.data.symbolSize || 50;// },roam:true,// Allows zoom and panlabel:{position:"inside"as any,// Default label positionverticalAlign:"middle",align:"center",fontSize:12,},// symbolOffset: [50, "-50%"],edgeSymbol:["none","arrow"],edgeSymbolSize:[4,10],lineStyle:{color:COLORS.connector,width:1.5,curveness:0,// Slight curve for aesthetics// 配置箭头:起点无符号,终点用箭头symbol:["none","arrow"],// 起始点和结束点的符号symbolSize:[10,15],// 符号大小(箭头尺寸)},initialTreeDepth:2,// Expand to 2 levels initially (Country -> Category)expandAndCollapse:true,animationDurationUpdate:750,emphasis:{// Default emphasis for all nodes, can be overridden per nodefocus:"descendant",scale:true,},// Adjust radial layout parametersradial:{// These might need tweaking to match the UI's spread// radius: ['5%', '80%'], // Inner and outer radius// nodeGap: 30, // Gap between nodes in the same level// layerGap: 150, // Gap between layers},top:"5%",left:"5%",bottom:"5%",right:"5%",},],};chartInstance.setOption(option,true);// true to not merge with previous options};onMounted(()=>{nextTick(()=>{initChart();});window.addEventListener("resize", resizeChart);});onBeforeUnmount(()=>{window.removeEventListener("resize", resizeChart);chartInstance?.dispose();});watch(()=> props.chartData,()=>{setChartOptions();},{deep:true});watch(()=> props.backgroundColor,()=>{setChartOptions();});constresizeChart=()=>{chartInstance?.resize();};// Expose a resize method if needed by parentdefineExpose({resizeChart,});</script><style scoped>.radial-tree-view-container {width:100%;height:100%;min-height: 500px;/* Ensure a minimum height for the chart */}.chart-container {width:100%;height:100%;}</style>
mock数据
exportinterfaceTreeNode{name: string;id: string;value?: number;itemStyle?:{color?: string;borderColor?: string;borderWidth?: number;};label?:{show?: boolean;position?: string;formatter?: string |((params: any)=> string);color?: string;fontSize?: number;overflow?:"truncate"|"break"|"breakAll";width?: number;ellipsis?: string;};lineStyle?:{color?: string;width?: number;curveness?: number;};children?: TreeNode[];nodeType:|"country"|"category"|"brand"|"product"|"regulation"|"customer"|"patent"|"contributor";isCollapsed?: boolean;symbol?: string;symbolSize?: number | number[];emphasis?:{focus?:"ancestor"|"descendant"|"self"|"none";scale?: boolean | number;itemStyle?:{color?: string;borderColor?: string;borderWidth?: number;};label?:{color?: string;fontSize?: number;};lineStyle?:{color?: string;width?: number;};};}exportconstmockRadialData: TreeNode ={name:"美国",id:"usa",nodeType:"country",children:[{name:"客户",id:"customer_category",nodeType:"category",children:[{name:"10000 客户",id:"customer_1",nodeType:"customer"},// Simplified, assuming this is a leaf{name:"消费者画像",id:"consumer_profile",nodeType:"category",// Sub-categorychildren:[{name:"年龄段",id:"age_group",nodeType:"product"},// Using 'product' type for generic items for now{name:"学历层次",id:"education_level",nodeType:"product"},{name:"贡献者: 刘先生",id:"contrib_liu",nodeType:"contributor",},],},],},{name:"法规",id:"regulation_category",nodeType:"category",children:[{name:"10000 法规",id:"regulation_1",nodeType:"regulation"},],},{name:"品牌",id:"brand_main_category",nodeType:"category",children:[{name:"10000 品牌",id:"brand_count_node",nodeType:"brand"},// A summary node{name:"品牌A",id:"brand_a",nodeType:"brand",children:[{name:"品牌形象",id:"brand_a_image",nodeType:"product"},{name:"品牌故事",id:"brand_a_story",nodeType:"product"},{name:"贡献者: 刘先生",id:"contrib_brand_a_liu",nodeType:"contributor",},],},{name:"品牌B",id:"brand_b",nodeType:"brand",children:[{name:"品牌定位",id:"brand_b_position",nodeType:"product"},{name:"目标群体",id:"brand_b_target",nodeType:"product"},],},],},{name:"专利",id:"patent_category",nodeType:"category",children:[{name:"10000 专利",id:"patent_1",nodeType:"patent"}],},{name:"产品",id:"product_main_category",nodeType:"category",children:[{name:"10000 产品",id:"product_count_node",nodeType:"product"},{name:"产品1 (USB-C)",id:"product_1",nodeType:"product",children:[{name:"设计风格",id:"product_1_design",nodeType:"product"},{name:"充电方式",id:"product_1_charge",nodeType:"product"},{name:"外观尺寸",id:"product_1_size",nodeType:"product"},{name:"贡献者: 李小姐",id:"contrib_prod_1_li",nodeType:"contributor",},],},{name:"产品2",id:"product_2",nodeType:"product",children:[{name:"材料",id:"product_2_material",nodeType:"product"},{name:"重量",id:"product_2_weight",nodeType:"product"},],},],},{name:"价格",// In UI, this looks like a categoryid:"price_category",nodeType:"category",children:[{name:"10000 价格区间",id:"price_range_1",nodeType:"product"},// Using 'product' for generic item],},{name:"消费者",// In UI, this looks like a categoryid:"consumer_category_alt",nodeType:"category",children:[{name:"10000 消费者",id:"consumer_1_alt",nodeType:"customer"},],},],};