D3 - 箱型圖

箱型圖(Boxplot)

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

在這裏插入圖片描述

箱型圖,是一種用於顯示一組數據分散情況資料的統計圖,即每一個box表示的是一組數據。
通常採用五個數值來概括這一組數據,即:中位數Q2Q2,下四分位數Q1Q1,上四分位數Q3Q3,上限和下限。
其中,Q2={a(n+1)/2,oddan/2+an/2+12,evenQ2=\left\{\begin{aligned}&a_{(n+1)/2},odd\\&\frac{a_{n/2}+a_{n/2+1}}{2},even \end{aligned}\right.Q1=a1(n+1)4Q1=a_{\frac{1*(n+1)}{4}}Q3=a3(n+1)4Q3=a_{\frac{3*(n+1)}{4}}IQR=Q3Q1IQR=Q3-Q1,上限爲不超過Q3+1.5IQRQ3+1.5*IQR的最大值,下限爲不低於Q11.5IQRQ1-1.5*IQR的最小值,超出上下限的數值均可看作異常值。

數據均來自網站:
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。樸素實現該過程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章