如何根据条件用不同颜色填充区域?

How can I fill an area with different colors based on conditions?

嗨,数据可视化爱好者,

Codepen 在这里:https://codepen.io/shanyulin/pen/ZEXNgOb

我正在尝试根据不同的条件填充D3区域的颜色。我有两组数据climate_data(绿色)和obs_data(红色),我相应地画了两条线。

我想在两条线之间添加区域,如下所示:

使用以下代码:

this.svg
.append("path")
.datum(this.data)
.attr("transform", this.x_translate)
.attr("fill", this.obs_data_color)
.attr("stroke", "none")
.attr("fill-opacity", opacity)
.attr("stroke-width", 0)
.attr(
  "d",
  d3
    .area()
    .curve(curve)
    .x((d) => {
      return this.x_scale(new Date(d.time));
    })
    .y0((d) => {
      return this.y_scale(d.climate_data);
    })
    .y1((d) => {
      return this.y_scale(d.obs_data);
    })

但是我想设置不同的颜色,一种在绿线上方,一种在下方。

我提到了这个postD3 Area fill with different color based on conditions

但输出看起来很奇怪(如红色方块所示):

有谁知道如何解决这个问题? 任何提示将不胜感激。谢谢!

这里是一个使用 clipPaths, based on this difference chart by Mike Bostock.

的例子

<!DOCTYPE html>
<html>

<head>
  <meta charset="UTF-8">
  <script src="https://d3js.org/d3.v7.js"></script>
</head>

<body>
  <div id="chart"></div>

  <script>
    // set up
    const margin = { top: 10, right: 10, bottom: 50, left: 50 };

    const width = 500 - margin.left - margin.right;
    const height = 300 - margin.top - margin.bottom;

    const svg = d3.select('#chart')
      .append('svg')
        .attr('width', width + margin.left + margin.right)
        .attr('height', height + margin.top + margin.bottom)
      .append('g')
        .attr('transform', `translate(${margin.left},${margin.top})`);

    // data
    const parseTime = d3.timeParse('%Y-%m-%d');
    const data = [
        { time: "2021-12-16", obs_data: 22.2, climate_data: 18.21 },
        { time: "2021-12-17", obs_data: 18.5, climate_data: 17.59 },
        { time: "2021-12-18", obs_data: 15.4, climate_data: 17.84 },
        { time: "2021-12-19", obs_data: 17.3, climate_data: 17.67 },
        { time: "2021-12-20", obs_data: 19.7, climate_data: 18.31 },
        { time: "2021-12-21", obs_data: 18.6, climate_data: 17.59 },
        { time: "2021-12-22", obs_data: 17.7, climate_data: 17.56 },
        { time: "2021-12-23", obs_data: 20, climate_data: 17.71 },
        { time: "2021-12-24", obs_data: 19.4, climate_data: 17.82 },
        { time: "2021-12-25", obs_data: 16.4, climate_data: 17.7 },
        { time: "2021-12-26", obs_data: 13.9, climate_data: 17.58 },
        { time: "2021-12-27", obs_data: 13.1, climate_data: 17.34 },
        { time: "2021-12-28", obs_data: 16.7, climate_data: 17.13 },
        { time: "2021-12-29", obs_data: 17.8, climate_data: 17.14 },
        { time: "2021-12-30", obs_data: 16, climate_data: 16.81 },
        { time: "2021-12-31", obs_data: 16, climate_data: 15.86 },
        { time: "2022-01-01", obs_data: 16.9, climate_data: 16.37 },
        { time: "2022-01-02", obs_data: 16.9, climate_data: 17.09 },
        { time: "2022-01-03", obs_data: 18.6, climate_data: 17.68 },
        { time: "2022-01-04", obs_data: 18, climate_data: 17.56 },
        { time: "2022-01-05", obs_data: 19.3, climate_data: 17.13 },
        { time: "2022-01-06", obs_data: 16.8, climate_data: 17.3 },
        { time: "2022-01-07", obs_data: 16.1, climate_data: 17.19 },
        { time: "2022-01-08", obs_data: 16.5, climate_data: 16.54 },
        { time: "2022-01-09", obs_data: 17.6, climate_data: 16.3 },
        { time: "2022-01-10", obs_data: 17.4, climate_data: 16.95 },
        { time: "2022-01-11", obs_data: 13.8, climate_data: 17.26 },
        { time: "2022-01-12", obs_data: 13.3, climate_data: 16.63 },
        { time: "2022-01-13", obs_data: 14, climate_data: 16.15 },
        { time: "2022-01-14", obs_data: 15.3, climate_data: 16.15 },
        { time: "2022-01-15", obs_data: 16.9, climate_data: 16.16 }
      ].map(({time, obs_data, climate_data}) => ({ time: parseTime(time), obs_data, climate_data }));

      // scales

      const x = d3.scaleTime()
          .domain(d3.extent(data, d => d.time))
          .range([0, width]);

      const y = d3.scaleLinear()
          .domain(d3.extent(data.flatMap(d => [d.obs_data, d.climate_data]))).nice()
          .range([height, 0]);

      // area generators

      // from the top of the chart to the line for climate
      const topToClimate = d3.area()
          .x(d => x(d.time))
          .y0(0)
          .y1(d => y(d.climate_data))
          .curve(d3.curveMonotoneX);

      // from the bottom of the chart to the line for climate
      const bottomToClimate = d3.area()
          .x(d => x(d.time))
          .y0(height)
          .y1(d => y(d.climate_data))
          .curve(d3.curveMonotoneX);

      // from the top of the chart to the line for obs
      const topToObs = d3.area()
          .x(d => x(d.time))
          .y0(0)
          .y1(d => y(d.obs_data))
          .curve(d3.curveMonotoneX);

      // from the bottom of the chart to the line for obs
      const bottomToObs = d3.area()
          .x(d => x(d.time))
          .y0(height)
          .y1(d => y(d.obs_data))
          .curve(d3.curveMonotoneX);

      // clip paths
      svg.append('clipPath')
          .attr('id', 'topToObs')
        .append('path')
          .attr('d', topToObs(data));

      svg.append('clipPath')
          .attr('id', 'bottomToObs')
        .append('path')
          .attr('d', bottomToObs(data));

      // areas

      // draw a blue area from the bottom of the chart to the blue line for climate.
      // the clip path makes any part of this area outside of the clip path invisible.
      // the clip path goes from the top of the chart to the red line for obs.
      // the result is that you can only see the blue area when it is above the obs
      // line and beneath the climate line.
      svg.append('path')
          .attr('fill', 'blue')
          .attr('opacity', 0.6)
          .attr('clip-path', 'url(#topToObs)')
          .attr('d', bottomToClimate(data));

      // draw a red area from the top of the chart to the blue line for climate.
      // the clip path makes any part of this area outside of the clip path invisible.
      // the clip path goes from the bottom of the chart to the red line for obs.
      // the result is that you can only see the read area when it is above the climate
      // line and beneath the obs line.
      svg.append('path')
          .attr('fill', 'red')
          .attr('opacity', 0.6)
          .attr('clip-path', 'url(#bottomToObs)')
          .attr('d', topToClimate(data));

      // lines

      // draw a blue line for climate
      svg.append('path')
          .attr('stroke', 'blue')
          .attr('fill', 'none')
          .attr('d', bottomToClimate.lineY1()(data));

      // draw a red line for obs
      svg.append('path')
          .attr('stroke', 'red')
          .attr('fill', 'none')
          .attr('d', bottomToObs.lineY1()(data));

      // axes

      svg.append('g')
          .attr('transform', `translate(0,${height})`)
          .call(d3.axisBottom(x).ticks(5, '%b %d'));

      svg.append('g')
          .call(d3.axisLeft(y));
  </script>
</body>

</html>