nteract / semiotic

A data visualization framework combining React & D3
https://semioticv1.nteract.io/
Other
2.43k stars 133 forks source link

XYFrame Heatmap: Certain chart size & bin combinations result in an extra bin. #542

Closed ShadyStego closed 2 years ago

ShadyStego commented 3 years ago

I have the following code:

const testData = [
      {
        coordinates: [
          { x: 0, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 0, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 10, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 10, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 20, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 20, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 30, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 30, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 40, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 40, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 50, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 50, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 60, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 60, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 70, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 70, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 80, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 80, y: 90, count: Math.ceil(Math.random() * 100) },

          { x: 90, y: 0, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 10, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 20, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 30, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 40, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 50, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 60, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 70, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 80, count: Math.ceil(Math.random() * 100) },
          { x: 90, y: 90, count: Math.ceil(Math.random() * 100) },
        ],
      },
    ]

    const maxCount = Math.max(...clusterHeatmapData[0].coordinates.map((c: any) => c.count))

    const color = scaleLinear<string>().domain([0, maxCount]).range(['cadetblue', 'tomato'])

    const frameProps = {
      summaries: testData,
      size: [276, 276],
      margin: { left: 10, bottom: 10, right: 10, top: 10 },
      summaryType: {
        type: 'heatmap',
        xBins: 10,
        yBins: 10,
        binValue: (d: any) => d.count,
      },
      xAccessor: 'x',
      yAccessor: 'y',
      yExtent: [0],
      xExtent: [0],
      summaryStyle: function (e: any) {
        return {
          fill: e.binItems[0] && color(e.binItems[0].count),
          stroke: '#ccc',
          strokeWidth: 0.5,
        }
      },
    }
    return <XYFrame {...frameProps} />

The data is 10 x 10. BinX & binY are set to 10 bins. For some reason there are extra binY and binX generated:

image

If I change the size slightly to , let's say, size = [268, 268], the chart is as expected:

image

My guess is javascript floating point is the culprit.

emeeks commented 2 years ago

I found the error in the code and it should get fixed with #571. This will be fixed in Semiotic 2, though. I'll see if I can find the time to patch it back to v1.