JSFiddle - React, Tailwind, and code Playground

HTML

<!DOCTYPE html>
<meta charset="utf-8">
<svg width="960" height="500"></svg>
<div id="gui"></div>
<input id="scale" value="1" readonly/>
<script src="https://d3js.org/d3.v5.min.js"></script>

CSS

.zoom-in,
.zoom-out {
  font-size: 20px;
  margin: 10px;
}

#gui {
  position: absolute;
  top: 100px;
  left: 20px;
  cursor: pointer;
}

#scale {
	position: absolute;
  top: 150px;
  left: 20px;
}

JavaScript

var svg = d3.select("svg"),
  width = +svg.attr("width"),
  height = +svg.attr("height");

var randomX = d3.randomNormal(width / 2, 80),
  randomY = d3.randomNormal(height / 2, 80),
  data = d3.range(200).map(function() {
    return [randomX(), randomY()];
  });

var scale;

var circle;


var _zoom = d3.zoom()
  .scaleExtent([1, 8])
  .on("zoom", zoom);
	

circle = svg.selectAll("circle")
	.data(data)
	.enter().append("rect")
	.attr("width", 5)
  .attr('height', 5)
	//.attr("transform", transform(d3.zoomIdentity));


svg.append("rect")
  .attr("fill", "none")
  .attr("pointer-events", "all")
  .attr("width", width)
  .attr("height", height)
  .call(_zoom);
		
function zoom() {
	circle.attr("transform", transform(d3.event.transform));
	scale = d3.event.transform.k;
	console.log(scale);
	document.getElementById('scale').value=scale;
}


// semantic zoom

function semanticZoom() {
  circle.attr("transform", transform(d3.event.transform));
}

function transform(t) {
	 return function(d) {
   console.log(t, d);
    return "translate(" + t.apply(d) + ")";
	}
}



var gui = d3.select("#gui");
gui.append("span")
  .classed("zoom-in", true)
  .text("+")
  .on("click", function() {
    _zoom.scaleBy(circle, 1.2);
  });
gui.append("span")
  .classed("zoom-out", true)
  .text("-")
  .on("click", function() {
    _zoom.scaleBy(circle, 0.8);
  });