🏠 back to Observable

Axis do not show

HI All,
I have nearly given up learning d3.js in Observable as I cannot seem to make any progress beyond plotting simple points and bars. I cannot seem to plot axes let alone do anything more (very frustrating!)

Here is the notebook I tried to work on while learning about d3.


I cannot seem to ‘view’ the axes.
What am I doing wrongly?

Here is the code:

svg_scatter = d3
  .attr('cx', d => {
    return d.disp;
  .attr('cy', d => {
    return d.mpg;
  .attr('r', 10)
  .attr('fill', 'black')

x_scale and y_scale are as follows:

x_scale = d3
  .domain([60, 400])
  .range([0, w]) 

y scale

y_scale = d3
  .domain([10, 30])
  .range([h, 0])
data = d3.csv(

(data2 is where I have converted the character vectors in the mtcars data to floating point numbers for mpg, cyl, and disp variables)

w, h, margin are width, height, and margins where
w = 600
h = 400
margin = 50

I’d greatly appreciate some insights.
Arindam Basu

Welcome Arindam!

A good way to debug d3 code is to check the output SVG with your browser’s inspector:

In this case, I see a few things wrong. First, you have a nested svg element, which means that the 3rd line of svg_scatter is unnecessary. Next, I see that each circle element contains a g element with lots of child g's with path elements inside. Those g correspond to your axes, but they’re being appended to every circle, whereas you want them to be appended to your top level svg instead.

Here’s a version of the cell with a few fixes (though it’s still far from ideal; see the comments below):

svg_scatter = d3
  .select(cont5); // we need to reuse this selection so we name it
//   .append('svg') // this is unnecessary since `cont5` is already an `svg`

svg_scatter.selectAll('circle') // here begins the `circle` selection
  .attr('cx', d => {
    return d.disp;
  .attr('cy', d => {
    return d.mpg;
  .attr('r', 10)
  .attr('fill', 'black'); // the `circle` selection ends here

svg_scatter // this appends the x-axis

svg_scatter // this appends the y-axis

Note that the axes will end fully or partially outside the svg since you haven’t set up proper margins. See the examples linked at the bottom of this post for an example of how you can take care of that.

In ordinary JS code each of these statements would stand alone. However, in Observable it’s usually best to combine statements which affect the same svg into one cell (there are some exceptions related to interactivity / interaction between svg’s, but that’s more advanced). This is to ensure that the code runs in the correct order.

As you have things structured now, if you happen to rerun some of the cells that contain code making up your chart, the data points and axes may get appended multiple times to the same SVG. (You can also work around this by making use of the “update” and “exit” selections in addition to the “enter” selection that you’re using).

See this example:

for some skeleton code for a chart with axes.

Here’s a more complete example of a simple chart with data and axes:

And there are plenty more you can find by searching.

Hope this helps!


Good reply, @bgchen!

@arinbasu, Here’s a suggestion you can merge with the minimal set of changes I recommend:


  1. Pin the major version of D3.
  2. Avoid side-effects by combining cells where appropriate.
  3. Use selection.join instead of selection.enter + selection.append.
  4. Append your axes to a new group (G) element, not each circle.
  5. Translate your axes into the desired position, or set overflow: visible.

Thank you @bgchen and @mike for your excellent guidance and solving the issue. I learned a lot reading both your solutions and reading the d3 documents you linked.

1 Like