D3中径向图的条件填充

问题描述 投票:0回答:1

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

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   } ]
const width = 954;
const height = width;
const margin = 5;
const innerRadius = width / 10;
const outerRadius = width / 4 - margin;



          function checkLineIntersection(line1StartX, line1StartY, line1EndX, line1EndY, line2StartX, line2StartY, line2EndX, line2EndY) {
        // 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
        var denominator, a, b, numerator1, numerator2, result = {
            x: null,
            y: null,
            onLine1: false,
            onLine2: false
        };
        denominator = ((line2EndY - line2StartY) * (line1EndX - line1StartX)) - ((line2EndX - line2StartX) * (line1EndY - line1StartY));
        if (denominator === 0) {
            return result;
        }
        a = line1StartY - line2StartY;
        b = line1StartX - line2StartX;
        numerator1 = ((line2EndX - line2StartX) * a) - ((line2EndY - line2StartY) * b);
        numerator2 = ((line1EndX - line1StartX) * a) - ((line1EndY - line1StartY) * b);
        a = numerator1 / denominator;
        b = numerator2 / denominator;

        // if we cast these lines infinitely in both directions, they intersect here:
        result.x = line1StartX + (a * (line1EndX - line1StartX));
        result.y = line1StartY + (a * (line1EndY - line1StartY));
        /*
                // it is worth noting that this should be the same as:
                x = line2StartX + (b * (line2EndX - line2StartX));
                y = line2StartX + (b * (line2EndY - line2StartY));
                */
        // if line1 is a segment and line2 is infinite, they intersect if:
        if (a > 0 && a < 1) {
            result.onLine1 = true;
        }
        // if line2 is a segment and line1 is infinite, they intersect if:
        if (b > 0 && b < 1) {
            result.onLine2 = true;
        }
        // if line1 and line2 are segments, they intersect if both of the above are true
        return result;
    }
// const width = 954;
// const height = width;
// const margin = 10;
// const innerRadius = width / 5;
// const outerRadius = width / 2 - margin;



      let rawdata2 = rawdata.filter(d => d.INSPECTION == 1);

      const minuteScale = d3.scaleLinear()
        .domain([0, 1439]) // 0 to 23:59 (24 hours * 60 minutes without -1 or else change to 1439 //- 1)
        .range([0, 2 * Math.PI]);

      const minuteTimeValues = rawdata2.map(d => d.minuteTime);

      const x = d3.scaleTime()
        .domain([0, 1439]) // Set the domain using the extent of the "minuteTime" values
        .range([0, 2 * Math.PI]); // Set the desired range

      //console.log(rawdata)

      const minValue1 = d3.min(rawdata, d => d.VALUE1);
      const minValue2 = d3.min(rawdata, d => d.VALUE2);
      const maxValue1 = d3.max(rawdata, d => d.VALUE1);
      const maxValue2 = d3.max(rawdata, d => d.VALUE2);

      const yMin = minValue1 < minValue2 ? minValue1 : minValue2;
      const yMax = maxValue1 > maxValue2 ? maxValue1 : maxValue2;

      const y = d3.scaleLinear()
                  .domain([yMin, yMax])
                  .range([innerRadius, outerRadius]);

// Modify the xAxis generator to display only the hour and add padding

const xAxis = (g) => g
  .attr("font-family", "sans-serif")
  .attr("font-size", 7)
  .call((g) => g.selectAll("g")
    .data(d3.range(0, 24)) // create an array of hour values
    .join("g")
      .each((d, i) => d.id = `hour-${i}`)
      .call((g) => g.append("path")
          .attr("stroke", "#000")
          .attr("stroke-opacity", 0.2)
          .attr("d", (d) => `
            M${d3.pointRadial(x(d * 60), innerRadius)}
            L${d3.pointRadial(x(d * 60), outerRadius)}
          `))
      .call((g) => g.append("path")
          .attr("id", (d) => `hour-${d}`)
          .datum((d) => [d * 60, (d + 1) * 60])
          .attr("fill", "none")
          .attr("d", ([a, b]) => `
            M${d3.pointRadial(x(a), innerRadius-10)}
            A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}
          `))
      .call((g) => g.append("text")
        .append("textPath")
          .attr("startOffset", 6)
          .attr("href", (d) => `#hour-${d}`)
          .text((d) => d.toString().padStart(2, "0"))
          .attr("text-anchor", "middle")
          .attr("alignment-baseline", "middle")
          .attr("transform", (d) => {
            const angle = x(d * 60);
            const radius = innerRadius - 20; // reduce the radius to move the labels closer to the center
                        return `translate(${d3.pointRadial(x(d * 60), radius)})`;
          })
      )
  );



      const yAxis = g => g
        .attr("text-anchor", "middle")
        .attr("font-family", "sans-serif")
        .attr("font-size", 7)
        .call(g => g.selectAll("g")
          .data(y.ticks().reverse())
          .join("g")
          .attr("fill", "none")
          .call(g => g.append("circle")
            .attr("stroke", "#000")
            .attr("stroke-opacity", 0.1)
            .attr("r", y))
          .call(g => g.append("text")
            .attr("y", d => -y(d))
            .attr("dy", "0.35em")
            .attr("stroke", "#fff")
            .attr("stroke-width", 5)
                  .attr("stroke-opacity", 0.6)
            .text((x, i) => `${x.toFixed(0)}${i ? "" : ""}`)
          .clone(true)
            .attr("y", d => y(d))
          .selectAll(function() { return [this, this.previousSibling]; })
          .clone(true)
            .attr("fill", "currentColor")
            .attr("stroke", "none")));

      const line = d3.lineRadial()
                    .curve(d3.curveLinear)
                    .angle(d => x(d.minuteTime));

      const area = d3.areaRadial()
                    .curve(d3.curveLinear)
                    .angle(d => x(d.minuteTime))

      const svg = d3.create("svg")
        .attr("viewBox", [-width / 2, -height / 2, width, height])
        .attr("stroke-linejoin", "round")
        .attr("stroke-linecap", "round");

      svg.append("path")
        .attr("fill", "none")
        .attr("stroke", "red")
        .attr("stroke-width", 3)
        .attr("d", line
          .radius(d => y(d.VALUE1))
          (rawdata2));

      svg.append("path")
        .attr("fill", "none")
        .attr("stroke", "green")
        .attr("stroke-width", 3)
        .attr("d", line
          .radius(d => y(d.VALUE2))
          (rawdata2));
// +++++++++++++++++++++++++++++++++++ calc crossing points ++++++++++++++++++++++++++++++++
function findCrossingPoints(data) {
  const crossingPoints = [];

  for (let i = 1; i < data.length; i++) {
    const d1 = data[i - 1];
    const d2 = data[i];

    if ((d1.VALUE1 - d1.VALUE2) * (d2.VALUE1 - d2.VALUE2) < 0) {
      // There's a crossing point between the two data points
      const t = (0 - (d1.VALUE1 - d1.VALUE2)) / ((d2.VALUE1 - d2.VALUE2) - (d1.VALUE1 - d1.VALUE2));
      const crossingValue1 = d1.VALUE1 + t * (d2.VALUE1 - d1.VALUE1);
      const crossingValue2 = d1.VALUE2 + t * (d2.VALUE2 - d1.VALUE2);
      crossingPoints.push({ VALUE1: crossingValue1, VALUE2: crossingValue2 });
    }
  }

  return crossingPoints;
}

const crossingPoints = findCrossingPoints(rawdata2);
   function getX(d) {
        return Number(line.radius(d => y(d.VALUE1))([d]).match('M(-?[0-9.]*),(-?[0-9.]*)')[1]);
    }
    function getY(d) {
        return Number(line.radius(d => y(d.VALUE1))([d]).match('M(-?[0-9.]*),(-?[0-9.]*)')[2]);
    }
    function getX2(d) {
        return Number(line.radius(d => y(d.VALUE2))([d]).match('M(-?[0-9.]*),(-?[0-9.]*)')[1]);
    }
    function getY2(d) {
        return Number(line.radius(d => y(d.VALUE2))([d]).match('M(-?[0-9.]*),(-?[0-9.]*)')[2]);
    }






// +++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ ++++++++++++++++++++++++++++++++



// separate data in positive and negative value
let separated = rawdata2.reduce((a, b,index) => {
    let sub = b.VALUE1 - b.VALUE2;
    if (sub > 0) {
        return {positive: [...a.positive, {...b,index}], negative: [...a.negative]};
    }
    return {positive: [...a.positive], negative: [...a.negative, {...b,index}]};

}, {positive: [], negative: []})

let positiveSets = [];

let previous = undefined

separated.positive.forEach((e)=>{
    //if not contiguous
    if (!previous || previous.index + 1 !== e.index) {
        // create a new set
        positiveSets.push([e]);
    } else {
        // append value to previous set
        positiveSets[positiveSets.length - 1] = [...positiveSets[positiveSets.length - 1], e]
    }
    previous = e;
})
//same for negatives
let negativeSets = [];
previous = undefined
separated.negative.forEach((e)=>{
    if (!previous || previous.index + 1 !== e.index) {
        negativeSets.push([e]);
    } else {
        negativeSets[negativeSets.length - 1] = [...negativeSets[negativeSets.length - 1], e]
    }
    previous = e;
})

    negativeSets =negativeSets.map(s=>{

        let first = s[0];
        let last = s[s.length-1];
        let next;
        let prev;
        if (first.index > 0){
             prev = rawdata2[first.index-1]
        }else{

             prev = rawdata2[rawdata2.length-1]
        }
        if(last.index === rawdata2.length-1){
            next = rawdata2[0];
        }else{
            next = rawdata2[last.index +1];
        }


        let w = checkLineIntersection(getX(prev),getY(prev),getX(first),getY(first),getX2(prev),getY2(prev),getX2(first),getY2(first))
        let n = checkLineIntersection(getX(last),getY(last),getX(next),getY(next),getX2(last),getY2(last),getX2(next),getY2(next))

        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}]

    })

    positiveSets =positiveSets.map(s=>{

        let first = s[0];
        let last = s[s.length-1];
        let next;
        let prev;
        if (first.index > 0){
            prev = rawdata2[first.index-1]
        }else{

            prev = rawdata2[rawdata2.length-1]
        }
        if(last.index === rawdata2.length-1){
            next = rawdata2[0];
        }else{
            next = rawdata2[last.index +1];
        }


        let w = checkLineIntersection(getX(prev),getY(prev),getX(first),getY(first),getX2(prev),getY2(prev),getX2(first),getY2(first))
        let n = checkLineIntersection(getX(last),getY(last),getX(next),getY(next),getX2(last),getY2(last),getX2(next),getY2(next))

        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}]

    })
  const negG = svg.selectAll('.negative').data(negativeSets)
    negG.enter().append('path')
        .attr('class','negative') // "negative" color
        .attr('fill','lightblue')
        .attr("d", (d)=>d3.area().x0(d => d.x1).x1(d => d.x2).y0(d => d.y1).y1(d => d.y2)(d));


    const posG = svg.selectAll('.positive').data(positiveSets)
    posG.enter().append('path')
        .attr('class','positive') // "negative" color
        .attr('fill','darkblue')
        .attr("d", (d)=>d3.area().x0(d => d.x1).x1(d => d.x2).y0(d => d.y1).y1(d => d.y2)(d));


      svg.append("g")
        .call(xAxis);

      svg.append("g")
        .call(yAxis);



      // console.log(negG)

const container = d3.select("body").append("div");
container.node().appendChild(svg.node());
#csvdata {
    display: none;
}
<script src="https://d3js.org/d3.v7.min.js"></script>
  <h1>D3 CSV Example</h1>

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

let rawdata3 = rawdata.filter(d => d.STATE== 0);
let rawdata4 = rawdata.filter(d => d.STATE== 1);

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

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

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

我的期望:

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

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

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

javascript d3.js
1个回答
© www.soinside.com 2019 - 2024. All rights reserved.