score:4

Setting the end ticks and specifying the precise space of the ticks in a linear scale is a pain in the neck. The reason is that D3 axis generator was created in such a way that the ticks are automatically generated and spaced. So, what is handy for someone who doesn't care too much for customisation can be a nuisance for those that want a precise customisation.

My solution here is a hack: create two scales, one linear scale that you'll use to plot your data, and a second scale, that you'll use only to make the axis and whose values you can set at your will. Here, I choose a `scalePoint()` for the ordinal scale.

Something like this:

``````var realScale = d3.scaleLinear()
.range([10,width-10])
.domain([-2*Math.PI, 2*Math.PI]);

var axisScale = d3.scalePoint()
.range([10,width-10])
.domain(["-2 \u03c0", "-1.5 \u03c0", "-\u03c0", "-0.5 \u03c0", "0",
"0.5 \u03c0", "\u03c0", "1.5 \u03c0", "2 \u03c0"]);
``````

Don't mind the `\u03c0`, that's just π (pi) in Unicode.

Check this demo, hover over the circles to see their positions:

``````var width = 500,
height = 150;
var data = [-2, -1, 0, 0.5, 1.5];

var realScale = d3.scaleLinear()
.range([10, width - 10])
.domain([-2 * Math.PI, 2 * Math.PI]);

var axisScale = d3.scalePoint()
.range([10, width - 10])
.domain(["-2 \u03c0", "-1.5 \u03c0", "-\u03c0", "-0.5 \u03c0", "0", "0.5 \u03c0", "\u03c0", "1.5 \u03c0", "2 \u03c0"]);

var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);

var circles = svg.selectAll("circle").data(data)
.enter()
.append("circle")
.attr("r", 8)
.attr("fill", "teal")
.attr("cy", 50)
.attr("cx", function(d) {
return realScale(d * Math.PI)
})
.append("title")
.text(function(d) {
return "this circle is at " + d + " \u03c0"
});

var axis = d3.axisBottom(axisScale);

var gX = svg.append("g")
.attr("transform", "translate(0,100)")
.call(axis);``````
``<script src="https://d3js.org/d3.v4.min.js"></script>``

score:0

I was able to implement an x axis in units of PI/2, under program control (not manually laid out), by targetting the D3 `tickValues` and `tickFormat` methods. The call to `tickValues` sets the ticks at intervals of PI/2. The call to `tickFormat` generates appropriate tick labels. You can view the complete code on GitHub: https://github.com/quantbo/sine_cosine

score:0

My solution is to customise tickValues and tickFormat. Only 1 scale is needed, and delegate d3.ticks function to give me the new tickValues that are proportional to Math.PI.

``````const piChar = String.fromCharCode(960);
const tickFormat = val => {
const piVal = val / Math.PI;
return piVal + piChar;
};

const convertSIToTrig = siDomain => {
const trigMin = siDomain / Math.PI;
const trigMax = siDomain / Math.PI;
return d3.ticks(trigMin, trigMax, 10).map(v => v * Math.PI);
};

const xScale = d3.scaleLinear().domain([-Math.PI * 2, Math.PI * 2]).range([0, 600]);

const xAxis = d3.axisBottom(xScale)
.tickValues(convertSIToTrig(xScale.domain()))
.tickFormat(tickFormat);
``````

This way if your xScale's domain were changed via zoom/pan, the new tickValues are nicely generated with smaller/bigger interval