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.
    – Mark
    Commented Jun 24 at 14:56
  • Thank you Mark, I was able to figure out the right answer by using your demo much appreciated!
    – nuccio
    Commented Jun 25 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);
                });
            }

    }
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.