如何在D3中正确地在径向图中有条件地填充区域?

huangapple go评论53阅读模式
英文:

How to conditionally fill the area in radial chart correctly in D3?

问题

我可以计算负面和正面区域。但是,我的图中出现了一些意外的间隙。

红线代表VALUE1列的24小时测量值,绿线代表VALUE2列的值。如果VALUE1大于VALUE2,我想用深蓝色填充区域,否则用浅蓝色填充。然而,我发现有些间隙,我的代码只部分工作。我该如何解决这个问题?问题是否出现在我编写负面和正面区域的代码中?

我希望有一个径向图表,如果VALUE1大于VALUE2,我想用深蓝色填充两条线之间的区域。如果VALUE2大于VALUE1,我想用浅蓝色填充两条线之间的区域。我该如何实现这个目标?

这是我的JSFiddle链接:https://jsfiddle.net/phvzur7a/
这是不起作用的部分。我的目标是填充交点的区域,但是这部分代码根本不起作用。

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) {
      // 存在两个数据点之间的交点
      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);

以上是您提供的代码部分的翻译。如果您需要进一步的帮助,请随时提出。

英文:

I can calculate the negative and positive area. However, there are some unexpected gaps in my plot. 如何在D3中正确地在径向图中有条件地填充区域?

Red line represents the 24 hour measurements of VALUE1 column and green line represents VALUE2 column. If VALUE1 is bigger than VALUE2, I want to fill the area with darkblue, else with lightblue. However, I ended up having some gaps and my code partially works. How can I solve this issue? Does the problem lie where I code negative and positive area?

I expect to have a radial chart where if VALUE1 is bigger than VALUE2, I want to fill the area between two lines with darkblue color. IF VALUE2 is bigger than VALUE1, I want to fill the area between two lines with lightblue color. How can I achieve it?

Here is my JSFiddle link: https://jsfiddle.net/phvzur7a/
This is the part that does not work. I aim to fill area of crossing oints, but this part of code does not function at all.

function findCrossingPoints(data) {
  const crossingPoints = [];

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

    if ((d1.VALUE1 - d1.VALUE2) * (d2.VALUE1 - d2.VALUE2) &lt; 0) {
      // There&#39;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);

<!-- begin snippet: js hide: false console: false babel: false -->

<!-- language: lang-js -->

    const rawdata = [
{
&quot;DATE_TIME&quot;: &quot;2018-10-31T23:00:00.000Z&quot;,
&quot;VALUE1&quot;: 160,
&quot;VALUE2&quot;: 110,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 0
},
{
&quot;DATE_TIME&quot;: &quot;2018-10-31T23:20:00.000Z&quot;,
&quot;VALUE1&quot;: 102,
&quot;VALUE2&quot;: 118,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 20
},
{
&quot;DATE_TIME&quot;: &quot;2018-10-31T23:40:00.000Z&quot;,
&quot;VALUE1&quot;: 153,
&quot;VALUE2&quot;: 117,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 40
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T00:00:00.000Z&quot;,
&quot;VALUE1&quot;: 112,
&quot;VALUE2&quot;: 117,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 60
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T00:20:00.000Z&quot;,
&quot;VALUE1&quot;: 95,
&quot;VALUE2&quot;: 103,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 80
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T00:40:00.000Z&quot;,
&quot;VALUE1&quot;: 157,
&quot;VALUE2&quot;: 119,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 100
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T01:00:00.000Z&quot;,
&quot;VALUE1&quot;: 129,
&quot;VALUE2&quot;: 120,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 120
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T01:20:00.000Z&quot;,
&quot;VALUE1&quot;: 135,
&quot;VALUE2&quot;: 108,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 140
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T01:40:00.000Z&quot;,
&quot;VALUE1&quot;: 126,
&quot;VALUE2&quot;: 119,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 160
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T02:00:00.000Z&quot;,
&quot;VALUE1&quot;: 121,
&quot;VALUE2&quot;: 110,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 180
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T02:20:00.000Z&quot;,
&quot;VALUE1&quot;: 133,
&quot;VALUE2&quot;: 101,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 200
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T02:40:00.000Z&quot;,
&quot;VALUE1&quot;: 124,
&quot;VALUE2&quot;: 120,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 220
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T03:00:00.000Z&quot;,
&quot;VALUE1&quot;: 121,
&quot;VALUE2&quot;: 112,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 240
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T03:20:00.000Z&quot;,
&quot;VALUE1&quot;: 147,
&quot;VALUE2&quot;: 108,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 260
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T03:40:00.000Z&quot;,
&quot;VALUE1&quot;: 98,
&quot;VALUE2&quot;: 102,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 280
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T04:00:00.000Z&quot;,
&quot;VALUE1&quot;: 136,
&quot;VALUE2&quot;: 109,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 300
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T04:20:00.000Z&quot;,
&quot;VALUE1&quot;: 130,
&quot;VALUE2&quot;: 112,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 320
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T04:40:00.000Z&quot;,
&quot;VALUE1&quot;: 156,
&quot;VALUE2&quot;: 100,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 340
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T05:00:00.000Z&quot;,
&quot;VALUE1&quot;: 135,
&quot;VALUE2&quot;: 102,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 360
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T05:20:00.000Z&quot;,
&quot;VALUE1&quot;: 112,
&quot;VALUE2&quot;: 120,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 380
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T05:40:00.000Z&quot;,
&quot;VALUE1&quot;: 116,
&quot;VALUE2&quot;: 103,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 400
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T06:00:00.000Z&quot;,
&quot;VALUE1&quot;: 129,
&quot;VALUE2&quot;: 107,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 420
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T06:20:00.000Z&quot;,
&quot;VALUE1&quot;: 107,
&quot;VALUE2&quot;: 118,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 440
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T06:40:00.000Z&quot;,
&quot;VALUE1&quot;: 112,
&quot;VALUE2&quot;: 118,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 460
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T07:00:00.000Z&quot;,
&quot;VALUE1&quot;: 116,
&quot;VALUE2&quot;: 113,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 480
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T07:20:00.000Z&quot;,
&quot;VALUE1&quot;: 92,
&quot;VALUE2&quot;: 109,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 500
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T07:40:00.000Z&quot;,
&quot;VALUE1&quot;: 92,
&quot;VALUE2&quot;: 120,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 520
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T08:00:00.000Z&quot;,
&quot;VALUE1&quot;: 112,
&quot;VALUE2&quot;: 101,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 540
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T08:20:00.000Z&quot;,
&quot;VALUE1&quot;: 126,
&quot;VALUE2&quot;: 105,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 560
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T08:40:00.000Z&quot;,
&quot;VALUE1&quot;: 135,
&quot;VALUE2&quot;: 108,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 580
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T09:40:00.000Z&quot;,
&quot;VALUE1&quot;: 108,
&quot;VALUE2&quot;: 115,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 640
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T10:40:00.000Z&quot;,
&quot;VALUE1&quot;: 112,
&quot;VALUE2&quot;: 114,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 700
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T11:40:00.000Z&quot;,
&quot;VALUE1&quot;: 107,
&quot;VALUE2&quot;: 114,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 760
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T12:40:00.000Z&quot;,
&quot;VALUE1&quot;: 110,
&quot;VALUE2&quot;: 115,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 820
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T13:40:00.000Z&quot;,
&quot;VALUE1&quot;: 101,
&quot;VALUE2&quot;: 101,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 880
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T14:40:00.000Z&quot;,
&quot;VALUE1&quot;: 145,
&quot;VALUE2&quot;: 118,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 940
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T15:40:00.000Z&quot;,
&quot;VALUE1&quot;: 100,
&quot;VALUE2&quot;: 107,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1000
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T16:40:00.000Z&quot;,
&quot;VALUE1&quot;: 155,
&quot;VALUE2&quot;: 107,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1060
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T17:40:00.000Z&quot;,
&quot;VALUE1&quot;: 132,
&quot;VALUE2&quot;: 101,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1120
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T18:40:00.000Z&quot;,
&quot;VALUE1&quot;: 110,
&quot;VALUE2&quot;: 112,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1180
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T19:40:00.000Z&quot;,
&quot;VALUE1&quot;: 160,
&quot;VALUE2&quot;: 119,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1240
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T20:40:00.000Z&quot;,
&quot;VALUE1&quot;: 127,
&quot;VALUE2&quot;: 104,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1300
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T21:40:00.000Z&quot;,
&quot;VALUE1&quot;: 150,
&quot;VALUE2&quot;: 108,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1360
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T22:40:00.000Z&quot;,
&quot;VALUE1&quot;: 108,
&quot;VALUE2&quot;: 111,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1420
},
{
&quot;DATE_TIME&quot;: &quot;2018-11-01T22:59:00.000Z&quot;,
&quot;VALUE1&quot;: 151,
&quot;VALUE2&quot;: 102,
&quot;INSPECTION&quot;: 1,
&quot;minuteTime&quot;: 1439
}
];
const width = 954;
const height = width;
const margin = 10;
const innerRadius = width / 5;
const outerRadius = width / 2 - margin;
let rawdata2 = rawdata.filter(d =&gt; 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 =&gt; d.minuteTime);
const x = d3.scaleTime()
.domain([0, 1439]) // Set the domain using the extent of the &quot;minuteTime&quot; values
.range([0, 2 * Math.PI]); // Set the desired range
console.log(rawdata)
const minValue1 = d3.min(rawdata, d =&gt; d.VALUE1);
const minValue2 = d3.min(rawdata, d =&gt; d.VALUE2);
const maxValue1 = d3.max(rawdata, d =&gt; d.VALUE1);
const maxValue2 = d3.max(rawdata, d =&gt; d.VALUE2);
const yMin = minValue1 &lt; minValue2 ? minValue1 : minValue2;
const yMax = maxValue1 &gt; 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) =&gt; g
.attr(&quot;font-family&quot;, &quot;sans-serif&quot;)
.attr(&quot;font-size&quot;, 10)
.call((g) =&gt; g.selectAll(&quot;g&quot;)
.data(d3.range(0, 24)) // create an array of hour values
.join(&quot;g&quot;)
.each((d, i) =&gt; d.id = `hour-${i}`)
.call((g) =&gt; g.append(&quot;path&quot;)
.attr(&quot;stroke&quot;, &quot;#000&quot;)
.attr(&quot;stroke-opacity&quot;, 0.2)
.attr(&quot;d&quot;, (d) =&gt; `
M${d3.pointRadial(x(d * 60), innerRadius)}
L${d3.pointRadial(x(d * 60), outerRadius)}
`))
.call((g) =&gt; g.append(&quot;path&quot;)
.attr(&quot;id&quot;, (d) =&gt; `hour-${d}`)
.datum((d) =&gt; [d * 60, (d + 1) * 60])
.attr(&quot;fill&quot;, &quot;none&quot;)
.attr(&quot;d&quot;, ([a, b]) =&gt; `
M${d3.pointRadial(x(a), innerRadius-10)}
A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}
`))
.call((g) =&gt; g.append(&quot;text&quot;)
.append(&quot;textPath&quot;)
.attr(&quot;startOffset&quot;, 6)
.attr(&quot;href&quot;, (d) =&gt; `#hour-${d}`)
.text((d) =&gt; d.toString().padStart(2, &quot;0&quot;))
.attr(&quot;text-anchor&quot;, &quot;middle&quot;)
.attr(&quot;alignment-baseline&quot;, &quot;middle&quot;)
.attr(&quot;transform&quot;, (d) =&gt; {
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 =&gt; g
.attr(&quot;text-anchor&quot;, &quot;middle&quot;)
.attr(&quot;font-family&quot;, &quot;sans-serif&quot;)
.attr(&quot;font-size&quot;, 10)
.call(g =&gt; g.selectAll(&quot;g&quot;)
.data(y.ticks().reverse())
.join(&quot;g&quot;)
.attr(&quot;fill&quot;, &quot;none&quot;)
.call(g =&gt; g.append(&quot;circle&quot;)
.attr(&quot;stroke&quot;, &quot;#000&quot;)
.attr(&quot;stroke-opacity&quot;, 0.2)
.attr(&quot;r&quot;, y))
.call(g =&gt; g.append(&quot;text&quot;)
.attr(&quot;y&quot;, d =&gt; -y(d))
.attr(&quot;dy&quot;, &quot;0.35em&quot;)
.attr(&quot;stroke&quot;, &quot;#fff&quot;)
.attr(&quot;stroke-width&quot;, 5)
.text((x, i) =&gt; `${x.toFixed(0)}${i ? &quot;&quot; : &quot;&quot;}`)
.clone(true)
.attr(&quot;y&quot;, d =&gt; y(d))
.selectAll(function() { return [this, this.previousSibling]; })
.clone(true)
.attr(&quot;fill&quot;, &quot;currentColor&quot;)
.attr(&quot;stroke&quot;, &quot;none&quot;)));
const line = d3.lineRadial()
.curve(d3.curveLinear)
.angle(d =&gt; x(d.minuteTime));
const area = d3.areaRadial()
.curve(d3.curveLinear)
.angle(d =&gt; x(d.minuteTime))
const svg = d3.create(&quot;svg&quot;)
.attr(&quot;viewBox&quot;, [-width / 2, -height / 2, width, height])
.attr(&quot;stroke-linejoin&quot;, &quot;round&quot;)
.attr(&quot;stroke-linecap&quot;, &quot;round&quot;);
svg.append(&quot;path&quot;)
.attr(&quot;fill&quot;, &quot;none&quot;)
.attr(&quot;stroke&quot;, &quot;red&quot;)
.attr(&quot;stroke-width&quot;, 3)
.attr(&quot;d&quot;, line
.radius(d =&gt; y(d.VALUE1))
(rawdata2));
svg.append(&quot;path&quot;)
.attr(&quot;fill&quot;, &quot;none&quot;)
.attr(&quot;stroke&quot;, &quot;green&quot;)
.attr(&quot;stroke-width&quot;, 3)
.attr(&quot;d&quot;, line
.radius(d =&gt; y(d.VALUE2))
(rawdata2));
// +++++++++++++++++++++++++++++++++++ calc crossing points ++++++++++++++++++++++++++++++++
function findCrossingPoints(data) {
const crossingPoints = [];
for (let i = 1; i &lt; data.length; i++) {
const d1 = data[i - 1];
const d2 = data[i];
if ((d1.VALUE1 - d1.VALUE2) * (d2.VALUE1 - d2.VALUE2) &lt; 0) {
// There&#39;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);
// +++++++++++++++++++++++++++++++++++ ++++++++++++++++++++++ ++++++++++++++++++++++++++++++++
// separate data in positive and negative value
let separated = rawdata2.reduce((a, b,index) =&gt; {
let sub = b.VALUE1 - b.VALUE2;
if (sub &gt; 0) {
return {positive: [...a.positive, {...b,index}], negative: [...a.negative]};
}
return {positive: [...a.positive], negative: [...a.negative, {...b,index}]};
}, {positive: [], negative: []})
const positiveSets = [];
let previous = undefined
separated.positive.forEach((e)=&gt;{
//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
const negativeSets = [];
previous = undefined
separated.negative.forEach((e)=&gt;{
if (!previous || previous.index + 1 !== e.index) {
negativeSets.push([e]);
} else {
negativeSets[negativeSets.length - 1] = [...negativeSets[negativeSets.length - 1], e]
}
previous = e;
})
const posG = svg.selectAll(&#39;.positive&#39;).data(positiveSets)
posG.enter().append(&#39;path&#39;)
.attr(&#39;class&#39;,&#39;positive&#39;)
.attr(&#39;fill&#39;,&#39;darkblue&#39;) // &quot;positive&quot; color
.attr(&quot;d&quot;, (d)=&gt;area
.innerRadius(d =&gt; y(d.VALUE1))
.outerRadius(d =&gt; y(d.VALUE2))
(d));
const negG = svg.selectAll(&#39;.negative&#39;).data(negativeSets)
negG.enter().append(&#39;path&#39;)
.attr(&#39;class&#39;,&#39;negative&#39;) // &quot;negative&quot; color
.attr(&#39;fill&#39;,&#39;lightblue&#39;)
.attr(&quot;d&quot;, (d)=&gt;area
.innerRadius(d =&gt; y(d.VALUE1))
.outerRadius(d =&gt; y(d.VALUE2))
(d));
svg.append(&quot;g&quot;)
.call(xAxis);
svg.append(&quot;g&quot;)
.call(yAxis);
// console.log(negG)
const container = d3.select(&quot;body&quot;).append(&quot;div&quot;);
container.node().appendChild(svg.node());

<!-- language: lang-css -->

#csvdata {
display: none;
}

<!-- language: lang-html -->

  &lt;script src=&quot;https://d3js.org/d3.v7.min.js&quot;&gt;&lt;/script&gt;
&lt;h1&gt;D3 CSV Example&lt;/h1&gt;

<!-- end snippet -->

答案1

得分: 2

const rawdata = [{
    "DATE_TIME": "2018-10-31T23:00:00.000Z",
    "VALUE1": 160,
    "VALUE2": 110,
    "INSPECTION": 1,
    "minuteTime": 0
  },
  {
    "DATE_TIME": "2018-10-31T23:20:00.000Z",
    "VALUE1": 102,
    "VALUE2": 118,
    "INSPECTION": 1,
    "minuteTime": 20
  },
  {
    "DATE_TIME": "2018-10-31T23:40:00.000Z",
    "VALUE1": 153,
    "VALUE2": 117,
    "INSPECTION": 1,
    "minuteTime": 40
  },
  {
    "DATE_TIME": "2018-11-01T00:00:00.000Z",
    "VALUE1": 112,
    "VALUE2": 117,
    "INSPECTION": 1,
    "minuteTime": 60
  },
  {
    "DATE_TIME": "2018-11-01T00:20:00.000Z",
    "VALUE1": 95,
    "VALUE2": 103,
    "INSPECTION": 1,
    "minuteTime": 80
  },
  {
    "DATE_TIME": "2018-11-01T00:40:00.000Z",
    "VALUE1": 157,
    "VALUE2": 119,
    "INSPECTION": 1,
    "minuteTime": 100
  },
  {
    "DATE_TIME": "2018-11-01T01:00:00.000Z",
    "VALUE1": 129,
    "VALUE2": 120,
    "INSPECTION": 1,
    "minuteTime": 120
  },
  {
    "DATE_TIME": "2018-11-01T01:20:00.000Z",
    "VALUE1": 135,
    "VALUE2": 108,
    "INSPECTION": 1,
    "minuteTime": 140
  },
  {
    "DATE_TIME": "2018-11-01T01:40:00.000Z",
    "VALUE1": 126,
    "VALUE2": 119,
    "INSPECTION": 1,
    "minuteTime": 160
  },
  {
    "DATE_TIME": "2018-11-01T02:00:00.000Z",
    "VALUE1": 121,
    "VALUE2": 110,
    "INSPECTION": 1,
    "minuteTime": 180
  },
  {
    "DATE_TIME": "2018-11-01T02:20:00.000Z",
    "VALUE1": 133,
    "VALUE2": 101,
    "INSPECTION": 1,
    "minuteTime": 200
  },
  {
    "DATE_TIME": "2018-11-01T02:40:00.000Z",
    "VALUE1": 124,
    "VALUE2": 120,
    "INSPECTION": 1,
    "minuteTime": 220
  },
  {
    "DATE_TIME": "2018-11-01T03:00:00.000Z",
    "VALUE1": 121,
    "VALUE2": 112,
    "INSPECTION": 1,
    "minuteTime": 240
  },
  {
    "DATE_TIME": "2018-11-01T03:20:00.000Z",
    "VALUE1": 147,
    "VALUE2": 108,
    "INSPECTION": 1,
    "minuteTime": 260
  },
  {
    "DATE_TIME": "2018-11-01T03:40:00.000Z",
    "VALUE1": 98,
    "VALUE2": 102,
    "INSPECTION": 1,
    "minuteTime": 280
  },
  {
    "DATE_TIME": "2018-11-01T04:00:00.000Z",
    "VALUE1": 136,
    "VALUE2": 109,
    "INSPECTION": 1,
    "minuteTime": 300
  },
  {
    "DATE_TIME": "2018-11-01T04:20:00.000Z",
    "VALUE1": 130,
    "VALUE2": 112,
    "INSPECTION": 1,
    "minuteTime": 320
  },
  {
    "DATE_TIME": "2018-11-01T04:40:00.000Z",
    "VALUE1": 156,
    "VALUE2": 100,
    "INSPECTION": 1,
    "minuteTime":

<details>
<summary>英文:</summary>

Here you go: 

&lt;!-- begin snippet: js hide: false console: false babel: false --&gt;

&lt;!-- language: lang-js --&gt;

    const rawdata = [{
        &quot;DATE_TIME&quot;: &quot;2018-10-31T23:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 160,
        &quot;VALUE2&quot;: 110,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 0
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-10-31T23:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 102,
        &quot;VALUE2&quot;: 118,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 20
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-10-31T23:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 153,
        &quot;VALUE2&quot;: 117,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 40
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T00:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 112,
        &quot;VALUE2&quot;: 117,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 60
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T00:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 95,
        &quot;VALUE2&quot;: 103,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 80
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T00:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 157,
        &quot;VALUE2&quot;: 119,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 100
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T01:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 129,
        &quot;VALUE2&quot;: 120,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 120
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T01:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 135,
        &quot;VALUE2&quot;: 108,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 140
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T01:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 126,
        &quot;VALUE2&quot;: 119,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 160
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T02:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 121,
        &quot;VALUE2&quot;: 110,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 180
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T02:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 133,
        &quot;VALUE2&quot;: 101,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 200
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T02:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 124,
        &quot;VALUE2&quot;: 120,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 220
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T03:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 121,
        &quot;VALUE2&quot;: 112,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 240
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T03:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 147,
        &quot;VALUE2&quot;: 108,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 260
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T03:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 98,
        &quot;VALUE2&quot;: 102,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 280
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T04:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 136,
        &quot;VALUE2&quot;: 109,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 300
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T04:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 130,
        &quot;VALUE2&quot;: 112,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 320
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T04:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 156,
        &quot;VALUE2&quot;: 100,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 340
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T05:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 135,
        &quot;VALUE2&quot;: 102,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 360
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T05:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 112,
        &quot;VALUE2&quot;: 120,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 380
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T05:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 116,
        &quot;VALUE2&quot;: 103,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 400
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T06:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 129,
        &quot;VALUE2&quot;: 107,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 420
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T06:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 107,
        &quot;VALUE2&quot;: 118,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 440
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T06:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 112,
        &quot;VALUE2&quot;: 118,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 460
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T07:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 116,
        &quot;VALUE2&quot;: 113,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 480
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T07:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 92,
        &quot;VALUE2&quot;: 109,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 500
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T07:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 92,
        &quot;VALUE2&quot;: 120,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 520
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T08:00:00.000Z&quot;,
        &quot;VALUE1&quot;: 112,
        &quot;VALUE2&quot;: 101,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 540
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T08:20:00.000Z&quot;,
        &quot;VALUE1&quot;: 126,
        &quot;VALUE2&quot;: 105,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 560
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T08:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 135,
        &quot;VALUE2&quot;: 108,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 580
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T09:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 108,
        &quot;VALUE2&quot;: 115,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 640
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T10:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 112,
        &quot;VALUE2&quot;: 114,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 700
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T11:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 107,
        &quot;VALUE2&quot;: 114,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 760
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T12:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 110,
        &quot;VALUE2&quot;: 115,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 820
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T13:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 101,
        &quot;VALUE2&quot;: 101,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 880
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T14:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 145,
        &quot;VALUE2&quot;: 118,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 940
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T15:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 100,
        &quot;VALUE2&quot;: 107,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1000
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T16:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 155,
        &quot;VALUE2&quot;: 107,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1060
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T17:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 132,
        &quot;VALUE2&quot;: 101,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1120
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T18:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 110,
        &quot;VALUE2&quot;: 112,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1180
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T19:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 160,
        &quot;VALUE2&quot;: 119,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1240
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T20:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 127,
        &quot;VALUE2&quot;: 104,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1300
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T21:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 150,
        &quot;VALUE2&quot;: 108,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1360
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T22:40:00.000Z&quot;,
        &quot;VALUE1&quot;: 108,
        &quot;VALUE2&quot;: 111,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1420
      },
      {
        &quot;DATE_TIME&quot;: &quot;2018-11-01T22:59:00.000Z&quot;,
        &quot;VALUE1&quot;: 151,
        &quot;VALUE2&quot;: 102,
        &quot;INSPECTION&quot;: 1,
        &quot;minuteTime&quot;: 1439
      }
    ];

    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 &gt; 0 &amp;&amp; a &lt; 1) {
        result.onLine1 = true;
      }
      // if line2 is a segment and line1 is infinite, they intersect if:
      if (b &gt; 0 &amp;&amp; b &lt; 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 =&gt; 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 =&gt; d.minuteTime);

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

    console.log(rawdata)

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

    const yMin = minValue1 &lt; minValue2 ? minValue1 : minValue2;
    const yMax = maxValue1 &gt; 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) =&gt; g
      .attr(&quot;font-family&quot;, &quot;sans-serif&quot;)
      .attr(&quot;font-size&quot;, 10)
      .call((g) =&gt; g.selectAll(&quot;g&quot;)
        .data(d3.range(0, 24)) // create an array of hour values
        .join(&quot;g&quot;)
        .each((d, i) =&gt; d.id = `hour-${i}`)
        .call((g) =&gt; g.append(&quot;path&quot;)
          .attr(&quot;stroke&quot;, &quot;#000&quot;)
          .attr(&quot;stroke-opacity&quot;, 0.2)
          .attr(&quot;d&quot;, (d) =&gt; `
                M${d3.pointRadial(x(d * 60), innerRadius)}
                L${d3.pointRadial(x(d * 60), outerRadius)}
              `))
        .call((g) =&gt; g.append(&quot;path&quot;)
          .attr(&quot;id&quot;, (d) =&gt; `hour-${d}`)
          .datum((d) =&gt; [d * 60, (d + 1) * 60])
          .attr(&quot;fill&quot;, &quot;none&quot;)
          .attr(&quot;d&quot;, ([a, b]) =&gt; `
                M${d3.pointRadial(x(a), innerRadius-10)}
                A${innerRadius},${innerRadius} 0,0,1 ${d3.pointRadial(x(b), innerRadius)}
              `))
        .call((g) =&gt; g.append(&quot;text&quot;)
          .append(&quot;textPath&quot;)
          .attr(&quot;startOffset&quot;, 6)
          .attr(&quot;href&quot;, (d) =&gt; `#hour-${d}`)
          .text((d) =&gt; d.toString().padStart(2, &quot;0&quot;))
          .attr(&quot;text-anchor&quot;, &quot;middle&quot;)
          .attr(&quot;alignment-baseline&quot;, &quot;middle&quot;)
          .attr(&quot;transform&quot;, (d) =&gt; {
            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 =&gt; g
      .attr(&quot;text-anchor&quot;, &quot;middle&quot;)
      .attr(&quot;font-family&quot;, &quot;sans-serif&quot;)
      .attr(&quot;font-size&quot;, 10)
      .call(g =&gt; g.selectAll(&quot;g&quot;)
        .data(y.ticks().reverse())
        .join(&quot;g&quot;)
        .attr(&quot;fill&quot;, &quot;none&quot;)
        .call(g =&gt; g.append(&quot;circle&quot;)
          .attr(&quot;stroke&quot;, &quot;#000&quot;)
          .attr(&quot;stroke-opacity&quot;, 0.2)
          .attr(&quot;r&quot;, y))
        .call(g =&gt; g.append(&quot;text&quot;)
          .attr(&quot;y&quot;, d =&gt; -y(d))
          .attr(&quot;dy&quot;, &quot;0.35em&quot;)
          .attr(&quot;stroke&quot;, &quot;#fff&quot;)
          .attr(&quot;stroke-width&quot;, 5)
          .text((x, i) =&gt; `${x.toFixed(0)}${i ? &quot;&quot; : &quot;&quot;}`)
          .clone(true)
          .attr(&quot;y&quot;, d =&gt; y(d))
          .selectAll(function() {
            return [this, this.previousSibling];
          })
          .clone(true)
          .attr(&quot;fill&quot;, &quot;currentColor&quot;)
          .attr(&quot;stroke&quot;, &quot;none&quot;)));

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

    const area = d3.areaRadial()
      .curve(d3.curveLinear)
      .angle(d =&gt; x(d.minuteTime))

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

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

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

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

        if ((d1.VALUE1 - d1.VALUE2) * (d2.VALUE1 - d2.VALUE2) &lt; 0) {
          // There&#39;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 =&gt; y(d.VALUE1))([d]).match(&#39;M(-?[0-9.]*),(-?[0-9.]*)&#39;)[1]);
    }

    function getY(d) {
      return Number(line.radius(d =&gt; y(d.VALUE1))([d]).match(&#39;M(-?[0-9.]*),(-?[0-9.]*)&#39;)[2]);
    }

    function getX2(d) {
      return Number(line.radius(d =&gt; y(d.VALUE2))([d]).match(&#39;M(-?[0-9.]*),(-?[0-9.]*)&#39;)[1]);
    }

    function getY2(d) {
      return Number(line.radius(d =&gt; y(d.VALUE2))([d]).match(&#39;M(-?[0-9.]*),(-?[0-9.]*)&#39;)[2]);
    }






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



    // separate data in positive and negative value
    let separated = rawdata2.reduce((a, b, index) =&gt; {
      let sub = b.VALUE1 - b.VALUE2;
      if (sub &gt; 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) =&gt; {
      //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) =&gt; {
      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 =&gt; {

      let first = s[0];
      let last = s[s.length - 1];
      let next;
      let prev;
      if (first.index &gt; 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 =&gt; ({
        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 =&gt; {

      let first = s[0];
      let last = s[s.length - 1];
      let next;
      let prev;
      if (first.index &gt; 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 =&gt; ({
        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(&#39;.negative&#39;).data(negativeSets)
    negG.enter().append(&#39;path&#39;)
      .attr(&#39;class&#39;, &#39;negative&#39;) // &quot;negative&quot; color
      .attr(&#39;fill&#39;, &#39;lightblue&#39;)
      .attr(&quot;d&quot;, (d) =&gt; d3.area().x0(d =&gt; d.x1).x1(d =&gt; d.x2).y0(d =&gt; d.y1).y1(d =&gt; d.y2)(d));


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


    svg.append(&quot;g&quot;)
      .call(xAxis);

    svg.append(&quot;g&quot;)
      .call(yAxis);



    // console.log(negG)

    const container = d3.select(&quot;body&quot;).append(&quot;div&quot;);
    container.node().appendChild(svg.node());

&lt;!-- language: lang-css --&gt;

    #csvdata {
      display: none;
    }

&lt;!-- language: lang-html --&gt;

    &lt;script src=&quot;https://d3js.org/d3.v7.min.js&quot;&gt;&lt;/script&gt;
    &lt;h1&gt;D3 CSV Example&lt;/h1&gt;

&lt;!-- end snippet --&gt;

[JSFiddle Example][1]

`checkLineIntersection(...)` compute crossing point for given segment

yes, it took me 9 days to understand and do it:

What I did (in an awful manner), I calculated crossing point of every extremity of negative and positive set, created a new set of point for area and plotted those areas.

My first idea was to compute actual data entry of every crossing point, but I discovered that they do not match crossing point in polar coordinate.

I don&#39;t know if this is any use to you.

The only thing I learned is : You can&#39;t interpolate between two point in polar coordinate with straight lines.

Good luck


  [1]: https://jsfiddle.net/qLykpsr9/

</details>



huangapple
  • 本文由 发表于 2023年7月24日 17:16:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/76752999.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定