D3强制布局防止标签重叠

如何解决D3强制布局防止标签重叠

我有一个D3部队布局,其中有两种类型的节点:源和目标。 源应该在左侧,链接到右侧的目标。节点的位置按预期工作,但是当我向节点添加标签时,我无法防止标签与节点或其他标签重叠。

我尝试使用以下模板:

https://observablehq.com/@d3/voronoi-labelshttp://bl.ocks.org/MoritzStefaner/1377729

当前状态:

PACKAGE_USAGE_STATS
const graph = {
  links: [{
      sourceid: 4433,targetid: 3528,source: 1,target: 0,value: 1
    },{
      sourceid: 4617,targetid: 96,source: 3,target: 2,value: 24
    },{
      sourceid: 4633,targetid: 149,source: 5,target: 4,targetid: 2876,target: 6,{
      sourceid: 4753,source: 7,value: 2
    },{
      sourceid: 4758,targetid: 192,source: 9,target: 8,{
      sourceid: 7174,targetid: 132,source: 11,target: 10,value: 32
    },targetid: 178,target: 12,targetid: 603,target: 13,targetid: 3120,target: 14,value: 30
    },targetid: 3678,target: 15,targetid: 3846,target: 16,{
      sourceid: 14806,source: 17,value: 3
    },{
      sourceid: 19163,source: 18,{
      sourceid: 19343,source: 19,value: 62
    },{
      sourceid: 20193,source: 20,value: 7
    },{
      sourceid: 20749,source: 21,{
      sourceid: 21585,targetid: 67,source: 23,target: 22,value: 18
    },value: 15
    },targetid: 3808,target: 24,{
      sourceid: 22373,source: 25,{
      sourceid: 23200,source: 26,{
      sourceid: 24201,targetid: 82,source: 28,target: 27,targetid: 86,target: 29,targetid: 113,target: 30,value: 10
    },targetid: 373,target: 31,value: 12
    },targetid: 702,target: 32,value: 9
    },targetid: 2756,target: 33,value: 22
    },{
      sourceid: 24617,source: 34,{
      sourceid: 25134,source: 35,{
      sourceid: 25385,source: 36,targetid: 2753,target: 37,{
      sourceid: 25823,source: 38,targetid: 3757,target: 39,{
      sourceid: 26184,source: 40,value: 8
    },value: 6
    },targetid: 672,target: 41,targetid: 2874,target: 42,{
      sourceid: 26388,source: 43,value: 21
    },{
      sourceid: 26510,source: 44,{
      sourceid: 26560,source: 45,value: 5
    },{
      sourceid: 26571,source: 46,{
      sourceid: 26572,source: 47,{
      sourceid: 26574,source: 48,{
      sourceid: 26597,source: 49,value: 13
    },{
      sourceid: 26603,source: 50,{
      sourceid: 26613,source: 51,value: 11
    },{
      sourceid: 26647,source: 52,{
      sourceid: 26668,source: 53,{
      sourceid: 26798,source: 54,value: 7
    }
  ],nodes: [{
      id: 3528,value: 1,name: 'target1',target: true
    },{
      id: 4433,name: 'source1',target: false
    },{
      id: 96,value: 110,name: 'target2',{
      id: 4617,value: 24,name: 'source2',{
      id: 149,value: 95,name: 'target3',{
      id: 4633,value: 2,name: 'source3',{
      id: 2876,value: 78,name: 'target4',{
      id: 4753,name: 'source4',{
      id: 192,name: 'target5',{
      id: 4758,name: 'source5',{
      id: 132,value: 61,name: 'target6',{
      id: 7174,value: 222,name: 'source6',{
      id: 178,value: 42,name: 'target7',{
      id: 603,value: 32,name: 'target8',{
      id: 3120,value: 64,name: 'target9',{
      id: 3678,name: 'target10',{
      id: 3846,name: 'target11',{
      id: 14806,value: 3,name: 'source7',{
      id: 19163,name: 'source8',{
      id: 19343,value: 62,name: 'source9',{
      id: 20193,value: 7,name: 'source10',{
      id: 20749,name: 'source11',{
      id: 67,value: 17,name: 'target12',{
      id: 21585,name: 'source12',{
      id: 3808,value: 5,name: 'target13',{
      id: 22373,name: 'source13',{
      id: 23200,name: 'source14',{
      id: 82,value: 40,name: 'target14',{
      id: 24201,value: 174,name: 'source15',{
      id: 86,name: 'target15',{
      id: 113,value: 49,name: 'target16',{
      id: 373,value: 12,name: 'target17',{
      id: 702,value: 9,name: 'target18',{
      id: 2756,name: 'target19',{
      id: 24617,name: 'source16',{
      id: 25134,name: 'source17',{
      id: 25385,value: 36,name: 'source18',{
      id: 2753,name: 'target20',{
      id: 25823,name: 'source19',{
      id: 3757,name: 'target21',{
      id: 26184,value: 21,name: 'source20',{
      id: 672,value: 29,name: 'target22',{
      id: 2874,name: 'target23',{
      id: 26388,name: 'source21',{
      id: 26510,name: 'source22',{
      id: 26560,value: 10,name: 'source23',{
      id: 26571,value: 4,name: 'source24',{
      id: 26572,value: 28,name: 'source25',{
      id: 26574,name: 'source26',{
      id: 26597,name: 'source27',{
      id: 26603,name: 'source28',{
      id: 26613,name: 'source29',{
      id: 26647,name: 'source30',{
      id: 26668,name: 'source31',{
      id: 26798,name: 'source32',{
      id: 3821,name: 'target24',{
      id: 597,name: 'target25',{
      id: 301,name: 'target26',target: true
    }
  ]
};

/* max "value" of nodes (to size the bubbles) */
var maxNodeValue = 62;

/* max "value" of link (to size the width of lines) */
var maxLinkValue = 22;

var radiusScale = d3.scaleSqrt().domain([1,(maxNodeValue)]).range([2,10]);
var linkScale = d3.scaleSqrt().domain([1,(maxLinkValue)]).range([0.5,3]);
// svg objects
let link,node,labelNode;

const width = 1200;
const height = 800;
var categoryCenters = {
  source: {
    x: /*1**/ width / 4,y: height / 2
  },target: {
    x: 3 * width / 4,y: height / 2
  }
};


var label = {
  'nodes': [],'links': []
};

//////////// FORCE SIMULATION //////////// 

// force simulator
var simulation = d3.forceSimulation();

var labelSimulation = d3.forceSimulation();

// set up the simulation and event to update locations after each tick
function initializeSimulation() {
  simulation.nodes(graph.nodes);
  labelSimulation.nodes(label.nodes);
  initializeForces();
  simulation.on("tick",ticked);
}

// values for all forces
const forceProperties = {
  center: {
    x: 0.5,y: 0.5
  },charge: {
    enabled: false,strength: -30,distanceMin: 1,distanceMax: 100
  },collide: {
    enabled: true,strength: 0.7,iterations: 1,radius: 5
  },forceX: {
    enabled: true,strength: 0.6,},forceY: {
    enabled: true,strength: 0.1,link: {
    enabled: true,iterations: 1
  },labelCharge: {
    enabled: true,strength: -30
  },labelLink: {
    distance: 0,strength: 2,iterations: 1
  }
}
let sources = 0,targets = 0;

graph.nodes.forEach(function(d,i) {
  d.target ? targets++ : sources++;
  label.nodes.push({
    node: d
  });
  label.nodes.push({
    node: d
  });
  label.links.push({
    source: i * 2,target: i * 2 + 1
  });
});

initializeDisplay();
initializeSimulation();

function nodeSpacer(d) {
  const nodeSpaceValue = Math.round(Math.max(2,(height / (Math.max(sources,targets)))));
  return nodeSpaceValue + radiusScale(d.value);
}


// add forces to the simulation
function initializeForces() {
  // add forces and associate each with a name
  simulation
    .force("link",d3.forceLink())
    .force("charge",d3.forceManyBody())
    .force("collide",d3.forceCollide(nodeSpacer))
    .force("center",d3.forceCenter())
    .force("forceX",d3.forceX())
    .force("forceY",d3.forceY());
  labelSimulation
    .force("link",d3.forceCollide());
  // apply properties to each of the forces
  updateForces();
}

// apply new force properties
function updateForces() {
  // get each force by name and update the properties
  simulation.force("center")
    .x(width * forceProperties.center.x)
    .y(height * forceProperties.center.y);
  simulation.force("charge")
    .strength(forceProperties.charge.strength * forceProperties.charge.enabled)
    .distanceMin(forceProperties.charge.distanceMin)
    .distanceMax(forceProperties.charge.distanceMax);
  simulation.force("collide")
    .strength(forceProperties.collide.strength * forceProperties.collide.enabled)
    .iterations(forceProperties.collide.iterations);
  simulation.force("forceX")
    .strength(forceProperties.forceX.strength * forceProperties.forceX.enabled)
    .x(function(d) {
      return d.target ? categoryCenters.target.x : categoryCenters.source.x;
    });
  simulation.force("forceY")
    .strength(forceProperties.forceY.strength * forceProperties.forceY.enabled)
    .y(function(d) {
      return d.target ? categoryCenters.target.y : categoryCenters.source.y;
    });
  simulation.force("link")
    .id(function(d) {
      return d.index;
    })
    .distance(function(d) {
      return radiusScale(d.value) + width / 2.0;
    })
    .iterations(forceProperties.link.iterations)
    .links(forceProperties.link.enabled ? graph.links : []);

  labelSimulation.force("charge")
    .strength(forceProperties.labelCharge.strength * forceProperties.labelCharge.enabled)
  labelSimulation.force("link")
    .distance(forceProperties.labelLink.distance)
    .iterations(forceProperties.labelLink.iterations)
    .links(label.links);

  // updates ignored until this is run
  // restarts the simulation (important if simulation has already slowed down)
  simulation.alpha(1).restart();
  labelSimulation.alpha(1).restart();
}



const adjlist = [];

graph.links.forEach(function(d) {
  adjlist[d.source.index + "-" + d.target.index] = true;
  adjlist[d.target.index + "-" + d.source.index] = true;
});

function neigh(a,b) {
  return a == b || adjlist[a + "-" + b];
}

//////////// DISPLAY ////////////

// generate the svg objects and force simulation
function initializeDisplay() {
  const svg = d3.select("#viz").attr("width",width).attr("height",height);
  var container = svg.append("g");

  link = container.append("g").attr("class","links")
    .selectAll("line")
    .data(graph.links)
    .enter()
    .append("line")
    .attr("stroke","#aaa")
    .attr("stroke-width",function(d) {
      return linkScale(d.value);
    });

  node = container.append("g").attr("class","nodes")
    .selectAll("g")
    .data(graph.nodes)
    .enter()
    .append("circle")
    .attr("r",function(d) {
      return radiusScale(d.value);
    })
    .attr("fill",function(d) {
      return d.target ? "#FF0000" : "#0000FF";
    })

  labelNode = container.append("g").attr("class","labelNodes")
    .selectAll("text")
    .data(label.nodes)
    .enter()
    .append("text")
    .text(function(d,i) {
      return i % 2 == 0 ? "" : d.node.name + "(" + d3.format(',.0f')(d.node.value) + ")";
    })
    .style("fill","#555")
    .style("font-family","Arial")
    .style("font-size",12)
    .style("pointer-events","none"); // to prevent mouseover/drag capture*/
}


// update the display based on the forces (but not positions)
function updateDisplay() {
  node
    .attr("r",function(d) {
      return radiusScale(d.value);
    })
    .attr("stroke",forceProperties.charge.strength > 0 ? "blue" : "red")
    .attr("stroke-width",forceProperties.charge.enabled == false ? 0 : Math.abs(forceProperties.charge.strength) / 15);

  link
    .attr("stroke-width",function(d) {
      return linkScale(d.value);
    })
    .attr("opacity",forceProperties.link.enabled ? 1 : 0);
}
// update the display positions after each simulation tick
function ticked() {
  node.call(updateNode);
  d3.select('#alpha_value').style('flex-basis',(simulation.alpha() * 100) + '%');
  labelSimulation.alphaTarget(0.3).restart();
  labelNode.each(function(d,i) {
    if (i % 2 == 0) {
      d.x = d.node.x;
      d.y = d.node.y;
    } else {
      var b = this.getBBox();

      var diffX = d.x - d.node.x;
      var diffY = d.y - d.node.y;

      var dist = Math.sqrt(diffX * diffX + diffY * diffY);

      var shiftX = b.width * (diffX - dist) / (dist * 2);
      shiftX = Math.max(-b.width,Math.min(0,shiftX));
      var shiftY = 5;
      this.setAttribute("transform","translate(" + shiftX + "," + shiftY + ")");
    }
  });
  link.call(updateLink);
  labelNode.call(updateNode);
}

function fixna(x) {
  if (isFinite(x)) return x;
  return 0;
}

function updateLink(link) {
  link.attr("x1",function(d) {
      return fixna(d.source.x);
    })
    .attr("y1",function(d) {
      return fixna(d.source.y);
    })
    .attr("x2",function(d) {
      return fixna(d.target.x);
    })
    .attr("y2",function(d) {
      return fixna(d.target.y);
    });
}

function updateNode(node) {
  node.attr("transform",function(d) {
    return "translate(" + fixna(d.x) + "," + fixna(d.y) + ")";
  });
}

// convenience function to update everything (run after UI input)
var updateAll = function() {
  updateForces();
  updateDisplay();
}

d3.selectAll("input").on("input",function() {
  updateAll();
});

带有强制选项的JSFiddle:https://jsfiddle.net/gothmogg/wh7px5oy/

有没有一种方法可以防止标签与其他标签以及任何节点重叠?

欢迎任何输入。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 <select id="xxx"> SELECT di.id, di.name, di.work_type, di.updated... <where> <if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 <property name="dynamic.classpath" value="tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams['font.sans-serif'] = ['SimHei'] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -> systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping("/hires") public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate<String
使用vite构建项目报错 C:\Users\ychen\work>npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-