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!
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.