d3 / d3-zoom

Pan and zoom SVG, HTML or Canvas using mouse or touch input.
https://d3js.org/d3-zoom
ISC License
507 stars 143 forks source link

Hide pointer events for items outside (layers lower) than current zoom #165

Closed emilynielson closed 5 years ago

emilynielson commented 5 years ago

I have a mouseover event that shows the labels for each item in the path above the sunburst chart, but I want to hide the mouseover for items outside current zoom (items that are children of current zoom). I tried setting pointer-events to none then all and it works for my chart if I have 4 total layers, but if I have more than that than it doesn't work. Any ideas would be greatly appreciated. And that you so much, your demo has been so awesome and helpful!

createRadialChart(view) {
const element = this.chartContainer.nativeElement;

const width = 750;
const height = 932;

const radius = Math.min(width - 500, height - 200) / 2;

const partition = data => {
  const root = d3.hierarchy(data)
      .sum(d => d.size)
      .sort((a, b) => b.value - a.value);
  return d3.partition()
      .size([2 * Math.PI, root.height + 1])
    (root);
};

const format = d3.format(',d');

const arc = d3.arc<{x0: any, x1: any, y0: any, y1: any}>()
.startAngle(d => d.x0)
.endAngle(d => d.x1)
.padAngle(d => Math.min((d.x1 - d.x0) / 2, 0.005))
.padRadius(radius * 1.5)
.innerRadius(d => d.y0 * radius)
.outerRadius(d => Math.max(d.y0 * radius, d.y1 * radius - 1));

const root = partition(this.data);

root.each(d => d['current'] = d);

this.svg = d3.select(element).append('svg')
.attr('width', width + this.margin.left + this.margin.right)
.attr('height', height + this.margin.top + this.margin.bottom)
.style('font', '10px sans-serif')
.attr('id', 'diagram');

const g = this.svg.append('g')
.attr('transform', 'translate(' + width / 2 + ',' + width / 2 + ')');

const color = d3.scaleOrdinal().range(d3.quantize(d3.interpolateRainbow, this.data.children.length + 1));

const path = g.append('g')
  .selectAll('path')
  .data(root.descendants().slice(1))
  .enter().append('path')
  .attr('pointer-events', 'none')
    .attr('fill', d => {
      if (d.data.state === 'enabled' && d.data.availability === 'available') {
            return 'green';
          } else if (d.data.availability === 'offline' && d.data.state === 'enabled') {
            return 'red';
          } else if (d.data.availability === 'offline' && (d.data.state === 'disabled' || d.data.state === 'disabled by parent')) {
            return 'red';
          } else if (d.data.availability === 'available' && (d.data.state === 'disabled' || d.data.state === 'disabled by parent')) {
            return 'red';
          }
          if (d.data.gtm_state === 'green') {
              return 'green';
          } else if (d.data.gtm_state === 'red') {
              return 'red';
          } else if (d.data.gtm_state === 'yellow') {
              return 'yellow';
          }
          if (d._children) {
            return 'lightsteelblue';
          } else {
            return '#e2e2e0';
          } })
    .attr('fill-opacity', d => arcVisible(d.current) ? (d.children ? 0.6 : 0.4) : 0)
    .attr('d', d => arc(d.current))
    .on('mouseover', (d) => {
          const sequenceArray = d.ancestors().reverse();
          sequenceArray.shift();
          this.updateBreadcrumbs(sequenceArray);

          d3.selectAll('path').style('opacity', 0.3);

          d3.selectAll('path')
          .filter(function(node) {
            return (sequenceArray.indexOf(node) >= 0);
          })
          .style('opacity', 1);
    })
    .on('dblclick', function(d) {
      window.location.href = BASE_URL + d.data.url;
    });

path.filter(d => d.children)
    .style('cursor', 'pointer')
    .attr('pointer-events', 'all')
    .on('click', clicked);

path.append('title')
    .text(d => `${d.ancestors().map(d => d.data.name).reverse().join('/')}\n${format(d.value)}`);

const label = g.append('g')
    .attr('pointer-events', 'none')
    .attr('text-anchor', 'middle')
    .style('user-select', 'none')
  .selectAll('text')
  .data(root.descendants().slice(1))
  .enter().append('text')
    .attr('dy', '0.35em')
    .attr('fill-opacity', d => +labelVisible(d.current))
    .attr('transform', d => labelTransform(d.current, radius))
    .text(d => d.data.name)
    .style('visibility', function(d) { // <-- set it initially
      if (view === 'individual') {
        return (d.depth === 0 || d.depth === 1 ) ? 'visible' : 'hidden';
      } else {
        return (d.depth === 0 || d.depth === 1 || d.depth === 2 ) ? 'visible' : 'hidden';
      }
    });

const parent = g.append('circle')
    .datum(root)
    .attr('r', radius)
    .attr('fill', 'none')
    .attr('pointer-events', 'all')
    .on('click', clicked);

  g.append('text')
    .datum(root)
    .attr('r', radius)
    .text(d => d.data.name)
    .attr('text-anchor', 'middle');

function clicked(p) {
  parent.datum(p.parent || root);

  root.each(d => d['target'] = {
    x0: Math.max(0, Math.min(1, (d.x0 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
    x1: Math.max(0, Math.min(1, (d.x1 - p.x0) / (p.x1 - p.x0))) * 2 * Math.PI,
    y0: Math.max(0, d.y0 - p.depth),
    y1: Math.max(0, d.y1 - p.depth)
  });

  const t = g.transition().duration(750);

  // Transition the data on all arcs, even the ones that aren’t visible,
  // so that if this transition is interrupted, entering arcs will start
  // the next transition from the desired position.
  path.transition(t)
      .tween('data', d => {
        const i = d3.interpolate(d.current, d.target);
        return t => d.current = i(t);
      })
    .attr('pointer-events', 'none')
    .filter(function(d) {
      return +this.getAttribute('fill-opacity') || arcVisible(d.target);
    })
      .attr('fill-opacity', d => arcVisible(d.target) ? (d.children ? 0.6 : 0.4) : 0)
      .attrTween('d', d =>  () => arc(d.current))
      .attr('pointer-events', 'all');

  label.filter(function(d) {
      return +this.getAttribute('fill-opacity') || labelVisible(d.target);
    }).transition(t)
      .attr('fill-opacity', d => +labelVisible(d.target))
      .attrTween('transform', d => () => labelTransform(d.current, radius));
}

function arcVisible(d) {
  return d.y1 <= 3 && d.y0 >= 1 && d.x1 > d.x0;
}

function labelVisible(d) {
  return d.y1 <= 3 && d.y0 >= 1 && (d.y1 - d.y0) * (d.x1 - d.x0) > 0.03;
}

function labelTransform(d, radius) {
  const x = (d.x0 + d.x1) / 2 * 180 / Math.PI;
  const y = (d.y0 + d.y1) / 2 * radius;
  return `rotate(${x - 90}) translate(${y},0) rotate(${x < 180 ? 0 : 180})`;
}

this.initializeBreadcrumbTrail();

return this.svg.node();

}

mbostock commented 5 years ago

Please use Stack Overflow tag d3.js to ask for help. Stack Overflow provides a better collaborative forum: thousands of D3-related questions have been asked there, and some answers may be relevant to you.

When asking for help, please include a link to demonstrate the issue, preferably as an Observable notebook. It is often impossible to debug from code snippets alone. Isolate the issue and reduce your code as much as possible before asking for help. The less code you post, the easier it is for someone to debug, and the more likely you are to get a helpful response.

If you have a question about D3’s behavior and want to discuss it with other users, also consider the d3-js Google Group or joining the d3-js Slack.

Thank you! 🤗