I’m created a packed circle visualisation using d3 and drawing it using Konva. If you click on a circle within the packed circle visualisation, the viewport should zoom to that circle.
Each circle has an event handler attached, which gets called on a click event. There I am calculating the value by which to scale the whole visualisation by, and the values by which to reposition the visualisation by.
I can’t quite seem to get the repositioning values correct. The circle on which the click event occurs should be centered in the viewport after scaling it.
function zoom(circle) { // By what value do we neeed to scale the group? let scaleBy = root.data.konva.radius() / circle.radius() group.scale({x: scaleBy, y: scaleBy}) // By what values do we neeed to reposition the group? let newX = (circle.x() - root.data.konva.x() ) * scaleBy let newY = (circle.y() - root.data.konva.y()) * scaleBy group.position({x: newX, y: newY}) }
const data = { children: [{ children: [{ children: [] },{ children: [] }] },{ children: [{ children: [] },{ children: [{ children: [] },{ children: [] }] }] }] } const width = 600 const height = 400 let pack = d3.pack().size([width, height]) let root = d3.hierarchy(data) .sum(d => { if(d.children && d.children.length > 0) { return d.children.length } return 1 }) pack(root) // --- const stage = new Konva.Stage({ container: 'container', width: width, height: height }) const layer = new Konva.Layer() const group = new Konva.Group() layer.add(group) stage.add(layer) // --- root.descendants().forEach( (node,i) => { const circle = new Konva.Circle({ x: node.x, y: node.y, radius: node.r, fill: 'grey', opacity: (0.1 * node.depth) + 0.1 }) node.data.konva = circle circle.data = { d3: node } group.add(circle) circle.on('click', () => zoom(circle)) }) // --- function zoom(circle) { // By what value do we neeed to scale the group? let scaleBy = root.data.konva.radius() / circle.radius() console.log(`Scaling by: ${scaleBy}`) group.scale({x: scaleBy, y: scaleBy}) // By what values do we neeed to reposition the group? let newX = (circle.x() - root.data.konva.x() ) * scaleBy let newY = (circle.y() - root.data.konva.y()) * scaleBy console.log(`Repositioning by: x:${newX}, y:${newY}`) group.position({x: newX, y: newY}) }
.konvajs-content { background: rgba(124, 7, 12, 0.1); }
<script src="https://cdn.jsdelivr.net/npm/konva@8.1.3/konva.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <div id="container"></div>
Advertisement
Answer
The task is one of moving the stage position (x, y) so that the center of the target circle is in the middle of the viewport – meaning the HTML canvas element visible bounds.
PosX = (viewPort.width / 2) – (circle.pos.x * scale) PosY = (viewPort.height/ 2) – (circle.pos.y * scale)
And using your code that means:
let newX = (width / 2) - (circle.x() * scaleBy) let newY = (height / 2) - (circle.y() * scaleBy)
See working snippet below, only those lines changed.
const data = { children: [{ children: [{ children: [] },{ children: [] }] },{ children: [{ children: [] },{ children: [{ children: [] },{ children: [] }] }] }] } const width = 600 const height = 400 let pack = d3.pack().size([width, height]) let root = d3.hierarchy(data) .sum(d => { if(d.children && d.children.length > 0) { return d.children.length } return 1 }) pack(root) // --- const stage = new Konva.Stage({ container: 'container', width: width, height: height }) const layer = new Konva.Layer() const group = new Konva.Group() layer.add(group) stage.add(layer) // --- root.descendants().forEach( (node,i) => { const circle = new Konva.Circle({ x: node.x, y: node.y, radius: node.r, fill: 'grey', opacity: (0.1 * node.depth) + 0.1 }) node.data.konva = circle circle.data = { d3: node } group.add(circle) circle.on('click', () => zoom(circle)) }) // --- function zoom(circle) { // By what value do we neeed to scale the group? let scaleBy = root.data.konva.radius() / circle.radius() console.log(`Scaling by: ${scaleBy}`) group.scale({x: scaleBy, y: scaleBy}) // By what values do we neeed to reposition the group? let newX = (width / 2) - (circle.x() * scaleBy) let newY = (height / 2) - (circle.y() * scaleBy) console.log(`Repositioning by: x:${newX}, y:${newY}`) group.position({x: newX, y: newY}) }
.konvajs-content { background: rgba(124, 7, 12, 0.1); }
<script src="https://cdn.jsdelivr.net/npm/konva@8.1.3/konva.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <div id="container"></div>