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/[email protected]/konva.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="container"></div>
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/[email protected]/konva.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="container"></div>