faerin
faerin

Reputation: 1925

D3.js enable/disable dragging programmatically

As far as I understand, it should be possible to programmatically enable/disable the drag behavior and always maintain the zoom behavior in the d3.js.

However I do no get that to work correctly.

I have enabled zoom by the following code. This code alone also enables dragging.

const zoom = d3.zoom()
    .scaleExtent([1, 15])
    .on('zoom', zoomed);

svg.call(zoom);

My hopes are to be able to control just the dragging/panning operation by using the dragging-API, and if I use the following code the drag listener will indeed not work, but the zoom will:

const drag = d3.drag()
    .on('start', d3.disableDrag)

svg.call(drag);

That said I figured I would be able to enable/disable the dragging/panning as I wish under certain circumstances. For example, if the zoom is at it's original state (scale 1), then dragging should disable. If the scale state is > 1, then panning should enable.

This is my complete code

const dragEnabled = false;
const drag = d3.drag()
    .on("start", d3.disableDrag)

svg.call(drag);

const zoom = d3.zoom()
    .scaleExtent([1, 15])
    .on("zoom", zoomed);

svg.call(zoom);    

function disableDrag() {
    svg.call(drag.on("start", d3.disableDrag));
}

function enableDrag() {
    svg.call(drag.on("start", d3.enableDrag));
    dragEnabled = true;
}

function zoomed(event) {
    const { transform } = event;
    const { k: scale } = transform;

    group.attr("transform", transform);
    group.attr("stroke-width", 1 / scale);

    switch(true) {
        case scale <= 1:
            disableDrag();
            break;

        default:
            if(!dragEnabled) enableDrag();
    }
}

The result when I run this code is

Do anyone have any suggestions on how I can achieve this?

Simple demo

const svg = d3.select('svg');
const group = d3.select('g#content');
let dragEnabled = false;

const drag = d3.drag()
    .on("start", d3.disableDrag)

svg.call(drag);

const zoom = d3.zoom()
    .scaleExtent([1, 15])
    .on("zoom", zoomed);

svg.call(zoom);    

function disableDrag() {
    svg.call(drag.on("start", null));
}

function enableDrag() {
    svg.call(drag.on("start", null));
    dragEnabled = true;
}

function zoomed(event) {
    const { transform } = event;
    const { k: scale } = transform;

    group.attr("transform", transform);
    group.attr("stroke-width", 1 / scale);

    switch(true) {
        case scale <= 1:
            disableDrag();
            break;

        default:
            if(!dragEnabled) enableDrag();
    }
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<div>
  <svg width="1200" height="500" viewBox="0 0 1200 500">
      <g id="content">
         <path d="M10,10   l50,0  0,50  -50,0  0,-50 Z" />
         <path d="M70,10   l50,0  0,50  -50,0  0,-50 Z" />
         <path d="M130,10   l50,0  0,50  -50,0  0,-50 Z" />
         <path d="M190,10   l50,0  0,50  -50,0  0,-50 Z" />
      </g>
  </svg>
</div>

External JS-Fiddle: https://jsfiddle.net/1vyw0tL6/

Upvotes: 0

Views: 749

Answers (1)

Ian
Ian

Reputation: 34489

So first thing as mention is it's d3.dragDisable and not d3.disableDrag. However regardless that function requires a window object to work.

I've tried to go for the result I think you're trying to achieve while also answering the specific "why isn't this working" question.

I've done some stuff with dragging before, and I've never tried to do it this way. Had to try and remember how I've done it before. So the reason what you've got isn't working is because the zoomed event fires even when panning...

So to fix this, firstly we're going to strip it right back. You found scaleExtent but if you combine with translateExtent you can achieve what you're after a lot more simply. The only thing that tripped me up, it seems you can't call both d3.zoom() and d3.drag() on the same element, so I've moved the drag down to the <g>.

const svg = d3.select('svg');
const group = d3.select('g#content');

const zoom = d3.zoom()
  .scaleExtent([1, 15])
  .translateExtent([[0,0],[1200,500]])
  .on("zoom", zoomed);
  
svg.call(zoom);    
svg.call(d3.drag());

function zoomed(event) {
  const { transform } = event;
  const { k: scale } = transform;

  group.attr("transform", transform);
  group.attr("stroke-width", 1 / scale);
}
<script src="https://d3js.org/d3.v7.min.js"></script>
<div>
  <svg width="1200" height="500" viewBox="0 0 1200 500">
      <g id="content">
         <path d="M10,10   l50,0  0,50  -50,0  0,-50 Z" />
         <path d="M70,10   l50,0  0,50  -50,0  0,-50 Z" />
         <path d="M130,10   l50,0  0,50  -50,0  0,-50 Z" />
         <path d="M190,10   l50,0  0,50  -50,0  0,-50 Z" />
      </g>
  </svg>
</div>

Upvotes: 1

Related Questions