Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how to perform d3 drag and drop

Having Issue with d3.drag for angular 4. whenever I drag the rectangle object , it is moving fine for first time. After release of mousepress and again trying to drag the rectangle, it is going back to previous event and not able to make mouse control on draggable object. Please give solution to my problem.

import { Component,Input, ElementRef, OnInit } from '@angular/core'; 
import * as d3 from 'd3';  
interface LineData{
  xVal: number,
  yVal:number
}

@Component({
  selector: 'app-line-chart',
  template:'<svg height="500" width="500" ></svg>',
  styleUrl: [] 
})

export class LineChartComponent implements OnInit {
  @Input() data : LineData[];
  private parentNativeElement : any;
  constructor(private element:ElementRef) { 
      this.parentNativeElement = element.nativeElement;
  }

  ngOnInit() {
    var width = 300;
    var height = 300;
    var margin = {top: 10, right: 10, bottom: 30, left: 10}

    var x = d3.scaleLinear().range([0, width]);
    var y = d3.scaleLinear().range([height, 0]);

    var xAxis = d3.axisBottom(x)
        .scale(x)
        .ticks(5);

    var yAxis = d3.axisLeft(y)
        .scale(y)
        .ticks(5);

    var valueline:any = d3.line()
        .x(function (d) {
            return x(d['xVal']);
        })
        .y(function (d) {
            return y(d['yVal']);
        });
        console.log(valueline);

    var svg = d3.select("svg");
    d3.select(this.parentNativeElement)
        .append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g");

    // Get the data
    var data = [
                  {
                    "xVal": 1,
                    "yVal": 2
                  },
                  {
                    "xVal": 2,
                    "yVal": 4
                  },
                  {
                    "xVal": 3,
                    "yVal": 1
                  },
                  {
                    "xVal": 4,
                    "yVal": 5
                  },
                  {
                    "xVal": 5,
                    "yVal": 3
                  }
              ];

    // Scale the range of the data
    x.domain(d3.extent(data,
        function (d) {
            return d.xVal;
        }));
    y.domain([
        0, d3.max(data,
            function (d) {
                return d.yVal;
            })
    ]);
     let color = d3.scaleOrdinal(d3.schemeCategory10);

     svg.append("path").datum(data).attr("class","path")// Add the valueline path.
    .attr("fill", "none")
    .attr("stroke", "red")
    .attr("stroke-width", 1.5)
    .attr("d", valueline(data)).attr("class", "line");


    let rectangle:any = d3.range(1).map(function(){
        return{
            x: Math.floor(Math.random()*width),
            y: Math.floor(Math.random()*height)
        };
    });

    console.log(rectangle);

   let dragRect = svg.selectAll('g').data(rectangle).enter().append("g")

    dragRect.append("rect")
    .attr("x",function(d){return d['x'];})
    .attr("y",function(d){return d['y'];})
    .attr("height",50)
    .attr("width",50).style("fill", "steelblue");

   svg.selectAll('g').attr("transform", 
    "translate(" + margin.left + "," + margin.top + ")").data(rectangle)
    .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));


    function dragstarted(d){
        d3.select(this).raise().classed("active",true);
    }



    function dragged(d){
        d.xVal = x.invert(d3.event.x);
        d.yVal = y.invert(d3.event.y);
        d3.select(this).select("rect")
        .attr("x", x(d.xVal))
        .attr("y", y(d.yVal))
        .attr("transform","translate("+d.xVal+","+d.yVal+")")
        console.log(d); 
    }


    function dragended(d){
        d3.select(this).raise().classed("active",false);
        //d3.select('rect#no-drag').on('mousedown.drag',null);
    }



    svg.append("g") // Add the X Axis
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    svg.append("g") // Add the Y Axis
        .attr("class", "y axis"`enter code here`)
        .call(yAxis);
  }

}
like image 325
Kanduri Madhurika Avatar asked Oct 26 '18 05:10

Kanduri Madhurika


1 Answers

The basic problem seems to be that dragged function is not remembering the x & y between successive drag events.

To do this, you need

d3.select(this)
  .attr("x", d.x = x(d.xVal))
  .attr("y", d.y = y(d.yVal))

instead of

d3.select(this)
  .attr("x", x(d.xVal))
  .attr("y", y(d.yVal))

Run this code snippet to check it out

console.clear()
var width = 300;
var height = 300;
var margin = {top: 10, right: 10, bottom: 30, left: 10}

var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);

var xAxis = d3.axisBottom(x).scale(x).ticks(5);

var yAxis = d3.axisLeft(y).scale(y).ticks(5);

var valueline = d3.line()
  .x(function (d) { return x(d['xVal']); })
  .y(function (d) { return y(d['yVal']); });

var svg = d3.select("svg");
d3.select(this.parentNativeElement)
  .append("svg")
  .attr("width", width)
  .attr("height", height)
  .append("g");

// Get the data
var data = [
  {
    "xVal": 1,
    "yVal": 2
  },
  {
    "xVal": 2,
    "yVal": 4
  },
  {
    "xVal": 3,
    "yVal": 1
  },
  {
    "xVal": 4,
    "yVal": 5
  },
  {
    "xVal": 5,
    "yVal": 3
  }
];

// Scale the range of the data
x.domain(d3.extent(data,
  function (d) {
    return d.xVal;
  }));
y.domain([
    0, d3.max(data,
        function (d) {
            return d.yVal;
        })
]);

let color = d3.scaleOrdinal(d3.schemeCategory10);

svg.append("path").datum(data).attr("class","path")
  .attr("fill", "none")
  .attr("stroke", "red")
  .attr("stroke-width", 1.5)
  .attr("d", valueline(data)).attr("class", "line");

let rectangle = d3.range(3).map(function() {
  return {
    x: Math.floor(Math.random()*width),
    y: Math.floor(Math.random()*height)
  };
});

let dragRect = svg.selectAll('g').data(rectangle).enter()
  .append("g")

dragRect.append("rect")
  .attr("x",function(d){return d['x'];})
  .attr("y",function(d){return d['y'];})
  .attr("height", 50)
  .attr("width", 50)
  .style("fill", "steelblue")

svg.selectAll('rect')
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")")
  .data(rectangle)
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended)
  );

const dragBounds = {}
const tickHeight = 10;

function setDragBounds(subject) {
  dragBounds.top = 0 - margin.top;
  dragBounds.left = 0 - margin.left;
  dragBounds.bottom = height - tickHeight - subject.attr('height');
  dragBounds.right = width - margin.right - subject.attr('width');
}

function dragstarted(d){
  /* 
    Calculate drag bounds at dragStart because it's one event vs many 
    events if done in 'dragged()'
  */    
  setDragBounds(d3.select(this)) 
  d3.select(this).raise().classed("active", true);
}

function dragged(d){
  d3.select(this)
    .attr("x", getX(d.x = d3.event.x) )
    .attr("y", getY(d.y = d3.event.y) );
}

function getX(x) {
  return x < dragBounds.left ? dragBounds.left
    : x > dragBounds.right ? dragBounds.right 
    : x
}

function getY(y) {
  return y < dragBounds.top ? dragBounds.top
    : y > dragBounds.bottom ? dragBounds.bottom 
    : y
}

function dragended(d){
  d3.select(this).classed("active", false);
}

svg.append("g") // Add the X Axis
  .attr("class", "x axis")
  .attr("transform", "translate(0," + height + ")")
  .call(xAxis)

svg.append("g") // Add the Y Axis
  .attr("class", "y axis")
  .call(yAxis);
  
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

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

Note I attached the drag events to rect instead of g, which I assume was a typo.


Keeping within bounds

To constrain the drag to bounds the way I found works best is a max/min function on the x & y values.

function dragged(d){
  d3.select(this)
    .attr("x", getX(d.x = d3.event.x) )
    .attr("y", getY(d.y = d3.event.y) );
}

function getX(x) {
  return x < dragBounds.left ? dragBounds.left
    : x > dragBounds.right ? dragBounds.right 
    : x
}

function getY(y) {
  return y < dragBounds.top ? dragBounds.top
    : y > dragBounds.bottom ? dragBounds.bottom 
    : y
}

Bounds are set at drag start to avoid repetition of any calculation.

const dragBounds = {}
const tickHeight = 10;

function setDragBounds(subject) {
  dragBounds.top = 0 - margin.top;
  dragBounds.left = 0 - margin.left;
  dragBounds.bottom = height - tickHeight - subject.attr('height');
  dragBounds.right = width - margin.right - subject.attr('width');
}

function dragstarted(d){
  /* 
    Calculate drag bounds at dragStart because it's one event vs many 
    events if done in 'dragged()'
  */    
  setDragBounds(d3.select(this)) 
  d3.select(this).raise().classed("active", true);
}
like image 73
Richard Matsen Avatar answered Nov 10 '22 08:11

Richard Matsen