我有一个散点图(使用apexchart),我正在尝试在将数据(数组中的对象)添加到图表之前准备数据。
我的问题是,我经常有一些数据在 x 轴和 y 轴上具有完全相同的值。这意味着它们将相互重叠,并且只会显示一个项目。
我首先使用 Math.random() 为每个重复的值添加了一个简单的解决方案,但这意味着每次更新图表时它们都会稍微重新定位,这会让用户感到困惑。
因此,我想要实现的是始终为每个项目添加相同的"抖动"/更改。所以我的想法是这样做:
//1. The first three duplicates should add 0.05 to the x value. So it will say:
item1.x = 10 //original value
item2.x = 10.05
item3.x = 10.1
item4.x = 10.15
//2. Then, If more duplicates than 3, the next 3 should reduce 0.05 of the original value:
item5.x = 9.95
item6.x = 9.90
item7.x = 9.85
//If more than 7 items with the same value, then just use the original value.
如何成为实现这一目标的最佳方法?现在我有重复的值,我可以添加"抖动",但是我怎样才能让它保持计数并按照上面想要的方式做?
我设法过滤掉了重复项,但后来我卡住了:
const duplicates = AllItems.filter((val, i, self) => self.findIndex(item => item.x == val.x && item.y == val.y) != i);
有什么想法吗?
考虑使用内置Map
。在这种情况下,它的性能更好,因为键值对的频繁添加和删除。
也就是说,值得一提的是,它的作用类似于哈希表实现。这个想法是通过每次发现一个出现时在其count
中添加一个来跟踪给定x
发生的次数。对于代码段中的points
示例,请考虑以下映射表:
╔═══════╦═══════════════════════════════╗
║ x ║ 1 2 3 4 5 6 7 8 9 10 ║
╠═══════╬═══════════════════════════════╣
║ count ║ 5 1 1 1 7 1 1 1 1 10 ║
╚═══════╩═══════════════════════════════╝
请注意,x
为1
的点的y
值不会更改,因为其计数为 5。而5
和10
会,因为它们的计数大于或等于MIN_TIMES(7)。逻辑如下:
- 构建地图以存储给定
x
点重复其y
坐标的次数。 - 仅将每个
x
存储在数组中大于或等于MIN_TIMES
的那些 - 同时,将计数更改为 0 以开始计数
- 映射
points
数组中的每个点,并根据您的逻辑将xRepeatedAtLeastMinTimes
中的点转换为递增或递减。
const points=[{x:1,y:1},{x:1,y:1},{x:1,y:1},{x:1,y:1},{x:1,y:1},{x:2,y:2},{x:3,y:3},{x:4,y:4},{x:5,y:5},{x:5,y:5},{x:5,y:5},{x:5,y:5},{x:5,y:5},{x:5,y:5},{x:5,y:5},{x:6,y:6},{x:7,y:7},{x:8,y:8},{x:9,y:9},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10},{x:10,y:10}];
const m = new Map(),
xRepeatedAtLeastMinTimes = new Array(),
INTERVAL = 0.05,
MIN_TIMES = 7
// Construct map
points.forEach(point => m.set(point.x, m.has(point.x) ? (m.get(point.x) + 1) : 1))
// Get x coordinates that happens at least MIN_TIMES and change its count to 0
for (let [x, count] of m.entries())
count >= MIN_TIMES && xRepeatedAtLeastMinTimes.push(x), m.set(x, 0)
// Map each point and check condition based on count stored on Map
let result = points.map(point => {
let x = point.x
if (xRepeatedAtLeastMinTimes.includes(x)) {
let count = m.get(x)
m.set(x, count + 1)
return count === 0 ? point :
count <= 3 ? { ...point, x: x + count*INTERVAL } :
count <= 6 ? { ...point, x: x - (count - 3)*INTERVAL } : point
}
return point
})
console.log(result)
假设有这个输入向量:
coordinates = [{x:1,y:2},{x:1,y:2},{x:10,y:1},{x:1,y:2},{x:10,y:1}]
而这个增量向量:
deltas = [0.05,0.1,0.15,-0.05,-0.1,-0.15]
此代码检索重复项,然后将增量添加到其中(如果重复项超过 6 个,则添加 0):
coordinates.map(
(e,i,v) => {
ret = !e.d
? v.reduce(
(acc,d,j) => {
if (!d.d && j != i && d.x === e.x && d.y === e.y) {
d.d=1
acc.push(d)
}
return acc
},
[]
)
: []
e.d =1
return ret
}
)
.filter(e => e.length)
.forEach(e => e.forEach((c,i) => c.x += deltas[i] || 0))
添加用于跟踪重复项的"d"属性应被删除:
coordinates.forEach(e => delete e.d)
结果是:
coordinates
(5) [{…}, {…}, {…}, {…}, {…}]
0: {x: 1, y: 2}
1: {x: 1.05, y: 2}
2: {x: 10, y: 1}
3: {x: 1.1, y: 2}
4: {x: 10.05, y: 1}
length: 5
我认为值得将处理抖动的代码与使用该抖动更新点的代码分开。 这将使您能够尝试不同的抖动函数(阿基米德螺旋会很好),但仍然保持主逻辑不变。 下面是执行此操作的一个版本:
const updatePoints = (jitterAlgo) => (points) =>
points .reduce (({res, dups}, {x, y}) => {
const key = `${x}:${y}`
const matches = dups [key] || (dups [key] = [])
const val = jitterAlgo (matches .length, {x, y})
matches .push (val)
res .push (val)
return {res, dups}
}, {res: [], dups: {}}) .res
const customJitter = (len, {x, y, ...rest}) =>
len == 0 || len > 6
? {x, y, ... rest}
: len < 3
? {x: x + len * .05, y, ... rest}
: {x: x - len * .05, y, ... rest}
const points = [{"x": 1, "y": 6}, {"x": 3, "y": 5}, {"x": 3, "y": 5}, {"x": 5, "y": 4}, {"x": 3, "y": 5}, {"x": 3, "y": 5}, {"x": 3, "y": 5}, {"x": 1, "y": 9}, {"x": 5, "y": 4}, {"x": 5, "y": 4}, {"x": 3, "y": 5}, {"x": 2, "y": 9}, {"x": 3, "y": 5}, {"x": 3, "y": 5}, {"x": 5, "y": 4}, {"x": 2, "y": 8}]
console .log (updatePoints (customJitter) (points))
.as-console-wrapper {max-height: 100% !important; top: 0}
我确实认为,您目前的逻辑,仅在x
方向上抖动,可能不会像既x
又y
的逻辑那样令人愉快。