Q:

d3js. Coordinates after zoom and pan. How to calculate it?

I have a simple code which appends a new rectangle under the cursor on Ctrl + click. Everything works fine on default zoom/pan but after zoom/pan we get the wrong coordinates. 
This is what I've tried till now: 

let width = 960,
  height = 500

let data = {
  nodes: [{
    name: "node1",
    x: 100,
    y: 100,
    height: 50,
    width: 50
  }, {
    name: "node2",
    x: 200,
    y: 200,
    height: 50,
    width: 50
  }]
}

let svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height)

let g = svg.append('g')
  .attr('class', 'everything')

let zoom_handler = d3.zoom()
  .on("zoom", zoomed)

zoom_handler(svg)

function zoomed() {
  g.attr("transform", d3.event.transform)
}

function update(data) {
  d3.selectAll('.nodes').remove()

  let nodes = g.selectAll("node")
    .data(data.nodes)
    .enter()
    .append('g')
    .attr('class', 'nodes')

  nodes
    .append("rect")
    .attr("class", "node")
    .attr("width", d => {
      return d.width
    })
    .attr("height", d => {
      return d.height
    })
    .attr("x", d => {
      return d.x
    })
    .attr("y", d => {
      return d.y
    })
    .attr("fill", 'red')
}

update(data)

d3.select('body')
  .on('click', () => {
    if (d3.event.ctrlKey) {
      addNode(d3.event.x, d3.event.y)
    }
  })

function addNode(x1, y1) {
  data.nodes.push({
    name: "nodeN",
    x: x1,
    y: y1,
    height: 50,
    width: 50,
  })

  update(data)
}
body {
  width: 960px;
  height: 500px;
  position: relative;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

I know the formula 'tx - x\k' but where I can find tx? Or maybe I'm doing something wrong here.
How can I solve this?

A:

You already have a transformation matrix applied to the group which contains the rectangles. The coordinates that we get via d3.event will have these transformations already applied and thus the transformation matrix gets applied twice for the new rectangles in the group. So you have to apply the reverse transformation matrix to the coordinates first and then use for rectangles.
Working code snippet: -

let width = 960,
  height = 500

let data = {
  nodes: [{
    name: "node1",
    x: 100,
    y: 100,
    height: 50,
    width: 50
  }, {
    name: "node2",
    x: 200,
    y: 200,
    height: 50,
    width: 50
  }]
}

let svg = d3.select("body").append("svg")
  .attr("width", width)
  .attr("height", height)

let g = svg.append('g')
  .attr('class', 'everything')

let zoom_handler = d3.zoom()
  .on("zoom", zoomed)

zoom_handler(svg)

function zoomed() {
  g.attr("transform", d3.event.transform)
}

function update(data) {
  d3.selectAll('.nodes').remove()

  let nodes = g.selectAll("node")
    .data(data.nodes)
    .enter()
    .append('g')
    .attr('class', 'nodes')

  nodes
    .append("rect")
    .attr("class", "node")
    .attr("width", d => {
      return d.width
    })
    .attr("height", d => {
      return d.height
    })
    .attr("x", d => {
      return d.x
    })
    .attr("y", d => {
      return d.y
    })
    .attr("fill", 'red')
}

update(data)

d3.select('body')
  .on('click', () => {
    if (d3.event.ctrlKey) {
      var svgEl = svg.node();
      var pt = svgEl.createSVGPoint();
      pt.x = d3.event.x;
      pt.y = d3.event.y;
      pt = pt.matrixTransform(g.node().getCTM().inverse());
      addNode(pt.x, pt.y)
    }
  });

function addNode(x1, y1) {
  data.nodes.push({
    name: "nodeN",
    x: x1,
    y: y1,
    height: 50,
    width: 50,
  })

  update(data)
}
body{
margin: 0px;
}
<script src="https://d3js.org/d3.v4.min.js"></script>

