d3.js - 如何在d3中绘制两条不同长度(不同x值)的曲线之间的区域/填充
问题描述
问题
我正在尝试构建一个折线图,其中我在两条线之间着色(不同的颜色取决于哪条线在另一条线之上)。这在所有测试的情况下都非常适合线性弯曲。
但是,这需要与实际弯曲一起使用(例如curveBasis
,如下所示)。如果线条具有完全相同的 x 值,则此方法非常有效;但是我们有以下情况:a) 一条线比另一条线长/短和/或 b) 一条线可能在另一条线没有丢失的中间缺少一个或多个 x 值。这是因为在两点之间绘制线的方式会根据非线性时之前/之后的点而变化。
一般来说,我理解为什么会发生这种情况;但我很难找到一个好的解决方案来让它真正按照我想要的方式工作。我很想至少指出正确的方向或给出一些想法(我考虑的一个想法列在底部)!
例子
这是它的工作原理curveLinear
(看起来不错):
curveBasis
如果两条线的 x 值相同(看起来不错),则如下所示:
curveBasis
如果两条线的 x 值不同(看起来不太好),它的实际外观如下:
当前代码/策略
这是当前的策略(请注意,我将线条称为好/坏,其中顶部的好线导致绿色填充,顶部的坏线导致红色填充)(删除了一些内容,如类名等以减少混乱) :
// I also set the domain and range appropriately for x/y--not shown here:
const x = d3.scaleTime();
const y = d3.scaleLinear();
// 1. Draw the lines "normally" (this is in a loop to build each line)
const lineData = d3.line()
.defined(point => !isNaN(point.y))
.x(point => x(point.x))
.y(point => y(point.y))
.curve(d3[lineCurve]);
d3Chart.append('path')
.datum(points)
.attr('d', lineData);
// 2. "Normalize" lines into the following format for each point (logic not shown here): {x, goodY, badY}
// Bind this data to a new svg group
const areaElement = d3Chart.append('g').datum(normlaizedData);
// 3. Clip paths and area paths
// Create the green fill clip path.
const goodLineClipPathId = `good-line-clip-path`;
areaElement.append('clipPath')
.attr('id', goodLineClipPathId)
.append('path')
.attr('d', d3.area()
.curve(lineCurve)
.x(point => x(point.x))
.y0(0)
.y1(point => y(point.badY))
);
// Create the red fill clip path.
const badLineClipPathId = `bad-line-clip-path`;
areaElement.append('clipPath')
.attr('id', badLineClipPathId)
.append('path')
.attr('d', d3.area()
.curve(lineCurve)
.x(point => x(point.x))
.y0(height)
.y1(point => y(point.badY))
);
// Create the red fill.
areaElement.append('path')
.attr('clip-path', `url(#${badLineClipPathId})`)
.attr('d', d3.area()
.curve(lineCurve)
.x(point => x(point.x))
.y0(point => y(point.goodY))
.y1(point => y(point.badY))
);
// Create the green fill.
areaElement.append('path')
.attr('clip-path', `url(#${goodLineClipPathId})`)
.attr('d', d3.area()
.curve(lineCurve)
.x(point => x(point.x))
.y0(point => y(point.badY))
.y1(point => y(point.goodY))
);
深思熟虑的解决方案
我的一个想法是“克隆”确切的 svg 线,但切断开始/结束(使线的其余部分保持不变)并将这些线用作区域的顶部/底部(并用直线垂直关闭末端行);但是路径数据本身使用了弯曲,所以改变开始/结束仍然会影响线(除非有办法解决这个问题)。
这是我想到的另一个想法:不要“规范化”线条并创建额外的剪辑来“切断”末端(在屏幕截图上绘制的垂直黑线处);但即使我这样做了,仍然会有问题(如箭头所示)。
解决方案
这不是一个很好的解决方案,但我发布是因为它是一个解决方案(或至少是部分解决方案)。
在没有深入细节的情况下,我注意到问题实际上有两个可能的原因(一个或两个都可能导致间隙或出血):
- 这些行不会以相同的 x 值开始和/或结束。
- 这些行不共享 x 值与实际 y 值的 1 对 1 映射(换句话说,两行中至少有一个是“缺失”另一行中没有缺失的相应 x 值的 ay 值) .
“解决方案”
注意:我只用curveBasis
--other curving types 对此进行了测试,可能不会以同样的方式工作。
- “填充”缺失的 y 值(修复了上面的原因 #2)——我通过在最近的左/右非缺失点之间进行线性插值来做到这一点(如果缺失值位于一行的末尾或开头,则最近的非- 缺失的点值被重新使用)。在标准化行和原始行中都执行此操作,但不要在原始行中的点和不在标准化行中的点之间进行插值 - 在这种情况下只需重新使用(参见下面的示例)。
- “一式三份”标准化的开始/结束点(修复了上面的原因#1)——我通过在标准化数据开始和结束行数据的 x 值处将同一点再克隆两次来做到这一点。只需要在您的原始(非规范化)行中执行此操作 - 规范化行已经开始/结束于它们需要的位置。这适用于
curveBasis
,因为曲线考虑了哪些点 - 这个解决方案基本上强制一种线“结束”,即使它在一条线的中间(下面的文档引用源):
[curveBasis] 使用指定的控制点生成三次基样条。第一个和最后一个点是三重的,这样样条线从第一个点开始,到最后一个点结束......
例子
第 1 行(x 值):
[3, 4, 5, 7, 8, 9, 10, 11]
原来的
[3, 4, 5, 6 (null y), 7, 8, 9, 10, 11]
标准化,预操作
[3, 4, 5, 6 (interpolated y), 7, 8, 9, 10, 11]
归一化,后操作
[3, 3, 3, 4, 5, 6 (interpolated y), 7, 8, 9, 10, 11, 11, 11]
原创,后期处理
请注意,对于第 1 行,我们可以跳过将起点/终点重复三次,因为它们已经是原始行的起点/终点
第 2 行(x 值):
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 13]
原来的
[3, 4, 5, 6, 7, 8, 9, 10, 11 (null y)]
标准化,预操作
[3, 4, 5, 6, 7, 8, 9, 10, 11 (re-used y from 10)]
归一化,后操作
[1, 2, 3, 3, 3, 4, 5, 6, 7, 8, 9, 10, 11 (re-used y from 10), 11 (re-used y from 10), 11 (re-used y from 10), 13]
原始的,后期操作(注意我们不会在 10 和 13 之间进行插值以获得 11,因为 13 不存在于标准化行中)
它是什么样子的?
不是那么好 - 但是,嘿,没有间隙或出血!箭头指向我们“三重”点的位置,以迫使曲线“结束”在那里。
我们要使用这个吗?
不见得。我仍在寻找更好的解决方案;但这是我迄今为止想出的。
推荐阅读
- mongodb - 来自 $lookup 的动态
- function - Try/Catch 语句似乎不起作用
- javascript - 如何让我的网站预加载器在消失前持续 3 秒,即使页面已加载(至少停留 3 秒)
- .net - 包括设备 SDK 的视觉工作室
- javascript - '错误类型错误:无法读取未定义的属性'concat''
- python - 在字段中使用逗号读取 csv 文件
- perl - 警告:从当前目录名称猜测 NAME [src]
- algorithm - JavaScript 中给定字符串的最大可能回文子串
- r - 如何在 R 中更改 highcharter 中的图例比例?
- xpath - XPath - 选择具有确切数量的特定子节点的元素