Nomadic cattle rustler and inventor of the electric lasso.

Company Website**Follow me on twitter**

Contact me for frontend answers.

January 16, 2016

Up until now when I have been dealing with d3.js’s axes components, I have always kept the axes positive, i.e. both the x and y axes where showing values greater than 0.

I have been hacking around with this fun side project that takes the input of an algebraic expression and plots a graph for a sample range of values for x. You can checkout the source code here if you are interested.

Below is a screenshot of the end result:

It quickly became apparent that in order to show the curve of the expression properly, I would need to construct negative and positive x and y axes.

I have the following function below that constructs the data of x and y coordinates to plot the curve against which uses the excellent mathjs library to transform the string algebraic expression into a javascript function (line 2). I then create a sample range for `x`

values ranging from `-10`

to `11`

and evaluate the `y`

coordinate for each item in the range by applying the function on line 5 to each item:

```
getDataFromProps(expr) {
const expression = math.parse(expr);
const fn = (x) => {
return expression.eval({x: x});
};
return d3.range(-10, 11).map( (d) => {
return {x:d, y:fn(d)};
});
}
```

Armed with this data, I can now construct my axes.

I first create the axis against a scale that is in proportion with the viewport dimensions.

```
const dimensions = this.getDimensions();
xScale = d3.scale.linear()
.range([0, dimensions.width]);
yScale = d3.scale.linear()
.range([dimensions.height, 0]);
const xAxis = d3.svg.axis()
.scale(xScale);
const yAxis = d3.svg.axis()
.orient('left')
.scale(yScale);
```

The `domain`

function of `d3`

allows you to specify a minimum and maximum value as the range of values that we can use for a particular axis.

Below I am using d3’s `extent`

function that returns the minum and maximum values of an array and is equivalent to calling `d3.min`

and `d3.max`

simultaneously.

`this.xScale.domain(d3.extent(data, function (d) {return d.x;}));`

As I am supplying the values for the range of `x`

values in the code above, I know that I will always have negative `x`

values and positive `x`

values.

The y coordinates are different depending on the function generated from the algebraic expression. Depending on the expression, there are basically 3 conditions I want to capture when displaying a curve.

The first case is when there are only positive y values:

The next case is when there are both negative and positive y values;

Lastly, only negative y values:

With this in mind, the code below creates a domain based on the minimum values of y and the maximum values of y:

```
const minY = d3.min(data, (d) => { return d.y; });
const maxY = d3.max(data, (d) => { return d.y; });
const nonNegativeAxis = minY >= 0 && maxY >= 0;
const positiveAndNegativeAxis = minY < 0 && maxY > 0;
let yScaleDomain, xAxisPosition;
if(nonNegativeAxis) {
yScaleDomain = [0, d3.max(data, (d) => {return d.y;})];
} else {
yScaleDomain = d3.extent(data, (d) => {return d.y;});
}
this.yScale.domain(yScaleDomain);
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + dimensions.width/2 + ',0)')
.call(yAxis);
```

I either start my domain at `0`

or use `d3.extent`

again to get the maximum and minimum values for y like I did before for x.

The last problem to solve was to position the x axis. In the 3 code samples below, I am capturing the domain for y and the x axis position for each condition.

This is easy if I only have negative or positive values for `y`

. I can simply place the `x`

axis at the bottom for only positive values:

```
yScaleDomain = [0, d3.max(data, (d) => {return d.y;})];
xAxisPosition = dimensions.height;
```

When I have only negative values, then I can place the x axis at the top of the document:

```
yScaleDomain = d3.extent(data, (d) => {return d.y;});
xAxisPosition = 0;
```

The interesting case was when I have both positive and negative values for `y`

.

What I ended up doing was selecting all the ticks or labels from the y axis and finding the label that had `0`

against it and from that I could use d3 to to select its position and then use that for my x axis position.

Below is the code that does that:

```
xAxisPosition = this.svg.selectAll(".tick").filter((data) => {
return data === 0;
}).map((tick) => {
return d3.transform(d3.select(tick[0]).attr('transform')).translate[1];
});
```

In the code above, I filter out all the other ticks apart from the `0`

label. The zero tick is then passed into the `map`

function which selects the transform attribute of the tick which might look something like this `translate(0,280)`

. The second value of translate, `280`

in this instance gives me the position of the `0`

label in the y axis. I can use this value to position my x axis.

Once I have the position of 0 in the y axis, I can position the axis to the document:

```
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(0,' + xAxisPosition + ')')
.call(xAxis);
```

When it comes to positioning the y axis, I simply divide the width by 2 and position it there:

```
this.svg.append('g')
.attr('class', 'axis')
.attr('transform', 'translate(' + dimensions.width/2 + ',0)')
.call(yAxis);
```

Nomadic cattle rustler and inventor of the electric lasso.

Company Website**Follow me on twitter**

Contact me for frontend answers.