Pre loader

paletteProvider and DataPointSelectionModifier for UniformHeatMap

Welcome to the SciChart Forums!

  • Please read our Question Asking Guidelines for how to format a good question
  • Some reputation is required to post answers. Get up-voted to avoid the spam filter!
  • We welcome community answers and upvotes. Every Q&A improves SciChart for everyone

WPF Forums | JavaScript Forums | Android Forums | iOS Forums

1
0

Hi scichart team,

Im unable to add a per point coloring to UniformHeatMap series using paletteProvider, it just doesnt seem to work

Also im using DataPointSelectionModifier and im only able to select one point at a time, unable to select multiple points onSelectionChanged and also drag selection is not working for multi select as well.

My goal is to select and highlight multiple cells/tiles in a UniformHeatMap series

please respond with a solution

Thanks

Version
5.0+
  • Jim Risen
    We will look into this as well
  • You must to post comments
0
0

Hello,
Unfortunately, those features are not yet supported in the UniformHeatMap.
We added corresponding tasks to our backlog
DataPointSelection support
PaletteProvider support

Also, we have discussed this within the team and tried to come up with an optimal solution for now.

So here is a workaround that uses an additional 2 layers of Rectangle Renderable Series that provide data point selection and highlighting.

Live Example at CodePen

See the Pen
Heatmap With DataPointSelection via Renderable Series
by Jim (@jim-risen)
on CodePen.

const GRID_SIZE = 50;
const CELL_SIZE = 1;
const INACTIVE_VALUE = -1; // marker for cells outside the wafer circle
const HIGHLIGHT_COLOR = "#FFD700";

// Scores are stored col-major: scores[col * GRID_SIZE + row].
// Both the heatmap transpose (buildZValues) and the selection-index decoding
// rely on this layout — keep these three functions in sync if you change it.
function buildScores(): Float64Array {
  const scores = new Float64Array(GRID_SIZE * GRID_SIZE);
  const center = (GRID_SIZE - 1) / 2;S
  const radius = GRID_SIZE / 2 - 0.5;
  let i = 0;
  for (let col = 0; col < GRID_SIZE; col++) {
    for (let row = 0; row < GRID_SIZE; row++) {
      const dx = col - center;
      const dy = row - center;
      scores[i++] =
        Math.sqrt(dx * dx + dy * dy) > radius ? INACTIVE_VALUE : Math.random();
    }
  }
  return scores;
}

// UniformHeatmapDataSeries.zValues expects zValues[row][col]; transpose to that shape.
function buildZValues(scores: Float64Array): number[][] {
  const zValues: number[][] = Array.from(
    { length: GRID_SIZE },
    () => new Array(GRID_SIZE)
  );
  let i = 0;
  for (let col = 0; col < GRID_SIZE; col++) {
    for (let row = 0; row < GRID_SIZE; row++) {
      zValues[row][col] = scores[i++];
    }
  }
  return zValues;
}

// Builds the full GRID_SIZE × GRID_SIZE rectangle grid used as the invisible
// hit-test layer. Col-major iteration here is what lets us decode dp.index back
// to (col, row) in the selection callback.
function buildRectangleDataSeries(wasmContext: any): XyNDataSeries {
  const size = GRID_SIZE * GRID_SIZE;
  const xValues = new Float64Array(size);
  const yValues = new Float64Array(size);
  const x1Values = new Float64Array(size).fill(CELL_SIZE);
  const y1Values = new Float64Array(size).fill(CELL_SIZE);
  let i = 0;
  for (let col = 0; col < GRID_SIZE; col++) {
    for (let row = 0; row < GRID_SIZE; row++) {
      xValues[i] = col * CELL_SIZE;
      // EColumnYMode.TopHeight uses yValue as the cell TOP (rect spans
      // [yValue - height, yValue]), so cell row r occupies y ∈ [r, r+1] —
      // matching heatmap row r at yStart=0, yStep=CELL_SIZE.
      yValues[i] = (row + 1) * CELL_SIZE;
      i++;
    }
  }
  return new XyNDataSeries(wasmContext, {
    valueNames: [EValueName.Y, EValueName.X1, EValueName.Y1],
    includeInYRange: [true, false, true],
    xValues,
    yValuesArray: [yValues, x1Values, y1Values],
    containsNaN: false
  });
}

function createAxes(wasmContext: any, surface: SciChartSurface): void {
  const padding = 0.5;
  const range = new NumberRange(-padding, GRID_SIZE * CELL_SIZE + padding);
  const axisOptions = {
    visibleRange: range,
    autoRange: EAutoRange.Never,
    drawLabels: false,
    drawMinorGridLines: false,
    drawMajorGridLines: false,
    drawMajorBands: false
  };
  surface.xAxes.add(new NumericAxis(wasmContext, axisOptions));
  surface.yAxes.add(new NumericAxis(wasmContext, axisOptions));
}

const initWaferMapChart = async (rootElement: string | HTMLDivElement) => {
  const scores = buildScores();
  const { sciChartSurface, wasmContext } = await SciChartSurface.create(
    rootElement,
    {}
  );

  createAxes(wasmContext, sciChartSurface);

  // Layer 1 — heatmap base. Renders cell scores as colour.
  sciChartSurface.renderableSeries.add(
    new UniformHeatmapRenderableSeries(wasmContext, {
      dataSeries: new UniformHeatmapDataSeries(wasmContext, {
        xStart: 0,
        xStep: CELL_SIZE,
        yStart: 0,
        yStep: CELL_SIZE,
        zValues: buildZValues(scores)
      }),
      colorMap: new HeatmapColorMap({
        minimum: INACTIVE_VALUE,
        maximum: 1,
        // Gradient stops are on the [0..1] offset that maps onto [minimum..maximum].
        // The gray plateau up to 0.49 keeps INACTIVE_VALUE (-1) visually distinct
        // from blue (low-but-active) cells.
        gradientStops: [
          { offset: 0, color: "rgba(50,50,50,1)" },
          { offset: 0.49, color: "rgba(50,50,50,1)" },
          { offset: 0.5, color: "#0000dc" },
          { offset: 0.75, color: "#00c800" },
          { offset: 1, color: "#dc0000" }
        ]
      }),
      useLinearTextureFiltering: false
    })
  );

  // Layer 2 — gold highlight overlay. Starts empty; rebuilt on every selection change.
  const highlightDataSeries = new XyNDataSeries(wasmContext, {
    valueNames: [EValueName.Y, EValueName.X1, EValueName.Y1],
    includeInYRange: [true, false, true],
    xValues: new Float64Array(0),
    yValuesArray: [
      new Float64Array(0),
      new Float64Array(0),
      new Float64Array(0)
    ],
    containsNaN: false
  });
  sciChartSurface.renderableSeries.add(
    new FastRectangleRenderableSeries(wasmContext, {
      columnXMode: EColumnMode.StartWidth,
      columnYMode: EColumnYMode.TopHeight,
      strokeThickness: 0,
      fill: HIGHLIGHT_COLOR,
      dataSeries: highlightDataSeries
    })
  );
  // Layer 3 — invisible hit-test grid. DataPointSelectionModifier needs a
  // rectangle/column-style series to hit; opacity:0 keeps it visually inert.
  const detectionSeries = new FastRectangleRenderableSeries(wasmContext, {
    columnXMode: EColumnMode.StartWidth,
    columnYMode: EColumnYMode.TopHeight,
    strokeThickness: 0,
    opacity: 0,
    dataSeries: buildRectangleDataSeries(wasmContext)
  });
  sciChartSurface.renderableSeries.add(detectionSeries);
  sciChartSurface.chartModifiers.add(
    new DataPointSelectionModifier({
      allowClickSelect: true,
      allowDragSelect: true,
      onSelectionChanged: (args) => {
        // Filter to the detection layer so hits on the highlight series don't double-count.
        const detectionPoints = args.selectedDataPoints.filter(
          (dp) => dp.renderableSeries === detectionSeries
        );
        highlightDataSeries.clear();
        if (detectionPoints.length === 0) return;
        const n = detectionPoints.length;
        const xVals = new Float64Array(n);
        const yVals = new Float64Array(n);
        const x1Vals = new Float64Array(n).fill(CELL_SIZE);
        const y1Vals = new Float64Array(n).fill(CELL_SIZE);
        let count = 0;
        for (const dp of detectionPoints) {
          if (scores[dp.index] < 0) continue; // skip cells outside the wafer circle
          // Decode index → (col, row); matches the col-major iteration in buildRectangleDataSeries.
          const col = Math.floor(dp.index / GRID_SIZE);
          const row = dp.index % GRID_SIZE;
          xVals[count] = col * CELL_SIZE;
          yVals[count] = (row + 1) * CELL_SIZE; // top of cell, see buildRectangleDataSeries
          count++;
        }
        if (count > 0) {
          highlightDataSeries.appendRangeN(xVals.subarray(0, count), [
            yVals.subarray(0, count),
            x1Vals.subarray(0, count),
            y1Vals.subarray(0, count)
          ]);
        }
      }
    })
  );
  sciChartSurface.chartModifiers.add(new MouseWheelZoomModifier());
  sciChartSurface.chartModifiers.add(new ZoomExtentsModifier());

  return { sciChartSurface };
};
  • You must to post comments
Showing 1 result
Your Answer

Please first to submit.