基于D3.js实现柱状图、折线图、饼图 、玫瑰图

255 阅读2分钟

一、坐标轴

连续性坐标轴

image.png

const width = 800
const height = 600
const x = d3.scaleLinear().range([0, width])
const scale = x.domain([0, 10])
 // x.domain([d3.max(data, (d) => d.value),d3.min(data, (d) => d.value)])
 // x.domain(d3.extent(data,(d) => d.value))
// d3.axisTop、d3.axisBottom() 控制坐标轴显示 上方或者下方
const axis = svg.append('g')
               .attr('class', 'axis')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(scale))

连续性时间型坐标轴

image.png

const width = 800
const height = 600
const x = d3.scaleTime().range([0, width])
const scale = x.domain([new Date(2022, 1), new Date(2022, 8)])
const axis = svg.append('g')
               .attr('class', 'axis')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(scale))

不从零开始的非连续性坐标轴

image.png

const width = 800
const height = 600
const x = d3.scaleBand().range([0, width])
const scale = x.domain(['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'])
const axis = svg.append('g')
               .attr('class', 'axis')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(scale))

从零开始的非连续性坐标轴

image.png

const width = 800
const height = 600
const x = d3.scaleOrdinal().range([150, 300, 450, 600, 750, 900, 1050]);
const scale = x.domain(['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'])
const axis = svg.append('g')
               .attr('class', 'axis')
               .attr('transform', `translate(0, ${height})`)
               .call(d3.axisBottom(xScale))

其他设置

• axis.scale – 为坐标轴设置比例尺
• axis.ticks – 定义坐标轴刻度生成方式(默认值为)
• axis.tickArguments – 定义坐标轴刻度参数
• axis.tickValues – 设置有特殊需求的刻度值(默认值为null)
• axis.tickFormat – 设置有特殊需求的刻度格式(默认值为null)
• axis.tickSize – 设置刻度的大小
• axis.tickSizeInner – 设置除两端外所有刻度的长度(默认值为6)
• axis.tickSizeOuter - 设置两端刻度的长度(默认值为6)
• axis.tickPadding – 设置刻度与文字之间距离(默认值为3

二、柱状图

<svg id="bar"></svg>
const data = [
    { name: '星期一', value: 6 },
    { name: '星期二', value: 9 },
    { name: '星期三', value: 16 },
    { name: '星期四', value: 6 },
    { name: '星期五', value: 14 },
    { name: '星期六', value: 12 },
    { name: '星期日', value: 18 },
];
const margin = {
    top: 50,
    left: 50,
    bottom: 50,
    right: 50,
};
const width = 700;
const height = 500;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const initBar = () => {
    const svg = d3
      .selectAll('#bar')
      .attr('width', width)
      .attr('height', height);
    xScale = d3
      .scaleBand()
      .padding(0.5)
      .range([0, innerWidth])
      .domain(data.map((n) => n.name));
    yScale = d3
      .scaleLinear()
      .domain([d3.max(data, (d) => d.value), 0])
      .range([0, innerHeight]);

    const g = svg
      .append('g')
      .attr('transform', `translate(${margin.left},${margin.top})`);
    svg
      .append('g') // 输出标题
      .attr('class', 'bar--title')
      .append('text')
      .attr('fill', '#000')
      .attr('font-size', '16px')
      .attr('font-weight', '700')
      .attr('text-anchor', 'middle')
      .attr('x', '50%')
      .attr('y', 40)
      .text('本周酒店房间空置率');
    g.append('g')
      .attr('class', 'y-title')
      .attr('transform', `translate(-30,-10)`)
      .append('text')
      .text('空置率 (%)');

    // 添加x轴
    g.append('g')
      .attr('class', 'axis axis--x')
      .attr('transform', `translate(0,${innerHeight})`)
      .call(d3.axisBottom(xScale));
    // 添加y轴
    g.append('g')
      .attr('class', 'axis axis--y')
      .call(d3.axisLeft(yScale).tickFormat((n) => n + '%'));
    // 设置tip
    let tip = d3Tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function (d) {
        const _data = d.target.__data__;
        return (
          '<strong>' +
          _data.name +
          "<br>空置率:</strong> <span style='color:#ffeb3b'>" +
          _data.value +
          '%</span>'
        );
      });
    svg.call(tip);

    g.selectAll('.bar')
      .data(data, (n) => n.name) // (n) => n.name name作为更新数据的key
      .enter()
      .append('rect')
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide)
      .attr('class', 'bar')
      .attr('fill', '#409eff')
      .attr('x', function (n) {
        return xScale(n.name);
      })
      .attr('y', function (d) {
        return yScale(d.value);
      })
      .attr('width', xScale.bandwidth())
      .attr('height', function (d) {
        return innerHeight - yScale(d.value);
      });
  };

image.png

三、折线图

<svg id="line"></svg>
const data = [
    { name: '星期一', value: 6 },
    { name: '星期二', value: 9 },
    { name: '星期三', value: 16 },
    { name: '星期四', value: 6 },
    { name: '星期五', value: 14 },
    { name: '星期六', value: 12 },
    { name: '星期日', value: 18 },
];

const margin = {
    top: 50,
    left: 50,
    bottom: 50,
    right: 50,
};
const width = 700;
const height = 500;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const initLine = () => {
    const svg = d3
      .selectAll('#line')
      .attr('width', width)
      .attr('height', height);
    // 设置tip
    const tip = d3Tip()
      .attr('class', 'd3-tip')
      .offset([-10, 0])
      .html(function (d, i) {
        console.log(d, i, 'd');
        const _data = d.target.__data__;
        return (
          '<strong>' +
          _data.name +
          "<br>空置率:</strong> <span style='color:#ffeb3b'>" +
          _data.value +
          '%</span>'
        );
      });
    svg.call(tip);
    const xAxis = d3
      .scaleBand()
      .range([0, innerWidth])
      .domain(data.map((n) => n.name));
    const yAxis = d3
      .scaleLinear()
      .domain([d3.max(data, (d) => d.value), 0])
      .range([0, innerHeight]);
    const g = svg
      .append('g')
      .attr('transform', `translate(${margin.left},${margin.top})`);

    g.append('g')
      .attr('class', 'axis axis--x')
      .attr('transform', `translate(0,${innerHeight})`)
      .call(d3.axisBottom(xAxis));
    g.append('g').attr('class', 'axis axis--y').call(d3.axisLeft(yAxis));
    const line = d3
      .line()
      .x((d) => {
        return xAxis(d.name) + xAxis.bandwidth() / 2;
      })
      .y((d) => yAxis(d.value))
      .curve(d3.curveCatmullRom);

    g.selectAll('circle')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', function (d) {
        return xAxis(d.name) + xAxis.bandwidth() / 2;
      })
      .attr('cy', function (d) {
        return yAxis(d.value);
      })
      .attr('r', 5)
      .attr('fill', function (d, i) {
        return colors[i];
      })
      .attr('style', `cursor: pointer;`)
      .on('mouseover', tip.show)
      .on('mouseout', tip.hide);

    g.append('path')
      .datum(data)
      .attr('stroke', 'green')
      .attr('stroke-width', 2)
      .attr('fill', 'none')
      .attr('d', line);
  };

image.png

四、折线区域

<svg id="area"></svg>
const data = [
    { name: '星期一', value: 6 },
    { name: '星期二', value: 9 },
    { name: '星期三', value: 16 },
    { name: '星期四', value: 6 },
    { name: '星期五', value: 14 },
    { name: '星期六', value: 12 },
    { name: '星期日', value: 18 },
];
const margin = {
    top: 50,
    left: 50,
    bottom: 50,
    right: 50,
};
const width = 700;
const height = 500;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const initArea = () => {
    const svg = d3
      .selectAll('#area')
      .attr('width', width)
      .attr('height', height);
    xScale = d3
      .scalePoint()
      .range([0, innerWidth])
      .domain(data.map((n) => n.name));
    yScale = d3
      .scaleLinear()
      .domain([d3.max(data, (d) => d.value), 0])
      .range([0, innerHeight]);

    const g = svg
      .append('g')
      .attr('transform', `translate(${margin.left},${margin.top})`);
    g.append('g')
      .attr('class', 'axis axis--x')
      .attr('transform', `translate(0,${innerHeight})`)
      .call(d3.axisBottom(xScale));
    // 添加y轴
    g.append('g')
      .attr('class', 'axis axis--y')
      .call(d3.axisLeft(yScale).tickFormat((n) => n + '%'));

    const line = d3
      .line()
      .x((d) => xScale(d.name))
      .y((d) => yScale(d.value));

    const area = d3
      .area()
      .x((d) => xScale(d.name))
      .y1((d) => yScale(d.value))
      .y0(yScale(0));

    g.append('g')
      .append('path')
      .style('fill', 'none')
      .style('fill', 'green')
      .style('stroke', 'rgb(51, 209, 243)')
      .style('stroke-width', 1)
      .datum(data)
      .attr('d', area);
  };

image.png

五、饼图、玫瑰图

<svg id="pie"></svg>
const margin = {
    top: 50,
    left: 50,
    bottom: 50,
    right: 50,
};
const width = 700;
const height = 500;
const innerWidth = width - margin.left - margin.right;
const innerHeight = height - margin.top - margin.bottom;

const initPie = () => {
    const data = [
      { name: '外包', value: 3000 },
      { name: '金融', value: 4754 },
      { name: '制造', value: 1120 },
      { name: '咨询', value: 4032 },
    ];

    const svg = d3
      .selectAll('#pie')
      .attr('width', width)
      .attr('height', height);

    const pieData = d3.pie().value((d) => d.value)(data);
    // const allCount = data.reduce((pre, cur) => pre + cur.value, 0);
    const arc = d3
      .arc()
      .innerRadius(0)
      .outerRadius((d) => {
        return 200;
        // return (d.value / allCount) * 100 + 100; 玫瑰图
      });
    const color = d3.scaleOrdinal(d3.schemeCategory10);

    const arcGroup = svg
      .append('g')
      .attr('transform', 'translate(300, 300)')
      .selectAll('path')
      .data(pieData);

    arcGroup
      .join('path')
      .attr('fill', (d, i) => color(i))
      .attr('d', arc);

    arcGroup
      .join('text')
      .text((d) => d.data.name)
      .attr('transform', function (d) {
        const angle = (d.endAngle * 180) / Math.PI;
        const dAngle = ((d.endAngle - d.startAngle) * 180) / Math.PI;
        let tAngle = angle - dAngle / 2;
        let tx = 40;
        if (tAngle > 270) {
          tAngle = tAngle - 275;
          tx = -70;
        } else {
          tAngle = tAngle - 85;
        }
        return `rotate(${tAngle})
        translate(${tx})`;
      });
  };

image.png