Techniques for manipulating multiple circles with JavaScript

After creating a series of circles with d3, I successfully implemented the functionality to drag individual circles and track their positions.


var width= 800;
 var height=600;
 svg= d3.select("body").select(".div1").append("svg")
                            .attr("width", width)
                            .attr("height",height);
 transformed_data =  [
                  [5, 20], [480, 90], [250, 50], [100, 33], [330, 95],
                  [410, 12], [475, 44], [25, 67], [85, 21], [220, 88] 
                ];

 X = [0,800];
 Y = [0,600];

 xScale =d3.scale.linear().domain(X).range([70, width-70]);
 yScale =d3.scale.linear().domain(Y).range([70, height-70]);

 drag = d3.behavior.drag()
          .on("drag", function(d,i) {
                d3.select(this).attr("cx", d3.event.x);
                d3.select(this).attr("cy", d3.event.y);
          })
         .on("dragend",function(d,i){

         });

svg.selectAll('circle')
        .data(transformed_data)
        .enter()
        .append("circle")
        .attr("id", function(d,i){return "id_" + i.toString();})
        .attr("cx", function(d,i){return xScale(transformed_data[i][0]);})
        .attr("cy", function(d,i){return yScale(transformed_data[i][1]);}) 
        .attr("r",10)
        .call(drag);

However, the challenge arises when attempting to select multiple circles simultaneously using mouse input and dragging them together. Is there any guidance or examples available for achieving this multi-selection and movement?

Answer №1

There is a method available for dragging multiple elements, shared by @Lars, which works well but has some unnatural interactions that I personally didn't find appealing. You can find the code here: Drag multiple elements that aren't grouped in a `g` tag?

After exploring various options, I arrived at the following code that functions flawlessly with a very natural interaction.

<html>
 <head>
   <title>jQuery UI Selectable - Serialize</title>
   <link rel="stylesheet" href="//code.jquery.com/ui/1.11.4/theme/smoothness/jquery-ui.css">
   <script src="http://code.jquery.com/jquery-1.11.0.min.js"></script>
   <script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
   <link rel="stylesheet" href="/resources/demos/style.css">  

  <style>
      rect.selection {
          stroke: black;
          stroke-dasharray: 4px;
          stroke-opacity  : 0.9;
          fill: transparent;
       }
      .state circle {
          stroke  : gray;
          cursor  : pointer;
       }
      .state {
          fill    : black;
      }
      .selected{
          fill    : red;
      }

   </style>

   <body> 
      <div class="div1"></div>  
   </body>


<script type="text/javascript">

var width = 800,
    radius=10,
    height = 600;

   svg= d3.select("body").select(".div1").append("svg")
                            .attr("width", width)
                            .attr("height",height);
   var transformed_data= d3.range(10).map(function(d) {
       return {
         x: parseInt(Math.random() * width),
         y: parseInt(Math.random() * height),
       }
     });

    // Drag Event 
    drag = d3.behavior.drag()
            .on("drag", function( d, i) {
                    var selection = d3.selectAll('.selected');
                    console.log(selection[0] + "---"+selection[1]);
                    svg.selectAll( "rect.selection").remove();
                    if(selection[0].indexOf(this)==-1) {
                        selection = d3.select(this);
                        selection.classed("selected", true);
                    } 
                    selection.attr("cx", function(d){ d.x += d3.event.dx; return d.x;})
                    selection.attr("cy", function(d,i){d.y += d3.event.dy; return d.y;})    
            });

    gStates = svg.selectAll('circle')
                 .data(transformed_data)
                 .enter()
                 .append("circle")
                 .attr("class","state")
                 .attr("id", function(d,i){return "id_" + i.toString();})
                 .attr("cx", function(d,i){return d.x;})
                 .attr("cy", function(d,i){return d.y;}) 
                 .attr("r",10)
                 .call(drag);

    svg.on( "mousedown", function() {
            if(!d3.event.ctrlKey) {
                    d3.selectAll(".selected").classed( "selected", false);
            }
            var p = d3.mouse(this);
            svg.append( "rect")
               .attr({
                    class   : "selection",
                    x       : p[0],
                    y       : p[1],
                    width   : 0,
                    height  : 0
                })
        })
       .on( "mousemove", function() {
            var s = svg.select( "rect.selection");
            if( !s.empty()) {
                var p = d3.mouse( this),
                    d = {
                        x       : parseInt( s.attr( "x"), 10),
                        y       : parseInt( s.attr( "y"), 10),
                        width   : parseInt( s.attr( "width"), 10),
                        height  : parseInt( s.attr( "height"), 10)
                    },
                    move = {
                        x : p[0] - d.x,
                        y : p[1] - d.y
                    };

                if( move.x < 1 || (move.x*2<d.width)) {
                    d.x = p[0];
                    d.width -= move.x;
                } else {
                    d.width = move.x;       
                }

                if( move.y < 1 || (move.y*2<d.height)) {
                    d.y = p[1];
                    d.height -= move.y;
                } else {
                    d.height = move.y;       
                }

                s.attr( d);
                d3.selectAll( ".state").each( function( state_data, i) {
                    if( 
                        !d3.select(this).classed("selected") && 
                            // inner circle inside selection frame
                        state_data.x-radius>=d.x && state_data.x+radius<=d.x+d.width && 
                        state_data.y-radius>=d.y && state_data.y+radius<=d.y+d.height
                    ) {
                        d3.select(this)
                        .classed( "selection", true)
                        .classed( "selected", true);
                    }
                });
            }
        })

        .on( "mouseup", function() {
              // remove selection frame
            svg.selectAll( "rect.selection").remove();

              // remove temporary selection marker class
            d3.selectAll( '.state.selection').classed( "selection", false);
        })
        .on( "mouseout", function() {
            if( d3.event.relatedTarget.tagName=='HTML') {
                    // remove selection frame
                svg.selectAll( "rect.selection").remove();

                    // remove temporary selection marker class
                d3.selectAll( '.state.selection').classed( "selection", false);
            }
        });
</script>
  </head>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

The specified property 'XYZ' is not found in the type 'Readonly<{ children?: ReactNode; }> & Readonly<{}>'

Whenever I try to access .props in RecipeList.js and Recipe.js, a syntax error occurs. Below is the code snippet for Recipe.js: import React, {Component} from 'react'; import "./Recipe.css"; class Recipe extends Component { // pr ...

How do I disable the hover and click highlighting effect on a div in Vuetify using v-on in Vue2?

Currently, I have implemented a Vuetify VListItem in a NavigationDrawer with an on click listener that displays a menu in the div below. The menu is functioning properly - opening and closing as expected. However, it highlights on hover/click which I wou ...

Generating a two-dimensional array using CoffeeScript comprehension

I'm struggling to navigate through nested JSON and Coffeescript. The structure of my JSON data is as follows: { "top-level": { "first_array": [y1,y2,y3...], "second_array: [y1,y2,y3...]... etc } My goal is to convert each array into a two-dimens ...

What is the best jQuery library to add to my website?

I have included several jQuery scripts on my website for various functionalities such as sticky header, anchors, and animations. I am wondering if it is necessary to include all of them or if I can just include one or two? Here are the jQuery scripts I ha ...

Input must be either a 12-byte string, a 24-character hexadecimal string, or an integer

As a beginner in coding, I am struggling to understand and fix this issue. If anyone could lend a helping hand, I would be truly grateful. Error Message: BSONTypeError - Argument passed in must be a string of 12 bytes or a string of 24 hex characters or a ...

Socketio: Issue: Isolated surrogate U+D83D is not a valid scalar value

I've been experiencing frequent crashes with my node.js server recently, all due to a recurring socket.io error. It seems that the client may be sending invalid UTF strings, causing an error in the utf8.js file. I'm getting frustrated with the co ...

Using Typescript in NextJS 13 application router, implement asynchronous fetching with async/await

Recently, I implemented a fetch feature using TypeScript for my NextJS 13 project. As I am still getting familiar with TypeScript, I wanted to double-check if my approach is correct and if there are any potential oversights. Here is the code snippet from ...

What is the solution to fixing the Vetur/Vuelidate issue where the error message "'validate' does not exist in type 'ComponentOptions<Vue [etc.]" is displayed?

QUERY: I'm facing an issue with error 'validations' does not exist in type 'ComponentOptions<Vue [etc.] when using Vetur with TypeScript installed in VSCode. How can I resolve this? CONTEXT: I integrated Vuelidate into a single-file ...

The resolvers contain the Query.Mutation but it is not specified in the schema

const { ApolloServer, gql } = require('apollo-server-express'); const express = require('express'); const port = process.env.PORT || 4000; const notes = [ { id: '1', content: 'This is a note', author: 'Adam ...

Tips for personalizing the webpack compilation process for transforming .vue files into .js files?

As a beginner in the realm of webpack plugins, I've grasped the idea behind the html-webpack-plugin, which empowers users to personalize the generation process of html files by webpack. In a similar vein, I am on the quest for a plugin tailored for . ...

Parent function variable cannot be updated within a $.ajax call

I'm facing an issue with pushing a value from inside an ajax call to an array located outside of the call but still within the parent function. It appears that I am unable to access or update any variable from inside the ajax success statement. Any as ...

What is the best way to determine the left and top coordinates when resizing a draggable image within a container?

I am struggling to correctly scale the image and set the left (x) and top (y) positions. Here is the code from my template: <div id="container" :style="`height: ${height}px;width: ${size}px;overflow: hidden;position: relative;`"> ...

What is the best way to interrupt an animation and restart it?

On my webpage, I have implemented some anchors and links that navigate to these anchors. When I click on a link, the background-color of the anchor changes. I use animation to gradually fade out this color over 10 seconds - starting with white and then rem ...

How can HTML5 geolocation be utilized to provide real-time latitude and longitude coordinates for integration with the Google Maps API?

I am currently working on a project where I need to dynamically fetch longitude and latitude values from the browser geolocation and then include them in the options array for the Google Maps API. Here is the code snippet I am using: function initMap(){ ...

Unable to activate the date range picker

I am having trouble with integrating the daterange picker on my webpage. I can't seem to get it to work properly. Can anyone help me figure out what I might be doing wrong or if there's something missing? CSHTML: <div class="has-feedback" &g ...

Multiple executions of Ajax callback detected

I am trying to utilize an ajax-call to a script that searches for numbers. The response is expected to be a json array containing names and surnames as strings. However, I am facing an issue where the script seems to be looping and sending multiple respons ...

What is the method to advance the opposing line in the dynamic chart?

Here is the link to the code for a dynamic graph. I am struggling with keeping the red line updated and moving forward together with the blue line. I cannot figure out why the red line remains static in the dynamic graph. Any suggestions on how to solve t ...

Guide on converting HTML datetime picker datetime-local to moment format

I am looking to convert an input type : <input type="datetime-local" name="sdTime" id="stTimeID" onChange={this.stDateTime} /> into a specific date format: const dateFormat = 'MM/DD/YYYY hh:mm:ss a'; To achieve this, I need to transfer ...

Implementing a Jquery check based on a checkbox

Hey, I'm having an issue with a condition. When I uncheck the checkbox, it doesn't uncheck. I've tried to make a block display, but the JavaScript isn't working. I attempted to add: document.getElementById("Reload").style.display = "b ...

Struggling with Vue's Router Transition fade in/out effect not functioning as expected?

Question: I've implemented Vue's Router and it switches between components without any issues. However, I added a <transition name="fade" mode="out=in"> around it but the fade effect is not working as expected. Desired ...