Need to have two different colors in a linear gradient after a specific (x, y) point

China☆狼群 提交于 2021-02-11 13:20:07

问题


I am trying to achieve this:

with the line path & shadow's transition in sync. And I am able to do that with the help of this answer,

but I am trying to figure out a way to append the red color gradient area after a certain (x, y) point (in this case (48, 0)).

I filtered the graph data and have two arrays (x-axis points -> from 0 to 48 and from 48 to 60), but then don't know how to apply the data in attrTween function.

Here's the stackblitz link.

Here's the shadow function:

    // Blue
    const colorArray1 = [
      ["rgb(8, 141, 218)", "0.8"],
      ["rgb(8, 141, 218)", "0.5"],
      ["rgb(23, 38, 65)", "0.3"]
    ];
    // Red
    const colorArray2 = [
      ["rgb(253, 17, 57)", "0.8"],
      ["rgb(253, 17, 57)", "0.4"],
      ["rgb(23, 38, 65)", "0.3"]
    ];
    const id: string = isShadowBefore48Hr ? "grad1" : "grad2";
    const data: Array<{}> = isShadowBefore48Hr
      ? this.graphData.filter(d => d.hrCount <= 48)
      : this.graphData.filter(d => d.hrCount >= 48);
    const angle: number = isShadowBefore48Hr ? -15 : 0;

    const defs = this.g.append("defs");
    const grad = defs
      .append("linearGradient")
      .attr("id", id)
      .attr("x1", "0%")
      .attr("x2", "0%")
      .attr("y1", "0%")
      .attr("y2", "100%")
      .attr("gradientTransform", "rotate(" + angle + ")");
    grad
      .selectAll("stop")
      .data(isShadowBefore48Hr ? colorArray1 : colorArray2)
      .enter()
      .append("stop")
      .style("stop-color", (d: any) => {
        return d[0];
      })
      .style("stop-opacity", (d: any) => {
        return d[1];
      })
      .attr("offset", (d: any, i: any) => {
        return 100 * (i / 2) + "%";
      });
    const area = d3
      .area()
      .y0(this.height)
      .y1((d: any) => d.y)
      .x((d: any) => d.x);
    this.g
      .append("path")
      .attr("fill", "url(#" + id + ")")
      .transition()
      .duration(5000)
      .attrTween("d", (d: any) => {
        const line = d3.select(".line").node(); // Find the line graph
        const length = line.getTotalLength(); // Get length
        const i = d3.interpolate(0, length); // Interpolator function over length
        const dataPoints = []; // Array to hold accumulated data points
        return (t: any) => {
          dataPoints.push(line.getPointAtLength(i(t))); // On every iteration, get a point on the line
          return area(dataPoints);
        };
      });
  } 

回答1:


As suggested in my comment you should have clear two paths (as you can't have only one gradient for both) and maybe reveal the chart by adding a clip path (of the entire chart area) that changes its width from 0 to chart width, essentially showing a chart in transition. This is old code for D3.js version4, slightly adapted with your gradients (you'll figure the rest out):

<html>
<head>
    <meta charset="utf-8" />
<style>
.line {
  fill: none;
  stroke: steelblue;
  stroke-width: 2px;
}

pre#data {
  display: none;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<pre id="data">
date,close
1-May-12,58.13
30-Apr-12,53.98
27-Apr-12,67.00
26-Apr-12,89.70
25-Apr-12,99.00
24-Apr-12,130.28
23-Apr-12,166.70
20-Apr-12,234.98
19-Apr-12,345.44
18-Apr-12,443.34
17-Apr-12,543.70
16-Apr-12,580.13
13-Apr-12,605.23
12-Apr-12,622.77
11-Apr-12,626.20
10-Apr-12,628.44
9-Apr-12,636.23
5-Apr-12,633.68
4-Apr-12,624.31
3-Apr-12,629.32
2-Apr-12,618.63
30-Mar-12,599.55
29-Mar-12,609.86
28-Mar-12,617.62
27-Mar-12,614.48
26-Mar-12,606.98
</pre>
<script>
    let animationDuration = 5000;
    // set the dimensions and margins of the graph
    var margin = {
        top: 20,
        right: 20,
        bottom: 30,
        left: 50
      },
      width = 720 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

    // parse the date / time
    var parseTime = d3.timeParse("%d-%b-%y");

    // set the ranges
    var x = d3.scaleTime().range([0, width]);
    var y = d3.scaleLinear().range([height, 0]);

    // define the area
    var area = function(datum, boolean) {
      return d3.area()
        .y0(height)
        .y1(function(d) {
          return boolean ? y(d.close) : y(d.close);
        })
        .x(function(d) {
          return boolean ? x(d.date) : 0;
        })
        (datum);
    }

    // define the line
    var valueline = d3.line()
      .x(function(d) {
        return x(d.date);
      })
      .y(function(d) {
        return y(d.close);
      });

    // append the svg obgect to the body of the page
    // appends a 'group' element to 'svg'
    // moves the 'group' element to the top left margin
    var svg = d3.select("body").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 + ")");

    var defs = svg.append("defs");

    var clip = defs.append("clipPath")
      .attr("id", "clip");
    var clipRect = clip.append("rect")
      .attr("width", 0)
      .attr("height", height);

// Blue
  const colorArray1 = [
    ["rgb(8, 141, 218)", "0.8"],
    ["rgb(8, 141, 218)", "0.5"],
    ["rgb(23, 38, 65)", "0.3"]
  ];
  // Red
  const colorArray2 = [
    ["rgb(253, 17, 57)", "0.8"],
    ["rgb(253, 17, 57)", "0.4"],
    ["rgb(23, 38, 65)", "0.3"]
  ];

  function addGradient(isShadowBefore48Hr) {
      const id = isShadowBefore48Hr ? "grad1" : "grad2";
        const angle = isShadowBefore48Hr ? -15 : 0;
        const grad = defs
        .append("linearGradient")
        .attr("id", id)
        .attr("x1", "0%")
        .attr("x2", "0%")
        .attr("y1", "0%")
        .attr("y2", "100%")
            .attr("gradientTransform", "rotate(" + angle + ")");    
        grad
      .selectAll("stop")
      .data(isShadowBefore48Hr ? colorArray1 : colorArray2)
      .enter()
      .append("stop")
      .style("stop-color", (d) => {
        return d[0];
      })
      .style("stop-opacity", (d) => {
        return d[1];
      })
      .attr("offset", (d, i) => {
        return 100 * (i / 2) + "%";
      });
  }

  addGradient(true);
  addGradient(false);

    var data = d3.csvParse(d3.select("pre#data").text());

    data.reverse();

    // format the data
    data.forEach(function(d) {
      d.date = parseTime(d.date);
      d.close = +d.close;
    });

    // scale the range of the data
    x.domain(d3.extent(data, function(d) {
      return d.date;
    }));
    y.domain([0, d3.max(data, function(d) {
      return d.close;
    })]);

    // add first area
    svg.append("path")
      .data([data.filter(function(item) { return item.date <= parseTime("23-Apr-12") } ) ])
      .attr("class", "area")
      .attr("d", d => area(d, true))
      //.attr("fill", "lightsteelblue")
    .attr("fill", "url(#" + "grad1" + ")")
      .attr("clip-path", "url(#clip)");

    // add second area
    svg.append("path")
      .data([data.filter(function(item) { return item.date >= parseTime("23-Apr-12") } ) ])
      .attr("class", "area")
      .attr("d", d => area(d, true))
      //.attr("fill", "red")
        .attr("fill", "url(#" + "grad2" + ")")
      .attr("clip-path", "url(#clip)");

    // add the valueline path.
    svg.append("path")
      .data([data])
      .attr("class", "line")
      .attr("d", valueline)
      .attr("clip-path", "url(#clip)");

    // add the X Axis
/*  svg.append("g")
      .attr("transform", "translate(0," + height + ")")
      .call(d3.axisBottom(x));
*/
    // add the Y Axis
    svg.append("g")
      .call(d3.axisLeft(y));

    clipRect.transition()
      .duration(5000)
      .ease(d3.easeLinear)
      .attr("width", width)
</script>
</body>
</html>


来源:https://stackoverflow.com/questions/65683163/need-to-have-two-different-colors-in-a-linear-gradient-after-a-specific-x-y-p

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!