i’m working on a data visualize project and trying to use D3js library for graphs. Everything is fine, except when I try to let my bar chart zoomable on X scale.
<div class="row" id="bar-chart-append">
<div class="container" style="margin: 20px; margin-top: 0px">
<input type="number" id="numofservice" placeholder="Number of service providers" class="col-3" step="10" min="0">
<select id="select-order" class="col-8">
<h6 class="dropdown-item-text">Order</h6>
<option value="alphabetical">Alphabetical</option>
<option value="ascending">TFT Score, ascending</option>
<option value="descending" selected>TFT Score, descending</option>
</select>
</div>
<div id="tooltip2" class="tooltip bs-tooltip-bottom"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/d3.min.js"></script>
<script src="/js/graph/graphs.js"></script>
graphs.js
:
const numOfService = document.getElementById('numofservice');
const barChartAppend = document.getElementById('bar-chart-append');
numOfService.addEventListener('change', async function() {
const value = numOfService.value;
if (!isNaN(value) && value.trim() !== '') {
data2 = await fetch(`/api/topUser?n=${value}`).then(response => response.json());
barChartAppend.removeChild(barChartSVG);
barChartSVG = barChart(data2, barChartAppend.clientWidth, barChartAppend.clientHeight);
barChartAppend.append(barChartSVG);
} else {
console.log('The value is not a number:', value);
}
});
$(document).ready(function() {
$('.selectpicker').selectpicker();
$('#select-order').on('changed.bs.select', function(e, clickedIndex, isSelected, previousValue) {
console.log('Selected value:', $(this).val());
switch ($(this).val()) {
case 'alphabetical':
barChartSVG.update((a, b) => a.owner.localeCompare(b.owner));
break;
case 'ascending':
barChartSVG.update((a, b) => a.first_combine - b.first_combine);
break;
case 'descending':
barChartSVG.update((a, b) => b.first_combine - a.first_combine);
break;
}
});
});
window.onload = async function() {
var data2 = await fetch('/api/topUser?n=10').then(response => response.json());
var barChartSVG = barChart(data2, barChartAppend.clientWidth, barChartAppend.clientHeight);
barChartAppend.append(barChartSVG);
}
function barChart(data2, parentWidth = 1200, parentHeight = 400) {
// Specify the chart’s dimensions.
const width = parentWidth;
const height = Math.min(parentHeight, width / 3);
const marginTop = 20;
const marginRight = 0;
const marginBottom = 30;
const marginLeft = 40;
const barColor = "#ff8c00";
// Declare the x (horizontal position) scale and the corresponding axis generator.
const x = d3.scaleBand()
.domain(data2.map(d => d.owner))
.range([marginLeft, width - marginRight])
.padding(0.1);
const xAxis = d3.axisBottom(x).tickSizeOuter(0);
// Declare the y (vertical position) scale.
const y = d3.scaleLinear()
.domain([0, d3.max(data2, d => d.first_combine)]).nice()
.range([height - marginBottom, marginTop]);
// Create the SVG container.
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("style", `max-width: ${width}px; height: auto; font: 10px rubik; overflow: visible;`)
.attr("width", width)
.attr("height", height);
const barsGroup = svg.append("g").attr("class", "bars");
const bar = barsGroup.selectAll("rect")
.data(data2)
.join("rect")
.attr("fill", barColor) // Default color of the bars.
.attr("x", d => x(d.owner))
.attr("y", d => y(d.first_combine))
.attr("height", d => y(0) - y(d.first_combine))
.attr("width", x.bandwidth())
.on("mouseover", function(event, d) {
d3.select('#tooltip2').html(nodeDetail(d)).style("visibility", "visible").style("opacity", 1);
d3.select(this)
.attr("fill", shadeColor(barColor, -15));
})
.on("click", function(event, d) {
window.open(`/profile?owner=${d.owner}`, '_blank');
})
.on("mousemove", function(event, d) {
d3.select('#tooltip2')
.style("top", (event.pageY - 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function() {
d3.select('#tooltip2').html(``).style("visibility", "hidden").style("opacity", 0);
d3.select(this).attr("fill", barColor);
});
// Create the axes.
const gx = svg.append("g")
.attr("class", "x-axis")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);
const gy = svg.append("g")
.attr("class", "y-axis")
.attr("transform", `translate(${marginLeft},0)`)
.call(d3.axisLeft(y).tickFormat(y => y))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 0)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text("↑ TFT Score"))
.call(g => g.select(".domain").remove());
function zoomed(event) {
console.log('Zoom event:', event);
console.log('Transform:', event.transform);
console.log('X scale:', x);
const transform = event.transform;
const newX = transform.rescaleX(x);
console.log('New X scale:', newX);
gx.call(d3.axisBottom(newX).tickSizeOuter(0));
barsGroup.selectAll("rect")
.attr("x", d => newX(d.owner))
.attr("width", newX.bandwidth());
}
svg.call(d3.zoom()
.scaleExtent([1, 8])
.translateExtent([[0, 0], [width, height]])
.extent([[0, 0], [width, height]])
.on("zoom", zoomed));
// Return the chart, with an update function that takes as input a domain
// comparator and transitions the x axis and bar positions accordingly.
return Object.assign(svg.node(), {
update(order) {
x.domain(data2.sort(order).map(d => d.owner));
const t = svg.transition()
.duration(750);
bar.data(data2, d => d.owner)
.order()
.transition(t)
.delay((d, i) => i * 20)
.attr("x", d => x(d.owner));
gx.transition(t)
.call(xAxis)
.selectAll(".tick")
.delay((d, i) => i * 20);
}
});
}
The data2
will be an array of json objects like [{"owner" : "Mei","id" : "1234","group" : ["service_provider"],"first_combine":100,"properties" : {}}]
Everytime I try to call the zoomed()
function, it throws an exception:
Uncaught TypeError: undefined is not a function
at Array.map (<anonymous>)
at rescaleX (graphs.js:476:28)
at SVGSVGElement.zoomed (graphs.js:481:22)
at Dt.call (d3.min.js:2:15713)
at M.emit (d3.min.js:2:278459)
at M.zoom (d3.min.js:2:278301)
at SVGSVGElement.T (d3.min.js:2:274295)
at SVGSVGElement.<anonymous> (d3.min.js:2:20686)
These console.log()
line will return:
Zoom event: xw {type: 'zoom', sourceEvent: WheelEvent, transform: ww, _: Dt, target: ƒ}
Transform: ww {k: 1.9042067625774186, x: -233.285344744974, y: -176.10556683706443}
X scale: ƒ i(i){let o=t.get(i);if(void 0===o){if(r!==pg)return r;t.set(i,o=n.push(i)-1)}return e[o%e.length]}
I think X scale is bugging but I don’t know how to fix that. Is there anyone can help me with that problem?