添加和减少重复值

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 ║
╚═══════╩═══════════════════════════════╝

请注意,x1 的点的 y 值不会改变,因为它的计数为 5。而 510会,因为它们的数量大于或等于 MIN_TIMES (7)。逻辑如下:

  1. 构建地图以存储给定 x 点重复其 y 坐标的次数。
  2. 在数组
  3. 中仅存储每个 x 发生次数大于或等于 MIN_TIMES 的那些
  4. 同时将count改为0开始计数
  5. 映射 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 方向上抖动,可能不如同时在 xy 方向上抖动的逻辑那么令人愉快。