Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to change the color of two circles upon overlap?

Hello I would like to know how it would be possible two make it that two circles change color when they overlap. Preferably the section that is overlapped would become white since its meant to represent sets.

var canvas = d3.select("canvas"),
    context = canvas.node().getContext("2d"),
    width = canvas.property("width"),
    height = canvas.property("height"),
    radius = 32;

var circles = d3.range(4).map(function(i) {
  return {
    index: i,
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

render();

canvas.call(d3.drag()
    .subject(dragsubject)
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended)
    .on("start.render drag.render end.render", render));

function render() {
  context.clearRect(0, 0, width, height);
  for (var i = 0, n = circles.length, circle; i < n; ++i) {
    circle = circles[i];
    context.beginPath();
    context.moveTo(circle.x + radius, circle.y);
    context.arc(circle.x, circle.y, radius, 0, 2 * Math.PI);
    context.fillStyle = color(circle.index);
    context.fill();
    if (circle.active) {
      context.lineWidth = 2;
      context.stroke();
    }
  }
}

function dragsubject() {
  for (var i = circles.length - 1, circle, x, y; i >= 0; --i) {
    circle = circles[i];
    x = circle.x - d3.event.x;
    y = circle.y - d3.event.y;
    if (x * x + y * y < radius * radius) return circle;
  }
}

function dragstarted() {
  circles.splice(circles.indexOf(d3.event.subject), 1);
  circles.push(d3.event.subject);
  d3.event.subject.active = true;
}

function dragged() {
  d3.event.subject.x = d3.event.x;
  d3.event.subject.y = d3.event.y;
}

function dragended() {
  d3.event.subject.active = false;
}
<canvas width="800" height="500"></canvas>
<script src="//d3js.org/d3.v4.min.js"></script>

My ideal solution would be something that allow me to change the color of the overlapping section to another color to represent the intersection between 2 sets.

Thank you in advance

Edit: some updates have been made however Ive only found how to do the coloring for static elements instead of moving

var   x1 = 100,
      y1 = 100,
      x2 = 150,
      y2 = 150,
      r = 70;

    var svg = d3.select('svg')
      .append('svg')
      .attr('width', 500)
      .attr('height', 500);

    svg.append('circle')
      .attr('cx', x1)
      .attr('cy', y1)
      .attr('r', r)
      .style('fill', 'steelblue')
      .style("fill-opacity",0.5)
      .style("stroke","black");

    svg.append('circle')
      .attr('cx', x2)
      .attr('cy', y2)
      .attr('r', r)
      .style('fill', 'orange')
      .style("fill-opacity",0.5)
      .style("stroke","black");

    var interPoints = intersection(x1, y1, r, x2, y2, r);

    svg.append("g")
      .append("path")
      .attr("d", function() {
        return "M" + interPoints[0] + "," + interPoints[2] + "A" + r + "," + r +
          " 0 0,1 " + interPoints[1] + "," + interPoints[3]+ "A" + r + "," + r +
          " 0 0,1 " + interPoints[0] + "," + interPoints[2];
      })
      .style('fill', 'red')
      .style("fill-opacity",0.5)
      .style("stroke","black");


    function intersection(x0, y0, r0, x1, y1, r1) {
      var a, dx, dy, d, h, rx, ry;
      var x2, y2;

      /* dx and dy are the vertical and horizontal distances between
       * the circle centers.
       */
      dx = x1 - x0;
      dy = y1 - y0;

      /* Determine the straight-line distance between the centers. */
      d = Math.sqrt((dy * dy) + (dx * dx));

      /* Check for solvability. */
      if (d > (r0 + r1)) {
        /* no solution. circles do not intersect. */
        return false;
      }
      if (d < Math.abs(r0 - r1)) {
        /* no solution. one circle is contained in the other */
        return false;
      }

      /* 'point 2' is the point where the line through the circle
       * intersection points crosses the line between the circle
       * centers.  
       */

      /* Determine the distance from point 0 to point 2. */
      a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);

      /* Determine the coordinates of point 2. */
      x2 = x0 + (dx * a / d);
      y2 = y0 + (dy * a / d);

      /* Determine the distance from point 2 to either of the
       * intersection points.
       */
      h = Math.sqrt((r0 * r0) - (a * a));

      /* Now determine the offsets of the intersection points from
       * point 2.
       */
      rx = -dy * (h / d);
      ry = dx * (h / d);

      /* Determine the absolute intersection points. */
      var xi = x2 + rx;
      var xi_prime = x2 - rx;
      var yi = y2 + ry;
      var yi_prime = y2 - ry;

      return [xi, xi_prime, yi, yi_prime];
    }
<script data-require="[email protected]" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
<svg width="500" height="500"></svg>

^This works for statics

var svg = d3.select("svg"),
    width = +svg.attr("width"),
    height = +svg.attr("height"),
    radius = 32;

var circles = d3.range(4).map(function() {
  return {
    x: Math.round(Math.random() * (width - radius * 2) + radius),
    y: Math.round(Math.random() * (height - radius * 2) + radius)
  };
});

var color = d3.scaleOrdinal()
    .range(d3.schemeCategory20);

svg.selectAll("circle")
  .data(circles)
  .enter().append("circle")
  	.style("fill-opacity",0.3)
    .style("stroke","black")
    .attr("cx", function(d) { return d.x; })
    .attr("cy", function(d) { return d.y; })
    .attr("r", 60)
    .style("fill", function(d, i) { return color(i); })
    .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) {
  d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
}

function dragended(d) {
  d3.select(this).classed("active", false);
}
<svg width="500" height="500"></svg>
<script src="//d3js.org/d3.v4.min.js"></script>

^This is my moving circles that I would like to add said effect on.

Is there any way to combine the two codes to achieve this ?

Thanks again

like image 782
Cormanno Avatar asked Dec 13 '17 06:12

Cormanno


People also ask

Why do the circles overlap?

A Venn diagram uses circles that overlap or don't overlap to show the commonalities and differences among things or groups of things. Things that have commonalities are shown as overlapping circles while things that are distinct stand alone.


1 Answers

You can use the intersection function of your static approach (second snippet) inside the dragged function of your dynamic approach (third snippet).

First of all, let's create 2 groups, so the "intersection" path will always be in front of the circles:

var g1 = svg.append("g");
var g2 = svg.append("g");

Now to the important part.

Inside the dragged function, get the position of the other (non-dragged) circle:

var otherCircle = circles.filter(function(e, j) {
    return i !== j;
}).datum();

If you have more than two circles you'll have to refactor this, but my demo below has just two circles, so let's move on.

Then, check if they overlap:

Math.hypot(d.x - otherCircle.x, d.y - otherCircle.y) < 2 * radius

If they do, call intersection, and set the path's d attribute:

var interPoints = intersection(d.x, d.y, radius, otherCircle.x, otherCircle.y, radius);
path.attr("d", function() {
  return "M" + interPoints[0] + "," + interPoints[2] + "A" + radius + "," + radius +
    " 0 0,1 " + interPoints[1] + "," + interPoints[3] + "A" + radius + "," + radius +
    " 0 0,1 " + interPoints[0] + "," + interPoints[2];
})

If they don't, erase the path:

path.attr("d", null)

Here is the working demo:

var svg = d3.select("svg"),
  width = +svg.attr("width"),
  height = +svg.attr("height"),
  radius = 60;

var data = d3.range(2).map(function(d, i) {
  return {
    x: i ? 200 : 400,
    y: 150
  };
});

var g1 = svg.append("g");
var g2 = svg.append("g");

var color = d3.scaleOrdinal()
  .range(d3.schemeCategory10);

var circles = g1.selectAll("circle")
  .data(data)
  .enter().append("circle")
  .style("fill-opacity", 0.3)
  .style("stroke", "black")
  .attr("cx", function(d) {
    return d.x;
  })
  .attr("cy", function(d) {
    return d.y;
  })
  .attr("r", radius)
  .style("fill", function(d, i) {
    return color(i);
  })
  .call(d3.drag()
    .on("start", dragstarted)
    .on("drag", dragged)
    .on("end", dragended));

var path = g2.append("path")
  .style("fill", "white")
  .style("stroke", "black")
  .attr("d", null);

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

function dragged(d, i) {
  d3.select(this).attr("cx", d.x = d3.event.x).attr("cy", d.y = d3.event.y);
  var otherCircle = circles.filter(function(e, j) {
    return i !== j;
  }).datum();
  if (Math.hypot(d.x - otherCircle.x, d.y - otherCircle.y) < 2 * radius) {
    var interPoints = intersection(d.x, d.y, radius, otherCircle.x, otherCircle.y, radius);
    path.attr("d", function() {
      return "M" + interPoints[0] + "," + interPoints[2] + "A" + radius + "," + radius +
        " 0 0,1 " + interPoints[1] + "," + interPoints[3] + "A" + radius + "," + radius +
        " 0 0,1 " + interPoints[0] + "," + interPoints[2];
    })
  } else {
    path.attr("d", null)
  }
}

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

function intersection(x0, y0, r0, x1, y1, r1) {
  var a, dx, dy, d, h, rx, ry;
  var x2, y2;

  /* dx and dy are the vertical and horizontal distances between
   * the circle centers.
   */
  dx = x1 - x0;
  dy = y1 - y0;

  /* Determine the straight-line distance between the centers. */
  d = Math.sqrt((dy * dy) + (dx * dx));

  /* Check for solvability. */
  if (d > (r0 + r1)) {
    /* no solution. circles do not intersect. */
    return false;
  }
  if (d < Math.abs(r0 - r1)) {
    /* no solution. one circle is contained in the other */
    return false;
  }

  /* 'point 2' is the point where the line through the circle
   * intersection points crosses the line between the circle
   * centers.  
   */

  /* Determine the distance from point 0 to point 2. */
  a = ((r0 * r0) - (r1 * r1) + (d * d)) / (2.0 * d);

  /* Determine the coordinates of point 2. */
  x2 = x0 + (dx * a / d);
  y2 = y0 + (dy * a / d);

  /* Determine the distance from point 2 to either of the
   * intersection points.
   */
  h = Math.sqrt((r0 * r0) - (a * a));

  /* Now determine the offsets of the intersection points from
   * point 2.
   */
  rx = -dy * (h / d);
  ry = dx * (h / d);

  /* Determine the absolute intersection points. */
  var xi = x2 + rx;
  var xi_prime = x2 - rx;
  var yi = y2 + ry;
  var yi_prime = y2 - ry;

  return [xi, xi_prime, yi, yi_prime];
}
svg {
  background-color: wheat;
}
<script src="https://d3js.org/d3.v5.min.js"></script>
<svg width="600" height="300"></svg>
like image 75
Gerardo Furtado Avatar answered Oct 21 '22 05:10

Gerardo Furtado