简体   繁体   中英

Is there an "axis equal" in D3?

I am using D3 and Python to create some plots. With Python I am able to do axis equal, as shown in image 1:

在 Python 中使用 Axis Equal

I want to achieve the same in an SVG using D3. Please refer to the image 2, without axis equal:

Javascript d3中没有轴等于

The axes scale which I am using are as follows:

var xScale = d3.scaleLinear()
    .range([0, width])
    .domain([minX, maxX]);

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

var xAxis = d3.axisBottom(xScale).ticks(5),
    yAxis = d3.axisLeft(yScale).ticks(5);

Could anyone tell me how can I achieve the axis equal with D3?

Short answer : no, there is no "axis equal" in D3. D3 is quite low level, it's just a collection of methods, so you have to do almost everything by hand ...

The good news is that all you need for solving your problem is some math to calculate the new ranges.

As an introduction, since this is a tagged question, "axis equal" is a term used in some programs like Matlab , which...

Use the same length for the data units along each axis.

It can be better explained visually. Have a look at this image, with different domains and ranges ( source ):

在此处输入图片说明

After this brief introduction, let's go back to your problem. Suppose this simple code, generating two axes:

 const minX = 0, minY = 0, maxX = 120, maxY = 50, width = 500, height = 300, paddings = [10, 10, 20, 30]; const xScale = d3.scaleLinear() .range([paddings[3], width - paddings[1]]) .domain([minX, maxX]); const yScale = d3.scaleLinear() .range([height - paddings[2], paddings[0]]) .domain([minY, maxY]); const svg = d3.select("svg") .attr("width", width) .attr("height", height); svg.append("g") .attr("transform", `translate(0,${height - paddings[2]})`) .call(d3.axisBottom(xScale).tickSizeInner(-height + paddings[2] + paddings[0])); svg.append("g") .attr("transform", `translate(${paddings[3]},0)`) .call(d3.axisLeft(yScale).tickValues(xScale.ticks()).tickSizeInner(-width + paddings[3] + paddings[1]));
 svg { border: 2px solid tan; } line { stroke-dasharray: 2, 2; stroke: gray; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <svg></svg>

As we can clearly see, the units are not equally spaced. We can do two things for creating axes with equally spaced units: 1. Change the domains; 2. Change the ranges.

According to your comment , changing the domains is not an option. So, let's change the ranges instead.

All we need is calculating the number of units per user space, and using the biggest among them (x or y) to change the range of the opposite scale.

For instance, given the scales of the snippet above:

const xStep = Math.abs((xScale.domain()[1] - xScale.domain()[0]) / (xScale.range()[1] - xScale.range()[0]));

const yStep = Math.abs((yScale.domain()[1] - yScale.domain()[0]) / (yScale.range()[1] - yScale.range()[0]));

xStep is bigger than yStep , showing us that the x axis has more units per pixels. Therefore, we'll change the y axis range:

yScale.range([yScale.range()[0], yScale.range()[0] - (yScale.domain()[1] - yScale.domain()[0]) / xStep]);

And here is the demo:

 const minX = 0, minY = 0, maxX = 120, maxY = 50, width = 500, height = 300, paddings = [10, 10, 20, 30]; const xScale = d3.scaleLinear() .range([paddings[3], width - paddings[1]]) .domain([minX, maxX]); const yScale = d3.scaleLinear() .range([height - paddings[2], paddings[0]]) .domain([minY, maxY]); const xStep = Math.abs((xScale.domain()[1] - xScale.domain()[0]) / (xScale.range()[1] - xScale.range()[0])); const yStep = Math.abs((yScale.domain()[1] - yScale.domain()[0]) / (yScale.range()[1] - yScale.range()[0])); yScale.range([yScale.range()[0], yScale.range()[0] - (yScale.domain()[1] - yScale.domain()[0]) / xStep]); const svg = d3.select("svg") .attr("width", width) .attr("height", height); svg.append("g") .attr("transform", `translate(0,${height - paddings[2]})`) .call(d3.axisBottom(xScale).tickSizeInner(-height + paddings[2] + yScale.range()[1])); svg.append("g") .attr("transform", `translate(${paddings[3]},0)`) .call(d3.axisLeft(yScale).tickValues(yScale.ticks().filter(function(d){return !(+d%10)})).tickSizeInner(-width + paddings[3] + paddings[1]));
 svg { border: 2px solid tan; } line { stroke-dasharray: 2, 2; stroke: gray; }
 <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <svg></svg>

As you can see using the gridlines, the units are equally spaced now.

Finally, have in mind that determining programmatically what scale will be changed, readjusting the correct range (your y scale, for instance, goes from the bottom to the top), centralising the axes etc are whole different issues.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM