Skip to content

Latest commit

 

History

History
234 lines (216 loc) · 6.33 KB

200303.md

File metadata and controls

234 lines (216 loc) · 6.33 KB

d3 画完整柱状图

需求

  1. 有刻度,且可以根据数据变化
  2. 柱状图可以根据数据变化

demo

柱状图

需要渲染的数据

let data = [
    { name:"吃饭", value: 300, },
    { name:"购物", value: 100, },
    { name:"交通", value: 500, },
    { name:"罚款", value: 200, },
    { name:"投资", value: 700, }
];

图形配置设定

let config = {
    warpW: 500, // svg 画布宽
    warpH: 400, // svg 画布高
    warpBg: "#faf0b1", // svg 画布背景颜色
    wrapPadding: 30, // svg 画布的内边距
    barW: 30, // 柱状图宽
};

画完整柱状图

1. 在选定的 dom 元素下初始化 svg 画布

注意:dom 元素一定要设定宽高,和 echarts 一样

function initD3(ele) {
    let _svgEle = d3.select(ele).append("svg");
    _svgEle
        .attr("width", "100%")
        .attr("height", "100%")
        .attr("style", `background: ${config.warpBg};`);
    return _svgEle;
}
let svgEle = initD3(".svg-box");

2. 得到比例尺

  1. 用来确定坐标轴的刻度
  2. 用来确定柱状图的相对高度以及 x 轴的相对位置
/**
    * @fn 获得 x 轴 y 轴的刻度,以及y轴最大值
    * @param data<array>: 需要渲染的数组
    * @param option<object>: 渲染参数
    * @return <array>: 返回 x 轴比例,y轴比例,最大值
*/
function getScale(data, option) {
    let nameArr = data.map(val => val.name);
    let valueArr = data.map(val => val.value);
    let maxValue = d3.max(valueArr);
    let xScale = d3.scaleBand()
        .domain(nameArr)
        .rangeRound([0, (option.warpW - option.wrapPadding * 2)]);
    // 渲染y轴,线性
    let yScale = d3.scaleLinear()
        .domain([0, maxValue])
        .rangeRound([(option.warpH - option.wrapPadding * 2), 0]);
    return [
        xScale,
        yScale,
        maxValue,
    ];
}
let [xScale, yScale, maxValue] = getScale(data, option);

3. 通过比例尺画出坐标轴

/**
* @fn 渲染 x 轴 y 轴
* @param data<array>: 需要渲染的数组
* @param svg<obj>: svg 画布对象
* @param option<object>: 渲染参数
* @param xScale<function>: x轴比例尺
* @param yScale<function>: y轴比例尺
*/
function renderAxis(data, svgEle, config, xScale, yScale) {
// 渲染x轴,离散型
let xAxis = d3.axisBottom().scale(xScale);  
svgEle.selectAll("g").remove();
svgEle
    .append("g")
    .attr(
        "transform",
        `translate(
            ${config.wrapPadding},
            ${config.warpH - config.wrapPadding}
        )`
    )
    .call(xAxis);
let yAxis = d3.axisLeft().scale(yScale);
svgEle
    .append("g")
    .attr(
        "transform",
        `translate(
            ${config.wrapPadding},
            ${config.wrapPadding}
        )`
    )
    .call(yAxis);
}
renderAxis(data, svgEle, option, xScale, yScale)

  1. svgEle.selectAll("g").remove(): 但需要重新渲染时将所有坐标轴删除,然后在渲染

4. 通过比例尺渲染柱状图

/**
* @fn 渲染柱状图
* @param svgEle<object>: svg 画布对象
* @param data<array>: 需要渲染的数据
* @param xScale<function>: x轴比例尺
* @param yScale<function>: y轴比例尺
* @param option<object>: 渲染参数
* @param maxValue<number>: 渲染数据最大值
*/
function renderBar(svgEle, data, xScale, yScale, option, maxValue) {
    let type = "rect";
    let updateBar = svgEle.selectAll(type).data(data);
    updateBar
        .attr("width", option.barW)
        .attr("height", val => {
            return yScale(maxValue - val.value);
        })
        .attr("fill", "red")
        .attr("x", (val, i) => {
            return xScale(val.name)
                + option.wrapPadding
                + (xScale.bandwidth() -  option.barW) / 2;
        }).attr("y", val => {
            return option.warpH
                - yScale(maxValue - val.value)
                - option.wrapPadding;
        });

    // 数据大于元素节点,初次渲染
    let enterBar = updateBar.enter();
    // 处理数据大于元素时的情况
    enterBar.append(type)
        .attr("width", option.barW)
        .attr("height", val => {
            return yScale(maxValue - val.value);
        })
        .attr("fill", "red")
        .attr("x", (val, i) => {
            return xScale(val.name)
                + option.wrapPadding
                + (xScale.bandwidth() -  option.barW) / 2;
        }).attr("y", val => {
            return option.warpH
                - yScale(maxValue - val.value)
                - option.wrapPadding;
        });
    // 元素节点大于数据
    let exitBar = updateBar.exit();
    exitBar.remove();
}
renderBar(svgEle, data, xScale, yScale, option, maxValue);

5. 渲染柱状图说明文字

/**
* @fn 渲染文字说明
* @param svgEle<object>: svg 画布对象
* @param data<array>: 需要渲染的数据
* @param xScale<function>: x轴比例尺
* @param yScale<function>: y轴比例尺
* @param option<object>: 渲染参数
* @param maxValue<number>: 渲染数据最大值
*/
function renderTxt(svgEle, data, xScale, yScale, option, maxValue) {
    // 渲染节点说明
    let updateTxt = svgEle.selectAll(".name").data(data);
    updateTxt
        .attr("x", (val, i) => {
            return xScale(val.name)
            + option.wrapPadding
            + (xScale.bandwidth() -  option.barW) / 2
            +  option.barW / 2;
        })
        .attr("y", val => {
            return option.warpH
            - yScale(maxValue - val.value)
            - option.wrapPadding;
        })
        .text(data => {
            return data.value;
        })
        .attr("text-anchor", "middle")
        .attr("fill", "#080");
    let enterTxt = updateTxt.enter();
    enterTxt.append("text")
        .attr("class", "name")
        .attr("x", (val, i) => {
            return xScale(val.name)
            + option.wrapPadding
            + (xScale.bandwidth() -  option.barW) / 2
            +  option.barW / 2;
        })
        .attr("y", val => {
            return option.warpH
                - yScale(maxValue - val.value)
                - option.wrapPadding;
        })
        .text(data => {
            return data.value;
        })
        .attr("text-anchor", "middle").attr("fill", "#080");
    // 删除多余元素
    let exitTxt = updateTxt.exit();
    exitTxt.remove();
}
renderTxt(svgEle, data, xScale, yScale, option, maxValue);