添加和减少重复值
Add and reduce on duplicated values
我有一个散点图(使用 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);
有什么想法吗?
假设有这个输入向量:
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
考虑使用 Map
内置。它在这种情况下表现更好,因为经常添加和删除键值对。
这就是说,值得一提的是它的作用类似于哈希 table 实现。这个想法是通过每次找到一个给定的 x
时将其 count
加一来跟踪给定的 x
出现的次数。对于代码段中的 points
示例,请考虑以下映射 table:
╔═══════╦═══════════════════════════════╗
║ 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
的那些
- 同时将count改为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)
我认为将处理抖动的代码与使用该抖动更新点的代码分开是值得的。这将使您能够尝试不同的抖动函数(阿基米德螺线会很好),但仍保持主要逻辑不变。这是执行此操作的一个版本:
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
方向上抖动的逻辑那么令人愉快。
我有一个散点图(使用 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);
有什么想法吗?
假设有这个输入向量:
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
考虑使用 Map
内置。它在这种情况下表现更好,因为经常添加和删除键值对。
这就是说,值得一提的是它的作用类似于哈希 table 实现。这个想法是通过每次找到一个给定的 x
时将其 count
加一来跟踪给定的 x
出现的次数。对于代码段中的 points
示例,请考虑以下映射 table:
╔═══════╦═══════════════════════════════╗
║ 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
坐标的次数。 - 在数组 中仅存储每个
- 同时将count改为0开始计数
- 映射
points
数组中的每个点,并根据您的逻辑将xRepeatedAtLeastMinTimes
中的点转换为递增或递减。
x
发生次数大于或等于 MIN_TIMES
的那些
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)
我认为将处理抖动的代码与使用该抖动更新点的代码分开是值得的。这将使您能够尝试不同的抖动函数(阿基米德螺线会很好),但仍保持主要逻辑不变。这是执行此操作的一个版本:
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
方向上抖动的逻辑那么令人愉快。