Как выполнить перетаскивание d3

Проблема с d3.drag для angular 4. всякий раз, когда я перетаскиваю прямоугольный объект, он впервые перемещается нормально. После отпускания кнопки мыши и повторной попытки перетащить прямоугольник он возвращается к предыдущему событию и не может управлять перемещаемым объектом с помощью мыши. Пожалуйста, дайте решение моей проблемы.

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);
  }

}

Это для линейной диаграммы, имеющей перемещаемый объект (прямоугольник). при перемещении прямоугольника будут отображаться значения перетаскиваемой оси

Kanduri Madhurika 26.10.2018 07:59
4
1
2 464
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Основная проблема заключается в том, что функция dragged не запоминает координаты x и y между последовательными событиями перетаскивания.

Для этого вам понадобится

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

вместо того

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

Запустите этот фрагмент кода, чтобы проверить его

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>

Обратите внимание, что я прикрепил события перетаскивания к rect вместо g, что, как я полагаю, было опечаткой.


В рамках

Чтобы ограничить перетаскивание границами, как я обнаружил, лучше всего работает функция max / min для значений x и y.

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
}

Границы устанавливаются в начале перетаскивания, чтобы избежать повторения каких-либо вычислений.

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);
}

И как я могу установить этот перетаскиваемый объект прямоугольника, указанный только для осей x и y? как я могу установить это, он не должен выходить за пределы оси или траектории линии?

Kanduri Madhurika 26.10.2018 11:15

Хороший вопрос, см. Сноску.

Richard Matsen 27.10.2018 03:10

Ричард Матсен - у меня это работает. Спасибо большое . Я узнал это.

Kanduri Madhurika 29.10.2018 11:21

Другие вопросы по теме