diff --git a/.collaboration b/.collaboration index 2698b9ff..2b18b8f5 100644 --- a/.collaboration +++ b/.collaboration @@ -2270,19 +2270,7 @@ }, { "ModuleName": "PCPage/工作台首页", - "State": 1, - "LockedBy": { - "UserName": "cuckooent", - "Email": "phoben@qq.com" - }, - "LockDateTime": "2024-11-05T20:09:42.8124043+08:00", - "ModuleType": 1, - "ToRemoveFiles": [ - "Pages\\工作台\\工作台首页.json", - "Pages\\工作台\\工作台首页.rd", - "Pages\\3868fc7f40b841649f14b5adba61954.json", - "Pages\\3868fc7f40b841649f14b5adba61954.rd" - ] + "ModuleType": 1 }, { "ModuleName": "PCPage/项目负荷 (2)", diff --git a/Pages/工作台/工作台首页.json b/Pages/工作台/工作台首页.json index a900529b..06f18cbd 100644 --- a/Pages/工作台/工作台首页.json +++ b/Pages/工作台/工作台首页.json @@ -702,7 +702,7 @@ ], "JSONDataSources": [], "ImageDataSource": [], - "Config": "{\"option\":\"console.log(JSON.stringify(Context[\\\"recommands\\\"]));\\n\\n// 处理数据,生成节点和链接 \\nconst processData = (data) => {\\n // 生成颜色函数 - 使用HSL颜色空间 \\n const getColorByType = (type, index, totalTypes) => {\\n const hue = ((index / totalTypes) * 360) % 360;\\n return `hsl(${hue}, 70%, 50%)`;\\n };\\n\\n // 收集所有唯一的节点 \\n const typeSet = new Set(data.map(item => item.类型));\\n const projectSet = new Set();\\n const taskSet = new Set();\\n\\n // 创建项目和任务的映射关系 \\n const projectTypeMap = new Map();\\n const taskTypeMap = new Map();\\n\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n\\n projectSet.add(projectKey);\\n taskSet.add(taskKey);\\n\\n // 记录项目和任务与类型的关系 \\n if (!projectTypeMap.has(projectKey)) {\\n projectTypeMap.set(projectKey, new Set());\\n }\\n if (!taskTypeMap.has(taskKey)) {\\n taskTypeMap.set(taskKey, new Set());\\n }\\n\\n projectTypeMap.get(projectKey).add(item.类型);\\n taskTypeMap.get(taskKey).add(item.类型);\\n });\\n\\n // 转换为数组 \\n const typeArray = Array.from(typeSet);\\n const projectArray = Array.from(projectSet);\\n const taskArray = Array.from(taskSet);\\n\\n // 创建类型颜色映射 \\n const typeColors = {};\\n typeArray.forEach((type, index) => {\\n typeColors[type] = getColorByType(type, index, typeArray.length);\\n });\\n\\n // 计算节点值 \\n const nodeValues = {};\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n\\n nodeValues[item.类型] = (nodeValues[item.类型] || 0) + item.数量;\\n nodeValues[projectKey] = (nodeValues[projectKey] || 0) + item.数量;\\n nodeValues[taskKey] = (nodeValues[taskKey] || 0) + item.数量;\\n });\\n\\n // 生成节点 \\n const nodes = [\\n // 类型节点 \\n ...typeArray.map((type, index) => ({\\n name: type,\\n value: nodeValues[type],\\n itemStyle: {\\n color: typeColors[type],\\n borderColor: typeColors[type]\\n },\\n category: '类型',\\n depth: 0\\n })),\\n\\n // 项目节点 \\n ...projectArray.map(project => ({\\n name: project,\\n value: nodeValues[project],\\n itemStyle: {\\n color: typeColors[Array.from(projectTypeMap.get(project))[0]],\\n opacity: 0.8\\n },\\n category: '项目',\\n depth: 1\\n })),\\n\\n // 任务节点 \\n ...taskArray.map(task => ({\\n name: task,\\n value: nodeValues[task],\\n itemStyle: {\\n color: typeColors[Array.from(taskTypeMap.get(task))[0]],\\n opacity: 0.2\\n },\\n category: '任务',\\n depth: 2\\n }))\\n ];\\n\\n // 生成连接 \\n const links = [];\\n\\n // 处理类型到项目的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const source = item.类型;\\n const target = projectKey;\\n\\n const existingLink = links.find(link =>\\n link.source === source && link.target === target\\n );\\n\\n if (existingLink) {\\n existingLink.value += item.数量;\\n } else {\\n links.push({\\n source: source,\\n target: target,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n\\n // 处理项目到任务的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n\\n const existingLink = links.find(link =>\\n link.source === projectKey && link.target === taskKey\\n );\\n\\n if (existingLink) {\\n existingLink.value += item.数量;\\n } else {\\n links.push({\\n source: projectKey,\\n target: taskKey,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n\\n return { nodes, links };\\n};\\n\\n// 修改提示文字获取函数 \\nconst gettooltip = (params) => {\\n if (params.dataType === 'node') {\\n return `${params.name}
数量: ${params.value}`;\\n }\\n\\n // 获取源节点和目标节点的深度 \\n const sourceNode = params.data.source;\\n const targetNode = params.data.target;\\n const isProjectToTask = sourceNode.includes(']') && targetNode.includes(']');\\n\\n if (isProjectToTask) {\\n // 项目到任务的连接 \\n return `\\n ${params.data.sourceType}
\\n 项目:${params.data.source}
\\n 任务:${params.data.target}
数量: ${params.value}`;\\n } else {\\n return `\\n ${params.data.sourceType}
\\n 项目:${params.data.target}
\\n 数量: ${params.value}`;\\n }\\n};\\n\\n// 处理数据 \\nconst { nodes, links } = processData(Context[\\\"recommands\\\"]);\\n\\n// Echarts配置 \\noption = {\\n backgroundColor: \\\"rgba(0, 0, 0, 0)\\\",\\n title: {\\n text: \\\"项目反馈桑基图\\\",\\n subtext: \\\"展示各个项目任务的不同反馈分类占比\\\",\\n left: \\\"center\\\",\\n top: 24,\\n textStyle: {\\n fontSize: 16,\\n fontWeight: \\\"bold\\\",\\n },\\n },\\n tooltip: {\\n trigger: 'item',\\n triggerOn: 'mousemove',\\n formatter: gettooltip\\n },\\n series: [{\\n type: \\\"sankey\\\",\\n animation: true,\\n animationDuration: 300,\\n animationEasingUpdate: 'quinticInOut',\\n left: '5%',\\n top: '12%',\\n right: '20%',\\n bottom: '12%',\\n nodeAlign: 'justify',\\n orient: 'horizontal',\\n emphasis: {\\n focus: 'adjacency'\\n },\\n data: nodes,\\n links: links,\\n nodeGap: 12,\\n nodeWidth: 24,\\n draggable: true,\\n levels: [{\\n depth: 0,\\n itemStyle: {\\n borderWidth: 2\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 1,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 2,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }],\\n label: {\\n position: 'right',\\n fontSize: 12,\\n lineHeight: 16,\\n color: '#000000',\\n distance: 10,\\n formatter: function (params) {\\n var name = params.name;\\n if (name.length > 15) {\\n name = name.substring(0, 15) + '...';\\n }\\n return `${name}(${params.value})`;\\n },\\n show: true,\\n align: 'left',\\n verticalAlign: 'middle',\\n backgroundColor: '#ffffffcc',\\n padding: [4, 8],\\n borderRadius: 4\\n }\\n }]\\n};\",\"graphTheme\":null,\"displayMode\":\"canvas\",\"jsCode\":\"\\n async ({Context,JSONContext,ImageContext,echarts,myChart,dat,Forguncy,d3,setInterval,setTimeout,ForguncyEchartsHelper,PublicResource})=>{\\n var datGUI=undefined;\\n var option={};\\n console.log(JSON.stringify(Context[\\\"recommands\\\"]));\\n// 处理数据,生成节点和链接 \\nconst processData = (data) => {\\n // 生成颜色函数 - 使用HSL颜色空间 \\n const getColorByType = (type, index, totalTypes) => {\\n const hue = ((index / totalTypes) * 360) % 360;\\n return `hsl(${hue}, 70%, 50%)`;\\n };\\n // 收集所有唯一的节点 \\n const typeSet = new Set(data.map(item => item.类型));\\n const projectSet = new Set();\\n const taskSet = new Set();\\n // 创建项目和任务的映射关系 \\n const projectTypeMap = new Map();\\n const taskTypeMap = new Map();\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n projectSet.add(projectKey);\\n taskSet.add(taskKey);\\n // 记录项目和任务与类型的关系 \\n if (!projectTypeMap.has(projectKey)) {\\n projectTypeMap.set(projectKey, new Set());\\n }\\n if (!taskTypeMap.has(taskKey)) {\\n taskTypeMap.set(taskKey, new Set());\\n }\\n projectTypeMap.get(projectKey).add(item.类型);\\n taskTypeMap.get(taskKey).add(item.类型);\\n });\\n // 转换为数组 \\n const typeArray = Array.from(typeSet);\\n const projectArray = Array.from(projectSet);\\n const taskArray = Array.from(taskSet);\\n // 创建类型颜色映射 \\n const typeColors = {};\\n typeArray.forEach((type, index) => {\\n typeColors[type] = getColorByType(type, index, typeArray.length);\\n });\\n // 计算节点值 \\n const nodeValues = {};\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n nodeValues[item.类型] = (nodeValues[item.类型] || 0) + item.数量;\\n nodeValues[projectKey] = (nodeValues[projectKey] || 0) + item.数量;\\n nodeValues[taskKey] = (nodeValues[taskKey] || 0) + item.数量;\\n });\\n // 生成节点 \\n const nodes = [\\n // 类型节点 \\n ...typeArray.map((type, index) => ({\\n name: type,\\n value: nodeValues[type],\\n itemStyle: {\\n color: typeColors[type],\\n borderColor: typeColors[type]\\n },\\n category: '类型',\\n depth: 0\\n })),\\n // 项目节点 \\n ...projectArray.map(project => ({\\n name: project,\\n value: nodeValues[project],\\n itemStyle: {\\n color: typeColors[Array.from(projectTypeMap.get(project))[0]],\\n opacity: 0.8\\n },\\n category: '项目',\\n depth: 1\\n })),\\n // 任务节点 \\n ...taskArray.map(task => ({\\n name: task,\\n value: nodeValues[task],\\n itemStyle: {\\n color: typeColors[Array.from(taskTypeMap.get(task))[0]],\\n opacity: 0.2\\n },\\n category: '任务',\\n depth: 2\\n }))\\n ];\\n // 生成连接 \\n const links = [];\\n // 处理类型到项目的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const source = item.类型;\\n const target = projectKey;\\n const existingLink = links.find(link => link.source === source && link.target === target);\\n if (existingLink) {\\n existingLink.value += item.数量;\\n }\\n else {\\n links.push({\\n source: source,\\n target: target,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n // 处理项目到任务的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n const existingLink = links.find(link => link.source === projectKey && link.target === taskKey);\\n if (existingLink) {\\n existingLink.value += item.数量;\\n }\\n else {\\n links.push({\\n source: projectKey,\\n target: taskKey,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n return { nodes, links };\\n};\\n// 修改提示文字获取函数 \\nconst gettooltip = (params) => {\\n if (params.dataType === 'node') {\\n return `${params.name}
数量: ${params.value}`;\\n }\\n // 获取源节点和目标节点的深度 \\n const sourceNode = params.data.source;\\n const targetNode = params.data.target;\\n const isProjectToTask = sourceNode.includes(']') && targetNode.includes(']');\\n if (isProjectToTask) {\\n // 项目到任务的连接 \\n return `\\n ${params.data.sourceType}
\\n 项目:${params.data.source}
\\n 任务:${params.data.target}
数量: ${params.value}`;\\n }\\n else {\\n return `\\n ${params.data.sourceType}
\\n 项目:${params.data.target}
\\n 数量: ${params.value}`;\\n }\\n};\\n// 处理数据 \\nconst { nodes, links } = processData(Context[\\\"recommands\\\"]);\\n// Echarts配置 \\noption = {\\n backgroundColor: \\\"rgba(0, 0, 0, 0)\\\",\\n title: {\\n text: \\\"项目反馈桑基图\\\",\\n subtext: \\\"展示各个项目任务的不同反馈分类占比\\\",\\n left: \\\"center\\\",\\n top: 24,\\n textStyle: {\\n fontSize: 16,\\n fontWeight: \\\"bold\\\",\\n },\\n },\\n tooltip: {\\n trigger: 'item',\\n triggerOn: 'mousemove',\\n formatter: gettooltip\\n },\\n series: [{\\n type: \\\"sankey\\\",\\n animation: true,\\n animationDuration: 300,\\n animationEasingUpdate: 'quinticInOut',\\n left: '5%',\\n top: '12%',\\n right: '20%',\\n bottom: '12%',\\n nodeAlign: 'justify',\\n orient: 'horizontal',\\n emphasis: {\\n focus: 'adjacency'\\n },\\n data: nodes,\\n links: links,\\n nodeGap: 12,\\n nodeWidth: 24,\\n draggable: true,\\n levels: [{\\n depth: 0,\\n itemStyle: {\\n borderWidth: 2\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 1,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 2,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }],\\n label: {\\n position: 'right',\\n fontSize: 12,\\n lineHeight: 16,\\n color: '#000000',\\n distance: 10,\\n formatter: function (params) {\\n var name = params.name;\\n if (name.length > 15) {\\n name = name.substring(0, 15) + '...';\\n }\\n return `${name}(${params.value})`;\\n },\\n show: true,\\n align: 'left',\\n verticalAlign: 'middle',\\n backgroundColor: '#ffffffcc',\\n padding: [4, 8],\\n borderRadius: 4\\n }\\n }]\\n};\\n\\n return {\\n option,\\n datGUI,\\n };\\n }\\n \"}" + "Config": "{\"option\":\"console.log(JSON.stringify(Context[\\\"recommands\\\"]));\\n\\n// 处理数据,生成节点和链接 \\nconst processData = (data) => {\\n // 生成颜色函数 - 使用HSL颜色空间,为每层生成不同的柔和颜色 \\n const generateColors = (count, depth) => {\\n const colors = [];\\n // 黄金角度 约137.5° \\n const goldenAngle = 137.5;\\n\\n // 根据深度调整饱和度和亮度 \\n const getSaturation = (depth) => {\\n switch (depth) {\\n case 0: return '60%'; // 第一层 \\n case 1: return '50%'; // 第二层 \\n case 2: return '40%'; // 第三层 \\n default: return '50%';\\n }\\n };\\n\\n const getLightness = (depth) => {\\n switch (depth) {\\n case 0: return '60%'; // 第一层 \\n case 1: return '60%'; // 第二层 \\n case 2: return '60%'; // 第三层 \\n default: return '60%';\\n }\\n };\\n\\n // 使用黄金角度来生成颜色 \\n const usedHues = new Set();\\n const minHueDistance = 30; // 最小色相差 \\n\\n for (let i = 0; i < count; i++) {\\n // 基础色相值使用黄金角度 \\n let baseHue = (i * goldenAngle) % 360;\\n\\n // 添加小范围随机偏移,但确保与现有颜色保持最小距离 \\n let hue = baseHue;\\n let attempts = 0;\\n const maxAttempts = 10;\\n\\n // 检查新生成的色相是否与已有的色相保持足够距离 \\n while (attempts < maxAttempts) {\\n let isValidHue = true;\\n\\n for (const existingHue of usedHues) {\\n const distance = Math.min(\\n Math.abs(hue - existingHue),\\n 360 - Math.abs(hue - existingHue)\\n );\\n\\n if (distance < minHueDistance) {\\n isValidHue = false;\\n break;\\n }\\n }\\n\\n if (isValidHue) {\\n break;\\n }\\n\\n // 如果当前色相不合适,添加一个小偏移再试 \\n hue = (baseHue + Math.random() * 30 - 15) % 360;\\n if (hue < 0) hue += 360;\\n attempts++;\\n }\\n\\n usedHues.add(hue);\\n colors.push(`hsl(${hue}, ${getSaturation(depth)}, ${getLightness(depth)})`);\\n }\\n\\n return colors;\\n };\\n\\n // 收集所有唯一的节点 \\n const typeSet = new Set(data.map(item => item.类型));\\n const projectSet = new Set();\\n const taskSet = new Set();\\n\\n // 创建项目和任务的映射关系 \\n const projectTypeMap = new Map();\\n const taskTypeMap = new Map();\\n\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n\\n projectSet.add(projectKey);\\n taskSet.add(taskKey);\\n\\n if (!projectTypeMap.has(projectKey)) {\\n projectTypeMap.set(projectKey, new Set());\\n }\\n if (!taskTypeMap.has(taskKey)) {\\n taskTypeMap.set(taskKey, new Set());\\n }\\n\\n projectTypeMap.get(projectKey).add(item.类型);\\n taskTypeMap.get(taskKey).add(item.类型);\\n });\\n\\n // 转换为数组 \\n const typeArray = Array.from(typeSet);\\n const projectArray = Array.from(projectSet);\\n const taskArray = Array.from(taskSet);\\n\\n // 为每层生成颜色 \\n const typeColors = {};\\n const projectColors = {};\\n const taskColors = {};\\n\\n const level0Colors = generateColors(typeArray.length, 0);\\n const level1Colors = generateColors(projectArray.length, 1);\\n const level2Colors = generateColors(taskArray.length, 2);\\n\\n typeArray.forEach((type, index) => {\\n typeColors[type] = level0Colors[index];\\n });\\n\\n projectArray.forEach((project, index) => {\\n projectColors[project] = level1Colors[index];\\n });\\n\\n taskArray.forEach((task, index) => {\\n taskColors[task] = level2Colors[index];\\n });\\n\\n // 计算节点值 \\n const nodeValues = {};\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n\\n nodeValues[item.类型] = (nodeValues[item.类型] || 0) + item.数量;\\n nodeValues[projectKey] = (nodeValues[projectKey] || 0) + item.数量;\\n nodeValues[taskKey] = (nodeValues[taskKey] || 0) + item.数量;\\n });\\n\\n // 生成节点 \\n const nodes = [\\n // 类型节点 \\n ...typeArray.map((type, index) => ({\\n name: type,\\n value: nodeValues[type],\\n itemStyle: {\\n color: typeColors[type],\\n borderColor: typeColors[type]\\n },\\n category: '类型',\\n depth: 0\\n })),\\n\\n // 项目节点 \\n ...projectArray.map(project => ({\\n name: project,\\n value: nodeValues[project],\\n itemStyle: {\\n color: projectColors[project]\\n },\\n category: '项目',\\n depth: 1\\n })),\\n\\n // 任务节点 \\n ...taskArray.map(task => ({\\n name: task,\\n value: nodeValues[task],\\n itemStyle: {\\n color: taskColors[task]\\n },\\n category: '任务',\\n depth: 2\\n }))\\n ];\\n\\n // 生成连接 \\n const links = [];\\n\\n // 处理类型到项目的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const source = item.类型;\\n const target = projectKey;\\n\\n const existingLink = links.find(link =>\\n link.source === source && link.target === target\\n );\\n\\n if (existingLink) {\\n existingLink.value += item.数量;\\n } else {\\n links.push({\\n source: source,\\n target: target,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n\\n // 处理项目到任务的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n\\n const existingLink = links.find(link =>\\n link.source === projectKey && link.target === taskKey\\n );\\n\\n if (existingLink) {\\n existingLink.value += item.数量;\\n } else {\\n links.push({\\n source: projectKey,\\n target: taskKey,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n\\n return { nodes, links };\\n};\\n\\n// 修改提示文字获取函数 \\nconst gettooltip = (params) => {\\n if (params.dataType === 'node') {\\n return `${params.name}
数量: ${params.value}`;\\n }\\n\\n // 获取源节点和目标节点的深度 \\n const sourceNode = params.data.source;\\n const targetNode = params.data.target;\\n const isProjectToTask = sourceNode.includes(']') && targetNode.includes(']');\\n\\n if (isProjectToTask) {\\n // 项目到任务的连接 \\n return ` \\n ${params.data.sourceType}
\\n 项目:${params.data.source}
\\n 任务:${params.data.target}
数量: ${params.value}`;\\n } else {\\n return ` \\n ${params.data.sourceType}
\\n 项目:${params.data.target}
\\n 数量: ${params.value}`;\\n }\\n};\\n\\n// 处理数据 \\nconst { nodes, links } = processData(Context[\\\"recommands\\\"]);\\n\\n// Echarts配置 \\noption = {\\n backgroundColor: \\\"rgba(0, 0, 0, 0)\\\",\\n title: {\\n text: \\\"项目反馈桑基图\\\",\\n subtext: \\\"展示各个项目任务的不同反馈分类占比\\\",\\n left: \\\"center\\\",\\n top: 24,\\n textStyle: {\\n fontSize: 16,\\n fontWeight: \\\"bold\\\",\\n },\\n },\\n tooltip: {\\n trigger: 'item',\\n triggerOn: 'mousemove',\\n formatter: gettooltip\\n },\\n series: [{\\n type: \\\"sankey\\\",\\n animation: true,\\n animationDuration: 300,\\n animationEasingUpdate: 'quinticInOut',\\n left: '5%',\\n top: '12%',\\n right: '20%',\\n bottom: '12%',\\n nodeAlign: 'justify',\\n orient: 'horizontal',\\n emphasis: {\\n focus: 'adjacency'\\n },\\n data: nodes,\\n links: links,\\n nodeGap: 12,\\n nodeWidth: 24,\\n draggable: true,\\n levels: [{\\n depth: 0,\\n itemStyle: {\\n borderWidth: 2\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 1,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 2,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }],\\n label: {\\n position: 'right',\\n fontSize: 10,\\n lineHeight: 14,\\n color: '#000000',\\n distance: 10,\\n formatter: function (params) {\\n var name = params.name;\\n if (name.length > 15) {\\n name = name.substring(0, 15) + '...';\\n }\\n return `${name}(${params.value})`;\\n },\\n show: true,\\n align: 'left',\\n verticalAlign: 'middle'\\n }\\n }]\\n};\",\"graphTheme\":null,\"displayMode\":\"canvas\",\"jsCode\":\"\\n async ({Context,JSONContext,ImageContext,echarts,myChart,dat,Forguncy,d3,setInterval,setTimeout,ForguncyEchartsHelper,PublicResource})=>{\\n var datGUI=undefined;\\n var option={};\\n console.log(JSON.stringify(Context[\\\"recommands\\\"]));\\n// 处理数据,生成节点和链接 \\nconst processData = (data) => {\\n // 生成颜色函数 - 使用HSL颜色空间,为每层生成不同的柔和颜色 \\n const generateColors = (count, depth) => {\\n const colors = [];\\n // 黄金角度 约137.5° \\n const goldenAngle = 137.5;\\n // 根据深度调整饱和度和亮度 \\n const getSaturation = (depth) => {\\n switch (depth) {\\n case 0: return '60%'; // 第一层 \\n case 1: return '50%'; // 第二层 \\n case 2: return '40%'; // 第三层 \\n default: return '50%';\\n }\\n };\\n const getLightness = (depth) => {\\n switch (depth) {\\n case 0: return '60%'; // 第一层 \\n case 1: return '60%'; // 第二层 \\n case 2: return '60%'; // 第三层 \\n default: return '60%';\\n }\\n };\\n // 使用黄金角度来生成颜色 \\n const usedHues = new Set();\\n const minHueDistance = 30; // 最小色相差 \\n for (let i = 0; i < count; i++) {\\n // 基础色相值使用黄金角度 \\n let baseHue = (i * goldenAngle) % 360;\\n // 添加小范围随机偏移,但确保与现有颜色保持最小距离 \\n let hue = baseHue;\\n let attempts = 0;\\n const maxAttempts = 10;\\n // 检查新生成的色相是否与已有的色相保持足够距离 \\n while (attempts < maxAttempts) {\\n let isValidHue = true;\\n for (const existingHue of usedHues) {\\n const distance = Math.min(Math.abs(hue - existingHue), 360 - Math.abs(hue - existingHue));\\n if (distance < minHueDistance) {\\n isValidHue = false;\\n break;\\n }\\n }\\n if (isValidHue) {\\n break;\\n }\\n // 如果当前色相不合适,添加一个小偏移再试 \\n hue = (baseHue + Math.random() * 30 - 15) % 360;\\n if (hue < 0)\\n hue += 360;\\n attempts++;\\n }\\n usedHues.add(hue);\\n colors.push(`hsl(${hue}, ${getSaturation(depth)}, ${getLightness(depth)})`);\\n }\\n return colors;\\n };\\n // 收集所有唯一的节点 \\n const typeSet = new Set(data.map(item => item.类型));\\n const projectSet = new Set();\\n const taskSet = new Set();\\n // 创建项目和任务的映射关系 \\n const projectTypeMap = new Map();\\n const taskTypeMap = new Map();\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n projectSet.add(projectKey);\\n taskSet.add(taskKey);\\n if (!projectTypeMap.has(projectKey)) {\\n projectTypeMap.set(projectKey, new Set());\\n }\\n if (!taskTypeMap.has(taskKey)) {\\n taskTypeMap.set(taskKey, new Set());\\n }\\n projectTypeMap.get(projectKey).add(item.类型);\\n taskTypeMap.get(taskKey).add(item.类型);\\n });\\n // 转换为数组 \\n const typeArray = Array.from(typeSet);\\n const projectArray = Array.from(projectSet);\\n const taskArray = Array.from(taskSet);\\n // 为每层生成颜色 \\n const typeColors = {};\\n const projectColors = {};\\n const taskColors = {};\\n const level0Colors = generateColors(typeArray.length, 0);\\n const level1Colors = generateColors(projectArray.length, 1);\\n const level2Colors = generateColors(taskArray.length, 2);\\n typeArray.forEach((type, index) => {\\n typeColors[type] = level0Colors[index];\\n });\\n projectArray.forEach((project, index) => {\\n projectColors[project] = level1Colors[index];\\n });\\n taskArray.forEach((task, index) => {\\n taskColors[task] = level2Colors[index];\\n });\\n // 计算节点值 \\n const nodeValues = {};\\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n nodeValues[item.类型] = (nodeValues[item.类型] || 0) + item.数量;\\n nodeValues[projectKey] = (nodeValues[projectKey] || 0) + item.数量;\\n nodeValues[taskKey] = (nodeValues[taskKey] || 0) + item.数量;\\n });\\n // 生成节点 \\n const nodes = [\\n // 类型节点 \\n ...typeArray.map((type, index) => ({\\n name: type,\\n value: nodeValues[type],\\n itemStyle: {\\n color: typeColors[type],\\n borderColor: typeColors[type]\\n },\\n category: '类型',\\n depth: 0\\n })),\\n // 项目节点 \\n ...projectArray.map(project => ({\\n name: project,\\n value: nodeValues[project],\\n itemStyle: {\\n color: projectColors[project]\\n },\\n category: '项目',\\n depth: 1\\n })),\\n // 任务节点 \\n ...taskArray.map(task => ({\\n name: task,\\n value: nodeValues[task],\\n itemStyle: {\\n color: taskColors[task]\\n },\\n category: '任务',\\n depth: 2\\n }))\\n ];\\n // 生成连接 \\n const links = [];\\n // 处理类型到项目的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const source = item.类型;\\n const target = projectKey;\\n const existingLink = links.find(link => link.source === source && link.target === target);\\n if (existingLink) {\\n existingLink.value += item.数量;\\n }\\n else {\\n links.push({\\n source: source,\\n target: target,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n // 处理项目到任务的连接 \\n data.forEach(item => {\\n const projectKey = `[${item.项目ID}]${item.项目}`;\\n const taskKey = `[${item.任务ID}]${item.任务}`;\\n const existingLink = links.find(link => link.source === projectKey && link.target === taskKey);\\n if (existingLink) {\\n existingLink.value += item.数量;\\n }\\n else {\\n links.push({\\n source: projectKey,\\n target: taskKey,\\n value: item.数量,\\n sourceType: item.类型,\\n lineStyle: {\\n color: typeColors[item.类型],\\n opacity: 0.2\\n }\\n });\\n }\\n });\\n return { nodes, links };\\n};\\n// 修改提示文字获取函数 \\nconst gettooltip = (params) => {\\n if (params.dataType === 'node') {\\n return `${params.name}
数量: ${params.value}`;\\n }\\n // 获取源节点和目标节点的深度 \\n const sourceNode = params.data.source;\\n const targetNode = params.data.target;\\n const isProjectToTask = sourceNode.includes(']') && targetNode.includes(']');\\n if (isProjectToTask) {\\n // 项目到任务的连接 \\n return ` \\n ${params.data.sourceType}
\\n 项目:${params.data.source}
\\n 任务:${params.data.target}
数量: ${params.value}`;\\n }\\n else {\\n return ` \\n ${params.data.sourceType}
\\n 项目:${params.data.target}
\\n 数量: ${params.value}`;\\n }\\n};\\n// 处理数据 \\nconst { nodes, links } = processData(Context[\\\"recommands\\\"]);\\n// Echarts配置 \\noption = {\\n backgroundColor: \\\"rgba(0, 0, 0, 0)\\\",\\n title: {\\n text: \\\"项目反馈桑基图\\\",\\n subtext: \\\"展示各个项目任务的不同反馈分类占比\\\",\\n left: \\\"center\\\",\\n top: 24,\\n textStyle: {\\n fontSize: 16,\\n fontWeight: \\\"bold\\\",\\n },\\n },\\n tooltip: {\\n trigger: 'item',\\n triggerOn: 'mousemove',\\n formatter: gettooltip\\n },\\n series: [{\\n type: \\\"sankey\\\",\\n animation: true,\\n animationDuration: 300,\\n animationEasingUpdate: 'quinticInOut',\\n left: '5%',\\n top: '12%',\\n right: '20%',\\n bottom: '12%',\\n nodeAlign: 'justify',\\n orient: 'horizontal',\\n emphasis: {\\n focus: 'adjacency'\\n },\\n data: nodes,\\n links: links,\\n nodeGap: 12,\\n nodeWidth: 24,\\n draggable: true,\\n levels: [{\\n depth: 0,\\n itemStyle: {\\n borderWidth: 2\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 1,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }, {\\n depth: 2,\\n itemStyle: {\\n borderWidth: 1\\n },\\n lineStyle: {\\n curveness: 0.5,\\n opacity: 0.8\\n }\\n }],\\n label: {\\n position: 'right',\\n fontSize: 10,\\n lineHeight: 14,\\n color: '#000000',\\n distance: 10,\\n formatter: function (params) {\\n var name = params.name;\\n if (name.length > 15) {\\n name = name.substring(0, 15) + '...';\\n }\\n return `${name}(${params.value})`;\\n },\\n show: true,\\n align: 'left',\\n verticalAlign: 'middle'\\n }\\n }]\\n};\\n\\n return {\\n option,\\n datGUI,\\n };\\n }\\n \"}" } }, "16,1": { @@ -1059,4 +1059,4 @@ "Size": "1473,1000" } ] -}//nZ2B20lyCMQergFE2+GiiAbmOeLp6pDjNvxE/Q0lmawej0A7rt8WfWX/JdODhcssE8XPEbV1He31mfKyL8PJ/h0zG1nKqvEmM0U+fs8PU3LsGcv0Nf64m1Jle9K9HjeDgAIRjEMvx3P6tKjk8jB/T7WJBBxuslEqcWXO9uVZBuj8QRZTHwaVLCO0KbYWyhZED/r1F0+Z+WJMBmNOMBQitIbAkV1zex2oUYLuUmPz/Ck5SS/8uzIwNAjtdXmHL6F8Lb2EJL8BCwqOLWTzKjY9NTJ1z7YzJmNnN0jxtz2QD5SGdm/0AqYUx5KHTaup5DTsSGMzuoVqmykeLfrfH/gJWKi3sABZIbdi7pC0lIfa9GULWkTUC+DWajHp0QhMwONAkwXyzd1LFldp+FnTy8FtIjSPXmERaB6zUUAdBdP8fb9b84qpfm/aZQRIF0u7AqEi14HyaiKISp2xE5p/17GyphKLPWurKTQuhjXMOh8/zxgzIXtBHPIb2AlA1EFPvhTrRIKSnO1TG596cL6jBic64UVyCVynNY8fTNjOIbYbHwJqjj38+sWK1O5mAa+eTXoKTcILBTxX9q2dHLjuzPl6SNn8Q8OlnJsPS+IN/vgV5je+l6RBvXDhkvyxH2nzFcjg8Zgst7WFXJW+ytQZw8IZIk5hTVptnBaDPnQA3RF9XHUx7Pcyk+ebTgR5aH/RtLrCBHhJwqzKGIWEcz8tTgmRahzfpnoqcifJgZD5jYSpH/6Pw8l5XZsY9tJKUHygpyR4b+CGDhu3DcKn798ex7ACAi2oYPgDOSMSr/OUyS5in+eG0FnDuJvsWS9Ev95LUgnEFXUdPsoG47bO656j24Oib63784KjbIAwc7ky1wki7Mb0fBDbg5UbgQfFepuxVeg2q2stIIUBuHR4ZF4E6OlM/Q==|920 \ No newline at end of file +}//AC7J+C2Ynr9zLRDgGyIDJJ5UKUpOQSv9f9BtzST5Hdk6W7R+4NoDq3GaHNygfIyKzLyq8IT8ABm0HDsmyXG9cVvRdfWD4bKR+dRaB6OuwDcsZYj6mLSPpY+OyKWTdlLwffPy/8nkGNc0vE/8b3RNyIZYeNaFxUmB5xwYs12yY1RqSohQ2180RfeMXq9oc8bP8xQPdfwZgJGuPDeIVsI4ocSle+haYZqbKCwI5LUl6ok9ruQpXP9lnRrrJqUQxtJgNU+2Dwt/rT3WyEtAGydnemcpWaOnKo+sREnTHHXRUPwPZ/+qgChTNR/mzy0CXRnOlxm+J6J2RcvZk3oueOV/CTzdzUDHBjzI0buWNNSvV2IYdJ7nKQ/GdTl4xMP1PDhdTnvtGkNiUsxiDrVcxailGo18lRMRtRN67ZqbYZehJ3QZYdpP1N7QrXJ3xbeeGc3KGAeCjnsrHNknQAdlvMBIZMZg4lDozQvxtp4VYwBI62CpKES96FyMHHIshVbOWRqTRio8yKCXlDTfXR9NtrlHZmcAoUJwkOiLwKDtX+eoVI86ftJTjLFZrCqMNSQLYdcqaVC8lUIKwNWLA/9eN+Huj5AmMQ0E59IGBPgUzBJUTVMr2MDV1lpbLXcnBNMkbC57KClc1vPUgOXAXW0V2zK+nwZFKDaf1IbMxtutvSEzM5SH0mqZSYmW1XoBuqne03d58cIlQwSdVHBEifOuT8ix+Me7JqiaSYdt8hvbgzt64C/TGgLTa3fjv85WO6eZskHoqxnTmh59rAmHmm7NgE487DxoGprivuz2N5B+psur2I1b44XAUeg+3T4LsR5MOxOoNg2aAkERd+WC6dMHMqgqpPUqAOKBrV1zIskdN5PIvblVl9eKcgIZ774BAfL4BrXBXwmIpohT6gQfqAQaSMlXPg==|920 \ No newline at end of file