Why d3 is creating an extra rect?

I am trying to create rect elements along the xAxis values.

I am aiming to end up with this

instead, I am getting end up with this

The full code is here. I also created a notebook here to help others help me. I have no idea at all, why the svg is not getting rendered.

<!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>
</body>
<script type="text/javascript">
	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 and Y Axis needs to start from 0
tbl.filter(a => (a.Month == 0)).length == 0 ? tbl.unshift({ "Month": 0, "Value": 0 }) : tbl

const monthCount = tbl.length - 1;

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'
let 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))

// //*--------------------------3.COLOR  SCALE----------------------------------*//  
const interpolatorSequentialMultiHueName = [
    'd3.interpolateTurbo', 'd3.interpolateViridis', 'd3.interpolateInferno'    
]
  const xColorScale = d3.scaleSequential().domain(d3.extent(tbl, d => d.Month))   
    .interpolator(eval(`${interpolatorSequentialMultiHueName[1]}`))
  
//-----------------4.AXES----------------------------------------------//     
//generate Y Axis
bound.append('g')
    .call(d3.axisLeft(scaleY).tickSizeOuter(0)) //removes the outer tick of the axis
    .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') //change the opacity of the first tick to 0
        .attr('opacity', 0))


//generate X Axis
bound.append('g')
    .call(d3.axisBottom(scaleX).tickSizeOuter(0) /*.tickSizeInner(-(glblDim.boundedHeight))*/ ) //removes the outer tick of the axis
    .attr('class', 'XAxis')
    //.style('stroke-width', '0.5')
    .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("text")
        .attr("fill", d => { return xColorScale(d) })
        .style("font-size", '30px')
    )
    .call(a => a.append('g')
        .attr('class', 'bRect')
        .selectAll('rect') //inherits class="XAxis" properties        
        .data(d3.selectAll("[class^=xAxisTick]:not(:last-child)"))
        .enter()
        .append('rect')
        .attr('x', '0')
        .attr('y', '0')
        .attr('height', `${glblDim.boundedHeight}`)
        .attr('width', `${scaleX(1)}`)
        .attr('class', (d, i) => { return `bgRect${i}` })
        .style('fill', (d, i) => { return xColorScale(i + 1) })
        .style('stroke', 'black')
        .style('stroke-opacity', '0.1')
        .attr('transform', (d, i) => {
            const attributeX = d.getAttribute('transform').match(/(\d+\.\d+)(?=\,)|(\d+)(?=\,)/gm)
            const attributeY = glblDim.boundedHeight * -1;
            return `translate(${attributeX} ${attributeY})`
        })
    )
    .call(a => a.selectAll("text")
        .attr("fill", (d, i) => { return xColorScale(i) })
        .style("font-size", '30px')
    )
	
	</script>
</html>

Coming back to the code, if I get rid of those two highlighted lines, I get the desired numbers of rect, But I want to create a g element and have the desired numbers of rect within that g element appended too.

Thank you in advance.

the line you have highlighted appends a g element in the axis (at the end, which makes it the last child of that axis). As a consequence the :not(:last-child) in the line below has no effect, because none of the g[class^=xAxisTick] elements are the last child.

d3.selectAll("[class^=xAxisTick]:not(:last-child)")

1 Like

Thanks @Fil is there a way around it? I wanted to create g and desired number of rect at the same time.

Do you suggest that I create desired number of rect first and then later append in a group like

var group = svg.appendChild( document.createElementNS(svgns, ‘g’))
document.querySelectorAll(’[class^=bgRect]’).forEach( elSVG => group.appendChild(elSVG))

Sure you could read (via d3.selectAll) before you create the new g. And store the results in a temporary variable.

@Fil I tried what you suggested but, it is still not generating the desired outcome

.call(a => a.selectAll('rect') //inherits class="XAxis" properties        
        .data(d3.selectAll("[class^=xAxisTick]:not(:last-child)"))
        .enter()
        .append('rect')
        .attr('x', '0')
        .attr('y', '0')
        .attr('height', `${glblDim.boundedHeight}`)
        .attr('width', `${scaleX(1)}`)
        .attr('class', (d, i) => {
            return `bgRect${i}`
        })
        .style('fill', (d, i) => {
            return xColorScale(i + 1)
        })
        .style('stroke', 'black')
        .style('stroke-opacity', '0.1')
        .attr('transform', (d, i) => {
            const attributeX = d.getAttribute('transform').match(/(\d+\.\d+)(?=\,)|(\d+)(?=\,)/gm)
            const attributeY = glblDim.boundedHeight * -1;
            return `translate(${attributeX} ${attributeY})`
        })
    )
    .call(
        b => {

            const container = b.append('g').attr('class', 'bgRect');

        }
    )
    .call(b => {
        const container = d3.select('.bgRect')
        const container2 = d3.selectAll("[class^=bgRect]").nodes();
        const len = container2.length;
        for (let i = 0; i < container2.length; i++) {
            return d3.select('.bgRect').append(() =>
                container2[i]
            )
        }
    })

I am not sure what am I doing wrong here
The full code is below

<!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>
</body>
<script type="text/javascript">
	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 and Y Axis needs to start from 0
tbl.filter(a => (a.Month == 0)).length == 0 ? tbl.unshift({ "Month": 0, "Value": 0 }) : tbl

const monthCount = tbl.length - 1;

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'
let 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))

// //*--------------------------3.COLOR  SCALE----------------------------------*//  
const interpolatorSequentialMultiHueName = [
    'd3.interpolateTurbo', 'd3.interpolateViridis', 'd3.interpolateInferno'    
]
  const xColorScale = d3.scaleSequential().domain(d3.extent(tbl, d => d.Month))   
    .interpolator(eval(`${interpolatorSequentialMultiHueName[1]}`))
  
//-----------------4.AXES----------------------------------------------//     
//generate Y Axis
bound.append('g')
    .call(d3.axisLeft(scaleY).tickSizeOuter(0)) //removes the outer tick of the axis
    .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') //change the opacity of the first tick to 0
        .attr('opacity', 0))


//generate X Axis
bound.append('g')
    .call(d3.axisBottom(scaleX).tickSizeOuter(0) /*.tickSizeInner(-(glblDim.boundedHeight))*/ ) //removes the outer tick of the axis
    .attr('class', 'XAxis')
    //.style('stroke-width', '0.5')
    .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("text")
        .attr("fill", d => {
            return xColorScale(d)
        })
        .style("font-size", '30px')
    )
    .call(a => a.selectAll('rect') //inherits class="XAxis" properties        
        .data(d3.selectAll("[class^=xAxisTick]:not(:last-child)"))
        .enter()
        .append('rect')
        .attr('x', '0')
        .attr('y', '0')
        .attr('height', `${glblDim.boundedHeight}`)
        .attr('width', `${scaleX(1)}`)
        .attr('class', (d, i) => {
            return `bgRect${i}`
        })
        .style('fill', (d, i) => {
            return xColorScale(i + 1)
        })
        .style('stroke', 'black')
        .style('stroke-opacity', '0.1')
        .attr('transform', (d, i) => {
            const attributeX = d.getAttribute('transform').match(/(\d+\.\d+)(?=\,)|(\d+)(?=\,)/gm)
            const attributeY = glblDim.boundedHeight * -1;
            return `translate(${attributeX} ${attributeY})`
        })
    )
    .call(
        b => {

            const container = b.append('g').attr('class', 'bgRect');

        }
    )
    .call(b => {
        const container = d3.select('.bgRect')
        const container2 = d3.selectAll("[class^=bgRect]").nodes();
        const len = container2.length;
        for (let i = 0; i < container2.length; i++) {
            return d3.select('.bgRect').append(() =>
                container2[i]
            )
        }
    })
    .call(a => a.selectAll("text")
        .attr("fill", (d, i) => {
            return xColorScale(i)
        })
        .style("font-size", '30px')
    )
	</script>
</html>

I think you are doing it the hard way with taking the transform data from a selectALL.

data only needs to be an array representing the months

.data(d3.range(0,12)) //d3.selectAll("[class^=xAxisTick]:not(:last-child)"))

And the transform gap is just the scaleX(1) * the month that’s represented.

     .attr('transform', (d, i) => {
            const attributeX = d*scaleX(1)  
            const attributeY = glblDim.boundedHeight * -1;
            return `translate(${attributeX} ${attributeY})`
          })

The selector used was making something like 28 rects.

1 Like