D3 - 箱型图

箱型图(Boxplot)

箱型图概念解释:https://blog.csdn.net/aijiudu/article/details/89387328

在这里插入图片描述

箱型图,是一种用于显示一组数据分散情况资料的统计图,即每一个box表示的是一组数据。
通常采用五个数值来概括这一组数据,即:中位数 Q 2 Q2 Q2,下四分位数 Q 1 Q1 Q1,上四分位数 Q 3 Q3 Q3,上限和下限。
其中, Q 2 = { a ( n + 1 ) / 2 , o d d a n / 2 + a n / 2 + 1 2 , e v e n Q2=\left\{\begin{aligned}&a_{(n+1)/2},odd\\&\frac{a_{n/2}+a_{n/2+1}}{2},even \end{aligned}\right. Q2=a(n+1)/2,odd2an/2+an/2+1,even Q 1 = a 1 ∗ ( n + 1 ) 4 Q1=a_{\frac{1*(n+1)}{4}} Q1=a41(n+1) Q 3 = a 3 ∗ ( n + 1 ) 4 Q3=a_{\frac{3*(n+1)}{4}} Q3=a43(n+1) I Q R = Q 3 − Q 1 IQR=Q3-Q1 IQR=Q3Q1,上限为不超过 Q 3 + 1.5 ∗ I Q R Q3+1.5*IQR Q3+1.5IQR的最大值,下限为不低于 Q 1 − 1.5 ∗ I Q R Q1-1.5*IQR Q11.5IQR的最小值,超出上下限的数值均可看作异常值。

数据均来自网站:
https://github.com/pearmini/d3-barchart-demo?tdsourcetag=s_pctim_aiomsg
https://github.com/datasets/population

效果图:
在这里插入图片描述

代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>BoxPlot</title>
</head>
<body>

<button onClick="display()">show</button>

<select id="ID">
    <option value="0">Death(%)</option>
    <option value="1">Cure(%)</option>
    <option value="2">Rate(per 10,000)</option>
</select>

<script src="https://d3js.org/d3.v5.js"></script>
<script>

    let w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
    let h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight;
    w *= 0.98;
    h *= 0.9;
    const inf = 1e9 + 10;
    let inner = {top: 50, right: 50, bottom: 50, left: 50};
    let W = w - inner.left - inner.right;
    let H = h - inner.top - inner.bottom;
    let Data = new Array();
    Initialize();

    function display() {

        let x = document.getElementById("ID");
        let T = parseInt(x.value);
        let DateData = new Array();
        let NumData = new Array();
        let Num = new Array();
        let cnt = 0;

        //不同的选项,排序的优先级不同
        Data.sort(function (a, b) {
            let stra = new Date(a.date), strb = new Date(b.date);
            if (stra.getTime() === strb.getTime()) {
                if (T === 0) return a.DeathRate - b.DeathRate;
                if (T === 1) return a.CuredRate - b.CuredRate;
                if (T === 2) return a.ComfirmedRate - b.ComfirmedRate;
            } else return stra.getTime() - strb.getTime();
        });

        for (let i = 0; i < Data.length; ++i) {
            if (i === 0 || Data[i].date !== Data[i - 1].date) {
                if (i !== 0) cnt++;
                DateData[cnt] = new Date(Data[i].date);
                NumData[cnt] = new Array();
            }
            if (T === 0) NumData[cnt].push(Data[i].DeathRate);
            if (T === 1) NumData[cnt].push(Data[i].CuredRate);
            if (T === 2) NumData[cnt].push(Data[i].ComfirmedRate);
        }

        ++cnt;

        // 计算每组数据的Q1 Q2 Q3等
        for (let i = 0; i < cnt; ++i) {
            NumData[i].sort(function (a, b) {
                return a - b;
            });
            let n = NumData[i].length;
            let Q1, Q2, Q3, U, D;
            Q1 = NumData[i][Math.round(n * 0.25)];
            Q3 = NumData[i][Math.round(n * 0.75)];
            if (n % 2 === 0) Q2 = (NumData[i][Math.ceil(n * 0.5)] + NumData[i][Math.floor(n * 0.5)]) * 0.5;
            else Q2 = NumData[i][Math.round(n * 0.5)];
            // alert(Q2);
            U = 0;
            D = inf;
            let L = Q1 - 1.5 * (Q3 - Q1);
            let R = Q3 + 1.5 * (Q3 - Q1);
            for (let j = 0; j < NumData[i].length; ++j) {
                if (NumData[i][j] >= L && NumData[i][j] <= R) {
                    U = Math.max(U, NumData[i][j]);
                    D = Math.min(D, NumData[i][j]);
                }
            }
            Num[i] = {Q1: Q1, Q2: Q2, Q3: Q3, U: U, D: D};
        }

        console.log(Num);

        let xScale = d3.scaleTime()
            .domain([DateData[0], DateData[cnt - 1].setDate(DateData[cnt - 1].getDate() + 1)])
            .range([0, w - inner.left - inner.right]);

        let xAxis = d3.axisBottom(xScale).ticks(DateData.length);

        let y1 = 100;

        if (T === 0) y1 = 50;
        if (T === 1) y1 = 50;
        if (T === 2) y1 = 3.5;

        // 横轴时间轴
        let yScale = d3.scaleLinear()
            .domain([0, y1])
            .range([h - inner.top - inner.bottom, 0])
            .nice();

        let yAxis = d3.axisLeft(yScale);

        let svg;

        d3.select("body")
            .selectAll("svg", function (d) {
                if (d.id === "123") return this;
            })
            .remove();

        svg = d3.select("body")
            .append("svg", id = "123")
            .attr("width", w)
            .attr("height", h)
            .append("g");

        svg.append("g")
            .attr("transform", "translate(" + inner.left + "," + (h - inner.bottom) + ")")
            .call(xAxis.tickFormat(d3.timeFormat("%m-%d")));

        svg.append("g")
            .attr("transform", "translate(" + inner.left + "," + (inner.top) + ")")
            .call(yAxis);

        svg.selectAll("rect")
            .data(Num)
            .enter()
            .append("rect")
            .attr("x", function (d, i) {
                return (i + 0.2) * (W / cnt) + inner.left;
            })
            .attr("y", function (d, i) {
                let tot = d.Q3 / y1 * H;
                return inner.top + (H - tot);
            })
            .attr("width", function (d, i) {
                return W / cnt * 0.6;
            })
            .attr("height", function (d, i) {
                return (d.Q3 - d.Q1) / y1 * H
            })
            .attr("fill", 'yellow')
            .attr("stroke", 'black');

        // 画线函数
        function draw(x1, y1, x2, y2, l = 1, c = "black") {
            svg.append("line")
                .attr("x1", x1)
                .attr("y1", y1)
                .attr("x2", x2)
                .attr("y2", y2)
                .attr("stroke-width", l)
                .attr("stroke", c);
        }

        for (let i = 0; i < cnt; ++i) {
            let x1 = (i + 0.2) * (W / cnt) + inner.left;
            let x2 = x1 + (W / cnt) * 0.6;
            let y;
            let tot = Num[i].Q2 / y1 * H;
            y = inner.top + (H - tot);
            draw(x1, y, x2, y, 2);
            tot = Num[i].U / y1 * H;
            y = inner.top + (H - tot);
            // alert(y);
            draw(x1, y, x2, y, 1, "red");
            draw((x1 + x2) * 0.5, inner.top + (H - Num[i].Q3 / y1 * H),
                (x1 + x2) * 0.5, inner.top + (H - Num[i].U / y1 * H));
            tot = Num[i].D / y1 * H;
            y = inner.top + (H - tot);
            draw(x1, y, x2, y);
            draw((x1 + x2) * 0.5, inner.top + (H - Num[i].Q1 / y1 * H),
                (x1 + x2) * 0.5, inner.top + (H - Num[i].D / y1 * H));
        }

    }

    function Initialize() {
        // ...
    }

</script>
</body>
</html>

代码中未包含Initialize初始化数据部分。

采用的一个矩形块+几条线段来确定每一个box。朴素实现该过程。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值