I’m trying to understand Tristan Wietsma’s Brush & Zoom example and change it to draw circles rather than areas. I have created a version below with my attempt. The circles plot correct initially, but the brush / zoom have no effect on the main (Focus) plot.
I think its because I need to replace the area object with a circle object and then refer to these in the brushed and zoomed functions, but I can’t see how to create a circle object in a similar way to the area object.
Also, if anyone can explain how the brushed and zoomed functions work, that would be really helpful.
<!doctype html>
<!-- External JS libraries -->
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.slim.min.js" integrity="sha384-J6qa4849blE2+poT4WnyKhv5vZF5SrPo0iEjwBvKU7imGFAV0wwj1yYfoRSJoZ+n" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js" integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
<!-- Custom JS -->
<script>
var svg = d3.select("svg")
// Create 2 charts:
// - Focus: the main chart
// - Context: a slider that allows a narrower part of the main chart to be focussed on
const MARGIN = { LEFT: 40, RIGHT: 20, TOP: 20, BOTTOM: 110 }
const WIDTH = +svg.attr("width") - MARGIN.LEFT - MARGIN.RIGHT
const HEIGHT = 500 - MARGIN.TOP - MARGIN.BOTTOM
const MARGIN2 = { LEFT: 40, RIGHT: 20, TOP: 430, BOTTOM: 30 }
const HEIGHT2 = 500 - MARGIN2.TOP - MARGIN2.BOTTOM
// Scales
const x = d3.scaleLinear()
.range([0, WIDTH])
.domain(["0","99"])
const y = d3.scaleLinear()
.range([HEIGHT, 0])
.domain([0, 30])
const x2 = d3.scaleLinear()
.range([0, WIDTH])
.domain(["0","99"])
const y2 = d3.scaleLinear()
.range([HEIGHT2, 0])
.domain([0, 30])
// Define Axes
var xAxis = d3.axisBottom(x),
xAxis2 = d3.axisBottom(x2),
yAxis = d3.axisLeft(y);
var brush = d3.brushX()
.extent([[0, 0], [WIDTH, HEIGHT2]])
.on("brush end", brushed);
var zoom = d3.zoom()
.scaleExtent([1, Infinity])
.translateExtent([[0, 0], [WIDTH, HEIGHT]])
.extent([[0, 0], [WIDTH, HEIGHT]])
.on("zoom", zoomed);
// Original area objects - no longer needed as using circles
// var area = d3.area()
// .curve(d3.curveMonotoneX)
// .x(function(d) { return x(d.date); })
// .y0(height)
// .y1(function(d) { return y(d.price); });
// var area2 = d3.area()
// .curve(d3.curveMonotoneX)
// .x(function(d) { return x2(d.date); })
// .y0(height2)
// .y1(function(d) { return y2(d.price); });
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", WIDTH)
.attr("height", HEIGHT);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + MARGIN.LEFT + "," + MARGIN.TOP + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + MARGIN2.LEFT + "," + MARGIN2.TOP + ")");
// Toptip
var div = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0);
var data=[
{"xValue":10,"yValue":10,"count":58},
{"xValue":97,"yValue":25,"count":99},
{"xValue":23,"yValue":5,"count":5},
{"xValue":49,"yValue":17,"count":29},
{"xValue":67,"yValue":25,"count":35}
];
plotChart(data)
function plotChart(data) {
console.log(data)
const circleArea = d3.scaleLinear()
.range([10*Math.PI, 750*Math.PI])
.domain([d3.min(data, d => d.count), d3.max(data, d => d.count)])
const circleArea2 = d3.scaleLinear()
.range([1*Math.PI, 25*Math.PI])
.domain([d3.min(data, d => d.count), d3.max(data, d => d.count)])
// Add circles to main (Focus) Plot
var circles = focus.selectAll("circle")
.data(data)
// ENTER new elements present in new data.
circles.enter().append("circle")
.attr("fill-opacity","0.9")
.attr("cy", d => y(d.yValue))
.attr("cx", d => x(d.xValue))
.attr("r", d => Math.sqrt(circleArea(d.count) / Math.PI))
.attr("fill", "Red")
// Add axes
focus.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + HEIGHT + ")")
.call(xAxis);
focus.append("g")
.attr("class", "axis axis--y")
.call(yAxis);
// Add circles to slider (Context) Plot
var circles2 = context.selectAll("circle")
.data(data)
// ENTER new elements present in new data.
circles2.enter().append("circle")
.attr("fill-opacity","0.9")
.attr("cy", d => y2(d.yValue))
.attr("cx", d => x2(d.xValue))
.attr("r", d => Math.sqrt(circleArea2(d.count) / Math.PI))
.attr("fill", d => "Red")
// Add axes
context.append("g")
.attr("class", "axis axis--x")
.attr("transform", "translate(0," + HEIGHT2 + ")")
.call(xAxis2);
context.append("g")
.attr("class", "brush")
.call(brush)
.call(brush.move, x.range());
svg.append("rect")
.attr("class", "zoom")
.attr("width", WIDTH)
.attr("height", HEIGHT)
.attr("transform", "translate(" + MARGIN.LEFT + "," + MARGIN.TOP + ")")
.call(zoom);
}
//
function brushed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "zoom") return; // ignore brush-by-zoom
var s = d3.event.selection || x2.range();
x.domain(s.map(x2.invert, x2));
//focus.select(".area").attr("d", area); - Need to replace this, version below not working
focus.select(".circle").attr("cx", d => x(d.xValue));
focus.select(".axis--x").call(xAxis);
svg.select(".zoom").call(zoom.transform, d3.zoomIdentity
.scale(WIDTH / (s[1] - s[0]))
.translate(-s[0], 0));
}
//
function zoomed() {
if (d3.event.sourceEvent && d3.event.sourceEvent.type === "brush") return; // ignore zoom-by-brush
var t = d3.event.transform;
x.domain(t.rescaleX(x2).domain());
//focus.select(".area").attr("d", area); - Need to replace this, version below not working
focus.select(".circle").attr("cx", d => x(d.xValue));
focus.select(".axis--x").call(xAxis);
context.select(".brush").call(brush.move, x.range().map(t.invertX, t));
}
</script>