score:2

You can get the type of event using d3.event.sourceEvent. In the code you shared, the dragging anywhere in the white space will rotate and dragging on the bars will move.

With d3.event.sourceEvent you can check whether the ctrl key is pressed and move/rotate accordingly. You don't even need the drag function for your svg. It can be handled using the zoom functions alone.

Here's the fiddle:

var origin = [100, 85],
  scale = 5,
  j = 10,
  cubesData = [];
var alpha = 0,
  beta = 0,
  startAngle = Math.PI / 6;
var zoom = d3.zoom()
  .scaleExtent([1, 40])
  .on("zoom", zoomed)
  .on('start', zoomStart)
  .on('end', zoomEnd);
var svg = d3.select('svg').call(zoom)
  .append('g');

var color = d3.scaleOrdinal(d3.schemeCategory20);
var cubesGroup = svg.append('g').attr('class', 'cubes').attr('transform', 'translate(0,0) scale(1)');
var mx, my, mouseX, mouseY;

var cubes3D = d3._3d()
  .shape('CUBE')
  .x(function(d) {
    return d.x;
  })
  .y(function(d) {
    return d.y;
  })
  .z(function(d) {
    return d.z;
  })
  .rotateY(startAngle)
  .rotateX(-startAngle)
  .origin(origin)
  .scale(scale);

function zoomStart() {
  mx = d3.event.sourceEvent.x;
  my = d3.event.sourceEvent.y;
  if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'mousemove' && d3.event.sourceEvent.ctrlKey == true) {
    cubesGroup.attr("transform", d3.event.transform);
  }
}

function zoomEnd() {
  if (d3.event.sourceEvent == null) return;
  mouseX = d3.event.sourceEvent.x - mx + mouseX
  mouseY = d3.event.sourceEvent.y - my + mouseY
}

function zoomed(d) {
  if (d3.event.sourceEvent == null) return;

  if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'wheel') {
    cubesGroup.attr("transform", "scale(" + d3.event.transform['k'] + ")");
  } else if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'mousemove' && d3.event.sourceEvent.ctrlKey == true) {
    cubesGroup.attr("transform", "translate(" + d3.event.transform['x'] + "," + d3.event.transform['y'] + ") scale(" + d3.event.transform['k'] + ")");
  } else if (d3.event.sourceEvent !== null && d3.event.sourceEvent.type == 'mousemove' && d3.event.sourceEvent.ctrlKey == false) {
    mouseX = mouseX || 0;
    mouseY = mouseY || 0;
    beta = (d3.event.sourceEvent.x - mx + mouseX) * Math.PI / 230;
    alpha = (d3.event.sourceEvent.y - my + mouseY) * Math.PI / 230 * (-1);
    processData(cubes3D.rotateY(beta + startAngle)
      .rotateX(alpha - startAngle)(cubesData), 0);

  };
}

function processData(data, tt) {

  /* --------- CUBES ---------*/

  var cubes = cubesGroup.selectAll('g.cube')
    .data(data, function(d) {
      return d.id
    });

  var ce = cubes
    .enter()
    .append('g')
    .attr('class', 'cube')
    .attr('fill', function(d) {
      return color(d.id);
    })
    .attr('stroke', function(d) {
      return d3.color(color(d.id)).darker(2);
    })
    .merge(cubes)
    .sort(cubes3D.sort);

  cubes.exit().remove();

  /* --------- FACES ---------*/

  var faces = cubes.merge(ce)
    .selectAll('path.face')
    .data(function(d) {
        return d.faces;
      },
      function(d) {
        return d.face;
      }
    );

  faces.enter()
    .append('path')
    .attr('class', 'face')
    .attr('fill-opacity', 0.95)
    .classed('_3d', true)
    .merge(faces)
    .transition().duration(tt)
    .attr('d', cubes3D.draw);

  faces.exit().remove();

  /* --------- TEXT ---------*/

  var texts = cubes.merge(ce)
    .selectAll('text.text').data(function(d) {
      var _t = d.faces.filter(function(d) {
        return d.face === 'top';
      });

      return [{
        height: d.height,
        centroid: _t[0].centroid
      }];
    });

  texts.enter()
    .append('text')
    .attr('class', 'text')
    .attr('dy', '-.7em')
    .attr('text-anchor', 'middle')
    .attr('font-family', 'sans-serif')
    .attr('font-weight', 'bolder')
    .attr('x', function(d) {
      return origin[0] + scale * d.centroid.x
    })
    .attr('y', function(d) {
      return origin[1] + scale * d.centroid.y
    })
    .classed('_3d', true)
    .merge(texts)
    .transition().duration(tt)
    .attr('fill', 'black')
    .attr('stroke', 'none')
    .attr('x', function(d) {
      return origin[0] + scale * d.centroid.x
    })
    .attr('y', function(d) {
      return origin[1] + scale * d.centroid.y
    })
    .tween('text', function(d) {
      var that = d3.select(this);
      var i = d3.interpolateNumber(+that.text(), Math.abs(d.height));
      return function(t) {
        that.text(i(t).toFixed(1));
      };
    });

  texts.exit().remove();

  /* --------- SORT TEXT & FACES ---------*/
  ce.selectAll('._3d').sort(d3._3d().sort);
}

function init() {
  cubesData = [];
  var cnt = 0;
  for (var z = -j / 2; z <= j / 2; z = z + 5) {
    for (var x = -j; x <= j; x = x + 5) {
      var h = d3.randomUniform(-2, -7)();
      var _cube = makeCube(h, x, z);
      _cube.id = 'cube_' + cnt++;
      _cube.height = h;
      cubesData.push(_cube);
    }
  }
  processData(cubes3D(cubesData), 1000);
}

function dragStart() {
  console.log('dragStart')
  mx = d3.event.x;
  my = d3.event.y;
}

function dragged() {
  console.log('dragged')
  mouseX = mouseX || 0;
  mouseY = mouseY || 0;
  beta = (d3.event.x - mx + mouseX) * Math.PI / 230;
  alpha = (d3.event.y - my + mouseY) * Math.PI / 230 * (-1);
  processData(cubes3D.rotateY(beta + startAngle)
    .rotateX(alpha - startAngle)(cubesData), 0);
}

function dragEnd() {
  console.log('dragend')
  mouseX = d3.event.x - mx + mouseX;
  mouseY = d3.event.y - my + mouseY;
}

function makeCube(h, x, z) {
  return [{
      x: x - 1,
      y: h,
      z: z + 1
    }, // FRONT TOP LEFT
    {
      x: x - 1,
      y: 0,
      z: z + 1
    }, // FRONT BOTTOM LEFT
    {
      x: x + 1,
      y: 0,
      z: z + 1
    }, // FRONT BOTTOM RIGHT
    {
      x: x + 1,
      y: h,
      z: z + 1
    }, // FRONT TOP RIGHT
    {
      x: x - 1,
      y: h,
      z: z - 1
    }, // BACK  TOP LEFT
    {
      x: x - 1,
      y: 0,
      z: z - 1
    }, // BACK  BOTTOM LEFT
    {
      x: x + 1,
      y: 0,
      z: z - 1
    }, // BACK  BOTTOM RIGHT
    {
      x: x + 1,
      y: h,
      z: z - 1
    }, // BACK  TOP RIGHT
  ];
}

d3.selectAll('button').on('click', init);

init();
button {
  position: absolute;
  right: 10px;
  top: 10px;
}
<!DOCTYPE html>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-3d/build/d3-3d.min.js"></script>

<body>
  <svg width="500" height="500"></svg>
</body>

On JSFiddle


Related Query