Home > Software design >  D3.js - Apparent bug in line append using trig functions
D3.js - Apparent bug in line append using trig functions

Time:11-04

I am setting about to manually append tick marks to a arc visual I'm working on. What remains is placing the axis tick marks about the outer edges of the chart at every 30 degrees. I have appended the 0 degree mark (which is easy, no math involved really) then I realized I may need to use Math.sin() and Math.cosin() to calculate the right x,y coordinates for the other ticks, since they are at angles. My 30 degree tick worked fine. So with 0 tick and 30 tick both appended correctly at the right size and place, one would assume that the math and code are both fine. However, mysteriously, the 60 degree tick is way off. Notice that it's in the inside of the chart and not where it should be, at the 60 degree mark, between the top and 30 degree tick. It's almost as if it thinks I'm giving it an entirely different adjacent (to use trig terminology). Code below:

var margins = {top:100, bottom:300, left:100, right:100};

var height = 600;
var width = 600;

var totalWidth = width margins.left margins.right;
var totalHeight = height margins.top margins.bottom;

var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);

var graphGroup = svg.append('g')
.attr('transform', "translate(" margins.left "," margins.top ")");

  var data = [
{'fmc':'xx','funds':30,'top':35,'bottom':-30},
{'fmc':'xx','funds':44,'top':5,'bottom':-40},
{'fmc':'xx','funds':2,'top':20,'bottom':-10},
{'fmc':'xx','funds':22,'top':10,'bottom':-25},
{'fmc':'xx','funds':35,'top':4,'bottom':-20},

  ];

var yScale = d3.scaleLinear()
.range([height,0])
.domain([-50,50]);

var realScale = d3.scaleLinear()
.range([0,height/2])
.domain([0,50]);

var dScale = d3.scaleLinear()
.domain([-50,50])
.range([Math.PI,0]);

let arc = d3.arc()
  .innerRadius(0)
  .outerRadius(function(d) {return realScale(d.funds)})
  .startAngle(function(d) {return dScale(d.top)})
  .endAngle(function(d) {return dScale(d.bottom)});

graphGroup.append('g')
.call(d3.axisRight(yScale).ticks(5))
    .selectAll('text')
    .attr('text-anchor','end')
    .attr('transform','translate(-15,0)');

graphGroup.append('line')
  .attr('x1', 0)
  .attr('x2',width/2)
  .attr('y1', yScale(0))
  .attr('y2', yScale(0))
  .style('stroke','#000')
  .style('stroke-width',2)
  .style('stroke-dasharray','2,2');


  var arcAxis = d3.arc()
  .innerRadius(0)
  .outerRadius(width/2)
  .startAngle(0)
  .endAngle(Math.PI);

  var grid1 = d3.arc()
  .innerRadius(0)
  .outerRadius(realScale(20))
  .startAngle(0)
  .endAngle(Math.PI);

  var grid2 = d3.arc()
  .innerRadius(0)
  .outerRadius(realScale(40))
  .startAngle(0)
  .endAngle(Math.PI);



  graphGroup.append("path")
  .attr("d", grid1)
  .style('fill','none')
  .style('stroke','#a6a6a6')
  .style('stroke-width','1px')
  .style('stroke-dasharray','2,2')
  .attr("transform", "translate(0,300)");

  graphGroup.append("path")
  .attr("d", grid2)
  .style('fill','none')
  .style('stroke','#a6a6a6')
  .style('stroke-width','1px')
  .style('stroke-dasharray','2,2')
  .attr("transform", "translate(0,300)");

  graphGroup.append("path")
      .attr("d", arcAxis)
      .style('fill','none')
      .style('stroke','#000')
      .style('stroke-width','1px')
      .attr("transform", "translate(0,300)");

graphGroup.append('line')
.attr('x1',width/2)
.attr('x2',(width/2) 6)
.attr('y1',width/2)
.attr('y2',width/2)
.style('stroke','#000')
.style('stroke-width','1px');

graphGroup.append('line')
    .attr('y1', Math.sin((Math.PI / 180)*30)*300)
    .attr('y2', Math.sin((Math.PI / 180)*30)*294)
    .attr('x1', Math.cos((Math.PI / 180)*30)*300)
    .attr('x2', Math.cos((Math.PI / 180)*30)*306)
    .style('stroke','#000')
    .style('stroke-width','1px');

    graphGroup.append('line')
        .attr('y1', Math.sin((Math.PI / 180)*60)*300)
        .attr('y2', Math.sin((Math.PI / 180)*60)*294)
        .attr('x1', Math.cos((Math.PI / 180)*60)*300)
        .attr('x2', Math.cos((Math.PI / 180)*60)*306)
        .style('stroke','#000')
        .style('stroke-width','1px');


  graphGroup
.selectAll()
.data(data)
.enter()
.append("path")
    .attr('transform','translate(0,300)')
    .attr("class", "arc")
    .attr("d", arc)
    .style('fill', "#003366")
    .style('opacity',.3);
<script src="https://d3js.org/d3.v5.min.js"></script>

Question

To me this looks like a bug, if we go just a few lines of code up, we can see the result rendered correctly, so why would changing 30 to 60 result in something totally different?

CodePudding user response:

Here is a solution:

const addMark = (g, angle, fromRadius, toRadius) => {
    const angleR = angle * Math.PI / 180;
    const x1 = fromRadius * Math.sin(angleR);
    const y1 = fromRadius * -Math.cos(angleR);
    const x2 = toRadius * Math.sin(angleR);
    const y2 = toRadius * -Math.cos(angleR);
    console.log(x1, x2, y1, y2)
    g.append('line')
      .attr('y1', y1)
      .attr('y2', y2)
      .attr('x1', x1)
      .attr('x2', x2)
      .attr('transform', 'translate(0, 300)')
      .style('stroke','red');
}

var margins = {top:100, bottom:300, left:100, right:100};

var height = 600;
var width = 600;

var totalWidth = width margins.left margins.right;
var totalHeight = height margins.top margins.bottom;

var svg = d3.select('body')
.append('svg')
.attr('width', totalWidth)
.attr('height', totalHeight);

var graphGroup = svg.append('g')
.attr('transform', "translate(" margins.left "," margins.top ")");

  var data = [
{'fmc':'xx','funds':30,'top':35,'bottom':-30},
{'fmc':'xx','funds':44,'top':5,'bottom':-40},
{'fmc':'xx','funds':2,'top':20,'bottom':-10},
{'fmc':'xx','funds':22,'top':10,'bottom':-25},
{'fmc':'xx','funds':35,'top':4,'bottom':-20},

  ];

var yScale = d3.scaleLinear()
.range([height,0])
.domain([-50,50]);

var realScale = d3.scaleLinear()
.range([0,height/2])
.domain([0,50]);

var dScale = d3.scaleLinear()
.domain([-50,50])
.range([Math.PI,0]);

let arc = d3.arc()
  .innerRadius(0)
  .outerRadius(function(d) {return realScale(d.funds)})
  .startAngle(function(d) {return dScale(d.top)})
  .endAngle(function(d) {return dScale(d.bottom)});

graphGroup.append('g')
.call(d3.axisRight(yScale).ticks(5))
    .selectAll('text')
    .attr('text-anchor','end')
    .attr('transform','translate(-15,0)');

graphGroup.append('line')
  .attr('x1', 0)
  .attr('x2',width/2)
  .attr('y1', yScale(0))
  .attr('y2', yScale(0))
  .style('stroke','#000')
  .style('stroke-width',2)
  .style('stroke-dasharray','2,2');


  var arcAxis = d3.arc()
  .innerRadius(0)
  .outerRadius(width/2)
  .startAngle(0)
  .endAngle(Math.PI);

  var grid1 = d3.arc()
  .innerRadius(0)
  .outerRadius(realScale(20))
  .startAngle(0)
  .endAngle(Math.PI);

  var grid2 = d3.arc()
  .innerRadius(0)
  .outerRadius(realScale(40))
  .startAngle(0)
  .endAngle(Math.PI);



  graphGroup.append("path")
  .attr("d", grid1)
  .style('fill','none')
  .style('stroke','#a6a6a6')
  .style('stroke-width','1px')
  .style('stroke-dasharray','2,2')
  .attr("transform", "translate(0,300)");

  graphGroup.append("path")
  .attr("d", grid2)
  .style('fill','none')
  .style('stroke','#a6a6a6')
  .style('stroke-width','1px')
  .style('stroke-dasharray','2,2')
  .attr("transform", "translate(0,300)");

  graphGroup.append("path")
      .attr("d", arcAxis)
      .style('fill','none')
      .style('stroke','#000')
      .style('stroke-width','1px')
      .attr("transform", "translate(0,300)");

graphGroup.append('line')
.attr('x1',width/2)
.attr('x2',(width/2) 6)
.attr('y1',width/2)
.attr('y2',width/2)
.style('stroke','#000')
.style('stroke-width','1px');

const addMark = (g, angle, fromRadius, toRadius) => {
    const angleR = angle * Math.PI / 180;
  const x1 = fromRadius * Math.sin(angleR);
  const y1 = fromRadius * -Math.cos(angleR);
  const x2 = toRadius * Math.sin(angleR);
  const y2 = toRadius * -Math.cos(angleR);
  console.log(x1, x2, y1, y2)
  g.append('line')
    .attr('y1', y1)
    .attr('y2', y2)
    .attr('x1', x1)
    .attr('x2', x2)
    .attr('transform', 'translate(0, 300)')
    .style('stroke','red')
    .style('stroke-width', 2)
}


  graphGroup
.selectAll()
.data(data)
.enter()
.append("path")
    .attr('transform','translate(0,300)')
    .attr("class", "arc")
    .attr("d", arc)
    .style('fill', "#003366")
    .style('opacity',.3);
    
addMark(graphGroup, 15, 300, 306);        
addMark(graphGroup, 30, 300, 306);        
addMark(graphGroup, 45, 300, 306);      
addMark(graphGroup, 60, 300, 306);      
addMark(graphGroup, 75, 300, 306);      
addMark(graphGroup, 90, 300, 306);      
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

  • Related