如何在d3.js中面积图的任意两点之间添加非线性曲线

如何解决如何在d3.js中面积图的任意两点之间添加非线性曲线

我最近开始使用 d3.js。我正在 d3 中制作一个堆积面积图,它看起来类似于下图,

stacked area chart

const stack = d3.stack().keys(["aData","bData"]);
const stackedValues = stack(data);
const stackedData = [];

stackedValues.forEach((layer,index) => {
  const currentStack = [];
  layer.forEach((d,i) => {
  currentStack.push({
   values: d,year: data[i].year
  });
});
 stackedData.push(currentStack);
 });


      const yScale = d3
.scaleLinear()
.range([height,0])
.domain([0,d3.max(stackedValues[stackedValues.length - 1],dp => dp[1])]);
const xScale = d3
       .scaleLinear()
       .range([0,width])
       .domain(d3.extent(data,dataPoint => dataPoint.year));

 const area = d3
             .area()
            .x(dataPoint => xScale(dataPoint.year))
            .y0(dataPoint => yScale(dataPoint.values[0]))
            .y1(dataPoint => yScale(dataPoint.values[1]));

    const series = grp
       .selectAll(".series")
       .data(stackedData)
       .enter()
      .append("g")
      .attr("class","series");

   series
    .append("path")
     .attr("transform",`translate(${margin.left},0)`)
    .style("fill",(d,i) => color[i])
    .attr("stroke","steelblue")
   .attr("stroke-linejoin","round")
   .attr("stroke-linecap","round")
  .attr("stroke-width",strokeWidth)
 .attr("d",d => area(d));

我要求能够在任意两点之间添加非线性曲线。我制作了一个非常基本的大纲图来解释我的观点。

outline chart

我尝试使用 curve 函数,但它将整条线更改为提供的曲线(这里是示例代码 https://codepen.io/saif_shaik/pen/VwmqxMR),我只需要在两条曲线之间添加一条非线性曲线点。有什么办法可以做到这一点吗?

解决方法

我通过删除以下内容简化了您的路径:https://yqnn.github.io/svg-path-editor/

您可以使用该编辑器来玩 d-path,并了解您想在何处/如何更改该 d-path 字符串

复制下面的 d-path 并将其粘贴到:https://yqnn.github.io/svg-path-editor/

<svg height="300" width="600"><g transform="translate(30,0)">
<g transform="translate(-28.5,-90)">
<g class="series">
<path stroke="steelblue" stroke-linejoin="round" 
      stroke-linecap="round" stroke-width="1.5" 
      d="M0 257 15 250C30 242 61 227 91 216C122 205 152 197 182 199C213 200 243 211 274 208
         C304 205 334 188 365 169C395 151 425 129 456 116C486 102 517 96 532 93
         L547 90 547 280 532 280C517 280 486 280 456 280C425 280 395 280 365 280
         C334 280 304 280 273 280C243 280 213 280 182 280C152 280 122 280 91 280
         C61 280 30 280 15 280L0 280Z" 
      style="fill: lightgreen;">
</path></g></g></g></svg>

,

您可以创建自定义曲线生成器。这可以采取多种不同的形式。我将通过调整现有的 d3 曲线之一并使用其点方法创建自定义曲线来回收以前的 example

通常自定义曲线在所有点之间应用相同的曲线,为了允许不同类型的线连接点,我将在下面的片段中跟踪当前点的索引。

以下代码段中的自定义曲线由采用索引值的父函数返回。该索引值指示哪个数据点应该在它和下一个数据点之间使用不同的曲线。这两种类型的曲线都是手工制作的 - 有些类型的曲线会比其他类型的曲线更具挑战性。

这会产生如下结果:

enter image description here

function generator(i,context) {
  var index = -1;
  return function(context) {
    var custom = d3.curveLinear(context);
    custom._context = context;
    custom.point = function(x,y) {
      x = +x,y = +y;
      index++;
      switch (this._point) {
        case 0: this._point = 1; 
          this._line ? this._context.lineTo(x,y) : this._context.moveTo(x,y);
          this.x0 = x; this.y0 = y;        
          break;
        case 1: this._point = 2;
        default: 
          // curvy mountains between values if index isn't specified:
          if(index != i+1) {
            var x1 = this.x0 * 0.5 + x * 0.5;
            var y1 = this.y0 * 0.5 + y * 0.5;
            var m = 1/(y1 - y)/(x1 - x);
            var r = -100; // offset of mid point.
            var k = r / Math.sqrt(1 + (m*m) );
            if (m == Infinity) {
              y1 += r;
            }
            else {
              y1 += k;
              x1 += m*k;
            }     
            this._context.quadraticCurveTo(x1,y1,x,y); 
            // always update x and y values for next segment:
            this.x0 = x; this.y0 = y;        
            break;
          }
          // straight lines if index matches:
          else {
            // the simplest line possible:
            this._context.lineTo(x,y);
            this.x0 = x; this.y0 = y;  
            break;         
          }
      }
    }
    return custom;
  }
}


var svg = d3.select("body")
  .append("svg")
  .attr("width",500)
  .attr("height",300);
  
  
var data = d3.range(10).map(function(d) {
  var x = d*40+40;
  var y = Math.random() * 200 + 50;
  
  return { x:x,y:y }
  
})


var line = d3.line()
  .curve(generator(3))  // striaght line between index 3 and 4.
  .x(d=>d.x)
  .y(d=>d.y)
  
  
svg.append("path")
  .datum(data)
  .attr("d",line)
  .style("fill","none")
  .style("stroke-width",3)
  .style("stroke","#aaa")
  
svg.selectAll("circle")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx",d=>d.x)
  .attr("cy",d=>d.y)
  .attr("r",2)
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>

如果您将上下文指定为 generator() 的第二个参数,则此行也适用于画布。这里可以进行各种改进 - 但是基本原则应该具有相当的适应性。

,

只需更改曲线类型即可。 curveBasis 近似于点之间的曲线,但它不会与它们交叉。因此,请使用通常使用的“curveCardinal”类型,或者甚至是“curveCatmullRom”,它们是通过数据点的曲线。

// Fake data
const data = [
  {
    year: 2000,aData: 50,bData: 300
  },{
    year: 2001,aData: 150,bData: 50
  },{
    year: 2002,aData: 200,bData: 100
  },{
    year: 2003,aData: 130,{
    year: 2004,aData: 240,bData: 80
  },{
    year: 2005,aData: 380,bData: 10
  },{
    year: 2006,aData: 420,bData: 200
  }
];
const color = ["lightgreen","lightblue"];
// Create SVG and padding for the chart
const svg = d3
  .select("#chart")
  .append("svg")
  .attr("height",300)
  .attr("width",600);

const strokeWidth = 1.5;
const margin = { top: 0,bottom: 20,left: 30,right: 20 };
const chart = svg.append("g").attr("transform",`translate(${margin.left},0)`);

const width = +svg.attr("width") - margin.left - margin.right - strokeWidth * 2;
const height = +svg.attr("height") - margin.top - margin.bottom;
const grp = chart
  .append("g")
  .attr("transform",`translate(-${margin.left - strokeWidth},-${margin.top})`);

// Create stack
const stack = d3.stack().keys(["aData","bData"]);
const stackedValues = stack(data);
const stackedData = [];
// Copy the stack offsets back into the data.
stackedValues.forEach((layer,index) => {
  const currentStack = [];
  layer.forEach((d,i) => {
    currentStack.push({
      values: d,year: data[i].year
    });
  });
  stackedData.push(currentStack);
});

// Create scales
const yScale = d3
  .scaleLinear()
  .range([height,0])
  .domain([0,d3.max(stackedValues[stackedValues.length - 1],dp => dp[1])]);
const xScale = d3
  .scaleLinear()
  .range([0,width])
  .domain(d3.extent(data,dataPoint => dataPoint.year));

const area = d3
  .area()
  .x(dataPoint => xScale(dataPoint.year))
  .y0(dataPoint => yScale(dataPoint.values[0]))
  .y1(dataPoint => yScale(dataPoint.values[1]))
//.curve(d3.curveBasis)
.curve(d3.curveCardinal)
//.curve(d3.curveCatmullRom.alpha(0.5))
;

const series = grp
  .selectAll(".series")
  .data(stackedData)
  .enter()
  .append("g")
  .attr("class","series");

series
  .append("path")
  .attr("transform",0)`)
  .style("fill",(d,i) => color[i])
  .attr("stroke","steelblue")
  .attr("stroke-linejoin","round")
  .attr("stroke-linecap","round")
  .attr("stroke-width",strokeWidth)
  .attr("d",d => area(d));

const dotsGreen = chart
  .selectAll(".gdot")
  .data(data)
  .enter()
  .append("circle")
  .attr("class","gdot")
  .attr("cx",function(d) { 
    return xScale(d.year)
  })
  .attr("cy",d => yScale(d.aData))
  .attr("r",4)
  .attr("fill","green");

const dotsBlue = chart
  .selectAll(".bdot")
  .data(data)
  .enter()
  .append("circle")
  .attr("class","bdot")
  .attr("cx",d => yScale(d.aData+d.bData))
  .attr("r","blue");

// Add the X Axis
chart
  .append("g")
  .attr("transform",`translate(0,${height})`)
  .call(d3.axisBottom(xScale).ticks(data.length));

// Add the Y Axis
chart
  .append("g")
  .attr("transform",0)`)
  .call(d3.axisLeft(yScale));
#chart {
  text-align: center;
  margin-top: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<div id="chart"></div>

从你的代码分叉出来的工作代码Pen也在这里:

changed CodePen pen

注意:我已经添加了两个圆圈组(不是 SVG G 元素)的代码,您可以简单地将其删除。它们仅用于证明根据脚本化的曲线类型在数据点附近绘制曲线。

您的草图看起来您希望在两个给定点之间有一条曲线。为此,您必须更改曲线调用以在函数中使用运行索引(例如 (d,i)),该函数将根据所选索引(或 indeces)返回不同的曲线类型。

添加:您可以在这里使用不同的 D3.js 曲线类型:

D3 curve explorer

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?