The legend is simply an SVG rect element shaded via a linear gradient. The portion that controls the size and placement of that rect looks like so:
svg.append("rect")
.attr("width", legendWidth)
.attr("height", legendHeight)
.style("fill", "url(#gradient)")
.attr("transform", `translate(${cx - legendWidth / 2}, ${cy + radius + 30})`);
Note that no cx or cy attributes are specified so they both default to 0, which is near the center of the figure due to the way the viewBox is specified when the SVG is setup:
svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-cx, -cy, width, height])
Note, for example, that cx is half the width. Now, let’s take a closer look at the transform attribute of the rect:
`translate(${cx - legendWidth / 2}, ${cy + radius + 30})`
That’s going to shift the rect far out of the picture. Both the x and the y components are too big; the y component is far too big.
You might set both the x and y components to zero so that it’s at the center of the page and then experiment with fractions of the width and height. I came up with
.attr("transform", `translate(${0.5*width - legendWidth},
${0.5*height - 100 - legendHeight})`)
which, I suppose, makes some sense. The 100 is in there because cy is set to be 0.59*height, which is a little odd.
Whatever you choose, you’ll need to do the same thing with the legendAxis.