0

My SVG just will not show up in my dom when I change it to try and use a viewbox.

I am just realistically just changing this:

            .attr("width", this.width + this.margin.left + this.margin.right)
            .attr("height", this.height + this.margin.top + this.margin.bottom)

to this:

        .attr("viewBox", `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`)
        .attr("preserveAspectRatio", "xMidYMid meet")

Here is my html:

<div #chartContainer [id]="uniqueId"  style="width: 100%; height: 100%;"></div>

Here is my typescript:

import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation, AfterViewInit, ViewChild, ElementRef, HostListener } from '@angular/core';
import * as d3 from 'd3';

@Component({
    selector: 'mky-barchart-double-sided',
    templateUrl: './barchart-double-sided.component.html',
    styleUrls: ['./barchart-double-sided.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
    encapsulation: ViewEncapsulation.None
})
export class BarChartDoubleSidedComponent implements OnInit, AfterViewInit {

    @Input() data: any;
    @Input() uniqueId: string;
    @Input() userName: string;

    elementArray: Array<any>;

    margin = { top: 40, right: 30, bottom: 20, left: 70 };
    width = 800 - this.margin.left - this.margin.right;
    height = 400 - this.margin.top - this.margin.bottom;

    @ViewChild('chartContainer', { static: true }) chartContainer: ElementRef;
    
    constructor() { }

    ngOnInit() {

        this.elementArray = [
            { element: 'Electric', value: this.data.electric.value, type: this.data.electric.value < 0 ? 'dark' : 'light', labelValue: this.data.electric.value, color: '#f39c11' },
            { element: 'Fire', value: this.data.fire.value, type: this.data.fire.value < 0 ? 'dark' : 'light', labelValue: this.data.fire.value, color: '#ed1c24' },
            { element: 'Leaf', value: this.data.leaf.value, type: this.data.leaf.value < 0 ? 'dark' : 'light', labelValue: this.data.leaf.value, color: '#006838' },
            { element: 'Metal', value: this.data.metal.value, type: this.data.metal.value < 0 ? 'dark' : 'light', labelValue: this.data.metal.value, color: '#71797E' },
            { element: 'Rock', value: this.data.rock.value, type: this.data.rock.value < 0 ? 'dark' : 'light', labelValue: this.data.rock.value, color: '#a97c50' },
            { element: 'Water', value: this.data.water.value, type: this.data.water.value < 0 ? 'dark' : 'light', labelValue: this.data.water.value, color: '#3598db' },
            { element: 'Wind', value: this.data.wind.value, type: this.data.wind.value < 0 ? 'dark' : 'light', labelValue: this.data.wind.value, color: '#18bb9c' }
        ];
    }

    ngAfterViewInit() {
        this.resizeChart();
        this.createChart();
    }

    @HostListener('window:resize')
    onResize() {
        this.resizeChart();
        d3.select(`#${this.uniqueId} svg`).remove(); // Remove old chart
        this.createChart(); // Create new chart
    }

    resizeChart() {
        const element = this.chartContainer.nativeElement;
        this.width = element.offsetWidth - this.margin.left - this.margin.right;
        this.height = element.offsetHeight - this.margin.top - this.margin.bottom;
    }


    createChart() {
    // Assuming the container for the chart is the entire window for simplicity.
    // You might want to base these on the size of a specific container element instead.
    this.width = window.innerWidth - this.margin.left - this.margin.right;
    this.height = window.innerHeight - this.margin.top - this.margin.bottom;

    const svg = d3.select("#" + this.uniqueId)
        .append("svg")
        // Remove fixed width and height, use viewBox for responsiveness
        .attr("viewBox", `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`)
        .attr("preserveAspectRatio", "xMidYMid meet") // Adjust this value based on your needs
        .append("g")
        .attr("transform", `translate(${this.margin.left},${this.margin.top})`);

            const x = d3.scaleBand()
            .domain(this.elementArray.map(d => d.element))
            .range([0, this.width])
            .padding(0.2);

        const y = d3.scaleLinear()
            .domain([-100, 100])
            .range([this.height, 0]);

        svg.append("g")
            .attr("transform", `translate(0,${this.height / 2})`)
            .call(d3.axisBottom(x).tickFormat(() => ''));

        svg.append("g")
            .call(d3.axisLeft(y).ticks(10).tickSize(-this.width));

        // Add dark and light labels to the y-axis with white boxes and text
        svg.append("rect")
            .attr("x", -60)
            .attr("y", y(-50) - 10)
            .attr("width", 40)
            .attr("height", 20)
            .attr("fill", "transparent")
            .attr("stroke", "white");

        svg.append("text")
            .attr("x", -40)
            .attr("y", y(-50))
            .attr("dy", ".35em")
            .style("text-anchor", "middle")
            .style("font-size", "12px")
            .style("fill", "white")
            .text("Dark");

        svg.append("rect")
            .attr("x", -60)
            .attr("y", y(50) - 10)
            .attr("width", 40)
            .attr("height", 20)
            .attr("fill", "transparent")
            .attr("stroke", "white");

        svg.append("text")
            .attr("x", -40)
            .attr("y", y(50))
            .attr("dy", ".35em")
            .style("text-anchor", "middle")
            .style("font-size", "12px")
            .style("fill", "white")
            .text("Light");

        // Add title with a white box and text
        // svg.append("rect")
        //     .attr("x", this.width / 2 - 80)
        //     .attr("y", -this.margin.top / 2 - 10)
        //     .attr("width", 150)
        //     .attr("height", 30)
        //     .attr("fill", "transparent")
        //     .attr("stroke", "white");

        svg.append("text")
            .attr("x", this.width / 2)
            .attr("y", -this.margin.top / 2 + 5)
            .attr("dy", ".35em")
            .style("text-anchor", "middle")
            .style("font-size", "16px")
            .style("font-weight", "bold")
            .style("fill", "white")
            .text(this.userName + " Moral Elements");

        svg.selectAll(".bar")
            .data(this.elementArray)
            .enter()
            .append("rect")
            .attr("class", "bar")
            .attr("x", d => x(d.element))
            .attr("y", d => d.value >= 0 ? y(d.value) : y(0))
            .attr("width", x.bandwidth())
            .attr("height", d => Math.abs(y(d.value) - y(0)))
            .attr("fill", d => d.type === 'light' ? d.color : '#100123')
            .attr("stroke", d => d.type === 'light' ? 'white' : d.color)
            .attr("stroke-width", 1);

        // Add the x-axis labels as boxes with text
        const xAxisGroup = svg.append("g")
            .attr("class", "x axis")
            .attr("transform", `translate(0, ${this.height / 2})`);

        xAxisGroup.selectAll(".tick")
            .data(this.elementArray)
            .enter()
            .append("g")
            .attr("class", "tick")
            .attr("transform", d => `translate(${x(d.element) + x.bandwidth() / 2}, 0)`)
            .each(function(d) {
                const tick = d3.select(this);
                tick.append('rect')
                    .attr('x', -20)
                    .attr('y', -10)
                    .attr('width', 40)
                    .attr('height', 20)
                    .attr('fill', d.type === 'light' ? d.color : '#100123')
                    .attr('stroke', d.type === 'light' ? "#FFF" : d.color)

                tick.append('text')
                    .attr('x', 0)
                    .attr('y', 0)
                    .attr('dy', '.35em')
                    .attr('text-anchor', 'middle')
                    .attr('fill', 'white')
                    .style('font-size', '10px')
                    .text(d.element);
            });
    }
}

The SVG appears in the DOM, with the viewbox, it just doesn't appear to be visible. It basically is completely in the DOM, just not viewable. Again when I remove the viewbox and just set the height and width like before, it works fine, but doesn't scale obviously to smaller sizes well.

Any optimizations or suggestions are very much appreciated!

2
  • 1
    I don't understand why you are using viewBox if you are redrawing your chart on a resize.... Here's your code slightly modified where the viewBox scales the chart without an explicit redraw. Commented Jun 24, 2024 at 14:56
  • Thank you Mark, I was able to figure out the right answer by using your demo much appreciated! Commented Jun 25, 2024 at 4:02

1 Answer 1

0

I was able to answer my own question because of the comment user Mark left and his demo. Much appreciated Mark.

import { ChangeDetectionStrategy, Component, Input, OnInit, ViewEncapsulation, AfterViewInit, ViewChild, ElementRef, HostListener } from '@angular/core';
import * as d3 from 'd3';

@Component({
    selector: 'mky-barchart-double-sided',
    templateUrl: './barchart-double-sided.component.html',
    styleUrls: ['./barchart-double-sided.component.scss'],
    changeDetection: ChangeDetectionStrategy.OnPush,
    encapsulation: ViewEncapsulation.None
})
export class BarChartDoubleSidedComponent implements OnInit, AfterViewInit {

    @Input() data: any;
    @Input() uniqueId: string;
    @Input() userName: string;

    elementArray: Array<any>;

    margin = { top: 40, right: 20, bottom: 20, left: 80 };
    width = 800 - this.margin.left - this.margin.right;
    height = 400 - this.margin.top - this.margin.bottom;
    mobile:boolean = false;

    @ViewChild('chartContainer', { static: true }) chartContainer: ElementRef;
    
    constructor() { }

    ngOnInit() {

        this.elementArray = [
            { element: 'Electric', value: this.data.electric.value, type: this.data.electric.value < 0 ? 'dark' : 'light', labelValue: this.data.electric.value, color: '#f39c11' },
            { element: 'Fire', value: this.data.fire.value, type: this.data.fire.value < 0 ? 'dark' : 'light', labelValue: this.data.fire.value, color: '#ed1c24' },
            { element: 'Leaf', value: this.data.leaf.value, type: this.data.leaf.value < 0 ? 'dark' : 'light', labelValue: this.data.leaf.value, color: '#006838' },
            { element: 'Metal', value: this.data.metal.value, type: this.data.metal.value < 0 ? 'dark' : 'light', labelValue: this.data.metal.value, color: '#71797E' },
            { element: 'Rock', value: this.data.rock.value, type: this.data.rock.value < 0 ? 'dark' : 'light', labelValue: this.data.rock.value, color: '#a97c50' },
            { element: 'Water', value: this.data.water.value, type: this.data.water.value < 0 ? 'dark' : 'light', labelValue: this.data.water.value, color: '#3598db' },
            { element: 'Wind', value: this.data.wind.value, type: this.data.wind.value < 0 ? 'dark' : 'light', labelValue: this.data.wind.value, color: '#18bb9c' }
        ];
    }

    ngAfterViewInit() {
        this.resizeChart();
        this.createChart();
    }

    @HostListener('window:resize')
    onResize() {
        this.resizeChart();
        d3.select(`#${this.uniqueId} svg`).remove(); // Remove old chart
        this.createChart(); // Create new chart
    }

    resizeChart() {
        const element = this.chartContainer.nativeElement;
        this.width = element.offsetWidth - this.margin.left - this.margin.right;
        this.height = element.offsetHeight - this.margin.top - this.margin.bottom;
        if(this.width > 590) {
            this.mobile = false;
            this.margin.left = 80;
        }else {
            this.mobile = true;
            this.margin.left = 30;
        }
    }


    createChart() {
    // Assuming the container for the chart is the entire window for simplicity.
    // You might want to base these on the size of a specific container element instead.
    this.width = (window.innerWidth - this.margin.left - this.margin.right) > 700 ? 700 : window.innerWidth - this.margin.left - this.margin.right;
    this.height = window.innerHeight - this.margin.top - this.margin.bottom;

    const svg = d3.select("#" + this.uniqueId)
        .append("svg")
        .attr("height", "100%")
        .attr("width", "100%")
        // Remove fixed width and height, use viewBox for responsiveness
        .attr("viewBox", `0 0 ${this.width + this.margin.left + this.margin.right} ${this.height + this.margin.top + this.margin.bottom}`)
        .attr("preserveAspectRatio", "xMidYMid meet") // Adjust this value 
        .append("g")
        .attr("transform", `translate(${this.margin.left},${this.margin.top})`);

            const x = d3.scaleBand()
            .domain(this.elementArray.map(d => d.element))
            .range([0, this.width])
            .padding(0.2);

        const y = d3.scaleLinear()
            .domain([-100, 100])
            .range([this.height, 0]);

        svg.append("g")
            .attr("transform", `translate(0,${this.height / 2})`)
            .call(d3.axisBottom(x).tickFormat(() => ''));

        svg.append("g")
            .call(d3.axisLeft(y).ticks(10).tickSize(-this.width));

        if(this.mobile == false){
            // Add dark and light labels to the y-axis with white boxes and text
            svg.append("rect")
                .attr("x", -70)
                .attr("y", y(-50) - 15)
                .attr("width", 65)
                .attr("height", 40)
                .attr("fill", "#000")
                .attr("stroke", "white");

            svg.append("text")
                .attr("x", -37)
                .attr("y", y(-52))
                .attr("dy", ".35em")
                .style("text-anchor", "middle")
                .style("font-size", "20px")
                .style("fill", "white")
                .text("Dark");

            svg.append("rect")
                .attr("x", -70)
                .attr("y", y(50) - 15)
                .attr("width", 65)
                .attr("height", 40)
                .attr("fill", "#000")
                .attr("stroke", "white");

            svg.append("text")
                .attr("x", -37)
                .attr("y", y(48))
                .attr("dy", ".35em")
                .style("text-anchor", "middle")
                .style("font-size", "20px")
                .style("fill", "white")
                .text("Light");
        } else if(this.mobile == true) {
        // Add dark and light labels to the y-axis with white boxes and text
        // svg.append("rect")
        //     .attr("x", -60)
        //     .attr("y", y(-50) - 10)
        //     .attr("width", 40)
        //     .attr("height", 20)
        //     .attr("fill", "transparent")
        //     .attr("stroke", "white");

        // svg.append("text")
        //     .attr("x", -40)
        //     .attr("y", y(-50))
        //     .attr("dy", ".35em")
        //     .style("text-anchor", "middle")
        //     .style("font-size", "12px")
        //     .style("fill", "white")
        //     .text("Dark");

        // svg.append("rect")
        //     .attr("x", -60)
        //     .attr("y", y(50) - 10)
        //     .attr("width", 40)
        //     .attr("height", 20)
        //     .attr("fill", "transparent")
        //     .attr("stroke", "white");

        // svg.append("text")
        //     .attr("x", -40)
        //     .attr("y", y(50))
        //     .attr("dy", ".35em")
        //     .style("text-anchor", "middle")
        //     .style("font-size", "12px")
        //     .style("fill", "white")
        //     .text("Light");
        }

        // Add title with a white box and text
        // svg.append("rect")
        //     .attr("x", this.width / 2 - 80)
        //     .attr("y", -this.margin.top / 2 - 10)
        //     .attr("width", 150)
        //     .attr("height", 30)
        //     .attr("fill", "transparent")
        //     .attr("stroke", "white");

        svg.append("text")
            .attr("x", this.width / 2)
            .attr("y", -this.margin.top / 2 + 5)
            .attr("dy", ".35em")
            .style("text-anchor", "middle")
            .style("font-size", "24px")
            .style("font-weight", "bold")
            .style("fill", "white")
            .text(this.userName + " Moral Elements");

        svg.selectAll(".bar")
            .data(this.elementArray)
            .enter()
            .append("rect")
            .attr("class", "bar")
            .attr("x", d => x(d.element))
            .attr("y", d => d.value >= 0 ? y(d.value) : y(0))
            .attr("width", x.bandwidth())
            .attr("height", d => Math.abs(y(d.value) - y(0)))
            .attr("fill", d => d.type === 'light' ? d.color : '#100123')
            .attr("stroke", d => d.type === 'light' ? 'white' : d.color)
            .attr("stroke-width", 1);

        // Add the x-axis labels as boxes with text
        const xAxisGroup = svg.append("g")
            .attr("class", "x axis")
            .attr("transform", `translate(0, ${this.height / 2})`);

            if(this.mobile == false){
                xAxisGroup.selectAll(".tick")
                .data(this.elementArray)
                .enter()
                .append("g")
                .attr("class", "tick")
                .attr("transform", d => `translate(${x(d.element) + x.bandwidth() / 2}, 0)`)
                .each(function(d) {
                    const tick = d3.select(this);
    
                    tick.append('rect')
                        .attr('x', -40)
                        .attr('y', -20)
                        .attr('width', 80)
                        .attr('height', 40)
                        .attr('fill', d.type === 'light' ? d.color : '#100123')
                        .attr('stroke', d.type === 'light' ? "#FFF" : d.color)
    
                    tick.append('text')
                        .attr('x', 0)
                        .attr('y', 0)
                        .attr('dy', '.35em')
                        .attr('text-anchor', 'middle')
                        .attr('fill', 'white')
                        .style('font-size', '20px')
                        .text(d.element);
                });
            } else if (this.mobile == true) {
                xAxisGroup.selectAll(".tick")
                .data(this.elementArray)
                .enter()
                .append("g")
                .attr("class", "tick")
                .attr("transform", d => `translate(${x(d.element) + x.bandwidth() / 2}, 0)`)
                .each(function(d) {
                    const tick = d3.select(this);
    
                    tick.append('rect')
                        .attr('x', -20)
                        .attr('y', -10)
                        .attr('width', 40)
                        .attr('height', 20)
                        .attr('fill', d.type === 'light' ? d.color : '#100123')
                        .attr('stroke', d.type === 'light' ? "#FFF" : d.color)
    
                    tick.append('text')
                        .attr('x', 0)
                        .attr('y', 0)
                        .attr('dy', '.35em')
                        .attr('text-anchor', 'middle')
                        .attr('fill', 'white')
                        .style('font-size', '10px')
                        .text(d.element);
                });
            }

    }
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.