@tetrascience-npm/tetrascience-react-ui 1.0.0-beta.148.1 → 1.0.0-beta.149.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/charts/AreaPlot/AreaPlot.cjs +1 -1
- package/dist/components/charts/AreaPlot/AreaPlot.cjs.map +1 -1
- package/dist/components/charts/AreaPlot/AreaPlot.js +166 -148
- package/dist/components/charts/AreaPlot/AreaPlot.js.map +1 -1
- package/dist/components/charts/BarChart/BarChart.cjs +1 -1
- package/dist/components/charts/BarChart/BarChart.cjs.map +1 -1
- package/dist/components/charts/BarChart/BarChart.d.ts +7 -0
- package/dist/components/charts/BarChart/BarChart.js +151 -120
- package/dist/components/charts/BarChart/BarChart.js.map +1 -1
- package/dist/components/charts/BoxPlot/BoxPlot.cjs +1 -1
- package/dist/components/charts/BoxPlot/BoxPlot.cjs.map +1 -1
- package/dist/components/charts/BoxPlot/BoxPlot.d.ts +4 -0
- package/dist/components/charts/BoxPlot/BoxPlot.js +133 -102
- package/dist/components/charts/BoxPlot/BoxPlot.js.map +1 -1
- package/dist/components/charts/ChartTooltip/lines.cjs.map +1 -1
- package/dist/components/charts/ChartTooltip/lines.d.ts +2 -0
- package/dist/components/charts/ChartTooltip/lines.js.map +1 -1
- package/dist/components/charts/Chromatogram/Chromatogram.cjs +1 -1
- package/dist/components/charts/Chromatogram/Chromatogram.cjs.map +1 -1
- package/dist/components/charts/Chromatogram/Chromatogram.js +111 -114
- package/dist/components/charts/Chromatogram/Chromatogram.js.map +1 -1
- package/dist/components/charts/Chromatogram/plotBuilder.cjs +4 -1
- package/dist/components/charts/Chromatogram/plotBuilder.cjs.map +1 -1
- package/dist/components/charts/Chromatogram/plotBuilder.d.ts +32 -2
- package/dist/components/charts/Chromatogram/plotBuilder.js +170 -127
- package/dist/components/charts/Chromatogram/plotBuilder.js.map +1 -1
- package/dist/components/charts/Chromatogram/regionOverlays.cjs +1 -1
- package/dist/components/charts/Chromatogram/regionOverlays.cjs.map +1 -1
- package/dist/components/charts/Chromatogram/regionOverlays.js +11 -11
- package/dist/components/charts/Chromatogram/regionOverlays.js.map +1 -1
- package/dist/components/charts/Chromatogram/types.d.ts +4 -2
- package/dist/components/charts/Electropherogram/Electropherogram.cjs +1 -1
- package/dist/components/charts/Electropherogram/Electropherogram.cjs.map +1 -1
- package/dist/components/charts/Electropherogram/Electropherogram.js +89 -88
- package/dist/components/charts/Electropherogram/Electropherogram.js.map +1 -1
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs +1 -1
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.cjs.map +1 -1
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js +4 -5
- package/dist/components/charts/ScatterPlotInteractive/ScatterPlotInteractive.js.map +1 -1
- package/dist/components/charts/chart-scale.cjs +2 -0
- package/dist/components/charts/chart-scale.cjs.map +1 -0
- package/dist/components/charts/chart-scale.d.ts +61 -0
- package/dist/components/charts/chart-scale.js +61 -0
- package/dist/components/charts/chart-scale.js.map +1 -0
- package/dist/index.css +1 -1
- package/dist/index.scoped.css +1 -1
- package/dist/index.tailwind.css +1 -1
- package/package.json +1 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("../../../utils/colors.cjs"),v=3.5;function h(s,
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const c=require("../../../utils/colors.cjs"),v=3.5;function h(i,s,o){const l=o.color??c.CHART_COLORS[s%c.CHART_COLORS.length],r=[];for(const e of i){if(!e.regionOverlay)continue;const t=e._computed?.startIndex,n=e._computed?.endIndex;if(t===void 0||n===void 0)continue;const a=e.color??l,d=e.regionOverlayWidth??v,u=e.hoverText?{text:e.hoverText,hoverinfo:"none"}:{hoverinfo:"skip"};r.push({x:o.x.slice(t,n+1),y:o.y.slice(t,n+1),type:"scatter",mode:"lines",line:{color:a,width:d},showlegend:!1,name:"",...u})}return r}exports.createRegionOverlayTraces=h;
|
|
2
2
|
//# sourceMappingURL=regionOverlays.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regionOverlays.cjs","sources":["../../../../src/components/charts/Chromatogram/regionOverlays.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\n\nimport type { PeakAnnotation, ChromatogramSeries } from \"./types\";\nimport type Plotly from \"plotly.js-dist\";\n\nconst DEFAULT_REGION_OVERLAY_WIDTH = 3.5;\n\n/**\n * Build one scatter trace per peak that has regionOverlay:true, slicing the\n * underlying series data between the peak's startIndex and endIndex.\n * Traces are pushed BEFORE the invisible hit-area trace so peak click/hover\n * still resolves to the hit-area marker, not the overlay.\n */\nexport function createRegionOverlayTraces(\n peaks: PeakAnnotation[],\n seriesIndex: number,\n series: ChromatogramSeries\n): Plotly.Data[] {\n const seriesColor = series.color ?? CHART_COLORS[seriesIndex % CHART_COLORS.length];\n const traces: Plotly.Data[] = [];\n\n for (const peak of peaks) {\n if (!peak.regionOverlay) continue;\n const startIdx = peak._computed?.startIndex;\n const endIdx = peak._computed?.endIndex;\n if (startIdx === undefined || endIdx === undefined) continue;\n\n const color = peak.color ?? seriesColor;\n const lineWidth = peak.regionOverlayWidth ?? DEFAULT_REGION_OVERLAY_WIDTH;\n const hoverProps = peak.hoverText\n ? {
|
|
1
|
+
{"version":3,"file":"regionOverlays.cjs","sources":["../../../../src/components/charts/Chromatogram/regionOverlays.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\n\nimport type { PeakAnnotation, ChromatogramSeries } from \"./types\";\nimport type Plotly from \"plotly.js-dist\";\n\nconst DEFAULT_REGION_OVERLAY_WIDTH = 3.5;\n\n/**\n * Build one scatter trace per peak that has regionOverlay:true, slicing the\n * underlying series data between the peak's startIndex and endIndex.\n * Traces are pushed BEFORE the invisible hit-area trace so peak click/hover\n * still resolves to the hit-area marker, not the overlay.\n */\nexport function createRegionOverlayTraces(\n peaks: PeakAnnotation[],\n seriesIndex: number,\n series: ChromatogramSeries\n): Plotly.Data[] {\n const seriesColor = series.color ?? CHART_COLORS[seriesIndex % CHART_COLORS.length];\n const traces: Plotly.Data[] = [];\n\n for (const peak of peaks) {\n if (!peak.regionOverlay) continue;\n const startIdx = peak._computed?.startIndex;\n const endIdx = peak._computed?.endIndex;\n if (startIdx === undefined || endIdx === undefined) continue;\n\n const color = peak.color ?? seriesColor;\n const lineWidth = peak.regionOverlayWidth ?? DEFAULT_REGION_OVERLAY_WIDTH;\n // Overlays with hoverText feed the shared ChartTooltip through `text`\n // (Plotly's own label stays off — SW-2298); the rest are decorative.\n const hoverProps = peak.hoverText\n ? { text: peak.hoverText, hoverinfo: \"none\" as const }\n : { hoverinfo: \"skip\" as const };\n\n traces.push({\n x: series.x.slice(startIdx, endIdx + 1),\n y: series.y.slice(startIdx, endIdx + 1),\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n line: { color, width: lineWidth },\n showlegend: false,\n name: \"\",\n ...hoverProps,\n });\n }\n\n return traces;\n}\n"],"names":["DEFAULT_REGION_OVERLAY_WIDTH","createRegionOverlayTraces","peaks","seriesIndex","series","seriesColor","CHART_COLORS","traces","peak","startIdx","endIdx","color","lineWidth","hoverProps"],"mappings":"6HAKMA,EAA+B,IAQ9B,SAASC,EACdC,EACAC,EACAC,EACe,CACf,MAAMC,EAAcD,EAAO,OAASE,EAAAA,aAAaH,EAAcG,EAAAA,aAAa,MAAM,EAC5EC,EAAwB,CAAA,EAE9B,UAAWC,KAAQN,EAAO,CACxB,GAAI,CAACM,EAAK,cAAe,SACzB,MAAMC,EAAWD,EAAK,WAAW,WAC3BE,EAASF,EAAK,WAAW,SAC/B,GAAIC,IAAa,QAAaC,IAAW,OAAW,SAEpD,MAAMC,EAAQH,EAAK,OAASH,EACtBO,EAAYJ,EAAK,oBAAsBR,EAGvCa,EAAaL,EAAK,UACpB,CAAE,KAAMA,EAAK,UAAW,UAAW,MAAA,EACnC,CAAE,UAAW,MAAA,EAEjBD,EAAO,KAAK,CACV,EAAGH,EAAO,EAAE,MAAMK,EAAUC,EAAS,CAAC,EACtC,EAAGN,EAAO,EAAE,MAAMK,EAAUC,EAAS,CAAC,EACtC,KAAM,UACN,KAAM,QACN,KAAM,CAAE,MAAAC,EAAO,MAAOC,CAAA,EACtB,WAAY,GACZ,KAAM,GACN,GAAGC,CAAA,CACJ,CACH,CAEA,OAAON,CACT"}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { CHART_COLORS as c } from "../../../utils/colors.js";
|
|
2
|
-
const
|
|
3
|
-
function
|
|
4
|
-
const
|
|
5
|
-
for (const
|
|
6
|
-
if (!
|
|
7
|
-
const t =
|
|
2
|
+
const v = 3.5;
|
|
3
|
+
function f(i, s, e) {
|
|
4
|
+
const d = e.color ?? c[s % c.length], r = [];
|
|
5
|
+
for (const o of i) {
|
|
6
|
+
if (!o.regionOverlay) continue;
|
|
7
|
+
const t = o._computed?.startIndex, n = o._computed?.endIndex;
|
|
8
8
|
if (t === void 0 || n === void 0) continue;
|
|
9
|
-
const
|
|
9
|
+
const l = o.color ?? d, a = o.regionOverlayWidth ?? v, h = o.hoverText ? { text: o.hoverText, hoverinfo: "none" } : { hoverinfo: "skip" };
|
|
10
10
|
r.push({
|
|
11
|
-
x:
|
|
12
|
-
y:
|
|
11
|
+
x: e.x.slice(t, n + 1),
|
|
12
|
+
y: e.y.slice(t, n + 1),
|
|
13
13
|
type: "scatter",
|
|
14
14
|
mode: "lines",
|
|
15
|
-
line: { color:
|
|
15
|
+
line: { color: l, width: a },
|
|
16
16
|
showlegend: !1,
|
|
17
17
|
name: "",
|
|
18
18
|
...h
|
|
@@ -21,6 +21,6 @@ function v(i, s, o) {
|
|
|
21
21
|
return r;
|
|
22
22
|
}
|
|
23
23
|
export {
|
|
24
|
-
|
|
24
|
+
f as createRegionOverlayTraces
|
|
25
25
|
};
|
|
26
26
|
//# sourceMappingURL=regionOverlays.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"regionOverlays.js","sources":["../../../../src/components/charts/Chromatogram/regionOverlays.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\n\nimport type { PeakAnnotation, ChromatogramSeries } from \"./types\";\nimport type Plotly from \"plotly.js-dist\";\n\nconst DEFAULT_REGION_OVERLAY_WIDTH = 3.5;\n\n/**\n * Build one scatter trace per peak that has regionOverlay:true, slicing the\n * underlying series data between the peak's startIndex and endIndex.\n * Traces are pushed BEFORE the invisible hit-area trace so peak click/hover\n * still resolves to the hit-area marker, not the overlay.\n */\nexport function createRegionOverlayTraces(\n peaks: PeakAnnotation[],\n seriesIndex: number,\n series: ChromatogramSeries\n): Plotly.Data[] {\n const seriesColor = series.color ?? CHART_COLORS[seriesIndex % CHART_COLORS.length];\n const traces: Plotly.Data[] = [];\n\n for (const peak of peaks) {\n if (!peak.regionOverlay) continue;\n const startIdx = peak._computed?.startIndex;\n const endIdx = peak._computed?.endIndex;\n if (startIdx === undefined || endIdx === undefined) continue;\n\n const color = peak.color ?? seriesColor;\n const lineWidth = peak.regionOverlayWidth ?? DEFAULT_REGION_OVERLAY_WIDTH;\n const hoverProps = peak.hoverText\n ? {
|
|
1
|
+
{"version":3,"file":"regionOverlays.js","sources":["../../../../src/components/charts/Chromatogram/regionOverlays.ts"],"sourcesContent":["import { CHART_COLORS } from \"../../../utils/colors\";\n\nimport type { PeakAnnotation, ChromatogramSeries } from \"./types\";\nimport type Plotly from \"plotly.js-dist\";\n\nconst DEFAULT_REGION_OVERLAY_WIDTH = 3.5;\n\n/**\n * Build one scatter trace per peak that has regionOverlay:true, slicing the\n * underlying series data between the peak's startIndex and endIndex.\n * Traces are pushed BEFORE the invisible hit-area trace so peak click/hover\n * still resolves to the hit-area marker, not the overlay.\n */\nexport function createRegionOverlayTraces(\n peaks: PeakAnnotation[],\n seriesIndex: number,\n series: ChromatogramSeries\n): Plotly.Data[] {\n const seriesColor = series.color ?? CHART_COLORS[seriesIndex % CHART_COLORS.length];\n const traces: Plotly.Data[] = [];\n\n for (const peak of peaks) {\n if (!peak.regionOverlay) continue;\n const startIdx = peak._computed?.startIndex;\n const endIdx = peak._computed?.endIndex;\n if (startIdx === undefined || endIdx === undefined) continue;\n\n const color = peak.color ?? seriesColor;\n const lineWidth = peak.regionOverlayWidth ?? DEFAULT_REGION_OVERLAY_WIDTH;\n // Overlays with hoverText feed the shared ChartTooltip through `text`\n // (Plotly's own label stays off — SW-2298); the rest are decorative.\n const hoverProps = peak.hoverText\n ? { text: peak.hoverText, hoverinfo: \"none\" as const }\n : { hoverinfo: \"skip\" as const };\n\n traces.push({\n x: series.x.slice(startIdx, endIdx + 1),\n y: series.y.slice(startIdx, endIdx + 1),\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n line: { color, width: lineWidth },\n showlegend: false,\n name: \"\",\n ...hoverProps,\n });\n }\n\n return traces;\n}\n"],"names":["DEFAULT_REGION_OVERLAY_WIDTH","createRegionOverlayTraces","peaks","seriesIndex","series","seriesColor","CHART_COLORS","traces","peak","startIdx","endIdx","color","lineWidth","hoverProps"],"mappings":";AAKA,MAAMA,IAA+B;AAQ9B,SAASC,EACdC,GACAC,GACAC,GACe;AACf,QAAMC,IAAcD,EAAO,SAASE,EAAaH,IAAcG,EAAa,MAAM,GAC5EC,IAAwB,CAAA;AAE9B,aAAWC,KAAQN,GAAO;AACxB,QAAI,CAACM,EAAK,cAAe;AACzB,UAAMC,IAAWD,EAAK,WAAW,YAC3BE,IAASF,EAAK,WAAW;AAC/B,QAAIC,MAAa,UAAaC,MAAW,OAAW;AAEpD,UAAMC,IAAQH,EAAK,SAASH,GACtBO,IAAYJ,EAAK,sBAAsBR,GAGvCa,IAAaL,EAAK,YACpB,EAAE,MAAMA,EAAK,WAAW,WAAW,OAAA,IACnC,EAAE,WAAW,OAAA;AAEjB,IAAAD,EAAO,KAAK;AAAA,MACV,GAAGH,EAAO,EAAE,MAAMK,GAAUC,IAAS,CAAC;AAAA,MACtC,GAAGN,EAAO,EAAE,MAAMK,GAAUC,IAAS,CAAC;AAAA,MACtC,MAAM;AAAA,MACN,MAAM;AAAA,MACN,MAAM,EAAE,OAAAC,GAAO,OAAOC,EAAA;AAAA,MACtB,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,GAAGC;AAAA,IAAA,CACJ;AAAA,EACH;AAEA,SAAON;AACT;"}
|
|
@@ -80,8 +80,10 @@ export interface PeakAnnotation {
|
|
|
80
80
|
/** Line width for the region overlay (default: 3.5) */
|
|
81
81
|
regionOverlayWidth?: number;
|
|
82
82
|
/**
|
|
83
|
-
*
|
|
84
|
-
* hit-area marker
|
|
83
|
+
* Text shown in the chart tooltip when hovering this peak (via the region
|
|
84
|
+
* overlay or the invisible hit-area marker). `<br>` starts a new line; other
|
|
85
|
+
* HTML tags are stripped, since the tooltip renders plain text. Falls back to
|
|
86
|
+
* `text` when omitted.
|
|
85
87
|
*/
|
|
86
88
|
hoverText?: string;
|
|
87
89
|
/**
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const a=require("react/jsx-runtime"),o=require("react"),B=require("../ChartTooltip/ChartTooltip.cjs"),S=require("../plotly-loader.cjs"),w=require("../../../hooks/use-plotly-theme.cjs"),k=require("../../../utils/colors.cjs");;/* empty css */const $=89,D=1.05,I=e=>{if(e.base!==void 0)return e.base;const i={A:e.peakA,T:e.peakT,G:e.peakG,C:e.peakC},u=Object.values(i);if(u.every(c=>c===u[0]))return"";let m="",h=0;return Object.entries(i).forEach(([c,f])=>{f>h&&(m=c,h=f)}),m},V=({data:e=[],width:i=900,height:u=600,positionInterval:y=10,colorA:m=k.CHART_COLORS[0],colorT:h=k.CHART_COLORS[2],colorG:c=k.CHART_COLORS[3],colorC:f=k.CHART_COLORS[1]})=>{const x=o.useRef(null),M=w.usePlotlyTheme(),{bindTooltip:O,tooltipElement:E}=B.useChartTooltip({xLabel:"Position",yLabel:"Intensity"}),t=o.useMemo(()=>e.map(s=>s.position),[e]),H=o.useMemo(()=>e.map(s=>I(s)),[e]),T=o.useMemo(()=>e.map(s=>s.peakA),[e]),v=o.useMemo(()=>e.map(s=>s.peakT),[e]),C=o.useMemo(()=>e.map(s=>s.peakG),[e]),P=o.useMemo(()=>e.map(s=>s.peakC),[e]),R=o.useMemo(()=>({x:t,y:T,type:"scatter",mode:"lines",hoverinfo:"none",name:"A",line:{color:m,width:2,shape:"spline"}}),[t,T,m]),_=o.useMemo(()=>({x:t,y:v,type:"scatter",mode:"lines",hoverinfo:"none",name:"T",line:{color:h,width:2,shape:"spline"}}),[t,v,h]),j=o.useMemo(()=>({x:t,y:C,type:"scatter",mode:"lines",hoverinfo:"none",name:"G",line:{color:c,width:2,shape:"spline"}}),[t,C,c]),A=o.useMemo(()=>({x:t,y:P,type:"scatter",mode:"lines",hoverinfo:"none",name:"C",line:{color:f,width:2,shape:"spline"}}),[t,P,f]),L=o.useMemo(()=>Math.max(...T,...v,...C,...P),[T,v,C,P]),{xMin:b,xMax:q}=o.useMemo(()=>{if(t.length===0)return{xMin:0,xMax:1};const s=Math.min(...t),g=Math.max(...t),r=(t.length>1?(g-s)/(t.length-1):1)/2;return{xMin:s-r,xMax:g+r}},[t]),N=s=>(s-b)/(q-b)*i;if(o.useEffect(()=>{if(!x.current||e.length===0)return;const s=[R,_,j,A],g={width:i,height:u-$,margin:{l:0,r:0,b:20,t:10,pad:0},paper_bgcolor:M.paperBg,plot_bgcolor:M.plotBg,font:{family:w.CHART_FONT_FAMILY},showlegend:!1,xaxis:{showgrid:!1,zeroline:!1,showticklabels:!1,showline:!1,range:[b,q],fixedrange:!0},yaxis:{showgrid:!1,zeroline:!1,showticklabels:!1,showline:!1,range:[0,L*D],fixedrange:!0}},n={responsive:!0,displayModeBar:!1,displaylogo:!1,fillFrame:!0};let r=!1,p=null;return S.loadPlotly().then(d=>{r||!x.current||(d.newPlot(x.current,s,g,n),O(x.current),p=x.current)}),()=>{r=!0,p&&S.getLoadedPlotly().purge(p)}},[e,i,u,R,_,j,A,L,b,q,M,O]),e.length===0)return a.jsx("div",{className:"electropherogram-empty",children:"No data available"});const F=()=>{const s=()=>a.jsx("div",{className:"electropherogram-sequence-letters",children:H.map((n,r)=>{const p=t[r],d=n==="A"?m:n==="T"?h:n==="G"?c:n==="C"?f:M.textColor,l=N(p);return a.jsx("span",{className:"electropherogram-sequence-letter",style:{left:`${l}px`,color:d},children:n},`base-${r}`)})}),g=()=>{const n=Math.min(...t),r=Math.max(...t),p=Math.ceil(n/y)*y,d=[];for(let l=p;l<=r;l+=y)d.push({position:l,label:l.toString()});return a.jsx("div",{className:"electropherogram-position-numbers",children:d.map(l=>{const G=N(l.position);return a.jsx("span",{className:"electropherogram-position-number",style:{left:`${G}px`},children:l.label},`pos-${l.position}`)})})};return a.jsxs("div",{className:"electropherogram-sequence-header",children:[s(),g()]})};return a.jsxs("div",{className:"electropherogram-container relative",style:{width:i,height:u},children:[F(),a.jsx("div",{className:"electropherogram-chart",children:a.jsx("div",{ref:x,style:{width:"100%",height:"100%"}})}),E]})};exports.Electropherogram=V;
|
|
2
2
|
//# sourceMappingURL=Electropherogram.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Electropherogram.cjs","sources":["../../../../src/components/charts/Electropherogram/Electropherogram.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { CHART_COLORS } from \"@/utils/colors\";\nimport \"./Electropherogram.scss\";\n\n/** Height offset for the plot area in pixels */\nconst PLOT_HEIGHT_OFFSET = 75;\n/** Scale factor for y-axis range to add padding above max value */\nconst Y_AXIS_PADDING_FACTOR = 1.05;\n\ninterface PeakData {\n position: number;\n base?: string;\n peakA: number;\n peakT: number;\n peakG: number;\n peakC: number;\n}\n\ninterface ElectropherogramProps {\n data?: PeakData[];\n width?: number;\n height?: number;\n positionInterval?: number;\n colorA?: string;\n colorT?: string;\n colorG?: string;\n colorC?: string;\n}\n\nconst determineBase = (item: PeakData): string => {\n if (item.base !== undefined) return item.base;\n const peakValues = {\n A: item.peakA,\n T: item.peakT,\n G: item.peakG,\n C: item.peakC,\n };\n\n const values = Object.values(peakValues);\n const allEqual = values.every((val) => val === values[0]);\n\n if (allEqual) {\n return \"\";\n }\n\n let highestBase = \"\";\n let highestValue = 0;\n\n Object.entries(peakValues).forEach(([base, value]) => {\n if (value > highestValue) {\n highestBase = base;\n highestValue = value;\n }\n });\n\n return highestBase;\n};\n\nconst Electropherogram: React.FC<ElectropherogramProps> = ({\n data = [],\n width = 900,\n height = 600,\n positionInterval = 10,\n colorA = CHART_COLORS[0], // blue\n colorT = CHART_COLORS[2], // teal/green\n colorG = CHART_COLORS[3], // red\n colorC = CHART_COLORS[1], // orange\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({\n xLabel: \"Position\",\n yLabel: \"Intensity\",\n });\n\n const positions = useMemo(() => data.map((item) => item.position), [data]);\n const sequence = useMemo(() => data.map((item) => determineBase(item)), [data]);\n const peakA = useMemo(() => data.map((item) => item.peakA), [data]);\n const peakT = useMemo(() => data.map((item) => item.peakT), [data]);\n const peakG = useMemo(() => data.map((item) => item.peakG), [data]);\n const peakC = useMemo(() => data.map((item) => item.peakC), [data]);\n\n const aTrace = useMemo(\n () => ({\n x: positions,\n y: peakA,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"A\",\n line: { color: colorA, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakA, colorA],\n );\n\n const tTrace = useMemo(\n () => ({\n x: positions,\n y: peakT,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"T\",\n line: { color: colorT, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakT, colorT],\n );\n\n const gTrace = useMemo(\n () => ({\n x: positions,\n y: peakG,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"G\",\n line: { color: colorG, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakG, colorG],\n );\n\n const cTrace = useMemo(\n () => ({\n x: positions,\n y: peakC,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"C\",\n line: { color: colorC, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakC, colorC],\n );\n\n const maxValue = useMemo(\n () => Math.max(...peakA, ...peakT, ...peakG, ...peakC),\n [peakA, peakT, peakG, peakC],\n );\n\n useEffect(() => {\n if (!plotRef.current || data.length === 0) return;\n\n const plotData = [aTrace, tTrace, gTrace, cTrace];\n\n const layout: Partial<Plotly.Layout> = {\n width: width,\n height: height - PLOT_HEIGHT_OFFSET,\n margin: { l: 0, r: 0, b: 20, t: 10, pad: 0 },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n showlegend: false,\n xaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [Math.min(...positions), Math.max(...positions)],\n fixedrange: true,\n },\n yaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [0, maxValue * Y_AXIS_PADDING_FACTOR],\n fixedrange: true,\n },\n };\n\n const config = {\n responsive: true,\n displayModeBar: false,\n displaylogo: false,\n fillFrame: true,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, plotData, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n }\n };\n }, [data, width, height, aTrace, tTrace, gTrace, cTrace, maxValue, positions, theme, bindTooltip]);\n\n if (data.length === 0) {\n return <div className=\"electropherogram-empty\">No data available</div>;\n }\n\n const renderSequence = () => {\n const renderSequenceLetters = () => {\n const minPosition = Math.min(...positions);\n const maxPosition = Math.max(...positions);\n const chartWidth = width;\n\n return (\n <div className=\"electropherogram-sequence-letters\">\n {sequence.map((base, index) => {\n const position = positions[index];\n const color =\n base === \"A\"\n ? colorA\n : base === \"T\"\n ? colorT\n : base === \"G\"\n ? colorG\n : base === \"C\"\n ? colorC\n : theme.textColor;\n\n const span = maxPosition - minPosition || 1;\n const percentage = (position - minPosition) / span;\n const leftPosition = percentage * chartWidth;\n\n return (\n <span\n key={`base-${index}`}\n className=\"electropherogram-sequence-letter\"\n style={{\n left: `${leftPosition}px`,\n color,\n }}\n >\n {base}\n </span>\n );\n })}\n </div>\n );\n };\n\n const renderPositionNumbers = () => {\n const minPosition = Math.min(...positions);\n const maxPosition = Math.max(...positions);\n const chartWidth = width;\n\n const startPos =\n Math.ceil(minPosition / positionInterval) * positionInterval;\n\n const regularPositionLabels: Array<{ position: number; label: string }> =\n [];\n\n for (let pos = startPos; pos <= maxPosition; pos += positionInterval) {\n regularPositionLabels.push({\n position: pos,\n label: pos.toString(),\n });\n }\n\n return (\n <div className=\"electropherogram-position-numbers\">\n {regularPositionLabels.map((label) => {\n const span = maxPosition - minPosition || 1;\n const percentage = (label.position - minPosition) / span;\n const leftPosition = percentage * chartWidth;\n\n return (\n <span\n key={`pos-${label.position}`}\n className=\"electropherogram-position-number\"\n style={{\n left: `${leftPosition}px`,\n }}\n >\n {label.label}\n </span>\n );\n })}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-sequence-header\">\n {renderSequenceLetters()}\n {renderPositionNumbers()}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-container relative\" style={{ width, height }}>\n {renderSequence()}\n <div className=\"electropherogram-chart\">\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n </div>\n {tooltipElement}\n </div>\n );\n};\n\nexport { Electropherogram };\nexport type { PeakData, ElectropherogramProps };\n"],"names":["PLOT_HEIGHT_OFFSET","Y_AXIS_PADDING_FACTOR","determineBase","item","peakValues","values","val","highestBase","highestValue","base","value","Electropherogram","data","width","height","positionInterval","colorA","CHART_COLORS","colorT","colorG","colorC","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","positions","useMemo","sequence","peakA","peakT","peakG","peakC","aTrace","tTrace","gTrace","cTrace","maxValue","useEffect","plotData","layout","CHART_FONT_FAMILY","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsx","renderSequence","renderSequenceLetters","minPosition","maxPosition","chartWidth","index","position","color","span","leftPosition","renderPositionNumbers","startPos","regularPositionLabels","pos","label","jsxs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Electropherogram.cjs","sources":["../../../../src/components/charts/Electropherogram/Electropherogram.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { CHART_COLORS } from \"@/utils/colors\";\nimport \"./Electropherogram.scss\";\n\n/**\n * Height of the sequence header above the plot (see Electropherogram.scss:\n * 12 + 6 padding, 35 + 6 letters row, 30 numbers row). The plot gets the rest.\n */\nconst PLOT_HEIGHT_OFFSET = 89;\n/** Scale factor for y-axis range to add padding above max value */\nconst Y_AXIS_PADDING_FACTOR = 1.05;\n\ninterface PeakData {\n position: number;\n base?: string;\n peakA: number;\n peakT: number;\n peakG: number;\n peakC: number;\n}\n\ninterface ElectropherogramProps {\n data?: PeakData[];\n width?: number;\n height?: number;\n positionInterval?: number;\n colorA?: string;\n colorT?: string;\n colorG?: string;\n colorC?: string;\n}\n\nconst determineBase = (item: PeakData): string => {\n if (item.base !== undefined) return item.base;\n const peakValues = {\n A: item.peakA,\n T: item.peakT,\n G: item.peakG,\n C: item.peakC,\n };\n\n const values = Object.values(peakValues);\n const allEqual = values.every((val) => val === values[0]);\n\n if (allEqual) {\n return \"\";\n }\n\n let highestBase = \"\";\n let highestValue = 0;\n\n Object.entries(peakValues).forEach(([base, value]) => {\n if (value > highestValue) {\n highestBase = base;\n highestValue = value;\n }\n });\n\n return highestBase;\n};\n\nconst Electropherogram: React.FC<ElectropherogramProps> = ({\n data = [],\n width = 900,\n height = 600,\n positionInterval = 10,\n colorA = CHART_COLORS[0], // blue\n colorT = CHART_COLORS[2], // teal/green\n colorG = CHART_COLORS[3], // red\n colorC = CHART_COLORS[1], // orange\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({\n xLabel: \"Position\",\n yLabel: \"Intensity\",\n });\n\n const positions = useMemo(() => data.map((item) => item.position), [data]);\n const sequence = useMemo(() => data.map((item) => determineBase(item)), [data]);\n const peakA = useMemo(() => data.map((item) => item.peakA), [data]);\n const peakT = useMemo(() => data.map((item) => item.peakT), [data]);\n const peakG = useMemo(() => data.map((item) => item.peakG), [data]);\n const peakC = useMemo(() => data.map((item) => item.peakC), [data]);\n\n const aTrace = useMemo(\n () => ({\n x: positions,\n y: peakA,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"A\",\n line: { color: colorA, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakA, colorA],\n );\n\n const tTrace = useMemo(\n () => ({\n x: positions,\n y: peakT,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"T\",\n line: { color: colorT, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakT, colorT],\n );\n\n const gTrace = useMemo(\n () => ({\n x: positions,\n y: peakG,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"G\",\n line: { color: colorG, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakG, colorG],\n );\n\n const cTrace = useMemo(\n () => ({\n x: positions,\n y: peakC,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"C\",\n line: { color: colorC, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakC, colorC],\n );\n\n const maxValue = useMemo(\n () => Math.max(...peakA, ...peakT, ...peakG, ...peakC),\n [peakA, peakT, peakG, peakC],\n );\n\n // The plot has no side margins, so an x-range that ends exactly on the first\n // and last sample clips the outermost peaks and pushes their base letters\n // half off the canvas (SW-2298). Pad by half a sample step on each side; the\n // header letters/numbers share this mapping so they stay aligned to peaks.\n const { xMin, xMax } = useMemo(() => {\n if (positions.length === 0) return { xMin: 0, xMax: 1 };\n const min = Math.min(...positions);\n const max = Math.max(...positions);\n const step = positions.length > 1 ? (max - min) / (positions.length - 1) : 1;\n const pad = step / 2;\n return { xMin: min - pad, xMax: max + pad };\n }, [positions]);\n const xToPx = (position: number) => ((position - xMin) / (xMax - xMin)) * width;\n\n useEffect(() => {\n if (!plotRef.current || data.length === 0) return;\n\n const plotData = [aTrace, tTrace, gTrace, cTrace];\n\n const layout: Partial<Plotly.Layout> = {\n width: width,\n height: height - PLOT_HEIGHT_OFFSET,\n margin: { l: 0, r: 0, b: 20, t: 10, pad: 0 },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n showlegend: false,\n xaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [xMin, xMax],\n fixedrange: true,\n },\n yaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [0, maxValue * Y_AXIS_PADDING_FACTOR],\n fixedrange: true,\n },\n };\n\n const config = {\n responsive: true,\n displayModeBar: false,\n displaylogo: false,\n fillFrame: true,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, plotData, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n }\n };\n }, [data, width, height, aTrace, tTrace, gTrace, cTrace, maxValue, xMin, xMax, theme, bindTooltip]);\n\n if (data.length === 0) {\n return <div className=\"electropherogram-empty\">No data available</div>;\n }\n\n const renderSequence = () => {\n const renderSequenceLetters = () => {\n return (\n <div className=\"electropherogram-sequence-letters\">\n {sequence.map((base, index) => {\n const position = positions[index];\n const color =\n base === \"A\"\n ? colorA\n : base === \"T\"\n ? colorT\n : base === \"G\"\n ? colorG\n : base === \"C\"\n ? colorC\n : theme.textColor;\n\n const leftPosition = xToPx(position);\n\n return (\n <span\n key={`base-${index}`}\n className=\"electropherogram-sequence-letter\"\n style={{\n left: `${leftPosition}px`,\n color,\n }}\n >\n {base}\n </span>\n );\n })}\n </div>\n );\n };\n\n const renderPositionNumbers = () => {\n const minPosition = Math.min(...positions);\n const maxPosition = Math.max(...positions);\n\n const startPos =\n Math.ceil(minPosition / positionInterval) * positionInterval;\n\n const regularPositionLabels: Array<{ position: number; label: string }> =\n [];\n\n for (let pos = startPos; pos <= maxPosition; pos += positionInterval) {\n regularPositionLabels.push({\n position: pos,\n label: pos.toString(),\n });\n }\n\n return (\n <div className=\"electropherogram-position-numbers\">\n {regularPositionLabels.map((label) => {\n const leftPosition = xToPx(label.position);\n\n return (\n <span\n key={`pos-${label.position}`}\n className=\"electropherogram-position-number\"\n style={{\n left: `${leftPosition}px`,\n }}\n >\n {label.label}\n </span>\n );\n })}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-sequence-header\">\n {renderSequenceLetters()}\n {renderPositionNumbers()}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-container relative\" style={{ width, height }}>\n {renderSequence()}\n <div className=\"electropherogram-chart\">\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n </div>\n {tooltipElement}\n </div>\n );\n};\n\nexport { Electropherogram };\nexport type { PeakData, ElectropherogramProps };\n"],"names":["PLOT_HEIGHT_OFFSET","Y_AXIS_PADDING_FACTOR","determineBase","item","peakValues","values","val","highestBase","highestValue","base","value","Electropherogram","data","width","height","positionInterval","colorA","CHART_COLORS","colorT","colorG","colorC","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","positions","useMemo","sequence","peakA","peakT","peakG","peakC","aTrace","tTrace","gTrace","cTrace","maxValue","xMin","xMax","min","max","pad","xToPx","position","useEffect","plotData","layout","CHART_FONT_FAMILY","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsx","renderSequence","renderSequenceLetters","index","color","leftPosition","renderPositionNumbers","minPosition","maxPosition","startPos","regularPositionLabels","pos","label","jsxs"],"mappings":"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"}
|
|
@@ -1,99 +1,103 @@
|
|
|
1
|
-
import { jsx as
|
|
2
|
-
import { useRef as
|
|
3
|
-
import { useChartTooltip as
|
|
4
|
-
import { loadPlotly as
|
|
5
|
-
import { usePlotlyTheme as
|
|
1
|
+
import { jsx as p, jsxs as S } from "react/jsx-runtime";
|
|
2
|
+
import { useRef as B, useMemo as o, useEffect as j } from "react";
|
|
3
|
+
import { useChartTooltip as H } from "../ChartTooltip/ChartTooltip.js";
|
|
4
|
+
import { loadPlotly as $, getLoadedPlotly as D } from "../plotly-loader.js";
|
|
5
|
+
import { usePlotlyTheme as I, CHART_FONT_FAMILY as V } from "../../../hooks/use-plotly-theme.js";
|
|
6
6
|
import { CHART_COLORS as N } from "../../../utils/colors.js";
|
|
7
7
|
/* empty css */
|
|
8
|
-
const
|
|
8
|
+
const z = 89, Y = 1.05, X = (e) => {
|
|
9
9
|
if (e.base !== void 0) return e.base;
|
|
10
|
-
const
|
|
10
|
+
const a = {
|
|
11
11
|
A: e.peakA,
|
|
12
12
|
T: e.peakT,
|
|
13
13
|
G: e.peakG,
|
|
14
14
|
C: e.peakC
|
|
15
|
-
},
|
|
16
|
-
if (
|
|
15
|
+
}, m = Object.values(a);
|
|
16
|
+
if (m.every((i) => i === m[0]))
|
|
17
17
|
return "";
|
|
18
|
-
let
|
|
19
|
-
return Object.entries(
|
|
20
|
-
|
|
21
|
-
}),
|
|
22
|
-
},
|
|
18
|
+
let h = "", u = 0;
|
|
19
|
+
return Object.entries(a).forEach(([i, f]) => {
|
|
20
|
+
f > u && (h = i, u = f);
|
|
21
|
+
}), h;
|
|
22
|
+
}, ne = ({
|
|
23
23
|
data: e = [],
|
|
24
|
-
width:
|
|
25
|
-
height:
|
|
26
|
-
positionInterval:
|
|
27
|
-
colorA:
|
|
24
|
+
width: a = 900,
|
|
25
|
+
height: m = 600,
|
|
26
|
+
positionInterval: y = 10,
|
|
27
|
+
colorA: h = N[0],
|
|
28
28
|
// blue
|
|
29
|
-
colorT:
|
|
29
|
+
colorT: u = N[2],
|
|
30
30
|
// teal/green
|
|
31
31
|
colorG: i = N[3],
|
|
32
32
|
// red
|
|
33
|
-
colorC:
|
|
33
|
+
colorC: f = N[1]
|
|
34
34
|
// orange
|
|
35
35
|
}) => {
|
|
36
|
-
const
|
|
36
|
+
const g = B(null), T = I(), { bindTooltip: A, tooltipElement: F } = H({
|
|
37
37
|
xLabel: "Position",
|
|
38
38
|
yLabel: "Intensity"
|
|
39
|
-
}),
|
|
39
|
+
}), n = o(() => e.map((t) => t.position), [e]), G = o(() => e.map((t) => X(t)), [e]), v = o(() => e.map((t) => t.peakA), [e]), k = o(() => e.map((t) => t.peakT), [e]), M = o(() => e.map((t) => t.peakG), [e]), P = o(() => e.map((t) => t.peakC), [e]), C = o(
|
|
40
40
|
() => ({
|
|
41
|
-
x:
|
|
41
|
+
x: n,
|
|
42
42
|
y: v,
|
|
43
43
|
type: "scatter",
|
|
44
44
|
mode: "lines",
|
|
45
45
|
hoverinfo: "none",
|
|
46
46
|
name: "A",
|
|
47
|
-
line: { color:
|
|
47
|
+
line: { color: h, width: 2, shape: "spline" }
|
|
48
48
|
}),
|
|
49
|
-
[
|
|
50
|
-
),
|
|
49
|
+
[n, v, h]
|
|
50
|
+
), _ = o(
|
|
51
51
|
() => ({
|
|
52
|
-
x:
|
|
53
|
-
y:
|
|
52
|
+
x: n,
|
|
53
|
+
y: k,
|
|
54
54
|
type: "scatter",
|
|
55
55
|
mode: "lines",
|
|
56
56
|
hoverinfo: "none",
|
|
57
57
|
name: "T",
|
|
58
|
-
line: { color:
|
|
58
|
+
line: { color: u, width: 2, shape: "spline" }
|
|
59
59
|
}),
|
|
60
|
-
[
|
|
61
|
-
),
|
|
60
|
+
[n, k, u]
|
|
61
|
+
), q = o(
|
|
62
62
|
() => ({
|
|
63
|
-
x:
|
|
64
|
-
y:
|
|
63
|
+
x: n,
|
|
64
|
+
y: M,
|
|
65
65
|
type: "scatter",
|
|
66
66
|
mode: "lines",
|
|
67
67
|
hoverinfo: "none",
|
|
68
68
|
name: "G",
|
|
69
69
|
line: { color: i, width: 2, shape: "spline" }
|
|
70
70
|
}),
|
|
71
|
-
[
|
|
71
|
+
[n, M, i]
|
|
72
72
|
), E = o(
|
|
73
73
|
() => ({
|
|
74
|
-
x:
|
|
75
|
-
y:
|
|
74
|
+
x: n,
|
|
75
|
+
y: P,
|
|
76
76
|
type: "scatter",
|
|
77
77
|
mode: "lines",
|
|
78
78
|
hoverinfo: "none",
|
|
79
79
|
name: "C",
|
|
80
|
-
line: { color:
|
|
80
|
+
line: { color: f, width: 2, shape: "spline" }
|
|
81
81
|
}),
|
|
82
|
-
[
|
|
82
|
+
[n, P, f]
|
|
83
83
|
), L = o(
|
|
84
|
-
() => Math.max(...v, ...
|
|
85
|
-
[v,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
84
|
+
() => Math.max(...v, ...k, ...M, ...P),
|
|
85
|
+
[v, k, M, P]
|
|
86
|
+
), { xMin: b, xMax: w } = o(() => {
|
|
87
|
+
if (n.length === 0) return { xMin: 0, xMax: 1 };
|
|
88
|
+
const t = Math.min(...n), x = Math.max(...n), r = (n.length > 1 ? (x - t) / (n.length - 1) : 1) / 2;
|
|
89
|
+
return { xMin: t - r, xMax: x + r };
|
|
90
|
+
}, [n]), O = (t) => (t - b) / (w - b) * a;
|
|
91
|
+
return j(() => {
|
|
92
|
+
if (!g.current || e.length === 0) return;
|
|
93
|
+
const t = [C, _, q, E], x = {
|
|
94
|
+
width: a,
|
|
95
|
+
height: m - z,
|
|
92
96
|
margin: { l: 0, r: 0, b: 20, t: 10, pad: 0 },
|
|
93
97
|
paper_bgcolor: T.paperBg,
|
|
94
98
|
plot_bgcolor: T.plotBg,
|
|
95
99
|
font: {
|
|
96
|
-
family:
|
|
100
|
+
family: V
|
|
97
101
|
},
|
|
98
102
|
showlegend: !1,
|
|
99
103
|
xaxis: {
|
|
@@ -101,7 +105,7 @@ const W = 75, Y = 1.05, X = (e) => {
|
|
|
101
105
|
zeroline: !1,
|
|
102
106
|
showticklabels: !1,
|
|
103
107
|
showline: !1,
|
|
104
|
-
range: [
|
|
108
|
+
range: [b, w],
|
|
105
109
|
fixedrange: !0
|
|
106
110
|
},
|
|
107
111
|
yaxis: {
|
|
@@ -112,69 +116,66 @@ const W = 75, Y = 1.05, X = (e) => {
|
|
|
112
116
|
range: [0, L * Y],
|
|
113
117
|
fixedrange: !0
|
|
114
118
|
}
|
|
115
|
-
},
|
|
119
|
+
}, s = {
|
|
116
120
|
responsive: !0,
|
|
117
121
|
displayModeBar: !1,
|
|
118
122
|
displaylogo: !1,
|
|
119
123
|
fillFrame: !0
|
|
120
124
|
};
|
|
121
|
-
let
|
|
122
|
-
return
|
|
123
|
-
|
|
125
|
+
let r = !1, c = null;
|
|
126
|
+
return $().then((d) => {
|
|
127
|
+
r || !g.current || (d.newPlot(g.current, t, x, s), A(g.current), c = g.current);
|
|
124
128
|
}), () => {
|
|
125
|
-
|
|
129
|
+
r = !0, c && D().purge(c);
|
|
126
130
|
};
|
|
127
|
-
}, [e,
|
|
131
|
+
}, [e, a, m, C, _, q, E, L, b, w, T, A]), e.length === 0 ? /* @__PURE__ */ p("div", { className: "electropherogram-empty", children: "No data available" }) : /* @__PURE__ */ S("div", { className: "electropherogram-container relative", style: { width: a, height: m }, children: [
|
|
128
132
|
(() => {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
131
|
-
return /* @__PURE__ */
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
"
|
|
135
|
-
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
left: `${B}px`,
|
|
139
|
-
color: A
|
|
140
|
-
},
|
|
141
|
-
children: r
|
|
133
|
+
const t = () => /* @__PURE__ */ p("div", { className: "electropherogram-sequence-letters", children: G.map((s, r) => {
|
|
134
|
+
const c = n[r], d = s === "A" ? h : s === "T" ? u : s === "G" ? i : s === "C" ? f : T.textColor, l = O(c);
|
|
135
|
+
return /* @__PURE__ */ p(
|
|
136
|
+
"span",
|
|
137
|
+
{
|
|
138
|
+
className: "electropherogram-sequence-letter",
|
|
139
|
+
style: {
|
|
140
|
+
left: `${l}px`,
|
|
141
|
+
color: d
|
|
142
142
|
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
143
|
+
children: s
|
|
144
|
+
},
|
|
145
|
+
`base-${r}`
|
|
146
|
+
);
|
|
147
|
+
}) }), x = () => {
|
|
148
|
+
const s = Math.min(...n), r = Math.max(...n), c = Math.ceil(s / y) * y, d = [];
|
|
149
|
+
for (let l = c; l <= r; l += y)
|
|
150
|
+
d.push({
|
|
151
|
+
position: l,
|
|
152
|
+
label: l.toString()
|
|
152
153
|
});
|
|
153
|
-
return /* @__PURE__ */
|
|
154
|
-
const
|
|
155
|
-
return /* @__PURE__ */
|
|
154
|
+
return /* @__PURE__ */ p("div", { className: "electropherogram-position-numbers", children: d.map((l) => {
|
|
155
|
+
const R = O(l.position);
|
|
156
|
+
return /* @__PURE__ */ p(
|
|
156
157
|
"span",
|
|
157
158
|
{
|
|
158
159
|
className: "electropherogram-position-number",
|
|
159
160
|
style: {
|
|
160
|
-
left: `${
|
|
161
|
+
left: `${R}px`
|
|
161
162
|
},
|
|
162
|
-
children:
|
|
163
|
+
children: l.label
|
|
163
164
|
},
|
|
164
|
-
`pos-${
|
|
165
|
+
`pos-${l.position}`
|
|
165
166
|
);
|
|
166
167
|
}) });
|
|
167
168
|
};
|
|
168
|
-
return /* @__PURE__ */
|
|
169
|
-
|
|
170
|
-
|
|
169
|
+
return /* @__PURE__ */ S("div", { className: "electropherogram-sequence-header", children: [
|
|
170
|
+
t(),
|
|
171
|
+
x()
|
|
171
172
|
] });
|
|
172
173
|
})(),
|
|
173
|
-
/* @__PURE__ */
|
|
174
|
-
|
|
174
|
+
/* @__PURE__ */ p("div", { className: "electropherogram-chart", children: /* @__PURE__ */ p("div", { ref: g, style: { width: "100%", height: "100%" } }) }),
|
|
175
|
+
F
|
|
175
176
|
] });
|
|
176
177
|
};
|
|
177
178
|
export {
|
|
178
|
-
|
|
179
|
+
ne as Electropherogram
|
|
179
180
|
};
|
|
180
181
|
//# sourceMappingURL=Electropherogram.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Electropherogram.js","sources":["../../../../src/components/charts/Electropherogram/Electropherogram.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { CHART_COLORS } from \"@/utils/colors\";\nimport \"./Electropherogram.scss\";\n\n/** Height offset for the plot area in pixels */\nconst PLOT_HEIGHT_OFFSET = 75;\n/** Scale factor for y-axis range to add padding above max value */\nconst Y_AXIS_PADDING_FACTOR = 1.05;\n\ninterface PeakData {\n position: number;\n base?: string;\n peakA: number;\n peakT: number;\n peakG: number;\n peakC: number;\n}\n\ninterface ElectropherogramProps {\n data?: PeakData[];\n width?: number;\n height?: number;\n positionInterval?: number;\n colorA?: string;\n colorT?: string;\n colorG?: string;\n colorC?: string;\n}\n\nconst determineBase = (item: PeakData): string => {\n if (item.base !== undefined) return item.base;\n const peakValues = {\n A: item.peakA,\n T: item.peakT,\n G: item.peakG,\n C: item.peakC,\n };\n\n const values = Object.values(peakValues);\n const allEqual = values.every((val) => val === values[0]);\n\n if (allEqual) {\n return \"\";\n }\n\n let highestBase = \"\";\n let highestValue = 0;\n\n Object.entries(peakValues).forEach(([base, value]) => {\n if (value > highestValue) {\n highestBase = base;\n highestValue = value;\n }\n });\n\n return highestBase;\n};\n\nconst Electropherogram: React.FC<ElectropherogramProps> = ({\n data = [],\n width = 900,\n height = 600,\n positionInterval = 10,\n colorA = CHART_COLORS[0], // blue\n colorT = CHART_COLORS[2], // teal/green\n colorG = CHART_COLORS[3], // red\n colorC = CHART_COLORS[1], // orange\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({\n xLabel: \"Position\",\n yLabel: \"Intensity\",\n });\n\n const positions = useMemo(() => data.map((item) => item.position), [data]);\n const sequence = useMemo(() => data.map((item) => determineBase(item)), [data]);\n const peakA = useMemo(() => data.map((item) => item.peakA), [data]);\n const peakT = useMemo(() => data.map((item) => item.peakT), [data]);\n const peakG = useMemo(() => data.map((item) => item.peakG), [data]);\n const peakC = useMemo(() => data.map((item) => item.peakC), [data]);\n\n const aTrace = useMemo(\n () => ({\n x: positions,\n y: peakA,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"A\",\n line: { color: colorA, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakA, colorA],\n );\n\n const tTrace = useMemo(\n () => ({\n x: positions,\n y: peakT,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"T\",\n line: { color: colorT, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakT, colorT],\n );\n\n const gTrace = useMemo(\n () => ({\n x: positions,\n y: peakG,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"G\",\n line: { color: colorG, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakG, colorG],\n );\n\n const cTrace = useMemo(\n () => ({\n x: positions,\n y: peakC,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"C\",\n line: { color: colorC, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakC, colorC],\n );\n\n const maxValue = useMemo(\n () => Math.max(...peakA, ...peakT, ...peakG, ...peakC),\n [peakA, peakT, peakG, peakC],\n );\n\n useEffect(() => {\n if (!plotRef.current || data.length === 0) return;\n\n const plotData = [aTrace, tTrace, gTrace, cTrace];\n\n const layout: Partial<Plotly.Layout> = {\n width: width,\n height: height - PLOT_HEIGHT_OFFSET,\n margin: { l: 0, r: 0, b: 20, t: 10, pad: 0 },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n showlegend: false,\n xaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [Math.min(...positions), Math.max(...positions)],\n fixedrange: true,\n },\n yaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [0, maxValue * Y_AXIS_PADDING_FACTOR],\n fixedrange: true,\n },\n };\n\n const config = {\n responsive: true,\n displayModeBar: false,\n displaylogo: false,\n fillFrame: true,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, plotData, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n }\n };\n }, [data, width, height, aTrace, tTrace, gTrace, cTrace, maxValue, positions, theme, bindTooltip]);\n\n if (data.length === 0) {\n return <div className=\"electropherogram-empty\">No data available</div>;\n }\n\n const renderSequence = () => {\n const renderSequenceLetters = () => {\n const minPosition = Math.min(...positions);\n const maxPosition = Math.max(...positions);\n const chartWidth = width;\n\n return (\n <div className=\"electropherogram-sequence-letters\">\n {sequence.map((base, index) => {\n const position = positions[index];\n const color =\n base === \"A\"\n ? colorA\n : base === \"T\"\n ? colorT\n : base === \"G\"\n ? colorG\n : base === \"C\"\n ? colorC\n : theme.textColor;\n\n const span = maxPosition - minPosition || 1;\n const percentage = (position - minPosition) / span;\n const leftPosition = percentage * chartWidth;\n\n return (\n <span\n key={`base-${index}`}\n className=\"electropherogram-sequence-letter\"\n style={{\n left: `${leftPosition}px`,\n color,\n }}\n >\n {base}\n </span>\n );\n })}\n </div>\n );\n };\n\n const renderPositionNumbers = () => {\n const minPosition = Math.min(...positions);\n const maxPosition = Math.max(...positions);\n const chartWidth = width;\n\n const startPos =\n Math.ceil(minPosition / positionInterval) * positionInterval;\n\n const regularPositionLabels: Array<{ position: number; label: string }> =\n [];\n\n for (let pos = startPos; pos <= maxPosition; pos += positionInterval) {\n regularPositionLabels.push({\n position: pos,\n label: pos.toString(),\n });\n }\n\n return (\n <div className=\"electropherogram-position-numbers\">\n {regularPositionLabels.map((label) => {\n const span = maxPosition - minPosition || 1;\n const percentage = (label.position - minPosition) / span;\n const leftPosition = percentage * chartWidth;\n\n return (\n <span\n key={`pos-${label.position}`}\n className=\"electropherogram-position-number\"\n style={{\n left: `${leftPosition}px`,\n }}\n >\n {label.label}\n </span>\n );\n })}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-sequence-header\">\n {renderSequenceLetters()}\n {renderPositionNumbers()}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-container relative\" style={{ width, height }}>\n {renderSequence()}\n <div className=\"electropherogram-chart\">\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n </div>\n {tooltipElement}\n </div>\n );\n};\n\nexport { Electropherogram };\nexport type { PeakData, ElectropherogramProps };\n"],"names":["PLOT_HEIGHT_OFFSET","Y_AXIS_PADDING_FACTOR","determineBase","item","peakValues","values","val","highestBase","highestValue","base","value","Electropherogram","data","width","height","positionInterval","colorA","CHART_COLORS","colorT","colorG","colorC","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","positions","useMemo","sequence","peakA","peakT","peakG","peakC","aTrace","tTrace","gTrace","cTrace","maxValue","useEffect","plotData","layout","CHART_FONT_FAMILY","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsx","jsxs","renderSequenceLetters","minPosition","maxPosition","chartWidth","index","position","color","span","leftPosition","renderPositionNumbers","startPos","regularPositionLabels","pos","label"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Electropherogram.js","sources":["../../../../src/components/charts/Electropherogram/Electropherogram.tsx"],"sourcesContent":["import React, { useEffect, useRef, useMemo } from \"react\";\n\nimport { useChartTooltip } from \"../ChartTooltip\";\nimport { getLoadedPlotly, loadPlotly } from \"../plotly-loader\";\n\nimport type Plotly from \"plotly.js-dist\";\n\nimport { CHART_FONT_FAMILY, usePlotlyTheme } from \"@/hooks/use-plotly-theme\";\nimport { CHART_COLORS } from \"@/utils/colors\";\nimport \"./Electropherogram.scss\";\n\n/**\n * Height of the sequence header above the plot (see Electropherogram.scss:\n * 12 + 6 padding, 35 + 6 letters row, 30 numbers row). The plot gets the rest.\n */\nconst PLOT_HEIGHT_OFFSET = 89;\n/** Scale factor for y-axis range to add padding above max value */\nconst Y_AXIS_PADDING_FACTOR = 1.05;\n\ninterface PeakData {\n position: number;\n base?: string;\n peakA: number;\n peakT: number;\n peakG: number;\n peakC: number;\n}\n\ninterface ElectropherogramProps {\n data?: PeakData[];\n width?: number;\n height?: number;\n positionInterval?: number;\n colorA?: string;\n colorT?: string;\n colorG?: string;\n colorC?: string;\n}\n\nconst determineBase = (item: PeakData): string => {\n if (item.base !== undefined) return item.base;\n const peakValues = {\n A: item.peakA,\n T: item.peakT,\n G: item.peakG,\n C: item.peakC,\n };\n\n const values = Object.values(peakValues);\n const allEqual = values.every((val) => val === values[0]);\n\n if (allEqual) {\n return \"\";\n }\n\n let highestBase = \"\";\n let highestValue = 0;\n\n Object.entries(peakValues).forEach(([base, value]) => {\n if (value > highestValue) {\n highestBase = base;\n highestValue = value;\n }\n });\n\n return highestBase;\n};\n\nconst Electropherogram: React.FC<ElectropherogramProps> = ({\n data = [],\n width = 900,\n height = 600,\n positionInterval = 10,\n colorA = CHART_COLORS[0], // blue\n colorT = CHART_COLORS[2], // teal/green\n colorG = CHART_COLORS[3], // red\n colorC = CHART_COLORS[1], // orange\n}) => {\n const plotRef = useRef<HTMLDivElement>(null);\n const theme = usePlotlyTheme();\n const { bindTooltip, tooltipElement } = useChartTooltip({\n xLabel: \"Position\",\n yLabel: \"Intensity\",\n });\n\n const positions = useMemo(() => data.map((item) => item.position), [data]);\n const sequence = useMemo(() => data.map((item) => determineBase(item)), [data]);\n const peakA = useMemo(() => data.map((item) => item.peakA), [data]);\n const peakT = useMemo(() => data.map((item) => item.peakT), [data]);\n const peakG = useMemo(() => data.map((item) => item.peakG), [data]);\n const peakC = useMemo(() => data.map((item) => item.peakC), [data]);\n\n const aTrace = useMemo(\n () => ({\n x: positions,\n y: peakA,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"A\",\n line: { color: colorA, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakA, colorA],\n );\n\n const tTrace = useMemo(\n () => ({\n x: positions,\n y: peakT,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"T\",\n line: { color: colorT, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakT, colorT],\n );\n\n const gTrace = useMemo(\n () => ({\n x: positions,\n y: peakG,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"G\",\n line: { color: colorG, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakG, colorG],\n );\n\n const cTrace = useMemo(\n () => ({\n x: positions,\n y: peakC,\n type: \"scatter\" as const,\n mode: \"lines\" as const,\n hoverinfo: \"none\" as const,\n name: \"C\",\n line: { color: colorC, width: 2, shape: \"spline\" as const },\n }),\n [positions, peakC, colorC],\n );\n\n const maxValue = useMemo(\n () => Math.max(...peakA, ...peakT, ...peakG, ...peakC),\n [peakA, peakT, peakG, peakC],\n );\n\n // The plot has no side margins, so an x-range that ends exactly on the first\n // and last sample clips the outermost peaks and pushes their base letters\n // half off the canvas (SW-2298). Pad by half a sample step on each side; the\n // header letters/numbers share this mapping so they stay aligned to peaks.\n const { xMin, xMax } = useMemo(() => {\n if (positions.length === 0) return { xMin: 0, xMax: 1 };\n const min = Math.min(...positions);\n const max = Math.max(...positions);\n const step = positions.length > 1 ? (max - min) / (positions.length - 1) : 1;\n const pad = step / 2;\n return { xMin: min - pad, xMax: max + pad };\n }, [positions]);\n const xToPx = (position: number) => ((position - xMin) / (xMax - xMin)) * width;\n\n useEffect(() => {\n if (!plotRef.current || data.length === 0) return;\n\n const plotData = [aTrace, tTrace, gTrace, cTrace];\n\n const layout: Partial<Plotly.Layout> = {\n width: width,\n height: height - PLOT_HEIGHT_OFFSET,\n margin: { l: 0, r: 0, b: 20, t: 10, pad: 0 },\n paper_bgcolor: theme.paperBg,\n plot_bgcolor: theme.plotBg,\n font: {\n family: CHART_FONT_FAMILY,\n },\n showlegend: false,\n xaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [xMin, xMax],\n fixedrange: true,\n },\n yaxis: {\n showgrid: false,\n zeroline: false,\n showticklabels: false,\n showline: false,\n range: [0, maxValue * Y_AXIS_PADDING_FACTOR],\n fixedrange: true,\n },\n };\n\n const config = {\n responsive: true,\n displayModeBar: false,\n displaylogo: false,\n fillFrame: true,\n };\n\n // Plotly is loaded lazily so it stays out of the consumer's main chunk\n // (SW-2007); the draw is skipped if the effect re-runs or unmounts first.\n let cancelled = false;\n let plotElement: HTMLDivElement | null = null;\n void loadPlotly().then((plotly) => {\n if (cancelled || !plotRef.current) return;\n plotly.newPlot(plotRef.current, plotData, layout, config);\n bindTooltip(plotRef.current);\n\n // Capture ref value for cleanup\n plotElement = plotRef.current;\n });\n\n return () => {\n cancelled = true;\n if (plotElement) {\n getLoadedPlotly().purge(plotElement);\n }\n };\n }, [data, width, height, aTrace, tTrace, gTrace, cTrace, maxValue, xMin, xMax, theme, bindTooltip]);\n\n if (data.length === 0) {\n return <div className=\"electropherogram-empty\">No data available</div>;\n }\n\n const renderSequence = () => {\n const renderSequenceLetters = () => {\n return (\n <div className=\"electropherogram-sequence-letters\">\n {sequence.map((base, index) => {\n const position = positions[index];\n const color =\n base === \"A\"\n ? colorA\n : base === \"T\"\n ? colorT\n : base === \"G\"\n ? colorG\n : base === \"C\"\n ? colorC\n : theme.textColor;\n\n const leftPosition = xToPx(position);\n\n return (\n <span\n key={`base-${index}`}\n className=\"electropherogram-sequence-letter\"\n style={{\n left: `${leftPosition}px`,\n color,\n }}\n >\n {base}\n </span>\n );\n })}\n </div>\n );\n };\n\n const renderPositionNumbers = () => {\n const minPosition = Math.min(...positions);\n const maxPosition = Math.max(...positions);\n\n const startPos =\n Math.ceil(minPosition / positionInterval) * positionInterval;\n\n const regularPositionLabels: Array<{ position: number; label: string }> =\n [];\n\n for (let pos = startPos; pos <= maxPosition; pos += positionInterval) {\n regularPositionLabels.push({\n position: pos,\n label: pos.toString(),\n });\n }\n\n return (\n <div className=\"electropherogram-position-numbers\">\n {regularPositionLabels.map((label) => {\n const leftPosition = xToPx(label.position);\n\n return (\n <span\n key={`pos-${label.position}`}\n className=\"electropherogram-position-number\"\n style={{\n left: `${leftPosition}px`,\n }}\n >\n {label.label}\n </span>\n );\n })}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-sequence-header\">\n {renderSequenceLetters()}\n {renderPositionNumbers()}\n </div>\n );\n };\n\n return (\n <div className=\"electropherogram-container relative\" style={{ width, height }}>\n {renderSequence()}\n <div className=\"electropherogram-chart\">\n <div ref={plotRef} style={{ width: \"100%\", height: \"100%\" }} />\n </div>\n {tooltipElement}\n </div>\n );\n};\n\nexport { Electropherogram };\nexport type { PeakData, ElectropherogramProps };\n"],"names":["PLOT_HEIGHT_OFFSET","Y_AXIS_PADDING_FACTOR","determineBase","item","peakValues","values","val","highestBase","highestValue","base","value","Electropherogram","data","width","height","positionInterval","colorA","CHART_COLORS","colorT","colorG","colorC","plotRef","useRef","theme","usePlotlyTheme","bindTooltip","tooltipElement","useChartTooltip","positions","useMemo","sequence","peakA","peakT","peakG","peakC","aTrace","tTrace","gTrace","cTrace","maxValue","xMin","xMax","min","max","pad","xToPx","position","useEffect","plotData","layout","CHART_FONT_FAMILY","config","cancelled","plotElement","loadPlotly","plotly","getLoadedPlotly","jsx","jsxs","renderSequenceLetters","index","color","leftPosition","renderPositionNumbers","minPosition","maxPosition","startPos","regularPositionLabels","pos","label"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const de=require("react/jsx-runtime"),n=require("react")
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const de=require("react/jsx-runtime"),n=require("react"),Se=require("../ChartTooltip/ChartTooltip.cjs"),x=require("../plotly-loader.cjs"),E=require("./constants.cjs"),c=require("./utils.cjs"),Te=require("../../../hooks/use-plotly-theme.cjs"),fe={},Ie={enabled:!0},ve=({data:R,title:z,xAxis:u=fe,yAxis:i=fe,colorMapping:r,shapeMapping:V,sizeMapping:X,tooltip:a=Ie,enableClickSelection:$=!0,enableBoxSelection:g=!0,enableLassoSelection:p=!0,selectedIds:H,onSelectionChange:J,onPointClick:K,downsampling:N,width:w=800,height:L=600,showColorBar:Q=!0,className:W})=>{const S=n.useRef(null),Y=Te.usePlotlyTheme(),P=n.useRef(J),Z=n.useRef(K),M=n.useRef(new Set),_=n.useRef(!1);P.current=J,Z.current=K;const[me,k]=n.useState(new Set),ee=H!==void 0,q=ee?H:me;M.current=q,_.current=ee;const te=n.useMemo(()=>new Set([...q].map(String)),[q]),ne=n.useMemo(()=>new Map(R.map(e=>[String(e.id),e.id])),[R]),t=n.useMemo(()=>N?c.downsampleData(R,N):R,[R,N]),h=n.useMemo(()=>c.mapColors(t,r),[t,r]),re=n.useMemo(()=>c.mapShapes(t,V),[t,V]),se=n.useMemo(()=>c.mapSizes(t,X),[t,X]),oe=n.useMemo(()=>{if(u.range)return u.range;if(u.autoRange!==!1)return c.calculateAxisRange(t,"x",u.autoRangePadding??E.PLOT_CONSTANTS.AUTO_RANGE_PADDING,u.scale)},[t,u]),ce=n.useMemo(()=>{if(i.range)return i.range;if(i.autoRange!==!1)return c.calculateAxisRange(t,"y",i.autoRangePadding??E.PLOT_CONSTANTS.AUTO_RANGE_PADDING,i.scale)},[t,i]),f=a.enabled!==!1,C=a.native===!0,B=n.useMemo(()=>f?t.map(e=>a.content?a.content(e):c.generateTooltipContent(e,a.fields)):[],[t,a,f]),{bindTooltip:le,tooltipElement:ye}=Se.useChartTooltip({getLines:e=>{const s=e[0]?.pointIndex;return s===void 0?[]:(B[s]??"").split("<br>").filter(Boolean)}}),ue=n.useMemo(()=>r?.type==="continuous"&&r.field&&t.length>0?t.map(e=>{const s=e.metadata?.[r.field];return typeof s=="number"&&Number.isFinite(s)?s:0}):h,[t,r,h]),D=n.useMemo(()=>{if(r?.type==="continuous"){const e=r.colorScale||E.DEFAULT_COLOR_SCALE;return e}},[r]),ie=n.useMemo(()=>{const e={size:se,symbol:re,line:{width:0}};if(r?.type==="continuous"&&D){if(e.color=ue,e.colorscale=D,e.showscale=Q,r.field){const s=r.min!==void 0&&r.max!==void 0?{min:r.min,max:r.max}:c.calculateRange(t,r.field);e.cmin=s.min,e.cmax=s.max,e.colorbar={title:{text:r.field,side:"right"},thickness:20,len:.7}}}else e.color=h;return e},[se,re,r,D,ue,Q,t,h]);n.useEffect(()=>{const e=S.current;if(!e||t.length===0)return;const s=t.map(d=>d.x),T=t.map(d=>d.y),O=t.map(d=>String(d.id)),I=[{x:s,y:T,ids:O,mode:"markers",type:"scatter",marker:ie,hoverinfo:f?C?"text":"none":"skip",text:B,hovertemplate:f&&C?"%{text}<extra></extra>":void 0,showlegend:!1,unselected:{marker:{opacity:.3}},selected:{marker:{opacity:1,line:{color:E.COLORS.selected,width:2}}}}],G=c.getPlotlyLayoutConfig({title:z,xAxis:u,yAxis:i,width:w,height:L,xRange:oe,yRange:ce,enableLassoSelection:p,enableBoxSelection:g,theme:Y}),U={responsive:!0,displayModeBar:!0,displaylogo:!1,modeBarButtonsToAdd:[],modeBarButtonsToRemove:["toImage"]};g&&U.modeBarButtonsToAdd?.push("select2d"),p&&U.modeBarButtonsToAdd?.push("lasso2d");let ae=!1,b=null;return x.loadPlotly().then(d=>{if(ae||!S.current)return;d.newPlot(e,I,G,U);const j=e;$&&j.on("plotly_click",l=>{const m=l.points[0];if(m&&m.data.ids){const A=m.data.ids[m.pointIndex],y=t.find(o=>String(o.id)===A);if(y){Z.current?.(y,l.event);const o=c.getSelectionMode(l.event),v=c.applySelection(M.current,new Set([y.id]),o);_.current||k(v),P.current?.(v,o)}}}),(g||p)&&(j.on("plotly_selected",l=>{if(l&&l.points){const m=l.points.map(o=>{if(o.data.ids&&o.pointIndex!==void 0){const v=o.data.ids[o.pointIndex];return ne.get(v)??v}return null}).filter(o=>o!==null),A="replace",y=new Set(m);_.current||k(y),P.current?.(y,A)}}),j.on("plotly_deselect",()=>{const l=new Set;_.current||k(l),P.current?.(l,"replace")})),f&&!C&&le(e),b=e}),()=>{ae=!0,b&&x.getLoadedPlotly().purge(b)}},[t,ie,u,i,oe,ce,z,w,L,a,B,$,g,p,ne,f,C,le,Y]),n.useEffect(()=>{if(!S.current||t.length===0)return;let e=!1;return x.loadPlotly().then(s=>{const T=S.current;if(e||!T||!T._fullLayout)return;const O=T,F=t.map((I,G)=>te.has(String(I.id))?G:null).filter(I=>I!==null);F.length>0?s.restyle(O,{selectedpoints:[F]},[0]):s.restyle(O,{selectedpoints:[null]},[0])}),()=>{e=!0}},[te,t]);const Re=W?`scatter-plot-interactive relative ${W}`:"scatter-plot-interactive relative";return de.jsxs("div",{className:Re,children:[de.jsx("div",{ref:S,className:"scatter-plot-interactive__plot",style:{width:w,height:L}}),ye]})};exports.ScatterPlotInteractive=ve;
|
|
2
2
|
//# sourceMappingURL=ScatterPlotInteractive.cjs.map
|