【问题标题】:d3 - sine wave with circles, the problem is that circles are overlapped in curvesd3 - 带圆圈的正弦波,问题是圆圈在曲线中重叠
【发布时间】:2019-10-01 20:11:36
【问题描述】:

我有大约 75 个数据点。每个点都表示成一个圆圈,并与正弦波对齐。正常的正弦波是水平的,但我希望它是垂直的。第一个点从宽度的一半开始。

问题是曲线中的圆圈重叠。我试图在高度、半径等方面进行一些小的编辑,但无法完全避免重叠。

我还没有开发一个函数来计算圆的位置,但位置设置如下:

    .attr("cx", function(d,i){ return (Math.sin(i/7)) * width/2.3 + width /2})
    .attr("cy", function(d,i){ return (i + 0.5) * (height-margin.bottom-margin.top) / len(data); })

svg 的高度和宽度设置为 1,000。如果方法可以避免重叠,它们可以更长,圆的半径可以更小。但我相信会有一种更好、更优雅的方式来将圆圈安装在相同的高度和相同的半径内。例如,波浪的宽度可以更小,以便更多的波浪可以适应相同的高度,然后圆圈可以更加分散。但老实说,我不知道如何计算这个。

如果您能帮助我,将不胜感激。

【问题讨论】:

  • 如果你能创建一个codepen,解决问题会容易得多。

标签: javascript css d3.js


【解决方案1】:

为了减少正弦曲线极端的重叠,我不会直接使用正弦计算来定位圆。相反,如果您绘制正弦曲线,然后使用该路径以固定间隔定位圆,则重叠会减少。根据路径的长度和圆的半径,可能还有一些。

例如看嵌入代码sn-p。

sn-p 使用生成的数据绘制正弦路径(调整范围和频率以绘制具有不同长度和曲线数量的曲线)。

曲线画好了,用节点路径的长度来确定每个圆的x和y:

let height = 800
    let width =  height
    let margin = 50
    
    let sineData = d3.range(0, 101).map(function(k) {
      let freq = 0.05
      var value = [freq * k * Math.PI, Math.sin(freq * k * Math.PI)];
      return value;
    });
    
    let sineX = d3.scaleLinear()
    	.domain([-1, 1])
    	.range([0, width])
		
    let sineY = d3.scaleLinear()
    	.domain(d3.extent(sineData, function(d) {
   			 return d[0]
      }))
    	.range([0, height])
      
   	let line = d3.line()
      .x(function(d) {
        return sineX(d[1]);
      })
      .y(function(d) {
        return sineY(d[0]);
      })
    	.curve(d3.curveLinear)
        
    var svg = d3.select("body").append("svg")
      .attr("width", width + margin + margin)
      .attr("height", height + margin + margin)

    var g = svg.append('g')
    	.attr('transform', 'translate(' + margin + ',' + margin + ')')
    
    var sinePath = g.append('path')
    	.datum(sineData)
    	.attr('d', line)
      .style('stroke', 'black')
      .style('stroke-width', 1)
      .style('fill', 'none')
    
    let n = 150
    let circleData = d3.range(0, n).map(function(k) { return k })
    let node = sinePath.node()
  
    let scaleLength = d3.scaleLinear()
      .domain([0, n-1])
      .range([0, node.getTotalLength()])
    
    var circleX = function(i){
      return node.getPointAtLength(scaleLength(i)).x
    }

    var circleY = function(i){
      return node.getPointAtLength(scaleLength(i)).y
    }
    
    g.selectAll('circle')
    	.data(circleData)
    	.enter()
    	.append('circle')
    	.style('fill', 'grey')
    	.style('opacity', 0.5)
    	.attr('r', 10)
      .attr("cx", function(d, i){ return circleX(i) })
      .attr("cy", function(d, i){ return circleY(i) })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

【讨论】:

    【解决方案2】:

    另一种可以避免重叠但速度较慢的解决方案是递归调整圆的半径并绘制一组圆,以查看适合正弦曲线的数字是否等于您想要的数字(例如 75 )。

    这段代码 sn-p 以半径 (10) 开始,在正弦路径上绘制尽可能多的圆,如果不是 75,则调整半径并重试。

    let height = 800
        let width =  height
        let margin = 50
        
        let noOfCircles = 75
        
        let sineData = d3.range(0, 101).map(function(k) {
          let freq = 0.05
          var value = [freq * k * Math.PI, Math.sin(freq * k * Math.PI)];
          return value;
        });
        
        let sineX = d3.scaleLinear()
        	.domain([-1, 1])
        	.range([0, width])
    		
        let sineY = d3.scaleLinear()
        	.domain(d3.extent(sineData, function(d) {
       			 return d[0]
          }))
        	.range([0, height])
          
       	let line = d3.line()
          .x(function(d) {
            return sineX(d[1]);
          })
          .y(function(d) {
            return sineY(d[0]);
          })
        	.curve(d3.curveLinear)
            
        var svg = d3.select("body").append("svg")
          .attr("width", width + margin + margin)
          .attr("height", height + margin + margin)
    
        var g = svg.append('g')
        	.attr('transform', 'translate(' + margin + ',' + margin + ')')
        
        var sinePath = g.append('path')
        	.datum(sineData)
        	.attr('d', line)
          .style('stroke', 'black')
          .style('stroke-width', 1)
          .style('fill', 'none')
        
    
        let node = sinePath.node()
        let pathLength = Math.floor(node.getTotalLength())
    
        
        var circleX = function(i){
          return node.getPointAtLength(i).x
        }
    
        var circleY = function(i){
          return node.getPointAtLength(i).y
        }
    
    	let direction = "TBC"
    	let magnitude = 20
      
        
        createCircles(10)
        
        function createCircles(radius){
          
          g.selectAll('circle').remove()
          
          let circleID = 0
        	let prevCircleX = circleX(0)
        	let prevCircleY = circleY(0)
        
          appendCircle(prevCircleX, prevCircleY, radius, circleID)
    
          for (var l = 1; l < pathLength; l++) {
    
            let thisCircleX = circleX(l)
            let thisCircleY = circleY(l)
    
            let sideX = Math.abs(thisCircleX - prevCircleX)
            let sideY = Math.abs(thisCircleY - prevCircleY)
    
            let hyp = Math.sqrt((sideX * sideX) + (sideY * sideY))
    
            if (hyp > (radius * 2)) {
    
                circleID = circleID + 1
                prevCircleX = thisCircleX
                prevCircleY = thisCircleY
                appendCircle(prevCircleX, prevCircleY, radius, circleID)
    
            }
            
        	} 
    
    			console.log(circleID) 
          
          if (circleID != 75) {
       
            let newDirection = circleID < 75 ? "down" : "up"
            magnitude = direction == newDirection ? magnitude : magnitude/10
            direction = newDirection 
            radius = circleID < 75 ? radius - magnitude  : radius + magnitude 
            circleID = 0
            createCircles(radius)
         }
          
        }                      
       
    
        
        function appendCircle(x, y, r, id) {
          g.append('circle')
                .attr('id', 'circle-' + id)
          			.attr('r', r)
                .attr("cx", x )
                .attr("cy", y )
          			.style('fill', 'grey')
        				.style('opacity', 0.5)
        }
    &lt;script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"&gt;&lt;/script&gt;

    【讨论】:

    • 非常感谢。这正是我想要的!我想接受他们两个作为答案,但只有一个被接受。相反,我对两者都投了赞成票。非常感谢!
    猜你喜欢
    • 1970-01-01
    • 2012-07-05
    • 1970-01-01
    • 2021-03-21
    • 2012-08-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多