D3.js v5.0 气泡图

本文详细介绍了如何使用D3.js v5.0库创建动态气泡图,包括数据绑定、坐标映射、形状绘制、颜色分配及交互功能的实现,适合数据可视化初学者及进阶者参考。
摘要由CSDN通过智能技术生成

在这里插入图片描述
在这里插入图片描述

{
  "name": "bubble",
  "children": [
    {
      "name": "Atlas",
      "description": "Atlas of Global Agriculture",
      "children": [
        {
          "name": "",
          "address": "",
          "note": ""
        },
      ]
    },
  ]
}
/* eslint-disable no-param-reassign */
import * as d3 from 'd3';

export default function bubbleChart(id, dataset) {

  /** 宽 */
  let w = window.innerWidth * 0.68 * 0.95;
  /** 高 */
  let h = Math.ceil(w);
  /** 半径 */
  let oR = 0;
  /** dataset的大气泡数 */
  let nTop = 0;

  const colVals = d3.scaleOrdinal(d3.schemeCategory10);

  const svgContainer = d3.select('#mainBubble').style('height', `${h}px`);

  const svg = d3
    .select(id)
    .append('svg')
    .attr('class', 'mainBubbleSVG')
    .attr('width', w)
    .attr('height', h)
    .on('mouseleave', function() {
      return resetBubbles();
    });
  // 添加大气泡
  const bubbleObj = svg
    .selectAll('.topBubble')
    .data(dataset.children)
    .enter()
    .append('g')
    .attr('id', (d, i) => {
      return `topBubbleAndText_${i}`;
    });
  nTop = dataset.children.length;
  oR = w / (1 + 3 * nTop);
  h = Math.ceil((w / nTop) * 2);
  svgContainer.style('height', `${h}px`);

  bubbleObj
    .append('circle')
    .attr('class', 'topBubble')
    .attr('id', (d, i) => {
      return `topBubble${i}`;
    })
    .attr('r', () => {
      return oR;
    })
    .attr('cx', (d, i) => {
      return oR * (3 * (1 + i) - 1);
    })
    .attr('cy', (h + oR) / 3)
    .style('fill', (d, i) => {
      return colVals(i);
    })
    .style('opacity', 0.3)
    .on('mouseover', (d, i) => {
      return activateBubble(d, i);
    });
  // 气泡中的文字
  bubbleObj
    .append('text')
    .attr('class', 'topBubbleText')
    .style('fill', (d, i) => {
      return colVals(i);
    })
    .attr('x', (d, i) => {
      return oR * (3 * (1 + i) - 1);
    })
    .attr('y', (h + oR) / 3)
    .attr('font-size', 12)
    .attr('text-anchor', 'middle')
    .text(d => d.name);

  // 添加子气泡
  dataset.children.map((value, index) => {
    return renderChildBubble(value.children, index);
  });
  function renderChildBubble(value, index) {
    const childBubbleObj = svg
      .selectAll('.childBubble')
      .data(value)
      .enter()
      .append('g');
    childBubbleObj
      .append('circle')
      .attr('class', `childBubble${index}`)
      .attr('r', () => {
        return oR / 3;
      })
      .attr('cx', (d, i) => {
        return oR * (3 * (index + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('cy', (d, i) => {
        return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
      })
      .style('opacity', 0.5)
      .style('fill', colVals(index));
    childBubbleObj
      .append('text')
      .attr('class', `childBubbleText${index}`)
      .attr('x', function(d, i) {
        return oR * (3 * (index + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('y', function(d, i) {
        return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('text-anchor', 'middle')
      .attr('font-size', 6)
      .style('opacity', 0.5)
      .text(d => d.name);
  }

  function activateBubble(d, i) {
    const t = svg.transition().duration(d3.event.altKey ? 7500 : 350);
    t.selectAll('.topBubble')
      .attr('cx', (_, ii) => {
        if (i === ii) {
          // Nothing to change
          return oR * (3 * (1 + ii) - 1) - 0.6 * oR * (ii - 1);
        } else {
          // Push away a little bit
          if (ii < i) {
            // left side
            return oR * 0.6 * (3 * (1 + ii) - 1);
          }
          // right side
          return oR * (nTop * 3 + 1) - oR * 0.6 * (3 * (nTop - ii) - 1);
        }
      })
      .attr('r', (_, ii) => {
        if (i === ii) return oR * 1.8;
        return oR * 0.8;
      });

    t.selectAll('.topBubbleText')
      .attr('x', (_, ii) => {
        if (i === ii) {
          // Nothing to change
          return oR * (3 * (1 + ii) - 1) - 0.6 * oR * (ii - 1);
        } else {
          // Push away a little bit
          if (ii < i) {
            // left side
            return oR * 0.6 * (3 * (1 + ii) - 1);
          }
          // right side
          return oR * (nTop * 3 + 1) - oR * 0.6 * (3 * (nTop - ii) - 1);
        }
      })
      .attr('font-size', (_, ii) => {
        if (i === ii) return 30;
        return 12 * 0.8;
      });

    let signSide = -1;
    for (let k = 0; k < nTop; k += 1) {
      signSide = 1;
      mapBubble(t, k, i, signSide);
    }
  }
  function mapBubble(t, k, a, signSide) {
    if (k < nTop / 2) {
      signSide = 1;
    }
    t.selectAll(`.childBubbleText${k}`)
      .attr('x', (d, i) => {
        return (
          oR * (3 * (k + 1) - 1) -
          0.6 * oR * (k - 1) +
          signSide * oR * 2.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926)
        );
      })
      .attr('y', (d, i) => {
        return (h + oR) / 3 + signSide * oR * 2.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('font-size', () => {
        return k === a ? 12 : 6;
      })
      .style('opacity', () => {
        return k === a ? 1 : 0;
      });

    t.selectAll(`.childBubble${k}`)
      .attr('cx', (d, i) => {
        return (
          oR * (3 * (k + 1) - 1) -
          0.6 * oR * (k - 1) +
          signSide * oR * 2.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926)
        );
      })
      .attr('cy', (d, i) => {
        return (h + oR) / 3 + signSide * oR * 2.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('r', () => {
        return k === a ? oR * 0.55 : oR / 3.0;
      })
      .style('opacity', function() {
        return k === a ? 1 : 0;
      });
  }
  function resetBubbles() {
    w = window.innerWidth * 0.68 * 0.95;
    oR = w / (1 + 3 * nTop);

    h = Math.ceil((w / nTop) * 2);
    svgContainer.style('height', `${h}px`);

    svg.attr('width', w);
    svg.attr('height', h);

    const t = svg.transition().duration(650);

    t.selectAll('.topBubble')
      .attr('r', () => {
        return oR;
      })
      .attr('cx', (d, i) => {
        return oR * (3 * (1 + i) - 1);
      })
      .attr('cy', (h + oR) / 3);

    t.selectAll('.topBubbleText')
      .attr('font-size', 12)
      .attr('x', function(d, i) {
        return oR * (3 * (1 + i) - 1);
      })
      .attr('y', (h + oR) / 3);

    for (let k = 0; k < nTop; k += 1) {
      restmapBubble(t, k);
    }
  }
  function restmapBubble(t, k) {
    t.selectAll(`.childBubbleText${k}`)
      .attr('x', function(d, i) {
        return oR * (3 * (k + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('y', function(d, i) {
        return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('font-size', 6)
      .style('opacity', 0.5);

    t.selectAll(`.childBubble${k}`)
      .attr('r', () => {
        return oR / 3.0;
      })
      .style('opacity', 0.5)
      .attr('cx', (d, i) => {
        return oR * (3 * (k + 1) - 1) + oR * 1.5 * Math.cos((((i - 1) * 45) / 180) * 3.1415926);
      })
      .attr('cy', (d, i) => {
        return (h + oR) / 3 + oR * 1.5 * Math.sin((((i - 1) * 45) / 180) * 3.1415926);
      });
  }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值