2D viewer: intensity readout is computed from the vertical pointer coordinate (only updates on vertical movement)

オープン 初心者向け
#4,427 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 3 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
80/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
react, typescript
領域
frontend

調査の方向性

src/component/2d/FooterBanner.tsxを、ポインター座標の処理に関する参照先としてissueが示しているsrc/component/2d/tools/XYLabelPointer.tsxと併せて読んでください。getXValueとgetYValueがメインのトラックと他のトラックについて、ポインター位置をどのように対応付けるかを確認してください。次に、等高線プロット上で水平方向に動かしたときにフッターの表示値が変化し、ポインターの下にある信号と一致することを確かめてください。

索引モデルが issue の本文から書いたものです。

説明

Summary

In the 2D viewer footer, the F2 value and the Intensity value are both derived from the pointer's vertical (y) pixel coordinate. Two consequences:

  1. The Intensity readout only changes when the pointer is moved vertically. Sweeping horizontally across the 2D contour plot changes nothing.
  2. The reported value does not correspond to the signal under the pointer: the z column is sampled at F2 = xScale.invert(pointer.y) instead of F2 = xScale.invert(pointer.x), so the intensity comes from a different point of the spectrum than the one being pointed at.

The F1 readout and the on-canvas pointer label (XYLabelPointer) are correct, so the footer contradicts the crosshair/label drawn next to the cursor.

Evidence

2D intensity readout only reacts to vertical pointer movement

How to reproduce

  1. Open a 2D spectrum (e.g. the JCAMP-DX sample used in the GIF above).
  2. Hover the pointer over the 2D contour area.
  3. Move the pointer horizontally over the peaks -> Intensity does not change.
  4. Move it vertically -> Intensity changes, and the value does not match the peak under the pointer.

Root cause

src/component/2d/FooterBanner.tsx (line 131):

const getXValue = (x: number | null = null) => {
  if (!scaleX || !trackID) return 0;

  if (trackID === 'MAIN') {
    return scaleX.invert(x || position.y);   // <-- BUG: should be position.x
  }
  // return if the trackID = "MAIN" | "TOP"
  return scaleX.invert(x || position.x);
};

getZValue() (line 188) builds both z indices from these helpers:

const xIndex = Math.floor((getXValue() - minX) / xStep);  // = f(position.y)
const yIndex = Math.floor((getYValue() - minY) / yStep);  // = f(position.y)
return z[yIndex][xIndex];

and getYValue() for MAIN is scaleY.invert(position.y). So in the main 2D area both indices are functions of position.y alone, which is exactly the reported behaviour: the intensity is constant along horizontal movement and jumps with tiny vertical drift.

Regression

Introduced in a6098c8a ("fix: correct rendering of 1D traces over the 2D (#3870)", 2025-12-03). That commit rewrote the switch (trackID) in getXValue as if-statements and, while doing so, changed the MAIN branch from position.x to position.y:

-      switch (trackID) {
-        case LAYOUT.main:
-        case LAYOUT.top: {
-          return scaleX.invert(x || position.x);
-        }
-        case LAYOUT.left: {
-          return scaleX.invert(x || position.y);
-        }
+    if (!scaleX || !trackID) return 0;
+
+    if (trackID === 'MAIN') {
+      return scaleX.invert(x || position.y);   // regression
+    }

Before that commit the MAIN branch was correct.

Reference implementation

src/component/2d/tools/XYLabelPointer.tsx (line 100) does it correctly:

const getXValue = (x = null) => {
  switch (trackID) {
    case LAYOUT.main:
    case LAYOUT.top: {
      return scaleX?.invert(x || position.x);   // correct
    }
    case LAYOUT.left: {
      return scaleX?.invert(x || position.y);
    }
    ...
const getYValue = () => {
  switch (trackID) {
    case LAYOUT.main:
    case LAYOUT.top: {
      return scaleY?.invert(position.y);        // correct
    }

Suggested fix

if (trackID === LAYOUT.main) {
  return scaleX.invert(x ?? position.x);
}

While there, the TOP / LEFT branches of getXValue / getYValue should be aligned with XYLabelPointer's switch: for LEFT, getXValue currently returns scaleX.invert(position.x) where scaleX is the 2D y scale (get2DYScale), i.e. the same class of coordinate mix-up.

Analysis of the GIF (frame by frame)

The GIF is 808x471, 64 frames. Measuring the pointer crosshair per frame:

  • Between frames 30 and 51 the crosshair moves horizontally from x ~ 241 px to x ~ 309 px (~68 px), while y stays within 249-251 px.
  • Over that same interval the footer shows F2 = 0.68 ppm and F1 = 0.0 ppm unchanged, and Intensity changes only when y drifts:
frame pointer y Intensity
30 249 3034415
34 251 3910041
42 250 3499514
50 248 2063091

So the intensity follows 3 px of vertical drift, not the 68 px horizontal sweep. With xDomain ~ [0.55, 0.82] mapped over the ~403 px plot width, dF2/dy ~ -6.7e-4 ppm/px, so a 3 px drift moves the sampled z column by a few bins - enough to change the readout.

Note that the F2 readout looks plausible in the GIF only by coincidence: the pointer's x and y are almost equal at the start of the sweep, so xScale.invert(position.y) happens to be close to xScale.invert(position.x).

Environment

  • nmrium 3.1.0, commit 5989ec6d
主要言語
TypeScript
スター
84
フォーク
35
平均マージ
4日 23時間
マージ済み PR(30日)
7

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

cheminfo/nmrium のほかの issue

cheminfo/nmrium の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。