mcli_
mcli_

Reputation: 21

Linear scale inconsistency between x/y and width/height

I am using d3 to make a bar graph, and I am running into an issue where the x values in a rect element seem to behave differently than the width values, same applies to y and height values. The strange thing is that I am passing the same values to the each counterpart.

I am testing out this issue and getting strange results, shown below with an image. Note that the yScale is being inverted when setting the range. My expectation is that the rectangle's four corners should touch the four circles, but that is not the case. Any reason for this behavior?

Image:

Image of results

My code:

const xMax = dataset.length-1;
const yMax = d3.max(dataset, (d)=> d[1]);

const xScale = d3.scaleLinear()
                 .domain([0, xMax])
                 .range([padding, width-padding]);
const yScale = d3.scaleLinear()
                 .domain([0, yMax])
                 .range([height-padding,padding]);


svg.append('circle')
  .attr('cx', xScale(0))
  .attr('cy', yScale(0))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(xMax))
  .attr('cy', yScale(0))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(xMax))
  .attr('cy', yScale(yMax))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(0))
  .attr('cy', yScale(yMax))
  .attr('r', 10);
svg.append('rect')
  .style('fill', 'none')
  .style('stroke', 'black')
  .style('stroke-width', 2)
  .attr('x', xScale(0))
  .attr('y', yScale(yMax))
  .attr('width', xScale(xMax))
  .attr('height', yScale(yMax));

Upvotes: 1

Views: 60

Answers (1)

Gerardo Furtado
Gerardo Furtado

Reputation: 102218

The reason why the rectangle doesn't go to the four points (the four circles) in your SVG is this: the rectangle ends in a position which is its width plus its x (initial) position. The same goes for the vertical scale.

Right now, you have this:

const svg = d3.select("svg");
const xMax = 280,
  yMax = 130,
  padding = 20,
  width = 300,
  height = 150;

const xScale = d3.scaleLinear()
  .domain([0, xMax])
  .range([padding, width - padding]);
const yScale = d3.scaleLinear()
  .domain([0, yMax])
  .range([height - padding, padding]);

svg.append('circle')
  .attr('cx', xScale(0))
  .attr('cy', yScale(0))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(xMax))
  .attr('cy', yScale(0))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(xMax))
  .attr('cy', yScale(yMax))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(0))
  .attr('cy', yScale(yMax))
  .attr('r', 10);
svg.append('rect')
  .style('fill', 'none')
  .style('stroke', 'black')
  .style('stroke-width', 2)
  .attr('x', xScale(0))
  .attr('y', yScale(yMax))
  .attr('width', xScale(xMax))
  .attr('height', yScale(yMax));
svg {
  background-color: wheat;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

So, why doesn't the rectangle end at the circle on the right-hand side, if that circle is at xScale(xMax)? Because you have to subtract the rectangle's x position from the width, in order to have this:

rectangle's x + rectangle's width = circle's centre

And, as previously stated, the same goes for the rectangle's height.

That being said, this is the working demo:

const svg = d3.select("svg");
const xMax = 280,
  yMax = 130,
  padding = 20,
  width = 300,
  height = 150;

const xScale = d3.scaleLinear()
  .domain([0, xMax])
  .range([padding, width - padding]);
const yScale = d3.scaleLinear()
  .domain([0, yMax])
  .range([height - padding, padding]);

svg.append('circle')
  .attr('cx', xScale(0))
  .attr('cy', yScale(0))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(xMax))
  .attr('cy', yScale(0))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(xMax))
  .attr('cy', yScale(yMax))
  .attr('r', 10);
svg.append('circle')
  .attr('cx', xScale(0))
  .attr('cy', yScale(yMax))
  .attr('r', 10);
svg.append('rect')
  .style('fill', 'none')
  .style('stroke', 'black')
  .style('stroke-width', 2)
  .attr('x', xScale(0))
  .attr('y', yScale(yMax))
  .attr('width', xScale(xMax) - xScale(0))
  .attr('height', yScale(0) - yScale(yMax));
svg {
  background-color: wheat;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg></svg>

Upvotes: 1

Related Questions