D3中径向图的条件填充

dsa*_*ice 6 javascript d3.js

当我想从同一数组(rawdata2)绘制两列(即 VALUE1 和 VALUE2)时,我可以计算负面积和正面积以及交叉点。

\n

\r\n
\r\n
rawdata = [   {     "DATE_TIME": "2018-10-31T23:20:00.000Z",     "VALUE1": 102,     "VALUE2": 118,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 20   },   {     "DATE_TIME": "2018-10-31T23:20:00.000Z",     "VALUE1": 110,     "VALUE2": 95,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 20   },   {     "DATE_TIME": "2018-10-31T23:40:00.000Z",     "VALUE1": 153,     "VALUE2": 117,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 40   },   {     "DATE_TIME": "2018-10-31T23:40:00.000Z",     "VALUE1": 125,     "VALUE2": 98,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 40   },   {     "DATE_TIME": "2018-11-01T00:00:00.000Z",     "VALUE1": 112,     "VALUE2": 117,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 60   },   {     "DATE_TIME": "2018-11-01T00:00:00.000Z",     "VALUE1": 120,     "VALUE2": 100,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 60   },   {     "DATE_TIME": "2018-11-01T03:20:00.000Z",     "VALUE1": 147,     "VALUE2": 108,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 260   },   {     "DATE_TIME": "2018-11-01T03:20:00.000Z",     "VALUE1": 130,     "VALUE2": 98,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 260   },   {     "DATE_TIME": "2018-11-01T06:30:00.000Z",     "VALUE1": 152,     "VALUE2": 111,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 450   },   {     "DATE_TIME": "2018-11-01T06:30:00.000Z",     "VALUE1": 140,     "VALUE2": 101,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 450   },   {     "DATE_TIME": "2018-11-01T08:00:00.000Z",     "VALUE1": 112,     "VALUE2": 101,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 540   },   {     "DATE_TIME": "2018-11-01T08:00:00.000Z",     "VALUE1": 120,     "VALUE2": 90,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 540   },   {     "DATE_TIME": "2018-11-01T11:00:00.000Z",     "VALUE1": 122,     "VALUE2": 100,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 720   },   {     "DATE_TIME": "2018-11-01T11:00:00.000Z",     "VALUE1": 130,     "VALUE2": 95,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 720   },   {     "DATE_TIME": "2018-11-01T15:00:00.000Z",     "VALUE1": 112,     "VALUE2": 101,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 960   },   {     "DATE_TIME": "2018-11-01T15:00:00.000Z",     "VALUE1": 125,     "VALUE2": 98,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 960   },   {     "DATE_TIME": "2018-11-01T17:20:00.000Z",     "VALUE1": 147,     "VALUE2": 108,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 1100   },   {     "DATE_TIME": "2018-11-01T17:20:00.000Z",     "VALUE1": 129,     "VALUE2": 102,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 1100   },   {     "DATE_TIME": "2018-11-01T20:59:00.000Z",     "VALUE1": 151,     "VALUE2": 102,     "INSPECTION": 1,     "STATE": 1,     "minuteTime": 1319   },   {     "DATE_TIME": "2018-11-01T20:59:00.000Z",     "VALUE1": 132,     "VALUE2": 90,     "INSPECTION": 1,     "STATE": 0,     "minuteTime": 1319   } ]\nconst width = 954;\nconst height = width;\nconst margin = 5;\nconst innerRadius = width / 10;\nconst outerRadius = width / 4 - margin;\n\n\n\n          function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {\n        // if the lines intersect, the result contains the x and y of the intersection (treating the lines as infinite) and booleans for whether line segment 1 or line segment 2 contain the point\n        var denominator, a, b, numerator1, numerator2, result = {\n            x: null,\n            y: null,\n            onLine1: false,\n            onLine2: false\n        };\n        denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));\n        if (denominator === 0) {\n            return result;\n        }\n        a = line1StartY - line2StartY;\n        b = line1StartX - line2StartX;\n        numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);\n        numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);\n        a = numerator1 / denominator;\n        b = numerator2 / denominator;\n\n        // if we cast these lines infinitely in both directions, they intersect here:\n        result.x = line1StartX + (a * (line1EndX - line1StartX));\n        result.y = line1StartY + (a * (line1EndY - line1StartY));\n        /*\n                // it is worth noting that this should be the same as:\n                x = line2StartX + (b * (line2EndX - line2StartX));\n                y = line2StartX + (b * (line2EndY - line2StartY));\n                */\n        // if line1 is a segment and line2 is infinite, they intersect if:\n        if (a > 0 && a < 1) {\n            result.onLine1 = true;\n        }\n        // if line2 is a segment and line1 is infinite, they intersect if:\n        if (b > 0 && b < 1) {\n            result.onLine2 = true;\n        }\n        // if line1 and line2 are segments, they intersect if both of the above are true\n        return result;\n    }\n// const width = 954;\n// const height = width;\n// const margin = 10;\n// const innerRadius = width / 5;\n// const outerRadius = width / 2 - margin;\n\n\n\n      let rawdata2 = rawdata.filter(d => d.INSPECTION == 1);\n\n      const minuteScale = d3.scaleLinear()\n        .domain([0, 1439]) // 0 to 23:59 (24 hours * 60 minutes without -1 or else change to 1439 //- 1)\n        .range([0, 2 * Math.PI]);\n\n      const minuteTimeValues = rawdata2.map(d => d.minuteTime);\n\n      const x = d3.scaleTime()\n        .domain([0, 1439]) // Set the domain using the extent of the "minuteTime" values\n        .range([0, 2 * Math.PI]); // Set the desired range\n\n      //console.log(rawdata)\n\n      const minValue1 = d3.min(rawdata, d => d.VALUE1);\n      const minValue2 = d3.min(rawdata, d => d.VALUE2);\n      const maxValue1 = d3.max(rawdata, d => d.VALUE1);\n      const maxValue2 = d3.max(rawdata, d => d.VALUE2);\n\n      const yMin = minValue1 < minValue2 ? minValue1 : minValue2;\n      const yMax = maxValue1 > maxValue2 ? maxValue1 : maxValue2;\n\n      const y = d3.scaleLinear()\n                  .domain([yMin, yMax])\n                  .range([innerRadius, outerRadius]);\n\n// Modify the xAxis generator to display only the hour and add padding\n\nconst xAxis = (g) => g\n  .attr("font-family", "sans-serif")\n  .attr("font-size", 7)\n  .call((g) => g.selectAll("g")\n    .data(d3.range(0, 24)) // create an array of hour values\n    .join("g")\n      .each((d, i) => d.id = `hour-${i}`)\n      .call((g) => g.append("path")\n          .attr("stroke", "#000")\n          .attr("stroke-opacity", 0.2)\n          .attr("d", (d) => `\n            M${d3.pointRadial(x(d * 60), innerRadius)}\n            L${d3.pointRadial(x(d * 60), outerRadius)}\n          `))\n      .call((g) => g.append("path")\n          .attr("id", (d) => `hour-${d}`)\n          .datum((d) => [d * 60, (d + 1) * 60])\n          .attr("fill", "none")\n          .attr("d", ([a, b]) => `\n            M${d3.pointRadial(x(a), innerRadius-10)}\n            A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}\n          `))\n      .call((g) => g.append("text")\n        .append("textPath")\n          .attr("startOffset", 6)\n          .attr("href", (d) => `#hour-${d}`)\n          .text((d) => d.toString().padStart(2, "0"))\n          .attr("text-anchor", "middle")\n          .attr("alignment-baseline", "middle")\n          .attr("transform", (d) => {\n            const angle = x(d * 60);\n            const radius = innerRadius - 20; // reduce the radius to move the labels closer to the center\n                        return `translate(${d3.pointRadial(x(d * 60), radius)})`;\n          })\n      )\n  );\n\n\n\n      const yAxis = g => g\n        .attr("text-anchor", "middle")\n        .attr("font-family", "sans-serif")\n        .attr("font-size", 7)\n        .call(g => g.selectAll("g")\n          .data(y.ticks().reverse())\n          .join("g")\n          .attr("fill", "none")\n          .call(g => g.append("circle")\n            .attr("stroke", "#000")\n            .attr("stroke-opacity", 0.1)\n            .attr("r", y))\n          .call(g => g.append("text")\n            .attr("y", d => -y(d))\n            .attr("dy", "0.35em")\n            .attr("stroke", "#fff")\n            .attr("stroke-width", 5)\n                  .attr("stroke-opacity", 0.6)\n            .text((x, i) => `${x.toFixed(0)}${i ? "" : ""}`)\n          .clone(true)\n            .attr("y", d => y(d))\n          .selectAll(function() { return [this, this.previousSibling]; })\n          .clone(true)\n            .attr("fill", "currentColor")\n            .attr("stroke", "none")));\n\n      const line = d3.lineRadial()\n                    .curve(d3.curveLinear)\n                    .angle(d => x(d.minuteTime));\n\n      const area = d3.areaRadial()\n                    .curve(d3.curveLinear)\n                    .angle(d => x(d.minuteTime))\n\n      const svg = d3.create("svg")\n        .attr("viewBox", [-width / 2, -height / 2, width, height])\n        .attr("stroke-linejoin", "round")\n        .attr("stroke-linecap", "round");\n\n      svg.append("path")\n        .attr("fill", "none")\n        .attr("stroke", "red")\n        .attr("stroke-width", 3)\n        .attr("d", line\n          .radius(d => y(d.VALUE1))\n          (rawdata2));\n\n      svg.append("path")\n        .attr("fill", "none")\n        .attr("stroke", "green")\n        .attr("stroke-width", 3)\n        .attr("d", line\n          .radius(d => y(d.VALUE2))\n          (rawdata2));\n// +++++++++++++++++++++++++++++++++++ calc crossing points ++++++++++++++++++++++++++++++++\nfunction findCrossingPoints(data) {\n  const crossingPoints = [];\n\n  for (let i = 1; i < data.length; i++) {\n    const d1 = data[i - 1];\n    const d2 = data[i];\n\n    if ((d1.VALUE1 - d1.VALUE2) * (d2.VALUE1 - d2.VALUE2) < 0) {\n      // There\'s a crossing point between the two data points\n      const t = (0 - (d1.VALUE1 - d1.VALUE2)) / ((d2.VALUE1 - d2.VALUE2) - (d1.VALUE1 - d1.VALUE2));\n      const crossingValue1 = d1.VALUE1 + t * (d2.VALUE1 - d1.VALUE1);\n      const crossingValue2 = d1.VALUE2 + t * (d2.VALUE2 - d1.VALUE2);\n      crossingPoints.push({ VALUE1: crossingValue1, VALUE2: crossingValue2 });\n    }\n  }\n\n  return crossingPoints;\n}\n\nconst crossingPoints = findCrossingPoints(rawdata2);\n   function getX(d) {\n        return Number(line.radius(d => y(d.VALUE1))([d]).match(\'M(-?[0-9.]*),(-?[0-9.]*)\')[1]);\n    }\n    function getY(d) {\n        return Number(line.radius(d => y(d.VALUE1))([d]).match(\'M(-?[0-9.]*),(-?[0-9.]*)\')[2]);\n    }\n    function getX2(d) {\n        return Number(line.radius(d => y(d.VALUE2))([d]).match(\'M(-?[0-9.]*),(-?[0-9.]*)\')[1]);\n    }\n    function getY2(d) {\n        return Number(line.radius(d => y(d.VALUE2))([d]).match(\'M(-?[0-9.]*),(-?[0-9.]*)\')[2]);\n    }\n\n\n\n\n\n\n// +++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ ++++++++++++++++++++++++++++++++\n\n\n\n// separate data in positive and negative value\nlet separated = rawdata2.reduce((a, b,index) => {\n    let sub = b.VALUE1 - b.VALUE2;\n    if (sub > 0) {\n        return {positive: [...a.positive, {...b,index}], negative: [...a.negative]};\n    }\n    return {positive: [...a.positive], negative: [...a.negative, {...b,index}]};\n\n}, {positive: [], negative: []})\n\nlet positiveSets = [];\n\nlet previous = undefined\n\nseparated.positive.forEach((e)=>{\n    //if not contiguous\n    if (!previous || previous.index + 1 !== e.index) {\n        // create a new set\n        positiveSets.push([e]);\n    } else {\n        // append value to previous set\n        positiveSets[positiveSets.length - 1] = [...positiveSets[positiveSets.length - 1], e]\n    }\n    previous = e;\n})\n//same for negatives\nlet negativeSets = [];\nprevious = undefined\nseparated.negative.forEach((e)=>{\n    if (!previous || previous.index + 1 !== e.index) {\n        negativeSets.push([e]);\n    } else {\n        negativeSets[negativeSets.length - 1] = [...negativeSets[negativeSets.length - 1], e]\n    }\n    previous = e;\n})\n\n    negativeSets =negativeSets.map(s=>{\n\n        let first = s[0];\n        let last = s[s.length-1];\n        let next;\n        let prev;\n        if (first.index > 0){\n             prev = rawdata2[first.index-1]\n        }else{\n\n             prev = rawdata2[rawdata2.length-1]\n        }\n        if(last.index === rawdata2.length-1){\n            next = rawdata2[0];\n        }else{\n            next = rawdata2[last.index +1];\n        }\n\n\n        let w = checkLineIntersection(getX(prev),getY(prev),getX(first),getY(first),getX2(prev),getY2(prev),getX2(first),getY2(first))\n        let n = checkLineIntersection(getX(last),getY(last),getX(next),getY(next),getX2(last),getY2(last),getX2(next),getY2(next))\n\n        return [{x1:w.x,y1:w.y,x2:w.x,y2:w.y},...s.map(w=>({x1:getX(w),y1:getY(w),x2:getX2(w),y2:getY2(w)})),{x1:n.x,y1:n.y,x2:n.x,y2:n.y}]\n\n    })\n\n    positiveSets =positiveSets.map(s=>{\n\n        let first = s[0];\n        let last = s[s.length-1];\n        let next;\n        let prev;\n        if (first.index > 0){\n            prev = rawdata2[first.index-1]\n        }else{\n\n            prev = rawdata2[rawdata2.length-1]\n        }\n        if(last.index === rawdata2.length-1){\n            next = rawdata2[0];\n        }else{\n            next = rawdata2[last.index +1];\n        }\n\n\n        let w = checkLineIntersection(getX(prev),getY(prev),getX(first),getY(first),getX2(prev),getY2(prev),getX2(first),getY2(first))\n        let n = checkLineIntersection(getX(last),getY(last),getX(next),getY(next),getX2(last),getY2(last),getX2(next),getY2(next))\n\n        return [{x1:w.x,y1:w.y,x2:w.x,y2:w.y},...s.map(w=>({x1:getX(w),y1:getY(w),x2:getX2(w),y2:getY2(w)})),{x1:n.x,y1:n.y,x2:n.x,y2:n.y}]\n\n    })\n  const negG = svg.selectAll(\'.negative\').data(negativeSets)\n    negG.enter().append(\'path\')\n        .attr(\'class\',\'negative\') // "negative" color\n        .attr(\'fill\',\'lightblue\')\n        .attr("d", (d)=>d3.area().x0(d => d.x1).x1(d => d.x2).y0(d => d.y1).y1(d => d.y2)(d));\n\n\n    const posG = svg.selectAll(\'.positive\').data(positiveSets)\n    posG.enter().append(\'path\')\n        .attr(\'class\',\'positive\') // "negative" color\n        .attr(\'fill\',\'darkblue\')\n        .attr("d", (d)=>d3.area().x0(d => d.x1).x1(d => d.x2).y0(d => d.y1).y1(d => d.y2)(d));\n\n\n      svg.append("g")\n        .call(xAxis);\n\n      svg.append("g")\n        .call(yAxis);\n\n\n\n      // console.log(negG)\n\nconst container = d3.select("body").append("div");\ncontainer.node().appendChild(svg.node());
Run Code Online (Sandbox Code Playgroud)\r\n
#csvdata {\n    display: none;\n}
Run Code Online (Sandbox Code Playgroud)\r\n
<script src="https://d3js.org/d3.v7.min.js"></script>\n  <h1>D3 CSV Example</h1>
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

我想用 STATE 列(二进制值)可视化另一个数据框。我想要可视化具有 0 个 STATE 列值的 VALUE1 列和具有 1 个 STATE 列值的 VALUE1 列。我可以为每个创建两个数组。

\n
let rawdata3 = rawdata.filter(d => d.STATE== 0);\nlet rawdata4 = rawdata.filter(d => d.STATE== 1);\n\n
Run Code Online (Sandbox Code Playgroud)\n

我遇到的问题是,如果我想使用两个不同的数组制作径向图来根据 STATE 列的二进制值可视化 VALUE1 列,我不知道如何更新交叉点部分和负/正区域部分。rawdata3 数组的 VALUE1 列的线条颜色应为红色,rawdata3 数组的 VALUE2 列的线条颜色应为蓝色。rawdata4 数组的 VALUE1 列的线条颜色应为绿色,rawdata4 数组的 VALUE2 列的线条颜色应为绿色。\n如何我可以更新我的代码,以便可以通过更新交叉点部分和负/正面积部分来制作径向图吗?

\n

如果绿线比红线大,则使用较浅的红色,否则使用较深的红色。如果绿线比蓝线大,则使用较浅的蓝色,否则使用较深的蓝色。

\n

我所做的:我创建了两个空集,即负集和正集。我计算了负集和正集的每个末端的交叉点,创建了一组新的面积点并绘制了这些面积。对于同一数据帧(数组)中的不同列,这可以顺利工作。\nTLDR:我创建了多个区域。将我的数据分隔在多组连续的 \xe2\x80\x9cpositive\xe2\x80\x9d 或 \xe2\x80\x9cnegative\xe2\x80\x9d 区域中。然后,我为每个数据集绘制了一个区域。

\n

我的期望:

\n

我想画4条线。我想使用 rawdata.filter(d => d.STATE== 0); 绘制 2 条线(VALUE1 列值 -> 红色线,VALUE2 列值 -> 蓝色线);和 2 行(VALUE1 列值 -> 绿色线,VALUE2 列值 -> 绿色线)使用 rawdata.filter(d => d.STATE== 1);。

\n

我可以画线。但是,我无法有条件地填写这些行。

\n

编辑:我接受此问题的任何其他替代解决方案。我发布了我的尝试以将其显示为帮助/参考。

\n

在此输入图像描述

\n