How to remove the top and bottom ticks of the axes

I am trying to figure out how can I customise an axis as per my req.

For example, the following code (an example notebook)

  const tbl = [{ "Month": 1, "Value": 14841 }, { "Month": 2, "Value": 24467 }, { "Month": 3, "Value": 78423 }, { "Month": 4, "Value": 60213 }, { "Month": 5, "Value": 87257 }, { "Month": 6, "Value": 21543 }, { "Month": 7, "Value": 21373 }, { "Month": 8, "Value": 87363 }, { "Month": 9, "Value": 50378 }, { "Month": 10, "Value": 29714 }, { "Month": 11, "Value": 20171 }, { "Month": 12, "Value": 70059 }]

//the chart X Axis needs to start from 0
tbl.filter(a => (a.Month == 0)).length == 0 ? tbl.unshift({ "Month": 0, "Value": 0 }) : tbl


const width = 1280;
const height = 720;

//*-------2. DRAW CHART STRUCTURE (SVG HEIGHT,WIDTH, VBOX ETC)----------*//      
const glblDim = {
    height: height,
    width: width,
    margin: {
        top: 20,
        right: 20,
        bottom: 30,
        left: 50
    }
}
glblDim.boundedWidth = glblDim.width - glblDim.margin.right - glblDim.margin.left;
glblDim.boundedHeight = glblDim.height - glblDim.margin.top - glblDim.margin.bottom;

//namespace
const svgns = 'http://www.w3.org/2000/svg'
const body = document.querySelector('body')
const svg = document.createElementNS(svgns, 'svg');
svg.setAttribute('xmlns', svgns);
svg.setAttribute('viewBox', `0 0 ${width} ${height}`)
body.append(svg);


const bound = document.createElementNS(svgns, 'g');
bound.setAttribute('class', 'bound');
bound.style.setProperty('transform', `translate(${glblDim.margin.left}px, ${glblDim.margin.top}px`)
svg.appendChild(bound);

//*--------------------------3. SCALE----------------------------------*//  
//generate range X
const rangeX = d3.scaleTime().range([0, glblDim.boundedWidth]);

//generate scale first X 
var scaleX = rangeX
    .domain(d3.extent(tbl, d => d.Month)) 

//generate rangeY
const rangeY = d3.scaleLinear().range([glblDim.boundedHeight, 0]);

//generate scale first Y
var scaleY = rangeY
    .domain(d3.extent(tbl, d => d.Value)) 

//-----------------4.AXES----------------------------------------------//     
//generate y Axis
d3.select(bound).append('g')
    .call(d3.axisLeft(scaleY))
    .attr('class', 'YAxis')
    .call(a => a.selectAll(".tick")
        .remove())

produces this


The markup is following

but what I really want is the following
image

which I can achieve by manually editing the path markup as following

Is there any way within d3 to get rid of those top and bottom lines of the axis path while generating the axis?

It looks to me like you’ve studied some material that describes the use of D3 in the browser context and are trying to apply those techniques within Observable notebooks. The two environments are really quite different, though, and the techniques you use at the document level are much more cumbersome than necessary. Observable is definitely an outstanding choice for the types of charts you want to generate but, if you’re interested in using it, I recommend that you read the documentation thoroughly.

To create an SVG, you might do something like the following:

{
  let svg = d3.create('svg')
    .attr(...)
  ...
  return svg.node()
}

No need create namespaces or append object to body or any other manipulation at the document level.

More specific to the task at hand, you might do something like the following:

{
  let w = 500;
  let h = 300;
  let pad = 30;
  let svg = d3.create("svg").attr("viewBox", `0 0 ${w} ${h}`);
  let axes_group = svg.append("g");

  let x_scale = d3.scaleLinear()
    .domain([0, 5]).range([pad, w - pad]);
  let y_scale = d3.scaleLinear()
    .domain([0, 3]).range([h - pad, pad]);

  svg.append("g")
    .attr("transform", `translate(0, ${h - pad})`)
    .call(d3.axisBottom(x_scale));
  svg.append("g")
    .attr("transform", `translate(${pad})`)
    .call(d3.axisLeft(y_scale).tickValues([]).tickSize(0));

  return svg.node();
}

Note that I used axis.tickSize to remove the short segments you didn’t want. The result looks like so:

Thanks @mcmcclur for the response.

You are right, that I am trying to figure out both d3 (first priority) and observable at the same time. However, my work will be consumed via a browser. Therefore, I only use Observable notebook to present an example notebook here for my question. I guess it will take me a while to seamlessly use Observable.

Coming back to the question. I tried your solution and it looks like tickValues([]).tickSize(0) will get rid of all the ticks and their corresponding values, which I don’t want.

To be able to solve my problem, I wrote a javascript like this, but I am not sure if that is foolproof. The code gets the d attribute of the xAxis and yAxis path and then I use a regex to manipulate that to my desire outcome.

I will totally avoid that if there is an in-built d3 way to do this.

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Document</title>
</head>

<script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script>

<body>   
  
<script>
const tbl = [{ "Month": 1, "Value": 14841 }, { "Month": 2, "Value": 24467 }, { "Month": 3, "Value": 78423 }, { "Month": 4, "Value": 60213 }, { "Month": 5, "Value": 87257 }, { "Month": 6, "Value": 21543 }, { "Month": 7, "Value": 21373 }, { "Month": 8, "Value": 87363 }, { "Month": 9, "Value": 50378 }, { "Month": 10, "Value": 29714 }, { "Month": 11, "Value": 20171 }, { "Month": 12, "Value": 70059 }]

//the chart X Axis needs to start from 0
tbl.filter(a => (a.Month == 0)).length == 0 ? tbl.unshift({ "Month": 0, "Value": 0 }) : tbl


const width = 1280;
const height = 720;

//*-------2. DRAW CHART STRUCTURE (SVG HEIGHT,WIDTH, VBOX ETC)----------*//      
const glblDim = {
    height: height,
    width: width,
    margin: {
        top: 20,
        right: 20,
        bottom: 30,
        left: 50
    }
}
glblDim.boundedWidth = glblDim.width - glblDim.margin.right - glblDim.margin.left;
glblDim.boundedHeight = glblDim.height - glblDim.margin.top - glblDim.margin.bottom;

//namespace
const svgns = 'http://www.w3.org/2000/svg'

const svg = d3.select('body').append('svg').attr('xmlns', svgns)
    .attr("viewBox", `0 0 ${width} ${height}`);
const bound = svg.append('g').attr('class', 'bound') //this is the inner portion where the drawing gets made
    .style('transform', `translate(${glblDim.margin.left}px, ${glblDim.margin.top}px)`)

//*--------------------------3. SCALE----------------------------------*//  
//generate range X
const rangeX = d3.scaleLinear().range([0, glblDim.boundedWidth]);

//generate scale first X 
var scaleX = rangeX
    .domain(d3.extent(tbl, d => d.Month))

//generate rangeY
const rangeY = d3.scaleLinear().range([glblDim.boundedHeight, 0]);

//generate scale first Y
var scaleY = rangeY
    .domain(d3.extent(tbl, d => d.Value))

//-----------------4.AXIS----------------------------------------------//     
//generate Y Axis
bound.append('g')
    .call(d3.axisLeft(scaleY))
    .attr('class', 'YAxis')
    .call(d => d.select('.domain') //change Axis class name
        .attr('class', 'yAxis'))
    .call(d => d.selectAll('.tick') //change the tick class name
        .attr('class', (d, i) => { return `yAxisTick${[i]}` }))
    .call(d => d.select('.yAxisTick0') //chnage the opacity of the first tick to 0
        .attr('opacity', 0))

//format Y Axis Path to get rid rid of bottom and top axis ticks
const getYAxis = d3.select('.yAxis')
const dStringY = getYAxis.attr('d').replace(/^(M-\d+|M\d+)/gm, 'M0').replace(/(H-\d+|H\d+)$/gm, '')
getYAxis.attr('d', dStringY);

//generate X Axis
bound.append('g')
    .call(d3.axisBottom(scaleX))
    .attr('class', 'XAxis')
    .style('transform', `translateY(${glblDim.boundedHeight}px)`)
    .call(d => d.select('.domain') //change Axis class name
        .attr('class', 'xAxis'))
    .call(d => d.selectAll('.tick') //change the tick class name
        .attr('class', (d, i) => { return `xAxisTick${[i]}` }))
    .call(d => d.select('.xAxisTick0') //chnage the opacity of the first tick to 0
        .attr('opacity', 0))
    // .call(a => a.selectAll("[class^='xAxisTick']")
    //     .remove())

//format X Axis Path to get rid rid of bottom and top axis ticks
const getXAxis = d3.select('.xAxis')
const dStringX = getXAxis.attr('d').replace(/,\d+V/gm, ',0').replace(/(V-\d+|V\d+)$/gm, '')
getXAxis.attr('d', dStringX);
</script>

</body>

</html>

generates this (which I want)


which was initially this without the path manipulation (which I don’t want)

Thank you in advance.

This would be the tickSizeOuter setting.

d3.axisBottom(x).tickSizeOuter(0)

@Fil I tried what you suggested but it does not give me what I need
image

bound.append('g')
    .call(d3.axisBottom(scaleX).tickSizeOuter(0))
    .attr('class', 'XAxis')
    .style('transform', `translateY(${glblDim.boundedHeight}px)`)

Then you should call .tickSizeOuter:

{
  let w = 500;
  let h = 300;
  let pad = 30;
  let svg = d3.create("svg").attr("viewBox", `0 0 ${w} ${h}`);
  let axes_group = svg.append("g");

  let x_scale = d3.scaleLinear()
    .domain([0, 5]).range([pad, w - pad]);
  let y_scale = d3.scaleLinear()
    .domain([0, 3.141]).range([h - pad, pad]);

  svg
    .append("g")
    .attr("transform", `translate(0, ${h - pad})`)
    .call(d3.axisBottom(x_scale));
  svg
    .append("g")
    .attr("transform", `translate(${pad})`)
    .call(d3.axisLeft(y_scale).tickSizeOuter(0));

  return svg.node();
}

Here’s your chart, with the ticks fixed using .tickSizeOuter written in a somewhat more idiomatic way:

I would also mention that this style is largely perfectly appropriate for working within the context of the brower. Once you have the webpage setup with D3 included as a script and a DIV with an id, simply change

d3.create('svg')

to

d3.select('#div_id').append('svg')

Of course, the return statement is no longer necessary but that’s it.

1 Like