From 1f15211f5992d554b96c2ff2e26b48105de1f73e Mon Sep 17 00:00:00 2001 From: evompmi <269925742+evompmi@users.noreply.github.com> Date: Wed, 3 Jun 2026 23:36:50 +0200 Subject: [PATCH] =?UTF-8?q?fix(ui):=20replace=20text-default=20=E2=84=B9?= =?UTF-8?q?=20with=20a=20themed=20SVG=20info=20icon=20on=20the=20upload=20?= =?UTF-8?q?hint?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shared upload-panel separator hint led with a bare U+2139 "ℹ", whose default presentation is text, so it rendered as a plain italic "i" in the IBM Plex Mono chrome font instead of an info icon. Swap it for an inline SVG info mark drawn with currentColor, so it inherits --info-text and themes correctly in light and dark. Co-Authored-By: Claude Opus 4.8 (1M context) --- CHANGELOG.md | 4 ++++ .../chunks/{app-BIQIRH5G.js => app-3EKSFA3F.js} | 4 ++-- .../{app-BIQIRH5G.js.map => app-3EKSFA3F.js.map} | 0 .../chunks/{app-3ELPKOLT.js => app-BLY3XSWB.js} | 4 ++-- .../{app-3ELPKOLT.js.map => app-BLY3XSWB.js.map} | 0 .../chunks/{app-7CWQ47AS.js => app-IRS66EWH.js} | 4 ++-- .../{app-7CWQ47AS.js.map => app-IRS66EWH.js.map} | 0 .../chunks/{app-G5L2IE5E.js => app-P3YNIKGG.js} | 4 ++-- .../{app-G5L2IE5E.js.map => app-P3YNIKGG.js.map} | 0 .../chunks/{app-5CQNAWMD.js => app-PCLQZVY3.js} | 4 ++-- .../{app-5CQNAWMD.js.map => app-PCLQZVY3.js.map} | 0 .../chunks/{app-NW63HSPZ.js => app-RNTWQ7N2.js} | 4 ++-- .../{app-NW63HSPZ.js.map => app-RNTWQ7N2.js.map} | 0 .../chunks/{app-BDRRNBIR.js => app-TQWDLQEC.js} | 4 ++-- .../{app-BDRRNBIR.js.map => app-TQWDLQEC.js.map} | 0 .../chunks/{app-ZT4GXXOQ.js => app-VRVO3EYQ.js} | 4 ++-- .../{app-ZT4GXXOQ.js.map => app-VRVO3EYQ.js.map} | 0 .../{chunk-J6277AEU.js => chunk-DT6WX6AK.js} | 4 ++-- ...unk-J6277AEU.js.map => chunk-DT6WX6AK.js.map} | 0 .../{chunk-5F6S3FYW.js => chunk-HFTDMS7M.js} | 4 ++-- ...unk-5F6S3FYW.js.map => chunk-HFTDMS7M.js.map} | 4 ++-- .../{chunk-II356IQT.js => chunk-MQIRRYKA.js} | 4 ++-- ...unk-II356IQT.js.map => chunk-MQIRRYKA.js.map} | 0 ...-app-CV5FPHQ6.js => molarity-app-MWMOFUHJ.js} | 4 ++-- ...FPHQ6.js.map => molarity-app-MWMOFUHJ.js.map} | 0 ...wer-app-WK6W4FL3.js => power-app-4LQAP5A5.js} | 4 ++-- ...WK6W4FL3.js.map => power-app-4LQAP5A5.js.map} | 0 tools/_app/index.js | 2 +- tools/_shell/UploadPanel.tsx | 16 +++++++++++++++- 29 files changed, 48 insertions(+), 30 deletions(-) rename tools/_app/chunks/{app-BIQIRH5G.js => app-3EKSFA3F.js} (99%) rename tools/_app/chunks/{app-BIQIRH5G.js.map => app-3EKSFA3F.js.map} (100%) rename tools/_app/chunks/{app-3ELPKOLT.js => app-BLY3XSWB.js} (99%) rename tools/_app/chunks/{app-3ELPKOLT.js.map => app-BLY3XSWB.js.map} (100%) rename tools/_app/chunks/{app-7CWQ47AS.js => app-IRS66EWH.js} (99%) rename tools/_app/chunks/{app-7CWQ47AS.js.map => app-IRS66EWH.js.map} (100%) rename tools/_app/chunks/{app-G5L2IE5E.js => app-P3YNIKGG.js} (99%) rename tools/_app/chunks/{app-G5L2IE5E.js.map => app-P3YNIKGG.js.map} (100%) rename tools/_app/chunks/{app-5CQNAWMD.js => app-PCLQZVY3.js} (99%) rename tools/_app/chunks/{app-5CQNAWMD.js.map => app-PCLQZVY3.js.map} (100%) rename tools/_app/chunks/{app-NW63HSPZ.js => app-RNTWQ7N2.js} (99%) rename tools/_app/chunks/{app-NW63HSPZ.js.map => app-RNTWQ7N2.js.map} (100%) rename tools/_app/chunks/{app-BDRRNBIR.js => app-TQWDLQEC.js} (99%) rename tools/_app/chunks/{app-BDRRNBIR.js.map => app-TQWDLQEC.js.map} (100%) rename tools/_app/chunks/{app-ZT4GXXOQ.js => app-VRVO3EYQ.js} (99%) rename tools/_app/chunks/{app-ZT4GXXOQ.js.map => app-VRVO3EYQ.js.map} (100%) rename tools/_app/chunks/{chunk-J6277AEU.js => chunk-DT6WX6AK.js} (87%) rename tools/_app/chunks/{chunk-J6277AEU.js.map => chunk-DT6WX6AK.js.map} (100%) rename tools/_app/chunks/{chunk-5F6S3FYW.js => chunk-HFTDMS7M.js} (98%) rename tools/_app/chunks/{chunk-5F6S3FYW.js.map => chunk-HFTDMS7M.js.map} (72%) rename tools/_app/chunks/{chunk-II356IQT.js => chunk-MQIRRYKA.js} (94%) rename tools/_app/chunks/{chunk-II356IQT.js.map => chunk-MQIRRYKA.js.map} (100%) rename tools/_app/chunks/{molarity-app-CV5FPHQ6.js => molarity-app-MWMOFUHJ.js} (99%) rename tools/_app/chunks/{molarity-app-CV5FPHQ6.js.map => molarity-app-MWMOFUHJ.js.map} (100%) rename tools/_app/chunks/{power-app-WK6W4FL3.js => power-app-4LQAP5A5.js} (99%) rename tools/_app/chunks/{power-app-WK6W4FL3.js.map => power-app-4LQAP5A5.js.map} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04b284d7..f65fcb00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- **Upload-screen info hint shows a real info icon.** The separator-detection + hint led with a bare `ℹ` (U+2139), which has a text-default presentation and + rendered as a plain italic "i" in the mono chrome font. Replaced with an + inline `currentColor` SVG info mark that themes in both light and dark. - **Venn → UpSet handoff link no longer 404s on middle/ctrl-click.** The "use the UpSet tool" link pointed at `upset.html`; in the hash-routed SPA a new-tab open hit a non-existent page. It now targets `#/upset`. diff --git a/tools/_app/chunks/app-BIQIRH5G.js b/tools/_app/chunks/app-3EKSFA3F.js similarity index 99% rename from tools/_app/chunks/app-BIQIRH5G.js rename to tools/_app/chunks/app-3EKSFA3F.js index b562170c..a2d312c8 100644 --- a/tools/_app/chunks/app-BIQIRH5G.js +++ b/tools/_app/chunks/app-3EKSFA3F.js @@ -1,4 +1,4 @@ -import{computeGroupStats,computeStats,kde,quartiles}from"./chunk-II356IQT.js";import{makeLogTicks,makeTicks}from"./chunk-T4V5V5TK.js";import{ActionsPanel,BaseStyleControls,CldLabels,ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,FilterCheckboxPanel,GroupColorEditor,HowTo,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,RenameReorderPanel,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K,STATS_TESTS_FOR_K2,STATS_TEST_REGISTRY,SegToggle,SignificanceBrackets,SliderControl,StatsTable,UploadPanel,applyDiscretePalette,assignBracketLevels,autoDetectSep,buildRScript,compactLetterDisplay,computeLegendHeight,computePowerFromData,consumeHandoff,detectWideFormat,downloadCsv,downloadPng,downloadSvg,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,getPointColors,guessColumnType,isNumericValue,pStars,parseRaw,postHocForTest,renderSvgLegend,reshapeWide,resolveDiscretePalette,roleColors,runPostHoc,runTest,sampleMean,sampleSD,sanitizeRComment,seededRandom,selectTest,svgSafeId,tinv,usePlotToolState,wideToLong}from"./chunk-5F6S3FYW.js";var STATS_LINE_H=11,STATS_FONT=8;function statsSummaryHeight(summary){return summary?summary.split(` +import{computeGroupStats,computeStats,kde,quartiles}from"./chunk-MQIRRYKA.js";import{makeLogTicks,makeTicks}from"./chunk-T4V5V5TK.js";import{ActionsPanel,BaseStyleControls,CldLabels,ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,FilterCheckboxPanel,GroupColorEditor,HowTo,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,RenameReorderPanel,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K,STATS_TESTS_FOR_K2,STATS_TEST_REGISTRY,SegToggle,SignificanceBrackets,SliderControl,StatsTable,UploadPanel,applyDiscretePalette,assignBracketLevels,autoDetectSep,buildRScript,compactLetterDisplay,computeLegendHeight,computePowerFromData,consumeHandoff,detectWideFormat,downloadCsv,downloadPng,downloadSvg,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,getPointColors,guessColumnType,isNumericValue,pStars,parseRaw,postHocForTest,renderSvgLegend,reshapeWide,resolveDiscretePalette,roleColors,runPostHoc,runTest,sampleMean,sampleSD,sanitizeRComment,seededRandom,selectTest,svgSafeId,tinv,usePlotToolState,wideToLong}from"./chunk-HFTDMS7M.js";var STATS_LINE_H=11,STATS_FONT=8;function statsSummaryHeight(summary){return summary?summary.split(` `).length*STATS_LINE_H+14:0}var TEST_LABELS_BP=Object.fromEntries(Object.entries(STATS_TEST_REGISTRY).map(([id,entry])=>[id,entry.label])),POSTHOC_LABELS_BP=Object.fromEntries(Object.entries(STATS_POSTHOC_REGISTRY).map(([id,entry])=>[id,entry.label])),TEST_OPTIONS_BP_2=STATS_TESTS_FOR_K2,TEST_OPTIONS_BP_K=STATS_TESTS_FOR_K,ERROR_BAR_LABELS={none:"None",sd:"SD",sem:"SEM",ci95:"95% CI"},numCast=v=>v;function formatBpStatShort(testName,res){if(!testName||!res||res.error)return"\u2014";if(testName==="studentT"||testName==="welchT")return`t(${numCast(res.df).toFixed(2)}) = ${numCast(res.t).toFixed(3)}`;if(testName==="mannWhitney")return`U = ${numCast(res.U).toFixed(1)}`;if(testName==="oneWayANOVA"||testName==="welchANOVA"){let df2=typeof res.df2=="number"?res.df2.toFixed(2):res.df2;return`F(${res.df1}, ${df2}) = ${numCast(res.F).toFixed(3)}`}return testName==="kruskalWallis"?`H(${numCast(res.df)}) = ${numCast(res.H).toFixed(3)}`:"\u2014"}function formatBpResultLine(testName,res){if(!testName||!res)return"\u2014";if(res.error)return"\u26A0 "+res.error;if(testName==="studentT"||testName==="welchT")return`t(${numCast(res.df).toFixed(2)}) = ${numCast(res.t).toFixed(3)}, p = ${formatP(res.p)}`;if(testName==="mannWhitney")return`U = ${numCast(res.U).toFixed(1)}, z = ${numCast(res.z).toFixed(3)}, p = ${formatP(res.p)}`;if(testName==="oneWayANOVA"||testName==="welchANOVA"){let df2=typeof res.df2=="number"?res.df2.toFixed(2):res.df2;return`F(${res.df1}, ${df2}) = ${numCast(res.F).toFixed(3)}, p = ${formatP(res.p)}`}return testName==="kruskalWallis"?`H(${numCast(res.df)}) = ${numCast(res.H).toFixed(3)}, p = ${formatP(res.p)}`:"\u2014"}function computeBpAnnotationSpec(row,displayMode,showNs){if(displayMode==="none"||!row||row.skip)return null;let{k,names,testResult,postHocResult}=row;if(k<2)return null;if(k===2){let p=testResult&&!testResult.error?testResult.p:null;return p==null||!showNs&&p>=.05?null:{kind:"brackets",pairs:[{i:0,j:1,p,label:pStars(p)}],groupNames:names}}if(!postHocResult||postHocResult.error)return null;if(displayMode==="cld")return{kind:"cld",labels:compactLetterDisplay(postHocResult.pairs,k),groupNames:names};let pairs=postHocResult.pairs.map(pr=>({i:pr.i,j:pr.j,p:pr.pAdj!=null?pr.pAdj:pr.p})).map(pr=>({...pr,label:pStars(pr.p)})).filter(pr=>showNs||pr.p<.05);return pairs.length===0?null:{kind:"brackets",pairs,groupNames:names}}function summariseNormality(norm){if(!Array.isArray(norm)||norm.length===0)return"\u2014";let hasTrue=!1,hasFalse=!1;for(let r of norm)r.normal===!0?hasTrue=!0:r.normal===!1&&(hasFalse=!0);return hasFalse?"no":hasTrue?"yes":"\u2014"}function summariseEqualVariance(lev){return!lev||"error"in lev||lev.F==null?"\u2014":lev.equalVar?"yes":"no"}function computeBpSummaryText(row,showSummary,errorBarLabel){if(!showSummary||!row||row.skip)return null;let{chosenTest,testResult,k,postHocName,rec}=row;if(!chosenTest||!testResult||testResult.error)return null;let lines=[`Normality: ${summariseNormality(rec?.normality)}`,`Equal variance: ${summariseEqualVariance(rec?.levene)}`,`Test: ${TEST_LABELS_BP[chosenTest]||chosenTest}`];return k>2&&postHocName&&lines.push(`Post-hoc: ${POSTHOC_LABELS_BP[postHocName]||postHocName}`),errorBarLabel&&lines.push(`Error bars: ${errorBarLabel}`),lines.join(` `)}function mergeSubgroupAnnotations(subgroups,flatGroups,perKeySpecs){let total=flatGroups.length,names=flatGroups.map(g=>g.name),cldLabels=new Array(total).fill(null),allPairs=[],hasCld=!1,hasBrackets=!1;for(let sg of subgroups){let spec=perKeySpecs[sg.name];if(spec){if(spec.kind==="cld"&&spec.labels)hasCld=!0,spec.labels.forEach((lbl,i)=>{cldLabels[sg.startIndex+i]=lbl});else if(spec.kind==="brackets"&&spec.pairs){hasBrackets=!0;for(let pr of spec.pairs)allPairs.push({...pr,i:pr.i+sg.startIndex,j:pr.j+sg.startIndex})}else if(spec.kind==="both"){hasCld=!0,hasBrackets=!0,spec.labels.forEach((lbl,i)=>{cldLabels[sg.startIndex+i]=lbl});for(let pr of spec.pairs)allPairs.push({...pr,i:pr.i+sg.startIndex,j:pr.j+sg.startIndex})}}}return!hasCld&&!hasBrackets?null:hasBrackets&&hasCld?{kind:"both",labels:cldLabels,pairs:allPairs,groupNames:names}:hasBrackets?{kind:"brackets",pairs:allPairs,groupNames:names}:{kind:"cld",labels:cldLabels,groupNames:names}}var BOXPLOT_HOWTO={toolName:"boxplot",title:"Group Plot \u2014 How to use",subtitle:"Compare a numeric measurement across categorical groups",purpose:React.createElement(React.Fragment,null,"Side-by-side comparison of a numeric measurement across two or more groups (genotypes, treatments, conditions). Routes the right statistical test for the data shape and overlays the result."),dataLayout:React.createElement(React.Fragment,null,React.createElement("strong",null,"Long")," (preferred) \u2014 one row per observation, with a categorical"," ",React.createElement("strong",null,"group")," column and a numeric ",React.createElement("strong",null,"value")," column."," ",React.createElement("strong",null,"Wide")," (one column per group) is auto-detected and reshaped on the fly. Optional extra columns become filters / facets / sub-groups."),display:React.createElement(React.Fragment,null,"Switch between ",React.createElement("strong",null,"box")," / ",React.createElement("strong",null,"violin")," / ",React.createElement("strong",null,"raincloud")," /"," ",React.createElement("strong",null,"bar"),". Significance is computed automatically (",React.createElement("em",null,"t")," / Welch / Mann-Whitney for k = 2; ANOVA / Welch-ANOVA / Kruskal-Wallis with Tukey / Games-Howell / Dunn post-hoc for k \u2265 3) and rendered as brackets or compact-letter display."),tips:React.createElement(React.Fragment,null,"Pick a ",React.createElement("strong",null,"Color by")," column to map a second categorical to point colour;"," ",React.createElement("strong",null,"Facet by")," splits the chart into a small-multiples grid;"," ",React.createElement("strong",null,"Subgroup by")," nests a second factor inside each group's box.")};var BP_AES_THEMES={group:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"Group (X axis)"},value:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Value (Y axis)"}};function BpAesBox({theme,children}){let t=BP_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function OtherColumnsPanel({headers,rows,colRoles,colNames,onRoleChange,onNameChange}){let groupColIdx=colRoles.indexOf("group"),valueColIdx=colRoles.indexOf("value"),otherIdxs=headers.map((_,i)=>i).filter(i=>i!==groupColIdx&&i!==valueColIdx);return otherIdxs.length===0?null:React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Other columns"),React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:"var(--text-faint)",lineHeight:1.4}},"Toggle ",React.createElement("strong",{style:{color:roleColors.filter}},"filter")," to keep the column available for the Filter step and for color / facet / subgroup mapping on the plot. Otherwise the column is ignored."),React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},otherIdxs.map(i=>{let seen=new Set,u=[];rows.forEach(r=>{let v=r[i];seen.has(v)||(seen.add(v),u.push(v))});let pv=u.slice(0,5).join(", ")+(u.length>5?` \u2026 (${u.length})`:""),isFilter=colRoles[i]==="filter";return React.createElement("div",{key:`col-${i}`,style:{display:"flex",gap:10,alignItems:"center",padding:"6px 10px",background:"var(--surface)",borderRadius:6,border:`1.5px solid ${isFilter?roleColors.filter:"var(--border)"}`}},React.createElement("span",{style:{fontWeight:700,color:"var(--text-muted)",minWidth:20,fontSize:11}},"#",i+1),React.createElement("input",{value:colNames[i],onChange:e=>onNameChange(i,e.target.value),className:"dv-input",style:{width:140,fontWeight:600,fontSize:12}}),React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:11,color:isFilter?roleColors.filter:"var(--text-muted)",fontWeight:600,cursor:"pointer"}},React.createElement("input",{type:"checkbox",checked:isFilter,onChange:e=>onRoleChange(i,e.target.checked?"filter":"ignore"),style:{accentColor:roleColors.filter,cursor:"pointer"}}),"filter"),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",flex:1,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"},title:pv},pv))})))}function UploadStep({sepOverride,onSepChange,rawText,doParse,handleFileLoad,handleTextPaste,setStep,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{onSepChange(v),rawText&&(doParse(rawText,v),setStep("configure"))},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Plant biomass under drought & salt",subtitle:"3 genotypes \xD7 3 treatments \xD7 8 replicates \xB7 72 rows",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 one row per observation \xB7 2 MB max"}),React.createElement(HowTo,{...BOXPLOT_HOWTO}))}function ConfigureStep({fileName,parsedHeaders,parsedRows,hasHeader,colRoles,colNames,valueColIdx,valueColIsNumeric,detectedSep,onRoleChange,onNameChange}){let groupColIdx=colRoles.indexOf("group");return React.createElement("div",null,React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(240px, 1fr))",gap:12,marginBottom:16}},React.createElement(BpAesBox,{theme:"group"},React.createElement("select",{value:groupColIdx>=0?groupColIdx:"",onChange:e=>{let raw=e.target.value;raw!==""&&onRoleChange(Number(raw),"group")},className:"dv-select",style:{width:"100%"}},groupColIdx<0&&React.createElement("option",{value:""},"\u2014 choose a group column \u2014"),parsedHeaders.map((_,i)=>React.createElement("option",{key:i,value:i},colNames[i]))),groupColIdx>=0&&React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,marginTop:6},title:"Rename the selected column. The new name is used on the X-axis label and in exports."},React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",whiteSpace:"nowrap"}},"Display as"),React.createElement("input",{value:colNames[groupColIdx],onChange:e=>onNameChange(groupColIdx,e.target.value),className:"dv-input",style:{flex:1,fontSize:12,fontWeight:600}})),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Categorical column that defines the X-axis groups (genotypes, treatments, \u2026).")),React.createElement(BpAesBox,{theme:"value"},React.createElement("select",{value:valueColIdx>=0?valueColIdx:"",onChange:e=>{let raw=e.target.value;raw!==""&&onRoleChange(Number(raw),"value")},className:"dv-select",style:{width:"100%"}},valueColIdx<0&&React.createElement("option",{value:""},"\u2014 choose a value column \u2014"),parsedHeaders.map((_,i)=>React.createElement("option",{key:i,value:i},colNames[i]))),valueColIdx>=0&&React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,marginTop:6},title:"Rename the selected column. The new name is used on the Y-axis label and in exports."},React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",whiteSpace:"nowrap"}},"Display as"),React.createElement("input",{value:colNames[valueColIdx],onChange:e=>onNameChange(valueColIdx,e.target.value),className:"dv-input",style:{flex:1,fontSize:12,fontWeight:600}})),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column plotted as the Y-axis measurement."))),React.createElement(OtherColumnsPanel,{headers:parsedHeaders,rows:parsedRows,colRoles,colNames,onRoleChange,onNameChange}),valueColIdx>=0&&!valueColIsNumeric&&React.createElement("div",{className:"dv-panel",style:{background:"var(--danger-bg)",borderColor:"var(--danger-border)",marginBottom:12}},React.createElement("p",{style:{fontSize:12,color:"var(--danger-text)"}},"\u26A0 Column ",React.createElement("strong",null,'"',colNames[valueColIdx],'"')," is assigned as"," ",React.createElement("strong",null,"value")," but appears to be non-numeric \u2014 the plot will be empty. Please assign a numeric column as value.")),(colRoles.indexOf("group")<0||colRoles.indexOf("value")<0)&&React.createElement("div",{className:"dv-panel",style:{background:"var(--warning-bg)",borderColor:"var(--warning-border)",marginBottom:12}},React.createElement("p",{style:{fontSize:12,color:"var(--warning-text)"}},"Assign at least one ",React.createElement("strong",{style:{color:roleColors.group}},"group")," and one"," ",React.createElement("strong",{style:{color:roleColors.value}},"value")," column to continue.")),React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,color:"var(--text-muted)"}},React.createElement("strong",{style:{color:"var(--text)"}},fileName)," \u2014 ",parsedHeaders.length," cols \xD7 ",parsedRows.length," rows",hasHeader?"":" (no header)",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement("p",{style:{fontSize:11,color:"var(--text-faint)",marginBottom:10}},"Preview (first 8 rows):"),React.createElement(DataPreview,{headers:parsedHeaders,rows:parsedRows,maxRows:8})))}function FilterStep({parsedHeaders,parsedRows,colRoles,colNames,filters,filteredRows,renamedRows,activeColIdxs,valueRenames,orderableCols,applyRename,toggleFilter,toggleAllFilter,setRenameVal,dragState,setDragState}){let[flashKey,setFlashKey]=React.useState(0),prevKeptRef=React.useRef(filteredRows.length);React.useEffect(()=>{prevKeptRef.current!==filteredRows.length&&(prevKeptRef.current=filteredRows.length,setFlashKey(k=>k+1))},[filteredRows.length]);let keptCount=filteredRows.length,totalCount=parsedRows.length,filteredOut=totalCount-keptCount;return React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:16,alignItems:"stretch",marginBottom:16}},React.createElement(FilterCheckboxPanel,{headers:parsedHeaders,colNames,colRoles,filters,filteredCount:filteredRows.length,totalCount:parsedRows.length,onToggle:toggleFilter,onToggleAll:toggleAllFilter}),React.createElement(RenameReorderPanel,{headers:parsedHeaders,colNames,colRoles,filters,valueRenames,orderableCols,applyRename,onRenameVal:setRenameVal,dragState,onDragStart:setDragState,onDragEnd:()=>setDragState(null)})),React.createElement("div",{key:`preview-${flashKey}`,style:{borderRadius:10,padding:16,marginBottom:16,border:"1px solid var(--border)",background:"var(--surface-subtle)",animation:flashKey>0?"bp-filter-flash 300ms ease-out":void 0}},React.createElement("p",{style:{margin:"0 0 6px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Preview \xB7 ",React.createElement("strong",null,keptCount.toLocaleString())," of ",totalCount.toLocaleString()," ","rows",filteredOut>0&&React.createElement(React.Fragment,null," \xB7 ",React.createElement("span",{style:{color:"var(--warning-text)"}},React.createElement("strong",null,filteredOut.toLocaleString())," filtered out"))),React.createElement(DataPreview,{headers:activeColIdxs.map(i=>colNames[i]),rows:renamedRows.map(r=>activeColIdxs.map(i=>r[i])),maxRows:10})),React.createElement("style",null,` @keyframes bp-filter-flash { @@ -20,4 +20,4 @@ import{computeGroupStats,computeStats,kde,quartiles}from"./chunk-II356IQT.js";im `):block))}}return parts.join(` `)}var statsInit={displayMode:"none",showNs:!1,showSummary:!1,cellAnnotations:{},cellSummaries:{}};function statsReducer(state,a){switch(a.type){case"reset":return statsInit;case"setDisplayMode":return a.value==="none"?state.displayMode==="none"&&Object.keys(state.cellAnnotations).length===0?state:{...state,displayMode:"none",cellAnnotations:{}}:state.displayMode===a.value?state:{...state,displayMode:a.value};case"setShowNs":return state.showNs===a.value?state:{...state,showNs:a.value};case"setShowSummary":return a.value?state.showSummary?state:{...state,showSummary:!0}:!state.showSummary&&Object.keys(state.cellSummaries).length===0?state:{...state,showSummary:!1,cellSummaries:{}};case"clearCells":return Object.keys(state.cellAnnotations).length===0&&Object.keys(state.cellSummaries).length===0?state:{...state,cellAnnotations:{},cellSummaries:{}};case"setCellAnnotation":return state.cellAnnotations[a.key]===a.value?state:{...state,cellAnnotations:{...state.cellAnnotations,[a.key]:a.value}};case"setCellSummary":return state.cellSummaries[a.key]===a.value?state:{...state,cellSummaries:{...state.cellSummaries,[a.key]:a.value}};default:return state}}var{useState,useMemo:useMemo2,useRef:useRef3,useEffect:useEffect2}=React;function BoxplotStatsDetail({row,onOverrideTest,isOverridden}){let names=row.names,values=row.values,k=names.length,res=row.testResult??{},rec=row.rec??{},recReason=rec.recommendation?.reason,recTest=rec.recommendation?.test??null,suggestion=rec.suggestion??null,testOptions=k===2?TEST_OPTIONS_BP_2:TEST_OPTIONS_BP_K,postHoc=row.postHocResult,power=row.powerResult,subhead={margin:"10px 0 6px",padding:"4px 10px",fontSize:10,fontWeight:800,textTransform:"uppercase",letterSpacing:"0.8px",color:"var(--subhead-text)",background:"var(--subhead-bg)",borderRadius:4,display:"block"},thS={textAlign:"left",padding:"3px 6px",borderBottom:"1px solid var(--border)",color:"var(--text-muted)",fontWeight:600,fontSize:11},tdS={padding:"3px 6px",borderBottom:"1px solid var(--border)",color:"var(--text)",fontSize:11},pillOk={display:"inline-block",padding:"1px 6px",borderRadius:8,fontSize:9,fontWeight:700,background:"var(--step-ready-bg)",color:"var(--step-ready)"},pillBad={...pillOk,background:"var(--danger-bg)",color:"var(--danger-text)"},pillNeutral={...pillOk,background:"var(--neutral-bg)",color:"var(--neutral-text)"},norm=rec.normality??[],lev=rec.levene??{};return React.createElement("div",{style:{padding:"6px 16px 12px 16px",background:"var(--surface-subtle)"}},React.createElement("div",{style:subhead},"Groups"),React.createElement("table",{style:{width:"100%",borderCollapse:"collapse"}},React.createElement("thead",null,React.createElement("tr",null,React.createElement("th",{style:thS},"Group"),React.createElement("th",{style:thS},"n"),React.createElement("th",{style:thS},"Mean"),React.createElement("th",{style:thS},"SD"),React.createElement("th",{style:thS},"SEM"),React.createElement("th",{style:thS},"95% CI"))),React.createElement("tbody",null,names.map((name,i)=>{let vs=values[i],n=vs.length,m=sampleMean(vs),sd=n>1?sampleSD(vs):0,sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0;return React.createElement("tr",{key:i},React.createElement("td",{style:tdS},name),React.createElement("td",{style:tdS},n),React.createElement("td",{style:tdS},m.toFixed(3)),React.createElement("td",{style:tdS},sd.toFixed(3)),React.createElement("td",{style:tdS},n>1?sem.toFixed(3):"\u2014"),React.createElement("td",{style:tdS},n>1?`\xB1${ci95.toFixed(3)}`:"\u2014"))}))),React.createElement("div",{style:subhead},"Assumptions"),norm.length>0&&React.createElement("div",{style:{marginBottom:6}},React.createElement("div",{style:{fontSize:11,fontWeight:600,color:"var(--text-muted)",marginBottom:2}},"Shapiro-Wilk (normality)"),React.createElement("div",{style:{display:"flex",gap:6,flexWrap:"wrap"}},norm.map((r,i)=>{let label=names[r.group]||`g${r.group}`,pill=r.normal===!0?pillOk:r.normal===!1?pillBad:pillNeutral,verdict=r.normal===!0?"normal":r.normal===!1?"not normal":"\u2014";return React.createElement("span",{key:i,style:{fontSize:11,color:"var(--text)"}},label,": ",React.createElement("span",{style:pill},verdict))}))),lev.F!=null&&React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)"}},React.createElement("span",{style:{fontWeight:600}},"Levene")," \u2014 F(",lev.df1,", ",lev.df2,") ="," ",lev.F.toFixed(3),", p = ",formatP(lev.p)," ",React.createElement("span",{style:lev.equalVar?pillOk:pillBad},lev.equalVar?"equal variance":"unequal variance")),React.createElement("div",{style:subhead},"Test"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",marginBottom:6}},React.createElement("select",{value:row.chosenTest||"",onChange:e=>{let next=e.target.value===""?null:e.target.value;onOverrideTest&&onOverrideTest(next===recTest?null:next)},className:"dv-select",style:{fontSize:11,padding:"2px 6px",minWidth:180},onClick:e=>e.stopPropagation()},testOptions.map(t=>React.createElement("option",{key:t,value:t},TEST_LABELS_BP[t],t===recTest?" (recommended)":""))),isOverridden&&React.createElement("button",{type:"button",onClick:e=>{e.stopPropagation(),onOverrideTest&&onOverrideTest(null)},className:"dv-btn dv-btn-secondary",style:{padding:"2px 8px",fontSize:10}},"Use recommendation")),recReason&&React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",fontStyle:"italic",marginBottom:6}},recReason),suggestion&&row.chosenTest!==suggestion.test&&React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap",padding:"6px 10px",marginBottom:6,background:"var(--info-bg)",border:"1px solid var(--info-border)",borderRadius:6,fontSize:11,color:"var(--info-text)"}},React.createElement("span",{style:{fontWeight:700}},"Suggested alternative:"),React.createElement("span",null,"Shapiro-Wilk flagged non-normal data \u2014 consider"," ",React.createElement("strong",null,TEST_LABELS_BP[suggestion.test]||suggestion.test),"."),React.createElement("button",{type:"button",onClick:e=>{e.stopPropagation(),onOverrideTest&&onOverrideTest(suggestion.test)},className:"dv-btn dv-btn-secondary",style:{padding:"2px 8px",fontSize:10,marginLeft:"auto"}},"Use suggestion")),React.createElement("div",{style:{padding:"6px 10px",background:"var(--surface)",border:"1px solid var(--border)",borderRadius:6,fontFamily:"ui-monospace, Menlo, monospace",fontSize:11,color:"var(--text)"}},res.error?`\u26A0 ${res.error}`:row.chosenTest?formatBpResultLine(row.chosenTest,res):"\u2014"),k>=3&&postHoc&&!postHoc.error&&row.postHocName&&React.createElement(React.Fragment,null,React.createElement("div",{style:subhead},"Post-hoc \u2014 ",POSTHOC_LABELS_BP[row.postHocName]||row.postHocName),React.createElement("table",{style:{width:"100%",borderCollapse:"collapse"}},React.createElement("thead",null,React.createElement("tr",null,React.createElement("th",{style:thS},"Pair"),React.createElement("th",{style:thS},row.postHocName==="dunn"?"Rank diff":"Mean diff"),React.createElement("th",{style:thS},"p"),React.createElement("th",{style:thS},"Signif."))),React.createElement("tbody",null,postHoc.pairs.map((pr,i)=>{let p=pr.pAdj!=null?pr.pAdj:pr.p,diff=pr.diff!=null?pr.diff.toFixed(3):pr.z!=null?`z = ${pr.z.toFixed(3)}`:"\u2014";return React.createElement("tr",{key:i},React.createElement("td",{style:tdS},names[pr.i]," vs ",names[pr.j]),React.createElement("td",{style:tdS},diff),React.createElement("td",{style:tdS},formatP(p)),React.createElement("td",{style:{...tdS,fontWeight:700,color:p<.05?"var(--step-ready)":"var(--text-faint)"}},pStars(p)))})))),power&&React.createElement(React.Fragment,null,React.createElement("div",{style:subhead},"Replication planning (n for 80% power)"),React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",marginBottom:6}},"Given the observed effect size, sample size a future study would need to detect this effect at 80% power."),React.createElement("table",{style:{width:"100%",borderCollapse:"collapse"}},React.createElement("thead",null,React.createElement("tr",null,React.createElement("th",{style:thS},"Effect size"),React.createElement("th",{style:thS},"\u03B1"),React.createElement("th",{style:thS},"n for 80% power"))),React.createElement("tbody",null,power.rows.map((pr,i)=>React.createElement("tr",{key:i},i===0?React.createElement("td",{style:tdS,rowSpan:power.rows.length},power.effectLabel," = ",power.effect.toFixed(3),power.effectCI?`, 95% CI [${power.effectCI.lo.toFixed(3)}, ${power.effectCI.hi.toFixed(3)}]`:""):null,React.createElement("td",{style:tdS},String(pr.alpha)),React.createElement("td",{style:tdS},pr.nForTarget!=null?`${pr.nForTarget} ${power.nLabel}`:"> 5000"))))),power.approximate&&React.createElement("div",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic",marginTop:4}},"Approximation \u2014 rank-based test power estimated from its parametric analog.")))}function BoxplotStatsPanel({sets,setLabel,fileStem,onAnnotationForKey,onSummaryForKey,singletonAutoExpand=!1,displayMode,onDisplayModeChange,showNs,onShowNsChange,showSummary,onShowSummaryChange,errorBarLabel}){let[expanded,setExpanded]=useState(singletonAutoExpand&&sets.length===1?{[sets[0].key]:!0}:{}),[hovered,setHovered]=useState(null),[overrides,setOverrides]=useState({}),setDisplayMode=onDisplayModeChange,setShowNs=onShowNsChange,setShowSummary=onShowSummaryChange,enriched=useMemo2(()=>sets.map(s=>{let validGroups=(s.groups||[]).filter(g=>!!g&&Array.isArray(g.values)&&g.values.length>=2),names=validGroups.map(g=>g.name),values=validGroups.map(g=>g.values.slice()),k=names.length;if(k<2)return{...s,names,values,k,skip:!0};let rec=selectTest(values),recTest=rec.recommendation?.test??null,chosenTest=overrides[s.key]||recTest||null,testResult=chosenTest?runTest(chosenTest,values):null,postHocName=postHocForTest(chosenTest),postHocResult=k>2&&postHocName?runPostHoc(postHocName,values):null,powerResult=chosenTest?computePowerFromData(chosenTest,values):null;return{...s,names,values,k,rec,recTest,chosenTest,testResult,postHocName,postHocResult,powerResult}}),[sets,overrides]),annotByKey=useMemo2(()=>{let out={};for(let r of enriched)out[r.key]=r.skip?null:computeBpAnnotationSpec(r,displayMode,showNs);return out},[enriched,displayMode,showNs]),annotKey=JSON.stringify(annotByKey),onAnnotationForKeyRef=useRef3(onAnnotationForKey);onAnnotationForKeyRef.current=onAnnotationForKey,useEffect2(()=>{if(typeof onAnnotationForKeyRef.current=="function")for(let r of sets)onAnnotationForKeyRef.current(r.key,annotByKey[r.key]||null)},[annotKey]);let summaryByKey=useMemo2(()=>{let out={};for(let r of enriched)out[r.key]=r.skip?null:computeBpSummaryText(r,showSummary,errorBarLabel);return out},[enriched,showSummary,errorBarLabel]),summaryKey=JSON.stringify(summaryByKey),onSummaryForKeyRef=useRef3(onSummaryForKey);onSummaryForKeyRef.current=onSummaryForKey,useEffect2(()=>{if(typeof onSummaryForKeyRef.current=="function")for(let r of sets)onSummaryForKeyRef.current(r.key,summaryByKey[r.key]||null)},[summaryKey]);let setOverride=(key,test)=>setOverrides(prev=>{let next={...prev};return test==null?delete next[key]:next[key]=test,next}),eligible=enriched.filter(r=>!r.skip);if(eligible.length===0)return null;let hasR=typeof buildRScript=="function",stem=typeof fileStem=="string"&&fileStem.trim()?(typeof svgSafeId=="function"?svgSafeId(fileStem):fileStem).replace(/^-+|-+$/g,""):"stats_report",rowSlug=(row,i)=>{let raw=row.name||`set-${i+1}`;return(typeof svgSafeId=="function"?svgSafeId(String(raw)).replace(/^-+|-+$/g,""):String(raw).replace(/[^A-Za-z0-9._-]+/g,"-").replace(/^-+|-+$/g,""))||`set-${i+1}`},downloadReport=e=>{eligible.length===1?downloadText(buildBpAggregateReport(eligible,setLabel),`${stem}_stats.txt`):eligible.forEach((row,i)=>{let content=buildBpAggregateReport([row],setLabel),name=`${stem}_${rowSlug(row,i)}_stats.txt`;setTimeout(()=>downloadText(content,name),i*120)}),flashSaved(e.currentTarget)},downloadR=e=>{eligible.length===1?downloadText(buildBpAggregateRScript(eligible,setLabel),`${stem}_stats.R`):eligible.forEach((row,i)=>{let content=buildBpAggregateRScript([row],setLabel),name=`${stem}_${rowSlug(row,i)}_stats.R`;setTimeout(()=>downloadText(content,name),i*120)}),flashSaved(e.currentTarget)},anyMulti=eligible.some(r=>r.k>2),singleSet=sets.length===1,headingLabel=setLabel&&!singleSet?`Statistics at each ${setLabel.toLowerCase()}`:"Statistics",thS={textAlign:"left",padding:"6px 8px",borderBottom:"1px solid var(--border)",color:"var(--subhead-text)",fontWeight:600,fontSize:12,background:"var(--subhead-bg)"},tdS={padding:"6px 8px",borderBottom:"1px solid var(--border)",color:"var(--text)",fontSize:12},mono={fontFamily:"ui-monospace, Menlo, monospace"},segBtn=(value,label)=>{let active=displayMode===value;return React.createElement("button",{key:value,type:"button",onClick:()=>setDisplayMode(value),style:{flex:"0 0 auto",padding:"4px 10px",fontSize:11,fontWeight:active?700:400,fontFamily:"inherit",cursor:"pointer",border:"none",background:active?"var(--accent-primary)":"var(--surface)",color:active?"var(--on-accent)":"var(--text-muted)",transition:"background 120ms ease, color 120ms ease"}},label)},nsDisabled=displayMode==="none"||anyMulti&&displayMode==="cld";return React.createElement("div",{className:"dv-panel",style:{padding:0,overflow:"hidden"}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",padding:"12px 14px",borderBottom:"1px solid var(--border)",gap:12,flexWrap:"wrap"}},React.createElement("div",null,React.createElement("h3",{style:{margin:0,fontSize:14,fontWeight:700,color:"var(--text)",letterSpacing:"0.2px"}},headingLabel),React.createElement("p",{style:{margin:"3px 0 0",fontSize:11,color:"var(--text-faint)"}},"Click a row to inspect decision trace, assumptions, post-hoc and power.",singleSet?"":" Tests are independent per "+(setLabel||"set").toLowerCase()+".")),React.createElement("div",{style:{display:"flex",gap:6,flexShrink:0,marginLeft:"auto"}},React.createElement("button",{type:"button",className:"dv-btn dv-btn-dl",onClick:downloadReport,title:singleSet?"Download a plain-text stats report":`Download a plain-text report covering every ${(setLabel||"set").toLowerCase()}`},"\u2B07 TXT"),hasR&&React.createElement("button",{type:"button",className:"dv-btn dv-btn-dl",onClick:downloadR,title:singleSet?"Download a runnable R script reproducing these tests":`Download a runnable R script reproducing every ${(setLabel||"set").toLowerCase()} test`},"\u2B07 R"))),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:14,flexWrap:"wrap",padding:"10px 14px",borderBottom:"1px solid var(--border)",background:"var(--surface-subtle)"}},React.createElement("span",{style:{fontSize:11,color:"var(--text-muted)",fontWeight:600}},"Display on plot"),React.createElement("div",{style:{display:"flex",borderRadius:6,overflow:"hidden",border:"1px solid var(--border-strong)"}},segBtn("none","Off"),anyMulti&&segBtn("cld","Letters"),segBtn("brackets","Brackets")),React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:12,color:nsDisabled?"var(--text-faint)":"var(--text)",cursor:nsDisabled?"not-allowed":"pointer",opacity:nsDisabled?.55:1}},React.createElement("input",{type:"checkbox",checked:!nsDisabled&&showNs,disabled:nsDisabled,onChange:e=>setShowNs(e.target.checked),style:{accentColor:"var(--cta-primary-bg)"}}),"Show ns"),React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,fontSize:12,color:"var(--text)",cursor:"pointer"}},React.createElement("input",{type:"checkbox",checked:showSummary,onChange:e=>setShowSummary(e.target.checked),style:{accentColor:"var(--cta-primary-bg)"}}),"Print summary below plot")),React.createElement("table",{style:{width:"100%",borderCollapse:"collapse"}},React.createElement("thead",null,React.createElement("tr",null,React.createElement("th",{style:thS},setLabel||"Set"),React.createElement("th",{style:thS},"Groups"),React.createElement("th",{style:thS},"Test"),React.createElement("th",{style:thS},"Statistic"),React.createElement("th",{style:thS},"p"),React.createElement("th",{style:{...thS,width:60}}))),React.createElement("tbody",null,enriched.map(r=>{let key=r.key,isOpen=!!expanded[key];if(r.skip)return React.createElement("tr",{key},React.createElement("td",{style:tdS},r.name||"\u2014"),React.createElement("td",{style:tdS,colSpan:5},React.createElement("span",{style:{color:"var(--text-faint)",fontStyle:"italic"}},"Needs \u2265 2 groups with n \u2265 2 to run a test.")));let p=r.testResult&&!r.testResult.error?r.testResult.p:null,sig=p!=null&&p<.05,stars=p!=null?pStars(p):"";return React.createElement(React.Fragment,{key},React.createElement("tr",{onClick:()=>setExpanded(prev=>({...prev,[key]:!isOpen})),onMouseEnter:()=>setHovered(key),onMouseLeave:()=>setHovered(h=>h===key?null:h),style:{cursor:"pointer",background:isOpen?"var(--surface-subtle)":hovered===key?"var(--row-hover-bg)":void 0,transition:"background 120ms ease"}},React.createElement("td",{style:tdS},r.name||"\u2014"),React.createElement("td",{style:tdS},r.k),React.createElement("td",{style:tdS},r.chosenTest?TEST_LABELS_BP[r.chosenTest]||r.chosenTest:"\u2014"),React.createElement("td",{style:{...tdS,...mono}},formatBpStatShort(r.chosenTest,r.testResult)),React.createElement("td",{style:{...tdS,...mono,fontWeight:sig?700:400,color:sig?"var(--step-ready)":"var(--text)"}},p!=null?formatP(p):"\u2014"),React.createElement("td",{style:{...tdS,textAlign:"right",color:sig?"var(--step-ready)":"var(--text-faint)",fontWeight:700}},stars&&stars!=="ns"?stars:"")),isOpen&&React.createElement("tr",null,React.createElement("td",{colSpan:6,style:{padding:0,borderBottom:"1px solid var(--border)"}},React.createElement(BoxplotStatsDetail,{row:r,isOverridden:!!overrides[key],onOverrideTest:t=>setOverride(key,t)}))))}))))}var{useState:useState2,useReducer,useMemo:useMemo3,useCallback,useRef:useRef4}=React,VIS_INIT_BOXPLOT={plotTitle:"",yLabel:"Value",plotBg:"#ffffff",showGrid:!1,gridColor:"#e0e0e0",boxFillOpacity:.15,boxWidth:70,boxGap:0,pointSize:2.5,showPoints:!0,jitterWidth:.6,pointOpacity:.6,xLabelAngle:0,yMinCustom:"",yMaxCustom:"",yScale:"linear",showCompPie:!1,plotStyle:"box",horizontal:!1,errorType:"sem",errStrokeWidth:1.2,showBarOutline:!1,barOutlineWidth:1.5,barOutlineColor:"#333333",barOpacity:.25,boxplotColors:{},categoryColors:{},discretePalette:"okabe-ito",categoryPalette:"okabe-ito"},EXAMPLE_CSV=(()=>{let rng=seededRandom(42),norm=()=>{let u=Math.max(rng(),1e-9),v=rng();return Math.sqrt(-2*Math.log(u))*Math.cos(2*Math.PI*v)},genotypes=[{name:"WT",base:100},{name:"abi4",base:96},{name:"oxSOS1",base:128}],treatments=[{name:"control",delta:0,sd:8},{name:"drought",delta:-24,sd:10},{name:"salt",delta:-5,sd:9}],lines=["Genotype,Treatment,Replicate,Biomass_mg"];for(let g of genotypes)for(let t of treatments)for(let r=1;r<=8;r++){let v=g.base+t.delta+norm()*t.sd;lines.push(`${g.name},${t.name},${r},${v.toFixed(1)}`)}return lines.join(` `)})();function App(){let shell=usePlotToolState("boxplot",VIS_INIT_BOXPLOT),{step,setStep,fileName,setFileName,setParseError,sepOverride,setSepOverride,setCommaFixed,setCommaFixCount,setInjectionWarning,vis,updVis}=shell,[rawText,setRawText]=useState2(null),[dataFormat,setDataFormat]=useState2("long"),[wideSkipped,setWideSkipped]=useState2(0),[parsedHeaders,setParsedHeaders]=useState2([]),[parsedRows,setParsedRows]=useState2([]),[hasHeader,setHasHeader]=useState2(!0),[colRoles,setColRoles]=useState2([]),[colNames,setColNames]=useState2([]),[filters,setFilters]=useState2({}),[valueRenames,setValueRenames]=useState2({}),boxplotColors=useMemo3(()=>vis.boxplotColors||{},[vis.boxplotColors]),setBoxplotColors=useCallback(updater=>updVis({boxplotColors:typeof updater=="function"?updater(vis.boxplotColors||{}):updater||{}}),[updVis,vis.boxplotColors]),[plotGroupRenames,setPlotGroupRenames]=useState2({}),[disabledGroups,setDisabledGroups]=useState2({}),[columnOrders,setColumnOrders]=useState2({}),setOrderForCol=(i,newOrder)=>setColumnOrders(prev=>({...prev,[i]:newOrder})),[colorByCol,setColorByCol]=useState2(-1),categoryColors=useMemo3(()=>vis.categoryColors||{},[vis.categoryColors]),setCategoryColors=useCallback(updater=>updVis({categoryColors:typeof updater=="function"?updater(vis.categoryColors||{}):updater||{}}),[updVis,vis.categoryColors]),[dragState,setDragState]=useState2(null),[facetByCol,_setFacetByCol]=useState2(-1),[subgroupByCol,_setSubgroupByCol]=useState2(-1),handleSetFacetByCol=v=>{facetByCol!==v&&dispatchStats({type:"clearCells"}),_setFacetByCol(v),v>=0&&v===subgroupByCol&&_setSubgroupByCol(-1)},handleSetSubgroupByCol=v=>{subgroupByCol!==v&&dispatchStats({type:"clearCells"}),_setSubgroupByCol(v),v>=0&&v===facetByCol&&_setFacetByCol(-1)},[statsUi,dispatchStats]=useReducer(statsReducer,statsInit),handleStatsShowSummaryChange=v=>dispatchStats({type:"setShowSummary",value:v}),handleStatsDisplayModeChange=v=>dispatchStats({type:"setDisplayMode",value:v}),setStatsShowNs=v=>dispatchStats({type:"setShowNs",value:v}),setCellAnnotation=useCallback((key,spec)=>dispatchStats({type:"setCellAnnotation",key,value:spec}),[]),setCellSummary=useCallback((key,txt)=>dispatchStats({type:"setCellSummary",key,value:txt}),[]),facetRefs=useRef4({}),chartRef=useRef4(null),resetDerived=useCallback(()=>{setValueRenames({}),setBoxplotColors({}),setPlotGroupRenames({}),setDisabledGroups({}),setColumnOrders({}),setColorByCol(-1),setCategoryColors({}),_setFacetByCol(-1),_setSubgroupByCol(-1),dispatchStats({type:"reset"}),updVis({yMinCustom:"",yMaxCustom:""})},[setBoxplotColors,setCategoryColors,updVis]),buildFilters=(hdrs,rws)=>{let f={};return hdrs.forEach((_,i)=>{let u=[...new Set(rws.map(r=>r[i]))].sort();f[i]={unique:u,included:new Set(u)}}),f},[detectedSep,setDetectedSep]=useState2(""),doParse=useCallback((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep),fixedText=dc.text;setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count),setRawText(fixedText);let{headers,rows,hasHeader:hh,injectionWarnings}=parseRaw(fixedText,effectiveSep);if(setInjectionWarning(injectionWarnings),!headers.length||!rows.length){setParseError("The file appears to be empty or has no data rows. Please check your file and try again.");return}if(setParseError(null),detectWideFormat(headers,rows)){let{headers:lh,rows:lr,skipped}=wideToLong(headers,rows);setParsedHeaders(lh),setParsedRows(lr),setHasHeader(!0),setColRoles(["group","value"]),setColNames([...lh]),setFilters(buildFilters(lh,lr)),resetDerived(),setDataFormat("wide"),setWideSkipped(skipped||0),setStep("plot")}else{setWideSkipped(0),setParsedHeaders(headers),setParsedRows(rows),setHasHeader(hh);{let seenGroup=!1,seenValue=!1;setColRoles(headers.map((_,i)=>{let r=guessColumnType(rows.map(row=>row[i]??""));return r==="group"?seenGroup?"filter":(seenGroup=!0,r):r==="value"?seenValue?"filter":(seenValue=!0,r):r}))}setColNames([...headers]),setFilters(buildFilters(headers,rows)),resetDerived(),setDataFormat("long"),setStep("configure")}},[resetDerived,setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep]),handleFileLoad=useCallback((text,name)=>{setFileName(name),doParse(text,sepOverride)},[sepOverride,doParse,setFileName]),loadExample=useCallback(()=>{setSepOverride(""),setFileName("example_plant_growth.csv"),doParse(EXAMPLE_CSV,"")},[doParse,setFileName,setSepOverride]),handleTextPaste=useCallback((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]);React.useEffect(()=>{let apply=payload=>{!payload||!payload.csv||(setSepOverride(""),setFileName(payload.fileName||"from_handoff.csv"),doParse(payload.csv,""),typeof payload.yLabel=="string"&&payload.yLabel.trim()!==""&&updVis({yLabel:payload.yLabel.trim()}),setStep("plot"))};apply(consumeHandoff("boxplot"));let onStorage=e=>{e.key!=="dataviz-handoff"||!e.newValue||apply(consumeHandoff("boxplot"))},onSameTabHandoff=()=>{apply(consumeHandoff("boxplot"))};return window.addEventListener("storage",onStorage),window.addEventListener("plottr-handoff",onSameTabHandoff),()=>{window.removeEventListener("storage",onStorage),window.removeEventListener("plottr-handoff",onSameTabHandoff)}},[]);let resetAll=()=>{setRawText(null),setParsedRows([]),setParsedHeaders([]),setFileName(""),setInjectionWarning(null),setStep("upload")},applyRename=(ci,v)=>valueRenames[ci]&&valueRenames[ci][v]!=null?valueRenames[ci][v]:v,filteredRows=useMemo3(()=>parsedRows.filter(r=>r.every((v,ci)=>!filters[ci]||filters[ci].included.has(v))),[parsedRows,filters]),renamedRows=useMemo3(()=>filteredRows.map(r=>r.map((v,ci)=>applyRename(ci,v))),[filteredRows,valueRenames]),activeColIdxs=useMemo3(()=>colRoles.reduce((acc,r,i)=>(r!=="ignore"&&acc.push(i),acc),[]),[colRoles]),groupColIdx=colRoles.indexOf("group"),valueColIdx=colRoles.indexOf("value"),valueColName=valueColIdx>=0&&colNames[valueColIdx]||"",prevAutoYLabel=useRef4(VIS_INIT_BOXPLOT.yLabel);React.useEffect(()=>{if(!valueColName)return;let current=vis.yLabel||"";(current===""||current===VIS_INIT_BOXPLOT.yLabel||current===prevAutoYLabel.current)&¤t!==valueColName&&updVis({yLabel:valueColName}),prevAutoYLabel.current=valueColName},[valueColName]);let groupedData=useMemo3(()=>{if(groupColIdx<0||valueColIdx<0)return{};let g={};return renamedRows.forEach(r=>{let k=r[groupColIdx];g[k]||(g[k]=[]),g[k].push(r[valueColIdx])}),g},[renamedRows,groupColIdx,valueColIdx]),stats=useMemo3(()=>computeGroupStats(groupedData),[groupedData]),wideData=useMemo3(()=>groupColIdx<0||valueColIdx<0?null:reshapeWide(renamedRows,groupColIdx,valueColIdx),[renamedRows,groupColIdx,valueColIdx]),naturalGroupOrder=useMemo3(()=>{if(groupColIdx<0||valueColIdx<0)return[];let seen=new Set,order=[];return renamedRows.forEach(r=>{let g=r[groupColIdx];seen.has(g)||(seen.add(g),order.push(g))}),order},[renamedRows,groupColIdx,valueColIdx]),effectiveOrder=useMemo3(()=>{let stored=columnOrders[groupColIdx];if(stored&&stored.length>0){let valid=stored.filter(g=>naturalGroupOrder.includes(g)),missing=naturalGroupOrder.filter(g=>!stored.includes(g));return[...valid,...missing]}return naturalGroupOrder},[columnOrders,groupColIdx,naturalGroupOrder]),colorByCandidates=useMemo3(()=>parsedHeaders.map((_,i)=>i).filter(i=>i!==groupColIdx&&i!==valueColIdx&&(colRoles[i]==="filter"||colRoles[i]==="group")),[parsedHeaders,groupColIdx,valueColIdx,colRoles]),colorByCategories=useMemo3(()=>colorByCol<0?[]:[...new Set(renamedRows.map(r=>r[colorByCol]))].sort(),[colorByCol,renamedRows]),boxplotGroups=useMemo3(()=>{if(groupColIdx<0||valueColIdx<0)return[];let gm={};renamedRows.forEach(r=>{if(groupColIdx>=r.length||valueColIdx>=r.length)return;let g=r[groupColIdx],v=Number(r[valueColIdx]);if(!(r[valueColIdx]===""||isNaN(v)))if(gm[g]||(gm[g]={}),colorByCol>=0){let cat=(colorByCol=0?colorByCategories:["_all"],filtered=effectiveOrder.filter(name=>gm[name]),seedColors=resolveDiscretePalette(vis.discretePalette||"okabe-ito",filtered.length);return filtered.map((name,gi)=>{let catMap=gm[name],sources=cats.filter(c=>catMap[c]).map((cat,si)=>({colIndex:si,values:catMap[cat],category:cat})),allValues=sources.flatMap(s=>s.values),q=quartiles(allValues),cs=computeStats(allValues),stats2=q&&cs?{...q,...cs}:null;return{name,sources,allValues,stats:stats2,color:boxplotColors[name]||seedColors[gi%Math.max(1,seedColors.length)]||PALETTE[gi%PALETTE.length]}})},[renamedRows,groupColIdx,valueColIdx,boxplotColors,effectiveOrder,colorByCol,colorByCategories,vis.discretePalette]),allDisplayGroups=useMemo3(()=>boxplotGroups.map(g=>({...g,displayName:plotGroupRenames[g.name]??g.name,enabled:!disabledGroups[g.name]})),[boxplotGroups,plotGroupRenames,disabledGroups]),displayBoxplotGroups=useMemo3(()=>allDisplayGroups.filter(g=>g.enabled).map(g=>({...g,name:g.displayName})),[allDisplayGroups]),naturalFacetOrder=useMemo3(()=>{if(facetByCol<0)return[];let seen=new Set,order=[];return renamedRows.forEach(r=>{let v=r[facetByCol];seen.has(v)||(seen.add(v),order.push(v))}),order},[facetByCol,renamedRows]),facetByCategories=useMemo3(()=>{let stored=columnOrders[facetByCol];if(stored&&stored.length>0){let valid=stored.filter(g=>naturalFacetOrder.includes(g)),missing=naturalFacetOrder.filter(g=>!stored.includes(g));return[...valid,...missing]}return naturalFacetOrder},[columnOrders,facetByCol,naturalFacetOrder]),orderableCols=useMemo3(()=>{let m={};return parsedHeaders.forEach((_,i)=>{if(i===valueColIdx||colRoles[i]!=="group"&&colRoles[i]!=="filter")return;let seen=new Set,natural=[];renamedRows.forEach(r=>{let v=r[i];seen.has(v)||(seen.add(v),natural.push(v))});let stored=columnOrders[i],order=natural;if(stored&&stored.length>0){let valid=stored.filter(g=>natural.includes(g)),missing=natural.filter(g=>!stored.includes(g));order=[...valid,...missing]}m[i]={order,onReorder:newOrder=>setOrderForCol(i,newOrder)}}),m},[parsedHeaders,colRoles,valueColIdx,renamedRows,columnOrders]),buildCellGroups=useCallback((cellRows,globalColorMap)=>{let gm={};cellRows.forEach(r=>{if(groupColIdx>=r.length||valueColIdx>=r.length)return;let g=r[groupColIdx],v=Number(r[valueColIdx]);if(!(r[valueColIdx]===""||isNaN(v)))if(gm[g]||(gm[g]={}),colorByCol>=0){let cc=(colorByCol=0?colorByCategories:["_all"],filtered=effectiveOrder.filter(name=>gm[name]&&!disabledGroups[name]),seedColors=resolveDiscretePalette(vis.discretePalette||"okabe-ito",filtered.length);return filtered.map((name,gi)=>{let catMap=gm[name],sources=cats.filter(c=>catMap[c]).map((c,si)=>({colIndex:si,values:catMap[c],category:c})),allValues=sources.flatMap(s=>s.values),q=quartiles(allValues),cs=computeStats(allValues),stats2=q&&cs?{...q,...cs}:null;return{name,sources,allValues,stats:stats2,color:globalColorMap[name]||boxplotColors[name]||seedColors[gi%Math.max(1,seedColors.length)]||PALETTE[gi%PALETTE.length]}})},[groupColIdx,valueColIdx,colorByCol,colorByCategories,effectiveOrder,disabledGroups,boxplotColors,vis.discretePalette]),facetedData=useMemo3(()=>{if(facetByCol<0)return[];let globalColorMap={};boxplotGroups.forEach(g=>{globalColorMap[g.name]=g.color});let sgOrder=subgroupByCol>=0?orderableCols[subgroupByCol]?.order||[]:null;return facetByCategories.map(cat=>{let catRows=renamedRows.filter(r=>r[facetByCol]===cat);if(sgOrder&&sgOrder.length>0){let subgroups=[],flatGroups=[],startIndex=0;for(let sgCat of sgOrder){let sgRows=catRows.filter(r=>r[subgroupByCol]===sgCat),groups2=buildCellGroups(sgRows,globalColorMap);groups2.length>0&&(subgroups.push({name:sgCat,startIndex,count:groups2.length}),flatGroups.push(...groups2),startIndex+=groups2.length)}return{category:cat,groups:flatGroups,subgroups,flatGroups}}let groups=buildCellGroups(catRows,globalColorMap);return{category:cat,groups,subgroups:null,flatGroups:null}})},[facetByCol,facetByCategories,subgroupByCol,orderableCols,renamedRows,boxplotGroups,buildCellGroups]),subgroupedData=useMemo3(()=>{if(subgroupByCol<0||facetByCol>=0||groupColIdx<0||valueColIdx<0)return null;let sgOrder=orderableCols[subgroupByCol]?.order||[];if(sgOrder.length===0)return null;let globalColorMap={};boxplotGroups.forEach(g=>{globalColorMap[g.name]=g.color});let subgroups=[],flatGroups=[],startIndex=0;for(let sgCat of sgOrder){let sgRows=renamedRows.filter(r=>r[subgroupByCol]===sgCat),groups=buildCellGroups(sgRows,globalColorMap);groups.length>0&&(subgroups.push({name:sgCat,startIndex,count:groups.length}),flatGroups.push(...groups),startIndex+=groups.length)}return subgroups.length===0?null:{subgroups,flatGroups}},[subgroupByCol,facetByCol,orderableCols,renamedRows,groupColIdx,valueColIdx,boxplotGroups,buildCellGroups]),FLAT_KEY="flat",cellKey=(facetCat,sgName)=>JSON.stringify([facetCat,sgName]),mergeAnnotForSubgroups=useCallback((subgroups,flatGroups,facetCat)=>{let renamedFlat=flatGroups.map(g=>({...g,name:plotGroupRenames[g.name]??g.name})),perKeySpecs={};for(let sg of subgroups)perKeySpecs[sg.name]=statsUi.cellAnnotations[cellKey(facetCat,sg.name)]||null;return mergeSubgroupAnnotations(subgroups,renamedFlat,perKeySpecs)},[statsUi.cellAnnotations,plotGroupRenames]),mergedSubgroupAnnot=useMemo3(()=>subgroupedData?mergeAnnotForSubgroups(subgroupedData.subgroups,subgroupedData.flatGroups,""):null,[subgroupedData,mergeAnnotForSubgroups]),chartAnnotations=subgroupByCol>=0&&subgroupedData?mergedSubgroupAnnot:statsUi.cellAnnotations[FLAT_KEY]||null,chartSummary=subgroupByCol>=0&&subgroupedData?null:statsUi.cellSummaries[FLAT_KEY]||null,perFacetChartAnnotations=useMemo3(()=>{if(facetByCol<0)return{};let out={};for(let fd of facetedData)fd.subgroups&&fd.flatGroups?out[fd.category]=mergeAnnotForSubgroups(fd.subgroups,fd.flatGroups,fd.category):out[fd.category]=statsUi.cellAnnotations[cellKey(fd.category,"")]||null;return out},[facetByCol,facetedData,statsUi.cellAnnotations,mergeAnnotForSubgroups]),perFacetSubgroupSummaries=useMemo3(()=>{if(facetByCol<0)return{};let out={};for(let fd of facetedData){if(!fd.subgroups)continue;let map={};for(let sg of fd.subgroups)map[sg.name]=statsUi.cellSummaries[cellKey(fd.category,sg.name)]||null;out[fd.category]=map}return out},[facetByCol,facetedData,statsUi.cellSummaries]),perFacetSummaries=useMemo3(()=>{if(facetByCol<0)return{};let out={};for(let fd of facetedData)fd.subgroups||(out[fd.category]=statsUi.cellSummaries[cellKey(fd.category,"")]||null);return out},[facetByCol,facetedData,statsUi.cellSummaries]),statsPanelMode=facetByCol>=0&&subgroupByCol>=0?"facetSubgroup":facetByCol>=0?"facet":subgroupByCol>=0?"subgroup":"flat",statsPanelLabel=useMemo3(()=>statsPanelMode==="facet"?"Facet":statsPanelMode==="subgroup"?"Subgroup":statsPanelMode==="facetSubgroup"?"Facet \xD7 Subgroup":"",[statsPanelMode]),statsPanelSets=useMemo3(()=>{let renamedValues=gs=>gs.map(g=>({name:plotGroupRenames[g.name]??g.name,values:g.allValues}));if(statsPanelMode==="flat")return displayBoxplotGroups.length<2?[]:[{key:FLAT_KEY,name:"",groups:displayBoxplotGroups.map(g=>({name:g.name,values:g.allValues}))}];if(statsPanelMode==="facet")return facetedData.filter(fd=>fd.groups.length>=2).map(fd=>({key:cellKey(fd.category,""),name:fd.category,groups:renamedValues(fd.groups)}));if(statsPanelMode==="subgroup")return subgroupedData?subgroupedData.subgroups.map(sg=>{let sgGroups=subgroupedData.flatGroups.slice(sg.startIndex,sg.startIndex+sg.count);return{key:cellKey("",sg.name),name:sg.name,groups:renamedValues(sgGroups)}}).filter(s=>s.groups.length>=2):[];let out=[];for(let fd of facetedData)if(!(!fd.subgroups||!fd.flatGroups))for(let sg of fd.subgroups){let sgGroups=fd.flatGroups.slice(sg.startIndex,sg.startIndex+sg.count);sgGroups.length<2||out.push({key:cellKey(fd.category,sg.name),name:`${fd.category} \u2014 ${sg.name}`,groups:renamedValues(sgGroups)})}return out},[statsPanelMode,displayBoxplotGroups,facetedData,subgroupedData,plotGroupRenames]),toggleFilter=(ci,v)=>setFilters(p=>{let f={...p},s=new Set(f[ci].included);return s.has(v)?s.delete(v):s.add(v),f[ci]={...f[ci],included:s},f}),toggleAllFilter=(ci,allOn)=>setFilters(p=>{let f={...p};return f[ci]={...f[ci],included:allOn?new Set(f[ci].unique):new Set},f}),setRenameVal=(ci,ov,nv)=>{(ci===facetByCol||ci===subgroupByCol)&&dispatchStats({type:"clearCells"}),setValueRenames(p=>{let r={...p};return r[ci]||(r[ci]={}),r[ci]={...r[ci],[ov]:nv},r})},updateRole=(i,role)=>{(i===facetByCol||i===subgroupByCol)&&dispatchStats({type:"clearCells"}),setColRoles(p=>p.map((r,j)=>j===i?role:(role==="group"||role==="value")&&r===role?"filter":r))},updateColName=(i,nm)=>setColNames(p=>p.map((n,j)=>j===i?nm:n)),yMinVal=vis.yMinCustom!==""?Number(vis.yMinCustom):null,yMaxVal=vis.yMaxCustom!==""?Number(vis.yMaxCustom):null,valueColIsNumeric=useMemo3(()=>{if(valueColIdx<0||!parsedRows.length)return!1;let vals=parsedRows.map(r=>r[valueColIdx]??"").filter(v=>v!=="");return vals.length>0&&vals.filter(v=>isNumericValue(v)).length/vals.length>.5},[parsedRows,valueColIdx]),canPlot=groupColIdx>=0&&valueColIdx>=0&&valueColIsNumeric&&boxplotGroups.length>0,handleToggleGroup=i=>{let name=boxplotGroups[i].name;setDisabledGroups(p=>({...p,[name]:!p[name]}))},fileStem=`${fileBaseName(fileName,"groupplot")}_groupplot`,handleDownloadSvg=useCallback(()=>{facetByCol>=0&&facetedData.length>0?facetedData.forEach(fd=>downloadSvg(facetRefs.current[fd.category],`${fileStem}_${fd.category}.svg`)):downloadSvg(chartRef.current,`${fileStem}.svg`)},[facetByCol,facetedData,fileStem]),handleDownloadPng=useCallback(()=>{facetByCol>=0&&facetedData.length>0?facetedData.forEach(fd=>downloadPng(facetRefs.current[fd.category],`${fileStem}_${fd.category}.png`)):downloadPng(chartRef.current,`${fileStem}.png`)},[facetByCol,facetedData,fileStem]);return React.createElement(PlotToolShell,{state:shell,toolName:"boxplot",title:"Group Plot",visInit:VIS_INIT_BOXPLOT,steps:["upload","configure","filter","output","plot"],canNavigate:s=>s==="upload"||parsedRows.length>0},step==="upload"&&React.createElement(UploadStep,{sepOverride,onSepChange:setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,setStep,onLoadExample:loadExample}),step==="configure"&&parsedRows.length>0&&React.createElement(ConfigureStep,{fileName,parsedHeaders,parsedRows,hasHeader,colRoles,colNames,valueColIdx,valueColIsNumeric,detectedSep,onRoleChange:updateRole,onNameChange:updateColName}),step==="filter"&&parsedRows.length>0&&React.createElement(FilterStep,{parsedHeaders,parsedRows,colRoles,colNames,filters,filteredRows,renamedRows,activeColIdxs,valueRenames,orderableCols,applyRename,toggleFilter,toggleAllFilter,setRenameVal,dragState,setDragState}),step==="output"&&parsedRows.length>0&&React.createElement(OutputStep,{colNames,groupColIdx,valueColIdx,valueColIsNumeric,stats,renamedRows,activeColIdxs,wideData,fileName}),step==="plot"&&canPlot&&wideSkipped>0&&React.createElement("div",{role:"status",style:{background:"var(--warning-bg)",color:"var(--warning-text)",border:"1px solid var(--warning-border)",borderRadius:6,padding:"8px 12px",fontSize:12,marginBottom:12}},"Wide-to-long reshape skipped ",React.createElement("strong",null,wideSkipped)," ",wideSkipped===1?"cell":"cells"," that were empty or non-numeric. Sample size shrank silently \u2014 verify the kept rows match what you expected."),step==="plot"&&canPlot&&React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotControls,{dataFormat,setDataFormat,setStep,resetAll,allDisplayGroups,boxplotGroups,renamedRows,setPlotGroupRenames,setBoxplotColors,onToggleGroup:handleToggleGroup,vis,updVis,colorByCol,setColorByCol,colorByCandidates,colNames,categoryColors,setCategoryColors,colorByCategories,facetByCol,setFacetByCol:handleSetFacetByCol,subgroupByCol,setSubgroupByCol:handleSetSubgroupByCol,onDownloadSvg:handleDownloadSvg,onDownloadPng:handleDownloadPng}),React.createElement("div",{style:{flex:1,minWidth:0}},vis.yScale!=="linear"&&boxplotGroups.some(g=>g.allValues.some(v=>v<=0))&&React.createElement("div",{style:{background:"var(--warning-bg)",color:"var(--warning-text)",border:"1px solid var(--warning-border)",borderRadius:6,padding:"6px 10px",fontSize:12,marginBottom:8}},"Some values are \u2264 0 and cannot be shown on a"," ",vis.yScale==="log10"?"log\u2081\u2080":vis.yScale==="log2"?"log\u2082":"ln"," ","scale."),facetByCol<0?React.createElement(PlotArea,{colorByCol,colorByCategories,colNames,categoryColors,facetByCol,facetedData,chartRef,displayBoxplotGroups:subgroupByCol>=0&&subgroupedData?subgroupedData.flatGroups.map(g=>({...g,name:plotGroupRenames[g.name]??g.name,color:boxplotColors[g.name]??g.color})):displayBoxplotGroups,vis,yMinVal,yMaxVal,chartAnnotations,chartSummary,subgroups:subgroupByCol>=0&&subgroupedData?subgroupedData.subgroups:null,subgroupSummaries:subgroupByCol>=0&&subgroupedData?Object.fromEntries(subgroupedData.subgroups.map(sg=>[sg.name,statsUi.cellSummaries[cellKey("",sg.name)]||null])):null}):React.createElement(FacetPlotList,{facetedData,facetRefs,vis,yMinVal,yMaxVal,plotGroupRenames,boxplotColors,categoryColors,colorByCol,colorByCategories,colNames,facetStatsAnnotations:perFacetChartAnnotations,facetStatsSummary:perFacetSummaries,facetSubgroupSummaries:perFacetSubgroupSummaries}),statsPanelSets.length>0&&React.createElement("div",{style:{marginTop:16}},React.createElement(BoxplotStatsPanel,{key:`stats-panel-${statsPanelMode}`,sets:statsPanelSets,setLabel:statsPanelLabel,fileStem,singletonAutoExpand:statsPanelMode==="flat",onAnnotationForKey:setCellAnnotation,onSummaryForKey:setCellSummary,displayMode:statsUi.displayMode,onDisplayModeChange:handleStatsDisplayModeChange,showNs:statsUi.showNs,onShowNsChange:setStatsShowNs,showSummary:statsUi.showSummary,onShowSummaryChange:handleStatsShowSummaryChange,errorBarLabel:vis.plotStyle==="bar"?ERROR_BAR_LABELS[vis.errorType]:null})))),step==="plot"&&!canPlot&&React.createElement("div",{className:"dv-panel",style:{background:"var(--warning-bg)",borderColor:"var(--warning-border)"}},React.createElement("p",{style:{fontSize:12,color:"var(--warning-text)"}},"\u26A0 Assign ",React.createElement("strong",null,"group")," + ",React.createElement("strong",null,"value")," columns and ensure filters keep data."),React.createElement("button",{onClick:()=>setStep("configure"),className:"dv-btn dv-btn-secondary",style:{marginTop:8}},"\u2190 Configure")))}export{App}; -//# sourceMappingURL=app-BIQIRH5G.js.map +//# sourceMappingURL=app-3EKSFA3F.js.map diff --git a/tools/_app/chunks/app-BIQIRH5G.js.map b/tools/_app/chunks/app-3EKSFA3F.js.map similarity index 100% rename from tools/_app/chunks/app-BIQIRH5G.js.map rename to tools/_app/chunks/app-3EKSFA3F.js.map diff --git a/tools/_app/chunks/app-3ELPKOLT.js b/tools/_app/chunks/app-BLY3XSWB.js similarity index 99% rename from tools/_app/chunks/app-3ELPKOLT.js rename to tools/_app/chunks/app-BLY3XSWB.js index 9bc19f01..a3aca9f9 100644 --- a/tools/_app/chunks/app-3ELPKOLT.js +++ b/tools/_app/chunks/app-BLY3XSWB.js @@ -1,4 +1,4 @@ -import{ColorInput,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,SliderControl,UploadPanel,applyDiscretePalette,autoDetectSep,detectLongFormat,downloadCsv,fileBaseName,fixDecimalCommas,navigateToTool,parseLongFormatSets,parseRaw,parseSetData,svgSafeId,usePlotToolState}from"./chunk-5F6S3FYW.js";var VENN_CONFIG={MIN_RADIUS_FRAC:.5,DEFAULT_READABILITY_BLEND:.45,REFINEMENT_ITERATIONS:40,SUBSET_CLEARANCE:3,DISJOINT_CLEARANCE:2,OVERLAP_CLEARANCE:2},VIS_INIT_VENN={plotTitle:"",plotBg:"#ffffff",fontSize:14,fillOpacity:.25,readabilityBlend:VENN_CONFIG.DEFAULT_READABILITY_BLEND,showOutline:!0,setColors:{},discretePalette:"okabe-ito"};function computeIntersections(setNames,sets){let n=setNames.length,membershipMap=new Map;setNames.forEach((name,i)=>{let s=sets.get(name);if(s)for(let item of s){let prev=membershipMap.get(item)||0;membershipMap.set(item,prev|1<mask&1<b.size-a.size)}function regionLabel(_setNames,mask,allSetNames){let active=allSetNames.filter((_,i)=>mask&1<!(mask&1<=r1+r2)return 0;if(d<=Math.abs(r1-r2))return Math.PI*Math.min(r1,r2)**2;let a=(r1*r1-r2*r2+d*d)/(2*d),h=Math.sqrt(Math.max(0,r1*r1-a*a));return r1*r1*Math.acos(Math.max(-1,Math.min(1,a/r1)))+r2*r2*Math.acos(Math.max(-1,Math.min(1,(d-a)/r2)))-d*h}function solveDistance(r1,r2,targetArea){let maxArea=Math.PI*Math.min(r1,r2)**2;if(targetArea<=0)return r1+r2+1;if(targetArea>=maxArea)return Math.abs(r1-r2);let lo=Math.abs(r1-r2),hi=r1+r2;for(let i=0;i<60;i++){let mid=(lo+hi)/2;circleOverlapArea(r1,r2,mid)>targetArea?lo=mid:hi=mid}return(lo+hi)/2}function circleIntersectionPoints(c1,c2){let dx=c2.cx-c1.cx,dy=c2.cy-c1.cy,d=Math.sqrt(dx*dx+dy*dy);if(d>c1.r+c2.r+1e-9||dp.ci===i||p.cj===i).map(p=>({...p,angle:p.angles[i]})).sort((a,b)=>a.angle-b.angle);if(pts.length===0){let mask=(function(){let m=1<2*Math.PI;)span-=2*Math.PI;if(ba.reversed){let largeArc=2*Math.PI-span>Math.PI?1:0;pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 ${largeArc},0 ${endX.toFixed(2)},${endY.toFixed(2)}`)}else{let largeArc=span>Math.PI?1:0;pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 ${largeArc},1 ${endX.toFixed(2)},${endY.toFixed(2)}`)}};var emitArc=emitArc2;if(R<=0)continue;let fullCircleArcs=[],partialArcs=[];for(let arc of arcs){let outsideMask=arc.insideMask^1<4)break;chain.push(partialArcs[bestIdx]),used.add(bestIdx)}chains.push(chain)}let pathParts=[];for(let chain of chains){let first=chain[0],c0=circles[first.circleIdx],sx=c0.cx+c0.r*Math.cos(first.angleFrom),sy=c0.cy+c0.r*Math.sin(first.angleFrom);pathParts.push(`M${sx.toFixed(2)},${sy.toFixed(2)}`);for(let ba of chain)emitArc2(ba);pathParts.push("Z")}for(let fc of fullCircleArcs){let c=circles[fc.circleIdx],r=c.r,x1=c.cx-r,y1=c.cy,x2=c.cx+r,y2=c.cy,sw=fc.reversed?0:1;pathParts.push(`M${x1.toFixed(2)},${y1.toFixed(2)}`),pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 1,${sw} ${x2.toFixed(2)},${y2.toFixed(2)}`),pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 1,${sw} ${x1.toFixed(2)},${y1.toFixed(2)}`),pathParts.push("Z")}pathParts.length>0&&(regions[R]=pathParts.join(" "))}return regions}function triangleArea(a,b,c){return Math.abs((b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y))/2}function chordSegmentArea(r,chordLen){let ratio=Math.max(-1,Math.min(1,chordLen/(2*r))),theta=2*Math.asin(ratio);return r*r*(theta-Math.sin(theta))/2}function tripleIntersectionArea(circles){function innerVertex(i,j,k){let pts=circleIntersectionPoints(circles[i],circles[j]);if(!pts)return null;for(let p of pts)if(isInsideCircle(p.x,p.y,circles[k]))return p;return null}let v01=innerVertex(0,1,2),v02=innerVertex(0,2,1),v12=innerVertex(1,2,0);if(!v01||!v02||!v12)return 0;let tri=triangleArea(v01,v12,v02),s0=chordSegmentArea(circles[0].r,Math.hypot(v01.x-v02.x,v01.y-v02.y)),s1=chordSegmentArea(circles[1].r,Math.hypot(v01.x-v12.x,v01.y-v12.y)),s2=chordSegmentArea(circles[2].r,Math.hypot(v02.x-v12.x,v02.y-v12.y));return tri+s0+s1+s2}function computeAllRegionAreas(circles){let areas=new Map,n=circles.length;if(n===2){let[c0,c1]=circles,A0=Math.PI*c0.r*c0.r,A1=Math.PI*c1.r*c1.r,d=Math.hypot(c1.cx-c0.cx,c1.cy-c0.cy),P=circleOverlapArea(c0.r,c1.r,d);areas.set(1,Math.max(0,A0-P)),areas.set(2,Math.max(0,A1-P)),areas.set(3,P)}else if(n===3){let[c0,c1,c2]=circles,A0=Math.PI*c0.r*c0.r,A1=Math.PI*c1.r*c1.r,A2=Math.PI*c2.r*c2.r,d01=Math.hypot(c1.cx-c0.cx,c1.cy-c0.cy),d02=Math.hypot(c2.cx-c0.cx,c2.cy-c0.cy),d12=Math.hypot(c2.cx-c1.cx,c2.cy-c1.cy),P01=circleOverlapArea(c0.r,c1.r,d01),P02=circleOverlapArea(c0.r,c2.r,d02),P12=circleOverlapArea(c1.r,c2.r,d12),T=Math.max(0,Math.min(P01,P02,P12,tripleIntersectionArea(circles)));areas.set(1,Math.max(0,A0-P01-P02+T)),areas.set(2,Math.max(0,A1-P01-P12+T)),areas.set(4,Math.max(0,A2-P02-P12+T)),areas.set(3,Math.max(0,P01-T)),areas.set(5,Math.max(0,P02-T)),areas.set(6,Math.max(0,P12-T)),areas.set(7,T)}return areas}function computeLayoutError(circles,intersections,targetScale){if(circles.length<2||targetScale<=0)return{maxError:0,meanError:0};let areas=computeAllRegionAreas(circles),totalTarget=0;for(let g of intersections)totalTarget+=g.size*targetScale;if(totalTarget<1e-9)return{maxError:0,meanError:0};let maxErr=0,sumErr=0,count=0;for(let g of intersections){let target=g.size*targetScale,actual=areas.get(g.mask)||0,err=Math.abs(actual-target)/totalTarget;err>maxErr&&(maxErr=err),sumErr+=err,count++}return{maxError:maxErr,meanError:count>0?sumErr/count:0}}function validateAndFixLayout(circles,setNames,sets,subsets,disjoint){let warnings=[],fixed=circles.map(c=>({...c}));function enforceSubsets(){for(let{sub,sup}of subsets){let dx=fixed[sub].cx-fixed[sup].cx,dy=fixed[sub].cy-fixed[sup].cy,dist=Math.sqrt(dx*dx+dy*dy),maxDist=fixed[sup].r-fixed[sub].r-VENN_CONFIG.SUBSET_CLEARANCE;if(!(maxDist<=0)&&dist>maxDist){let subSize=sets.get(setNames[sub]).size,supSize=sets.get(setNames[sup]).size,ratio=supSize>0?(supSize-subSize)/supSize:.5,proportion=Math.min(.9,Math.max(.2,ratio)),scale=maxDist*proportion/Math.max(dist,1e-9);return fixed[sub].cx=fixed[sup].cx+dx*scale,fixed[sub].cy=fixed[sup].cy+dy*scale,!0}}return!1}enforceSubsets()&&warnings.push(...subsets.map(({sub,sup})=>`"${setNames[sub]}" is a subset of "${setNames[sup]}" \u2014 positions adjusted for containment`));for(let[i,j]of disjoint){let dx=fixed[j].cx-fixed[i].cx,dy=fixed[j].cy-fixed[i].cy,dist=Math.sqrt(dx*dx+dy*dy),minDist=fixed[i].r+fixed[j].r+VENN_CONFIG.DISJOINT_CLEARANCE;if(distd[0]===i&&d[1]===j||d[0]===j&&d[1]===i)||subsets.some(s=>s.sub===i&&s.sup===j||s.sub===j&&s.sup===i))continue;let si=sets.get(setNames[i]),sj=sets.get(setNames[j]);if(!si||!sj)continue;let hasOverlap=!1;for(let item of si)if(sj.has(item)){hasOverlap=!0;break}if(!hasOverlap)continue;let dx=fixed[j].cx-fixed[i].cx,dy=fixed[j].cy-fixed[i].cy,dist=Math.sqrt(dx*dx+dy*dy),maxDist=fixed[i].r+fixed[j].r-VENN_CONFIG.OVERLAP_CLEARANCE;if(dist>maxDist){let pull=(dist-maxDist)/2+1,ux=dx/Math.max(dist,1e-9),uy=dy/Math.max(dist,1e-9);fixed[i].cx+=ux*pull,fixed[i].cy+=uy*pull,fixed[j].cx-=ux*pull,fixed[j].cy-=uy*pull,warnings.push(`"${setNames[i]}" and "${setNames[j]}" share items \u2014 ensured overlap`)}}for(let iter=0;iter<5&&enforceSubsets();iter++);return{circles:fixed,warnings}}function fitCirclesToViewport(circles,viewW,viewH,margin=15){let minX=Math.min(...circles.map(c=>c.cx-c.r)),maxX=Math.max(...circles.map(c=>c.cx+c.r)),minY=Math.min(...circles.map(c=>c.cy-c.r)),maxY=Math.max(...circles.map(c=>c.cy+c.r)),bw=maxX-minX,bh=maxY-minY,marginTop=Math.max(margin,40),marginBottom=Math.max(margin,circles.length*22+20),availH=viewH-marginTop-marginBottom,s=Math.min((viewW-2*margin)/bw,availH/bh,1),bcx=(minX+maxX)/2,bcy=(minY+maxY)/2,centerY=marginTop+availH/2;return circles.map(c=>({cx:viewW/2+(c.cx-bcx)*s,cy:centerY+(c.cy-bcy)*s,r:c.r*s}))}function clampRadii(radii){let maxR=Math.max(...radii),minR=Math.min(...radii),actualRatio=maxR>0?minR/maxR:1,minAllowed=maxR*VENN_CONFIG.MIN_RADIUS_FRAC,adjusted=!1;return{radii:radii.map(r=>r({...c})),targets=new Map;for(let g of intersections)targets.set(g.mask,g.size*targetScale);function cost(cs){let areas=computeAllRegionAreas(cs),s=0;for(let[mask,target]of targets){let diff=(areas.get(mask)||0)-target;s+=diff*diff}return s}let best=cost(current),maxR=Math.max(...current.map(c=>c.r)),step=maxR*.2,minStep=maxR*.005,moves=[[1,0],[-1,0],[0,1],[0,-1]],iters=0,maxIters=VENN_CONFIG.REFINEMENT_ITERATIONS*6;for(;step>minStep&&itersk===i?{...c2,cx:c2.cx+dx,cy:c2.cy+dy}:{...c2}),c=cost(trial);cg.mask===3),interSize=inter?inter.size:0,scale=Math.min(viewW,viewH)*.304/Math.sqrt(Math.max(s0,s1)),radii=[scale*Math.sqrt(s0),scale*Math.sqrt(s1)],warnings=[],rc=clampRadii(radii);rc.adjusted&&warnings.push(`Set sizes differ drastically (ratio ${rc.actualRatio.toFixed(2)}) \u2014 smaller set enlarged for visibility`),radii=rc.radii;let[r0,r1]=radii,targetScale=Math.PI*scale*scale,targetOA=targetScale*interSize,d=solveDistance(r0,r1,targetOA),cx=viewW/2,cy=viewH/2,propCircles=[{cx:cx-d/2,cy,r:r0},{cx:cx+d/2,cy,r:r1}],working=propCircles;if(blend>0){let classicR=Math.min(viewW,viewH)*.272,classicD=classicR,classicCircles=[{cx:cx-classicD/2,cy,r:classicR},{cx:cx+classicD/2,cy,r:classicR}];working=propCircles.map((pc,i)=>({cx:pc.cx*(1-blend)+classicCircles[i].cx*blend,cy:pc.cy*(1-blend)+classicCircles[i].cy*blend,r:pc.r*(1-blend)+classicCircles[i].r*blend}))}let subsets=detectSubsets(setNames,sets),disjoint=detectDisjoint(setNames,sets),{circles:fixed,warnings:fixWarnings}=validateAndFixLayout(working,setNames,sets,subsets,disjoint);warnings.push(...fixWarnings);let errors=computeLayoutError(fixed,intersections,targetScale);return{circles:fitCirclesToViewport(fixed,viewW,viewH),warnings,proportional:warnings.length===0,maxError:errors.maxError,meanError:errors.meanError}}function buildVenn3Layout(setNames,sets,intersections,viewW,viewH,blend){let sizes=setNames.map(n=>sets.get(n).size),scale=Math.min(viewW,viewH)*.256/Math.sqrt(Math.max(...sizes)),radii=sizes.map(s=>scale*Math.sqrt(s)),warnings=[],rc=clampRadii(radii);rc.adjusted&&warnings.push(`Set sizes differ drastically (ratio ${rc.actualRatio.toFixed(2)}) \u2014 smaller sets enlarged for visibility`),radii=rc.radii;let targetScale=Math.PI*scale*scale,pairMasks=[[0,1],[0,2],[1,2]],pairDists=[];for(let[i,j]of pairMasks){let totalPairwise=0;for(let g of intersections)g.mask&1<0,cx=viewW/2,cy=viewH/2,d01=pairDists[0],d02=pairDists[1],d12=pairDists[2];d01<=d02+d12+1e-6&&d02<=d01+d12+1e-6&&d12<=d01+d02+1e-6||warnings.push("Pairwise overlaps are geometrically inconsistent (triangle inequality violated) \u2014 layout approximated");let ax=0,ay=0,bx=d01,by=0,tcx=d01>1e-9?(d02*d02-d12*d12+d01*d01)/(2*d01):0,tcySq=d02*d02-tcx*tcx,tcy=tcySq>0?-Math.sqrt(tcySq):0,triPts=[{x:ax,y:ay},{x:bx,y:by},{x:tcx,y:tcy}],triCentX=(ax+bx+tcx)/3,triCentY=(ay+by+tcy)/3,triCentered=triPts.map(p=>({x:p.x-triCentX,y:p.y-triCentY})),pts;if(hasSubsets)pts=triCentered;else{let avgDist=(d01+d02+d12)/3,eqPts=[-Math.PI/6,-5*Math.PI/6,Math.PI/2].map(a=>({x:avgDist*.6*Math.cos(a),y:avgDist*.6*Math.sin(a)})),mix=Math.abs(tcy)({x:p.x*mix+eqPts[i].x*(1-mix),y:p.y*mix+eqPts[i].y*(1-mix)}))}let centX=pts.reduce((s,p)=>s+p.x,0)/3,centY=pts.reduce((s,p)=>s+p.y,0)/3,propCircles=pts.map((p,i)=>({cx:cx+(p.x-centX),cy:cy+(p.y-centY),r:radii[i]}));hasSubsets||(propCircles=refine3SetLayout(propCircles,intersections,targetScale));let working=propCircles;if(blend>0){let classicR=Math.min(viewW,viewH)*.3,classicD=classicR*.65,classicCircles=[-Math.PI/2,Math.PI/6,5*Math.PI/6].map(a=>({cx:cx+classicD*Math.cos(a),cy:cy+classicD*Math.sin(a),r:classicR}));working=propCircles.map((pc,i)=>({cx:pc.cx*(1-blend)+classicCircles[i].cx*blend,cy:pc.cy*(1-blend)+classicCircles[i].cy*blend,r:pc.r*(1-blend)+classicCircles[i].r*blend}))}let{circles:fixed,warnings:fixWarnings}=validateAndFixLayout(working,setNames,sets,subsets,disjoint);warnings.push(...fixWarnings);let errors=computeLayoutError(fixed,intersections,targetScale);return{circles:fitCirclesToViewport(fixed,viewW,viewH),warnings,proportional:warnings.length===0,maxError:errors.maxError,meanError:errors.meanError}}function buildVenn2LayoutClassic(_setNames,_sets,_intersections,viewW,viewH){let R=Math.min(viewW,viewH)*.272,cx=viewW/2,cy=viewH/2,d=R,circles=[{cx:cx-d/2,cy,r:R},{cx:cx+d/2,cy,r:R}];return{circles:fitCirclesToViewport(circles,viewW,viewH),warnings:[],proportional:!1,maxError:0,meanError:0}}function buildVenn3LayoutClassic(_setNames,_sets,_intersections,viewW,viewH){let R=Math.min(viewW,viewH)*.24,cx=viewW/2,cy=viewH/2,d=R*.65,circles=[-Math.PI/2,Math.PI/6,5*Math.PI/6].map(a=>({cx:cx+d*Math.cos(a),cy:cy+d*Math.sin(a),r:R}));return{circles:fitCirclesToViewport(circles,viewW,viewH),warnings:[],proportional:!1,maxError:0,meanError:0}}function computeRegionCentroids(circles,_regionPaths,intersections){let centroids={},bbox={x1:Math.min(...circles.map(c=>c.cx-c.r))-5,y1:Math.min(...circles.map(c=>c.cy-c.r))-5,x2:Math.max(...circles.map(c=>c.cx+c.r))+5,y2:Math.max(...circles.map(c=>c.cy+c.r))+5},n=circles.length,step=Math.max(bbox.x2-bbox.x1,bbox.y2-bbox.y1)/150;for(let inter of intersections){let mask=inter.mask,bestX=0,bestY=0,bestDist=-1;for(let x=bbox.x1;x<=bbox.x2;x+=step)for(let y=bbox.y1;y<=bbox.y2;y+=step){let m=0;for(let i=0;ibestDist&&(bestDist=minEdgeDist,bestX=x,bestY=y)}bestDist>=0&&(centroids[mask]={x:bestX,y:bestY})}return centroids}var VENN_HOWTO={toolName:"venn",title:"Venn Diagram \u2014 How to use",subtitle:"Item-overlap between 2\u20133 sets, area-proportional or classic",purpose:React.createElement(React.Fragment,null,"Show which items are shared between 2 or 3 sets (genes upregulated in two conditions; taxa common to multiple samples). For \u2265 4 sets, use the UpSet tool instead."),dataLayout:React.createElement(React.Fragment,null,React.createElement("strong",null,"Wide")," \u2014 one column per set, items stacked in each column."," ",React.createElement("strong",null,"Long")," \u2014 two columns: ",React.createElement("em",null,"item")," and ",React.createElement("em",null,"set"),". Both formats auto-detect."),display:React.createElement(React.Fragment,null,"Toggle between ",React.createElement("strong",null,"area-proportional")," (circle sizes scale with set size; subset relationships are exact) and ",React.createElement("strong",null,"classic")," (uniform-radius Euler-style). Click any region to drill down into its item list. CSV export per region.")};function UploadStep({sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:setSepOverride,onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Arabidopsis stress-response DEGs",subtitle:"3 sets \u2014 Drought \xB7 Heat \xB7 Salt",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 wide (one column per set, 2\u20133) or long (item, set) \xB7 2 MB max"}),React.createElement(HowTo,{...VENN_HOWTO}))}function ConfigureStep({fileName,detectedSep,parsedHeaders,parsedRows,allColumnNames,allColumnSets,pendingSelection,setPendingSelection,isLongFormat}){let needsPicker=allColumnNames.length>3,selectedCount=pendingSelection.length,openUpset=e=>{e.preventDefault();let escape=c=>String(c??"").replace(/[\t\n\r]/g," "),payload={type:"dataviz-handoff",text:[parsedHeaders.map(escape).join(" "),...parsedRows.map(r=>r.map(escape).join(" "))].join(` +import{ColorInput,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,SliderControl,UploadPanel,applyDiscretePalette,autoDetectSep,detectLongFormat,downloadCsv,fileBaseName,fixDecimalCommas,navigateToTool,parseLongFormatSets,parseRaw,parseSetData,svgSafeId,usePlotToolState}from"./chunk-HFTDMS7M.js";var VENN_CONFIG={MIN_RADIUS_FRAC:.5,DEFAULT_READABILITY_BLEND:.45,REFINEMENT_ITERATIONS:40,SUBSET_CLEARANCE:3,DISJOINT_CLEARANCE:2,OVERLAP_CLEARANCE:2},VIS_INIT_VENN={plotTitle:"",plotBg:"#ffffff",fontSize:14,fillOpacity:.25,readabilityBlend:VENN_CONFIG.DEFAULT_READABILITY_BLEND,showOutline:!0,setColors:{},discretePalette:"okabe-ito"};function computeIntersections(setNames,sets){let n=setNames.length,membershipMap=new Map;setNames.forEach((name,i)=>{let s=sets.get(name);if(s)for(let item of s){let prev=membershipMap.get(item)||0;membershipMap.set(item,prev|1<mask&1<b.size-a.size)}function regionLabel(_setNames,mask,allSetNames){let active=allSetNames.filter((_,i)=>mask&1<!(mask&1<=r1+r2)return 0;if(d<=Math.abs(r1-r2))return Math.PI*Math.min(r1,r2)**2;let a=(r1*r1-r2*r2+d*d)/(2*d),h=Math.sqrt(Math.max(0,r1*r1-a*a));return r1*r1*Math.acos(Math.max(-1,Math.min(1,a/r1)))+r2*r2*Math.acos(Math.max(-1,Math.min(1,(d-a)/r2)))-d*h}function solveDistance(r1,r2,targetArea){let maxArea=Math.PI*Math.min(r1,r2)**2;if(targetArea<=0)return r1+r2+1;if(targetArea>=maxArea)return Math.abs(r1-r2);let lo=Math.abs(r1-r2),hi=r1+r2;for(let i=0;i<60;i++){let mid=(lo+hi)/2;circleOverlapArea(r1,r2,mid)>targetArea?lo=mid:hi=mid}return(lo+hi)/2}function circleIntersectionPoints(c1,c2){let dx=c2.cx-c1.cx,dy=c2.cy-c1.cy,d=Math.sqrt(dx*dx+dy*dy);if(d>c1.r+c2.r+1e-9||dp.ci===i||p.cj===i).map(p=>({...p,angle:p.angles[i]})).sort((a,b)=>a.angle-b.angle);if(pts.length===0){let mask=(function(){let m=1<2*Math.PI;)span-=2*Math.PI;if(ba.reversed){let largeArc=2*Math.PI-span>Math.PI?1:0;pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 ${largeArc},0 ${endX.toFixed(2)},${endY.toFixed(2)}`)}else{let largeArc=span>Math.PI?1:0;pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 ${largeArc},1 ${endX.toFixed(2)},${endY.toFixed(2)}`)}};var emitArc=emitArc2;if(R<=0)continue;let fullCircleArcs=[],partialArcs=[];for(let arc of arcs){let outsideMask=arc.insideMask^1<4)break;chain.push(partialArcs[bestIdx]),used.add(bestIdx)}chains.push(chain)}let pathParts=[];for(let chain of chains){let first=chain[0],c0=circles[first.circleIdx],sx=c0.cx+c0.r*Math.cos(first.angleFrom),sy=c0.cy+c0.r*Math.sin(first.angleFrom);pathParts.push(`M${sx.toFixed(2)},${sy.toFixed(2)}`);for(let ba of chain)emitArc2(ba);pathParts.push("Z")}for(let fc of fullCircleArcs){let c=circles[fc.circleIdx],r=c.r,x1=c.cx-r,y1=c.cy,x2=c.cx+r,y2=c.cy,sw=fc.reversed?0:1;pathParts.push(`M${x1.toFixed(2)},${y1.toFixed(2)}`),pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 1,${sw} ${x2.toFixed(2)},${y2.toFixed(2)}`),pathParts.push(`A${r.toFixed(2)},${r.toFixed(2)} 0 1,${sw} ${x1.toFixed(2)},${y1.toFixed(2)}`),pathParts.push("Z")}pathParts.length>0&&(regions[R]=pathParts.join(" "))}return regions}function triangleArea(a,b,c){return Math.abs((b.x-a.x)*(c.y-a.y)-(c.x-a.x)*(b.y-a.y))/2}function chordSegmentArea(r,chordLen){let ratio=Math.max(-1,Math.min(1,chordLen/(2*r))),theta=2*Math.asin(ratio);return r*r*(theta-Math.sin(theta))/2}function tripleIntersectionArea(circles){function innerVertex(i,j,k){let pts=circleIntersectionPoints(circles[i],circles[j]);if(!pts)return null;for(let p of pts)if(isInsideCircle(p.x,p.y,circles[k]))return p;return null}let v01=innerVertex(0,1,2),v02=innerVertex(0,2,1),v12=innerVertex(1,2,0);if(!v01||!v02||!v12)return 0;let tri=triangleArea(v01,v12,v02),s0=chordSegmentArea(circles[0].r,Math.hypot(v01.x-v02.x,v01.y-v02.y)),s1=chordSegmentArea(circles[1].r,Math.hypot(v01.x-v12.x,v01.y-v12.y)),s2=chordSegmentArea(circles[2].r,Math.hypot(v02.x-v12.x,v02.y-v12.y));return tri+s0+s1+s2}function computeAllRegionAreas(circles){let areas=new Map,n=circles.length;if(n===2){let[c0,c1]=circles,A0=Math.PI*c0.r*c0.r,A1=Math.PI*c1.r*c1.r,d=Math.hypot(c1.cx-c0.cx,c1.cy-c0.cy),P=circleOverlapArea(c0.r,c1.r,d);areas.set(1,Math.max(0,A0-P)),areas.set(2,Math.max(0,A1-P)),areas.set(3,P)}else if(n===3){let[c0,c1,c2]=circles,A0=Math.PI*c0.r*c0.r,A1=Math.PI*c1.r*c1.r,A2=Math.PI*c2.r*c2.r,d01=Math.hypot(c1.cx-c0.cx,c1.cy-c0.cy),d02=Math.hypot(c2.cx-c0.cx,c2.cy-c0.cy),d12=Math.hypot(c2.cx-c1.cx,c2.cy-c1.cy),P01=circleOverlapArea(c0.r,c1.r,d01),P02=circleOverlapArea(c0.r,c2.r,d02),P12=circleOverlapArea(c1.r,c2.r,d12),T=Math.max(0,Math.min(P01,P02,P12,tripleIntersectionArea(circles)));areas.set(1,Math.max(0,A0-P01-P02+T)),areas.set(2,Math.max(0,A1-P01-P12+T)),areas.set(4,Math.max(0,A2-P02-P12+T)),areas.set(3,Math.max(0,P01-T)),areas.set(5,Math.max(0,P02-T)),areas.set(6,Math.max(0,P12-T)),areas.set(7,T)}return areas}function computeLayoutError(circles,intersections,targetScale){if(circles.length<2||targetScale<=0)return{maxError:0,meanError:0};let areas=computeAllRegionAreas(circles),totalTarget=0;for(let g of intersections)totalTarget+=g.size*targetScale;if(totalTarget<1e-9)return{maxError:0,meanError:0};let maxErr=0,sumErr=0,count=0;for(let g of intersections){let target=g.size*targetScale,actual=areas.get(g.mask)||0,err=Math.abs(actual-target)/totalTarget;err>maxErr&&(maxErr=err),sumErr+=err,count++}return{maxError:maxErr,meanError:count>0?sumErr/count:0}}function validateAndFixLayout(circles,setNames,sets,subsets,disjoint){let warnings=[],fixed=circles.map(c=>({...c}));function enforceSubsets(){for(let{sub,sup}of subsets){let dx=fixed[sub].cx-fixed[sup].cx,dy=fixed[sub].cy-fixed[sup].cy,dist=Math.sqrt(dx*dx+dy*dy),maxDist=fixed[sup].r-fixed[sub].r-VENN_CONFIG.SUBSET_CLEARANCE;if(!(maxDist<=0)&&dist>maxDist){let subSize=sets.get(setNames[sub]).size,supSize=sets.get(setNames[sup]).size,ratio=supSize>0?(supSize-subSize)/supSize:.5,proportion=Math.min(.9,Math.max(.2,ratio)),scale=maxDist*proportion/Math.max(dist,1e-9);return fixed[sub].cx=fixed[sup].cx+dx*scale,fixed[sub].cy=fixed[sup].cy+dy*scale,!0}}return!1}enforceSubsets()&&warnings.push(...subsets.map(({sub,sup})=>`"${setNames[sub]}" is a subset of "${setNames[sup]}" \u2014 positions adjusted for containment`));for(let[i,j]of disjoint){let dx=fixed[j].cx-fixed[i].cx,dy=fixed[j].cy-fixed[i].cy,dist=Math.sqrt(dx*dx+dy*dy),minDist=fixed[i].r+fixed[j].r+VENN_CONFIG.DISJOINT_CLEARANCE;if(distd[0]===i&&d[1]===j||d[0]===j&&d[1]===i)||subsets.some(s=>s.sub===i&&s.sup===j||s.sub===j&&s.sup===i))continue;let si=sets.get(setNames[i]),sj=sets.get(setNames[j]);if(!si||!sj)continue;let hasOverlap=!1;for(let item of si)if(sj.has(item)){hasOverlap=!0;break}if(!hasOverlap)continue;let dx=fixed[j].cx-fixed[i].cx,dy=fixed[j].cy-fixed[i].cy,dist=Math.sqrt(dx*dx+dy*dy),maxDist=fixed[i].r+fixed[j].r-VENN_CONFIG.OVERLAP_CLEARANCE;if(dist>maxDist){let pull=(dist-maxDist)/2+1,ux=dx/Math.max(dist,1e-9),uy=dy/Math.max(dist,1e-9);fixed[i].cx+=ux*pull,fixed[i].cy+=uy*pull,fixed[j].cx-=ux*pull,fixed[j].cy-=uy*pull,warnings.push(`"${setNames[i]}" and "${setNames[j]}" share items \u2014 ensured overlap`)}}for(let iter=0;iter<5&&enforceSubsets();iter++);return{circles:fixed,warnings}}function fitCirclesToViewport(circles,viewW,viewH,margin=15){let minX=Math.min(...circles.map(c=>c.cx-c.r)),maxX=Math.max(...circles.map(c=>c.cx+c.r)),minY=Math.min(...circles.map(c=>c.cy-c.r)),maxY=Math.max(...circles.map(c=>c.cy+c.r)),bw=maxX-minX,bh=maxY-minY,marginTop=Math.max(margin,40),marginBottom=Math.max(margin,circles.length*22+20),availH=viewH-marginTop-marginBottom,s=Math.min((viewW-2*margin)/bw,availH/bh,1),bcx=(minX+maxX)/2,bcy=(minY+maxY)/2,centerY=marginTop+availH/2;return circles.map(c=>({cx:viewW/2+(c.cx-bcx)*s,cy:centerY+(c.cy-bcy)*s,r:c.r*s}))}function clampRadii(radii){let maxR=Math.max(...radii),minR=Math.min(...radii),actualRatio=maxR>0?minR/maxR:1,minAllowed=maxR*VENN_CONFIG.MIN_RADIUS_FRAC,adjusted=!1;return{radii:radii.map(r=>r({...c})),targets=new Map;for(let g of intersections)targets.set(g.mask,g.size*targetScale);function cost(cs){let areas=computeAllRegionAreas(cs),s=0;for(let[mask,target]of targets){let diff=(areas.get(mask)||0)-target;s+=diff*diff}return s}let best=cost(current),maxR=Math.max(...current.map(c=>c.r)),step=maxR*.2,minStep=maxR*.005,moves=[[1,0],[-1,0],[0,1],[0,-1]],iters=0,maxIters=VENN_CONFIG.REFINEMENT_ITERATIONS*6;for(;step>minStep&&itersk===i?{...c2,cx:c2.cx+dx,cy:c2.cy+dy}:{...c2}),c=cost(trial);cg.mask===3),interSize=inter?inter.size:0,scale=Math.min(viewW,viewH)*.304/Math.sqrt(Math.max(s0,s1)),radii=[scale*Math.sqrt(s0),scale*Math.sqrt(s1)],warnings=[],rc=clampRadii(radii);rc.adjusted&&warnings.push(`Set sizes differ drastically (ratio ${rc.actualRatio.toFixed(2)}) \u2014 smaller set enlarged for visibility`),radii=rc.radii;let[r0,r1]=radii,targetScale=Math.PI*scale*scale,targetOA=targetScale*interSize,d=solveDistance(r0,r1,targetOA),cx=viewW/2,cy=viewH/2,propCircles=[{cx:cx-d/2,cy,r:r0},{cx:cx+d/2,cy,r:r1}],working=propCircles;if(blend>0){let classicR=Math.min(viewW,viewH)*.272,classicD=classicR,classicCircles=[{cx:cx-classicD/2,cy,r:classicR},{cx:cx+classicD/2,cy,r:classicR}];working=propCircles.map((pc,i)=>({cx:pc.cx*(1-blend)+classicCircles[i].cx*blend,cy:pc.cy*(1-blend)+classicCircles[i].cy*blend,r:pc.r*(1-blend)+classicCircles[i].r*blend}))}let subsets=detectSubsets(setNames,sets),disjoint=detectDisjoint(setNames,sets),{circles:fixed,warnings:fixWarnings}=validateAndFixLayout(working,setNames,sets,subsets,disjoint);warnings.push(...fixWarnings);let errors=computeLayoutError(fixed,intersections,targetScale);return{circles:fitCirclesToViewport(fixed,viewW,viewH),warnings,proportional:warnings.length===0,maxError:errors.maxError,meanError:errors.meanError}}function buildVenn3Layout(setNames,sets,intersections,viewW,viewH,blend){let sizes=setNames.map(n=>sets.get(n).size),scale=Math.min(viewW,viewH)*.256/Math.sqrt(Math.max(...sizes)),radii=sizes.map(s=>scale*Math.sqrt(s)),warnings=[],rc=clampRadii(radii);rc.adjusted&&warnings.push(`Set sizes differ drastically (ratio ${rc.actualRatio.toFixed(2)}) \u2014 smaller sets enlarged for visibility`),radii=rc.radii;let targetScale=Math.PI*scale*scale,pairMasks=[[0,1],[0,2],[1,2]],pairDists=[];for(let[i,j]of pairMasks){let totalPairwise=0;for(let g of intersections)g.mask&1<0,cx=viewW/2,cy=viewH/2,d01=pairDists[0],d02=pairDists[1],d12=pairDists[2];d01<=d02+d12+1e-6&&d02<=d01+d12+1e-6&&d12<=d01+d02+1e-6||warnings.push("Pairwise overlaps are geometrically inconsistent (triangle inequality violated) \u2014 layout approximated");let ax=0,ay=0,bx=d01,by=0,tcx=d01>1e-9?(d02*d02-d12*d12+d01*d01)/(2*d01):0,tcySq=d02*d02-tcx*tcx,tcy=tcySq>0?-Math.sqrt(tcySq):0,triPts=[{x:ax,y:ay},{x:bx,y:by},{x:tcx,y:tcy}],triCentX=(ax+bx+tcx)/3,triCentY=(ay+by+tcy)/3,triCentered=triPts.map(p=>({x:p.x-triCentX,y:p.y-triCentY})),pts;if(hasSubsets)pts=triCentered;else{let avgDist=(d01+d02+d12)/3,eqPts=[-Math.PI/6,-5*Math.PI/6,Math.PI/2].map(a=>({x:avgDist*.6*Math.cos(a),y:avgDist*.6*Math.sin(a)})),mix=Math.abs(tcy)({x:p.x*mix+eqPts[i].x*(1-mix),y:p.y*mix+eqPts[i].y*(1-mix)}))}let centX=pts.reduce((s,p)=>s+p.x,0)/3,centY=pts.reduce((s,p)=>s+p.y,0)/3,propCircles=pts.map((p,i)=>({cx:cx+(p.x-centX),cy:cy+(p.y-centY),r:radii[i]}));hasSubsets||(propCircles=refine3SetLayout(propCircles,intersections,targetScale));let working=propCircles;if(blend>0){let classicR=Math.min(viewW,viewH)*.3,classicD=classicR*.65,classicCircles=[-Math.PI/2,Math.PI/6,5*Math.PI/6].map(a=>({cx:cx+classicD*Math.cos(a),cy:cy+classicD*Math.sin(a),r:classicR}));working=propCircles.map((pc,i)=>({cx:pc.cx*(1-blend)+classicCircles[i].cx*blend,cy:pc.cy*(1-blend)+classicCircles[i].cy*blend,r:pc.r*(1-blend)+classicCircles[i].r*blend}))}let{circles:fixed,warnings:fixWarnings}=validateAndFixLayout(working,setNames,sets,subsets,disjoint);warnings.push(...fixWarnings);let errors=computeLayoutError(fixed,intersections,targetScale);return{circles:fitCirclesToViewport(fixed,viewW,viewH),warnings,proportional:warnings.length===0,maxError:errors.maxError,meanError:errors.meanError}}function buildVenn2LayoutClassic(_setNames,_sets,_intersections,viewW,viewH){let R=Math.min(viewW,viewH)*.272,cx=viewW/2,cy=viewH/2,d=R,circles=[{cx:cx-d/2,cy,r:R},{cx:cx+d/2,cy,r:R}];return{circles:fitCirclesToViewport(circles,viewW,viewH),warnings:[],proportional:!1,maxError:0,meanError:0}}function buildVenn3LayoutClassic(_setNames,_sets,_intersections,viewW,viewH){let R=Math.min(viewW,viewH)*.24,cx=viewW/2,cy=viewH/2,d=R*.65,circles=[-Math.PI/2,Math.PI/6,5*Math.PI/6].map(a=>({cx:cx+d*Math.cos(a),cy:cy+d*Math.sin(a),r:R}));return{circles:fitCirclesToViewport(circles,viewW,viewH),warnings:[],proportional:!1,maxError:0,meanError:0}}function computeRegionCentroids(circles,_regionPaths,intersections){let centroids={},bbox={x1:Math.min(...circles.map(c=>c.cx-c.r))-5,y1:Math.min(...circles.map(c=>c.cy-c.r))-5,x2:Math.max(...circles.map(c=>c.cx+c.r))+5,y2:Math.max(...circles.map(c=>c.cy+c.r))+5},n=circles.length,step=Math.max(bbox.x2-bbox.x1,bbox.y2-bbox.y1)/150;for(let inter of intersections){let mask=inter.mask,bestX=0,bestY=0,bestDist=-1;for(let x=bbox.x1;x<=bbox.x2;x+=step)for(let y=bbox.y1;y<=bbox.y2;y+=step){let m=0;for(let i=0;ibestDist&&(bestDist=minEdgeDist,bestX=x,bestY=y)}bestDist>=0&&(centroids[mask]={x:bestX,y:bestY})}return centroids}var VENN_HOWTO={toolName:"venn",title:"Venn Diagram \u2014 How to use",subtitle:"Item-overlap between 2\u20133 sets, area-proportional or classic",purpose:React.createElement(React.Fragment,null,"Show which items are shared between 2 or 3 sets (genes upregulated in two conditions; taxa common to multiple samples). For \u2265 4 sets, use the UpSet tool instead."),dataLayout:React.createElement(React.Fragment,null,React.createElement("strong",null,"Wide")," \u2014 one column per set, items stacked in each column."," ",React.createElement("strong",null,"Long")," \u2014 two columns: ",React.createElement("em",null,"item")," and ",React.createElement("em",null,"set"),". Both formats auto-detect."),display:React.createElement(React.Fragment,null,"Toggle between ",React.createElement("strong",null,"area-proportional")," (circle sizes scale with set size; subset relationships are exact) and ",React.createElement("strong",null,"classic")," (uniform-radius Euler-style). Click any region to drill down into its item list. CSV export per region.")};function UploadStep({sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:setSepOverride,onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Arabidopsis stress-response DEGs",subtitle:"3 sets \u2014 Drought \xB7 Heat \xB7 Salt",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 wide (one column per set, 2\u20133) or long (item, set) \xB7 2 MB max"}),React.createElement(HowTo,{...VENN_HOWTO}))}function ConfigureStep({fileName,detectedSep,parsedHeaders,parsedRows,allColumnNames,allColumnSets,pendingSelection,setPendingSelection,isLongFormat}){let needsPicker=allColumnNames.length>3,selectedCount=pendingSelection.length,openUpset=e=>{e.preventDefault();let escape=c=>String(c??"").replace(/[\t\n\r]/g," "),payload={type:"dataviz-handoff",text:[parsedHeaders.map(escape).join(" "),...parsedRows.map(r=>r.map(escape).join(" "))].join(` `),fileName:fileName||"",sep:" ",format:isLongFormat?"long":"wide"};try{sessionStorage.setItem("dataviz-upset-handoff",JSON.stringify(payload))}catch{}try{window.dispatchEvent(new CustomEvent("plottr-handoff",{detail:{key:"dataviz-upset-handoff"}}))}catch{}navigateToTool("upset")},showNudge=allColumnNames.length>=4,toggle=name=>{setPendingSelection(prev=>prev.includes(name)?prev.filter(n=>n!==name):prev.length>=3?prev:[...prev,name])},pickerStatusText="Pick 2 or 3 sets to overlap.",pickerStatusColor="var(--text-muted)";return selectedCount===1?(pickerStatusText="1 selected \u2014 pick at least one more.",pickerStatusColor="var(--warning-text)"):(selectedCount===2||selectedCount===3)&&(pickerStatusText=`${selectedCount} selected \u2014 ready to plot.`,pickerStatusColor="var(--success-text)"),React.createElement("div",null,showNudge&&React.createElement("div",{style:{marginBottom:12,padding:"10px 14px",borderRadius:8,background:"var(--info-bg)",border:"1px solid var(--info-border)",color:"var(--info-text)",display:"flex",alignItems:"center",gap:12,fontSize:12}},React.createElement("span",{style:{fontSize:16}},"\u{1F4A1}"),React.createElement("span",{style:{flex:1}},React.createElement("strong",null,allColumnNames.length," sets detected")," \u2014 Venn diagrams only render 2 or 3 sets. For 4+ sets, use the UpSet tool."),React.createElement("a",{href:"#/upset",onClick:openUpset,style:{display:"inline-flex",alignItems:"center",gap:6,padding:"5px 12px",borderRadius:999,background:"var(--cta-primary-bg)",color:"var(--on-accent)",fontWeight:700,fontSize:12,textDecoration:"none",whiteSpace:"nowrap",boxShadow:"var(--cta-primary-shadow)"}},"Open in UpSet \u2192")),needsPicker&&React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,fontWeight:600,color:"var(--text)"}},"Choose sets to overlap"),React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:pickerStatusColor}},pickerStatusText),React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:6}},allColumnNames.map(name=>{let checked=pendingSelection.includes(name),atCap=!checked&&pendingSelection.length>=3,size=allColumnSets.get(name)?.size??0;return React.createElement("label",{key:name,style:{display:"flex",alignItems:"center",gap:8,padding:"6px 10px",borderRadius:6,border:`1px solid ${checked?"var(--accent-primary)":"var(--border)"}`,background:checked?"var(--info-bg)":"var(--surface-subtle)",cursor:atCap?"not-allowed":"pointer",opacity:atCap?.5:1,fontSize:12,color:"var(--text)"}},React.createElement("input",{type:"checkbox",checked,disabled:atCap,onChange:()=>toggle(name),style:{accentColor:"var(--cta-primary-bg)"}}),React.createElement("span",{style:{fontWeight:600,flex:"1 1 auto",minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},name),React.createElement("span",{style:{color:"var(--text-faint)",fontFamily:"monospace",fontSize:11}},size))}))),React.createElement("div",{className:"dv-panel",style:{marginTop:needsPicker||showNudge?16:0}},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,color:"var(--text-muted)"}},React.createElement("strong",{style:{color:"var(--text)"}},fileName)," \u2014 ",parsedHeaders.length," cols \xD7 ",parsedRows.length," rows",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement("p",{style:{fontSize:11,color:"var(--text-faint)",marginBottom:10}},"Preview (first 8 rows):"),React.createElement(DataPreview,{headers:parsedHeaders,rows:parsedRows,maxRows:8})))}function PlotControls({allSetNames,allSets,activeSetNames,activeSets,intersections,onToggleSet,setColors,onColorChange,onRename,vis,updVis,chartRef,resetAll,proportional,onProportionalChange,fileName}){let baseName=fileBaseName(fileName,"venn"),sv=k=>v=>updVis({[k]:v});return React.createElement(PlotSidebar,null,React.createElement(DownloadTiles,{chartRef,fileStem:`${baseName}_venn`,onReset:resetAll,extraDownloads:[{label:"CSV",title:"Download the membership matrix \u2014 one row per item, a 0/1 column for each active set (long/tidy format)",onClick:()=>{let allItems=new Set;for(let n of activeSetNames){let s=allSets.get(n);if(s)for(let item of s)allItems.add(item)}let headers=["Item",...activeSetNames],rows=[...allItems].sort().map(item=>[item,...activeSetNames.map(n=>allSets.get(n)?.has(item)?"1":"0")]);downloadCsv(headers,rows,`${baseName}_venn_membership.csv`)}},{label:"Regions",title:"Download one CSV per non-empty region (fires multiple saves \u2014 your browser may ask once to allow them)",onClick:()=>{intersections.filter(r=>r.size>0).forEach((r,i)=>{let label=regionLabel(r.setNames,r.mask,activeSetNames),name=`${baseName}_venn_${regionFilenamePart(label)}.csv`;setTimeout(()=>downloadCsv(["Item"],r.items.map(it=>[it]),name),i*40)})}}]}),React.createElement("div",{className:"dv-panel"},React.createElement("p",{className:"dv-tile-title",style:{margin:"0 0 8px"}},"Sets"),React.createElement(DiscretePaletteRow,{value:vis.discretePalette||"okabe-ito",onChange:next=>{updVis({discretePalette:next,setColors:applyDiscretePalette(next,allSetNames)})},names:allSetNames}),React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:5}},allSetNames.map((name,i)=>{let active=activeSets.has(name),canUncheck=activeSets.size>2;return React.createElement("div",{key:name,style:{display:"flex",alignItems:"center",gap:6,padding:"5px 8px",borderRadius:6,fontSize:12,background:active?"var(--surface-sunken)":"var(--surface-subtle)",border:active?"1px solid var(--border-strong)":"1px solid var(--border)",opacity:active?1:.5}},React.createElement("input",{type:"checkbox",checked:active,disabled:active&&!canUncheck,onChange:()=>onToggleSet(name),style:{accentColor:setColors[name]||PALETTE[i%PALETTE.length],flexShrink:0}}),React.createElement(ColorInput,{value:setColors[name]||PALETTE[i%PALETTE.length],onChange:v=>onColorChange(name,v),size:20}),React.createElement("input",{key:name,defaultValue:name,style:{flex:1,minWidth:0,fontWeight:600,color:active?"var(--text)":"var(--text-faint)",border:"1px solid var(--border-strong)",background:"var(--surface)",fontFamily:"monospace",fontSize:12,padding:"2px 6px",borderRadius:3,outline:"none"},onFocus:e=>{e.target.style.borderColor="var(--accent-primary)",e.target.style.boxShadow="var(--focus-ring)"},onBlur:e=>{e.target.style.borderColor="var(--border-strong)",e.target.style.boxShadow="none";let nv=e.target.value.trim();nv&&nv!==name?onRename(name,nv)||(e.target.value=name):nv||(e.target.value=name)},onKeyDown:e=>{e.key==="Enter"&&e.target.blur()}}),React.createElement("span",{style:{color:"var(--text-faint)",fontSize:11,whiteSpace:"nowrap",flexShrink:0}},"(",allSets.get(name)?.size??0,")"))}))),React.createElement("div",{className:"dv-panel"},React.createElement("p",{className:"dv-tile-title",style:{margin:"0 0 8px"}},"Display"),React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:8}},React.createElement("div",null,React.createElement("span",{className:"dv-label"},"Proportional areas"),React.createElement(OnOffToggle,{value:proportional,onChange:onProportionalChange,ariaLabel:"Proportional areas"})),proportional&&React.createElement(SliderControl,{label:"Proportional \u2194 Readable",value:vis.readabilityBlend,min:0,max:1,step:.05,displayValue:`${Math.round(vis.readabilityBlend*100)}%`,onChange:sv("readabilityBlend")}),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Title"),React.createElement("input",{value:vis.plotTitle,onChange:e=>updVis({plotTitle:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement(SliderControl,{label:"Fill opacity",value:vis.fillOpacity,min:.05,max:.8,step:.05,onChange:sv("fillOpacity")}),React.createElement("div",null,React.createElement("span",{className:"dv-label"},"Circle outline"),React.createElement(OnOffToggle,{value:vis.showOutline,onChange:v=>updVis({showOutline:v}),ariaLabel:"Circle outline"})),React.createElement(SliderControl,{label:"Font size",value:vis.fontSize,min:8,max:24,step:1,onChange:sv("fontSize")}),React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},React.createElement("span",{className:"dv-label"},"Background"),React.createElement(ColorInput,{value:vis.plotBg,onChange:sv("plotBg"),size:24})))))}var{useMemo,useEffect,forwardRef,memo}=React,VW=600,VH=500,VennChart=memo(forwardRef(function({setNames,sets,intersections,colors,selectedMask,onRegionClick,plotTitle,plotBg,fontSize,fillOpacity,onLayoutInfo,proportional,readabilityBlend,showOutline},ref){let n=setNames.length,blend=readabilityBlend??VENN_CONFIG.DEFAULT_READABILITY_BLEND,layout=useMemo(()=>proportional?n===2?buildVenn2Layout(setNames,sets,intersections,VW,VH,blend):buildVenn3Layout(setNames,sets,intersections,VW,VH,blend):n===2?buildVenn2LayoutClassic(setNames,sets,intersections,VW,VH):buildVenn3LayoutClassic(setNames,sets,intersections,VW,VH),[setNames,sets,intersections,n,proportional,blend]),circles=layout.circles;useEffect(()=>{onLayoutInfo&&onLayoutInfo({warnings:layout.warnings,proportional:layout.proportional,maxError:layout.maxError||0,meanError:layout.meanError||0})},[layout.warnings,layout.proportional,layout.maxError,layout.meanError,onLayoutInfo]);let regionPaths=useMemo(()=>buildRegionPaths(circles),[circles]),centroids=useMemo(()=>computeRegionCentroids(circles,regionPaths,intersections),[circles,regionPaths,intersections]),fSize=fontSize||14,fOpacity=fillOpacity??.25,outlineOn=showOutline!==!1;return React.createElement("svg",{ref,viewBox:`0 0 ${VW} ${VH}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"Venn diagram"},React.createElement("title",null,plotTitle||"Venn diagram"),React.createElement("desc",null,`Venn diagram with ${n} set${n!==1?"s":""}: ${setNames.join(", ")}`),React.createElement("rect",{id:"background",width:VW,height:VH,fill:plotBg||"#fff",rx:"8"}),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:VW/2,y:24,textAnchor:"middle",fontSize:"16",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},plotTitle)),React.createElement("g",{id:"set-circles"},circles.map((c,i)=>React.createElement("circle",{key:`circle-${i}`,id:`set-${svgSafeId(setNames[i])}`,cx:c.cx,cy:c.cy,r:c.r,fill:colors[setNames[i]]||PALETTE[i],fillOpacity:fOpacity,stroke:outlineOn?colors[setNames[i]]||PALETTE[i]:"none",strokeWidth:outlineOn?2:0,strokeOpacity:outlineOn?.6:0,"aria-label":`Set ${setNames[i]}: ${sets.get(setNames[i])?.size??0} elements`}))),selectedMask!=null&®ionPaths[selectedMask]&&React.createElement("g",{id:"selected-region"},React.createElement("path",{d:regionPaths[selectedMask],fill:"none",stroke:"#222",strokeWidth:"2.5",strokeDasharray:"6,3",style:{pointerEvents:"none"}})),React.createElement("g",{id:"region-counts"},intersections.map(inter=>{let c=centroids[inter.mask];if(!c)return null;let isSelected=selectedMask===inter.mask,regionPath=regionPaths[inter.mask],labelId=inter.setNames.map(n2=>svgSafeId(n2)).join("-")||`mask-${inter.mask}`;return React.createElement("g",{key:`label-${inter.mask}`,id:`count-${labelId}`,style:{cursor:"pointer"},onClick:()=>onRegionClick&&onRegionClick(isSelected?null:inter.mask)},regionPath?React.createElement("path",{d:regionPath,fill:"none",pointerEvents:"all"}):React.createElement("circle",{cx:c.x,cy:c.y,r:Math.max(fSize*1.5,20),fill:"transparent",pointerEvents:"all"}),React.createElement("text",{x:c.x,y:c.y,textAnchor:"middle",dominantBaseline:"central",fontSize:fSize,fontWeight:"700",fill:"#333",fontFamily:"sans-serif",pointerEvents:"none"},inter.size))})),React.createElement("g",{id:"legend"},circles.map((c,i)=>React.createElement("g",{key:`setlabel-${i}`,id:`legend-${svgSafeId(setNames[i])}`},React.createElement("circle",{cx:18,cy:VH-20-(circles.length-1-i)*22,r:6,fill:colors[setNames[i]]||PALETTE[i],fillOpacity:"0.5",stroke:colors[setNames[i]]||PALETTE[i],strokeWidth:"1.5"}),React.createElement("text",{x:30,y:VH-20-(circles.length-1-i)*22,textAnchor:"start",dominantBaseline:"central",fontSize:"13",fontWeight:"600",fill:colors[setNames[i]]||PALETTE[i],fontFamily:"sans-serif"},setNames[i]," (",sets.get(setNames[i])?.size??0,")")))))}));var{useState}=React;function IntersectionTable({intersections,allSetNames,selectedMask,onSelect}){let[hoveredMask,setHoveredMask]=useState(null);return React.createElement("div",{style:{overflowX:"auto"}},React.createElement("table",{style:{borderCollapse:"collapse",fontSize:12,width:"100%"}},React.createElement("thead",null,React.createElement("tr",{style:{borderBottom:"2px solid var(--border)"}},React.createElement("th",{style:{padding:"6px 10px",textAlign:"left",color:"var(--text-muted)",fontWeight:700}},"Region"),React.createElement("th",{style:{padding:"6px 10px",textAlign:"center",color:"var(--text-muted)",fontWeight:700}},"Degree"),React.createElement("th",{style:{padding:"6px 10px",textAlign:"right",color:"var(--text-muted)",fontWeight:700}},"Count"))),React.createElement("tbody",null,intersections.map(inter=>React.createElement("tr",{key:inter.mask,onClick:()=>onSelect(inter.mask),onMouseEnter:()=>setHoveredMask(inter.mask),onMouseLeave:()=>setHoveredMask(m=>m===inter.mask?null:m),style:{borderBottom:"1px solid var(--border)",cursor:"pointer",background:selectedMask===inter.mask?"var(--row-hover-bg)":hoveredMask===inter.mask?"var(--surface-subtle)":"transparent",transition:"background 120ms ease"}},React.createElement("td",{style:{padding:"6px 10px",color:"var(--text)",fontWeight:500}},regionLabel(inter.setNames,inter.mask,allSetNames)),React.createElement("td",{style:{padding:"6px 10px",textAlign:"center",color:"var(--text-faint)"}},inter.degree),React.createElement("td",{style:{padding:"6px 10px",textAlign:"right",color:"var(--accent-primary)",fontWeight:700,fontFamily:"monospace"}},inter.size))))))}function ItemListPanel({intersection,allSetNames,fileName}){let baseName=fileBaseName(fileName,"venn");if(!intersection)return React.createElement("div",{style:{padding:"30px 20px",textAlign:"center",color:"var(--text-faint)",fontSize:13}},"Click a region in the Venn diagram or a row in the table to view items.");let label=regionLabel(intersection.setNames,intersection.mask,allSetNames);return React.createElement("div",null,React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:8}},React.createElement("p",{style:{margin:0,fontSize:13,fontWeight:600,color:"var(--text)"}},label," ",React.createElement("span",{style:{color:"var(--text-faint)",fontWeight:400}},"(",intersection.size," items)")),React.createElement("button",{onClick:()=>{downloadCsv(["Item"],intersection.items.map(i=>[i]),`${baseName}_venn_${regionFilenamePart(label)}.csv`)},className:"dv-btn dv-btn-secondary",style:{background:"var(--success-bg)",border:"1px solid var(--success-border)",color:"var(--success-text)",fontWeight:600,fontSize:11,marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),React.createElement("div",{style:{maxHeight:240,overflowY:"auto",border:"1px solid var(--border)",borderRadius:6,background:"var(--surface-subtle)"}},intersection.items.map((item,i)=>React.createElement("div",{key:i,style:{padding:"3px 10px",fontSize:12,color:"var(--text)",borderBottom:"1px solid var(--border)",fontFamily:"monospace"}},item))))}function LayoutInfoBanner({proportional,layoutInfo}){if(!proportional)return null;let pctMax=(layoutInfo.maxError*100).toFixed(1),pctMean=(layoutInfo.meanError*100).toFixed(1),exact=layoutInfo.warnings.length===0&&layoutInfo.maxError<.005,hasWarnings=layoutInfo.warnings.length>0,bg=exact?"var(--success-bg)":hasWarnings?"var(--warning-bg)":"var(--info-bg)",border=exact?"var(--success-border)":hasWarnings?"var(--warning-border)":"var(--info-border)",color=exact?"var(--success-text)":hasWarnings?"var(--warning-text)":"var(--info-text)";return React.createElement("div",{style:{margin:"8px 0 0",padding:"6px 12px",borderRadius:6,background:bg,border:`1px solid ${border}`,fontSize:11,color}},exact?React.createElement("div",null,"Areas are proportional to set sizes (max region error < 0.5%)"):React.createElement("div",null,"Max region error: ",React.createElement("strong",null,pctMax,"%")," \xB7 mean ",pctMean,"%"),hasWarnings&&layoutInfo.warnings.map((w,i)=>React.createElement("div",{key:i,style:{marginTop:2}},w)))}function PlotArea({chartRef,activeSetNames,activeSetsMap,intersections,setColors,selectedMask,setSelectedMask,vis,proportional,layoutInfo,setLayoutInfo,selectedIntersection,fileName}){return React.createElement("div",{style:{flex:1,minWidth:0}},React.createElement("div",{className:"dv-panel dv-plot-card",style:{padding:20,background:"var(--plot-card-bg)",borderColor:"var(--plot-card-border)"}},React.createElement(VennChart,{ref:chartRef,setNames:activeSetNames,sets:activeSetsMap,intersections,colors:setColors,selectedMask,onRegionClick:setSelectedMask,plotTitle:vis.plotTitle,plotBg:vis.plotBg,fontSize:vis.fontSize,fillOpacity:vis.fillOpacity,readabilityBlend:vis.readabilityBlend,showOutline:vis.showOutline,onLayoutInfo:setLayoutInfo,proportional})),React.createElement(LayoutInfoBanner,{proportional,layoutInfo}),React.createElement("div",{className:"dv-panel",style:{marginTop:16}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Intersections"),React.createElement(IntersectionTable,{intersections,allSetNames:activeSetNames,selectedMask,onSelect:setSelectedMask})),React.createElement("div",{className:"dv-panel",style:{marginTop:16}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Items"),React.createElement(ItemListPanel,{intersection:selectedIntersection,allSetNames:activeSetNames,fileName})))}var{useState:useState2,useMemo:useMemo2,useCallback,useRef}=React,EXAMPLE_CSV=`Drought,Heat,Salt RD29B,HSP70,SOS1 NCED3,HSP90,SOS2 @@ -20,4 +20,4 @@ HSFA9,ANAC019,RD26 RD26,ANAC055,ANAC019 ANAC019,,ANAC055 ANAC055,,`;function App(){let shell=usePlotToolState("venn",VIS_INIT_VENN),{step,setStep,fileName,setFileName,setParseError,sepOverride,setSepOverride,setCommaFixed,setCommaFixCount,setInjectionWarning,vis,updVis}=shell,[detectedSep,setDetectedSep]=useState2(""),[setNames,setSetNames]=useState2([]),[sets,setSets]=useState2(new Map),setColors=useMemo2(()=>vis.setColors||{},[vis.setColors]),setSetColors=useCallback(updater=>updVis({setColors:typeof updater=="function"?updater(vis.setColors||{}):updater||{}}),[updVis,vis.setColors]),[parsedHeaders,setParsedHeaders]=useState2([]),[parsedRows,setParsedRows]=useState2([]),[selectedMask,setSelectedMask]=useState2(null),[activeSets,setActiveSets]=useState2(new Set),[allColumnNames,setAllColumnNames]=useState2([]),[allColumnSets,setAllColumnSets]=useState2(new Map),[pendingSelection,setPendingSelection]=useState2([]),[isLongFormat,setIsLongFormat]=useState2(!1),[proportional,setProportional]=useState2(!1),chartRef=useRef(null),[layoutInfo,setLayoutInfo]=useState2({warnings:[],proportional:!0,maxError:0,meanError:0}),activeSetNames=useMemo2(()=>setNames.filter(n=>activeSets.has(n)),[setNames,activeSets]),activeSetsMap=useMemo2(()=>{let m=new Map;for(let n of activeSetNames)m.set(n,sets.get(n));return m},[activeSetNames,sets]),intersections=useMemo2(()=>activeSetNames.length<2?[]:computeIntersections(activeSetNames,activeSetsMap),[activeSetNames,activeSetsMap]),canNavigate=useCallback(target=>target==="upload"?!0:target==="configure"?allColumnNames.length>=2:target==="plot"?step==="configure"?pendingSelection.length>=2&&pendingSelection.length<=3:setNames.length>=2:!1,[allColumnNames,setNames,step,pendingSelection]),commitSelection=useCallback((names,allSets)=>{let chosen=new Map;names.forEach(n=>chosen.set(n,allSets.get(n))),setSetNames(names),setSets(chosen),setActiveSets(new Set(names)),setSetColors(applyDiscretePalette(vis.discretePalette||"okabe-ito",names)),setSelectedMask(null)},[setSetColors,vis.discretePalette]),navigateStep=useCallback(target=>{target==="plot"&&step==="configure"&&pendingSelection.length>=2&&pendingSelection.length<=3&&(pendingSelection.length!==setNames.length||pendingSelection.some(n=>!setNames.includes(n)))&&commitSelection(pendingSelection,allColumnSets),setStep(target)},[step,pendingSelection,setNames,allColumnSets,commitSelection,setStep]),doParse=useCallback((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count);let{headers,rows,injectionWarnings}=parseRaw(dc.text,effectiveSep);if(setInjectionWarning(injectionWarnings),!headers.length||!rows.length){setParseError("The file appears to be empty or has no data rows.");return}let sn=[],ss=new Map,usedLongFormat=!1;if(headers.length===2&&detectLongFormat(headers,rows).isLong)try{let longParsed=parseLongFormatSets(headers,rows);sn=longParsed.setNames,ss=longParsed.sets,usedLongFormat=!0}catch{}if(!usedLongFormat){let wide=parseSetData(headers,rows);sn=wide.setNames,ss=wide.sets}if(sn.length<2){setParseError("Need at least 2 sets \u2014 each column header becomes a set name.");return}setParseError(null),setParsedHeaders(headers),setParsedRows(rows),setAllColumnNames(sn),setAllColumnSets(ss),setIsLongFormat(usedLongFormat),sn.length<=3?(setPendingSelection(sn),commitSelection(sn,ss),setStep("configure")):(setPendingSelection([]),setSetNames([]),setSets(new Map),setActiveSets(new Set),setSetColors({}),setSelectedMask(null),setStep("configure"))},[commitSelection,setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep,setSetColors]),handleFileLoad=useCallback((text,name)=>{setFileName(name),doParse(text,sepOverride)},[sepOverride,doParse,setFileName]),handleTextPaste=useCallback((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]),loadExample=useCallback(()=>{let text=EXAMPLE_CSV;text&&(setSepOverride(""),setFileName("arabidopsis_stress.csv"),doParse(text,""))},[doParse,setFileName,setSepOverride]),handleColorChange=(name,color)=>{setSetColors(prev=>({...prev,[name]:color}))},handleRename=(oldName,newName)=>oldName===newName||setNames.includes(newName)?!1:(setSetNames(prev=>prev.map(n=>n===oldName?newName:n)),setSets(prev=>{let m=new Map;for(let[k,v]of prev)m.set(k===oldName?newName:k,v);return m}),setSetColors(prev=>{let c={};for(let[k,v]of Object.entries(prev))c[k===oldName?newName:k]=v;return c}),setActiveSets(prev=>{let s=new Set(prev);return s.has(oldName)&&(s.delete(oldName),s.add(newName)),s}),!0),handleToggleSet=name=>{setActiveSets(prev=>{let s=new Set(prev);return s.has(name)?s.delete(name):s.add(name),s}),setSelectedMask(null)},resetAll=()=>{setStep("upload"),setFileName(""),setSetNames([]),setSets(new Map),setSetColors({}),setActiveSets(new Set),setParseError(null),setInjectionWarning(null),setSelectedMask(null),setProportional(!1),updVis({_reset:!0})},selectedIntersection=intersections.find(g=>g.mask===selectedMask)||null;return React.createElement(PlotToolShell,{state:{...shell,setStep:navigateStep},toolName:"venn",title:"Venn Diagram",visInit:VIS_INIT_VENN,steps:["upload","configure","plot"],stepLabels:{configure:allColumnNames.length>=4?"Configure":"Import check"},canNavigate},step==="upload"&&React.createElement(UploadStep,{sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample:loadExample}),step==="configure"&&allColumnNames.length>=2&&React.createElement(ConfigureStep,{fileName,detectedSep,parsedHeaders,parsedRows,allColumnNames,allColumnSets,pendingSelection,setPendingSelection,isLongFormat}),step==="plot"&&activeSetNames.length>=2&&React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotControls,{allSetNames:setNames,allSets:sets,activeSetNames,activeSets,intersections,onToggleSet:handleToggleSet,setColors,onColorChange:handleColorChange,onRename:handleRename,vis,updVis,chartRef,resetAll,proportional,onProportionalChange:setProportional,fileName}),React.createElement(PlotArea,{chartRef,activeSetNames,activeSetsMap,intersections,setColors,selectedMask,setSelectedMask,vis,proportional,layoutInfo,setLayoutInfo,selectedIntersection,fileName}))))}export{App}; -//# sourceMappingURL=app-3ELPKOLT.js.map +//# sourceMappingURL=app-BLY3XSWB.js.map diff --git a/tools/_app/chunks/app-3ELPKOLT.js.map b/tools/_app/chunks/app-BLY3XSWB.js.map similarity index 100% rename from tools/_app/chunks/app-3ELPKOLT.js.map rename to tools/_app/chunks/app-BLY3XSWB.js.map diff --git a/tools/_app/chunks/app-7CWQ47AS.js b/tools/_app/chunks/app-IRS66EWH.js similarity index 99% rename from tools/_app/chunks/app-7CWQ47AS.js rename to tools/_app/chunks/app-IRS66EWH.js index 077e7434..c656cd58 100644 --- a/tools/_app/chunks/app-7CWQ47AS.js +++ b/tools/_app/chunks/app-IRS66EWH.js @@ -1,4 +1,4 @@ -import{hclust,kmeans,pairwiseDistance}from"./chunk-GD3FGCBE.js";import{ActionsPanel,COLOR_PALETTES,ColorInput,ControlSection,DIVERGING_PALETTES,DataPreview,DetectedSeparatorBadge,HowTo,NumberInput,PlotSidebar,PlotToolShell,UploadPanel,autoDetectSep,downloadCsv,downloadPng,downloadSvg,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatRNumber,interpolateColor,parseWideMatrix,registerSvgExportMutator,sanitizeRString,seededRandom,unregisterSvgExportMutator,usePlotToolState,wrapRItems}from"./chunk-5F6S3FYW.js";function finiteMean(arr){let s=0,n=0;for(let i=0;i1?Math.sqrt(s/(n-1)):0}function normalizeMatrix(matrix,mode){let nRows=matrix.length;if(nRows===0)return matrix;let nCols=matrix[0].length;if(mode==="zrow")return matrix.map(row=>{let m=finiteMean(row),sd=finiteSD(row,m);return!Number.isFinite(m)||sd===0?row.slice():row.map(v=>Number.isFinite(v)?(v-m)/sd:NaN)});if(mode==="zcol"){let out=matrix.map(r=>r.slice());for(let ci=0;cirow.map(v=>Number.isFinite(v)&&v>-1?Math.log2(v+1):NaN)):matrix}function autoRange(matrix,diverging){let lo=1/0,hi=-1/0;for(let i=0;ihi&&(hi=v))}if(!Number.isFinite(lo)||!Number.isFinite(hi))return[0,1];if(diverging){let m=Math.max(Math.abs(lo),Math.abs(hi));return[-m||0,m||1]}return lo===hi?[lo-.5,hi+.5]:[lo,hi]}var DENDRO_STROKE="#555555",DENDRO_HOVER_STROKE="#0072B2",NAN_FILL="#e0e0e0",SELECTION_STROKE="#111111",HIGHLIGHT_STROKE="#111111";function buildDendroLayout(tree){if(!tree)return{segments:[],nodes:[],maxHeight:0};let segments=[],nodes=[],leafIdx=0,maxHeight=0;function walk(node){if(!node.left&&!node.right){let pos=leafIdx++;return{x:pos,h:0,leaves:[node.index??-1],xMin:pos,xMax:pos}}let L=walk(node.left),R=walk(node.right),h=node.height;h>maxHeight&&(maxHeight=h);let leaves=L.leaves.concat(R.leaves),xMin=Math.min(L.xMin,R.xMin),xMax=Math.max(L.xMax,R.xMax);return segments.push({x1:L.x,y1:L.h,x2:L.x,y2:h,xMin,xMax}),segments.push({x1:R.x,y1:R.h,x2:R.x,y2:h,xMin,xMax}),segments.push({x1:L.x,y1:h,x2:R.x,y2:h,xMin,xMax}),nodes.push({height:h,leaves,xMin,xMax}),{x:(L.x+R.x)/2,h,leaves,xMin,xMax}}return walk(tree),{segments,nodes,maxHeight}}function pruneDendroTree(tree,keepSet){function rec(node){if(!node)return null;if(!node.left&&!node.right)return node.index!=null&&keepSet.has(node.index)?node:null;let L=rec(node.left),R=rec(node.right);return!L&&!R?null:L?R?{index:-1,left:L,right:R,height:node.height,size:(L.size||1)+(R.size||1)}:L:R}let pruned=rec(tree);return!pruned||!pruned.left&&!pruned.right?null:pruned}var CLUSTER_PALETTE=["#E69F00","#56B4E9","#009E73","#F0E442","#0072B2","#D55E00","#CC79A7","#999999","#88CCEE","#AA4499"];function fmtColorbarTick(v){if(!Number.isFinite(v))return"\u2014";if(v===0)return"0";let abs=Math.abs(v);return abs>=1e3||abs<.01?v.toExponential(1):abs<1?v.toFixed(2):v.toFixed(1)}var{useState,useMemo,useCallback,useRef,useEffect,forwardRef,memo}=React,CELL_RASTER_ONSCREEN_DPR_MULT=2,CELL_RASTER_ONSCREEN_MAX_DPR=6,CELL_RASTER_EXPORT_DPR_MULT=4,CELL_RASTER_EXPORT_MAX_DPR=8,CELL_VECTOR_EXPORT_LIMIT=2e3;function PaletteStrip({palette,invert=!1,height=12}){let base=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,stops=invert?[...base].reverse():base,n=48;return React.createElement("div",{style:{display:"flex",width:"100%",height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border)"}},Array.from({length:n},(_,i)=>React.createElement("div",{key:i,style:{flex:1,background:interpolateColor(stops,i/(n-1))}})))}var HeatmapChart=memo(forwardRef(function({rowLabels,colLabels,matrix,rowOrder,colOrder,rowCluster,colCluster,vmin,vmax,palette,invertPalette=!1,cellBorder,plotTitle,plotSubtitle,rowAxisLabel,colAxisLabel,showRowLabels=!0,showColLabels=!0,showRowDendrogram=!0,showColDendrogram=!0,interactive=!1,selection=null,onBrushEnd,onAxisSelect,onClearSelection,rawMatrix,baseCellW,baseCellH,baseColorbarH,baseDendroSizeLeft,baseDendroSizeTop,baseColLabelH,baseRowLabelW,cellOffsetCols=0,cellOffsetRows=0,basePlotW,basePlotH,colGapStartPx=0,rowGapStartPx=0,showClusterStrip=!0,showKmeansStrip=void 0,dendrogramStrokeWidth=1},ref){let nRows=rowOrder.length,nCols=colOrder.length,paletteBase=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,stops=invertPalette?[...paletteBase].reverse():paletteBase,longestRowLabel=Math.max(0,...rowLabels.map(l=>(l||"").length)),longestColLabel=Math.max(0,...colLabels.map(l=>(l||"").length)),cellW=baseCellW??Math.max(2,Math.min(28,Math.floor(720/Math.max(1,nCols)))),cellH=baseCellH??Math.max(2,Math.min(28,Math.floor(480/Math.max(1,nRows)))),rowIsHier=rowCluster&&rowCluster.mode==="hierarchical"&&rowCluster.tree,rowIsKmeans=rowCluster&&rowCluster.mode==="kmeans",colIsHier=colCluster&&colCluster.mode==="hierarchical"&&colCluster.tree,colIsKmeans=colCluster&&colCluster.mode==="kmeans",K_GAP=10,colGapOffsets=useMemo(()=>{let arr=new Array(nCols).fill(0);if(colIsKmeans)for(let ci=1;ci{let arr=new Array(nRows).fill(0);if(rowIsKmeans)for(let ri=1;ri0?colGapOffsets[nCols-1]:0,totalRowGap=nRows>0?rowGapOffsets[nRows-1]:0,plotW=basePlotW??cellW*nCols+totalColGap,plotH=basePlotH??cellH*nRows+totalRowGap,computedDendroTop=colIsHier&&showColDendrogram?60:colIsKmeans?14:0,computedDendroLeft=rowIsHier&&showRowDendrogram?60:rowIsKmeans?14:0,DENDRO_SIZE_TOP=baseDendroSizeTop??computedDendroTop,DENDRO_SIZE_LEFT=baseDendroSizeLeft??computedDendroLeft,effShowKmeans=showKmeansStrip??showClusterStrip,CLUSTER_LABEL_H=colIsKmeans&&effShowKmeans?24:0,CLUSTER_LABEL_W=rowIsKmeans&&effShowKmeans?92:0,LABEL_GAP=6,ROW_LABEL_W=baseRowLabelW??(showRowLabels?Math.min(160,longestRowLabel*6+12):0),COL_LABEL_H=baseColLabelH??(showColLabels?Math.min(120,Math.round(longestColLabel*5.5)+16):0),TITLE_H=plotTitle?plotSubtitle?42:26:0,AXIS_LABEL_TOP=colAxisLabel?16:0,AXIS_LABEL_LEFT=rowAxisLabel?16:0,CB_W=12,CB_OFFSET=16,CB_TICK_W=44,CB_H=baseColorbarH??Math.min(180,Math.max(60,Math.round(plotH*.6))),MARGIN={top:TITLE_H+10+AXIS_LABEL_TOP+CLUSTER_LABEL_H+COL_LABEL_H+LABEL_GAP+DENDRO_SIZE_TOP,left:AXIS_LABEL_LEFT+CLUSTER_LABEL_W+DENDRO_SIZE_LEFT+LABEL_GAP,right:ROW_LABEL_W+CB_OFFSET+CB_W+CB_TICK_W+10,bottom:12},vbW=MARGIN.left+plotW+MARGIN.right,vbH=MARGIN.top+Math.max(plotH,CB_H+14)+MARGIN.bottom,bordersOn=cellBorder&&cellBorder.on,colIx=ci=>ci+cellOffsetCols,rowIx=ri=>ri+cellOffsetRows,colGap=ci=>colGapOffsets[ci]||0,rowGap=ri=>rowGapOffsets[ri]||0,cellX=ci=>(bordersOn?colIx(ci)*cellW+colGap(ci):Math.round(colIx(ci)*cellW)+colGap(ci))+colGapStartPx,cellY=ri=>(bordersOn?rowIx(ri)*cellH+rowGap(ri):Math.round(rowIx(ri)*cellH)+rowGap(ri))+rowGapStartPx,cellWPx=ci=>bordersOn?cellW:Math.round((colIx(ci)+1)*cellW)-Math.round(colIx(ci)*cellW),cellHPx=ri=>bordersOn?cellH:Math.round((rowIx(ri)+1)*cellH)-Math.round(rowIx(ri)*cellH),valueToColor=v=>{if(!Number.isFinite(v))return NAN_FILL;let t=(v-vmin)/(vmax-vmin||1);return interpolateColor(stops,Math.max(0,Math.min(1,t)))},[hover,setHover]=useState(null),[brush,setBrush]=useState(null),[axisHover,setAxisHover]=useState(null),svgLocalRef=useRef(null),wrapperRef=useRef(null),setRefs=useCallback(node=>{svgLocalRef.current=node,typeof ref=="function"?ref(node):ref&&(ref.current=node)},[ref]);function svgPoint(e){let svg=svgLocalRef.current;if(!svg||!svg.createSVGPoint)return null;let pt=svg.createSVGPoint();pt.x=e.clientX,pt.y=e.clientY;let ctm=svg.getScreenCTM();return ctm?pt.matrixTransform(ctm.inverse()):null}function pointToCell(p){if(!p)return null;let localX=p.x-MARGIN.left,localY=p.y-MARGIN.top;if(localX<0||localY<0||localX>plotW||localY>plotH)return null;let ci=-1;for(let c=0;c=x&&localX=y&&localY{if(!selection||!selection.rows)return null;let keep=new Set(selection.rows),ris=[];for(let ri=0;ri{if(!selection||!selection.cols)return null;let keep=new Set(selection.cols),cis=[];for(let ci=0;ci{if(!axisHover||axisHover.axis!=="row")return null;let keep=axisHover.leaves,ris=[];for(let ri=0;ri{if(!axisHover||axisHover.axis!=="col")return null;let keep=axisHover.leaves,cis=[];for(let ci=0;ciReact.createElement("stop",{key:i,offset:(i/(stops.length-1)*100).toFixed(2)+"%",stopColor:c}));function segmentInHover(seg,axis){return!axisHover||axisHover.axis!==axis||axisHover.xMin==null||axisHover.xMax==null?!1:seg.xMin>=axisHover.xMin&&seg.xMax<=axisHover.xMax}function renderColDendrogram(){if(!colIsHier)return null;let{segments,nodes,maxHeight}=buildDendroLayout(colCluster.tree);if(maxHeight===0||segments.length===0)return null;let yBase=MARGIN.top-LABEL_GAP-COL_LABEL_H-LABEL_GAP,yTop=yBase-DENDRO_SIZE_TOP+4,scaleY=h=>yBase-h/maxHeight*(yBase-yTop),scaleX=x=>MARGIN.left+x*cellW+cellW/2;return React.createElement("g",{id:"col-dendrogram"},React.createElement("g",{fill:"none",strokeLinecap:"round"},segments.map((s,i)=>{let active=segmentInHover(s,"col");return React.createElement("line",{key:i,x1:scaleX(s.x1),y1:scaleY(s.y1),x2:scaleX(s.x2),y2:scaleY(s.y2),stroke:active?DENDRO_HOVER_STROKE:DENDRO_STROKE,strokeWidth:active?2.5*dendrogramStrokeWidth:dendrogramStrokeWidth})})),interactive&&React.createElement("g",{id:"col-dendrogram-hits",onMouseLeave:()=>setAxisHover(null)},nodes.map((n,i)=>{let x=scaleX(n.xMin)-cellW/2,w=scaleX(n.xMax)-scaleX(n.xMin)+cellW,y=scaleY(n.height)-4;return React.createElement("rect",{key:i,x,y,width:w,height:8,fill:"none",pointerEvents:"all",style:{cursor:"pointer"},onMouseEnter:()=>setAxisHover({axis:"col",leaves:new Set(n.leaves),xMin:n.xMin,xMax:n.xMax}),onClick:e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("col",n.leaves)}})})))}function renderRowDendrogram(){if(!rowIsHier)return null;let{segments,nodes,maxHeight}=buildDendroLayout(rowCluster.tree);if(maxHeight===0||segments.length===0)return null;let xRight=MARGIN.left-LABEL_GAP,xLeft=xRight-DENDRO_SIZE_LEFT+4,scaleX=h=>xRight-h/maxHeight*(xRight-xLeft),scaleY=x=>MARGIN.top+x*cellH+cellH/2;return React.createElement("g",{id:"row-dendrogram"},React.createElement("g",{fill:"none",strokeLinecap:"round"},segments.map((s,i)=>{let active=segmentInHover(s,"row");return React.createElement("line",{key:i,x1:scaleX(s.y1),y1:scaleY(s.x1),x2:scaleX(s.y2),y2:scaleY(s.x2),stroke:active?DENDRO_HOVER_STROKE:DENDRO_STROKE,strokeWidth:active?2.5*dendrogramStrokeWidth:dendrogramStrokeWidth})})),interactive&&React.createElement("g",{id:"row-dendrogram-hits",onMouseLeave:()=>setAxisHover(null)},nodes.map((n,i)=>{let y=scaleY(n.xMin)-cellH/2,h=scaleY(n.xMax)-scaleY(n.xMin)+cellH,x=scaleX(n.height)-4;return React.createElement("rect",{key:i,x,y,width:8,height:h,fill:"none",pointerEvents:"all",style:{cursor:"pointer"},onMouseEnter:()=>setAxisHover({axis:"row",leaves:new Set(n.leaves),xMin:n.xMin,xMax:n.xMax}),onClick:e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("row",n.leaves)}})})))}function clusterColor(id){return CLUSTER_PALETTE[(id%CLUSTER_PALETTE.length+CLUSTER_PALETTE.length)%CLUSTER_PALETTE.length]}function renderColClusterStrip(){if(!colIsKmeans)return null;let y=MARGIN.top-LABEL_GAP-COL_LABEL_H-LABEL_GAP-DENDRO_SIZE_TOP+2,leavesByCluster=interactive?new Map:null;if(leavesByCluster)for(let c=0;csetAxisHover(null):void 0},colOrder.map((origCi,ci)=>{let cid=colCluster.clusters[origCi];return React.createElement("rect",{key:ci,x:MARGIN.left+cellX(ci),y,width:cellWPx(ci),height:DENDRO_SIZE_TOP-4,fill:clusterColor(cid),stroke:"none",shapeRendering:"crispEdges",style:interactive?{cursor:"pointer"}:void 0,onMouseEnter:interactive?()=>setAxisHover({axis:"col",leaves:new Set(leavesByCluster.get(cid))}):void 0,onClick:interactive?e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("col",leavesByCluster.get(cid)??null,{clusterId:cid})}:void 0})}),CLUSTER_LABEL_H>0&&bands.map(b=>{let x0=MARGIN.left+cellX(b.start),x1=MARGIN.left+cellX(b.end-1)+cellWPx(b.end-1),cx=(x0+x1)/2,cy=y-CLUSTER_LABEL_H/2-4;return React.createElement("text",{key:`lbl-${b.id}-${b.start}`,x:cx,y:cy,textAnchor:"middle",dominantBaseline:"middle",fontSize:10,fill:"#111111",stroke:"#ffffff",strokeWidth:3,paintOrder:"stroke",style:{pointerEvents:"none"}},`Cluster n\xB0 ${b.id+1}`)}))}function renderRowClusterStrip(){if(!rowIsKmeans)return null;let x=MARGIN.left-LABEL_GAP-DENDRO_SIZE_LEFT+2,leavesByCluster=interactive?new Map:null;if(leavesByCluster)for(let r=0;rsetAxisHover(null):void 0},rowOrder.map((origRi,ri)=>{let cid=rowCluster.clusters[origRi];return React.createElement("rect",{key:ri,x,y:MARGIN.top+cellY(ri),width:DENDRO_SIZE_LEFT-4,height:cellHPx(ri),fill:clusterColor(cid),stroke:"none",shapeRendering:"crispEdges",style:interactive?{cursor:"pointer"}:void 0,onMouseEnter:interactive?()=>setAxisHover({axis:"row",leaves:new Set(leavesByCluster.get(cid))}):void 0,onClick:interactive?e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("row",leavesByCluster.get(cid)??null,{clusterId:cid})}:void 0})}),CLUSTER_LABEL_W>0&&bands.map(b=>{let y0=MARGIN.top+cellY(b.start),y1=MARGIN.top+cellY(b.end-1)+cellHPx(b.end-1),cy=(y0+y1)/2,cx=x-CLUSTER_LABEL_W/2-4;return React.createElement("text",{key:`lbl-${b.id}-${b.start}`,x:cx,y:cy,textAnchor:"middle",dominantBaseline:"middle",fontSize:10,fill:"#111111",stroke:"#ffffff",strokeWidth:3,paintOrder:"stroke",style:{pointerEvents:"none"}},`Cluster n\xB0 ${b.id+1}`)}))}let hoverInfo=hover&&rowOrder[hover.ri]!=null&&colOrder[hover.ci]!=null?(()=>{let origRi=rowOrder[hover.ri],origCi=colOrder[hover.ci],vNorm=matrix[origRi]&&matrix[origRi][origCi],vRaw=rawMatrix&&rawMatrix[origRi]&&rawMatrix[origRi][origCi];return{rowLabel:rowLabels[origRi],colLabel:colLabels[origCi],vNorm,vRaw}})():null,brushRect=brush?(()=>{let riMin=Math.min(brush.anchorRi,brush.curRi),riMax=Math.max(brush.anchorRi,brush.curRi),ciMin=Math.min(brush.anchorCi,brush.curCi),ciMax=Math.max(brush.anchorCi,brush.curCi);return{x:MARGIN.left+cellX(ciMin),y:MARGIN.top+cellY(riMin),w:cellX(ciMax)+cellWPx(ciMax)-cellX(ciMin),h:cellY(riMax)+cellHPx(riMax)-cellY(riMin)}})():null;function renderRowBands(ris,stroke,fillOpacity){return!ris||ris.length===0?null:ris.map(ri=>React.createElement("rect",{key:`rb-${ri}`,x:MARGIN.left+cellX(0),y:MARGIN.top+cellY(ri),width:cellX(nCols-1)+cellWPx(nCols-1)-cellX(0),height:cellHPx(ri),fill:stroke,fillOpacity,stroke:"none",pointerEvents:"none"}))}function renderColBands(cis,stroke,fillOpacity){return!cis||cis.length===0?null:cis.map(ci=>React.createElement("rect",{key:`cb-${ci}`,x:MARGIN.left+cellX(ci),y:MARGIN.top+cellY(0),width:cellWPx(ci),height:cellY(nRows-1)+cellHPx(nRows-1)-cellY(0),fill:stroke,fillOpacity,stroke:"none",pointerEvents:"none"}))}function renderSelectionMask(selRis,selCis){if((!selRis||selRis.length===0)&&(!selCis||selCis.length===0))return null;let rowSel=selRis&&selRis.length?new Set(selRis):null,colSel=selCis&&selCis.length?new Set(selCis):null,rects=[],MASK_FILL="#ffffff",MASK_OPACITY=.6,fullW=cellX(nCols-1)+cellWPx(nCols-1)-cellX(0);if(rowSel)for(let ri=0;ri{if(plotW<=0||plotH<=0)return"";let d=Math.max(1,density);canvas.width=Math.max(1,Math.round(plotW*d)),canvas.height=Math.max(1,Math.round(plotH*d));let ctx=canvas.getContext("2d");if(!ctx)return"";ctx.setTransform(d,0,0,d,0,0),ctx.clearRect(0,0,plotW,plotH);for(let ri=0;ri{let canvas=cellCanvasRef.current;if(!canvas)return"";let baseDpr=typeof window<"u"&&window.devicePixelRatio||1,density=Math.min(CELL_RASTER_ONSCREEN_MAX_DPR,baseDpr*CELL_RASTER_ONSCREEN_DPR_MULT);return paintCellsToCanvas(canvas,density)},[rowOrder,colOrder,matrix,bordersOn,cellBorder,vmin,vmax,palette,invertPalette,cellW,cellH,cellOffsetCols,cellOffsetRows,colGapStartPx,rowGapStartPx,colGapOffsets,rowGapOffsets,plotW,plotH,nRows,nCols]),exportSnapshotRef=useRef(null);exportSnapshotRef.current={nRows,nCols,bordersOn,cellBorder,rowOrder,colOrder,matrix,cellX,cellY,cellWPx,cellHPx,valueToColor,paint:paintCellsToCanvas},useEffect(()=>{let el=svgLocalRef.current;if(el)return registerSvgExportMutator(el,clone=>{let snap=exportSnapshotRef.current;if(!snap)return;let cellsGroup=clone.querySelector("#cells");if(!cellsGroup)return;let doc=clone.ownerDocument;if(!doc)return;let NS="http://www.w3.org/2000/svg";if(snap.nRows*snap.nCols<=CELL_VECTOR_EXPORT_LIMIT){cellsGroup.querySelectorAll(":scope > image").forEach(node=>node.parentNode&&node.parentNode.removeChild(node));for(let ri=0;ri image");if(!image)return;let exportCanvas=doc.createElement("canvas"),baseDpr=typeof window<"u"&&window.devicePixelRatio||1,density=Math.min(CELL_RASTER_EXPORT_MAX_DPR,baseDpr*CELL_RASTER_EXPORT_DPR_MULT),href=snap.paint(exportCanvas,density);href&&image.setAttribute("href",href)}}),()=>unregisterSvgExportMutator(el)},[]);let hasClustering=rowIsHier||rowIsKmeans||colIsHier||colIsKmeans;return React.createElement("div",{ref:wrapperRef,style:{position:"relative"}},React.createElement("svg",{ref:setRefs,xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${vbW} ${vbH}`,width:vbW,height:vbH,style:{maxWidth:"100%",height:"auto",display:"block"},role:"img","aria-label":plotTitle||"Heatmap"},React.createElement("title",null,plotTitle||"Heatmap"),React.createElement("desc",null,`Heatmap of ${nRows} row${nRows!==1?"s":""} \xD7 ${nCols} column${nCols!==1?"s":""}${hasClustering?", with clustering":""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:vbW,height:vbH,fill:"#ffffff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:vbW/2,y:18,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"14",fontWeight:"700",fill:"#222222"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:vbW/2,y:plotTitle?34:18,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"11",fill:"#555555"},plotSubtitle)),colAxisLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+plotW/2,y:TITLE_H+22,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"11",fontWeight:"600",fill:"#333333"},colAxisLabel)),rowAxisLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`rotate(-90) translate(${-(MARGIN.top+plotH/2)}, 12)`,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"11",fontWeight:"600",fill:"#333333"},rowAxisLabel)),showClusterStrip&&showColDendrogram&&renderColDendrogram(),showClusterStrip&&showRowDendrogram&&renderRowDendrogram(),(showKmeansStrip??showClusterStrip)&&renderColClusterStrip(),(showKmeansStrip??showClusterStrip)&&renderRowClusterStrip(),React.createElement("g",{id:"chart",transform:`translate(${MARGIN.left}, ${MARGIN.top})`},React.createElement("g",{id:"plot-area-background"},React.createElement("rect",{x:0,y:0,width:plotW,height:plotH,fill:"#ffffff"})),React.createElement("g",{id:"cells","aria-label":`Matrix of ${nRows} row${nRows!==1?"s":""} \xD7 ${nCols} column${nCols!==1?"s":""}, values from ${vmin.toPrecision(3)} to ${vmax.toPrecision(3)}`},cellsImageHref&&React.createElement("image",{x:0,y:0,width:plotW,height:plotH,href:cellsImageHref,preserveAspectRatio:"none","aria-hidden":"true"}))),showColLabels&&React.createElement("g",{id:"col-labels","aria-label":`${nCols} column label${nCols!==1?"s":""}`},colOrder.map((origCi,ci)=>{let cx=MARGIN.left+cellX(ci)+cellW/2,cy=MARGIN.top-LABEL_GAP;return React.createElement("text",{key:ci,x:cx,y:cy,transform:`rotate(-45 ${cx} ${cy})`,textAnchor:"start",fontFamily:"sans-serif",fontSize:"10",fill:"#333333"},colLabels[origCi])})),showRowLabels&&React.createElement("g",{id:"row-labels","aria-label":`${nRows} row label${nRows!==1?"s":""}`},rowOrder.map((origRi,ri)=>React.createElement("text",{key:ri,x:MARGIN.left+plotW+LABEL_GAP,y:MARGIN.top+cellY(ri)+cellH/2+3,textAnchor:"start",fontFamily:"sans-serif",fontSize:"10",fill:"#333333"},rowLabels[origRi]))),React.createElement("g",{id:"colorbar","aria-label":`Colourbar: values range from ${vmin.toPrecision(3)} to ${vmax.toPrecision(3)}`},React.createElement("defs",null,React.createElement("linearGradient",{id:cbGradId,x1:"0%",y1:"100%",x2:"0%",y2:"0%"},cbStops)),React.createElement("rect",{x:cbX,y:cbY,width:CB_W,height:CB_H,fill:`url(#${cbGradId})`,stroke:"#888888",strokeWidth:"0.5"}),React.createElement("text",{x:cbX+CB_W+4,y:cbY+4,fontFamily:"sans-serif",fontSize:"9",fill:"#555555",textAnchor:"start",dominantBaseline:"hanging"},fmtColorbarTick(vmax)),React.createElement("text",{x:cbX+CB_W+4,y:cbY+CB_H/2+3,fontFamily:"sans-serif",fontSize:"9",fill:"#555555",textAnchor:"start"},fmtColorbarTick((vmin+vmax)/2)),React.createElement("text",{x:cbX+CB_W+4,y:cbY+CB_H,fontFamily:"sans-serif",fontSize:"9",fill:"#555555",textAnchor:"start"},fmtColorbarTick(vmin))),renderSelectionMask(selRowsDisplay,selColsDisplay),hoverRowsDisplay&&renderRowBands(hoverRowsDisplay,HIGHLIGHT_STROKE,.04),hoverColsDisplay&&renderColBands(hoverColsDisplay,HIGHLIGHT_STROKE,.04),brushRect&&React.createElement("rect",{x:brushRect.x,y:brushRect.y,width:brushRect.w,height:brushRect.h,fill:SELECTION_STROKE,fillOpacity:.12,stroke:SELECTION_STROKE,strokeWidth:1,strokeDasharray:"3 2",pointerEvents:"none"}),React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:plotW,height:plotH,fill:"none",pointerEvents:"all",style:{cursor:"crosshair"},onPointerDown:handlePointerDown,onPointerMove:handlePointerMove,onPointerUp:handlePointerUp,onPointerLeave:handlePointerLeave})),hoverInfo&&hover&&React.createElement("div",{style:{position:"absolute",left:hover.localX+12,top:hover.localY+12,background:"var(--surface)",color:"var(--text)",border:"1px solid var(--border)",borderRadius:4,padding:"4px 8px",fontSize:11,fontFamily:"sans-serif",pointerEvents:"none",boxShadow:"var(--shadow-md)",zIndex:10,whiteSpace:"nowrap"}},React.createElement("div",{style:{fontWeight:600}},hoverInfo.rowLabel," \xB7 ",hoverInfo.colLabel),React.createElement("div",{style:{color:"var(--text-muted)"}},"value: ",Number.isFinite(hoverInfo.vRaw)?hoverInfo.vRaw:"NaN",Number.isFinite(hoverInfo.vNorm)&&hoverInfo.vNorm!==hoverInfo.vRaw&&React.createElement(React.Fragment,null," \xB7 plotted: ",hoverInfo.vNorm.toFixed(3)))))}));var HEATMAP_HOWTO={toolName:"heatmap",title:"Heatmap \u2014 How to use",subtitle:"Numeric matrix with optional row / column clustering",purpose:React.createElement(React.Fragment,null,"Visualise a 2D numeric matrix (genes \xD7 samples, taxa \xD7 conditions, distance matrix). Reorder rows + columns by hierarchical or k-means clustering to surface structure."),dataLayout:React.createElement(React.Fragment,null,"Wide matrix \u2014 first column holds the row labels (genes / features), the header row holds the column labels (samples / conditions), the rest is a numeric grid. Missing values are tolerated."),display:React.createElement(React.Fragment,null,"Pick a ",React.createElement("strong",null,"palette")," (continuous viridis-family or diverging), optional"," ",React.createElement("strong",null,"z-score")," / ",React.createElement("strong",null,"log\u2082")," normalisation per row or column, and independent row / column clustering modes (",React.createElement("strong",null,"hierarchical")," with linkage + distance metric, or ",React.createElement("strong",null,"k-means")," with explicit k). Drag-select any region for a zoomed detail view.")};function UploadStep({sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:setSepOverride,onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Gene-expression matrix",subtitle:"500 genes \xD7 6 samples (3 Control \xB7 3 Stress) \xB7 clustered demo",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 first column = row labels, first row = column labels, rest numeric \xB7 2 MB max"}),React.createElement(HowTo,{...HEATMAP_HOWTO}))}function buildHeatmapRScript({rawMatrix,normalization,rowMode,colMode,rowK,colK,kmeansSeed,distanceMetric,linkageMethod,palette,invertPalette,vmin,vmax,plotTitle,cellBorder}){let{rowLabels,colLabels,matrix}=rawMatrix,rowNamesR=wrapRItems(rowLabels.map(l=>`"${sanitizeRString(l)}"`)),colNamesR=wrapRItems(colLabels.map(l=>`"${sanitizeRString(l)}"`)),flat=[];for(let r=0;r`"${c}"`).join(", ")+")",pheatmapDist=distanceMetric==="correlation"?"correlation":distanceMetric||"euclidean",lines=[];lines.push("# Pl\xF6ttr \u2014 Heatmap R script export"),lines.push("# Generated "+new Date().toISOString()),lines.push("#"),lines.push("# Reproduces the normalisation + hierarchical clustering"),lines.push("# you saw in the browser tool, using the {pheatmap} package."),lines.push("# To swap in your own data, replace the matrix block below with:"),lines.push('# mat <- as.matrix(read.csv("your_file.csv", row.names = 1,'),lines.push("# check.names = FALSE))"),lines.push(""),lines.push('if (!requireNamespace("pheatmap", quietly = TRUE))'),lines.push(' install.packages("pheatmap")'),lines.push("library(pheatmap)"),lines.push(""),lines.push("# \u2500\u2500 Raw data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push("row_labels <- "+rowNamesR),lines.push("col_labels <- "+colNamesR),lines.push("mat <- matrix("),lines.push(" "+dataLiteral+","),lines.push(" nrow = length(row_labels),"),lines.push(" byrow = TRUE,"),lines.push(" dimnames = list(row_labels, col_labels)"),lines.push(")"),lines.push(""),lines.push("# \u2500\u2500 Normalisation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push('# Toolbox setting: "'+normalization+'"'),normalization==="zrow"?(lines.push("# Z-score by row: (x - rowMean) / rowSD"),lines.push("mat <- t(scale(t(mat)))")):normalization==="zcol"?(lines.push("# Z-score by column: (x - colMean) / colSD"),lines.push("mat <- scale(mat)")):normalization==="log2"?(lines.push("# log2(x + 1) \u2014 gentle compression for right-skewed data"),lines.push("mat <- log2(mat + 1)")):lines.push("# No normalisation \u2014 plot raw values."),lines.push(""),lines.push("# \u2500\u2500 Clustering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");let rowUsesK=rowMode==="kmeans",colUsesK=colMode==="kmeans";lines.push("# Rows: "+rowMode+(rowUsesK?` (k = ${rowK})`:"")),lines.push("# Columns: "+colMode+(colUsesK?` (k = ${colK})`:"")),(rowUsesK||colUsesK)&&(lines.push("# K-means reorders the matrix by cluster id before plotting; pheatmap then"),lines.push("# renders the rows/columns as-is (cluster_rows = FALSE for the k-means axis)."),lines.push("set.seed("+(kmeansSeed||1)+")"),rowUsesK&&(lines.push("row_km <- kmeans(mat, centers = "+rowK+', nstart = 8, iter.max = 100, algorithm = "Hartigan-Wong")'),lines.push("row_order <- order(row_km$cluster)"),lines.push("mat <- mat[row_order, , drop = FALSE]"),lines.push("row_clusters <- row_km$cluster[row_order]")),colUsesK&&(lines.push("col_km <- kmeans(t(mat), centers = "+colK+', nstart = 8, iter.max = 100, algorithm = "Hartigan-Wong")'),lines.push("col_order <- order(col_km$cluster)"),lines.push("mat <- mat[, col_order, drop = FALSE]"),lines.push("col_clusters <- col_km$cluster[col_order]"))),(rowMode==="hierarchical"||colMode==="hierarchical")&&lines.push('# Hierarchical settings: distance = "'+(distanceMetric||"euclidean")+'", linkage = "'+(linkageMethod||"average")+'"'),lines.push(""),lines.push("# \u2500\u2500 Colour scale \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push("# Palette: "+palette+(DIVERGING_PALETTES.has(palette)?" (diverging)":" (sequential)")+(invertPalette?" \u2014 inverted":"")),lines.push("palette_stops <- "+stopsR),lines.push("heat_colors <- colorRampPalette(palette_stops)(100)"),lines.push("breaks <- seq("+formatRNumber(vmin)+", "+formatRNumber(vmax)+", length.out = 101)"),lines.push(""),lines.push("# \u2500\u2500 Plot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push("pheatmap("),lines.push(" mat,"),lines.push(" cluster_rows = "+(rowMode==="hierarchical"?"TRUE":"FALSE")+","),lines.push(" cluster_cols = "+(colMode==="hierarchical"?"TRUE":"FALSE")+","),(rowMode==="hierarchical"||colMode==="hierarchical")&&(lines.push(' clustering_distance_rows = "'+pheatmapDist+'",'),lines.push(' clustering_distance_cols = "'+pheatmapDist+'",'),lines.push(' clustering_method = "'+(linkageMethod||"average")+'",')),lines.push(" color = heat_colors,"),lines.push(" breaks = breaks,");let borderR=cellBorder&&cellBorder.on?`"${cellBorder.color}"`:"NA";return lines.push(" border_color = "+borderR+","),plotTitle&&lines.push(' main = "'+sanitizeRString(plotTitle)+'",'),rowUsesK&&(lines.push(" annotation_row = data.frame(cluster = factor(row_clusters),"),lines.push(" row.names = rownames(mat)),")),colUsesK&&(lines.push(" annotation_col = data.frame(cluster = factor(col_clusters),"),lines.push(" row.names = colnames(mat)),")),lines.push(" show_rownames = TRUE,"),lines.push(" show_colnames = TRUE"),lines.push(")"),lines.push(""),lines.join(` +import{hclust,kmeans,pairwiseDistance}from"./chunk-GD3FGCBE.js";import{ActionsPanel,COLOR_PALETTES,ColorInput,ControlSection,DIVERGING_PALETTES,DataPreview,DetectedSeparatorBadge,HowTo,NumberInput,PlotSidebar,PlotToolShell,UploadPanel,autoDetectSep,downloadCsv,downloadPng,downloadSvg,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatRNumber,interpolateColor,parseWideMatrix,registerSvgExportMutator,sanitizeRString,seededRandom,unregisterSvgExportMutator,usePlotToolState,wrapRItems}from"./chunk-HFTDMS7M.js";function finiteMean(arr){let s=0,n=0;for(let i=0;i1?Math.sqrt(s/(n-1)):0}function normalizeMatrix(matrix,mode){let nRows=matrix.length;if(nRows===0)return matrix;let nCols=matrix[0].length;if(mode==="zrow")return matrix.map(row=>{let m=finiteMean(row),sd=finiteSD(row,m);return!Number.isFinite(m)||sd===0?row.slice():row.map(v=>Number.isFinite(v)?(v-m)/sd:NaN)});if(mode==="zcol"){let out=matrix.map(r=>r.slice());for(let ci=0;cirow.map(v=>Number.isFinite(v)&&v>-1?Math.log2(v+1):NaN)):matrix}function autoRange(matrix,diverging){let lo=1/0,hi=-1/0;for(let i=0;ihi&&(hi=v))}if(!Number.isFinite(lo)||!Number.isFinite(hi))return[0,1];if(diverging){let m=Math.max(Math.abs(lo),Math.abs(hi));return[-m||0,m||1]}return lo===hi?[lo-.5,hi+.5]:[lo,hi]}var DENDRO_STROKE="#555555",DENDRO_HOVER_STROKE="#0072B2",NAN_FILL="#e0e0e0",SELECTION_STROKE="#111111",HIGHLIGHT_STROKE="#111111";function buildDendroLayout(tree){if(!tree)return{segments:[],nodes:[],maxHeight:0};let segments=[],nodes=[],leafIdx=0,maxHeight=0;function walk(node){if(!node.left&&!node.right){let pos=leafIdx++;return{x:pos,h:0,leaves:[node.index??-1],xMin:pos,xMax:pos}}let L=walk(node.left),R=walk(node.right),h=node.height;h>maxHeight&&(maxHeight=h);let leaves=L.leaves.concat(R.leaves),xMin=Math.min(L.xMin,R.xMin),xMax=Math.max(L.xMax,R.xMax);return segments.push({x1:L.x,y1:L.h,x2:L.x,y2:h,xMin,xMax}),segments.push({x1:R.x,y1:R.h,x2:R.x,y2:h,xMin,xMax}),segments.push({x1:L.x,y1:h,x2:R.x,y2:h,xMin,xMax}),nodes.push({height:h,leaves,xMin,xMax}),{x:(L.x+R.x)/2,h,leaves,xMin,xMax}}return walk(tree),{segments,nodes,maxHeight}}function pruneDendroTree(tree,keepSet){function rec(node){if(!node)return null;if(!node.left&&!node.right)return node.index!=null&&keepSet.has(node.index)?node:null;let L=rec(node.left),R=rec(node.right);return!L&&!R?null:L?R?{index:-1,left:L,right:R,height:node.height,size:(L.size||1)+(R.size||1)}:L:R}let pruned=rec(tree);return!pruned||!pruned.left&&!pruned.right?null:pruned}var CLUSTER_PALETTE=["#E69F00","#56B4E9","#009E73","#F0E442","#0072B2","#D55E00","#CC79A7","#999999","#88CCEE","#AA4499"];function fmtColorbarTick(v){if(!Number.isFinite(v))return"\u2014";if(v===0)return"0";let abs=Math.abs(v);return abs>=1e3||abs<.01?v.toExponential(1):abs<1?v.toFixed(2):v.toFixed(1)}var{useState,useMemo,useCallback,useRef,useEffect,forwardRef,memo}=React,CELL_RASTER_ONSCREEN_DPR_MULT=2,CELL_RASTER_ONSCREEN_MAX_DPR=6,CELL_RASTER_EXPORT_DPR_MULT=4,CELL_RASTER_EXPORT_MAX_DPR=8,CELL_VECTOR_EXPORT_LIMIT=2e3;function PaletteStrip({palette,invert=!1,height=12}){let base=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,stops=invert?[...base].reverse():base,n=48;return React.createElement("div",{style:{display:"flex",width:"100%",height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border)"}},Array.from({length:n},(_,i)=>React.createElement("div",{key:i,style:{flex:1,background:interpolateColor(stops,i/(n-1))}})))}var HeatmapChart=memo(forwardRef(function({rowLabels,colLabels,matrix,rowOrder,colOrder,rowCluster,colCluster,vmin,vmax,palette,invertPalette=!1,cellBorder,plotTitle,plotSubtitle,rowAxisLabel,colAxisLabel,showRowLabels=!0,showColLabels=!0,showRowDendrogram=!0,showColDendrogram=!0,interactive=!1,selection=null,onBrushEnd,onAxisSelect,onClearSelection,rawMatrix,baseCellW,baseCellH,baseColorbarH,baseDendroSizeLeft,baseDendroSizeTop,baseColLabelH,baseRowLabelW,cellOffsetCols=0,cellOffsetRows=0,basePlotW,basePlotH,colGapStartPx=0,rowGapStartPx=0,showClusterStrip=!0,showKmeansStrip=void 0,dendrogramStrokeWidth=1},ref){let nRows=rowOrder.length,nCols=colOrder.length,paletteBase=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,stops=invertPalette?[...paletteBase].reverse():paletteBase,longestRowLabel=Math.max(0,...rowLabels.map(l=>(l||"").length)),longestColLabel=Math.max(0,...colLabels.map(l=>(l||"").length)),cellW=baseCellW??Math.max(2,Math.min(28,Math.floor(720/Math.max(1,nCols)))),cellH=baseCellH??Math.max(2,Math.min(28,Math.floor(480/Math.max(1,nRows)))),rowIsHier=rowCluster&&rowCluster.mode==="hierarchical"&&rowCluster.tree,rowIsKmeans=rowCluster&&rowCluster.mode==="kmeans",colIsHier=colCluster&&colCluster.mode==="hierarchical"&&colCluster.tree,colIsKmeans=colCluster&&colCluster.mode==="kmeans",K_GAP=10,colGapOffsets=useMemo(()=>{let arr=new Array(nCols).fill(0);if(colIsKmeans)for(let ci=1;ci{let arr=new Array(nRows).fill(0);if(rowIsKmeans)for(let ri=1;ri0?colGapOffsets[nCols-1]:0,totalRowGap=nRows>0?rowGapOffsets[nRows-1]:0,plotW=basePlotW??cellW*nCols+totalColGap,plotH=basePlotH??cellH*nRows+totalRowGap,computedDendroTop=colIsHier&&showColDendrogram?60:colIsKmeans?14:0,computedDendroLeft=rowIsHier&&showRowDendrogram?60:rowIsKmeans?14:0,DENDRO_SIZE_TOP=baseDendroSizeTop??computedDendroTop,DENDRO_SIZE_LEFT=baseDendroSizeLeft??computedDendroLeft,effShowKmeans=showKmeansStrip??showClusterStrip,CLUSTER_LABEL_H=colIsKmeans&&effShowKmeans?24:0,CLUSTER_LABEL_W=rowIsKmeans&&effShowKmeans?92:0,LABEL_GAP=6,ROW_LABEL_W=baseRowLabelW??(showRowLabels?Math.min(160,longestRowLabel*6+12):0),COL_LABEL_H=baseColLabelH??(showColLabels?Math.min(120,Math.round(longestColLabel*5.5)+16):0),TITLE_H=plotTitle?plotSubtitle?42:26:0,AXIS_LABEL_TOP=colAxisLabel?16:0,AXIS_LABEL_LEFT=rowAxisLabel?16:0,CB_W=12,CB_OFFSET=16,CB_TICK_W=44,CB_H=baseColorbarH??Math.min(180,Math.max(60,Math.round(plotH*.6))),MARGIN={top:TITLE_H+10+AXIS_LABEL_TOP+CLUSTER_LABEL_H+COL_LABEL_H+LABEL_GAP+DENDRO_SIZE_TOP,left:AXIS_LABEL_LEFT+CLUSTER_LABEL_W+DENDRO_SIZE_LEFT+LABEL_GAP,right:ROW_LABEL_W+CB_OFFSET+CB_W+CB_TICK_W+10,bottom:12},vbW=MARGIN.left+plotW+MARGIN.right,vbH=MARGIN.top+Math.max(plotH,CB_H+14)+MARGIN.bottom,bordersOn=cellBorder&&cellBorder.on,colIx=ci=>ci+cellOffsetCols,rowIx=ri=>ri+cellOffsetRows,colGap=ci=>colGapOffsets[ci]||0,rowGap=ri=>rowGapOffsets[ri]||0,cellX=ci=>(bordersOn?colIx(ci)*cellW+colGap(ci):Math.round(colIx(ci)*cellW)+colGap(ci))+colGapStartPx,cellY=ri=>(bordersOn?rowIx(ri)*cellH+rowGap(ri):Math.round(rowIx(ri)*cellH)+rowGap(ri))+rowGapStartPx,cellWPx=ci=>bordersOn?cellW:Math.round((colIx(ci)+1)*cellW)-Math.round(colIx(ci)*cellW),cellHPx=ri=>bordersOn?cellH:Math.round((rowIx(ri)+1)*cellH)-Math.round(rowIx(ri)*cellH),valueToColor=v=>{if(!Number.isFinite(v))return NAN_FILL;let t=(v-vmin)/(vmax-vmin||1);return interpolateColor(stops,Math.max(0,Math.min(1,t)))},[hover,setHover]=useState(null),[brush,setBrush]=useState(null),[axisHover,setAxisHover]=useState(null),svgLocalRef=useRef(null),wrapperRef=useRef(null),setRefs=useCallback(node=>{svgLocalRef.current=node,typeof ref=="function"?ref(node):ref&&(ref.current=node)},[ref]);function svgPoint(e){let svg=svgLocalRef.current;if(!svg||!svg.createSVGPoint)return null;let pt=svg.createSVGPoint();pt.x=e.clientX,pt.y=e.clientY;let ctm=svg.getScreenCTM();return ctm?pt.matrixTransform(ctm.inverse()):null}function pointToCell(p){if(!p)return null;let localX=p.x-MARGIN.left,localY=p.y-MARGIN.top;if(localX<0||localY<0||localX>plotW||localY>plotH)return null;let ci=-1;for(let c=0;c=x&&localX=y&&localY{if(!selection||!selection.rows)return null;let keep=new Set(selection.rows),ris=[];for(let ri=0;ri{if(!selection||!selection.cols)return null;let keep=new Set(selection.cols),cis=[];for(let ci=0;ci{if(!axisHover||axisHover.axis!=="row")return null;let keep=axisHover.leaves,ris=[];for(let ri=0;ri{if(!axisHover||axisHover.axis!=="col")return null;let keep=axisHover.leaves,cis=[];for(let ci=0;ciReact.createElement("stop",{key:i,offset:(i/(stops.length-1)*100).toFixed(2)+"%",stopColor:c}));function segmentInHover(seg,axis){return!axisHover||axisHover.axis!==axis||axisHover.xMin==null||axisHover.xMax==null?!1:seg.xMin>=axisHover.xMin&&seg.xMax<=axisHover.xMax}function renderColDendrogram(){if(!colIsHier)return null;let{segments,nodes,maxHeight}=buildDendroLayout(colCluster.tree);if(maxHeight===0||segments.length===0)return null;let yBase=MARGIN.top-LABEL_GAP-COL_LABEL_H-LABEL_GAP,yTop=yBase-DENDRO_SIZE_TOP+4,scaleY=h=>yBase-h/maxHeight*(yBase-yTop),scaleX=x=>MARGIN.left+x*cellW+cellW/2;return React.createElement("g",{id:"col-dendrogram"},React.createElement("g",{fill:"none",strokeLinecap:"round"},segments.map((s,i)=>{let active=segmentInHover(s,"col");return React.createElement("line",{key:i,x1:scaleX(s.x1),y1:scaleY(s.y1),x2:scaleX(s.x2),y2:scaleY(s.y2),stroke:active?DENDRO_HOVER_STROKE:DENDRO_STROKE,strokeWidth:active?2.5*dendrogramStrokeWidth:dendrogramStrokeWidth})})),interactive&&React.createElement("g",{id:"col-dendrogram-hits",onMouseLeave:()=>setAxisHover(null)},nodes.map((n,i)=>{let x=scaleX(n.xMin)-cellW/2,w=scaleX(n.xMax)-scaleX(n.xMin)+cellW,y=scaleY(n.height)-4;return React.createElement("rect",{key:i,x,y,width:w,height:8,fill:"none",pointerEvents:"all",style:{cursor:"pointer"},onMouseEnter:()=>setAxisHover({axis:"col",leaves:new Set(n.leaves),xMin:n.xMin,xMax:n.xMax}),onClick:e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("col",n.leaves)}})})))}function renderRowDendrogram(){if(!rowIsHier)return null;let{segments,nodes,maxHeight}=buildDendroLayout(rowCluster.tree);if(maxHeight===0||segments.length===0)return null;let xRight=MARGIN.left-LABEL_GAP,xLeft=xRight-DENDRO_SIZE_LEFT+4,scaleX=h=>xRight-h/maxHeight*(xRight-xLeft),scaleY=x=>MARGIN.top+x*cellH+cellH/2;return React.createElement("g",{id:"row-dendrogram"},React.createElement("g",{fill:"none",strokeLinecap:"round"},segments.map((s,i)=>{let active=segmentInHover(s,"row");return React.createElement("line",{key:i,x1:scaleX(s.y1),y1:scaleY(s.x1),x2:scaleX(s.y2),y2:scaleY(s.x2),stroke:active?DENDRO_HOVER_STROKE:DENDRO_STROKE,strokeWidth:active?2.5*dendrogramStrokeWidth:dendrogramStrokeWidth})})),interactive&&React.createElement("g",{id:"row-dendrogram-hits",onMouseLeave:()=>setAxisHover(null)},nodes.map((n,i)=>{let y=scaleY(n.xMin)-cellH/2,h=scaleY(n.xMax)-scaleY(n.xMin)+cellH,x=scaleX(n.height)-4;return React.createElement("rect",{key:i,x,y,width:8,height:h,fill:"none",pointerEvents:"all",style:{cursor:"pointer"},onMouseEnter:()=>setAxisHover({axis:"row",leaves:new Set(n.leaves),xMin:n.xMin,xMax:n.xMax}),onClick:e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("row",n.leaves)}})})))}function clusterColor(id){return CLUSTER_PALETTE[(id%CLUSTER_PALETTE.length+CLUSTER_PALETTE.length)%CLUSTER_PALETTE.length]}function renderColClusterStrip(){if(!colIsKmeans)return null;let y=MARGIN.top-LABEL_GAP-COL_LABEL_H-LABEL_GAP-DENDRO_SIZE_TOP+2,leavesByCluster=interactive?new Map:null;if(leavesByCluster)for(let c=0;csetAxisHover(null):void 0},colOrder.map((origCi,ci)=>{let cid=colCluster.clusters[origCi];return React.createElement("rect",{key:ci,x:MARGIN.left+cellX(ci),y,width:cellWPx(ci),height:DENDRO_SIZE_TOP-4,fill:clusterColor(cid),stroke:"none",shapeRendering:"crispEdges",style:interactive?{cursor:"pointer"}:void 0,onMouseEnter:interactive?()=>setAxisHover({axis:"col",leaves:new Set(leavesByCluster.get(cid))}):void 0,onClick:interactive?e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("col",leavesByCluster.get(cid)??null,{clusterId:cid})}:void 0})}),CLUSTER_LABEL_H>0&&bands.map(b=>{let x0=MARGIN.left+cellX(b.start),x1=MARGIN.left+cellX(b.end-1)+cellWPx(b.end-1),cx=(x0+x1)/2,cy=y-CLUSTER_LABEL_H/2-4;return React.createElement("text",{key:`lbl-${b.id}-${b.start}`,x:cx,y:cy,textAnchor:"middle",dominantBaseline:"middle",fontSize:10,fill:"#111111",stroke:"#ffffff",strokeWidth:3,paintOrder:"stroke",style:{pointerEvents:"none"}},`Cluster n\xB0 ${b.id+1}`)}))}function renderRowClusterStrip(){if(!rowIsKmeans)return null;let x=MARGIN.left-LABEL_GAP-DENDRO_SIZE_LEFT+2,leavesByCluster=interactive?new Map:null;if(leavesByCluster)for(let r=0;rsetAxisHover(null):void 0},rowOrder.map((origRi,ri)=>{let cid=rowCluster.clusters[origRi];return React.createElement("rect",{key:ri,x,y:MARGIN.top+cellY(ri),width:DENDRO_SIZE_LEFT-4,height:cellHPx(ri),fill:clusterColor(cid),stroke:"none",shapeRendering:"crispEdges",style:interactive?{cursor:"pointer"}:void 0,onMouseEnter:interactive?()=>setAxisHover({axis:"row",leaves:new Set(leavesByCluster.get(cid))}):void 0,onClick:interactive?e=>{e.stopPropagation(),onAxisSelect&&onAxisSelect("row",leavesByCluster.get(cid)??null,{clusterId:cid})}:void 0})}),CLUSTER_LABEL_W>0&&bands.map(b=>{let y0=MARGIN.top+cellY(b.start),y1=MARGIN.top+cellY(b.end-1)+cellHPx(b.end-1),cy=(y0+y1)/2,cx=x-CLUSTER_LABEL_W/2-4;return React.createElement("text",{key:`lbl-${b.id}-${b.start}`,x:cx,y:cy,textAnchor:"middle",dominantBaseline:"middle",fontSize:10,fill:"#111111",stroke:"#ffffff",strokeWidth:3,paintOrder:"stroke",style:{pointerEvents:"none"}},`Cluster n\xB0 ${b.id+1}`)}))}let hoverInfo=hover&&rowOrder[hover.ri]!=null&&colOrder[hover.ci]!=null?(()=>{let origRi=rowOrder[hover.ri],origCi=colOrder[hover.ci],vNorm=matrix[origRi]&&matrix[origRi][origCi],vRaw=rawMatrix&&rawMatrix[origRi]&&rawMatrix[origRi][origCi];return{rowLabel:rowLabels[origRi],colLabel:colLabels[origCi],vNorm,vRaw}})():null,brushRect=brush?(()=>{let riMin=Math.min(brush.anchorRi,brush.curRi),riMax=Math.max(brush.anchorRi,brush.curRi),ciMin=Math.min(brush.anchorCi,brush.curCi),ciMax=Math.max(brush.anchorCi,brush.curCi);return{x:MARGIN.left+cellX(ciMin),y:MARGIN.top+cellY(riMin),w:cellX(ciMax)+cellWPx(ciMax)-cellX(ciMin),h:cellY(riMax)+cellHPx(riMax)-cellY(riMin)}})():null;function renderRowBands(ris,stroke,fillOpacity){return!ris||ris.length===0?null:ris.map(ri=>React.createElement("rect",{key:`rb-${ri}`,x:MARGIN.left+cellX(0),y:MARGIN.top+cellY(ri),width:cellX(nCols-1)+cellWPx(nCols-1)-cellX(0),height:cellHPx(ri),fill:stroke,fillOpacity,stroke:"none",pointerEvents:"none"}))}function renderColBands(cis,stroke,fillOpacity){return!cis||cis.length===0?null:cis.map(ci=>React.createElement("rect",{key:`cb-${ci}`,x:MARGIN.left+cellX(ci),y:MARGIN.top+cellY(0),width:cellWPx(ci),height:cellY(nRows-1)+cellHPx(nRows-1)-cellY(0),fill:stroke,fillOpacity,stroke:"none",pointerEvents:"none"}))}function renderSelectionMask(selRis,selCis){if((!selRis||selRis.length===0)&&(!selCis||selCis.length===0))return null;let rowSel=selRis&&selRis.length?new Set(selRis):null,colSel=selCis&&selCis.length?new Set(selCis):null,rects=[],MASK_FILL="#ffffff",MASK_OPACITY=.6,fullW=cellX(nCols-1)+cellWPx(nCols-1)-cellX(0);if(rowSel)for(let ri=0;ri{if(plotW<=0||plotH<=0)return"";let d=Math.max(1,density);canvas.width=Math.max(1,Math.round(plotW*d)),canvas.height=Math.max(1,Math.round(plotH*d));let ctx=canvas.getContext("2d");if(!ctx)return"";ctx.setTransform(d,0,0,d,0,0),ctx.clearRect(0,0,plotW,plotH);for(let ri=0;ri{let canvas=cellCanvasRef.current;if(!canvas)return"";let baseDpr=typeof window<"u"&&window.devicePixelRatio||1,density=Math.min(CELL_RASTER_ONSCREEN_MAX_DPR,baseDpr*CELL_RASTER_ONSCREEN_DPR_MULT);return paintCellsToCanvas(canvas,density)},[rowOrder,colOrder,matrix,bordersOn,cellBorder,vmin,vmax,palette,invertPalette,cellW,cellH,cellOffsetCols,cellOffsetRows,colGapStartPx,rowGapStartPx,colGapOffsets,rowGapOffsets,plotW,plotH,nRows,nCols]),exportSnapshotRef=useRef(null);exportSnapshotRef.current={nRows,nCols,bordersOn,cellBorder,rowOrder,colOrder,matrix,cellX,cellY,cellWPx,cellHPx,valueToColor,paint:paintCellsToCanvas},useEffect(()=>{let el=svgLocalRef.current;if(el)return registerSvgExportMutator(el,clone=>{let snap=exportSnapshotRef.current;if(!snap)return;let cellsGroup=clone.querySelector("#cells");if(!cellsGroup)return;let doc=clone.ownerDocument;if(!doc)return;let NS="http://www.w3.org/2000/svg";if(snap.nRows*snap.nCols<=CELL_VECTOR_EXPORT_LIMIT){cellsGroup.querySelectorAll(":scope > image").forEach(node=>node.parentNode&&node.parentNode.removeChild(node));for(let ri=0;ri image");if(!image)return;let exportCanvas=doc.createElement("canvas"),baseDpr=typeof window<"u"&&window.devicePixelRatio||1,density=Math.min(CELL_RASTER_EXPORT_MAX_DPR,baseDpr*CELL_RASTER_EXPORT_DPR_MULT),href=snap.paint(exportCanvas,density);href&&image.setAttribute("href",href)}}),()=>unregisterSvgExportMutator(el)},[]);let hasClustering=rowIsHier||rowIsKmeans||colIsHier||colIsKmeans;return React.createElement("div",{ref:wrapperRef,style:{position:"relative"}},React.createElement("svg",{ref:setRefs,xmlns:"http://www.w3.org/2000/svg",viewBox:`0 0 ${vbW} ${vbH}`,width:vbW,height:vbH,style:{maxWidth:"100%",height:"auto",display:"block"},role:"img","aria-label":plotTitle||"Heatmap"},React.createElement("title",null,plotTitle||"Heatmap"),React.createElement("desc",null,`Heatmap of ${nRows} row${nRows!==1?"s":""} \xD7 ${nCols} column${nCols!==1?"s":""}${hasClustering?", with clustering":""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:vbW,height:vbH,fill:"#ffffff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:vbW/2,y:18,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"14",fontWeight:"700",fill:"#222222"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:vbW/2,y:plotTitle?34:18,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"11",fill:"#555555"},plotSubtitle)),colAxisLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+plotW/2,y:TITLE_H+22,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"11",fontWeight:"600",fill:"#333333"},colAxisLabel)),rowAxisLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`rotate(-90) translate(${-(MARGIN.top+plotH/2)}, 12)`,textAnchor:"middle",fontFamily:"sans-serif",fontSize:"11",fontWeight:"600",fill:"#333333"},rowAxisLabel)),showClusterStrip&&showColDendrogram&&renderColDendrogram(),showClusterStrip&&showRowDendrogram&&renderRowDendrogram(),(showKmeansStrip??showClusterStrip)&&renderColClusterStrip(),(showKmeansStrip??showClusterStrip)&&renderRowClusterStrip(),React.createElement("g",{id:"chart",transform:`translate(${MARGIN.left}, ${MARGIN.top})`},React.createElement("g",{id:"plot-area-background"},React.createElement("rect",{x:0,y:0,width:plotW,height:plotH,fill:"#ffffff"})),React.createElement("g",{id:"cells","aria-label":`Matrix of ${nRows} row${nRows!==1?"s":""} \xD7 ${nCols} column${nCols!==1?"s":""}, values from ${vmin.toPrecision(3)} to ${vmax.toPrecision(3)}`},cellsImageHref&&React.createElement("image",{x:0,y:0,width:plotW,height:plotH,href:cellsImageHref,preserveAspectRatio:"none","aria-hidden":"true"}))),showColLabels&&React.createElement("g",{id:"col-labels","aria-label":`${nCols} column label${nCols!==1?"s":""}`},colOrder.map((origCi,ci)=>{let cx=MARGIN.left+cellX(ci)+cellW/2,cy=MARGIN.top-LABEL_GAP;return React.createElement("text",{key:ci,x:cx,y:cy,transform:`rotate(-45 ${cx} ${cy})`,textAnchor:"start",fontFamily:"sans-serif",fontSize:"10",fill:"#333333"},colLabels[origCi])})),showRowLabels&&React.createElement("g",{id:"row-labels","aria-label":`${nRows} row label${nRows!==1?"s":""}`},rowOrder.map((origRi,ri)=>React.createElement("text",{key:ri,x:MARGIN.left+plotW+LABEL_GAP,y:MARGIN.top+cellY(ri)+cellH/2+3,textAnchor:"start",fontFamily:"sans-serif",fontSize:"10",fill:"#333333"},rowLabels[origRi]))),React.createElement("g",{id:"colorbar","aria-label":`Colourbar: values range from ${vmin.toPrecision(3)} to ${vmax.toPrecision(3)}`},React.createElement("defs",null,React.createElement("linearGradient",{id:cbGradId,x1:"0%",y1:"100%",x2:"0%",y2:"0%"},cbStops)),React.createElement("rect",{x:cbX,y:cbY,width:CB_W,height:CB_H,fill:`url(#${cbGradId})`,stroke:"#888888",strokeWidth:"0.5"}),React.createElement("text",{x:cbX+CB_W+4,y:cbY+4,fontFamily:"sans-serif",fontSize:"9",fill:"#555555",textAnchor:"start",dominantBaseline:"hanging"},fmtColorbarTick(vmax)),React.createElement("text",{x:cbX+CB_W+4,y:cbY+CB_H/2+3,fontFamily:"sans-serif",fontSize:"9",fill:"#555555",textAnchor:"start"},fmtColorbarTick((vmin+vmax)/2)),React.createElement("text",{x:cbX+CB_W+4,y:cbY+CB_H,fontFamily:"sans-serif",fontSize:"9",fill:"#555555",textAnchor:"start"},fmtColorbarTick(vmin))),renderSelectionMask(selRowsDisplay,selColsDisplay),hoverRowsDisplay&&renderRowBands(hoverRowsDisplay,HIGHLIGHT_STROKE,.04),hoverColsDisplay&&renderColBands(hoverColsDisplay,HIGHLIGHT_STROKE,.04),brushRect&&React.createElement("rect",{x:brushRect.x,y:brushRect.y,width:brushRect.w,height:brushRect.h,fill:SELECTION_STROKE,fillOpacity:.12,stroke:SELECTION_STROKE,strokeWidth:1,strokeDasharray:"3 2",pointerEvents:"none"}),React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:plotW,height:plotH,fill:"none",pointerEvents:"all",style:{cursor:"crosshair"},onPointerDown:handlePointerDown,onPointerMove:handlePointerMove,onPointerUp:handlePointerUp,onPointerLeave:handlePointerLeave})),hoverInfo&&hover&&React.createElement("div",{style:{position:"absolute",left:hover.localX+12,top:hover.localY+12,background:"var(--surface)",color:"var(--text)",border:"1px solid var(--border)",borderRadius:4,padding:"4px 8px",fontSize:11,fontFamily:"sans-serif",pointerEvents:"none",boxShadow:"var(--shadow-md)",zIndex:10,whiteSpace:"nowrap"}},React.createElement("div",{style:{fontWeight:600}},hoverInfo.rowLabel," \xB7 ",hoverInfo.colLabel),React.createElement("div",{style:{color:"var(--text-muted)"}},"value: ",Number.isFinite(hoverInfo.vRaw)?hoverInfo.vRaw:"NaN",Number.isFinite(hoverInfo.vNorm)&&hoverInfo.vNorm!==hoverInfo.vRaw&&React.createElement(React.Fragment,null," \xB7 plotted: ",hoverInfo.vNorm.toFixed(3)))))}));var HEATMAP_HOWTO={toolName:"heatmap",title:"Heatmap \u2014 How to use",subtitle:"Numeric matrix with optional row / column clustering",purpose:React.createElement(React.Fragment,null,"Visualise a 2D numeric matrix (genes \xD7 samples, taxa \xD7 conditions, distance matrix). Reorder rows + columns by hierarchical or k-means clustering to surface structure."),dataLayout:React.createElement(React.Fragment,null,"Wide matrix \u2014 first column holds the row labels (genes / features), the header row holds the column labels (samples / conditions), the rest is a numeric grid. Missing values are tolerated."),display:React.createElement(React.Fragment,null,"Pick a ",React.createElement("strong",null,"palette")," (continuous viridis-family or diverging), optional"," ",React.createElement("strong",null,"z-score")," / ",React.createElement("strong",null,"log\u2082")," normalisation per row or column, and independent row / column clustering modes (",React.createElement("strong",null,"hierarchical")," with linkage + distance metric, or ",React.createElement("strong",null,"k-means")," with explicit k). Drag-select any region for a zoomed detail view.")};function UploadStep({sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:setSepOverride,onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Gene-expression matrix",subtitle:"500 genes \xD7 6 samples (3 Control \xB7 3 Stress) \xB7 clustered demo",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 first column = row labels, first row = column labels, rest numeric \xB7 2 MB max"}),React.createElement(HowTo,{...HEATMAP_HOWTO}))}function buildHeatmapRScript({rawMatrix,normalization,rowMode,colMode,rowK,colK,kmeansSeed,distanceMetric,linkageMethod,palette,invertPalette,vmin,vmax,plotTitle,cellBorder}){let{rowLabels,colLabels,matrix}=rawMatrix,rowNamesR=wrapRItems(rowLabels.map(l=>`"${sanitizeRString(l)}"`)),colNamesR=wrapRItems(colLabels.map(l=>`"${sanitizeRString(l)}"`)),flat=[];for(let r=0;r`"${c}"`).join(", ")+")",pheatmapDist=distanceMetric==="correlation"?"correlation":distanceMetric||"euclidean",lines=[];lines.push("# Pl\xF6ttr \u2014 Heatmap R script export"),lines.push("# Generated "+new Date().toISOString()),lines.push("#"),lines.push("# Reproduces the normalisation + hierarchical clustering"),lines.push("# you saw in the browser tool, using the {pheatmap} package."),lines.push("# To swap in your own data, replace the matrix block below with:"),lines.push('# mat <- as.matrix(read.csv("your_file.csv", row.names = 1,'),lines.push("# check.names = FALSE))"),lines.push(""),lines.push('if (!requireNamespace("pheatmap", quietly = TRUE))'),lines.push(' install.packages("pheatmap")'),lines.push("library(pheatmap)"),lines.push(""),lines.push("# \u2500\u2500 Raw data \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push("row_labels <- "+rowNamesR),lines.push("col_labels <- "+colNamesR),lines.push("mat <- matrix("),lines.push(" "+dataLiteral+","),lines.push(" nrow = length(row_labels),"),lines.push(" byrow = TRUE,"),lines.push(" dimnames = list(row_labels, col_labels)"),lines.push(")"),lines.push(""),lines.push("# \u2500\u2500 Normalisation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push('# Toolbox setting: "'+normalization+'"'),normalization==="zrow"?(lines.push("# Z-score by row: (x - rowMean) / rowSD"),lines.push("mat <- t(scale(t(mat)))")):normalization==="zcol"?(lines.push("# Z-score by column: (x - colMean) / colSD"),lines.push("mat <- scale(mat)")):normalization==="log2"?(lines.push("# log2(x + 1) \u2014 gentle compression for right-skewed data"),lines.push("mat <- log2(mat + 1)")):lines.push("# No normalisation \u2014 plot raw values."),lines.push(""),lines.push("# \u2500\u2500 Clustering \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");let rowUsesK=rowMode==="kmeans",colUsesK=colMode==="kmeans";lines.push("# Rows: "+rowMode+(rowUsesK?` (k = ${rowK})`:"")),lines.push("# Columns: "+colMode+(colUsesK?` (k = ${colK})`:"")),(rowUsesK||colUsesK)&&(lines.push("# K-means reorders the matrix by cluster id before plotting; pheatmap then"),lines.push("# renders the rows/columns as-is (cluster_rows = FALSE for the k-means axis)."),lines.push("set.seed("+(kmeansSeed||1)+")"),rowUsesK&&(lines.push("row_km <- kmeans(mat, centers = "+rowK+', nstart = 8, iter.max = 100, algorithm = "Hartigan-Wong")'),lines.push("row_order <- order(row_km$cluster)"),lines.push("mat <- mat[row_order, , drop = FALSE]"),lines.push("row_clusters <- row_km$cluster[row_order]")),colUsesK&&(lines.push("col_km <- kmeans(t(mat), centers = "+colK+', nstart = 8, iter.max = 100, algorithm = "Hartigan-Wong")'),lines.push("col_order <- order(col_km$cluster)"),lines.push("mat <- mat[, col_order, drop = FALSE]"),lines.push("col_clusters <- col_km$cluster[col_order]"))),(rowMode==="hierarchical"||colMode==="hierarchical")&&lines.push('# Hierarchical settings: distance = "'+(distanceMetric||"euclidean")+'", linkage = "'+(linkageMethod||"average")+'"'),lines.push(""),lines.push("# \u2500\u2500 Colour scale \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push("# Palette: "+palette+(DIVERGING_PALETTES.has(palette)?" (diverging)":" (sequential)")+(invertPalette?" \u2014 inverted":"")),lines.push("palette_stops <- "+stopsR),lines.push("heat_colors <- colorRampPalette(palette_stops)(100)"),lines.push("breaks <- seq("+formatRNumber(vmin)+", "+formatRNumber(vmax)+", length.out = 101)"),lines.push(""),lines.push("# \u2500\u2500 Plot \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"),lines.push("pheatmap("),lines.push(" mat,"),lines.push(" cluster_rows = "+(rowMode==="hierarchical"?"TRUE":"FALSE")+","),lines.push(" cluster_cols = "+(colMode==="hierarchical"?"TRUE":"FALSE")+","),(rowMode==="hierarchical"||colMode==="hierarchical")&&(lines.push(' clustering_distance_rows = "'+pheatmapDist+'",'),lines.push(' clustering_distance_cols = "'+pheatmapDist+'",'),lines.push(' clustering_method = "'+(linkageMethod||"average")+'",')),lines.push(" color = heat_colors,"),lines.push(" breaks = breaks,");let borderR=cellBorder&&cellBorder.on?`"${cellBorder.color}"`:"NA";return lines.push(" border_color = "+borderR+","),plotTitle&&lines.push(' main = "'+sanitizeRString(plotTitle)+'",'),rowUsesK&&(lines.push(" annotation_row = data.frame(cluster = factor(row_clusters),"),lines.push(" row.names = rownames(mat)),")),colUsesK&&(lines.push(" annotation_col = data.frame(cluster = factor(col_clusters),"),lines.push(" row.names = colnames(mat)),")),lines.push(" show_rownames = TRUE,"),lines.push(" show_colnames = TRUE"),lines.push(")"),lines.push(""),lines.join(` `)}function buildCsvExport({rowLabels,colLabels,matrix,rowOrder,colOrder,rowClusterIds,colClusterIds}){let hasRowClusters=Array.isArray(rowClusterIds),hasColClusters=Array.isArray(colClusterIds),headers=[""].concat(colOrder.map(i=>colLabels[i])).concat(hasRowClusters?["cluster"]:[]),rows=rowOrder.map(ri=>{let cells=colOrder.map(ci=>{let v=matrix[ri][ci];return Number.isFinite(v)?String(v):""}),base=[rowLabels[ri]].concat(cells);if(hasRowClusters&&rowClusterIds){let cid=rowClusterIds[ri];base.push(cid!=null?String(cid+1):"")}return base});if(hasColClusters&&colClusterIds){let clusterRow=["cluster"].concat(colOrder.map(ci=>colClusterIds[ci]!=null?String(colClusterIds[ci]+1):"")).concat(hasRowClusters?[""]:[]);rows.unshift(clusterRow)}return{headers,rows}}function ClusterModeControl({label,mode,setMode,k,setK}){let OPTIONS=[{k:"none",label:"None"},{k:"hierarchical",label:"Hier."},{k:"kmeans",label:"K-means"}];return React.createElement("div",{style:{marginBottom:8}},React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",marginBottom:4}},label),React.createElement("div",{className:"dv-seg",role:"group","aria-label":`${label} clustering mode`},OPTIONS.map(o=>React.createElement("button",{key:o.k,type:"button","aria-pressed":mode===o.k,className:"dv-seg-btn"+(mode===o.k?" dv-seg-btn-active":""),onClick:()=>setMode(o.k)},o.label))),mode==="kmeans"&&React.createElement("div",{style:{fontSize:11,marginTop:6}},React.createElement("div",{style:{marginBottom:2}},"k"),React.createElement(NumberInput,{value:k,step:"1",min:"2",max:"20",onChange:e=>{let v=parseInt(e.target.value,10);setK(Math.max(2,Math.min(20,Number.isFinite(v)?v:3)))},style:{width:"100%"}})))}function PlotControls({vis,updVis,cellBorder,updCellBorder,matrixRef,rawMatrix,resetAll,fileName,normalization,setNormalization,rowMode,setRowMode,colMode,setColMode,rowK,setRowK,colK,setColK,kmeansSeed,setKmeansSeed,distanceMetric,setDistanceMetric,linkageMethod,setLinkageMethod,autoVRange}){let paletteKeys=Object.keys(COLOR_PALETTES),anyHier=rowMode==="hierarchical"||colMode==="hierarchical",anyKmeans=rowMode==="kmeans"||colMode==="kmeans",baseName=fileBaseName(fileName,"heatmap"),NORM_OPTIONS=[{k:"none",label:"None"},{k:"zrow",label:"Z row"},{k:"zcol",label:"Z col"},{k:"log2",label:"log\u2082"}],DIST_OPTIONS=[{k:"euclidean",label:"Euclidean"},{k:"manhattan",label:"Manhattan"},{k:"correlation",label:"1 \u2212 r"}],LINK_OPTIONS=[{k:"average",label:"Average"},{k:"complete",label:"Complete"},{k:"single",label:"Single"}];return React.createElement(PlotSidebar,{sticky:!1,width:280},React.createElement(ActionsPanel,{onReset:resetAll,extraDownloads:[{label:"CSV",title:"Download the plotted matrix as CSV \u2014 normalisation and row / column reordering applied",onClick:()=>{if(!matrixRef.current)return;let{headers,rows}=buildCsvExport(matrixRef.current);downloadCsv(headers,rows,`${baseName}_heatmap.csv`)}},{label:"R script",title:"Download a runnable R script that reproduces this plot with pheatmap (includes the raw matrix, clustering, normalisation, palette)",onClick:()=>{if(!rawMatrix||!rawMatrix.rowLabels.length)return;let script=buildHeatmapRScript({rawMatrix,normalization,rowMode,colMode,rowK,colK,kmeansSeed,distanceMetric,linkageMethod,palette:vis.palette,invertPalette:vis.invertPalette,vmin:vis.vmin,vmax:vis.vmax,plotTitle:vis.plotTitle,cellBorder});downloadText(script,`${baseName}_heatmap.R`)}}]}),React.createElement(ControlSection,{title:"Normalisation"},React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Normalisation"},NORM_OPTIONS.map(o=>React.createElement("button",{key:o.k,type:"button","aria-pressed":normalization===o.k,className:"dv-seg-btn"+(normalization===o.k?" dv-seg-btn-active":""),onClick:()=>setNormalization(o.k)},o.label)))),React.createElement(ControlSection,{title:"Clustering",defaultOpen:!0},React.createElement(ClusterModeControl,{label:"Rows",mode:rowMode,setMode:setRowMode,k:rowK,setK:setRowK}),React.createElement(ClusterModeControl,{label:"Columns",mode:colMode,setMode:setColMode,k:colK,setK:setColK}),anyHier&&React.createElement("div",{style:{marginTop:10}},React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",marginBottom:4}},"Hierarchical \xB7 Distance"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Distance metric"},DIST_OPTIONS.map(o=>React.createElement("button",{key:o.k,type:"button","aria-pressed":distanceMetric===o.k,className:"dv-seg-btn"+(distanceMetric===o.k?" dv-seg-btn-active":""),onClick:()=>setDistanceMetric(o.k)},o.label))),React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",margin:"8px 0 4px"}},"Hierarchical \xB7 Linkage"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Linkage method"},LINK_OPTIONS.map(o=>React.createElement("button",{key:o.k,type:"button","aria-pressed":linkageMethod===o.k,className:"dv-seg-btn"+(linkageMethod===o.k?" dv-seg-btn-active":""),onClick:()=>setLinkageMethod(o.k)},o.label))),rowMode==="hierarchical"&&React.createElement(React.Fragment,null,React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",margin:"8px 0 4px"}},"Hierarchical \xB7 Row dendrogram"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Show row dendrogram"},[[!1,"Off"],[!0,"On"]].map(([value,label])=>{let active=!!vis.showRowDendrogram===value;return React.createElement("button",{key:String(value),type:"button","aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),onClick:()=>updVis({showRowDendrogram:value})},label)}))),colMode==="hierarchical"&&React.createElement(React.Fragment,null,React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",margin:"8px 0 4px"}},"Hierarchical \xB7 Column dendrogram"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Show column dendrogram"},[[!1,"Off"],[!0,"On"]].map(([value,label])=>{let active=!!vis.showColDendrogram===value;return React.createElement("button",{key:String(value),type:"button","aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),onClick:()=>updVis({showColDendrogram:value})},label)}))),React.createElement("div",{style:{fontSize:10,color:"var(--text-muted)",marginTop:4}},"Leaf order + cluster structure stay applied when hidden. Drag on the heatmap to open a zoomed selection if you still need per-cluster exports. Applies to both the main and zoomed plots.")),anyKmeans&&React.createElement("div",{style:{marginTop:10}},React.createElement("div",{style:{fontSize:11,marginBottom:2}},"K-means \xB7 Seed"),React.createElement(NumberInput,{value:kmeansSeed,step:"1",min:"1",onChange:e=>setKmeansSeed(Math.max(1,parseInt(e.target.value,10)||1)),style:{width:"100%"}}),React.createElement("div",{style:{fontSize:10,color:"var(--text-muted)",marginTop:4}},"Change the seed to try a different k-means++ initialisation."))),React.createElement(ControlSection,{title:"Colour scale"},React.createElement("label",{style:{fontSize:11,display:"block",marginBottom:6}},"Palette",React.createElement("select",{value:vis.palette,onChange:e=>updVis({palette:e.target.value}),style:{width:"100%",fontSize:11,margin:"2px 0 6px"}},paletteKeys.map(p=>React.createElement("option",{key:p,value:p},p,DIVERGING_PALETTES.has(p)?" (diverging)":""))),React.createElement(PaletteStrip,{palette:vis.palette,invert:vis.invertPalette})),React.createElement("div",{style:{display:"flex",gap:6,alignItems:"center",marginBottom:6}},React.createElement("span",{className:"dv-label",style:{fontSize:11,flexShrink:0}},"Direction"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Palette direction"},React.createElement("button",{type:"button","aria-pressed":!vis.invertPalette,className:"dv-seg-btn"+(vis.invertPalette?"":" dv-seg-btn-active"),onClick:()=>updVis({invertPalette:!1}),style:{fontSize:11,padding:"3px 8px"}},"Normal"),React.createElement("button",{type:"button","aria-pressed":vis.invertPalette,className:"dv-seg-btn"+(vis.invertPalette?" dv-seg-btn-active":""),onClick:()=>updVis({invertPalette:!0}),style:{fontSize:11,padding:"3px 8px"}},"Inverted"))),React.createElement("div",{style:{display:"flex",gap:6,alignItems:"flex-end",marginBottom:6}},React.createElement("label",{style:{fontSize:11,flex:1,display:"block"}},React.createElement("span",{className:"dv-label"},"Min"),React.createElement(NumberInput,{value:vis.vmin,step:"0.1",onChange:e=>updVis({vmin:parseFloat(e.target.value)}),style:{width:"100%"}})),React.createElement("label",{style:{fontSize:11,flex:1,display:"block"}},React.createElement("span",{className:"dv-label"},"Max"),React.createElement(NumberInput,{value:vis.vmax,step:"0.1",onChange:e=>updVis({vmax:parseFloat(e.target.value)}),style:{width:"100%"}}))),React.createElement("button",{onClick:autoVRange,className:"dv-btn dv-btn-secondary",style:{fontSize:11}},"Auto from data")),React.createElement(ControlSection,{title:"Cell borders"},React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Cell borders"},React.createElement("button",{type:"button","aria-pressed":!cellBorder.on,className:"dv-seg-btn"+(cellBorder.on?"":" dv-seg-btn-active"),onClick:()=>updCellBorder({on:!1})},"Off"),React.createElement("button",{type:"button","aria-pressed":cellBorder.on,className:"dv-seg-btn"+(cellBorder.on?" dv-seg-btn-active":""),onClick:()=>updCellBorder({on:!0})},"On")),cellBorder.on&&React.createElement("div",{style:{marginTop:8,display:"flex",alignItems:"center",gap:8}},React.createElement(ColorInput,{value:cellBorder.color,onChange:c=>updCellBorder({color:c}),size:18}),React.createElement("label",{style:{fontSize:11,flex:1}},"Width",React.createElement("input",{type:"number",value:cellBorder.width,min:"0.25",step:"0.25",max:"2",onChange:e=>updCellBorder({width:parseFloat(e.target.value)||.5}),style:{width:"100%",fontSize:11,marginTop:2}})))),React.createElement(ControlSection,{title:"Labels"},React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Title"),React.createElement("input",{type:"text",value:vis.plotTitle,onChange:e=>updVis({plotTitle:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Subtitle"),React.createElement("input",{type:"text",value:vis.plotSubtitle,onChange:e=>updVis({plotSubtitle:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"X-axis label"),React.createElement("input",{type:"text",value:vis.colAxisLabel,onChange:e=>updVis({colAxisLabel:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Y-axis label"),React.createElement("input",{type:"text",value:vis.rowAxisLabel,onChange:e=>updVis({rowAxisLabel:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement("div",{style:{marginTop:8}},React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",marginBottom:4}},"Row names"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Show row names"},React.createElement("button",{type:"button","aria-pressed":!vis.showRowLabels,className:"dv-seg-btn"+(vis.showRowLabels?"":" dv-seg-btn-active"),onClick:()=>updVis({showRowLabels:!1})},"Off"),React.createElement("button",{type:"button","aria-pressed":vis.showRowLabels,className:"dv-seg-btn"+(vis.showRowLabels?" dv-seg-btn-active":""),onClick:()=>updVis({showRowLabels:!0})},"On"))),React.createElement("div",{style:{marginTop:8}},React.createElement("div",{style:{fontSize:11,color:"var(--text-muted)",marginBottom:4}},"Column names"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Show column names"},React.createElement("button",{type:"button","aria-pressed":!vis.showColLabels,className:"dv-seg-btn"+(vis.showColLabels?"":" dv-seg-btn-active"),onClick:()=>updVis({showColLabels:!1})},"Off"),React.createElement("button",{type:"button","aria-pressed":vis.showColLabels,className:"dv-seg-btn"+(vis.showColLabels?" dv-seg-btn-active":""),onClick:()=>updVis({showColLabels:!0})},"On")))))}var{useMemo:useMemo2,useRef:useRef2}=React,DETAIL_DENDRO_STROKE_WIDTH=1.5;function DetailView({rawMatrix,normalized,detailRowOrder,detailColOrder,mainRowCluster,mainColCluster,vis,cellBorder,detailChartRef,fileName,clusterId}){let detailRowCount=detailRowOrder.length,baseCellH=Math.max(14,Math.min(28,Math.floor(720/Math.max(1,detailRowCount)))),base=fileBaseName(fileName||"heatmap")||"heatmap",mainRowIsKmeans=mainRowCluster&&mainRowCluster.mode==="kmeans",mainColIsKmeans=mainColCluster&&mainColCluster.mode==="kmeans",mainRowIsHier=mainRowCluster&&mainRowCluster.mode==="hierarchical"&&mainRowCluster.tree,mainColIsHier=mainColCluster&&mainColCluster.mode==="hierarchical"&&mainColCluster.tree,detailRowCluster=useMemo2(()=>{if(mainRowIsKmeans)return mainRowCluster;if(mainRowIsHier&&mainRowCluster&&mainRowCluster.mode==="hierarchical"){let pruned=pruneDendroTree(mainRowCluster.tree,new Set(detailRowOrder));return pruned?{mode:"hierarchical",tree:pruned,order:detailRowOrder}:null}return null},[mainRowCluster,mainRowIsKmeans,mainRowIsHier,detailRowOrder]),detailColCluster=useMemo2(()=>{if(mainColIsKmeans)return mainColCluster;if(mainColIsHier&&mainColCluster&&mainColCluster.mode==="hierarchical"){let pruned=pruneDendroTree(mainColCluster.tree,new Set(detailColOrder));return pruned?{mode:"hierarchical",tree:pruned,order:detailColOrder}:null}return null},[mainColCluster,mainColIsKmeans,mainColIsHier,detailColOrder]),detailRowDendroVisible=mainRowIsHier&&detailRowCluster&&vis.showRowDendrogram!==!1,detailColDendroVisible=mainColIsHier&&detailColCluster&&vis.showColDendrogram!==!1,detailShowDendrogram=detailRowDendroVisible||detailColDendroVisible,clusterSuffix=clusterId!=null?`_cluster${clusterId+1}`:"",downloadButton=(label,onClick)=>React.createElement("button",{onClick:e=>{onClick(e),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{padding:"4px 10px",fontSize:11}},"\u2B07 ",label);return React.createElement("div",{className:"dv-panel dv-plot-card",style:{padding:20,background:"var(--plot-card-bg)",borderColor:"var(--plot-card-border)",display:"flex",flexDirection:"column",gap:12,flex:1,minWidth:0}},React.createElement("div",{style:{display:"flex",gap:6,flexWrap:"wrap",justifyContent:"flex-end"}},downloadButton("SVG",()=>downloadSvg(detailChartRef.current,`${base}_heatmap${clusterSuffix}_detail.svg`)),downloadButton("PNG",()=>downloadPng(detailChartRef.current,`${base}_heatmap${clusterSuffix}_detail.png`,2))),React.createElement("div",{style:{display:"flex",justifyContent:"center",alignItems:"flex-start"}},React.createElement(HeatmapChart,{ref:detailChartRef,rowLabels:rawMatrix.rowLabels,colLabels:rawMatrix.colLabels,matrix:normalized,rawMatrix:rawMatrix.matrix,rowOrder:detailRowOrder,colOrder:detailColOrder,rowCluster:detailRowCluster,colCluster:detailColCluster,showClusterStrip:!!detailShowDendrogram,showRowDendrogram:vis.showRowDendrogram!==!1,showColDendrogram:vis.showColDendrogram!==!1,showKmeansStrip:!!(mainRowIsKmeans||mainColIsKmeans),dendrogramStrokeWidth:DETAIL_DENDRO_STROKE_WIDTH,vmin:vis.vmin,vmax:vis.vmax,palette:vis.palette,invertPalette:vis.invertPalette,cellBorder,plotTitle:vis.plotTitle?`${vis.plotTitle} \u2014 detail`:void 0,plotSubtitle:vis.plotSubtitle,rowAxisLabel:vis.rowAxisLabel,colAxisLabel:vis.colAxisLabel,showRowLabels:vis.showRowLabels,showColLabels:vis.showColLabels,interactive:!1,baseCellH})))}function DetailPreviewCard({rawMatrix,normalized,detailRowOrder,detailColOrder,fileName,clusterId}){let base=fileBaseName(fileName||"heatmap")||"heatmap",clusterSuffix=clusterId!=null?`_cluster${clusterId+1}`:"",nR=detailRowOrder.length,nC=detailColOrder.length,tableHeaders=[""].concat(detailColOrder.map(ci=>rawMatrix.colLabels[ci])),tableRows=detailRowOrder.map(ri=>{let cells=detailColOrder.map(ci=>{let v=rawMatrix.matrix[ri][ci];return Number.isFinite(v)?String(v):""});return[rawMatrix.rowLabels[ri]].concat(cells)}),detailMatrixRef=useRef2(null);detailMatrixRef.current={rowLabels:rawMatrix.rowLabels,colLabels:rawMatrix.colLabels,matrix:normalized,rowOrder:detailRowOrder,colOrder:detailColOrder};let csvOpts={...detailMatrixRef.current,rowClusterIds:null,colClusterIds:null};return React.createElement("div",{className:"dv-panel dv-plot-card",style:{padding:16,background:"var(--plot-card-bg)",borderColor:"var(--plot-card-border)",display:"flex",flexDirection:"column",gap:12}},React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:12,flexWrap:"wrap"}},React.createElement("div",{style:{fontSize:12,fontWeight:600,color:"var(--text)"}},"Selection data \u2014 ",nR," row",nR===1?"":"s"," \xD7 ",nC," col",nC===1?"":"s"),React.createElement("button",{onClick:e=>{if(!detailMatrixRef.current)return;let{headers,rows}=buildCsvExport(csvOpts);downloadCsv(headers,rows,`${base}_heatmap${clusterSuffix}_detail.csv`),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{padding:"4px 10px",fontSize:11,marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),React.createElement(DataPreview,{headers:tableHeaders,rows:tableRows,maxRows:50}),nR>50&&React.createElement("p",{style:{margin:0,fontSize:11,color:"var(--text-faint)"}},"Showing first 50 of ",nR," rows. Download CSV for the full selection."))}var{useState:useState2,useReducer,useMemo:useMemo3,useCallback:useCallback2,useRef:useRef3}=React,VIS_INIT_HEATMAP={palette:"viridis",invertPalette:!1,vmin:0,vmax:1,plotTitle:"",plotSubtitle:"",colAxisLabel:"",rowAxisLabel:"",showRowLabels:!1,showColLabels:!0,showRowDendrogram:!0,showColDendrogram:!0},EXAMPLE_CSV=(()=>{let rand=seededRandom(42),header="gene,Control1,Control2,Control3,Stress1,Stress2,Stress3",patterns=[{label:"up-strong",ctrl:[.6,1.4],stress:[3.8,5.6],noise:.35},{label:"up-moderate",ctrl:[.8,1.6],stress:[2.2,3.4],noise:.3},{label:"up-mild",ctrl:[.7,1.5],stress:[1.8,2.6],noise:.25},{label:"down",ctrl:[3.2,4.8],stress:[.6,1.4],noise:.35},{label:"flat",ctrl:[.8,2.4],stress:[.8,2.4],noise:.3}],perPattern=100,lines=[header],geneIdx=0;for(let p of patterns)for(let i=0;i"_reset"in a&&a._reset?{...cellBorderInit}:{...s,...a},cellBorderInit),chartRef=useRef3(null),detailChartRef=useRef3(null),matrixRef=useRef3(null),[selection,setSelection]=useState2({rows:null,cols:null,clusterId:null,clusterAxis:null}),selectBox=useCallback2((rows,cols)=>{setSelection({rows:rows&&rows.length?rows:null,cols:cols&&cols.length?cols:null,clusterId:null,clusterAxis:null})},[]),selectAxis=useCallback2((axis,indices,meta)=>{let valid=indices&&indices.length?indices:null;setSelection({rows:axis==="row"?valid:null,cols:axis==="col"?valid:null,clusterId:meta&&meta.clusterId!=null?meta.clusterId:null,clusterAxis:meta&&meta.clusterId!=null?axis:null})},[]),clearSelection=useCallback2(()=>setSelection({rows:null,cols:null,clusterId:null,clusterAxis:null}),[]),normalized=useMemo3(()=>normalizeMatrix(rawMatrix.matrix,normalization),[rawMatrix,normalization]),rowCluster=useMemo3(()=>{if(rowMode==="hierarchical"){if(normalized.length<2)return null;let D=pairwiseDistance(normalized,distanceMetric),h=hclust(D,linkageMethod);return h.tree?{mode:"hierarchical",tree:h.tree,order:h.order}:null}if(rowMode==="kmeans"){if(normalized.length<2)return null;let k=Math.max(2,Math.min(rowK,normalized.length)),res=kmeans(normalized,k,{seed:kmeansSeed});return{mode:"kmeans",clusters:res.clusters,order:res.order,k}}return null},[rowMode,normalized,distanceMetric,linkageMethod,rowK,kmeansSeed]),colCluster=useMemo3(()=>{if(normalized.length<1||normalized[0].length<2)return null;let nRows2=normalized.length,nCols2=normalized[0].length,T=Array.from({length:nCols2},(_,c)=>{let row=new Array(nRows2);for(let r=0;rrowCluster?rowCluster.order:rawMatrix.rowLabels.map((_,i)=>i),[rowCluster,rawMatrix.rowLabels]),colOrder=useMemo3(()=>colCluster?colCluster.order:rawMatrix.colLabels.map((_,i)=>i),[colCluster,rawMatrix.colLabels]),detailRowOrder=useMemo3(()=>{if(!selection.rows)return rowOrder;let keep=new Set(selection.rows);return rowOrder.filter(i=>keep.has(i))},[rowOrder,selection.rows]),detailColOrder=useMemo3(()=>{if(!selection.cols)return colOrder;let keep=new Set(selection.cols);return colOrder.filter(i=>keep.has(i))},[colOrder,selection.cols]),hasSelection=selection.rows!==null||selection.cols!==null,onBrushEnd=useCallback2(({riMin,riMax,ciMin,ciMax})=>{let rows=[];for(let ri=riMin;ri<=riMax;ri++)rowOrder[ri]!=null&&rows.push(rowOrder[ri]);let cols=[];for(let ci=ciMin;ci<=ciMax;ci++)colOrder[ci]!=null&&cols.push(colOrder[ci]);selectBox(rows,cols)},[rowOrder,colOrder,selectBox]);React.useEffect(()=>{clearSelection()},[rowMode,colMode,rowK,colK,kmeansSeed,distanceMetric,linkageMethod,normalization,clearSelection]),React.useEffect(()=>{matrixRef.current={rowLabels:rawMatrix.rowLabels,colLabels:rawMatrix.colLabels,matrix:normalized,rowOrder,colOrder,rowClusterIds:rowCluster&&rowCluster.mode==="kmeans"?rowCluster.clusters:null,colClusterIds:colCluster&&colCluster.mode==="kmeans"?colCluster.clusters:null}},[rawMatrix,normalized,rowOrder,colOrder,rowCluster,colCluster]);let autoVRange=useCallback2(()=>{let diverging=DIVERGING_PALETTES.has(vis.palette)||normalization.startsWith("z"),[lo,hi]=autoRange(normalized,diverging);updVis({vmin:Number(lo.toFixed(3)),vmax:Number(hi.toFixed(3))})},[normalized,vis.palette,normalization,updVis]),prevNormRef=useRef3(normalization);React.useEffect(()=>{prevNormRef.current!==normalization&&(prevNormRef.current=normalization,autoVRange())},[normalization,autoVRange]);let paletteIsDiverging=DIVERGING_PALETTES.has(vis.palette),prevPaletteDivergingRef=useRef3(paletteIsDiverging);React.useEffect(()=>{prevPaletteDivergingRef.current!==paletteIsDiverging&&(prevPaletteDivergingRef.current=paletteIsDiverging,autoVRange())},[paletteIsDiverging,autoVRange]);let canNavigate=useCallback2(target=>target==="upload"?!0:target==="configure"||target==="plot"?rawMatrix.rowLabels.length>0:!1,[rawMatrix]),doParse=useCallback2((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count);let parsed=parseWideMatrix(dc.text,effectiveSep);if(setInjectionWarning(parsed.injectionWarnings),!parsed.rowLabels.length||!parsed.colLabels.length){setParseError("The file needs at least one row label column and one data column with a header.");return}setParseError(null),setRawMatrix({rowLabels:parsed.rowLabels,colLabels:parsed.colLabels,matrix:parsed.matrix}),setWarnings(parsed.warnings);let diverging=DIVERGING_PALETTES.has("viridis"),[lo,hi]=autoRange(parsed.matrix,diverging);updVis({vmin:Number(lo.toFixed(3)),vmax:Number(hi.toFixed(3))}),setStep("configure")},[setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep,updVis]),handleFileLoad=useCallback2((text,name)=>{setFileName(name),doParse(text,sepOverride)},[sepOverride,doParse,setFileName]),handleTextPaste=useCallback2((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]),loadExample=useCallback2(()=>{setSepOverride(""),setFileName("stress_response_genes.csv"),doParse(EXAMPLE_CSV,"")},[doParse,setFileName,setSepOverride]),resetAll=()=>{setStep("upload"),setFileName(""),setRawMatrix({rowLabels:[],colLabels:[],matrix:[]}),setWarnings({nonNumeric:0}),setNormalization("none"),setRowMode("hierarchical"),setColMode("hierarchical"),setRowK(3),setColK(3),setKmeansSeed(1),setDistanceMetric("euclidean"),setLinkageMethod("average"),setParseError(null),setInjectionWarning(null),updVis({_reset:!0}),updCellBorder({_reset:!0})},nRows=rawMatrix.rowLabels.length,nCols=rawMatrix.colLabels.length,oversize=nRows>500||nCols>500;return React.createElement(PlotToolShell,{state:shell,toolName:"heatmap",title:"Heatmap",visInit:VIS_INIT_HEATMAP,steps:["upload","configure","plot"],stepLabels:{configure:"Import check"},canNavigate},step==="upload"&&React.createElement(UploadStep,{sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample:loadExample}),step==="configure"&&nRows>0&&React.createElement("div",null,React.createElement("p",{style:{margin:"0 0 10px",fontSize:12,color:"var(--text-muted)"}},React.createElement("strong",null,fileName||"Pasted data")," \u2014 parsed ",nRows," rows \xD7 ",nCols," columns",React.createElement(DetectedSeparatorBadge,{sep:detectedSep}),warnings.nonNumeric>0&&React.createElement(React.Fragment,null," ","\xB7"," ",React.createElement("span",{style:{color:"var(--warning-text)"}},warnings.nonNumeric," non-numeric cell",warnings.nonNumeric>1?"s":""," ","rendered as NaN")),oversize&&React.createElement(React.Fragment,null," ","\xB7"," ",React.createElement("span",{style:{color:"var(--warning-text)"}},"matrix is large \u2014 clustering may take a few seconds"))),React.createElement(DataPreview,{headers:[""].concat(rawMatrix.colLabels),rows:rawMatrix.matrix.map((row,ri)=>[rawMatrix.rowLabels[ri]].concat(row.map(v=>Number.isFinite(v)?String(v):"")))})),step==="plot"&&nRows>0&&React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotControls,{vis,updVis,cellBorder,updCellBorder,matrixRef,rawMatrix,resetAll,fileName,normalization,setNormalization,rowMode,setRowMode,colMode,setColMode,rowK,setRowK,colK,setColK,kmeansSeed,setKmeansSeed,distanceMetric,setDistanceMetric,linkageMethod,setLinkageMethod,autoVRange}),React.createElement("div",{style:{flex:1,minWidth:0,display:"flex",flexDirection:"column",gap:12}},React.createElement("div",{style:{display:"flex",gap:12,alignItems:"flex-start",flexWrap:"wrap"}},React.createElement("div",{className:"dv-panel dv-plot-card",style:{padding:20,background:"var(--plot-card-bg)",borderColor:"var(--plot-card-border)",display:"flex",flexDirection:"column",gap:12,alignItems:"center",flex:1,minWidth:0}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",alignSelf:"stretch",gap:12,flexWrap:"wrap"}},React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"}},hasSelection?React.createElement("button",{onClick:clearSelection,className:"dv-btn dv-btn-secondary",style:{padding:"4px 10px",fontSize:11}},"Clear"):React.createElement("span",{style:{fontSize:11,color:"var(--text-muted)",fontStyle:"italic"}},"\u21B3 Drag on the heatmap or click a dendrogram / k-means band to open a zoomed view")),React.createElement("div",{style:{display:"flex",gap:6,flexWrap:"wrap",marginLeft:"auto",flexShrink:0}},React.createElement("button",{onClick:e=>{let bn=fileBaseName(fileName||"heatmap")||"heatmap";downloadSvg(chartRef.current,`${bn}_heatmap.svg`),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{padding:"4px 10px",fontSize:11}},"\u2B07 SVG"),React.createElement("button",{onClick:e=>{let bn=fileBaseName(fileName||"heatmap")||"heatmap";downloadPng(chartRef.current,`${bn}_heatmap.png`,2),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{padding:"4px 10px",fontSize:11}},"\u2B07 PNG"))),React.createElement(HeatmapChart,{ref:chartRef,rowLabels:rawMatrix.rowLabels,colLabels:rawMatrix.colLabels,matrix:normalized,rawMatrix:rawMatrix.matrix,rowOrder,colOrder,rowCluster,colCluster,vmin:vis.vmin,vmax:vis.vmax,palette:vis.palette,invertPalette:vis.invertPalette,cellBorder,plotTitle:vis.plotTitle,plotSubtitle:vis.plotSubtitle,rowAxisLabel:vis.rowAxisLabel,colAxisLabel:vis.colAxisLabel,showRowLabels:vis.showRowLabels,showColLabels:vis.showColLabels,showRowDendrogram:vis.showRowDendrogram,showColDendrogram:vis.showColDendrogram,interactive:!0,selection,onBrushEnd,onAxisSelect:selectAxis,onClearSelection:clearSelection})),hasSelection&&React.createElement(DetailView,{rawMatrix,normalized,detailRowOrder,detailColOrder,mainRowCluster:rowCluster,mainColCluster:colCluster,vis,cellBorder,detailChartRef,fileName,clusterId:selection.clusterId})),hasSelection&&React.createElement(DetailPreviewCard,{rawMatrix,normalized,detailRowOrder,detailColOrder,fileName,clusterId:selection.clusterId})))))}export{App}; -//# sourceMappingURL=app-7CWQ47AS.js.map +//# sourceMappingURL=app-IRS66EWH.js.map diff --git a/tools/_app/chunks/app-7CWQ47AS.js.map b/tools/_app/chunks/app-IRS66EWH.js.map similarity index 100% rename from tools/_app/chunks/app-7CWQ47AS.js.map rename to tools/_app/chunks/app-IRS66EWH.js.map diff --git a/tools/_app/chunks/app-G5L2IE5E.js b/tools/_app/chunks/app-P3YNIKGG.js similarity index 99% rename from tools/_app/chunks/app-G5L2IE5E.js rename to tools/_app/chunks/app-P3YNIKGG.js index 0ca41c9e..e7855267 100644 --- a/tools/_app/chunks/app-G5L2IE5E.js +++ b/tools/_app/chunks/app-P3YNIKGG.js @@ -1,4 +1,4 @@ -import{multisetExclusiveExpected,multisetExclusiveP}from"./chunk-J6277AEU.js";import{niceStep}from"./chunk-T4V5V5TK.js";import{ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DownloadTiles,FILE_LIMIT_BYTES,HowTo,NumberInput,OnOffToggle,PlotSidebar,PlotToolShell,ScrollablePlotCard,SliderControl,UploadPanel,autoDetectSep,bhAdjust,detectLongFormat,downloadCsv,fileBaseName,fixDecimalCommas,formatP,pStars,parseLongFormatSets,parseRaw,parseSetData,svgSafeId,usePlotToolState}from"./chunk-5F6S3FYW.js";function computeMemberships(setNames,sets){let membershipMap=new Map;return setNames.forEach((name,i)=>{let s=sets.get(name);if(s)for(let item of s){let prev=membershipMap.get(item)||0;membershipMap.set(item,prev|1<a.mask-b.mask,copy=list.slice();switch(mode){case"size-asc":return copy.sort((a,b)=>a.size-b.size||byMaskAsc(a,b));case"degree-asc":return copy.sort((a,b)=>a.degree-b.degree||b.size-a.size||byMaskAsc(a,b));case"degree-desc":return copy.sort((a,b)=>b.degree-a.degree||b.size-a.size||byMaskAsc(a,b));case"sets":return copy.sort((a,b)=>{let la=a.setIndices,lb=b.setIndices,n=Math.min(la.length,lb.length);for(let i=0;ib.size-a.size||byMaskAsc(a,b))}}function truncateIntersections(list,{minSize=1,minDegree=1,maxDegree=1/0}={}){return list.filter(r=>r.size>=minSize&&r.degree>=minDegree&&r.degree<=maxDegree)}function intersectionLabel(setIndices,setNames){return setIndices.map(i=>setNames[i]).join(" \u2229 ")}function intersectionShortLabel(setIndices){return setIndices.map(i=>`S${i+1}`).join(" \u2229 ")}function intersectionFilenamePart(label){return label.replace(/∩/g,"and").replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_]/g,"")}function intersectionIdKey(setIndices,setNames){return setIndices.map(i=>svgSafeId(setNames[i])).join("-")||"empty"}function buildBarTicks(max,count){if(!(max>0))return[0,1];let step=niceStep(max,count),domainMax=Math.ceil((max+step*1e-9)/step)*step,last=domainMax>max?domainMax:domainMax+step,ticks=[];for(let v=0;v<=last+step*.001;v+=step)ticks.push(parseFloat(v.toPrecision(10)));return ticks}var{forwardRef,memo}=React,PREFERRED_SVG_W=960,MIN_COL_W=18,MAX_COL_W=36,TITLE_H_WITH=40,TITLE_H_NONE=16,SUBTITLE_H=18,TOP_PANEL_H=200,MATRIX_TOP_PAD=8,BOTTOM_H=62,LEFT_MARGIN=44,LEFT_BAR_MAX=110,LEFT_LABEL_AREA_MIN=82,LEFT_GAP=6,RIGHT_MARGIN=20,BAR_FILL="#000000",DOT_FILL="#000000",EMPTY_DOT="#DDDDDD",ZEBRA_FILL="#F4F4F4",TEXT_DARK="#333333",TEXT_MUTED="#555555",BAR_FILL_ENRICHED="#2ca25f",BAR_FILL_DEPLETED="#a50f15";function computeRowHeight(nSets){return Math.max(22,Math.min(40,Math.round(140/Math.max(1,nSets)+14)))}function computeColWidth(nCols,matrixLeftX){if(nCols<=0)return 24;let avail=PREFERRED_SVG_W-matrixLeftX-RIGHT_MARGIN;return Math.max(MIN_COL_W,Math.min(MAX_COL_W,avail/nCols))}var UpsetChart=memo(forwardRef(function({setNames,setSizes,intersections,selectedMask,onColumnClick,plotTitle,plotSubtitle,plotBg,fontSize,barOpacity,dotSize,showIntersectionLabels,showSetSizeLabels,significanceDisplay,significanceByMask,colorBarsBySignificance},ref){let nSets=setNames.length,nCols=intersections.length,fSize=fontSize||12,barOp=barOpacity??1,dotR=dotSize||6,labelFontSize=Math.max(10,fSize-1),estLabelW=Math.max(0,...setNames.map(n=>String(n).length*labelFontSize*.58)),leftLabelArea=Math.max(LEFT_LABEL_AREA_MIN,Math.ceil(estLabelW)+6),setIdFontSize=Math.max(8,Math.min(10,fSize-4)),maxSetIdChars=1+String(Math.max(1,nSets)).length,setIdLaneW=Math.ceil(maxSetIdChars*setIdFontSize*.6)+6,setIdLaneX=LEFT_MARGIN+LEFT_BAR_MAX+LEFT_GAP+leftLabelArea,matrixLeftX=setIdLaneX+setIdLaneW,rowH=computeRowHeight(nSets),colW=computeColWidth(nCols,matrixLeftX),matrixH=nSets*rowH,titleH=plotTitle?TITLE_H_WITH:TITLE_H_NONE,topPanelY=titleH+(plotSubtitle?SUBTITLE_H:0),matrixY=topPanelY+TOP_PANEL_H+MATRIX_TOP_PAD,idFontSize=Math.max(8,Math.min(10,fSize-4)),maxIdChars=1+String(Math.max(1,nCols)).length,idLabelSpan=Math.ceil(maxIdChars*idFontSize*.58),legendFS=Math.max(9,fSize-3),idLaneOffset=10,legendOffset=idLaneOffset+idLabelSpan+8,bottomNeeded=legendOffset+legendFS+8,VH=matrixY+matrixH+Math.max(BOTTOM_H,bottomNeeded),SVG_W=Math.max(PREFERRED_SVG_W,matrixLeftX+Math.max(0,nCols)*colW+RIGHT_MARGIN),topPanelBottom=topPanelY+TOP_PANEL_H,topAxisMax=Math.max(1,...intersections.map(r=>r.size)),topTicks=buildBarTicks(topAxisMax,4),topDomainMax=topTicks[topTicks.length-1],sizeLabelFS=Math.max(9,fSize-3),sizeLabelShown=showIntersectionLabels!==!1,maxSizeChars=String(topAxisMax).length,maxSizeLabelHeight=sizeLabelShown?Math.ceil(maxSizeChars*sizeLabelFS*.58):0,maxSigLabelHeight=0;significanceDisplay==="stars"?maxSigLabelHeight=Math.ceil(4*Math.max(10,fSize-1)*.58):significanceDisplay==="p-value"&&(maxSigLabelHeight=Math.ceil(9*Math.max(9,fSize-3)*.58));let hasAnyLabel=maxSizeLabelHeight>0||maxSigLabelHeight>0,gapBetweenLabels=maxSizeLabelHeight>0&&maxSigLabelHeight>0?4:0,labelHeadroom=maxSizeLabelHeight+maxSigLabelHeight+gapBetweenLabels+(hasAnyLabel?6:0),barAreaHeight=Math.max(40,TOP_PANEL_H-labelHeadroom),topBarScale=v=>v/topDomainMax*barAreaHeight,barAreaTop=topPanelBottom-barAreaHeight,setSizeMax=Math.max(1,...setNames.map(n=>setSizes.get(n)||0)),leftTicks=buildBarTicks(setSizeMax,3),leftDomainMax=leftTicks[leftTicks.length-1],leftBarScale=v=>v/leftDomainMax*LEFT_BAR_MAX,colX=i=>matrixLeftX+colW*(i+.5),rowY=i=>matrixY+rowH*(i+.5),topAxisX=matrixLeftX-4;return React.createElement("svg",{ref,viewBox:`0 0 ${SVG_W} ${VH}`,width:SVG_W,height:VH,preserveAspectRatio:"xMidYMid meet",style:{display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"UpSet plot"},React.createElement("title",null,plotTitle||"UpSet plot"),React.createElement("desc",null,`UpSet plot with ${nSets} sets and ${nCols} intersections`),React.createElement("g",{id:"background"},React.createElement("rect",{width:SVG_W,height:VH,fill:plotBg||"#ffffff",rx:"8"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:SVG_W/2,y:24,textAnchor:"middle",fontSize:Math.max(14,fSize+4),fontWeight:"700",fill:TEXT_DARK,fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:SVG_W/2,y:titleH+12,textAnchor:"middle",fontSize:Math.max(11,fSize),fill:TEXT_MUTED,fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"axis-intersection-size"},React.createElement("line",{x1:topAxisX,x2:topAxisX,y1:barAreaTop,y2:topPanelBottom,stroke:TEXT_DARK,strokeWidth:"1"}),topTicks.map((t,i)=>{let y=topPanelBottom-topBarScale(t);return React.createElement("g",{key:`ta-${i}`},React.createElement("line",{x1:topAxisX-3,x2:topAxisX,y1:y,y2:y,stroke:TEXT_DARK,strokeWidth:"1"}),React.createElement("text",{x:topAxisX-6,y,textAnchor:"end",dominantBaseline:"central",fontSize:Math.max(9,fSize-3),fill:TEXT_MUTED,fontFamily:"sans-serif"},t))}),(()=>{let tickFS=Math.max(9,fSize-3),labelFS=Math.max(10,fSize-2),tickTextW=String(topDomainMax).length*tickFS*.6,labelCx=topAxisX-6-tickTextW-labelFS/2-6,labelCy=topPanelY+TOP_PANEL_H/2;return React.createElement("text",{x:labelCx,y:labelCy,textAnchor:"middle",dominantBaseline:"central",fontSize:labelFS,fill:TEXT_MUTED,fontFamily:"sans-serif",transform:`rotate(-90 ${labelCx} ${labelCy})`},"Intersection size")})()),React.createElement("g",{id:"intersection-bars"},intersections.map((inter,i)=>{let cx=colX(i),barW=Math.max(6,colW*.7),barX=cx-barW/2,h=topBarScale(inter.size),isSelected=selectedMask===inter.mask,idKey=intersectionIdKey(inter.setIndices,setNames),fill=BAR_FILL;if(colorBarsBySignificance&&significanceByMask){let sig=significanceByMask.get(inter.mask),twoSided=sig?.pAdjTwoSided;sig&&twoSided!=null&&Number.isFinite(twoSided)&&twoSided<.05&&(sig.direction==="enriched"?fill=BAR_FILL_ENRICHED:sig.direction==="depleted"&&(fill=BAR_FILL_DEPLETED))}return React.createElement("g",{key:`tb-${inter.mask}`,id:`intersection-bar-${idKey}`,style:{cursor:"pointer"},onClick:()=>onColumnClick&&onColumnClick(isSelected?null:inter.mask),role:"img","aria-label":`${intersectionLabel(inter.setIndices,setNames)}: ${inter.size} element${inter.size!==1?"s":""}`},React.createElement("rect",{x:barX,y:topPanelBottom-h,width:barW,height:h,fill,fillOpacity:barOp,stroke:isSelected?TEXT_DARK:"none",strokeWidth:isSelected?1.5:0}))})),sizeLabelShown&&React.createElement("g",{id:"intersection-bar-labels"},intersections.map((inter,i)=>{let cx=colX(i),h=topBarScale(inter.size),anchorY=topPanelBottom-h-3;return React.createElement("text",{key:`tbl-${inter.mask}`,x:cx,y:anchorY,textAnchor:"start",dominantBaseline:"central",fontSize:sizeLabelFS,fill:TEXT_DARK,fontFamily:"sans-serif",transform:`rotate(-90 ${cx} ${anchorY})`},inter.size)})),significanceDisplay&&significanceDisplay!=="off"&&significanceByMask&&React.createElement("g",{id:"significance-markers"},intersections.map((inter,i)=>{let sig=significanceByMask.get(inter.mask),pAdj=sig?.pAdj;if(!sig||pAdj==null||!Number.isFinite(pAdj))return null;let cx=colX(i),h=topBarScale(inter.size),thisSizeLabelHeight=sizeLabelShown?String(inter.size).length*sizeLabelFS*.58:0,labelOffset=sizeLabelShown?thisSizeLabelHeight+7:3,text=significanceDisplay==="stars"?pStars(pAdj):"p="+formatP(pAdj),isStarsMode=significanceDisplay==="stars",isNs=isStarsMode&&text==="ns",tSize=isNs?Math.max(8,fSize-4):isStarsMode?Math.max(10,fSize-1):Math.max(9,fSize-3),fill=isStarsMode?"#111111":pAdj<.05?"#1f6feb":"#555555",anchorY=topPanelBottom-h-labelOffset;return React.createElement("text",{key:`sig-${inter.mask}`,x:cx,y:anchorY,textAnchor:"start",dominantBaseline:"central",fontSize:tSize,fill,fontFamily:"sans-serif",fontWeight:isStarsMode&&!isNs?700:400,transform:`rotate(-90 ${cx} ${anchorY})`},text)})),React.createElement("g",{id:"matrix-background"},setNames.map((_,i)=>i%2===0?React.createElement("rect",{key:`zb-${i}`,x:matrixLeftX,y:matrixY+i*rowH,width:Math.max(0,nCols)*colW,height:rowH,fill:ZEBRA_FILL,fillOpacity:"0.5"}):null)),React.createElement("g",{id:"set-labels"},setNames.map((name,i)=>React.createElement("text",{key:`sl-${i}`,x:setIdLaneX-2,y:rowY(i),textAnchor:"end",dominantBaseline:"central",fontSize:labelFontSize,fontWeight:"600",fill:TEXT_DARK,fontFamily:"sans-serif"},name))),React.createElement("g",{id:"set-ids"},setNames.map((_,i)=>React.createElement("text",{key:`sid-${i}`,id:`set-id-${i+1}`,x:setIdLaneX+setIdLaneW/2,y:rowY(i),textAnchor:"middle",dominantBaseline:"central",fontSize:setIdFontSize,fontFamily:"monospace",fill:TEXT_MUTED},`S${i+1}`))),React.createElement("g",{id:"set-size-bars"},setNames.map((name,i)=>{let size=setSizes.get(name)||0,w=leftBarScale(size),barRightX=setIdLaneX-LEFT_GAP-leftLabelArea;return React.createElement("rect",{key:`sb-${i}`,id:`set-size-${svgSafeId(name)}`,x:barRightX-w,y:rowY(i)-rowH*.3,width:w,height:rowH*.6,fill:BAR_FILL,fillOpacity:barOp})})),showSetSizeLabels!==!1&&React.createElement("g",{id:"set-size-bar-labels"},setNames.map((name,i)=>{let size=setSizes.get(name)||0,w=leftBarScale(size),barRightX=setIdLaneX-LEFT_GAP-leftLabelArea;return React.createElement("text",{key:`sbl-${i}`,x:barRightX-w-4,y:rowY(i),textAnchor:"end",dominantBaseline:"central",fontSize:Math.max(9,fSize-3),fill:TEXT_MUTED,fontFamily:"sans-serif"},size)})),React.createElement("g",{id:"axis-set-size"},(()=>{let barRightX=setIdLaneX-LEFT_GAP-leftLabelArea,axisY=matrixY+matrixH+4,axisLeftX=barRightX-LEFT_BAR_MAX;return React.createElement(React.Fragment,null,React.createElement("line",{x1:axisLeftX,x2:barRightX,y1:axisY,y2:axisY,stroke:TEXT_DARK,strokeWidth:"1"}),leftTicks.map((t,i)=>{let x=barRightX-leftBarScale(t);return React.createElement("g",{key:`la-${i}`},React.createElement("line",{x1:x,x2:x,y1:axisY,y2:axisY+3,stroke:TEXT_DARK,strokeWidth:"1"}),React.createElement("text",{x,y:axisY+18,textAnchor:"middle",fontSize:Math.max(9,fSize-3),fill:TEXT_MUTED,fontFamily:"sans-serif"},t))}),React.createElement("text",{x:axisLeftX+LEFT_BAR_MAX/2,y:axisY+44,textAnchor:"middle",fontSize:Math.max(10,fSize-2),fill:TEXT_MUTED,fontFamily:"sans-serif"},"Set size"))})()),React.createElement("g",{id:"column-ids"},(()=>{let idLaneY=matrixY+matrixH+idLaneOffset;return intersections.map((inter,i)=>{let cx=colX(i);return React.createElement("text",{key:`cid-${inter.mask}`,id:`column-id-${i+1}`,x:cx,y:idLaneY,textAnchor:"end",dominantBaseline:"middle",fontSize:idFontSize,fontFamily:"monospace",fill:TEXT_MUTED,transform:`rotate(-90 ${cx} ${idLaneY})`},`I${i+1}`)})})()),React.createElement("g",{id:"column-ids-legend"},(()=>{let legendY=matrixY+matrixH+legendOffset;return React.createElement("text",{x:matrixLeftX,y:legendY,textAnchor:"start",dominantBaseline:"hanging",fontSize:legendFS,fontFamily:"sans-serif",fontStyle:"italic",fill:TEXT_MUTED},"I# = intersection id (used as bulk-download filename) \xB7 S# = set id (rows)")})()),React.createElement("g",{id:"matrix"},React.createElement("g",{id:"matrix-columns"},intersections.map((inter,i)=>{let cx=colX(i),inSet=new Set(inter.setIndices),isSelected=selectedMask===inter.mask,idKey=intersectionIdKey(inter.setIndices,setNames),activeRows=inter.setIndices,minR=activeRows.length?rowY(Math.min(...activeRows)):0,maxR=activeRows.length?rowY(Math.max(...activeRows)):0;return React.createElement("g",{key:`col-${inter.mask}`,id:`col-${idKey}`,style:{cursor:"pointer"},onClick:()=>onColumnClick&&onColumnClick(isSelected?null:inter.mask)},React.createElement("rect",{x:cx-colW/2,y:matrixY,width:colW,height:matrixH,fill:isSelected?"#648FFF":"#ffffff",fillOpacity:isSelected?.12:0,pointerEvents:"all"}),activeRows.length>1&&React.createElement("line",{className:"matrix-line",x1:cx,x2:cx,y1:minR,y2:maxR,stroke:DOT_FILL,strokeWidth:Math.max(1.5,dotR/3)}),setNames.map((name,j)=>React.createElement("circle",{key:`d-${j}`,id:`dot-${idKey}-${svgSafeId(name)}`,cx,cy:rowY(j),r:dotR,fill:inSet.has(j)?DOT_FILL:EMPTY_DOT})))}))))}));var UPSET_HOWTO={toolName:"upset",title:"UpSet Plot \u2014 How to use",subtitle:"Set-intersection sizes for 2+ sets, where Venn breaks down",purpose:React.createElement(React.Fragment,null,"Show intersections between many sets at once \u2014 UpSet plots scale gracefully past three sets where Venn diagrams collapse into unreadable shapes."),dataLayout:React.createElement(React.Fragment,null,React.createElement("strong",null,"Wide")," \u2014 one column per set, items stacked in each column."," ",React.createElement("strong",null,"Long")," \u2014 two columns: ",React.createElement("em",null,"item")," and ",React.createElement("em",null,"set"),". Same format as Venn."),display:React.createElement(React.Fragment,null,"Each intersection is a column: top-half bar shows its size, bottom-half dot matrix shows the set membership. Sort by ",React.createElement("strong",null,"size")," (default) or ",React.createElement("strong",null,"degree"),"; filter by minimum size + degree window. Per-intersection significance test against a uniform-random null with BH-adjusted p-values.")};var{useEffect,useMemo}=React;function UploadStep({sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:setSepOverride,onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Arabidopsis stress-response DEGs",subtitle:"5 sets \u2014 Drought \xB7 Heat \xB7 Salt \xB7 Cold \xB7 ABA",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 wide (one column per set, 2+) or long (item, set) \xB7 2 MB max"}),React.createElement(HowTo,{...UPSET_HOWTO}))}function ConfigureStep({fileName,detectedSep,parsedHeaders,parsedRows,allColumnNames,allColumnSets,pendingSelection,setPendingSelection,minDegree,setMinDegree,maxDegree,setMaxDegree}){let selectedCount=pendingSelection.length,needsCutoff=selectedCount>8;useEffect(()=>{if(!needsCutoff){setMinDegree(1),setMaxDegree(1/0);return}setMinDegree(d=>Math.max(1,Math.min(selectedCount,d))),setMaxDegree(d=>Number.isFinite(d)?Math.max(1,Math.min(selectedCount,d)):selectedCount)},[needsCutoff,selectedCount,setMaxDegree,setMinDegree]),useEffect(()=>{Number.isFinite(maxDegree)&&minDegree>maxDegree&&setMinDegree(maxDegree)},[minDegree,maxDegree,setMinDegree]);let allPossible=selectedCount>=2?Math.pow(2,selectedCount)-1:0,effectiveMaxDegree=Number.isFinite(maxDegree)?maxDegree:selectedCount,cutoffPreview=useMemo(()=>{if(!needsCutoff)return null;let pendingSets=new Map;pendingSelection.forEach(n=>pendingSets.set(n,allColumnSets.get(n)));let{membershipMap}=computeMemberships(pendingSelection,pendingSets),all=enumerateIntersections(membershipMap,pendingSelection),kept=all.filter(r=>r.degree>=minDegree&&r.degree<=effectiveMaxDegree).length;return{nonEmpty:all.length,kept}},[needsCutoff,pendingSelection,allColumnSets,minDegree,effectiveMaxDegree]),toggle=name=>{setPendingSelection(prev=>prev.includes(name)?prev.filter(n=>n!==name):[...prev,name])},pickerStatusText="Pick at least 2 sets to plot.",pickerStatusColor="var(--text-muted)";return selectedCount===1?(pickerStatusText="1 selected \u2014 pick at least one more.",pickerStatusColor="var(--warning-text)"):selectedCount>=2&&(pickerStatusText=`${selectedCount} selected \u2014 ready to plot.`,pickerStatusColor="var(--success-text)"),React.createElement("div",null,React.createElement("div",{className:"dv-panel"},React.createElement("p",{className:"dv-tile-title",style:{margin:"0 0 4px"}},"Sets to include"),React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:pickerStatusColor}},pickerStatusText),React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:6}},allColumnNames.map(name=>{let checked=pendingSelection.includes(name),size=allColumnSets.get(name)?.size??0;return React.createElement("label",{key:name,style:{display:"flex",alignItems:"center",gap:8,padding:"6px 10px",borderRadius:6,border:`1px solid ${checked?"var(--accent-primary)":"var(--border)"}`,background:checked?"var(--info-bg)":"var(--surface-subtle)",cursor:"pointer",fontSize:12,color:"var(--text)"}},React.createElement("input",{type:"checkbox",checked,onChange:()=>toggle(name),style:{accentColor:"var(--cta-primary-bg)"}}),React.createElement("span",{style:{fontWeight:600,flex:"1 1 auto",minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},name),React.createElement("span",{style:{color:"var(--text-faint)",fontFamily:"monospace",fontSize:11}},size))}))),needsCutoff&&React.createElement("div",{className:"dv-panel",style:{marginTop:16}},React.createElement("p",{className:"dv-tile-title",style:{margin:"0 0 4px"}},"Intersection cutoff"),React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:"var(--text-muted)"}},"With ",selectedCount," sets, up to ",allPossible.toLocaleString()," intersections are possible. Keep only intersections whose degree falls in this window:"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"}},React.createElement("label",{style:{fontSize:11,color:"var(--text-muted)"}},"Min"),React.createElement(NumberInput,{min:1,max:selectedCount,step:1,value:minDegree,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let clamped=Math.max(1,Math.min(selectedCount,v));setMinDegree(clamped),Number.isFinite(maxDegree)&&clamped>maxDegree&&setMaxDegree(clamped)},style:{width:96}}),React.createElement("label",{style:{fontSize:11,color:"var(--text-muted)"}},"Max"),React.createElement(NumberInput,{min:1,max:selectedCount,step:1,value:effectiveMaxDegree,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let clamped=Math.max(1,Math.min(selectedCount,v));setMaxDegree(clamped),clampeddownloadCsv(["Item"],intersection.items.map(i=>[i]),columnId!=null?`${baseName}_upset_I${columnId}.csv`:`${baseName}_upset_${intersectionFilenamePart(label)}.csv`),className:"dv-btn dv-btn-secondary",style:{background:"var(--success-bg)",border:"1px solid var(--success-border)",color:"var(--success-text)",fontWeight:600,fontSize:11,marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),React.createElement("div",{style:{maxHeight:240,overflowY:"auto",border:"1px solid var(--border)",borderRadius:6,background:"var(--surface-subtle)"}},intersection.items.map((item,i)=>React.createElement("div",{key:i,style:{padding:"3px 10px",fontSize:12,color:"var(--text)",borderBottom:"1px solid var(--border)",fontFamily:"monospace"}},item))))}function PlotControls({activeSetNames,allSets,vis,updVis,chartRef,resetAll,fileName,intersections,computeAllIntersectionStats,clearIntersectionStats,computingStats,computeProgress,intersectionTestsCount,universeSize,setUniverseSize,universeOverridden,setUniverseOverridden,defaultUniverseSize,maxAllIntersectionSize,allIntersectionsCount}){let baseName=fileBaseName(fileName,"upset"),sv=k=>v=>updVis({[k]:v}),universeValid=universeSize!==""&&Number.isFinite(Number(universeSize))&&Number(universeSize)>0;return React.createElement(PlotSidebar,null,React.createElement(DownloadTiles,{chartRef,fileStem:`${baseName}_upset`,onReset:resetAll,extraDownloads:[{label:"Table",title:"Download the currently-plotted intersection table (Intersection, Degree, Size, + per-set flags). Matches the plot exactly \u2014 reflects sort, Top N, Minimum/Maximum degree, and Minimum size filters.",onClick:()=>{let headers=["Intersection","Degree","Size",...activeSetNames],rows=intersections.map(r=>{let label=intersectionLabel(r.setIndices,activeSetNames),flags=activeSetNames.map((_,i)=>r.setIndices.includes(i)?"1":"0");return[label,String(r.degree),String(r.size),...flags]});downloadCsv(headers,rows,`${baseName}_upset_intersections.csv`)}},{label:"Matrix",title:"Download the membership matrix \u2014 one row per item, a 0/1 column for each active set",onClick:()=>{let allItems=new Set;for(let n of activeSetNames){let s=allSets.get(n);if(s)for(let item of s)allItems.add(item)}let headers=["Item",...activeSetNames],rows=[...allItems].sort().map(item=>[item,...activeSetNames.map(n=>allSets.get(n)?.has(item)?"1":"0")]);downloadCsv(headers,rows,`${baseName}_upset_membership.csv`)}},{label:"All regions",title:"One CSV per currently-plotted intersection (named _I1, _I2, \u2026 matching the on-plot identifiers) plus an _index.csv mapping Id \u2192 Intersection, Degree, Size. Your browser may ask once to allow multiple downloads.",onClick:()=>{if(!intersections.length)return;let indexHeaders=["Id","Intersection","Degree","Size"],indexRows=intersections.map((inter,i)=>[`I${i+1}`,intersectionLabel(inter.setIndices,activeSetNames),String(inter.degree),String(inter.size)]);downloadCsv(indexHeaders,indexRows,`${baseName}_upset_index.csv`),intersections.forEach((inter,i)=>{setTimeout(()=>{downloadCsv(["Item"],inter.items.map(item=>[item]),`${baseName}_upset_I${i+1}.csv`)},40*(i+1))})}}]}),React.createElement(ControlSection,{title:"Columns",defaultOpen:!0},React.createElement("div",null,React.createElement("span",{className:"dv-label"},"Sort by"),React.createElement("select",{value:vis.sortMode,onChange:e=>updVis({sortMode:e.target.value}),className:"dv-input",style:{width:"100%"}},React.createElement("option",{value:"size-desc"},"Size (largest first)"),React.createElement("option",{value:"size-asc"},"Size (smallest first)"),React.createElement("option",{value:"degree-desc"},"Degree (highest first)"),React.createElement("option",{value:"degree-asc"},"Degree (lowest first)"),React.createElement("option",{value:"sets"},"Set order"))),React.createElement("label",{style:{display:"block"}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",marginBottom:2}},React.createElement("span",{className:"dv-label"},"Minimum intersection size"),maxAllIntersectionSize>0&&React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"max in data: ",maxAllIntersectionSize.toLocaleString())),React.createElement(NumberInput,{value:vis.minSize,min:0,max:maxAllIntersectionSize>0?maxAllIntersectionSize:void 0,step:1,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let cap=maxAllIntersectionSize>0?maxAllIntersectionSize:v;updVis({minSize:Math.max(0,Math.min(cap,v))})},style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Minimum degree"),React.createElement(NumberInput,{value:vis.minDegree,min:1,max:Math.max(1,activeSetNames.length),step:1,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let cap=Math.max(1,activeSetNames.length),clamped=Math.max(1,Math.min(cap,v)),patch={minDegree:clamped};vis.maxDegree!=null&&clamped>vis.maxDegree&&(patch.maxDegree=clamped),updVis(patch)},style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",marginBottom:2}},React.createElement("span",{className:"dv-label"},"Maximum degree"),activeSetNames.length>0&&React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"max in data: ",activeSetNames.length.toLocaleString())),React.createElement(NumberInput,{value:vis.maxDegree??Math.max(1,activeSetNames.length),min:1,max:Math.max(1,activeSetNames.length),step:1,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let cap=Math.max(1,activeSetNames.length),clamped=Math.max(1,Math.min(cap,v)),patch={maxDegree:clamped};clampedupdVis({plotTitle:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Subtitle"),React.createElement("input",{value:vis.plotSubtitle,onChange:e=>updVis({plotSubtitle:e.target.value}),className:"dv-input",style:{width:"100%"}}))),React.createElement(ControlSection,{title:"Display"},React.createElement(SliderControl,{label:"Bar opacity",value:vis.barOpacity,min:.3,max:1,step:.05,onChange:sv("barOpacity")}),React.createElement(SliderControl,{label:"Dot size",value:vis.dotSize,min:3,max:12,step:1,onChange:sv("dotSize")}),React.createElement(SliderControl,{label:"Font size",value:vis.fontSize,min:8,max:20,step:1,onChange:sv("fontSize")}),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Intersection size labels"),React.createElement(OnOffToggle,{value:vis.showIntersectionLabels!==!1,onChange:v=>updVis({showIntersectionLabels:v}),ariaLabel:"Intersection size labels"})),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Set size labels"),React.createElement(OnOffToggle,{value:vis.showSetSizeLabels!==!1,onChange:v=>updVis({showSetSizeLabels:v}),ariaLabel:"Set size labels"})),React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},React.createElement("span",{className:"dv-label"},"Background"),React.createElement(ColorInput,{value:vis.plotBg,onChange:sv("plotBg"),size:24}))),React.createElement(ControlSection,{title:"Statistics"},React.createElement("div",null,React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",marginBottom:2}},React.createElement("span",{className:"dv-label"},"Universe size (N)"),universeOverridden&&Number.isFinite(Number(defaultUniverseSize))&&React.createElement("button",{type:"button",onClick:()=>{setUniverseOverridden(!1),setUniverseSize(defaultUniverseSize||"")},style:{fontSize:10,padding:"1px 8px",background:"none",border:"1px solid var(--border-strong)",borderRadius:4,color:"var(--text-muted)",cursor:"pointer",fontFamily:"inherit"},title:"Revert to the union of uploaded items"},"Reset to |\u222A|=",defaultUniverseSize)),React.createElement(NumberInput,{value:universeSize,min:1,step:1,onChange:e=>{let v=e.target.value;setUniverseSize(v===""?"":Number(v)),setUniverseOverridden(!0)},style:{width:"100%"}}),React.createElement("p",{style:{margin:"4px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},"Defaults to the union of uploaded items (|\u222A|). Override with the genome / proteome / predefined background for real enrichment analyses \u2014 a smaller universe inflates p-values.")),React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",{className:"dv-label"},"Intersection statistics"),React.createElement("button",{type:"button",className:"dv-btn dv-btn-primary",onClick:computeAllIntersectionStats,disabled:computingStats||!universeValid||allIntersectionsCount===0,title:universeValid?computingStats?"Computing\u2026":`Run the SuperExactTest exact test for every one of the ${allIntersectionsCount} intersections in the active set selection and BH-adjust across them. Display filters (minimum size / degree) do NOT change which intersections are tested.`:"Set a Universe size above before computing stats",style:{fontSize:12,padding:"6px 10px",display:"flex",alignItems:"center",justifyContent:"center",gap:6}},computingStats?React.createElement(React.Fragment,null,React.createElement("span",{style:{display:"inline-block",width:12,height:12,border:"2px solid currentColor",borderRightColor:"transparent",borderRadius:"50%",animation:"dv-spin 0.9s linear infinite"},"aria-hidden":"true"}),"Computing ",computeProgress.done,"/",computeProgress.total,"\u2026"):intersectionTestsCount>0?`Recompute stats (${allIntersectionsCount} intersections)`:`Compute stats (${allIntersectionsCount} intersections)`),computingStats&&computeProgress.total>0&&React.createElement("div",{style:{height:3,borderRadius:2,background:"var(--border)",overflow:"hidden"},"aria-hidden":"true"},React.createElement("div",{style:{height:"100%",width:`${computeProgress.done/computeProgress.total*100}%`,background:"var(--accent-primary)",transition:"width 120ms linear"}})),intersectionTestsCount>0&&!computingStats&&React.createElement("button",{type:"button",onClick:clearIntersectionStats,className:"dv-btn dv-btn-secondary",style:{fontSize:11,padding:"3px 8px"}},"Clear ",intersectionTestsCount," cached"," ",intersectionTestsCount===1?"result":"results"),React.createElement("p",{style:{margin:"2px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},"Computes the exact Binomial p (upper tail, lower tail, and the headline two-sided = smaller tail \xD7 2) per intersection, then BH-adjusts each family across every intersection in the active set selection. Display filters (minimum size / degree) only affect what's shown on the plot \u2014 they never change the BH family."),React.createElement("style",null,"@keyframes dv-spin { to { transform: rotate(360deg); } }")),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Significance markers"),React.createElement("div",{style:{display:"flex",borderRadius:6,overflow:"hidden",border:"1px solid var(--border-strong)"}},[["off","Off"],["stars","Stars"],["p-value","p-value"]].map(([mode,label])=>{let active=(vis.significanceDisplay||"off")===mode;return React.createElement("button",{key:mode,type:"button",onClick:()=>updVis({significanceDisplay:mode}),style:{flex:1,padding:"4px 0",fontSize:11,fontWeight:active?700:400,fontFamily:"inherit",cursor:"pointer",border:"none",background:active?"var(--accent-primary)":"var(--surface)",color:active?"var(--on-accent)":"var(--text-muted)",transition:"background 120ms ease, color 120ms ease"}},label)})),React.createElement("p",{style:{margin:"4px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},"Only tested intersections are marked. Uses the two-sided p (smaller tail \xD7 2, BH- adjusted across every test run this session), so both enrichment and depletion show up.")),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Color bars by significance"),React.createElement("div",{style:{display:"flex",borderRadius:6,overflow:"hidden",border:"1px solid var(--border-strong)"}},[[!1,"Off"],[!0,"On"]].map(([value,label])=>{let active=!!vis.colorBarsBySignificance===value;return React.createElement("button",{key:String(value),type:"button",onClick:()=>updVis({colorBarsBySignificance:value}),style:{flex:1,padding:"4px 0",fontSize:11,fontWeight:active?700:400,fontFamily:"inherit",cursor:"pointer",border:"none",background:active?"var(--accent-primary)":"var(--surface)",color:active?"var(--on-accent)":"var(--text-muted)",transition:"background 120ms ease, color 120ms ease"}},label)})),React.createElement("p",{style:{margin:"4px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},React.createElement("span",{style:{color:BAR_FILL_ENRICHED,fontWeight:700}},"Green")," = enriched."," ",React.createElement("span",{style:{color:BAR_FILL_DEPLETED,fontWeight:700}},"Dark red")," = depleted. Both at two-sided p_adj < 0.05, direction from the sign of observed \u2212 expected. Untested or non-significant bars stay black."))))}function IntersectionStatsPanel({intersection,displaySetNames,sets,membershipMap,universeSize,intersectionTests}){let inclusiveSize=React.useMemo(()=>{if(!intersection)return 0;let mask=intersection.mask,count=0;for(let m of membershipMap.values())(m&mask)===mask&&count++;return count},[intersection,membershipMap]);if(!intersection)return null;let selectedSetSizes=intersection.setIndices.map(i=>(sets.get(displaySetNames[i])||new Set).size),selectedSetNames=intersection.setIndices.map(i=>displaySetNames[i]),universeN=typeof universeSize=="number"?universeSize:Number(universeSize),cacheKey=`${intersection.mask}:${universeN}`,cachedResult=intersectionTests.get(cacheKey),fmtP=p=>p==null||!Number.isFinite(p)?"\u2014":p===0?"0":p>=1e-4?p.toPrecision(4):p.toExponential(3),sidebarSection=(label,value,tooltip=null)=>React.createElement("div",{style:{display:"flex",justifyContent:"space-between",gap:16}},React.createElement("span",{style:{color:"var(--text-muted)"}},label),React.createElement("span",{style:{fontFamily:"monospace",color:"var(--text)",cursor:tooltip?"help":void 0},title:tooltip||void 0},value));return React.createElement("div",{className:"dv-panel",style:{marginTop:16,display:"flex",flexDirection:"column",gap:10}},React.createElement("div",{style:{display:"flex",alignItems:"baseline",gap:10}},React.createElement("p",{className:"dv-tile-title",style:{margin:0}},"Intersection significance"),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"SuperExactTest-style exact test against the fixed-margin null")),(()=>{let universeFinite=Number.isFinite(universeN)&&universeN>0,xExclusive=intersection.size,outsideSetSizes=[];for(let j=0;jexpected?"enriched":"depleted":null,directionGlyph=direction==="enriched"?"\u2191 enriched":direction==="depleted"?"\u2193 depleted":direction==="neutral"?"\u2248 as expected":"",directionColor=direction==="enriched"?"var(--accent-plot, #1f6feb)":direction==="depleted"?"var(--warning-text, #b45309)":"var(--text-muted)",fmtExpected=v=>Number.isFinite(v)?v===0?"0":v>=.01&&v<1e3?v.toPrecision(4).replace(/\.?0+$/,""):v.toExponential(3):"\u2014";return React.createElement("div",{style:{display:"grid",gap:4,fontSize:12}},sidebarSection("Sets tested",intersectionShortLabel(intersection.setIndices),selectedSetNames.join(" \u2229 ")),sidebarSection("Set sizes (n\u1D62)",selectedSetSizes.join(", ")),sidebarSection("Exclusive overlap (bar)",React.createElement("span",{style:{display:"inline-flex",gap:8,alignItems:"baseline"}},React.createElement("span",null,xExclusive),direction&&React.createElement("span",{style:{fontSize:11,color:directionColor,fontWeight:600}},directionGlyph))),expectedKnown&&sidebarSection("Expected under null",React.createElement("span",{style:{display:"inline-flex",gap:6,alignItems:"baseline"}},React.createElement("span",null,fmtExpected(expected)),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"},title:"E[exclusive] = N \xB7 \u03A0(n\u1D62/N) \xB7 \u03A0(1 \u2212 n\u2C7C/N) under the independence approximation (each item falls in each set with its marginal probability). Inside: sets the bar covers. Outside: the other uploaded sets."},"= N \xB7 \u03A0(n\u1D62/N) \xB7 \u03A0(1 \u2212 n\u2C7C/N)"))),sidebarSection(React.createElement("span",{style:{color:"var(--text-faint)"}},"Inclusive overlap"),React.createElement("span",{style:{color:"var(--text-faint)"}},inclusiveSize)))})(),cachedResult?React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},(()=>{let two=cachedResult.pTwoSided,twoAdj=cachedResult.pAdjTwoSided,enr=Number.isFinite(cachedResult.pUpper)?cachedResult.pUpper:cachedResult.p,enrAdj=cachedResult.pAdjUpper,dep=cachedResult.pLower,depAdj=cachedResult.pAdjLower,rowStyle={display:"flex",alignItems:"baseline",gap:10,flexWrap:"wrap",fontSize:12},renderRow=(label,hint,p,pAdj)=>React.createElement("div",{style:rowStyle},React.createElement("span",{style:{color:"var(--text-muted)",minWidth:110,display:"inline-block"}},label),React.createElement("span",null,React.createElement("span",{style:{color:"var(--text-muted)"}},"p = "),React.createElement("span",{style:{fontFamily:"monospace",fontWeight:600}},fmtP(p))),React.createElement("span",null,React.createElement("span",{style:{color:"var(--text-muted)"}},"p_adj = "),React.createElement("span",{style:{fontFamily:"monospace",fontWeight:600}},fmtP(pAdj))),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},hint));return React.createElement(React.Fragment,null,renderRow("Two-sided","min(2\xB7pUpper, 2\xB7pLower, 1) \u2014 headline p, drives plot markers + bar colour",two,twoAdj),renderRow("Enrichment","P(X \u2265 bar) \u2014 Binomial(N, p_M), upper tail",enr,enrAdj),renderRow("Depletion","P(X \u2264 bar) \u2014 lower tail",dep,depAdj),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",marginTop:2}},"Each family BH-adjusted separately across ",intersectionTests.size," intersection",intersectionTests.size===1?"":"s"," cached for N=",universeN,". The two-sided p is the honest headline (one test per bar, no cherry-picking); the per-tail rows are there for directional breakdown. The Binomial null assumes each item is independently placed in every set at its marginal rate."))})()):React.createElement("div",{style:{fontSize:12,color:"var(--text-muted)",fontStyle:"italic"}},"No p-value for this intersection yet \u2014 use ",React.createElement("strong",null,"Compute stats")," in the sidebar to run the two-sided Binomial test (plus the per-tail enrichment / depletion breakdown) on the exclusive bar height for every intersection in the current set selection in one pass."))}var{useState,useMemo:useMemo2,useCallback,useRef,useEffect:useEffect2}=React,VIS_INIT_UPSET={plotTitle:"",plotSubtitle:"",plotBg:"#ffffff",fontSize:12,barOpacity:1,dotSize:6,sortMode:"size-desc",minSize:1,minDegree:1,maxDegree:null,showIntersectionLabels:!0,showSetSizeLabels:!0,significanceDisplay:"off",colorBarsBySignificance:!1},EXAMPLE_CSV=`Drought,Heat,Salt,Cold,ABA +import{multisetExclusiveExpected,multisetExclusiveP}from"./chunk-DT6WX6AK.js";import{niceStep}from"./chunk-T4V5V5TK.js";import{ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DownloadTiles,FILE_LIMIT_BYTES,HowTo,NumberInput,OnOffToggle,PlotSidebar,PlotToolShell,ScrollablePlotCard,SliderControl,UploadPanel,autoDetectSep,bhAdjust,detectLongFormat,downloadCsv,fileBaseName,fixDecimalCommas,formatP,pStars,parseLongFormatSets,parseRaw,parseSetData,svgSafeId,usePlotToolState}from"./chunk-HFTDMS7M.js";function computeMemberships(setNames,sets){let membershipMap=new Map;return setNames.forEach((name,i)=>{let s=sets.get(name);if(s)for(let item of s){let prev=membershipMap.get(item)||0;membershipMap.set(item,prev|1<a.mask-b.mask,copy=list.slice();switch(mode){case"size-asc":return copy.sort((a,b)=>a.size-b.size||byMaskAsc(a,b));case"degree-asc":return copy.sort((a,b)=>a.degree-b.degree||b.size-a.size||byMaskAsc(a,b));case"degree-desc":return copy.sort((a,b)=>b.degree-a.degree||b.size-a.size||byMaskAsc(a,b));case"sets":return copy.sort((a,b)=>{let la=a.setIndices,lb=b.setIndices,n=Math.min(la.length,lb.length);for(let i=0;ib.size-a.size||byMaskAsc(a,b))}}function truncateIntersections(list,{minSize=1,minDegree=1,maxDegree=1/0}={}){return list.filter(r=>r.size>=minSize&&r.degree>=minDegree&&r.degree<=maxDegree)}function intersectionLabel(setIndices,setNames){return setIndices.map(i=>setNames[i]).join(" \u2229 ")}function intersectionShortLabel(setIndices){return setIndices.map(i=>`S${i+1}`).join(" \u2229 ")}function intersectionFilenamePart(label){return label.replace(/∩/g,"and").replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_]/g,"")}function intersectionIdKey(setIndices,setNames){return setIndices.map(i=>svgSafeId(setNames[i])).join("-")||"empty"}function buildBarTicks(max,count){if(!(max>0))return[0,1];let step=niceStep(max,count),domainMax=Math.ceil((max+step*1e-9)/step)*step,last=domainMax>max?domainMax:domainMax+step,ticks=[];for(let v=0;v<=last+step*.001;v+=step)ticks.push(parseFloat(v.toPrecision(10)));return ticks}var{forwardRef,memo}=React,PREFERRED_SVG_W=960,MIN_COL_W=18,MAX_COL_W=36,TITLE_H_WITH=40,TITLE_H_NONE=16,SUBTITLE_H=18,TOP_PANEL_H=200,MATRIX_TOP_PAD=8,BOTTOM_H=62,LEFT_MARGIN=44,LEFT_BAR_MAX=110,LEFT_LABEL_AREA_MIN=82,LEFT_GAP=6,RIGHT_MARGIN=20,BAR_FILL="#000000",DOT_FILL="#000000",EMPTY_DOT="#DDDDDD",ZEBRA_FILL="#F4F4F4",TEXT_DARK="#333333",TEXT_MUTED="#555555",BAR_FILL_ENRICHED="#2ca25f",BAR_FILL_DEPLETED="#a50f15";function computeRowHeight(nSets){return Math.max(22,Math.min(40,Math.round(140/Math.max(1,nSets)+14)))}function computeColWidth(nCols,matrixLeftX){if(nCols<=0)return 24;let avail=PREFERRED_SVG_W-matrixLeftX-RIGHT_MARGIN;return Math.max(MIN_COL_W,Math.min(MAX_COL_W,avail/nCols))}var UpsetChart=memo(forwardRef(function({setNames,setSizes,intersections,selectedMask,onColumnClick,plotTitle,plotSubtitle,plotBg,fontSize,barOpacity,dotSize,showIntersectionLabels,showSetSizeLabels,significanceDisplay,significanceByMask,colorBarsBySignificance},ref){let nSets=setNames.length,nCols=intersections.length,fSize=fontSize||12,barOp=barOpacity??1,dotR=dotSize||6,labelFontSize=Math.max(10,fSize-1),estLabelW=Math.max(0,...setNames.map(n=>String(n).length*labelFontSize*.58)),leftLabelArea=Math.max(LEFT_LABEL_AREA_MIN,Math.ceil(estLabelW)+6),setIdFontSize=Math.max(8,Math.min(10,fSize-4)),maxSetIdChars=1+String(Math.max(1,nSets)).length,setIdLaneW=Math.ceil(maxSetIdChars*setIdFontSize*.6)+6,setIdLaneX=LEFT_MARGIN+LEFT_BAR_MAX+LEFT_GAP+leftLabelArea,matrixLeftX=setIdLaneX+setIdLaneW,rowH=computeRowHeight(nSets),colW=computeColWidth(nCols,matrixLeftX),matrixH=nSets*rowH,titleH=plotTitle?TITLE_H_WITH:TITLE_H_NONE,topPanelY=titleH+(plotSubtitle?SUBTITLE_H:0),matrixY=topPanelY+TOP_PANEL_H+MATRIX_TOP_PAD,idFontSize=Math.max(8,Math.min(10,fSize-4)),maxIdChars=1+String(Math.max(1,nCols)).length,idLabelSpan=Math.ceil(maxIdChars*idFontSize*.58),legendFS=Math.max(9,fSize-3),idLaneOffset=10,legendOffset=idLaneOffset+idLabelSpan+8,bottomNeeded=legendOffset+legendFS+8,VH=matrixY+matrixH+Math.max(BOTTOM_H,bottomNeeded),SVG_W=Math.max(PREFERRED_SVG_W,matrixLeftX+Math.max(0,nCols)*colW+RIGHT_MARGIN),topPanelBottom=topPanelY+TOP_PANEL_H,topAxisMax=Math.max(1,...intersections.map(r=>r.size)),topTicks=buildBarTicks(topAxisMax,4),topDomainMax=topTicks[topTicks.length-1],sizeLabelFS=Math.max(9,fSize-3),sizeLabelShown=showIntersectionLabels!==!1,maxSizeChars=String(topAxisMax).length,maxSizeLabelHeight=sizeLabelShown?Math.ceil(maxSizeChars*sizeLabelFS*.58):0,maxSigLabelHeight=0;significanceDisplay==="stars"?maxSigLabelHeight=Math.ceil(4*Math.max(10,fSize-1)*.58):significanceDisplay==="p-value"&&(maxSigLabelHeight=Math.ceil(9*Math.max(9,fSize-3)*.58));let hasAnyLabel=maxSizeLabelHeight>0||maxSigLabelHeight>0,gapBetweenLabels=maxSizeLabelHeight>0&&maxSigLabelHeight>0?4:0,labelHeadroom=maxSizeLabelHeight+maxSigLabelHeight+gapBetweenLabels+(hasAnyLabel?6:0),barAreaHeight=Math.max(40,TOP_PANEL_H-labelHeadroom),topBarScale=v=>v/topDomainMax*barAreaHeight,barAreaTop=topPanelBottom-barAreaHeight,setSizeMax=Math.max(1,...setNames.map(n=>setSizes.get(n)||0)),leftTicks=buildBarTicks(setSizeMax,3),leftDomainMax=leftTicks[leftTicks.length-1],leftBarScale=v=>v/leftDomainMax*LEFT_BAR_MAX,colX=i=>matrixLeftX+colW*(i+.5),rowY=i=>matrixY+rowH*(i+.5),topAxisX=matrixLeftX-4;return React.createElement("svg",{ref,viewBox:`0 0 ${SVG_W} ${VH}`,width:SVG_W,height:VH,preserveAspectRatio:"xMidYMid meet",style:{display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"UpSet plot"},React.createElement("title",null,plotTitle||"UpSet plot"),React.createElement("desc",null,`UpSet plot with ${nSets} sets and ${nCols} intersections`),React.createElement("g",{id:"background"},React.createElement("rect",{width:SVG_W,height:VH,fill:plotBg||"#ffffff",rx:"8"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:SVG_W/2,y:24,textAnchor:"middle",fontSize:Math.max(14,fSize+4),fontWeight:"700",fill:TEXT_DARK,fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:SVG_W/2,y:titleH+12,textAnchor:"middle",fontSize:Math.max(11,fSize),fill:TEXT_MUTED,fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"axis-intersection-size"},React.createElement("line",{x1:topAxisX,x2:topAxisX,y1:barAreaTop,y2:topPanelBottom,stroke:TEXT_DARK,strokeWidth:"1"}),topTicks.map((t,i)=>{let y=topPanelBottom-topBarScale(t);return React.createElement("g",{key:`ta-${i}`},React.createElement("line",{x1:topAxisX-3,x2:topAxisX,y1:y,y2:y,stroke:TEXT_DARK,strokeWidth:"1"}),React.createElement("text",{x:topAxisX-6,y,textAnchor:"end",dominantBaseline:"central",fontSize:Math.max(9,fSize-3),fill:TEXT_MUTED,fontFamily:"sans-serif"},t))}),(()=>{let tickFS=Math.max(9,fSize-3),labelFS=Math.max(10,fSize-2),tickTextW=String(topDomainMax).length*tickFS*.6,labelCx=topAxisX-6-tickTextW-labelFS/2-6,labelCy=topPanelY+TOP_PANEL_H/2;return React.createElement("text",{x:labelCx,y:labelCy,textAnchor:"middle",dominantBaseline:"central",fontSize:labelFS,fill:TEXT_MUTED,fontFamily:"sans-serif",transform:`rotate(-90 ${labelCx} ${labelCy})`},"Intersection size")})()),React.createElement("g",{id:"intersection-bars"},intersections.map((inter,i)=>{let cx=colX(i),barW=Math.max(6,colW*.7),barX=cx-barW/2,h=topBarScale(inter.size),isSelected=selectedMask===inter.mask,idKey=intersectionIdKey(inter.setIndices,setNames),fill=BAR_FILL;if(colorBarsBySignificance&&significanceByMask){let sig=significanceByMask.get(inter.mask),twoSided=sig?.pAdjTwoSided;sig&&twoSided!=null&&Number.isFinite(twoSided)&&twoSided<.05&&(sig.direction==="enriched"?fill=BAR_FILL_ENRICHED:sig.direction==="depleted"&&(fill=BAR_FILL_DEPLETED))}return React.createElement("g",{key:`tb-${inter.mask}`,id:`intersection-bar-${idKey}`,style:{cursor:"pointer"},onClick:()=>onColumnClick&&onColumnClick(isSelected?null:inter.mask),role:"img","aria-label":`${intersectionLabel(inter.setIndices,setNames)}: ${inter.size} element${inter.size!==1?"s":""}`},React.createElement("rect",{x:barX,y:topPanelBottom-h,width:barW,height:h,fill,fillOpacity:barOp,stroke:isSelected?TEXT_DARK:"none",strokeWidth:isSelected?1.5:0}))})),sizeLabelShown&&React.createElement("g",{id:"intersection-bar-labels"},intersections.map((inter,i)=>{let cx=colX(i),h=topBarScale(inter.size),anchorY=topPanelBottom-h-3;return React.createElement("text",{key:`tbl-${inter.mask}`,x:cx,y:anchorY,textAnchor:"start",dominantBaseline:"central",fontSize:sizeLabelFS,fill:TEXT_DARK,fontFamily:"sans-serif",transform:`rotate(-90 ${cx} ${anchorY})`},inter.size)})),significanceDisplay&&significanceDisplay!=="off"&&significanceByMask&&React.createElement("g",{id:"significance-markers"},intersections.map((inter,i)=>{let sig=significanceByMask.get(inter.mask),pAdj=sig?.pAdj;if(!sig||pAdj==null||!Number.isFinite(pAdj))return null;let cx=colX(i),h=topBarScale(inter.size),thisSizeLabelHeight=sizeLabelShown?String(inter.size).length*sizeLabelFS*.58:0,labelOffset=sizeLabelShown?thisSizeLabelHeight+7:3,text=significanceDisplay==="stars"?pStars(pAdj):"p="+formatP(pAdj),isStarsMode=significanceDisplay==="stars",isNs=isStarsMode&&text==="ns",tSize=isNs?Math.max(8,fSize-4):isStarsMode?Math.max(10,fSize-1):Math.max(9,fSize-3),fill=isStarsMode?"#111111":pAdj<.05?"#1f6feb":"#555555",anchorY=topPanelBottom-h-labelOffset;return React.createElement("text",{key:`sig-${inter.mask}`,x:cx,y:anchorY,textAnchor:"start",dominantBaseline:"central",fontSize:tSize,fill,fontFamily:"sans-serif",fontWeight:isStarsMode&&!isNs?700:400,transform:`rotate(-90 ${cx} ${anchorY})`},text)})),React.createElement("g",{id:"matrix-background"},setNames.map((_,i)=>i%2===0?React.createElement("rect",{key:`zb-${i}`,x:matrixLeftX,y:matrixY+i*rowH,width:Math.max(0,nCols)*colW,height:rowH,fill:ZEBRA_FILL,fillOpacity:"0.5"}):null)),React.createElement("g",{id:"set-labels"},setNames.map((name,i)=>React.createElement("text",{key:`sl-${i}`,x:setIdLaneX-2,y:rowY(i),textAnchor:"end",dominantBaseline:"central",fontSize:labelFontSize,fontWeight:"600",fill:TEXT_DARK,fontFamily:"sans-serif"},name))),React.createElement("g",{id:"set-ids"},setNames.map((_,i)=>React.createElement("text",{key:`sid-${i}`,id:`set-id-${i+1}`,x:setIdLaneX+setIdLaneW/2,y:rowY(i),textAnchor:"middle",dominantBaseline:"central",fontSize:setIdFontSize,fontFamily:"monospace",fill:TEXT_MUTED},`S${i+1}`))),React.createElement("g",{id:"set-size-bars"},setNames.map((name,i)=>{let size=setSizes.get(name)||0,w=leftBarScale(size),barRightX=setIdLaneX-LEFT_GAP-leftLabelArea;return React.createElement("rect",{key:`sb-${i}`,id:`set-size-${svgSafeId(name)}`,x:barRightX-w,y:rowY(i)-rowH*.3,width:w,height:rowH*.6,fill:BAR_FILL,fillOpacity:barOp})})),showSetSizeLabels!==!1&&React.createElement("g",{id:"set-size-bar-labels"},setNames.map((name,i)=>{let size=setSizes.get(name)||0,w=leftBarScale(size),barRightX=setIdLaneX-LEFT_GAP-leftLabelArea;return React.createElement("text",{key:`sbl-${i}`,x:barRightX-w-4,y:rowY(i),textAnchor:"end",dominantBaseline:"central",fontSize:Math.max(9,fSize-3),fill:TEXT_MUTED,fontFamily:"sans-serif"},size)})),React.createElement("g",{id:"axis-set-size"},(()=>{let barRightX=setIdLaneX-LEFT_GAP-leftLabelArea,axisY=matrixY+matrixH+4,axisLeftX=barRightX-LEFT_BAR_MAX;return React.createElement(React.Fragment,null,React.createElement("line",{x1:axisLeftX,x2:barRightX,y1:axisY,y2:axisY,stroke:TEXT_DARK,strokeWidth:"1"}),leftTicks.map((t,i)=>{let x=barRightX-leftBarScale(t);return React.createElement("g",{key:`la-${i}`},React.createElement("line",{x1:x,x2:x,y1:axisY,y2:axisY+3,stroke:TEXT_DARK,strokeWidth:"1"}),React.createElement("text",{x,y:axisY+18,textAnchor:"middle",fontSize:Math.max(9,fSize-3),fill:TEXT_MUTED,fontFamily:"sans-serif"},t))}),React.createElement("text",{x:axisLeftX+LEFT_BAR_MAX/2,y:axisY+44,textAnchor:"middle",fontSize:Math.max(10,fSize-2),fill:TEXT_MUTED,fontFamily:"sans-serif"},"Set size"))})()),React.createElement("g",{id:"column-ids"},(()=>{let idLaneY=matrixY+matrixH+idLaneOffset;return intersections.map((inter,i)=>{let cx=colX(i);return React.createElement("text",{key:`cid-${inter.mask}`,id:`column-id-${i+1}`,x:cx,y:idLaneY,textAnchor:"end",dominantBaseline:"middle",fontSize:idFontSize,fontFamily:"monospace",fill:TEXT_MUTED,transform:`rotate(-90 ${cx} ${idLaneY})`},`I${i+1}`)})})()),React.createElement("g",{id:"column-ids-legend"},(()=>{let legendY=matrixY+matrixH+legendOffset;return React.createElement("text",{x:matrixLeftX,y:legendY,textAnchor:"start",dominantBaseline:"hanging",fontSize:legendFS,fontFamily:"sans-serif",fontStyle:"italic",fill:TEXT_MUTED},"I# = intersection id (used as bulk-download filename) \xB7 S# = set id (rows)")})()),React.createElement("g",{id:"matrix"},React.createElement("g",{id:"matrix-columns"},intersections.map((inter,i)=>{let cx=colX(i),inSet=new Set(inter.setIndices),isSelected=selectedMask===inter.mask,idKey=intersectionIdKey(inter.setIndices,setNames),activeRows=inter.setIndices,minR=activeRows.length?rowY(Math.min(...activeRows)):0,maxR=activeRows.length?rowY(Math.max(...activeRows)):0;return React.createElement("g",{key:`col-${inter.mask}`,id:`col-${idKey}`,style:{cursor:"pointer"},onClick:()=>onColumnClick&&onColumnClick(isSelected?null:inter.mask)},React.createElement("rect",{x:cx-colW/2,y:matrixY,width:colW,height:matrixH,fill:isSelected?"#648FFF":"#ffffff",fillOpacity:isSelected?.12:0,pointerEvents:"all"}),activeRows.length>1&&React.createElement("line",{className:"matrix-line",x1:cx,x2:cx,y1:minR,y2:maxR,stroke:DOT_FILL,strokeWidth:Math.max(1.5,dotR/3)}),setNames.map((name,j)=>React.createElement("circle",{key:`d-${j}`,id:`dot-${idKey}-${svgSafeId(name)}`,cx,cy:rowY(j),r:dotR,fill:inSet.has(j)?DOT_FILL:EMPTY_DOT})))}))))}));var UPSET_HOWTO={toolName:"upset",title:"UpSet Plot \u2014 How to use",subtitle:"Set-intersection sizes for 2+ sets, where Venn breaks down",purpose:React.createElement(React.Fragment,null,"Show intersections between many sets at once \u2014 UpSet plots scale gracefully past three sets where Venn diagrams collapse into unreadable shapes."),dataLayout:React.createElement(React.Fragment,null,React.createElement("strong",null,"Wide")," \u2014 one column per set, items stacked in each column."," ",React.createElement("strong",null,"Long")," \u2014 two columns: ",React.createElement("em",null,"item")," and ",React.createElement("em",null,"set"),". Same format as Venn."),display:React.createElement(React.Fragment,null,"Each intersection is a column: top-half bar shows its size, bottom-half dot matrix shows the set membership. Sort by ",React.createElement("strong",null,"size")," (default) or ",React.createElement("strong",null,"degree"),"; filter by minimum size + degree window. Per-intersection significance test against a uniform-random null with BH-adjusted p-values.")};var{useEffect,useMemo}=React;function UploadStep({sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:setSepOverride,onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Arabidopsis stress-response DEGs",subtitle:"5 sets \u2014 Drought \xB7 Heat \xB7 Salt \xB7 Cold \xB7 ABA",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 wide (one column per set, 2+) or long (item, set) \xB7 2 MB max"}),React.createElement(HowTo,{...UPSET_HOWTO}))}function ConfigureStep({fileName,detectedSep,parsedHeaders,parsedRows,allColumnNames,allColumnSets,pendingSelection,setPendingSelection,minDegree,setMinDegree,maxDegree,setMaxDegree}){let selectedCount=pendingSelection.length,needsCutoff=selectedCount>8;useEffect(()=>{if(!needsCutoff){setMinDegree(1),setMaxDegree(1/0);return}setMinDegree(d=>Math.max(1,Math.min(selectedCount,d))),setMaxDegree(d=>Number.isFinite(d)?Math.max(1,Math.min(selectedCount,d)):selectedCount)},[needsCutoff,selectedCount,setMaxDegree,setMinDegree]),useEffect(()=>{Number.isFinite(maxDegree)&&minDegree>maxDegree&&setMinDegree(maxDegree)},[minDegree,maxDegree,setMinDegree]);let allPossible=selectedCount>=2?Math.pow(2,selectedCount)-1:0,effectiveMaxDegree=Number.isFinite(maxDegree)?maxDegree:selectedCount,cutoffPreview=useMemo(()=>{if(!needsCutoff)return null;let pendingSets=new Map;pendingSelection.forEach(n=>pendingSets.set(n,allColumnSets.get(n)));let{membershipMap}=computeMemberships(pendingSelection,pendingSets),all=enumerateIntersections(membershipMap,pendingSelection),kept=all.filter(r=>r.degree>=minDegree&&r.degree<=effectiveMaxDegree).length;return{nonEmpty:all.length,kept}},[needsCutoff,pendingSelection,allColumnSets,minDegree,effectiveMaxDegree]),toggle=name=>{setPendingSelection(prev=>prev.includes(name)?prev.filter(n=>n!==name):[...prev,name])},pickerStatusText="Pick at least 2 sets to plot.",pickerStatusColor="var(--text-muted)";return selectedCount===1?(pickerStatusText="1 selected \u2014 pick at least one more.",pickerStatusColor="var(--warning-text)"):selectedCount>=2&&(pickerStatusText=`${selectedCount} selected \u2014 ready to plot.`,pickerStatusColor="var(--success-text)"),React.createElement("div",null,React.createElement("div",{className:"dv-panel"},React.createElement("p",{className:"dv-tile-title",style:{margin:"0 0 4px"}},"Sets to include"),React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:pickerStatusColor}},pickerStatusText),React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fill, minmax(220px, 1fr))",gap:6}},allColumnNames.map(name=>{let checked=pendingSelection.includes(name),size=allColumnSets.get(name)?.size??0;return React.createElement("label",{key:name,style:{display:"flex",alignItems:"center",gap:8,padding:"6px 10px",borderRadius:6,border:`1px solid ${checked?"var(--accent-primary)":"var(--border)"}`,background:checked?"var(--info-bg)":"var(--surface-subtle)",cursor:"pointer",fontSize:12,color:"var(--text)"}},React.createElement("input",{type:"checkbox",checked,onChange:()=>toggle(name),style:{accentColor:"var(--cta-primary-bg)"}}),React.createElement("span",{style:{fontWeight:600,flex:"1 1 auto",minWidth:0,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},name),React.createElement("span",{style:{color:"var(--text-faint)",fontFamily:"monospace",fontSize:11}},size))}))),needsCutoff&&React.createElement("div",{className:"dv-panel",style:{marginTop:16}},React.createElement("p",{className:"dv-tile-title",style:{margin:"0 0 4px"}},"Intersection cutoff"),React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:"var(--text-muted)"}},"With ",selectedCount," sets, up to ",allPossible.toLocaleString()," intersections are possible. Keep only intersections whose degree falls in this window:"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"}},React.createElement("label",{style:{fontSize:11,color:"var(--text-muted)"}},"Min"),React.createElement(NumberInput,{min:1,max:selectedCount,step:1,value:minDegree,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let clamped=Math.max(1,Math.min(selectedCount,v));setMinDegree(clamped),Number.isFinite(maxDegree)&&clamped>maxDegree&&setMaxDegree(clamped)},style:{width:96}}),React.createElement("label",{style:{fontSize:11,color:"var(--text-muted)"}},"Max"),React.createElement(NumberInput,{min:1,max:selectedCount,step:1,value:effectiveMaxDegree,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let clamped=Math.max(1,Math.min(selectedCount,v));setMaxDegree(clamped),clampeddownloadCsv(["Item"],intersection.items.map(i=>[i]),columnId!=null?`${baseName}_upset_I${columnId}.csv`:`${baseName}_upset_${intersectionFilenamePart(label)}.csv`),className:"dv-btn dv-btn-secondary",style:{background:"var(--success-bg)",border:"1px solid var(--success-border)",color:"var(--success-text)",fontWeight:600,fontSize:11,marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),React.createElement("div",{style:{maxHeight:240,overflowY:"auto",border:"1px solid var(--border)",borderRadius:6,background:"var(--surface-subtle)"}},intersection.items.map((item,i)=>React.createElement("div",{key:i,style:{padding:"3px 10px",fontSize:12,color:"var(--text)",borderBottom:"1px solid var(--border)",fontFamily:"monospace"}},item))))}function PlotControls({activeSetNames,allSets,vis,updVis,chartRef,resetAll,fileName,intersections,computeAllIntersectionStats,clearIntersectionStats,computingStats,computeProgress,intersectionTestsCount,universeSize,setUniverseSize,universeOverridden,setUniverseOverridden,defaultUniverseSize,maxAllIntersectionSize,allIntersectionsCount}){let baseName=fileBaseName(fileName,"upset"),sv=k=>v=>updVis({[k]:v}),universeValid=universeSize!==""&&Number.isFinite(Number(universeSize))&&Number(universeSize)>0;return React.createElement(PlotSidebar,null,React.createElement(DownloadTiles,{chartRef,fileStem:`${baseName}_upset`,onReset:resetAll,extraDownloads:[{label:"Table",title:"Download the currently-plotted intersection table (Intersection, Degree, Size, + per-set flags). Matches the plot exactly \u2014 reflects sort, Top N, Minimum/Maximum degree, and Minimum size filters.",onClick:()=>{let headers=["Intersection","Degree","Size",...activeSetNames],rows=intersections.map(r=>{let label=intersectionLabel(r.setIndices,activeSetNames),flags=activeSetNames.map((_,i)=>r.setIndices.includes(i)?"1":"0");return[label,String(r.degree),String(r.size),...flags]});downloadCsv(headers,rows,`${baseName}_upset_intersections.csv`)}},{label:"Matrix",title:"Download the membership matrix \u2014 one row per item, a 0/1 column for each active set",onClick:()=>{let allItems=new Set;for(let n of activeSetNames){let s=allSets.get(n);if(s)for(let item of s)allItems.add(item)}let headers=["Item",...activeSetNames],rows=[...allItems].sort().map(item=>[item,...activeSetNames.map(n=>allSets.get(n)?.has(item)?"1":"0")]);downloadCsv(headers,rows,`${baseName}_upset_membership.csv`)}},{label:"All regions",title:"One CSV per currently-plotted intersection (named _I1, _I2, \u2026 matching the on-plot identifiers) plus an _index.csv mapping Id \u2192 Intersection, Degree, Size. Your browser may ask once to allow multiple downloads.",onClick:()=>{if(!intersections.length)return;let indexHeaders=["Id","Intersection","Degree","Size"],indexRows=intersections.map((inter,i)=>[`I${i+1}`,intersectionLabel(inter.setIndices,activeSetNames),String(inter.degree),String(inter.size)]);downloadCsv(indexHeaders,indexRows,`${baseName}_upset_index.csv`),intersections.forEach((inter,i)=>{setTimeout(()=>{downloadCsv(["Item"],inter.items.map(item=>[item]),`${baseName}_upset_I${i+1}.csv`)},40*(i+1))})}}]}),React.createElement(ControlSection,{title:"Columns",defaultOpen:!0},React.createElement("div",null,React.createElement("span",{className:"dv-label"},"Sort by"),React.createElement("select",{value:vis.sortMode,onChange:e=>updVis({sortMode:e.target.value}),className:"dv-input",style:{width:"100%"}},React.createElement("option",{value:"size-desc"},"Size (largest first)"),React.createElement("option",{value:"size-asc"},"Size (smallest first)"),React.createElement("option",{value:"degree-desc"},"Degree (highest first)"),React.createElement("option",{value:"degree-asc"},"Degree (lowest first)"),React.createElement("option",{value:"sets"},"Set order"))),React.createElement("label",{style:{display:"block"}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",marginBottom:2}},React.createElement("span",{className:"dv-label"},"Minimum intersection size"),maxAllIntersectionSize>0&&React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"max in data: ",maxAllIntersectionSize.toLocaleString())),React.createElement(NumberInput,{value:vis.minSize,min:0,max:maxAllIntersectionSize>0?maxAllIntersectionSize:void 0,step:1,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let cap=maxAllIntersectionSize>0?maxAllIntersectionSize:v;updVis({minSize:Math.max(0,Math.min(cap,v))})},style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Minimum degree"),React.createElement(NumberInput,{value:vis.minDegree,min:1,max:Math.max(1,activeSetNames.length),step:1,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let cap=Math.max(1,activeSetNames.length),clamped=Math.max(1,Math.min(cap,v)),patch={minDegree:clamped};vis.maxDegree!=null&&clamped>vis.maxDegree&&(patch.maxDegree=clamped),updVis(patch)},style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",marginBottom:2}},React.createElement("span",{className:"dv-label"},"Maximum degree"),activeSetNames.length>0&&React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"max in data: ",activeSetNames.length.toLocaleString())),React.createElement(NumberInput,{value:vis.maxDegree??Math.max(1,activeSetNames.length),min:1,max:Math.max(1,activeSetNames.length),step:1,onChange:e=>{let v=parseInt(e.target.value,10);if(!Number.isFinite(v))return;let cap=Math.max(1,activeSetNames.length),clamped=Math.max(1,Math.min(cap,v)),patch={maxDegree:clamped};clampedupdVis({plotTitle:e.target.value}),className:"dv-input",style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"Subtitle"),React.createElement("input",{value:vis.plotSubtitle,onChange:e=>updVis({plotSubtitle:e.target.value}),className:"dv-input",style:{width:"100%"}}))),React.createElement(ControlSection,{title:"Display"},React.createElement(SliderControl,{label:"Bar opacity",value:vis.barOpacity,min:.3,max:1,step:.05,onChange:sv("barOpacity")}),React.createElement(SliderControl,{label:"Dot size",value:vis.dotSize,min:3,max:12,step:1,onChange:sv("dotSize")}),React.createElement(SliderControl,{label:"Font size",value:vis.fontSize,min:8,max:20,step:1,onChange:sv("fontSize")}),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Intersection size labels"),React.createElement(OnOffToggle,{value:vis.showIntersectionLabels!==!1,onChange:v=>updVis({showIntersectionLabels:v}),ariaLabel:"Intersection size labels"})),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Set size labels"),React.createElement(OnOffToggle,{value:vis.showSetSizeLabels!==!1,onChange:v=>updVis({showSetSizeLabels:v}),ariaLabel:"Set size labels"})),React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},React.createElement("span",{className:"dv-label"},"Background"),React.createElement(ColorInput,{value:vis.plotBg,onChange:sv("plotBg"),size:24}))),React.createElement(ControlSection,{title:"Statistics"},React.createElement("div",null,React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"baseline",marginBottom:2}},React.createElement("span",{className:"dv-label"},"Universe size (N)"),universeOverridden&&Number.isFinite(Number(defaultUniverseSize))&&React.createElement("button",{type:"button",onClick:()=>{setUniverseOverridden(!1),setUniverseSize(defaultUniverseSize||"")},style:{fontSize:10,padding:"1px 8px",background:"none",border:"1px solid var(--border-strong)",borderRadius:4,color:"var(--text-muted)",cursor:"pointer",fontFamily:"inherit"},title:"Revert to the union of uploaded items"},"Reset to |\u222A|=",defaultUniverseSize)),React.createElement(NumberInput,{value:universeSize,min:1,step:1,onChange:e=>{let v=e.target.value;setUniverseSize(v===""?"":Number(v)),setUniverseOverridden(!0)},style:{width:"100%"}}),React.createElement("p",{style:{margin:"4px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},"Defaults to the union of uploaded items (|\u222A|). Override with the genome / proteome / predefined background for real enrichment analyses \u2014 a smaller universe inflates p-values.")),React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",{className:"dv-label"},"Intersection statistics"),React.createElement("button",{type:"button",className:"dv-btn dv-btn-primary",onClick:computeAllIntersectionStats,disabled:computingStats||!universeValid||allIntersectionsCount===0,title:universeValid?computingStats?"Computing\u2026":`Run the SuperExactTest exact test for every one of the ${allIntersectionsCount} intersections in the active set selection and BH-adjust across them. Display filters (minimum size / degree) do NOT change which intersections are tested.`:"Set a Universe size above before computing stats",style:{fontSize:12,padding:"6px 10px",display:"flex",alignItems:"center",justifyContent:"center",gap:6}},computingStats?React.createElement(React.Fragment,null,React.createElement("span",{style:{display:"inline-block",width:12,height:12,border:"2px solid currentColor",borderRightColor:"transparent",borderRadius:"50%",animation:"dv-spin 0.9s linear infinite"},"aria-hidden":"true"}),"Computing ",computeProgress.done,"/",computeProgress.total,"\u2026"):intersectionTestsCount>0?`Recompute stats (${allIntersectionsCount} intersections)`:`Compute stats (${allIntersectionsCount} intersections)`),computingStats&&computeProgress.total>0&&React.createElement("div",{style:{height:3,borderRadius:2,background:"var(--border)",overflow:"hidden"},"aria-hidden":"true"},React.createElement("div",{style:{height:"100%",width:`${computeProgress.done/computeProgress.total*100}%`,background:"var(--accent-primary)",transition:"width 120ms linear"}})),intersectionTestsCount>0&&!computingStats&&React.createElement("button",{type:"button",onClick:clearIntersectionStats,className:"dv-btn dv-btn-secondary",style:{fontSize:11,padding:"3px 8px"}},"Clear ",intersectionTestsCount," cached"," ",intersectionTestsCount===1?"result":"results"),React.createElement("p",{style:{margin:"2px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},"Computes the exact Binomial p (upper tail, lower tail, and the headline two-sided = smaller tail \xD7 2) per intersection, then BH-adjusts each family across every intersection in the active set selection. Display filters (minimum size / degree) only affect what's shown on the plot \u2014 they never change the BH family."),React.createElement("style",null,"@keyframes dv-spin { to { transform: rotate(360deg); } }")),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Significance markers"),React.createElement("div",{style:{display:"flex",borderRadius:6,overflow:"hidden",border:"1px solid var(--border-strong)"}},[["off","Off"],["stars","Stars"],["p-value","p-value"]].map(([mode,label])=>{let active=(vis.significanceDisplay||"off")===mode;return React.createElement("button",{key:mode,type:"button",onClick:()=>updVis({significanceDisplay:mode}),style:{flex:1,padding:"4px 0",fontSize:11,fontWeight:active?700:400,fontFamily:"inherit",cursor:"pointer",border:"none",background:active?"var(--accent-primary)":"var(--surface)",color:active?"var(--on-accent)":"var(--text-muted)",transition:"background 120ms ease, color 120ms ease"}},label)})),React.createElement("p",{style:{margin:"4px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},"Only tested intersections are marked. Uses the two-sided p (smaller tail \xD7 2, BH- adjusted across every test run this session), so both enrichment and depletion show up.")),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Color bars by significance"),React.createElement("div",{style:{display:"flex",borderRadius:6,overflow:"hidden",border:"1px solid var(--border-strong)"}},[[!1,"Off"],[!0,"On"]].map(([value,label])=>{let active=!!vis.colorBarsBySignificance===value;return React.createElement("button",{key:String(value),type:"button",onClick:()=>updVis({colorBarsBySignificance:value}),style:{flex:1,padding:"4px 0",fontSize:11,fontWeight:active?700:400,fontFamily:"inherit",cursor:"pointer",border:"none",background:active?"var(--accent-primary)":"var(--surface)",color:active?"var(--on-accent)":"var(--text-muted)",transition:"background 120ms ease, color 120ms ease"}},label)})),React.createElement("p",{style:{margin:"4px 0 0",fontSize:10,color:"var(--text-faint)",lineHeight:1.4}},React.createElement("span",{style:{color:BAR_FILL_ENRICHED,fontWeight:700}},"Green")," = enriched."," ",React.createElement("span",{style:{color:BAR_FILL_DEPLETED,fontWeight:700}},"Dark red")," = depleted. Both at two-sided p_adj < 0.05, direction from the sign of observed \u2212 expected. Untested or non-significant bars stay black."))))}function IntersectionStatsPanel({intersection,displaySetNames,sets,membershipMap,universeSize,intersectionTests}){let inclusiveSize=React.useMemo(()=>{if(!intersection)return 0;let mask=intersection.mask,count=0;for(let m of membershipMap.values())(m&mask)===mask&&count++;return count},[intersection,membershipMap]);if(!intersection)return null;let selectedSetSizes=intersection.setIndices.map(i=>(sets.get(displaySetNames[i])||new Set).size),selectedSetNames=intersection.setIndices.map(i=>displaySetNames[i]),universeN=typeof universeSize=="number"?universeSize:Number(universeSize),cacheKey=`${intersection.mask}:${universeN}`,cachedResult=intersectionTests.get(cacheKey),fmtP=p=>p==null||!Number.isFinite(p)?"\u2014":p===0?"0":p>=1e-4?p.toPrecision(4):p.toExponential(3),sidebarSection=(label,value,tooltip=null)=>React.createElement("div",{style:{display:"flex",justifyContent:"space-between",gap:16}},React.createElement("span",{style:{color:"var(--text-muted)"}},label),React.createElement("span",{style:{fontFamily:"monospace",color:"var(--text)",cursor:tooltip?"help":void 0},title:tooltip||void 0},value));return React.createElement("div",{className:"dv-panel",style:{marginTop:16,display:"flex",flexDirection:"column",gap:10}},React.createElement("div",{style:{display:"flex",alignItems:"baseline",gap:10}},React.createElement("p",{className:"dv-tile-title",style:{margin:0}},"Intersection significance"),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"SuperExactTest-style exact test against the fixed-margin null")),(()=>{let universeFinite=Number.isFinite(universeN)&&universeN>0,xExclusive=intersection.size,outsideSetSizes=[];for(let j=0;jexpected?"enriched":"depleted":null,directionGlyph=direction==="enriched"?"\u2191 enriched":direction==="depleted"?"\u2193 depleted":direction==="neutral"?"\u2248 as expected":"",directionColor=direction==="enriched"?"var(--accent-plot, #1f6feb)":direction==="depleted"?"var(--warning-text, #b45309)":"var(--text-muted)",fmtExpected=v=>Number.isFinite(v)?v===0?"0":v>=.01&&v<1e3?v.toPrecision(4).replace(/\.?0+$/,""):v.toExponential(3):"\u2014";return React.createElement("div",{style:{display:"grid",gap:4,fontSize:12}},sidebarSection("Sets tested",intersectionShortLabel(intersection.setIndices),selectedSetNames.join(" \u2229 ")),sidebarSection("Set sizes (n\u1D62)",selectedSetSizes.join(", ")),sidebarSection("Exclusive overlap (bar)",React.createElement("span",{style:{display:"inline-flex",gap:8,alignItems:"baseline"}},React.createElement("span",null,xExclusive),direction&&React.createElement("span",{style:{fontSize:11,color:directionColor,fontWeight:600}},directionGlyph))),expectedKnown&&sidebarSection("Expected under null",React.createElement("span",{style:{display:"inline-flex",gap:6,alignItems:"baseline"}},React.createElement("span",null,fmtExpected(expected)),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"},title:"E[exclusive] = N \xB7 \u03A0(n\u1D62/N) \xB7 \u03A0(1 \u2212 n\u2C7C/N) under the independence approximation (each item falls in each set with its marginal probability). Inside: sets the bar covers. Outside: the other uploaded sets."},"= N \xB7 \u03A0(n\u1D62/N) \xB7 \u03A0(1 \u2212 n\u2C7C/N)"))),sidebarSection(React.createElement("span",{style:{color:"var(--text-faint)"}},"Inclusive overlap"),React.createElement("span",{style:{color:"var(--text-faint)"}},inclusiveSize)))})(),cachedResult?React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},(()=>{let two=cachedResult.pTwoSided,twoAdj=cachedResult.pAdjTwoSided,enr=Number.isFinite(cachedResult.pUpper)?cachedResult.pUpper:cachedResult.p,enrAdj=cachedResult.pAdjUpper,dep=cachedResult.pLower,depAdj=cachedResult.pAdjLower,rowStyle={display:"flex",alignItems:"baseline",gap:10,flexWrap:"wrap",fontSize:12},renderRow=(label,hint,p,pAdj)=>React.createElement("div",{style:rowStyle},React.createElement("span",{style:{color:"var(--text-muted)",minWidth:110,display:"inline-block"}},label),React.createElement("span",null,React.createElement("span",{style:{color:"var(--text-muted)"}},"p = "),React.createElement("span",{style:{fontFamily:"monospace",fontWeight:600}},fmtP(p))),React.createElement("span",null,React.createElement("span",{style:{color:"var(--text-muted)"}},"p_adj = "),React.createElement("span",{style:{fontFamily:"monospace",fontWeight:600}},fmtP(pAdj))),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},hint));return React.createElement(React.Fragment,null,renderRow("Two-sided","min(2\xB7pUpper, 2\xB7pLower, 1) \u2014 headline p, drives plot markers + bar colour",two,twoAdj),renderRow("Enrichment","P(X \u2265 bar) \u2014 Binomial(N, p_M), upper tail",enr,enrAdj),renderRow("Depletion","P(X \u2264 bar) \u2014 lower tail",dep,depAdj),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",marginTop:2}},"Each family BH-adjusted separately across ",intersectionTests.size," intersection",intersectionTests.size===1?"":"s"," cached for N=",universeN,". The two-sided p is the honest headline (one test per bar, no cherry-picking); the per-tail rows are there for directional breakdown. The Binomial null assumes each item is independently placed in every set at its marginal rate."))})()):React.createElement("div",{style:{fontSize:12,color:"var(--text-muted)",fontStyle:"italic"}},"No p-value for this intersection yet \u2014 use ",React.createElement("strong",null,"Compute stats")," in the sidebar to run the two-sided Binomial test (plus the per-tail enrichment / depletion breakdown) on the exclusive bar height for every intersection in the current set selection in one pass."))}var{useState,useMemo:useMemo2,useCallback,useRef,useEffect:useEffect2}=React,VIS_INIT_UPSET={plotTitle:"",plotSubtitle:"",plotBg:"#ffffff",fontSize:12,barOpacity:1,dotSize:6,sortMode:"size-desc",minSize:1,minDegree:1,maxDegree:null,showIntersectionLabels:!0,showSetSizeLabels:!0,significanceDisplay:"off",colorBarsBySignificance:!1},EXAMPLE_CSV=`Drought,Heat,Salt,Cold,ABA RD29A,HSP70,SOS1,COR15A,ABI1 RD29B,HSP90,SOS2,COR47,ABI2 NCED3,HSP101,SOS3,COR78,ABI3 @@ -23,4 +23,4 @@ GolS1,,HSFA3,,GolS1 MYB44,,HSFA1B,, DREB2A,,HSF3,, WRKY40,,WRKY40,,`;function App(){let shell=usePlotToolState("upset",VIS_INIT_UPSET),{step,setStep,fileName,setFileName,setParseError,sepOverride,setSepOverride,setCommaFixed,setCommaFixCount,setInjectionWarning,vis,updVis}=shell,[format,setFormat]=useState("wide"),[detectedSep,setDetectedSep]=useState(""),[setNames,setSetNames]=useState([]),[sets,setSets]=useState(new Map),[parsedHeaders,setParsedHeaders]=useState([]),[parsedRows,setParsedRows]=useState([]),[selectedMask,setSelectedMask]=useState(null),[allColumnNames,setAllColumnNames]=useState([]),[allColumnSets,setAllColumnSets]=useState(new Map),[pendingSelection,setPendingSelection]=useState([]),[pendingMinDegree,setPendingMinDegree]=useState(1),[pendingMaxDegree,setPendingMaxDegree]=useState(1/0),[intersectionTests,setIntersectionTests]=useState(new Map),[universeSize,setUniverseSize]=useState(""),[universeOverridden,setUniverseOverridden]=useState(!1),[computingStats,setComputingStats]=useState(!1),[computeProgress,setComputeProgress]=useState({done:0,total:0}),chartRef=useRef(null),displaySetNames=useMemo2(()=>{let copy=setNames.slice();return copy.sort((a,b)=>(sets.get(b)?.size||0)-(sets.get(a)?.size||0)),copy},[setNames,sets]),{allIntersections,membershipMap}=useMemo2(()=>{if(displaySetNames.length<2)return{allIntersections:[],membershipMap:new Map};let{membershipMap:membershipMap2}=computeMemberships(displaySetNames,sets);return{allIntersections:enumerateIntersections(membershipMap2,displaySetNames),membershipMap:membershipMap2}},[displaySetNames,sets]),defaultUniverseSize=membershipMap.size;React.useEffect(()=>{universeOverridden||setUniverseSize(defaultUniverseSize||"")},[defaultUniverseSize,universeOverridden]);let significanceByMask=useMemo2(()=>{let m=new Map,currentN=typeof universeSize=="number"?universeSize:Number(universeSize);if(!Number.isFinite(currentN))return m;for(let entry of intersectionTests.values())entry.universe===currentN&&m.set(entry.mask,{p:entry.p,pAdj:entry.pAdj,pAdjUpper:entry.pAdjUpper,pAdjLower:entry.pAdjLower,pAdjTwoSided:entry.pAdjTwoSided,direction:entry.direction});return m},[intersectionTests,universeSize]),sortedIntersections=useMemo2(()=>sortIntersections(allIntersections,vis.sortMode),[allIntersections,vis.sortMode]),maxAllIntersectionSize=useMemo2(()=>allIntersections.reduce((m,r)=>r.size>m?r.size:m,0),[allIntersections]);React.useEffect(()=>{maxAllIntersectionSize>0&&vis.minSize>maxAllIntersectionSize&&updVis({minSize:maxAllIntersectionSize})},[maxAllIntersectionSize]);let truncatedIntersections=useMemo2(()=>truncateIntersections(sortedIntersections,{minSize:vis.minSize,minDegree:vis.minDegree,maxDegree:vis.maxDegree??1/0}),[sortedIntersections,vis.minSize,vis.minDegree,vis.maxDegree]),computeAllIntersectionStats=useCallback(async()=>{if(computingStats)return;let universeN=typeof universeSize=="number"?universeSize:Number(universeSize);if(!Number.isFinite(universeN)||universeN<=0)return;let bars=allIntersections;if(bars.length===0)return;setComputingStats(!0),setComputeProgress({done:0,total:bars.length});let pending=new Map(intersectionTests),CHUNK_SIZE=16;for(let i=0;i(sets.get(displaySetNames[idx])||new Set).size),outsideSizes=[];for(let j=0;jexpected?"enriched":"depleted",pUpper=multisetExclusiveP(xExclusive,insideSizes,outsideSizes,universeN,{tail:"upper"}),pLower=multisetExclusiveP(xExclusive,insideSizes,outsideSizes,universeN,{tail:"lower"}),pTwoSided=Number.isFinite(pUpper)&&Number.isFinite(pLower)?Math.min(1,2*Math.min(pUpper,pLower)):NaN,key=`${inter.mask}:${universeN}`;pending.set(key,{mask:inter.mask,universe:universeN,xExclusive,insideSizes,outsideSizes,expected,direction,p:pTwoSided,pUpper,pLower,pTwoSided,pAdj:null,pAdjUpper:null,pAdjLower:null,pAdjTwoSided:null}),((i+1)%CHUNK_SIZE===0||i===bars.length-1)&&(setComputeProgress({done:i+1,total:bars.length}),await new Promise(r=>setTimeout(r,0)))}let matching=[...pending.values()].filter(e=>e.universe===universeN&&Number.isFinite(e.pUpper)&&Number.isFinite(e.pLower)&&Number.isFinite(e.pTwoSided)),adjUpper=bhAdjust(matching.map(e=>e.pUpper)),adjLower=bhAdjust(matching.map(e=>e.pLower)),adjTwoSided=bhAdjust(matching.map(e=>e.pTwoSided));matching.forEach((e,j)=>{e.pAdjUpper=adjUpper[j],e.pAdjLower=adjLower[j],e.pAdjTwoSided=adjTwoSided[j],e.pAdj=adjTwoSided[j]}),setIntersectionTests(pending),setComputingStats(!1),setComputeProgress({done:0,total:0})},[computingStats,universeSize,allIntersections,intersectionTests,sets,displaySetNames]),clearIntersectionStats=useCallback(()=>{setIntersectionTests(new Map)},[]),canNavigate=useCallback(target=>target==="upload"?!0:target==="configure"?allColumnNames.length>=2:target==="plot"?step==="configure"?pendingSelection.length>=2:displaySetNames.length>=2:!1,[allColumnNames,displaySetNames,step,pendingSelection]),commitSelection=useCallback((names,allSets)=>{let chosen=new Map;names.forEach(n=>chosen.set(n,allSets.get(n))),setSetNames(names),setSets(chosen),setSelectedMask(null)},[]),navigateStep=useCallback(target=>{target==="plot"&&step==="configure"&&pendingSelection.length>=2&&((pendingSelection.length!==setNames.length||pendingSelection.some(n=>!setNames.includes(n)))&&commitSelection(pendingSelection,allColumnSets),updVis({minDegree:Math.max(1,pendingMinDegree||1),maxDegree:Number.isFinite(pendingMaxDegree)?pendingMaxDegree:null})),setStep(target)},[step,pendingSelection,setNames,allColumnSets,commitSelection,setStep,pendingMinDegree,pendingMaxDegree,updVis]),doParse=useCallback((text,sep,fmt)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count);let{headers,rows,injectionWarnings}=parseRaw(dc.text,effectiveSep);if(setInjectionWarning(injectionWarnings),!headers.length||!rows.length){setParseError("The file appears to be empty or has no data rows.");return}let effectiveFmt=fmt;fmt!=="long"&&headers.length===2&&detectLongFormat(headers,rows).isLong&&(effectiveFmt="long",setFormat("long"));let parsed;try{parsed=effectiveFmt==="long"?parseLongFormatSets(headers,rows):parseSetData(headers,rows)}catch(e){let msg=e instanceof Error?e.message:"";setParseError(msg||"Unable to parse set membership.");return}let{setNames:sn,sets:ss}=parsed;if(sn.length<2){setParseError(effectiveFmt==="long"?"Need at least 2 distinct set names in the second column.":"Need at least 2 non-empty set columns.");return}setParseError(null),setParsedHeaders(headers),setParsedRows(rows),setAllColumnNames(sn),setAllColumnSets(ss),setPendingSelection(sn),commitSelection(sn,ss),setStep("configure")},[commitSelection,setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep]),handleFileLoad=useCallback((text,name)=>{setFileName(name),doParse(text,sepOverride,format)},[sepOverride,format,doParse,setFileName]),handleTextPaste=useCallback((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"",format)},[doParse,setFileName,setSepOverride,format]),loadExample=useCallback(()=>{let text=EXAMPLE_CSV;text&&(setSepOverride(""),setFormat("wide"),setFileName("arabidopsis_stress_5set.csv"),doParse(text,"","wide"))},[doParse,setFileName,setSepOverride]),handleHandoff=useCallback(payload=>{if(!payload||typeof payload.text!="string"||payload.text.length>FILE_LIMIT_BYTES)return;let sep=typeof payload.sep=="string"?payload.sep:"",fmt=payload.format==="long"?"long":"wide",safeName=(typeof payload.fileName=="string"?payload.fileName:"").slice(0,255).replace(/[/\\]/g,"_").replace(/^\.+/,"");setFileName(safeName),setSepOverride(sep),setFormat(fmt),setSelectedMask(null),doParse(payload.text,sep,fmt)},[doParse,setFileName,setSepOverride]);useEffect2(()=>{let consume=()=>{try{let raw=sessionStorage.getItem("dataviz-upset-handoff");if(!raw)return;sessionStorage.removeItem("dataviz-upset-handoff"),handleHandoff(JSON.parse(raw))}catch{}};consume();let onSameTabHandoff=()=>consume(),onMessage=e=>{if(!e||e.origin!==window.location.origin)return;let d=e.data;!d||d.type!=="dataviz-handoff"||handleHandoff(d)};return window.addEventListener("plottr-handoff",onSameTabHandoff),window.addEventListener("message",onMessage),()=>{window.removeEventListener("plottr-handoff",onSameTabHandoff),window.removeEventListener("message",onMessage)}},[handleHandoff]);let resetAll=()=>{setStep("upload"),setFileName(""),setSetNames([]),setSets(new Map),setParseError(null),setInjectionWarning(null),setSelectedMask(null),updVis({_reset:!0})},setSizes=useMemo2(()=>{let m=new Map;for(let n of displaySetNames)m.set(n,(sets.get(n)||new Set).size);return m},[displaySetNames,sets]),selectedIntersectionIdx=truncatedIntersections.findIndex(g=>g.mask===selectedMask),selectedIntersection=selectedIntersectionIdx>=0?truncatedIntersections[selectedIntersectionIdx]:null,selectedColumnId=selectedIntersectionIdx>=0?selectedIntersectionIdx+1:null,showColumnWarning=truncatedIntersections.length>60;return React.createElement(PlotToolShell,{state:{...shell,setStep:navigateStep},toolName:"upset",title:"UpSet Plot",visInit:VIS_INIT_UPSET,steps:["upload","configure","plot"],canNavigate},step==="upload"&&React.createElement(UploadStep,{sepOverride,setSepOverride,handleFileLoad,handleTextPaste,onLoadExample:loadExample}),step==="configure"&&allColumnNames.length>=2&&React.createElement(ConfigureStep,{fileName,detectedSep,parsedHeaders,parsedRows,allColumnNames,allColumnSets,pendingSelection,setPendingSelection,minDegree:pendingMinDegree,setMinDegree:setPendingMinDegree,maxDegree:pendingMaxDegree,setMaxDegree:setPendingMaxDegree}),step==="plot"&&displaySetNames.length>=2&&React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotControls,{activeSetNames:displaySetNames,allSets:sets,vis,updVis,chartRef,resetAll,fileName,intersections:truncatedIntersections,computeAllIntersectionStats,clearIntersectionStats,computingStats,computeProgress,intersectionTestsCount:intersectionTests.size,universeSize,setUniverseSize,universeOverridden,setUniverseOverridden,defaultUniverseSize,maxAllIntersectionSize,allIntersectionsCount:allIntersections.length}),React.createElement("div",{style:{flex:1,minWidth:0,position:"relative"}},selectedMask!=null&&React.createElement("button",{type:"button",onClick:()=>setSelectedMask(null),className:"dv-btn dv-btn-secondary",style:{position:"absolute",top:10,right:14,zIndex:2,padding:"4px 10px",fontSize:11}},"Clear selection"),React.createElement(ScrollablePlotCard,null,React.createElement(UpsetChart,{ref:chartRef,setNames:displaySetNames,setSizes,intersections:truncatedIntersections,selectedMask,onColumnClick:setSelectedMask,plotTitle:vis.plotTitle,plotSubtitle:vis.plotSubtitle,plotBg:vis.plotBg,fontSize:vis.fontSize,barOpacity:vis.barOpacity,dotSize:vis.dotSize,showIntersectionLabels:vis.showIntersectionLabels,showSetSizeLabels:vis.showSetSizeLabels,significanceDisplay:vis.significanceDisplay,significanceByMask,colorBarsBySignificance:vis.colorBarsBySignificance})),showColumnWarning&&React.createElement("div",{style:{margin:"8px 0 0",padding:"6px 12px",borderRadius:6,background:"var(--warning-bg)",border:"1px solid var(--warning-border)",fontSize:11,color:"var(--warning-text)"}},truncatedIntersections.length," columns \u2014 dots may overlap. Raise Minimum intersection size, raise Minimum degree, or lower Maximum degree to reduce."),truncatedIntersections.length===0&&React.createElement("div",{style:{margin:"8px 0 0",padding:"6px 12px",borderRadius:6,background:"var(--info-bg)",border:"1px solid var(--info-border)",fontSize:11,color:"var(--info-text)"}},"No intersections to show. Lower Minimum intersection size, lower Minimum degree, or raise Maximum degree."),selectedIntersection&&React.createElement(IntersectionStatsPanel,{intersection:selectedIntersection,displaySetNames,sets,membershipMap,universeSize,intersectionTests}),React.createElement("div",{className:"dv-panel",style:{marginTop:16}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Items"),React.createElement(ItemListPanel,{intersection:selectedIntersection,setNames:displaySetNames,fileName,columnId:selectedColumnId}))))))}export{App}; -//# sourceMappingURL=app-G5L2IE5E.js.map +//# sourceMappingURL=app-P3YNIKGG.js.map diff --git a/tools/_app/chunks/app-G5L2IE5E.js.map b/tools/_app/chunks/app-P3YNIKGG.js.map similarity index 100% rename from tools/_app/chunks/app-G5L2IE5E.js.map rename to tools/_app/chunks/app-P3YNIKGG.js.map diff --git a/tools/_app/chunks/app-5CQNAWMD.js b/tools/_app/chunks/app-PCLQZVY3.js similarity index 99% rename from tools/_app/chunks/app-5CQNAWMD.js rename to tools/_app/chunks/app-PCLQZVY3.js index 5c847525..618d9668 100644 --- a/tools/_app/chunks/app-5CQNAWMD.js +++ b/tools/_app/chunks/app-PCLQZVY3.js @@ -1,4 +1,4 @@ -import{makeTicks}from"./chunk-T4V5V5TK.js";import{COLOR_PALETTES,ColorInput,ControlSection,DIVERGING_PALETTES,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,NumberInput,PALETTE,PlotSidebar,PlotToolShell,SliderControl,UploadPanel,autoDetectSep,downloadCsv,downloadText,fileBaseName,fixDecimalCommas,formatRNumber,interpolateColor,isNumericValue,parseData,registerSvgExportMutator,resolveDiscretePalette,sanitizeRString,toNumericValue,unregisterSvgExportMutator,usePlotToolState,wrapRItems}from"./chunk-5F6S3FYW.js";var VOLCANO_HOWTO={toolName:"volcano",title:"Volcano Plot \u2014 How to use",subtitle:"One row per feature \xB7 log\u2082FC on X \xB7 p-value (\u2212log\u2081\u2080) on Y",purpose:React.createElement(React.Fragment,null,"Highlight differentially expressed features by combining ",React.createElement("strong",null,"fold change")," with"," ",React.createElement("strong",null,"statistical significance")," \u2014 the canonical way to inspect RNA-seq, proteomics, or metabolomics tables."),dataLayout:React.createElement(React.Fragment,null,"One ",React.createElement("strong",null,"row")," per feature. Two numeric columns: a ",React.createElement("strong",null,"log\u2082 fold change")," ","and a ",React.createElement("strong",null,"p-value")," (raw or adjusted). An optional ",React.createElement("strong",null,"label")," column (gene symbol, feature ID) drives annotations. DESeq2, limma, edgeR, MaxQuant column names auto-detect."),display:React.createElement(React.Fragment,null,"Tweak ",React.createElement("strong",null,"|log\u2082FC|")," + ",React.createElement("strong",null,"p")," cutoffs in the Thresholds tile to set the up / down / ns split. Label features via auto top-N, click-to-label, or paste-list search in the Labels tile. Optional colour and size aesthetic mappings (e.g. expression level) render in-SVG legends.")};var VOLCANO_DEFAULT_COLORS={up:"#D55E00",down:"#0072B2",ns:"#737373"};function classifyPoint(log2fc,p,fcCutoff,pCutoff){return!Number.isFinite(log2fc)||!Number.isFinite(p)||!(pfcCutoff?"up":log2fc<-fcCutoff?"down":"ns"}function computePFloor(points){let minNonZero=1/0;for(let pt of points)Number.isFinite(pt.p)&&pt.p>0&&pt.ppt.label==null?null:pt.label.trim().toLocaleLowerCase()),matchedIdx=new Set,tokenHit=new Array(tokens.length).fill(!1);for(let i=0;i=0;yCol<0&&(yCol=findFirstMatch(headers,Y_RAW_PATTERNS));let labelCol=findFirstMatch(headers,LABEL_PATTERNS);return{xCol,yCol,labelCol,yIsAdjusted}}function pickTopLabels(points,nUp,nDown,fcCutoff,pCutoff,pFloor){let ups=[],downs=[];for(let i=0;ib.score-a.score),downs.sort((a,b)=>b.score-a.score);let out=[];if(nUp>0)for(let e of ups.slice(0,nUp))out.push(e);if(nDown>0)for(let e of downs.slice(0,nDown))out.push(e);return out}var LEADER_DISTANCES=[38,56,80,108],LABEL_BBOX_PAD=1,LABEL_ANGLES_DEG=[-90,-75,-105,-60,-120,-45,-135,-30,-150,-15,-165,0,180,15,165,30,150,45,135,60,120,75,105,90];function within(bbox,bounds){return bbox.x>=bounds.x&&bbox.y>=bounds.y&&bbox.x+bbox.w<=bounds.x+bounds.w&&bbox.y+bbox.h<=bounds.y+bounds.h}function lineSegmentIntersectsCircle(x1,y1,x2,y2,cx,cy,r){let dx=x2-x1,dy=y2-y1,len2=dx*dx+dy*dy,t;len2<1e-9?t=0:(t=((cx-x1)*dx+(cy-y1)*dy)/len2,t<0?t=0:t>1&&(t=1));let px=x1+t*dx,py=y1+t*dy,ddx=cx-px,ddy=cy-py;return ddx*ddx+ddy*ddy<=r*r}function lineSegmentsIntersect(ax1,ay1,ax2,ay2,bx1,by1,bx2,by2){let d1x=ax2-ax1,d1y=ay2-ay1,d2x=bx2-bx1,d2y=by2-by1,denom=d1x*d2y-d1y*d2x;if(Math.abs(denom)<1e-9)return!1;let dx=bx1-ax1,dy=by1-ay1,t=(dx*d2y-dy*d2x)/denom,u=(dx*d1y-dy*d1x)/denom;return t>1e-6&&t<1-1e-6&&u>1e-6&&u<1-1e-6}function nearestPointOnBbox(bbox,sx,sy){let nx=Math.max(bbox.x,Math.min(sx,bbox.x+bbox.w)),ny=Math.max(bbox.y,Math.min(sy,bbox.y+bbox.h));return{x:nx,y:ny}}function pointInPaddedRect(px,py,bbox,pad){return px>=bbox.x-pad&&px<=bbox.x+bbox.w+pad&&py>=bbox.y-pad&&py<=bbox.y+bbox.h+pad}var PENALTY_BBOX_ON_POINT=200,PENALTY_LEADER_ON_POINT=50,PENALTY_LEADER_CROSS=50,PENALTY_BBOX_OVERLAP_PER_100PX2=1;function evaluateCandidate(inp,obstacles,bounds,placed,angleDeg,distance){let w=inp.charWidth*inp.text.length,h=inp.lineHeight,sx=inp.pointPx.x,sy=inp.pointPx.y,rad=angleDeg*Math.PI/180,cosA=Math.cos(rad),sinA=Math.sin(rad),labelCx=sx+distance*cosA,labelCy=sy+distance*sinA,bbox={x:labelCx-w/2,y:labelCy-h/2,w,h};if(!within(bbox,bounds))return null;let startX=sx+inp.ringRadius*cosA,startY=sy+inp.ringRadius*sinA,leaderEnd=nearestPointOnBbox(bbox,sx,sy),labelOverlapArea=0;for(let p of placed){let ix1=Math.max(bbox.x,p.bbox.x),iy1=Math.max(bbox.y,p.bbox.y),ix2=Math.min(bbox.x+bbox.w,p.bbox.x+p.bbox.w),iy2=Math.min(bbox.y+bbox.h,p.bbox.y+p.bbox.h);ix2>ix1&&iy2>iy1&&(labelOverlapArea+=(ix2-ix1)*(iy2-iy1))}let bboxHitsPointCount=0;for(let obs of obstacles){let ox=obs.x-sx,oy=obs.y-sy;ox*ox+oy*oy<1||pointInPaddedRect(obs.x,obs.y,bbox,obs.r+LABEL_BBOX_PAD)&&bboxHitsPointCount++}let leaderHitsPointCount=0;for(let obs of obstacles){let ox=obs.x-sx,oy=obs.y-sy;ox*ox+oy*oy<1||lineSegmentIntersectsCircle(startX,startY,leaderEnd.x,leaderEnd.y,obs.x,obs.y,obs.r+.5)&&leaderHitsPointCount++}let leaderCrossCount=0;for(let p of placed)lineSegmentsIntersect(startX,startY,leaderEnd.x,leaderEnd.y,p.leaderStart.x,p.leaderStart.y,p.leaderEnd.x,p.leaderEnd.y)&&leaderCrossCount++;let penalty=labelOverlapArea*(PENALTY_BBOX_OVERLAP_PER_100PX2/100)+leaderCrossCount*PENALTY_LEADER_CROSS+leaderHitsPointCount*PENALTY_LEADER_ON_POINT+bboxHitsPointCount*PENALTY_BBOX_ON_POINT;return{placement:{pointPx:{x:sx,y:sy},textPx:{x:labelCx,y:labelCy+h/4},bbox,text:inp.text,leaderStart:{x:startX,y:startY},leaderEnd,forced:!1},penalty}}var REFINE_RANGE_DEG=7.5,REFINE_STEPS_LEVEL_1=5,REFINE_STEPS_LEVEL_2=3;function refineForcedAngle(inp,obstacles,bounds,placed,baseAngleDeg,baseDistance,basePenalty,baseCandidate){let best={placement:baseCandidate,penalty:basePenalty},bestLevel1Angle=baseAngleDeg;for(let i=0;ii)];let idxs=Array.from({length:n},(_,i)=>i),inputOrder=[...idxs],reverseOrder=[...idxs].reverse(),isolation=idxs.map(i=>{let minD2=1/0;for(let j of idxs){if(j===i)continue;let dx=inputs[i].pointPx.x-inputs[j].pointPx.x,dy=inputs[i].pointPx.y-inputs[j].pointPx.y,d2=dx*dx+dy*dy;d2b.isolation-a.isolation);let isolatedFirst=isolation.map(e=>e.idx),clusteredFirst=[...isolatedFirst].reverse();return[inputOrder,reverseOrder,isolatedFirst,clusteredFirst]}function layoutLabels(inputs,obstacles,bounds){if(inputs.length===0)return[];let orderings=buildOrderings(inputs),best=greedyPass(inputs,obstacles,bounds,orderings[0]);for(let i=1;i{s!==""&&(total++,unique.add(s),isNumericString(s)&&numeric++)};if(pointIndices)for(let idx of pointIndices)inspect(rawCell(rawData,idx,col));else for(let row of rawData){let v=row[col];inspect(v==null?"":String(v))}return total===0?"discrete":numeric/total>.8&&unique.size>12?"continuous":"discrete"}function buildColorMap(args){let{rawData,pointIndices,col,paletteStops,paletteName,discretePalette,interpolate}=args;if(col<0)return null;if(detectColorMapType(rawData,col,pointIndices)==="continuous"){let vmin=1/0,vmax=-1/0,numeric=[];for(let idx of pointIndices){let raw=rawCell(rawData,idx,col);if(!isNumericString(raw))continue;let n=Number(raw);numeric.push({idx,v:n}),nvmax&&(vmax=n)}(!Number.isFinite(vmin)||!Number.isFinite(vmax)||vmin===vmax)&&(vmin=0,vmax=1);let colorByIdx2=new Map;for(let{idx,v}of numeric){let t=vmax>vmin?(v-vmin)/(vmax-vmin):.5;colorByIdx2.set(idx,interpolate(paletteStops,Math.max(0,Math.min(1,t))))}return{type:"continuous",colorByIdx:colorByIdx2,vmin,vmax,paletteName,paletteStops:paletteStops.slice()}}let seen=new Map,order=[];for(let idx of pointIndices){let raw=rawCell(rawData,idx,col);if(raw!==""&&!seen.has(raw)){let color=discretePalette[seen.size%discretePalette.length];seen.set(raw,color),order.push(raw)}}let colorByIdx=new Map;for(let idx of pointIndices){let raw=rawCell(rawData,idx,col),c=seen.get(raw);c!=null&&colorByIdx.set(idx,c)}return{type:"discrete",colorByIdx,legend:order.map(value=>({value,color:seen.get(value)}))}}function buildSizeMap(rawData,pointIndices,col,minR,maxR){if(col<0)return null;let vmin=1/0,vmax=-1/0,numeric=[];for(let idx of pointIndices){let raw=rawCell(rawData,idx,col);if(raw==="")continue;let n=Number(raw);Number.isFinite(n)&&(numeric.push({idx,v:n}),nvmax&&(vmax=n))}if(numeric.length===0)return null;let byIdx=new Map;if(!Number.isFinite(vmin)||!Number.isFinite(vmax)||vmin===vmax){let mid=(minR+maxR)/2;for(let{idx}of numeric)byIdx.set(idx,mid);return{byIdx,vmin:Number.isFinite(vmin)?vmin:0,vmax:Number.isFinite(vmax)?vmax:0,minR,maxR}}let span=vmax-vmin;for(let{idx,v}of numeric){let t=(v-vmin)/span;byIdx.set(idx,minR+t*(maxR-minR))}return{byIdx,vmin,vmax,minR,maxR}}function buildPoints(rawData,xCol,yCol,labelCol){let out=[];for(let i=0;i=0&&row[labelCol]!=null&&row[labelCol]!==""?String(row[labelCol]):null,label=labelRaw==null?null:labelRaw.trim()||null;out.push({idx:i,log2fc,p,label})}return out}function eligibleColumns(parsed,xCol,yCol,labelCol){let used=new Set([xCol,yCol]);return(parsed?.headers??[]).map((h,i)=>({h,i})).filter(({i})=>!used.has(i))}var{useState,useEffect,useMemo}=React,VOLCANO_AES_THEMES={x:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"X axis \xB7 log\u2082 fold change"},y:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Y axis \xB7 p-value (\u2212log\u2081\u2080)"},label:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Feature label (optional)"},colorMap:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Color"},sizeMap:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Size"}};function VolcanoAesBox({theme,children}){let t=VOLCANO_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function ToggleRow({checked,onChange,children}){return React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4}},React.createElement("span",{className:"dv-label"},children),React.createElement("div",{className:"dv-seg"},React.createElement("button",{type:"button",onClick:()=>onChange(!0),"aria-pressed":checked,className:"dv-seg-btn"+(checked?" dv-seg-btn-active":""),style:{fontSize:12}},"On"),React.createElement("button",{type:"button",onClick:()=>onChange(!1),"aria-pressed":!checked,className:"dv-seg-btn"+(checked?"":" dv-seg-btn-active"),style:{fontSize:12}},"Off")))}function ThresholdsTile({vis,updVis}){let onFcChange=e=>{let v=parseFloat(e.target.value);Number.isFinite(v)&&updVis({fcCutoff:Math.max(0,Math.min(10,v))})},P_OPTIONS=[{value:1,label:"None"},{value:.05,label:"0.05"},{value:.01,label:"0.01"},{value:.001,label:"0.001"}],pPickValue=P_OPTIONS.find(o=>Math.abs(o.value-vis.pCutoff)<1e-12)?.value??.05;return React.createElement(ControlSection,{title:"Thresholds",defaultOpen:!0},React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"|log2FC| cutoff"),React.createElement(NumberInput,{value:vis.fcCutoff,min:0,max:10,step:.1,onChange:onFcChange,style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"p-value cutoff"),React.createElement("div",{className:"dv-seg"},P_OPTIONS.map(o=>{let active=pPickValue===o.value;return React.createElement("button",{key:o.value,type:"button","aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),style:{fontSize:12},onClick:()=>updVis({pCutoff:o.value})},o.label)}))),React.createElement(ToggleRow,{checked:vis.showRefLines,onChange:v=>updVis({showRefLines:v})},"Show reference lines"))}function ColorsTile({vis,updVis}){let handlePalette=next=>{let seed=resolveDiscretePalette(next,2);updVis({discretePalette:next,colorUp:seed[0]||vis.colorUp,colorDown:seed[1]||vis.colorDown,colorNs:VOLCANO_DEFAULT_COLORS.ns})};return React.createElement(ControlSection,{title:"Colors"},React.createElement(DiscretePaletteRow,{value:vis.discretePalette||"okabe-ito",onChange:handlePalette,names:["up","down","ns"]}),React.createElement(ColorRow,{label:"Up-regulated",value:vis.colorUp,onChange:v=>updVis({colorUp:v})}),React.createElement(ColorRow,{label:"Down-regulated",value:vis.colorDown,onChange:v=>updVis({colorDown:v})}),React.createElement(ColorRow,{label:"Not significant",value:vis.colorNs,onChange:v=>updVis({colorNs:v})}))}function ColorRow({label,value,onChange}){return React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8}},React.createElement("span",{style:{fontSize:12,color:"var(--text)"}},label),React.createElement(ColorInput,{value,onChange,size:20}))}function LabelSearchRow({points,labelCol,addToManualSelection}){let[query,setQuery]=useState(""),[previewQuery,setPreviewQuery]=useState(""),[showUnmatched,setShowUnmatched]=useState(!1),labelLowerCache=useMemo(()=>points.map(pt=>pt.label==null?null:pt.label.toLocaleLowerCase()),[points]);useEffect(()=>{let id=setTimeout(()=>setPreviewQuery(query),150);return()=>clearTimeout(id)},[query]);let preview=useMemo(()=>previewQuery.trim()?matchPointsByLabel(points,previewQuery,labelLowerCache):{matched:[],unmatchedTokens:[]},[previewQuery,points,labelLowerCache]),disabled=labelCol==null||labelCol<0,matchCount=preview.matched.length,unmatchedCount=preview.unmatchedTokens.length,hasQuery=previewQuery.trim().length>0,hasOnlyUnmatched=hasQuery&&matchCount===0,tooMany=matchCount>50,submit=()=>{disabled||matchCount===0||(addToManualSelection(preview.matched),setQuery(""),setPreviewQuery(""),setShowUnmatched(!1))};return React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4}},React.createElement("div",{className:"dv-label"},"Search by name"),React.createElement("div",{style:{display:"flex",gap:6}},React.createElement("input",{type:"search",className:"dv-input",value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{e.key==="Enter"&&(e.preventDefault(),submit())},placeholder:"gene name (or paste a list)",disabled,style:{flex:1,fontSize:11,padding:"3px 6px"},title:"Comma- or newline-separated. Case-insensitive substring."}),React.createElement("button",{type:"button",onClick:submit,disabled:disabled||matchCount===0,className:"dv-btn dv-btn-secondary",style:{padding:"4px 10px",fontSize:11},title:disabled?"Pick a label column in Configure to enable search":matchCount===0?"Type a name to search":`Add ${matchCount} matched point${matchCount===1?"":"s"} to the labelled set`},"Add")),disabled?React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic"}},"\u21B3 Pick a label column in Configure to enable search"):hasQuery?React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:2}},React.createElement("span",{style:{fontSize:10,color:hasOnlyUnmatched||tooMany?"var(--warning-text)":"var(--success-text)"}},matchCount===0?"no matches":`${matchCount} match${matchCount===1?"":"es"}`+(tooMany?" \u2014 labels may overlap":"")+(unmatchedCount>0?` \xB7 ${unmatchedCount} unmatched`:"")),unmatchedCount>0&&React.createElement("button",{type:"button",onClick:()=>setShowUnmatched(v=>!v),style:{alignSelf:"flex-start",background:"none",border:"none",padding:0,fontSize:10,color:"var(--text-faint)",fontFamily:"inherit",cursor:"pointer",textDecoration:"underline"},title:"Toggle the list of tokens that matched zero points"},showUnmatched?"hide unmatched":"show unmatched"),showUnmatched&&unmatchedCount>0&&React.createElement("ul",{style:{margin:"2px 0 0",padding:"4px 8px 4px 18px",fontSize:10,fontFamily:"monospace",color:"var(--warning-text)",background:"var(--warning-bg)",border:"1px solid var(--warning-border)",borderRadius:4,maxHeight:80,overflowY:"auto"}},preview.unmatchedTokens.map(t=>React.createElement("li",{key:t},t)))):React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic"}},"\u21B3 Comma- or newline-separated \xB7 case-insensitive substring"))}function LabelsTile({vis,updVis,manualSelection,clearManualSelection,points,labelCol,addToManualSelection,labelDensity}){let manualCount=manualSelection?manualSelection.size:0,hasManual=manualCount>0,forcedCount=labelDensity&&labelDensity.forcedCount||0,attemptedCount=labelDensity&&labelDensity.attemptedCount||0,showDensityWarning=!hasManual&&forcedCount>0&&attemptedCount>0,cleanBudget=Math.max(0,attemptedCount-forcedCount),totalRequested=(vis.topNUp||0)+(vis.topNDown||0),ratioUp=totalRequested>0?(vis.topNUp||0)/totalRequested:.5,suggestedUp=Math.max(0,Math.round(cleanBudget*ratioUp)),suggestedDown=Math.max(0,cleanBudget-suggestedUp);return React.createElement(ControlSection,{title:"Labels",defaultOpen:hasManual},React.createElement(ToggleRow,{checked:vis.showLabels,onChange:v=>updVis({showLabels:v})},"Annotate top features"),React.createElement(LabelSearchRow,{points,labelCol,addToManualSelection}),hasManual?React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,padding:"8px 10px",borderRadius:6,background:"var(--info-bg)",border:"1px solid var(--info-border)"}},React.createElement("span",{style:{fontSize:11,color:"var(--info-text)"}},manualCount," point",manualCount===1?"":"s"," clicked"),React.createElement("button",{onClick:clearManualSelection,className:"dv-btn dv-btn-secondary",style:{padding:"4px 10px",fontSize:11},title:"Clear the manual selection \u2014 labelling falls back to the auto top-N picks"},"Clear")):React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic"}},"\u21B3 Click any point on the chart to label it directly"),React.createElement(SliderControl,{label:"Top up-regulated",value:vis.topNUp,displayValue:String(vis.topNUp),min:0,max:50,step:1,onChange:v=>updVis({topNUp:Number(v)})}),React.createElement(SliderControl,{label:"Top down-regulated",value:vis.topNDown,displayValue:String(vis.topNDown),min:0,max:50,step:1,onChange:v=>updVis({topNDown:Number(v)})}),React.createElement(SliderControl,{label:"Font size",value:vis.labelFontSize,displayValue:String(vis.labelFontSize),min:8,max:16,step:1,onChange:v=>updVis({labelFontSize:Number(v)})}),showDensityWarning&&React.createElement("div",{role:"status",style:{display:"flex",flexDirection:"column",gap:6,padding:"8px 10px",borderRadius:6,background:"var(--warning-bg)",border:"1px solid var(--warning-border)",fontSize:11,color:"var(--warning-text)",marginTop:6}},React.createElement("span",null,"\u26A0 ",forcedCount," of ",attemptedCount," labels couldn't place cleanly at this data density."),cleanBudget>0&&cleanBudgetupdVis({topNUp:suggestedUp,topNDown:suggestedDown}),className:"dv-btn dv-btn-secondary",style:{alignSelf:"flex-start",padding:"3px 10px",fontSize:11},title:"Drop top-N to ("+suggestedUp+" up / "+suggestedDown+" down) so every label places without overlap."},"Use suggested (",suggestedUp," / ",suggestedDown,")")))}function StyleTile({vis,updVis}){return React.createElement(ControlSection,{title:"Style"},React.createElement(SliderControl,{label:"Plot width",value:vis.plotWidth,displayValue:String(Math.round(vis.plotWidth))+" px",min:600,max:1600,step:20,onChange:v=>updVis({plotWidth:Number(v)})}),React.createElement(SliderControl,{label:"Point radius",value:vis.pointRadius,displayValue:vis.pointRadius.toFixed(1),min:1,max:8,step:.5,onChange:v=>updVis({pointRadius:Number(v)})}),React.createElement(SliderControl,{label:"Point alpha",value:vis.pointAlpha,displayValue:vis.pointAlpha.toFixed(2),min:.1,max:1,step:.05,onChange:v=>updVis({pointAlpha:Number(v)})}),React.createElement(ToggleRow,{checked:vis.showAxes,onChange:v=>updVis({showAxes:v})},"Show grid"),React.createElement("label",{style:{display:"flex",flexDirection:"column",gap:4,fontSize:12,color:"var(--text)"}},"Plot title",React.createElement("input",{type:"text",className:"dv-input",value:vis.plotTitle,onChange:e=>updVis({plotTitle:e.target.value}),placeholder:"(optional)"})))}function PaletteStrip({palette,invert=!1,height=12}){let base=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,stops=invert?[...base].reverse():base,n=48;return React.createElement("div",{style:{display:"flex",width:"100%",height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border)"}},Array.from({length:n},(_,i)=>React.createElement("div",{key:i,style:{flex:1,background:interpolateColor(stops,i/(n-1))}})))}function ColorMapTile({parsed,xCol,yCol,labelCol,col,setCol,colorMap,vis,updVis}){let candidates=eligibleColumns(parsed,xCol,yCol,labelCol),paletteNames=Object.keys(COLOR_PALETTES);return React.createElement(VolcanoAesBox,{theme:"colorMap"},React.createElement("select",{className:"dv-select",value:col===-1?"":col,onChange:e=>setCol(e.target.value===""?-1:parseInt(e.target.value)),style:{width:"100%",marginBottom:colorMap?8:0}},React.createElement("option",{value:""},"\u2014 None \u2014"),candidates.map(({h,i})=>React.createElement("option",{key:i,value:i},h))),colorMap&&React.createElement(React.Fragment,null,React.createElement("div",{style:{fontSize:11,color:"var(--text-faint)",marginBottom:6}},"Detected:"," ",React.createElement("strong",{style:{color:colorMap.type==="continuous"?"var(--accent-dna)":"var(--accent-blue)"}},colorMap.type==="continuous"?"numeric (continuous)":`categorical (${colorMap.legend.length} groups)`)),colorMap.type==="continuous"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("select",{className:"dv-select",value:vis.colorMapPalette,onChange:e=>updVis({colorMapPalette:e.target.value}),style:{width:"100%",fontSize:11}},paletteNames.map(name=>React.createElement("option",{key:name,value:name},name,DIVERGING_PALETTES.has(name)?" (diverging)":""))),React.createElement(PaletteStrip,{palette:vis.colorMapPalette,invert:vis.colorMapInvert}),React.createElement("div",{style:{display:"flex",gap:6,alignItems:"center"}},React.createElement("span",{className:"dv-label",style:{fontSize:11,flexShrink:0}},"Direction"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Palette direction"},React.createElement("button",{type:"button","aria-pressed":!vis.colorMapInvert,className:"dv-seg-btn"+(vis.colorMapInvert?"":" dv-seg-btn-active"),onClick:()=>updVis({colorMapInvert:!1}),style:{fontSize:11,padding:"3px 8px"}},"Normal"),React.createElement("button",{type:"button","aria-pressed":vis.colorMapInvert,className:"dv-seg-btn"+(vis.colorMapInvert?" dv-seg-btn-active":""),onClick:()=>updVis({colorMapInvert:!0}),style:{fontSize:11,padding:"3px 8px"}},"Inverted"))),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"range: ",colorMap.vmin.toPrecision(3)," \u2192 ",colorMap.vmax.toPrecision(3))),colorMap.type==="discrete"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4,maxHeight:160,overflowY:"auto"}},colorMap.legend.map(entry=>React.createElement("div",{key:entry.value,style:{display:"flex",alignItems:"center",gap:8,fontSize:12}},React.createElement("span",{style:{display:"inline-block",width:14,height:14,borderRadius:3,background:entry.color,border:"1px solid var(--border)",flexShrink:0}}),React.createElement("span",{style:{color:"var(--text)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},entry.value))))))}function SizeMapTile({parsed,xCol,yCol,labelCol,col,setCol,vis,updVis}){let candidates=eligibleColumns(parsed,xCol,yCol,labelCol),active=col>=0;return React.createElement(VolcanoAesBox,{theme:"sizeMap"},React.createElement("select",{className:"dv-select",value:col===-1?"":col,onChange:e=>setCol(e.target.value===""?-1:parseInt(e.target.value)),style:{width:"100%",marginBottom:active?8:0}},React.createElement("option",{value:""},"\u2014 None \u2014"),candidates.map(({h,i})=>React.createElement("option",{key:i,value:i},h))),active&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement(SliderControl,{label:"Min radius",value:vis.sizeMapMinR,displayValue:vis.sizeMapMinR.toFixed(1),min:1,max:vis.sizeMapMaxR-.5,step:.5,onChange:v=>updVis({sizeMapMinR:Number(v)})}),React.createElement(SliderControl,{label:"Max radius",value:vis.sizeMapMaxR,displayValue:vis.sizeMapMaxR.toFixed(1),min:vis.sizeMapMinR+.5,max:20,step:.5,onChange:v=>updVis({sizeMapMaxR:Number(v)})}),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"Non-numeric / blank cells fall back to the default radius from the Style tile.")))}function SummaryTile({summary,fcCutoff,pCutoff}){return React.createElement("div",{className:"dv-panel",style:{padding:"10px 14px",display:"flex",gap:16,flexWrap:"wrap",fontSize:12}},React.createElement("span",null,React.createElement("strong",{style:{color:VOLCANO_DEFAULT_COLORS.up}},"\u2191 up"),": ",summary.up),React.createElement("span",null,React.createElement("strong",{style:{color:VOLCANO_DEFAULT_COLORS.down}},"\u2193 down"),": ",summary.down),React.createElement("span",null,React.createElement("strong",{style:{color:VOLCANO_DEFAULT_COLORS.ns}},"\xB7 ns"),": ",summary.ns),React.createElement("span",{style:{color:"var(--text-muted)"}},"of ",summary.total," valid",summary.discarded>0?` (+${summary.discarded} discarded)`:""),React.createElement("span",{style:{color:"var(--text-faint)"}},"|log2FC| > ",fcCutoff," \xB7 p < ",pCutoff))}var DEFAULT_VBW=800,VBH=500,MARGIN={top:56,right:60,bottom:78,left:86},LABEL_OUTSIDE_PAD=56,SELECTION_RING_PAD=1.5,LEGEND_W=130,LEGEND_GAP=14;function fmtTick(t){return t===0?"0":Math.abs(t)>=100?t.toFixed(0):Math.abs(t)>=10?t.toFixed(1):t.toFixed(2)}function fmtLegend(n){if(!Number.isFinite(n))return"\u2014";let abs=Math.abs(n);return abs===0?"0":abs>=1e6?n.toExponential(1):abs>=1e3?Math.round(n).toString():abs>=1?n.toFixed(2):abs>=.01?n.toFixed(3):n.toExponential(1)}function computeAxisRanges(points,pFloor,fcCutoff,pCutoff,userXMin,userXMax,userYMin,userYMax){let absMaxFc=0,maxNL=0;for(let pt of points){if(Number.isFinite(pt.log2fc)){let a=Math.abs(pt.log2fc);a>absMaxFc&&(absMaxFc=a)}let nl=negLog10P(pt.p,pFloor);Number.isFinite(nl)&&nl>maxNL&&(maxNL=nl)}absMaxFc=Math.max(absMaxFc,fcCutoff*1.5,1),maxNL=Math.max(maxNL,-Math.log10(pCutoff)*1.5,1);let autoXMin=-absMaxFc*1.05,autoXMax=absMaxFc*1.05,autoYMin=-maxNL*.03,autoYMax=maxNL*1.05;return{xMin:userXMin??autoXMin,xMax:userXMax??autoXMax,yMin:userYMin??autoYMin,yMax:userYMax??autoYMax}}function buildRenderedPoints(points,pFloor,fcCutoff,pCutoff,sx,sy){let out=[];for(let pt of points){if(!Number.isFinite(pt.log2fc))continue;let nl=negLog10P(pt.p,pFloor),cls=classifyPoint(pt.log2fc,pt.p,fcCutoff,pCutoff);out.push({pt,cls,nl,px:{x:sx(pt.log2fc),y:sy(nl)}})}return out.sort((a,b)=>{let order={ns:0,down:1,up:2};return order[a.cls]-order[b.cls]}),out}function buildLabelLayout(input){let{showLabels,topNUp,topNDown,points,rendered,fcCutoff,pCutoff,pFloor,labelFontSize,manualSelection,radiusFor,w,h}=input;if(!showLabels)return{labels:[],radii:[],forcedCount:0,attemptedCount:0};let renderByIdx=new Map;for(let r of rendered)renderByIdx.set(r.pt.idx,r);let charW=approxMonoCharWidth(labelFontSize),lineH=labelFontSize*1.15,pickedRenders;if(manualSelection&&manualSelection.size>0){pickedRenders=[];for(let idx of manualSelection){let r=renderByIdx.get(idx);r&&pickedRenders.push(r)}}else{if(topNUp<=0&&topNDown<=0)return{labels:[],radii:[],forcedCount:0,attemptedCount:0};pickedRenders=pickTopLabels(points,topNUp,topNDown,fcCutoff,pCutoff,pFloor).map(({idx})=>renderByIdx.get(points[idx].idx)).filter(r=>r!=null&&r.pt.label!=null)}let inputs=pickedRenders.map(r=>{let pr=radiusFor(r.pt.idx);return{pointPx:{x:r.px.x-MARGIN.left,y:r.px.y-MARGIN.top},text:r.pt.label!=null&&r.pt.label!==""?r.pt.label:"row "+(r.pt.idx+1),charWidth:charW,lineHeight:lineH,pointRadius:pr,ringRadius:pr+SELECTION_RING_PAD}}),obstacles=rendered.map(r=>({x:r.px.x-MARGIN.left,y:r.px.y-MARGIN.top,r:radiusFor(r.pt.idx)})),labelBounds={x:-LABEL_OUTSIDE_PAD,y:-LABEL_OUTSIDE_PAD,w:w+LABEL_OUTSIDE_PAD*2,h:h+LABEL_OUTSIDE_PAD*2},placed=layoutLabels(inputs,obstacles,labelBounds),radii=pickedRenders.map(r=>radiusFor(r.pt.idx)),forcedCount=placed.reduce((acc,p)=>acc+(p.forced?1:0),0);return{labels:placed,radii,forcedCount,attemptedCount:placed.length}}function makeFillFor(colors,colorMap){let colorByIdx=colorMap?colorMap.colorByIdx:null;return(idx,cls)=>cls!=="ns"&&colorByIdx&&colorByIdx.has(idx)?colorByIdx.get(idx):colors[cls]}function makeRadiusFor(pointRadius,sizeMap){let radiusByIdx=sizeMap?sizeMap.byIdx:null;return idx=>radiusByIdx&&radiusByIdx.has(idx)?radiusByIdx.get(idx):pointRadius}function ColorLegend({colorMap,title,width,yOffset}){return React.createElement("g",{id:"color-legend",transform:`translate(0, ${yOffset})`},React.createElement("text",{x:0,y:10,fontSize:"11",fontWeight:"700",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},title),colorMap.type==="continuous"?React.createElement(ContinuousColorLegend,{stops:colorMap,width}):React.createElement(DiscreteColorLegend,{legend:colorMap.legend}))}function ContinuousColorLegend({stops,width}){let stripWidth=width,stripH=10,stripY=18;return React.createElement(React.Fragment,null,Array.from({length:32},(_,i)=>{let t=i/31;return React.createElement("rect",{key:i,x:i*stripWidth/32,y:stripY,width:stripWidth/32+.5,height:stripH,fill:interpolateColor(stops.paletteStops,t)})}),React.createElement("rect",{x:0,y:stripY,width:stripWidth,height:stripH,fill:"none",stroke:"#888",strokeWidth:"0.6"}),React.createElement("text",{x:0,y:stripY+stripH+12,fontSize:"10",fill:"#555",fontFamily:"sans-serif"},fmtLegend(stops.vmin)),React.createElement("text",{x:stripWidth,y:stripY+stripH+12,textAnchor:"end",fontSize:"10",fill:"#555",fontFamily:"sans-serif"},fmtLegend(stops.vmax)))}function DiscreteColorLegend({legend}){let rows=legend.slice(0,14),overflow=legend.length-rows.length,ROW_H=14,startY=18;return React.createElement(React.Fragment,null,rows.map((entry,i)=>React.createElement("g",{key:entry.value,transform:`translate(0, ${startY+i*ROW_H})`},React.createElement("rect",{x:0,y:0,width:10,height:10,fill:entry.color,stroke:"#888",strokeWidth:"0.5"}),React.createElement("text",{x:16,y:9,fontSize:"10",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},entry.value.length>16?entry.value.slice(0,16)+"\u2026":entry.value))),overflow>0&&React.createElement("text",{x:0,y:startY+rows.length*ROW_H+8,fontSize:"9",fill:"#888",fontStyle:"italic",fontFamily:"ui-monospace, Menlo, monospace"},"+ ",overflow," more"))}function SizeLegend({sizeMap,title,width,yOffset}){let span=sizeMap.vmax-sizeMap.vmin,ticks=span>0?makeTicks(sizeMap.vmin,sizeMap.vmax,4).filter(t=>t>=sizeMap.vmin&&t<=sizeMap.vmax):[],samples=ticks.length>0?ticks.map(v=>({v,r:sizeMap.minR+(v-sizeMap.vmin)/span*(sizeMap.maxR-sizeMap.minR)})):[{v:sizeMap.vmin,r:span>0?sizeMap.minR:(sizeMap.minR+sizeMap.maxR)/2}],ROW_H=Math.max(20,sizeMap.maxR*2+6),cx=sizeMap.maxR+2;return React.createElement("g",{id:"size-legend",transform:`translate(0, ${yOffset})`},React.createElement("text",{x:0,y:10,fontSize:"11",fontWeight:"700",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},title),samples.map((s,i)=>React.createElement("g",{key:i,transform:`translate(0, ${20+i*ROW_H})`},React.createElement("circle",{cx,cy:ROW_H/2,r:s.r,fill:"#bbb",stroke:"#444",strokeWidth:"0.6"}),React.createElement("text",{x:sizeMap.maxR*2+10,y:ROW_H/2+3,fontSize:"10",fill:"#555",fontFamily:"sans-serif"},fmtLegend(s.v)))),width<0&&React.createElement("text",null,title))}var{forwardRef,useMemo:useMemo2,useEffect:useEffect2,useRef,memo}=React,POINT_RASTERIZE_THRESHOLD=1e3,POINT_RASTERIZE_MAX_DPR=6,VolcanoChart=memo(forwardRef(function(props,ref){let{points,pFloor,fcCutoff,pCutoff,xMin:userXMin,xMax:userXMax,yMin:userYMin,yMax:userYMax,xLabel,yLabel,title,subtitle,colors,pointRadius,pointAlpha,showRefLines,showLabels,topNUp,topNDown,labelFontSize,showAxes,manualSelection,onPointClick,colorMap,colorMapLabel,sizeMap,sizeMapLabel,plotWidth,plotBg,onLabelLayoutInfo}=props,VBW=plotWidth&&plotWidth>0?plotWidth:DEFAULT_VBW,fillFor=makeFillFor(colors,colorMap),radiusFor=makeRadiusFor(pointRadius,sizeMap),hasLegend=!!colorMap||!!sizeMap,legendW=hasLegend?LEGEND_W:0,w=VBW-MARGIN.left-MARGIN.right-legendW,h=VBH-MARGIN.top-MARGIN.bottom,{xMin,xMax,yMin,yMax}=useMemo2(()=>computeAxisRanges(points,pFloor,fcCutoff,pCutoff,userXMin,userXMax,userYMin,userYMax),[points,pFloor,fcCutoff,pCutoff,userXMin,userXMax,userYMin,userYMax]),xRange=xMax-xMin||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xMin)/xRange*w,sy=v=>MARGIN.top+(1-(v-yMin)/yRange)*h,xTicks=makeTicks(xMin,xMax,8),yTicks=makeTicks(yMin,yMax,6),rendered=useMemo2(()=>buildRenderedPoints(points,pFloor,fcCutoff,pCutoff,sx,sy),[points,pFloor,fcCutoff,pCutoff,xMin,xMax,yMin,yMax,w,h]),labelLayout=useMemo2(()=>buildLabelLayout({showLabels,topNUp,topNDown,points,rendered,fcCutoff,pCutoff,pFloor,labelFontSize,pointRadius,manualSelection,radiusFor,w,h}),[showLabels,topNUp,topNDown,points,rendered,fcCutoff,pCutoff,pFloor,labelFontSize,pointRadius,manualSelection,colorMap,sizeMap,w,h]),labels=labelLayout.labels,labelRadii=labelLayout.radii;useEffect2(()=>{onLabelLayoutInfo&&onLabelLayoutInfo({forcedCount:labelLayout.forcedCount,attemptedCount:labelLayout.attemptedCount})},[onLabelLayoutInfo,labelLayout.forcedCount,labelLayout.attemptedCount]);let refXLeft=-fcCutoff,refXRight=fcCutoff,refY=-Math.log10(pCutoff),summary=useMemo2(()=>summarize(points,fcCutoff,pCutoff),[points,fcCutoff,pCutoff]),useRasterize=rendered.length>=POINT_RASTERIZE_THRESHOLD,canvasRef=useRef(null);canvasRef.current===null&&typeof document<"u"&&(canvasRef.current=document.createElement("canvas"));let pointsImageHref=useMemo2(()=>{if(!useRasterize)return"";let canvas=canvasRef.current;if(!canvas)return"";let baseDpr=typeof window<"u"&&window.devicePixelRatio||1,dpr=Math.min(POINT_RASTERIZE_MAX_DPR,baseDpr*2);canvas.width=Math.max(1,Math.round(VBW*dpr)),canvas.height=Math.max(1,Math.round(VBH*dpr));let ctx=canvas.getContext("2d");if(!ctx)return"";ctx.setTransform(dpr,0,0,dpr,0,0),ctx.clearRect(0,0,VBW,VBH),ctx.globalAlpha=pointAlpha;for(let cls of["ns","down","up"])for(let r of rendered)r.cls===cls&&(ctx.fillStyle=fillFor(r.pt.idx,cls),ctx.beginPath(),ctx.arc(r.px.x,r.px.y,radiusFor(r.pt.idx),0,2*Math.PI),ctx.fill());return canvas.toDataURL("image/png")},[useRasterize,rendered,pointAlpha,VBW,colors,colorMap,pointRadius,sizeMap]),handleRasterClick=e=>{if(!onPointClick)return;e.stopPropagation();let svg=e.currentTarget.ownerSVGElement;if(!svg)return;let screenCTM=svg.getScreenCTM();if(!screenCTM)return;let pt=svg.createSVGPoint();pt.x=e.clientX,pt.y=e.clientY;let local=pt.matrixTransform(screenCTM.inverse()),bestD2=1/0,bestIdx=-1,bestRadius=0;for(let r of rendered){let dx=r.px.x-local.x,dy=r.px.y-local.y,d2=dx*dx+dy*dy;d2=0&&bestD2<=tol*tol&&onPointClick(bestIdx)},exportDataRef=useRef(null);exportDataRef.current=useRasterize?{rendered,pointAlpha,summaryNs:summary.ns,summaryDown:summary.down,summaryUp:summary.up,fillFor,radiusFor}:null;let localSvgRef=useRef(null),setSvgRef=el=>{localSvgRef.current=el,typeof ref=="function"?ref(el):ref&&(ref.current=el)};return useEffect2(()=>{let el=localSvgRef.current;if(el){if(!useRasterize){unregisterSvgExportMutator(el);return}return registerSvgExportMutator(el,clone=>{let snapshot=exportDataRef.current;if(!snapshot)return;let dataPoints=clone.querySelector("#data-points");if(!dataPoints)return;let doc=clone.ownerDocument;if(!doc)return;let NS="http://www.w3.org/2000/svg";dataPoints.querySelectorAll(":scope > image, :scope > rect, :scope > g[id^='points-']").forEach(node=>node.parentNode&&node.parentNode.removeChild(node));let classMeta=[{cls:"ns",count:snapshot.summaryNs,label:"not significant"},{cls:"down",count:snapshot.summaryDown,label:"downregulated"},{cls:"up",count:snapshot.summaryUp,label:"upregulated"}];for(let meta of classMeta){let g=doc.createElementNS(NS,"g");g.setAttribute("id",`points-${meta.cls}`),g.setAttribute("fill-opacity",String(snapshot.pointAlpha)),g.setAttribute("aria-label",`${meta.count} ${meta.label} point${meta.count!==1?"s":""}`);for(let r of snapshot.rendered){if(r.cls!==meta.cls)continue;let c=doc.createElementNS(NS,"circle");c.setAttribute("cx",String(r.px.x)),c.setAttribute("cy",String(r.px.y)),c.setAttribute("r",String(snapshot.radiusFor(r.pt.idx))),c.setAttribute("fill",snapshot.fillFor(r.pt.idx,meta.cls)),g.appendChild(c)}dataPoints.appendChild(g)}}),()=>unregisterSvgExportMutator(el)}},[useRasterize]),React.createElement("svg",{ref:setSvgRef,viewBox:`0 0 ${VBW} ${VBH}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":title||"Volcano plot"},React.createElement("title",null,title||"Volcano plot"),React.createElement("desc",null,`Volcano plot of ${summary.total} point${summary.total!==1?"s":""}: ${summary.up} up, ${summary.down} down, ${summary.ns} not significant${summary.discarded>0?`, ${summary.discarded} discarded`:""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:VBW,height:VBH,fill:plotBg||"#fff"})),React.createElement("g",{id:"plot-area-background"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"})),showAxes&&React.createElement("g",{id:"grid"},xTicks.map((t,i)=>React.createElement("line",{key:`gx-${i}`,x1:sx(t),x2:sx(t),y1:MARGIN.top,y2:MARGIN.top+h,stroke:"#e5e5e5",strokeWidth:"0.6"})),yTicks.map((t,i)=>React.createElement("line",{key:`gy-${i}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:"#e5e5e5",strokeWidth:"0.6"}))),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:`tx-${t}`},React.createElement("line",{x1:sx(t),x2:sx(t),y1:MARGIN.top+h,y2:MARGIN.top+h+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:MARGIN.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:`ty-${t}`},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:VBH-14,textAnchor:"middle",fontSize:"13",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},xLabel)),React.createElement("g",{id:"y-axis-label"},React.createElement("text",{x:20,y:MARGIN.top+h/2,textAnchor:"middle",fontSize:"13",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace",transform:`rotate(-90, 20, ${MARGIN.top+h/2})`},yLabel)),title&&React.createElement("g",{id:"title"},React.createElement("text",{x:MARGIN.left+w/2,y:18,textAnchor:"middle",fontSize:"14",fontWeight:"700",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},title)),subtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:MARGIN.left+w/2,y:VBH-30,textAnchor:"middle",fontSize:"11",fill:"#666",fontFamily:"ui-monospace, Menlo, monospace"},subtitle)),showRefLines&&React.createElement("g",{id:"reference-lines"},refXLeft>=xMin&&refXLeft<=xMax&&React.createElement("line",{x1:sx(refXLeft),x2:sx(refXLeft),y1:MARGIN.top,y2:MARGIN.top+h,stroke:"#888",strokeWidth:"1",strokeDasharray:"4 4"}),refXRight>=xMin&&refXRight<=xMax&&React.createElement("line",{x1:sx(refXRight),x2:sx(refXRight),y1:MARGIN.top,y2:MARGIN.top+h,stroke:"#888",strokeWidth:"1",strokeDasharray:"4 4"}),refY>=yMin&&refY<=yMax&&React.createElement("line",{x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(refY),y2:sy(refY),stroke:"#888",strokeWidth:"1",strokeDasharray:"4 4"})),React.createElement("g",{id:"data-points","aria-label":`${summary.total} point${summary.total!==1?"s":""} total`},useRasterize?React.createElement(React.Fragment,null,pointsImageHref&&React.createElement("image",{x:0,y:0,width:VBW,height:VBH,href:pointsImageHref,preserveAspectRatio:"none","aria-hidden":"true"}),["ns","down","up"].map(cls=>{let count=cls==="up"?summary.up:cls==="down"?summary.down:summary.ns,classLabel=cls==="up"?"upregulated":cls==="down"?"downregulated":"not significant";return React.createElement("g",{key:cls,id:`points-${cls}`,"aria-label":`${count} ${classLabel} point${count!==1?"s":""}`})}),onPointClick&&React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:"none",pointerEvents:"all",style:{cursor:"pointer"},onClick:handleRasterClick,"aria-hidden":"true"})):["ns","down","up"].map(cls=>{let count=cls==="up"?summary.up:cls==="down"?summary.down:summary.ns,classLabel=cls==="up"?"upregulated":cls==="down"?"downregulated":"not significant";return React.createElement("g",{key:cls,id:`points-${cls}`,fillOpacity:pointAlpha,"aria-label":`${count} ${classLabel} point${count!==1?"s":""}`},rendered.filter(r=>r.cls===cls).map((r,i)=>React.createElement("circle",{key:i,cx:r.px.x,cy:r.px.y,r:radiusFor(r.pt.idx),fill:fillFor(r.pt.idx,cls),style:onPointClick?{cursor:"pointer"}:void 0,onClick:onPointClick?e=>{e.stopPropagation(),onPointClick(r.pt.idx)}:void 0},React.createElement("title",null,(r.pt.label?r.pt.label+" \xB7 ":"")+"log\u2082FC="+r.pt.log2fc.toFixed(3)+", p="+(r.pt.p===0?"0 (clamped)":r.pt.p.toExponential(2))+", "+cls+(onPointClick?" \u2014 click to label":"")))))})),showLabels&&labels.length>0&&React.createElement("g",{id:"selected-point-rings"},labels.map((lab,i)=>React.createElement("circle",{key:i,cx:MARGIN.left+lab.pointPx.x,cy:MARGIN.top+lab.pointPx.y,r:(labelRadii[i]||pointRadius)+SELECTION_RING_PAD,fill:"none",stroke:"#222",strokeWidth:"1.4"}))),showLabels&&labels.length>0&&React.createElement("g",{id:"top-n-leaders"},labels.map((lab,i)=>React.createElement("line",{key:i,x1:MARGIN.left+lab.leaderStart.x,y1:MARGIN.top+lab.leaderStart.y,x2:MARGIN.left+lab.leaderEnd.x,y2:MARGIN.top+lab.leaderEnd.y,stroke:"#666",strokeWidth:lab.forced?"1":"0.7",strokeDasharray:"3 2"}))),showLabels&&labels.length>0&&React.createElement("g",{id:"top-n-labels"},labels.map((lab,i)=>React.createElement("text",{key:i,x:MARGIN.left+lab.textPx.x,y:MARGIN.top+lab.textPx.y,textAnchor:"middle",fontSize:labelFontSize,fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},lab.text))),React.createElement("g",{id:"plot-frame"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:"none",stroke:"#333",strokeWidth:"1"})),hasLegend&&React.createElement("g",{id:"aesthetic-legends",transform:`translate(${MARGIN.left+w+LEGEND_GAP}, ${MARGIN.top})`},colorMap&&React.createElement(ColorLegend,{colorMap,title:colorMapLabel||"color",width:LEGEND_W-LEGEND_GAP,yOffset:0}),sizeMap&&React.createElement(SizeLegend,{sizeMap,title:sizeMapLabel||"size",width:LEGEND_W-LEGEND_GAP,yOffset:colorMap?colorMap.type==="continuous"?78:Math.min(14,colorMap.legend.length)*14+28:0})))}));function ConfigureStep({parsed,fileName,detectedSep,xCol,yCol,labelCol,yIsAdjusted,setXCol,setYCol,setLabelCol,setYIsAdjusted}){let xValid=xCol>=0,yValid=yCol>=0;return React.createElement("div",null,React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(260px, 1fr))",gap:12,marginBottom:12}},React.createElement(VolcanoAesBox,{theme:"x"},React.createElement("select",{className:"dv-select",style:{width:"100%"},value:xValid?xCol:"",onChange:e=>{let raw=e.target.value;raw!==""&&setXCol(Number(raw))}},!xValid&&React.createElement("option",{value:""},"\u2014 choose a log\u2082FC column \u2014"),parsed.headers.map((h,i)=>React.createElement("option",{key:i,value:i},h))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column with the log\u2082 fold change for each feature. DESeq2:"," ",React.createElement("code",null,"log2FoldChange"),"; limma / edgeR: ",React.createElement("code",null,"logFC"),".")),React.createElement(VolcanoAesBox,{theme:"y"},React.createElement("select",{className:"dv-select",style:{width:"100%"},value:yValid?yCol:"",onChange:e=>{let raw=e.target.value;raw!==""&&setYCol(Number(raw))}},!yValid&&React.createElement("option",{value:""},"\u2014 choose a p-value column \u2014"),parsed.headers.map((h,i)=>React.createElement("option",{key:i,value:i},h))),React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,marginTop:8,fontSize:11,color:"var(--text)"}},React.createElement("input",{type:"checkbox",checked:yIsAdjusted,onChange:e=>setYIsAdjusted(e.target.checked),style:{accentColor:"var(--cta-primary-bg)",margin:0,flexShrink:0}}),React.createElement("span",null,"This column is an ",React.createElement("strong",null,"adjusted")," p-value (FDR / BH / qvalue)")),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Plotted as \u2212log\u2081\u2080(p). Auto-detect prefers an adjusted column when both raw and adjusted are present.")),React.createElement(VolcanoAesBox,{theme:"label"},React.createElement("select",{className:"dv-select",style:{width:"100%"},value:labelCol,onChange:e=>setLabelCol(parseInt(e.target.value))},React.createElement("option",{value:-1},"\u2014 none \u2014"),parsed.headers.map((h,i)=>React.createElement("option",{key:i,value:i},h))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Categorical column used to annotate the top-N most-significant features (gene symbol, protein name, accession). Skip if your data has no such column."))),(!xValid||!yValid)&&React.createElement("div",{className:"dv-panel",style:{background:"var(--warning-bg)",borderColor:"var(--warning-border)",marginBottom:12}},React.createElement("p",{style:{fontSize:12,color:"var(--warning-text)",margin:0}},"Assign both a ",React.createElement("strong",null,"log\u2082FC column")," and a ",React.createElement("strong",null,"p-value column")," to unlock the Plot step in the navigation above.")),React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,color:"var(--text-muted)"}},React.createElement("strong",{style:{color:"var(--text)"}},fileName||"(pasted data)")," \u2014"," ",parsed.headers.length," cols \xD7 ",parsed.rawData.length," rows",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement("p",{style:{fontSize:11,color:"var(--text-faint)",marginBottom:10}},"Preview (first 8 rows):"),React.createElement(DataPreview,{headers:parsed.headers,rows:parsed.rawData,maxRows:8})))}function PlotStep({chartRef,parsed,xCol,yCol,labelCol,points,pFloor,clampedCount,summary,xLabel,yLabel,vis,updVis,manualSelection,togglePointSelection,clearManualSelection,addToManualSelection,colorMapCol,setColorMapCol,colorMap,colorMapLabel,sizeMapCol,setSizeMapCol,sizeMap,sizeMapLabel,fileName,onDownloadCsv,onDownloadR,onReset,labelDensity,onLabelLayoutInfo}){return React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotSidebar,null,React.createElement(DownloadTiles,{chartRef,fileStem:fileBaseName(fileName,"volcano"),onReset,extraDownloads:[{label:"CSV",title:"Download the per-feature classification table \u2014 feature, log2FC, p, \u2212log10(p), class",onClick:onDownloadCsv},{label:"R",title:"Download a self-contained ggplot2 R script that reproduces this volcano from the underlying data",onClick:onDownloadR}]}),React.createElement(ThresholdsTile,{vis,updVis}),React.createElement(ColorsTile,{vis,updVis}),React.createElement(ColorMapTile,{parsed,xCol,yCol,labelCol,col:colorMapCol,setCol:setColorMapCol,colorMap,vis,updVis}),React.createElement(SizeMapTile,{parsed,xCol,yCol,labelCol,col:sizeMapCol,setCol:setSizeMapCol,vis,updVis}),React.createElement(LabelsTile,{vis,updVis,manualSelection,clearManualSelection,points,labelCol,addToManualSelection,labelDensity}),React.createElement(StyleTile,{vis,updVis})),React.createElement("div",{style:{flex:"1 1 0",minWidth:0,display:"flex",flexDirection:"column",gap:12}},clampedCount>0&&React.createElement("div",{role:"status",style:{padding:"8px 12px",borderRadius:8,background:"var(--warning-bg)",border:"1px solid var(--warning-border)",fontSize:11,color:"var(--warning-text)"}},"\u26A0\uFE0F ",clampedCount," feature",clampedCount===1?"":"s"," had p = 0; clamped to a finite floor for display so the y-axis stays bounded."),React.createElement("div",{style:{background:"var(--plot-card-bg)",border:"1.5px solid var(--plot-card-border)",borderRadius:10,padding:16}},React.createElement(VolcanoChart,{ref:chartRef,points,pFloor,fcCutoff:vis.fcCutoff,pCutoff:vis.pCutoff,xMin:vis.xMin,xMax:vis.xMax,yMin:vis.yMin,yMax:vis.yMax,xLabel,yLabel,title:vis.plotTitle,subtitle:"",colors:{up:vis.colorUp,down:vis.colorDown,ns:vis.colorNs},pointRadius:vis.pointRadius,pointAlpha:vis.pointAlpha,showRefLines:vis.showRefLines,showLabels:vis.showLabels,topNUp:vis.topNUp,topNDown:vis.topNDown,labelFontSize:vis.labelFontSize,showAxes:vis.showAxes,manualSelection,onPointClick:togglePointSelection,colorMap,colorMapLabel,sizeMap,sizeMapLabel,plotWidth:vis.plotWidth,plotBg:"#ffffff",onLabelLayoutInfo})),React.createElement(SummaryTile,{summary,fcCutoff:vis.fcCutoff,pCutoff:vis.pCutoff})))}function buildVolcanoRScript(args){let{points,fcCutoff,pCutoff,colors,xLabel,yLabel,plotTitle,yIsAdjusted,generatedAt}=args,now=generatedAt||new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),summary=summarize(points,fcCutoff,pCutoff),featureItems=[],log2fcItems=[],pItems=[];for(let pt of points){if(!Number.isFinite(pt.log2fc)||!Number.isFinite(pt.p))continue;let labelStr=pt.label!=null&&pt.label!==""?'"'+sanitizeRString(pt.label)+'"':'"feature_'+(pt.idx+1)+'"';featureItems.push(labelStr),log2fcItems.push(formatRNumber(pt.log2fc)),pItems.push(formatRNumber(pt.p))}let lines=[];return lines.push("# -----------------------------------------------------------------------------"),lines.push("# Pl\xF6ttr \u2014 Volcano plot R script export"),lines.push("# Generated: "+now),lines.push("#"),lines.push("# Reproduces the volcano plot you saw in the browser tool, using ggplot2."),lines.push("# Each point is one feature. Classification:"),lines.push("# up : log2FC > +"+formatRNumber(fcCutoff)+" AND p < "+formatRNumber(pCutoff)),lines.push("# down : log2FC < -"+formatRNumber(fcCutoff)+" AND p < "+formatRNumber(pCutoff)),lines.push("# ns : everything else"),lines.push("# Y-axis is -log10("+(yIsAdjusted?"adjusted p-value":"raw p-value")+") \u2014 pick the column that matches your downstream pipeline."),lines.push("#"),lines.push("# Toolbox reported: up="+summary.up+", down="+summary.down+", ns="+summary.ns+" (out of "+summary.total+" valid points; "+summary.discarded+" discarded for non-finite values)."),lines.push("# -----------------------------------------------------------------------------"),lines.push(""),lines.push('if (!requireNamespace("ggplot2", quietly = TRUE)) install.packages("ggplot2")'),lines.push("library(ggplot2)"),lines.push(""),lines.push("df <- data.frame("),lines.push(" feature = "+wrapRItems(featureItems,6)+","),lines.push(" log2FC = "+wrapRItems(log2fcItems,8)+","),lines.push(" pvalue = "+wrapRItems(pItems,8)+","),lines.push(" stringsAsFactors = FALSE"),lines.push(")"),lines.push(""),lines.push("# Clamp p = 0 to a finite floor (smallest non-zero / 10) so -log10 stays finite."),lines.push("p_floor <- min(df$pvalue[df$pvalue > 0], na.rm = TRUE) / 10"),lines.push("df$nlog10p <- -log10(pmax(df$pvalue, p_floor))"),lines.push("df$class <- with(df, ifelse("),lines.push(" pvalue < "+formatRNumber(pCutoff)+" & log2FC > "+formatRNumber(fcCutoff)+', "up",'),lines.push(" ifelse("),lines.push(" pvalue < "+formatRNumber(pCutoff)+" & log2FC < -"+formatRNumber(fcCutoff)+', "down", "ns"'),lines.push(" )"),lines.push("))"),lines.push('df$class <- factor(df$class, levels = c("ns", "down", "up"))'),lines.push(""),lines.push('plot_colors <- c(up = "'+colors.up+'", down = "'+colors.down+'", ns = "'+colors.ns+'")'),lines.push(""),lines.push("ggplot(df, aes(x = log2FC, y = nlog10p, color = class)) +"),lines.push(" geom_point(alpha = 0.7) +"),lines.push(" geom_vline(xintercept = c(-"+formatRNumber(fcCutoff)+", "+formatRNumber(fcCutoff)+'), linetype = "dashed", color = "grey60") +'),lines.push(" geom_hline(yintercept = -log10("+formatRNumber(pCutoff)+'), linetype = "dashed", color = "grey60") +'),lines.push(" scale_color_manual(values = plot_colors) +"),lines.push(' labs(x = "'+sanitizeRString(xLabel||"log2(fold change)")+'", y = "'+sanitizeRString(yLabel||"-log10(p)")+'"'+(plotTitle?', title = "'+sanitizeRString(plotTitle)+'"':"")+") +"),lines.push(" theme_classic() +"),lines.push(' theme(legend.position = "right")'),lines.push(""),lines.join(` +import{makeTicks}from"./chunk-T4V5V5TK.js";import{COLOR_PALETTES,ColorInput,ControlSection,DIVERGING_PALETTES,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,NumberInput,PALETTE,PlotSidebar,PlotToolShell,SliderControl,UploadPanel,autoDetectSep,downloadCsv,downloadText,fileBaseName,fixDecimalCommas,formatRNumber,interpolateColor,isNumericValue,parseData,registerSvgExportMutator,resolveDiscretePalette,sanitizeRString,toNumericValue,unregisterSvgExportMutator,usePlotToolState,wrapRItems}from"./chunk-HFTDMS7M.js";var VOLCANO_HOWTO={toolName:"volcano",title:"Volcano Plot \u2014 How to use",subtitle:"One row per feature \xB7 log\u2082FC on X \xB7 p-value (\u2212log\u2081\u2080) on Y",purpose:React.createElement(React.Fragment,null,"Highlight differentially expressed features by combining ",React.createElement("strong",null,"fold change")," with"," ",React.createElement("strong",null,"statistical significance")," \u2014 the canonical way to inspect RNA-seq, proteomics, or metabolomics tables."),dataLayout:React.createElement(React.Fragment,null,"One ",React.createElement("strong",null,"row")," per feature. Two numeric columns: a ",React.createElement("strong",null,"log\u2082 fold change")," ","and a ",React.createElement("strong",null,"p-value")," (raw or adjusted). An optional ",React.createElement("strong",null,"label")," column (gene symbol, feature ID) drives annotations. DESeq2, limma, edgeR, MaxQuant column names auto-detect."),display:React.createElement(React.Fragment,null,"Tweak ",React.createElement("strong",null,"|log\u2082FC|")," + ",React.createElement("strong",null,"p")," cutoffs in the Thresholds tile to set the up / down / ns split. Label features via auto top-N, click-to-label, or paste-list search in the Labels tile. Optional colour and size aesthetic mappings (e.g. expression level) render in-SVG legends.")};var VOLCANO_DEFAULT_COLORS={up:"#D55E00",down:"#0072B2",ns:"#737373"};function classifyPoint(log2fc,p,fcCutoff,pCutoff){return!Number.isFinite(log2fc)||!Number.isFinite(p)||!(pfcCutoff?"up":log2fc<-fcCutoff?"down":"ns"}function computePFloor(points){let minNonZero=1/0;for(let pt of points)Number.isFinite(pt.p)&&pt.p>0&&pt.ppt.label==null?null:pt.label.trim().toLocaleLowerCase()),matchedIdx=new Set,tokenHit=new Array(tokens.length).fill(!1);for(let i=0;i=0;yCol<0&&(yCol=findFirstMatch(headers,Y_RAW_PATTERNS));let labelCol=findFirstMatch(headers,LABEL_PATTERNS);return{xCol,yCol,labelCol,yIsAdjusted}}function pickTopLabels(points,nUp,nDown,fcCutoff,pCutoff,pFloor){let ups=[],downs=[];for(let i=0;ib.score-a.score),downs.sort((a,b)=>b.score-a.score);let out=[];if(nUp>0)for(let e of ups.slice(0,nUp))out.push(e);if(nDown>0)for(let e of downs.slice(0,nDown))out.push(e);return out}var LEADER_DISTANCES=[38,56,80,108],LABEL_BBOX_PAD=1,LABEL_ANGLES_DEG=[-90,-75,-105,-60,-120,-45,-135,-30,-150,-15,-165,0,180,15,165,30,150,45,135,60,120,75,105,90];function within(bbox,bounds){return bbox.x>=bounds.x&&bbox.y>=bounds.y&&bbox.x+bbox.w<=bounds.x+bounds.w&&bbox.y+bbox.h<=bounds.y+bounds.h}function lineSegmentIntersectsCircle(x1,y1,x2,y2,cx,cy,r){let dx=x2-x1,dy=y2-y1,len2=dx*dx+dy*dy,t;len2<1e-9?t=0:(t=((cx-x1)*dx+(cy-y1)*dy)/len2,t<0?t=0:t>1&&(t=1));let px=x1+t*dx,py=y1+t*dy,ddx=cx-px,ddy=cy-py;return ddx*ddx+ddy*ddy<=r*r}function lineSegmentsIntersect(ax1,ay1,ax2,ay2,bx1,by1,bx2,by2){let d1x=ax2-ax1,d1y=ay2-ay1,d2x=bx2-bx1,d2y=by2-by1,denom=d1x*d2y-d1y*d2x;if(Math.abs(denom)<1e-9)return!1;let dx=bx1-ax1,dy=by1-ay1,t=(dx*d2y-dy*d2x)/denom,u=(dx*d1y-dy*d1x)/denom;return t>1e-6&&t<1-1e-6&&u>1e-6&&u<1-1e-6}function nearestPointOnBbox(bbox,sx,sy){let nx=Math.max(bbox.x,Math.min(sx,bbox.x+bbox.w)),ny=Math.max(bbox.y,Math.min(sy,bbox.y+bbox.h));return{x:nx,y:ny}}function pointInPaddedRect(px,py,bbox,pad){return px>=bbox.x-pad&&px<=bbox.x+bbox.w+pad&&py>=bbox.y-pad&&py<=bbox.y+bbox.h+pad}var PENALTY_BBOX_ON_POINT=200,PENALTY_LEADER_ON_POINT=50,PENALTY_LEADER_CROSS=50,PENALTY_BBOX_OVERLAP_PER_100PX2=1;function evaluateCandidate(inp,obstacles,bounds,placed,angleDeg,distance){let w=inp.charWidth*inp.text.length,h=inp.lineHeight,sx=inp.pointPx.x,sy=inp.pointPx.y,rad=angleDeg*Math.PI/180,cosA=Math.cos(rad),sinA=Math.sin(rad),labelCx=sx+distance*cosA,labelCy=sy+distance*sinA,bbox={x:labelCx-w/2,y:labelCy-h/2,w,h};if(!within(bbox,bounds))return null;let startX=sx+inp.ringRadius*cosA,startY=sy+inp.ringRadius*sinA,leaderEnd=nearestPointOnBbox(bbox,sx,sy),labelOverlapArea=0;for(let p of placed){let ix1=Math.max(bbox.x,p.bbox.x),iy1=Math.max(bbox.y,p.bbox.y),ix2=Math.min(bbox.x+bbox.w,p.bbox.x+p.bbox.w),iy2=Math.min(bbox.y+bbox.h,p.bbox.y+p.bbox.h);ix2>ix1&&iy2>iy1&&(labelOverlapArea+=(ix2-ix1)*(iy2-iy1))}let bboxHitsPointCount=0;for(let obs of obstacles){let ox=obs.x-sx,oy=obs.y-sy;ox*ox+oy*oy<1||pointInPaddedRect(obs.x,obs.y,bbox,obs.r+LABEL_BBOX_PAD)&&bboxHitsPointCount++}let leaderHitsPointCount=0;for(let obs of obstacles){let ox=obs.x-sx,oy=obs.y-sy;ox*ox+oy*oy<1||lineSegmentIntersectsCircle(startX,startY,leaderEnd.x,leaderEnd.y,obs.x,obs.y,obs.r+.5)&&leaderHitsPointCount++}let leaderCrossCount=0;for(let p of placed)lineSegmentsIntersect(startX,startY,leaderEnd.x,leaderEnd.y,p.leaderStart.x,p.leaderStart.y,p.leaderEnd.x,p.leaderEnd.y)&&leaderCrossCount++;let penalty=labelOverlapArea*(PENALTY_BBOX_OVERLAP_PER_100PX2/100)+leaderCrossCount*PENALTY_LEADER_CROSS+leaderHitsPointCount*PENALTY_LEADER_ON_POINT+bboxHitsPointCount*PENALTY_BBOX_ON_POINT;return{placement:{pointPx:{x:sx,y:sy},textPx:{x:labelCx,y:labelCy+h/4},bbox,text:inp.text,leaderStart:{x:startX,y:startY},leaderEnd,forced:!1},penalty}}var REFINE_RANGE_DEG=7.5,REFINE_STEPS_LEVEL_1=5,REFINE_STEPS_LEVEL_2=3;function refineForcedAngle(inp,obstacles,bounds,placed,baseAngleDeg,baseDistance,basePenalty,baseCandidate){let best={placement:baseCandidate,penalty:basePenalty},bestLevel1Angle=baseAngleDeg;for(let i=0;ii)];let idxs=Array.from({length:n},(_,i)=>i),inputOrder=[...idxs],reverseOrder=[...idxs].reverse(),isolation=idxs.map(i=>{let minD2=1/0;for(let j of idxs){if(j===i)continue;let dx=inputs[i].pointPx.x-inputs[j].pointPx.x,dy=inputs[i].pointPx.y-inputs[j].pointPx.y,d2=dx*dx+dy*dy;d2b.isolation-a.isolation);let isolatedFirst=isolation.map(e=>e.idx),clusteredFirst=[...isolatedFirst].reverse();return[inputOrder,reverseOrder,isolatedFirst,clusteredFirst]}function layoutLabels(inputs,obstacles,bounds){if(inputs.length===0)return[];let orderings=buildOrderings(inputs),best=greedyPass(inputs,obstacles,bounds,orderings[0]);for(let i=1;i{s!==""&&(total++,unique.add(s),isNumericString(s)&&numeric++)};if(pointIndices)for(let idx of pointIndices)inspect(rawCell(rawData,idx,col));else for(let row of rawData){let v=row[col];inspect(v==null?"":String(v))}return total===0?"discrete":numeric/total>.8&&unique.size>12?"continuous":"discrete"}function buildColorMap(args){let{rawData,pointIndices,col,paletteStops,paletteName,discretePalette,interpolate}=args;if(col<0)return null;if(detectColorMapType(rawData,col,pointIndices)==="continuous"){let vmin=1/0,vmax=-1/0,numeric=[];for(let idx of pointIndices){let raw=rawCell(rawData,idx,col);if(!isNumericString(raw))continue;let n=Number(raw);numeric.push({idx,v:n}),nvmax&&(vmax=n)}(!Number.isFinite(vmin)||!Number.isFinite(vmax)||vmin===vmax)&&(vmin=0,vmax=1);let colorByIdx2=new Map;for(let{idx,v}of numeric){let t=vmax>vmin?(v-vmin)/(vmax-vmin):.5;colorByIdx2.set(idx,interpolate(paletteStops,Math.max(0,Math.min(1,t))))}return{type:"continuous",colorByIdx:colorByIdx2,vmin,vmax,paletteName,paletteStops:paletteStops.slice()}}let seen=new Map,order=[];for(let idx of pointIndices){let raw=rawCell(rawData,idx,col);if(raw!==""&&!seen.has(raw)){let color=discretePalette[seen.size%discretePalette.length];seen.set(raw,color),order.push(raw)}}let colorByIdx=new Map;for(let idx of pointIndices){let raw=rawCell(rawData,idx,col),c=seen.get(raw);c!=null&&colorByIdx.set(idx,c)}return{type:"discrete",colorByIdx,legend:order.map(value=>({value,color:seen.get(value)}))}}function buildSizeMap(rawData,pointIndices,col,minR,maxR){if(col<0)return null;let vmin=1/0,vmax=-1/0,numeric=[];for(let idx of pointIndices){let raw=rawCell(rawData,idx,col);if(raw==="")continue;let n=Number(raw);Number.isFinite(n)&&(numeric.push({idx,v:n}),nvmax&&(vmax=n))}if(numeric.length===0)return null;let byIdx=new Map;if(!Number.isFinite(vmin)||!Number.isFinite(vmax)||vmin===vmax){let mid=(minR+maxR)/2;for(let{idx}of numeric)byIdx.set(idx,mid);return{byIdx,vmin:Number.isFinite(vmin)?vmin:0,vmax:Number.isFinite(vmax)?vmax:0,minR,maxR}}let span=vmax-vmin;for(let{idx,v}of numeric){let t=(v-vmin)/span;byIdx.set(idx,minR+t*(maxR-minR))}return{byIdx,vmin,vmax,minR,maxR}}function buildPoints(rawData,xCol,yCol,labelCol){let out=[];for(let i=0;i=0&&row[labelCol]!=null&&row[labelCol]!==""?String(row[labelCol]):null,label=labelRaw==null?null:labelRaw.trim()||null;out.push({idx:i,log2fc,p,label})}return out}function eligibleColumns(parsed,xCol,yCol,labelCol){let used=new Set([xCol,yCol]);return(parsed?.headers??[]).map((h,i)=>({h,i})).filter(({i})=>!used.has(i))}var{useState,useEffect,useMemo}=React,VOLCANO_AES_THEMES={x:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"X axis \xB7 log\u2082 fold change"},y:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Y axis \xB7 p-value (\u2212log\u2081\u2080)"},label:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Feature label (optional)"},colorMap:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Color"},sizeMap:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Size"}};function VolcanoAesBox({theme,children}){let t=VOLCANO_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function ToggleRow({checked,onChange,children}){return React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4}},React.createElement("span",{className:"dv-label"},children),React.createElement("div",{className:"dv-seg"},React.createElement("button",{type:"button",onClick:()=>onChange(!0),"aria-pressed":checked,className:"dv-seg-btn"+(checked?" dv-seg-btn-active":""),style:{fontSize:12}},"On"),React.createElement("button",{type:"button",onClick:()=>onChange(!1),"aria-pressed":!checked,className:"dv-seg-btn"+(checked?"":" dv-seg-btn-active"),style:{fontSize:12}},"Off")))}function ThresholdsTile({vis,updVis}){let onFcChange=e=>{let v=parseFloat(e.target.value);Number.isFinite(v)&&updVis({fcCutoff:Math.max(0,Math.min(10,v))})},P_OPTIONS=[{value:1,label:"None"},{value:.05,label:"0.05"},{value:.01,label:"0.01"},{value:.001,label:"0.001"}],pPickValue=P_OPTIONS.find(o=>Math.abs(o.value-vis.pCutoff)<1e-12)?.value??.05;return React.createElement(ControlSection,{title:"Thresholds",defaultOpen:!0},React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"|log2FC| cutoff"),React.createElement(NumberInput,{value:vis.fcCutoff,min:0,max:10,step:.1,onChange:onFcChange,style:{width:"100%"}})),React.createElement("label",{style:{display:"block"}},React.createElement("span",{className:"dv-label"},"p-value cutoff"),React.createElement("div",{className:"dv-seg"},P_OPTIONS.map(o=>{let active=pPickValue===o.value;return React.createElement("button",{key:o.value,type:"button","aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),style:{fontSize:12},onClick:()=>updVis({pCutoff:o.value})},o.label)}))),React.createElement(ToggleRow,{checked:vis.showRefLines,onChange:v=>updVis({showRefLines:v})},"Show reference lines"))}function ColorsTile({vis,updVis}){let handlePalette=next=>{let seed=resolveDiscretePalette(next,2);updVis({discretePalette:next,colorUp:seed[0]||vis.colorUp,colorDown:seed[1]||vis.colorDown,colorNs:VOLCANO_DEFAULT_COLORS.ns})};return React.createElement(ControlSection,{title:"Colors"},React.createElement(DiscretePaletteRow,{value:vis.discretePalette||"okabe-ito",onChange:handlePalette,names:["up","down","ns"]}),React.createElement(ColorRow,{label:"Up-regulated",value:vis.colorUp,onChange:v=>updVis({colorUp:v})}),React.createElement(ColorRow,{label:"Down-regulated",value:vis.colorDown,onChange:v=>updVis({colorDown:v})}),React.createElement(ColorRow,{label:"Not significant",value:vis.colorNs,onChange:v=>updVis({colorNs:v})}))}function ColorRow({label,value,onChange}){return React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8}},React.createElement("span",{style:{fontSize:12,color:"var(--text)"}},label),React.createElement(ColorInput,{value,onChange,size:20}))}function LabelSearchRow({points,labelCol,addToManualSelection}){let[query,setQuery]=useState(""),[previewQuery,setPreviewQuery]=useState(""),[showUnmatched,setShowUnmatched]=useState(!1),labelLowerCache=useMemo(()=>points.map(pt=>pt.label==null?null:pt.label.toLocaleLowerCase()),[points]);useEffect(()=>{let id=setTimeout(()=>setPreviewQuery(query),150);return()=>clearTimeout(id)},[query]);let preview=useMemo(()=>previewQuery.trim()?matchPointsByLabel(points,previewQuery,labelLowerCache):{matched:[],unmatchedTokens:[]},[previewQuery,points,labelLowerCache]),disabled=labelCol==null||labelCol<0,matchCount=preview.matched.length,unmatchedCount=preview.unmatchedTokens.length,hasQuery=previewQuery.trim().length>0,hasOnlyUnmatched=hasQuery&&matchCount===0,tooMany=matchCount>50,submit=()=>{disabled||matchCount===0||(addToManualSelection(preview.matched),setQuery(""),setPreviewQuery(""),setShowUnmatched(!1))};return React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4}},React.createElement("div",{className:"dv-label"},"Search by name"),React.createElement("div",{style:{display:"flex",gap:6}},React.createElement("input",{type:"search",className:"dv-input",value:query,onChange:e=>setQuery(e.target.value),onKeyDown:e=>{e.key==="Enter"&&(e.preventDefault(),submit())},placeholder:"gene name (or paste a list)",disabled,style:{flex:1,fontSize:11,padding:"3px 6px"},title:"Comma- or newline-separated. Case-insensitive substring."}),React.createElement("button",{type:"button",onClick:submit,disabled:disabled||matchCount===0,className:"dv-btn dv-btn-secondary",style:{padding:"4px 10px",fontSize:11},title:disabled?"Pick a label column in Configure to enable search":matchCount===0?"Type a name to search":`Add ${matchCount} matched point${matchCount===1?"":"s"} to the labelled set`},"Add")),disabled?React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic"}},"\u21B3 Pick a label column in Configure to enable search"):hasQuery?React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:2}},React.createElement("span",{style:{fontSize:10,color:hasOnlyUnmatched||tooMany?"var(--warning-text)":"var(--success-text)"}},matchCount===0?"no matches":`${matchCount} match${matchCount===1?"":"es"}`+(tooMany?" \u2014 labels may overlap":"")+(unmatchedCount>0?` \xB7 ${unmatchedCount} unmatched`:"")),unmatchedCount>0&&React.createElement("button",{type:"button",onClick:()=>setShowUnmatched(v=>!v),style:{alignSelf:"flex-start",background:"none",border:"none",padding:0,fontSize:10,color:"var(--text-faint)",fontFamily:"inherit",cursor:"pointer",textDecoration:"underline"},title:"Toggle the list of tokens that matched zero points"},showUnmatched?"hide unmatched":"show unmatched"),showUnmatched&&unmatchedCount>0&&React.createElement("ul",{style:{margin:"2px 0 0",padding:"4px 8px 4px 18px",fontSize:10,fontFamily:"monospace",color:"var(--warning-text)",background:"var(--warning-bg)",border:"1px solid var(--warning-border)",borderRadius:4,maxHeight:80,overflowY:"auto"}},preview.unmatchedTokens.map(t=>React.createElement("li",{key:t},t)))):React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic"}},"\u21B3 Comma- or newline-separated \xB7 case-insensitive substring"))}function LabelsTile({vis,updVis,manualSelection,clearManualSelection,points,labelCol,addToManualSelection,labelDensity}){let manualCount=manualSelection?manualSelection.size:0,hasManual=manualCount>0,forcedCount=labelDensity&&labelDensity.forcedCount||0,attemptedCount=labelDensity&&labelDensity.attemptedCount||0,showDensityWarning=!hasManual&&forcedCount>0&&attemptedCount>0,cleanBudget=Math.max(0,attemptedCount-forcedCount),totalRequested=(vis.topNUp||0)+(vis.topNDown||0),ratioUp=totalRequested>0?(vis.topNUp||0)/totalRequested:.5,suggestedUp=Math.max(0,Math.round(cleanBudget*ratioUp)),suggestedDown=Math.max(0,cleanBudget-suggestedUp);return React.createElement(ControlSection,{title:"Labels",defaultOpen:hasManual},React.createElement(ToggleRow,{checked:vis.showLabels,onChange:v=>updVis({showLabels:v})},"Annotate top features"),React.createElement(LabelSearchRow,{points,labelCol,addToManualSelection}),hasManual?React.createElement("div",{style:{display:"flex",alignItems:"center",justifyContent:"space-between",gap:8,padding:"8px 10px",borderRadius:6,background:"var(--info-bg)",border:"1px solid var(--info-border)"}},React.createElement("span",{style:{fontSize:11,color:"var(--info-text)"}},manualCount," point",manualCount===1?"":"s"," clicked"),React.createElement("button",{onClick:clearManualSelection,className:"dv-btn dv-btn-secondary",style:{padding:"4px 10px",fontSize:11},title:"Clear the manual selection \u2014 labelling falls back to the auto top-N picks"},"Clear")):React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)",fontStyle:"italic"}},"\u21B3 Click any point on the chart to label it directly"),React.createElement(SliderControl,{label:"Top up-regulated",value:vis.topNUp,displayValue:String(vis.topNUp),min:0,max:50,step:1,onChange:v=>updVis({topNUp:Number(v)})}),React.createElement(SliderControl,{label:"Top down-regulated",value:vis.topNDown,displayValue:String(vis.topNDown),min:0,max:50,step:1,onChange:v=>updVis({topNDown:Number(v)})}),React.createElement(SliderControl,{label:"Font size",value:vis.labelFontSize,displayValue:String(vis.labelFontSize),min:8,max:16,step:1,onChange:v=>updVis({labelFontSize:Number(v)})}),showDensityWarning&&React.createElement("div",{role:"status",style:{display:"flex",flexDirection:"column",gap:6,padding:"8px 10px",borderRadius:6,background:"var(--warning-bg)",border:"1px solid var(--warning-border)",fontSize:11,color:"var(--warning-text)",marginTop:6}},React.createElement("span",null,"\u26A0 ",forcedCount," of ",attemptedCount," labels couldn't place cleanly at this data density."),cleanBudget>0&&cleanBudgetupdVis({topNUp:suggestedUp,topNDown:suggestedDown}),className:"dv-btn dv-btn-secondary",style:{alignSelf:"flex-start",padding:"3px 10px",fontSize:11},title:"Drop top-N to ("+suggestedUp+" up / "+suggestedDown+" down) so every label places without overlap."},"Use suggested (",suggestedUp," / ",suggestedDown,")")))}function StyleTile({vis,updVis}){return React.createElement(ControlSection,{title:"Style"},React.createElement(SliderControl,{label:"Plot width",value:vis.plotWidth,displayValue:String(Math.round(vis.plotWidth))+" px",min:600,max:1600,step:20,onChange:v=>updVis({plotWidth:Number(v)})}),React.createElement(SliderControl,{label:"Point radius",value:vis.pointRadius,displayValue:vis.pointRadius.toFixed(1),min:1,max:8,step:.5,onChange:v=>updVis({pointRadius:Number(v)})}),React.createElement(SliderControl,{label:"Point alpha",value:vis.pointAlpha,displayValue:vis.pointAlpha.toFixed(2),min:.1,max:1,step:.05,onChange:v=>updVis({pointAlpha:Number(v)})}),React.createElement(ToggleRow,{checked:vis.showAxes,onChange:v=>updVis({showAxes:v})},"Show grid"),React.createElement("label",{style:{display:"flex",flexDirection:"column",gap:4,fontSize:12,color:"var(--text)"}},"Plot title",React.createElement("input",{type:"text",className:"dv-input",value:vis.plotTitle,onChange:e=>updVis({plotTitle:e.target.value}),placeholder:"(optional)"})))}function PaletteStrip({palette,invert=!1,height=12}){let base=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,stops=invert?[...base].reverse():base,n=48;return React.createElement("div",{style:{display:"flex",width:"100%",height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border)"}},Array.from({length:n},(_,i)=>React.createElement("div",{key:i,style:{flex:1,background:interpolateColor(stops,i/(n-1))}})))}function ColorMapTile({parsed,xCol,yCol,labelCol,col,setCol,colorMap,vis,updVis}){let candidates=eligibleColumns(parsed,xCol,yCol,labelCol),paletteNames=Object.keys(COLOR_PALETTES);return React.createElement(VolcanoAesBox,{theme:"colorMap"},React.createElement("select",{className:"dv-select",value:col===-1?"":col,onChange:e=>setCol(e.target.value===""?-1:parseInt(e.target.value)),style:{width:"100%",marginBottom:colorMap?8:0}},React.createElement("option",{value:""},"\u2014 None \u2014"),candidates.map(({h,i})=>React.createElement("option",{key:i,value:i},h))),colorMap&&React.createElement(React.Fragment,null,React.createElement("div",{style:{fontSize:11,color:"var(--text-faint)",marginBottom:6}},"Detected:"," ",React.createElement("strong",{style:{color:colorMap.type==="continuous"?"var(--accent-dna)":"var(--accent-blue)"}},colorMap.type==="continuous"?"numeric (continuous)":`categorical (${colorMap.legend.length} groups)`)),colorMap.type==="continuous"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("select",{className:"dv-select",value:vis.colorMapPalette,onChange:e=>updVis({colorMapPalette:e.target.value}),style:{width:"100%",fontSize:11}},paletteNames.map(name=>React.createElement("option",{key:name,value:name},name,DIVERGING_PALETTES.has(name)?" (diverging)":""))),React.createElement(PaletteStrip,{palette:vis.colorMapPalette,invert:vis.colorMapInvert}),React.createElement("div",{style:{display:"flex",gap:6,alignItems:"center"}},React.createElement("span",{className:"dv-label",style:{fontSize:11,flexShrink:0}},"Direction"),React.createElement("div",{className:"dv-seg",role:"group","aria-label":"Palette direction"},React.createElement("button",{type:"button","aria-pressed":!vis.colorMapInvert,className:"dv-seg-btn"+(vis.colorMapInvert?"":" dv-seg-btn-active"),onClick:()=>updVis({colorMapInvert:!1}),style:{fontSize:11,padding:"3px 8px"}},"Normal"),React.createElement("button",{type:"button","aria-pressed":vis.colorMapInvert,className:"dv-seg-btn"+(vis.colorMapInvert?" dv-seg-btn-active":""),onClick:()=>updVis({colorMapInvert:!0}),style:{fontSize:11,padding:"3px 8px"}},"Inverted"))),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"range: ",colorMap.vmin.toPrecision(3)," \u2192 ",colorMap.vmax.toPrecision(3))),colorMap.type==="discrete"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:4,maxHeight:160,overflowY:"auto"}},colorMap.legend.map(entry=>React.createElement("div",{key:entry.value,style:{display:"flex",alignItems:"center",gap:8,fontSize:12}},React.createElement("span",{style:{display:"inline-block",width:14,height:14,borderRadius:3,background:entry.color,border:"1px solid var(--border)",flexShrink:0}}),React.createElement("span",{style:{color:"var(--text)",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},entry.value))))))}function SizeMapTile({parsed,xCol,yCol,labelCol,col,setCol,vis,updVis}){let candidates=eligibleColumns(parsed,xCol,yCol,labelCol),active=col>=0;return React.createElement(VolcanoAesBox,{theme:"sizeMap"},React.createElement("select",{className:"dv-select",value:col===-1?"":col,onChange:e=>setCol(e.target.value===""?-1:parseInt(e.target.value)),style:{width:"100%",marginBottom:active?8:0}},React.createElement("option",{value:""},"\u2014 None \u2014"),candidates.map(({h,i})=>React.createElement("option",{key:i,value:i},h))),active&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement(SliderControl,{label:"Min radius",value:vis.sizeMapMinR,displayValue:vis.sizeMapMinR.toFixed(1),min:1,max:vis.sizeMapMaxR-.5,step:.5,onChange:v=>updVis({sizeMapMinR:Number(v)})}),React.createElement(SliderControl,{label:"Max radius",value:vis.sizeMapMaxR,displayValue:vis.sizeMapMaxR.toFixed(1),min:vis.sizeMapMinR+.5,max:20,step:.5,onChange:v=>updVis({sizeMapMaxR:Number(v)})}),React.createElement("span",{style:{fontSize:10,color:"var(--text-faint)"}},"Non-numeric / blank cells fall back to the default radius from the Style tile.")))}function SummaryTile({summary,fcCutoff,pCutoff}){return React.createElement("div",{className:"dv-panel",style:{padding:"10px 14px",display:"flex",gap:16,flexWrap:"wrap",fontSize:12}},React.createElement("span",null,React.createElement("strong",{style:{color:VOLCANO_DEFAULT_COLORS.up}},"\u2191 up"),": ",summary.up),React.createElement("span",null,React.createElement("strong",{style:{color:VOLCANO_DEFAULT_COLORS.down}},"\u2193 down"),": ",summary.down),React.createElement("span",null,React.createElement("strong",{style:{color:VOLCANO_DEFAULT_COLORS.ns}},"\xB7 ns"),": ",summary.ns),React.createElement("span",{style:{color:"var(--text-muted)"}},"of ",summary.total," valid",summary.discarded>0?` (+${summary.discarded} discarded)`:""),React.createElement("span",{style:{color:"var(--text-faint)"}},"|log2FC| > ",fcCutoff," \xB7 p < ",pCutoff))}var DEFAULT_VBW=800,VBH=500,MARGIN={top:56,right:60,bottom:78,left:86},LABEL_OUTSIDE_PAD=56,SELECTION_RING_PAD=1.5,LEGEND_W=130,LEGEND_GAP=14;function fmtTick(t){return t===0?"0":Math.abs(t)>=100?t.toFixed(0):Math.abs(t)>=10?t.toFixed(1):t.toFixed(2)}function fmtLegend(n){if(!Number.isFinite(n))return"\u2014";let abs=Math.abs(n);return abs===0?"0":abs>=1e6?n.toExponential(1):abs>=1e3?Math.round(n).toString():abs>=1?n.toFixed(2):abs>=.01?n.toFixed(3):n.toExponential(1)}function computeAxisRanges(points,pFloor,fcCutoff,pCutoff,userXMin,userXMax,userYMin,userYMax){let absMaxFc=0,maxNL=0;for(let pt of points){if(Number.isFinite(pt.log2fc)){let a=Math.abs(pt.log2fc);a>absMaxFc&&(absMaxFc=a)}let nl=negLog10P(pt.p,pFloor);Number.isFinite(nl)&&nl>maxNL&&(maxNL=nl)}absMaxFc=Math.max(absMaxFc,fcCutoff*1.5,1),maxNL=Math.max(maxNL,-Math.log10(pCutoff)*1.5,1);let autoXMin=-absMaxFc*1.05,autoXMax=absMaxFc*1.05,autoYMin=-maxNL*.03,autoYMax=maxNL*1.05;return{xMin:userXMin??autoXMin,xMax:userXMax??autoXMax,yMin:userYMin??autoYMin,yMax:userYMax??autoYMax}}function buildRenderedPoints(points,pFloor,fcCutoff,pCutoff,sx,sy){let out=[];for(let pt of points){if(!Number.isFinite(pt.log2fc))continue;let nl=negLog10P(pt.p,pFloor),cls=classifyPoint(pt.log2fc,pt.p,fcCutoff,pCutoff);out.push({pt,cls,nl,px:{x:sx(pt.log2fc),y:sy(nl)}})}return out.sort((a,b)=>{let order={ns:0,down:1,up:2};return order[a.cls]-order[b.cls]}),out}function buildLabelLayout(input){let{showLabels,topNUp,topNDown,points,rendered,fcCutoff,pCutoff,pFloor,labelFontSize,manualSelection,radiusFor,w,h}=input;if(!showLabels)return{labels:[],radii:[],forcedCount:0,attemptedCount:0};let renderByIdx=new Map;for(let r of rendered)renderByIdx.set(r.pt.idx,r);let charW=approxMonoCharWidth(labelFontSize),lineH=labelFontSize*1.15,pickedRenders;if(manualSelection&&manualSelection.size>0){pickedRenders=[];for(let idx of manualSelection){let r=renderByIdx.get(idx);r&&pickedRenders.push(r)}}else{if(topNUp<=0&&topNDown<=0)return{labels:[],radii:[],forcedCount:0,attemptedCount:0};pickedRenders=pickTopLabels(points,topNUp,topNDown,fcCutoff,pCutoff,pFloor).map(({idx})=>renderByIdx.get(points[idx].idx)).filter(r=>r!=null&&r.pt.label!=null)}let inputs=pickedRenders.map(r=>{let pr=radiusFor(r.pt.idx);return{pointPx:{x:r.px.x-MARGIN.left,y:r.px.y-MARGIN.top},text:r.pt.label!=null&&r.pt.label!==""?r.pt.label:"row "+(r.pt.idx+1),charWidth:charW,lineHeight:lineH,pointRadius:pr,ringRadius:pr+SELECTION_RING_PAD}}),obstacles=rendered.map(r=>({x:r.px.x-MARGIN.left,y:r.px.y-MARGIN.top,r:radiusFor(r.pt.idx)})),labelBounds={x:-LABEL_OUTSIDE_PAD,y:-LABEL_OUTSIDE_PAD,w:w+LABEL_OUTSIDE_PAD*2,h:h+LABEL_OUTSIDE_PAD*2},placed=layoutLabels(inputs,obstacles,labelBounds),radii=pickedRenders.map(r=>radiusFor(r.pt.idx)),forcedCount=placed.reduce((acc,p)=>acc+(p.forced?1:0),0);return{labels:placed,radii,forcedCount,attemptedCount:placed.length}}function makeFillFor(colors,colorMap){let colorByIdx=colorMap?colorMap.colorByIdx:null;return(idx,cls)=>cls!=="ns"&&colorByIdx&&colorByIdx.has(idx)?colorByIdx.get(idx):colors[cls]}function makeRadiusFor(pointRadius,sizeMap){let radiusByIdx=sizeMap?sizeMap.byIdx:null;return idx=>radiusByIdx&&radiusByIdx.has(idx)?radiusByIdx.get(idx):pointRadius}function ColorLegend({colorMap,title,width,yOffset}){return React.createElement("g",{id:"color-legend",transform:`translate(0, ${yOffset})`},React.createElement("text",{x:0,y:10,fontSize:"11",fontWeight:"700",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},title),colorMap.type==="continuous"?React.createElement(ContinuousColorLegend,{stops:colorMap,width}):React.createElement(DiscreteColorLegend,{legend:colorMap.legend}))}function ContinuousColorLegend({stops,width}){let stripWidth=width,stripH=10,stripY=18;return React.createElement(React.Fragment,null,Array.from({length:32},(_,i)=>{let t=i/31;return React.createElement("rect",{key:i,x:i*stripWidth/32,y:stripY,width:stripWidth/32+.5,height:stripH,fill:interpolateColor(stops.paletteStops,t)})}),React.createElement("rect",{x:0,y:stripY,width:stripWidth,height:stripH,fill:"none",stroke:"#888",strokeWidth:"0.6"}),React.createElement("text",{x:0,y:stripY+stripH+12,fontSize:"10",fill:"#555",fontFamily:"sans-serif"},fmtLegend(stops.vmin)),React.createElement("text",{x:stripWidth,y:stripY+stripH+12,textAnchor:"end",fontSize:"10",fill:"#555",fontFamily:"sans-serif"},fmtLegend(stops.vmax)))}function DiscreteColorLegend({legend}){let rows=legend.slice(0,14),overflow=legend.length-rows.length,ROW_H=14,startY=18;return React.createElement(React.Fragment,null,rows.map((entry,i)=>React.createElement("g",{key:entry.value,transform:`translate(0, ${startY+i*ROW_H})`},React.createElement("rect",{x:0,y:0,width:10,height:10,fill:entry.color,stroke:"#888",strokeWidth:"0.5"}),React.createElement("text",{x:16,y:9,fontSize:"10",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},entry.value.length>16?entry.value.slice(0,16)+"\u2026":entry.value))),overflow>0&&React.createElement("text",{x:0,y:startY+rows.length*ROW_H+8,fontSize:"9",fill:"#888",fontStyle:"italic",fontFamily:"ui-monospace, Menlo, monospace"},"+ ",overflow," more"))}function SizeLegend({sizeMap,title,width,yOffset}){let span=sizeMap.vmax-sizeMap.vmin,ticks=span>0?makeTicks(sizeMap.vmin,sizeMap.vmax,4).filter(t=>t>=sizeMap.vmin&&t<=sizeMap.vmax):[],samples=ticks.length>0?ticks.map(v=>({v,r:sizeMap.minR+(v-sizeMap.vmin)/span*(sizeMap.maxR-sizeMap.minR)})):[{v:sizeMap.vmin,r:span>0?sizeMap.minR:(sizeMap.minR+sizeMap.maxR)/2}],ROW_H=Math.max(20,sizeMap.maxR*2+6),cx=sizeMap.maxR+2;return React.createElement("g",{id:"size-legend",transform:`translate(0, ${yOffset})`},React.createElement("text",{x:0,y:10,fontSize:"11",fontWeight:"700",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},title),samples.map((s,i)=>React.createElement("g",{key:i,transform:`translate(0, ${20+i*ROW_H})`},React.createElement("circle",{cx,cy:ROW_H/2,r:s.r,fill:"#bbb",stroke:"#444",strokeWidth:"0.6"}),React.createElement("text",{x:sizeMap.maxR*2+10,y:ROW_H/2+3,fontSize:"10",fill:"#555",fontFamily:"sans-serif"},fmtLegend(s.v)))),width<0&&React.createElement("text",null,title))}var{forwardRef,useMemo:useMemo2,useEffect:useEffect2,useRef,memo}=React,POINT_RASTERIZE_THRESHOLD=1e3,POINT_RASTERIZE_MAX_DPR=6,VolcanoChart=memo(forwardRef(function(props,ref){let{points,pFloor,fcCutoff,pCutoff,xMin:userXMin,xMax:userXMax,yMin:userYMin,yMax:userYMax,xLabel,yLabel,title,subtitle,colors,pointRadius,pointAlpha,showRefLines,showLabels,topNUp,topNDown,labelFontSize,showAxes,manualSelection,onPointClick,colorMap,colorMapLabel,sizeMap,sizeMapLabel,plotWidth,plotBg,onLabelLayoutInfo}=props,VBW=plotWidth&&plotWidth>0?plotWidth:DEFAULT_VBW,fillFor=makeFillFor(colors,colorMap),radiusFor=makeRadiusFor(pointRadius,sizeMap),hasLegend=!!colorMap||!!sizeMap,legendW=hasLegend?LEGEND_W:0,w=VBW-MARGIN.left-MARGIN.right-legendW,h=VBH-MARGIN.top-MARGIN.bottom,{xMin,xMax,yMin,yMax}=useMemo2(()=>computeAxisRanges(points,pFloor,fcCutoff,pCutoff,userXMin,userXMax,userYMin,userYMax),[points,pFloor,fcCutoff,pCutoff,userXMin,userXMax,userYMin,userYMax]),xRange=xMax-xMin||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xMin)/xRange*w,sy=v=>MARGIN.top+(1-(v-yMin)/yRange)*h,xTicks=makeTicks(xMin,xMax,8),yTicks=makeTicks(yMin,yMax,6),rendered=useMemo2(()=>buildRenderedPoints(points,pFloor,fcCutoff,pCutoff,sx,sy),[points,pFloor,fcCutoff,pCutoff,xMin,xMax,yMin,yMax,w,h]),labelLayout=useMemo2(()=>buildLabelLayout({showLabels,topNUp,topNDown,points,rendered,fcCutoff,pCutoff,pFloor,labelFontSize,pointRadius,manualSelection,radiusFor,w,h}),[showLabels,topNUp,topNDown,points,rendered,fcCutoff,pCutoff,pFloor,labelFontSize,pointRadius,manualSelection,colorMap,sizeMap,w,h]),labels=labelLayout.labels,labelRadii=labelLayout.radii;useEffect2(()=>{onLabelLayoutInfo&&onLabelLayoutInfo({forcedCount:labelLayout.forcedCount,attemptedCount:labelLayout.attemptedCount})},[onLabelLayoutInfo,labelLayout.forcedCount,labelLayout.attemptedCount]);let refXLeft=-fcCutoff,refXRight=fcCutoff,refY=-Math.log10(pCutoff),summary=useMemo2(()=>summarize(points,fcCutoff,pCutoff),[points,fcCutoff,pCutoff]),useRasterize=rendered.length>=POINT_RASTERIZE_THRESHOLD,canvasRef=useRef(null);canvasRef.current===null&&typeof document<"u"&&(canvasRef.current=document.createElement("canvas"));let pointsImageHref=useMemo2(()=>{if(!useRasterize)return"";let canvas=canvasRef.current;if(!canvas)return"";let baseDpr=typeof window<"u"&&window.devicePixelRatio||1,dpr=Math.min(POINT_RASTERIZE_MAX_DPR,baseDpr*2);canvas.width=Math.max(1,Math.round(VBW*dpr)),canvas.height=Math.max(1,Math.round(VBH*dpr));let ctx=canvas.getContext("2d");if(!ctx)return"";ctx.setTransform(dpr,0,0,dpr,0,0),ctx.clearRect(0,0,VBW,VBH),ctx.globalAlpha=pointAlpha;for(let cls of["ns","down","up"])for(let r of rendered)r.cls===cls&&(ctx.fillStyle=fillFor(r.pt.idx,cls),ctx.beginPath(),ctx.arc(r.px.x,r.px.y,radiusFor(r.pt.idx),0,2*Math.PI),ctx.fill());return canvas.toDataURL("image/png")},[useRasterize,rendered,pointAlpha,VBW,colors,colorMap,pointRadius,sizeMap]),handleRasterClick=e=>{if(!onPointClick)return;e.stopPropagation();let svg=e.currentTarget.ownerSVGElement;if(!svg)return;let screenCTM=svg.getScreenCTM();if(!screenCTM)return;let pt=svg.createSVGPoint();pt.x=e.clientX,pt.y=e.clientY;let local=pt.matrixTransform(screenCTM.inverse()),bestD2=1/0,bestIdx=-1,bestRadius=0;for(let r of rendered){let dx=r.px.x-local.x,dy=r.px.y-local.y,d2=dx*dx+dy*dy;d2=0&&bestD2<=tol*tol&&onPointClick(bestIdx)},exportDataRef=useRef(null);exportDataRef.current=useRasterize?{rendered,pointAlpha,summaryNs:summary.ns,summaryDown:summary.down,summaryUp:summary.up,fillFor,radiusFor}:null;let localSvgRef=useRef(null),setSvgRef=el=>{localSvgRef.current=el,typeof ref=="function"?ref(el):ref&&(ref.current=el)};return useEffect2(()=>{let el=localSvgRef.current;if(el){if(!useRasterize){unregisterSvgExportMutator(el);return}return registerSvgExportMutator(el,clone=>{let snapshot=exportDataRef.current;if(!snapshot)return;let dataPoints=clone.querySelector("#data-points");if(!dataPoints)return;let doc=clone.ownerDocument;if(!doc)return;let NS="http://www.w3.org/2000/svg";dataPoints.querySelectorAll(":scope > image, :scope > rect, :scope > g[id^='points-']").forEach(node=>node.parentNode&&node.parentNode.removeChild(node));let classMeta=[{cls:"ns",count:snapshot.summaryNs,label:"not significant"},{cls:"down",count:snapshot.summaryDown,label:"downregulated"},{cls:"up",count:snapshot.summaryUp,label:"upregulated"}];for(let meta of classMeta){let g=doc.createElementNS(NS,"g");g.setAttribute("id",`points-${meta.cls}`),g.setAttribute("fill-opacity",String(snapshot.pointAlpha)),g.setAttribute("aria-label",`${meta.count} ${meta.label} point${meta.count!==1?"s":""}`);for(let r of snapshot.rendered){if(r.cls!==meta.cls)continue;let c=doc.createElementNS(NS,"circle");c.setAttribute("cx",String(r.px.x)),c.setAttribute("cy",String(r.px.y)),c.setAttribute("r",String(snapshot.radiusFor(r.pt.idx))),c.setAttribute("fill",snapshot.fillFor(r.pt.idx,meta.cls)),g.appendChild(c)}dataPoints.appendChild(g)}}),()=>unregisterSvgExportMutator(el)}},[useRasterize]),React.createElement("svg",{ref:setSvgRef,viewBox:`0 0 ${VBW} ${VBH}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":title||"Volcano plot"},React.createElement("title",null,title||"Volcano plot"),React.createElement("desc",null,`Volcano plot of ${summary.total} point${summary.total!==1?"s":""}: ${summary.up} up, ${summary.down} down, ${summary.ns} not significant${summary.discarded>0?`, ${summary.discarded} discarded`:""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:VBW,height:VBH,fill:plotBg||"#fff"})),React.createElement("g",{id:"plot-area-background"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"})),showAxes&&React.createElement("g",{id:"grid"},xTicks.map((t,i)=>React.createElement("line",{key:`gx-${i}`,x1:sx(t),x2:sx(t),y1:MARGIN.top,y2:MARGIN.top+h,stroke:"#e5e5e5",strokeWidth:"0.6"})),yTicks.map((t,i)=>React.createElement("line",{key:`gy-${i}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:"#e5e5e5",strokeWidth:"0.6"}))),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:`tx-${t}`},React.createElement("line",{x1:sx(t),x2:sx(t),y1:MARGIN.top+h,y2:MARGIN.top+h+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:MARGIN.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:`ty-${t}`},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:VBH-14,textAnchor:"middle",fontSize:"13",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},xLabel)),React.createElement("g",{id:"y-axis-label"},React.createElement("text",{x:20,y:MARGIN.top+h/2,textAnchor:"middle",fontSize:"13",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace",transform:`rotate(-90, 20, ${MARGIN.top+h/2})`},yLabel)),title&&React.createElement("g",{id:"title"},React.createElement("text",{x:MARGIN.left+w/2,y:18,textAnchor:"middle",fontSize:"14",fontWeight:"700",fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},title)),subtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:MARGIN.left+w/2,y:VBH-30,textAnchor:"middle",fontSize:"11",fill:"#666",fontFamily:"ui-monospace, Menlo, monospace"},subtitle)),showRefLines&&React.createElement("g",{id:"reference-lines"},refXLeft>=xMin&&refXLeft<=xMax&&React.createElement("line",{x1:sx(refXLeft),x2:sx(refXLeft),y1:MARGIN.top,y2:MARGIN.top+h,stroke:"#888",strokeWidth:"1",strokeDasharray:"4 4"}),refXRight>=xMin&&refXRight<=xMax&&React.createElement("line",{x1:sx(refXRight),x2:sx(refXRight),y1:MARGIN.top,y2:MARGIN.top+h,stroke:"#888",strokeWidth:"1",strokeDasharray:"4 4"}),refY>=yMin&&refY<=yMax&&React.createElement("line",{x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(refY),y2:sy(refY),stroke:"#888",strokeWidth:"1",strokeDasharray:"4 4"})),React.createElement("g",{id:"data-points","aria-label":`${summary.total} point${summary.total!==1?"s":""} total`},useRasterize?React.createElement(React.Fragment,null,pointsImageHref&&React.createElement("image",{x:0,y:0,width:VBW,height:VBH,href:pointsImageHref,preserveAspectRatio:"none","aria-hidden":"true"}),["ns","down","up"].map(cls=>{let count=cls==="up"?summary.up:cls==="down"?summary.down:summary.ns,classLabel=cls==="up"?"upregulated":cls==="down"?"downregulated":"not significant";return React.createElement("g",{key:cls,id:`points-${cls}`,"aria-label":`${count} ${classLabel} point${count!==1?"s":""}`})}),onPointClick&&React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:"none",pointerEvents:"all",style:{cursor:"pointer"},onClick:handleRasterClick,"aria-hidden":"true"})):["ns","down","up"].map(cls=>{let count=cls==="up"?summary.up:cls==="down"?summary.down:summary.ns,classLabel=cls==="up"?"upregulated":cls==="down"?"downregulated":"not significant";return React.createElement("g",{key:cls,id:`points-${cls}`,fillOpacity:pointAlpha,"aria-label":`${count} ${classLabel} point${count!==1?"s":""}`},rendered.filter(r=>r.cls===cls).map((r,i)=>React.createElement("circle",{key:i,cx:r.px.x,cy:r.px.y,r:radiusFor(r.pt.idx),fill:fillFor(r.pt.idx,cls),style:onPointClick?{cursor:"pointer"}:void 0,onClick:onPointClick?e=>{e.stopPropagation(),onPointClick(r.pt.idx)}:void 0},React.createElement("title",null,(r.pt.label?r.pt.label+" \xB7 ":"")+"log\u2082FC="+r.pt.log2fc.toFixed(3)+", p="+(r.pt.p===0?"0 (clamped)":r.pt.p.toExponential(2))+", "+cls+(onPointClick?" \u2014 click to label":"")))))})),showLabels&&labels.length>0&&React.createElement("g",{id:"selected-point-rings"},labels.map((lab,i)=>React.createElement("circle",{key:i,cx:MARGIN.left+lab.pointPx.x,cy:MARGIN.top+lab.pointPx.y,r:(labelRadii[i]||pointRadius)+SELECTION_RING_PAD,fill:"none",stroke:"#222",strokeWidth:"1.4"}))),showLabels&&labels.length>0&&React.createElement("g",{id:"top-n-leaders"},labels.map((lab,i)=>React.createElement("line",{key:i,x1:MARGIN.left+lab.leaderStart.x,y1:MARGIN.top+lab.leaderStart.y,x2:MARGIN.left+lab.leaderEnd.x,y2:MARGIN.top+lab.leaderEnd.y,stroke:"#666",strokeWidth:lab.forced?"1":"0.7",strokeDasharray:"3 2"}))),showLabels&&labels.length>0&&React.createElement("g",{id:"top-n-labels"},labels.map((lab,i)=>React.createElement("text",{key:i,x:MARGIN.left+lab.textPx.x,y:MARGIN.top+lab.textPx.y,textAnchor:"middle",fontSize:labelFontSize,fill:"#222",fontFamily:"ui-monospace, Menlo, monospace"},lab.text))),React.createElement("g",{id:"plot-frame"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:"none",stroke:"#333",strokeWidth:"1"})),hasLegend&&React.createElement("g",{id:"aesthetic-legends",transform:`translate(${MARGIN.left+w+LEGEND_GAP}, ${MARGIN.top})`},colorMap&&React.createElement(ColorLegend,{colorMap,title:colorMapLabel||"color",width:LEGEND_W-LEGEND_GAP,yOffset:0}),sizeMap&&React.createElement(SizeLegend,{sizeMap,title:sizeMapLabel||"size",width:LEGEND_W-LEGEND_GAP,yOffset:colorMap?colorMap.type==="continuous"?78:Math.min(14,colorMap.legend.length)*14+28:0})))}));function ConfigureStep({parsed,fileName,detectedSep,xCol,yCol,labelCol,yIsAdjusted,setXCol,setYCol,setLabelCol,setYIsAdjusted}){let xValid=xCol>=0,yValid=yCol>=0;return React.createElement("div",null,React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(260px, 1fr))",gap:12,marginBottom:12}},React.createElement(VolcanoAesBox,{theme:"x"},React.createElement("select",{className:"dv-select",style:{width:"100%"},value:xValid?xCol:"",onChange:e=>{let raw=e.target.value;raw!==""&&setXCol(Number(raw))}},!xValid&&React.createElement("option",{value:""},"\u2014 choose a log\u2082FC column \u2014"),parsed.headers.map((h,i)=>React.createElement("option",{key:i,value:i},h))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column with the log\u2082 fold change for each feature. DESeq2:"," ",React.createElement("code",null,"log2FoldChange"),"; limma / edgeR: ",React.createElement("code",null,"logFC"),".")),React.createElement(VolcanoAesBox,{theme:"y"},React.createElement("select",{className:"dv-select",style:{width:"100%"},value:yValid?yCol:"",onChange:e=>{let raw=e.target.value;raw!==""&&setYCol(Number(raw))}},!yValid&&React.createElement("option",{value:""},"\u2014 choose a p-value column \u2014"),parsed.headers.map((h,i)=>React.createElement("option",{key:i,value:i},h))),React.createElement("label",{style:{display:"flex",alignItems:"center",gap:6,marginTop:8,fontSize:11,color:"var(--text)"}},React.createElement("input",{type:"checkbox",checked:yIsAdjusted,onChange:e=>setYIsAdjusted(e.target.checked),style:{accentColor:"var(--cta-primary-bg)",margin:0,flexShrink:0}}),React.createElement("span",null,"This column is an ",React.createElement("strong",null,"adjusted")," p-value (FDR / BH / qvalue)")),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Plotted as \u2212log\u2081\u2080(p). Auto-detect prefers an adjusted column when both raw and adjusted are present.")),React.createElement(VolcanoAesBox,{theme:"label"},React.createElement("select",{className:"dv-select",style:{width:"100%"},value:labelCol,onChange:e=>setLabelCol(parseInt(e.target.value))},React.createElement("option",{value:-1},"\u2014 none \u2014"),parsed.headers.map((h,i)=>React.createElement("option",{key:i,value:i},h))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Categorical column used to annotate the top-N most-significant features (gene symbol, protein name, accession). Skip if your data has no such column."))),(!xValid||!yValid)&&React.createElement("div",{className:"dv-panel",style:{background:"var(--warning-bg)",borderColor:"var(--warning-border)",marginBottom:12}},React.createElement("p",{style:{fontSize:12,color:"var(--warning-text)",margin:0}},"Assign both a ",React.createElement("strong",null,"log\u2082FC column")," and a ",React.createElement("strong",null,"p-value column")," to unlock the Plot step in the navigation above.")),React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,color:"var(--text-muted)"}},React.createElement("strong",{style:{color:"var(--text)"}},fileName||"(pasted data)")," \u2014"," ",parsed.headers.length," cols \xD7 ",parsed.rawData.length," rows",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement("p",{style:{fontSize:11,color:"var(--text-faint)",marginBottom:10}},"Preview (first 8 rows):"),React.createElement(DataPreview,{headers:parsed.headers,rows:parsed.rawData,maxRows:8})))}function PlotStep({chartRef,parsed,xCol,yCol,labelCol,points,pFloor,clampedCount,summary,xLabel,yLabel,vis,updVis,manualSelection,togglePointSelection,clearManualSelection,addToManualSelection,colorMapCol,setColorMapCol,colorMap,colorMapLabel,sizeMapCol,setSizeMapCol,sizeMap,sizeMapLabel,fileName,onDownloadCsv,onDownloadR,onReset,labelDensity,onLabelLayoutInfo}){return React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotSidebar,null,React.createElement(DownloadTiles,{chartRef,fileStem:fileBaseName(fileName,"volcano"),onReset,extraDownloads:[{label:"CSV",title:"Download the per-feature classification table \u2014 feature, log2FC, p, \u2212log10(p), class",onClick:onDownloadCsv},{label:"R",title:"Download a self-contained ggplot2 R script that reproduces this volcano from the underlying data",onClick:onDownloadR}]}),React.createElement(ThresholdsTile,{vis,updVis}),React.createElement(ColorsTile,{vis,updVis}),React.createElement(ColorMapTile,{parsed,xCol,yCol,labelCol,col:colorMapCol,setCol:setColorMapCol,colorMap,vis,updVis}),React.createElement(SizeMapTile,{parsed,xCol,yCol,labelCol,col:sizeMapCol,setCol:setSizeMapCol,vis,updVis}),React.createElement(LabelsTile,{vis,updVis,manualSelection,clearManualSelection,points,labelCol,addToManualSelection,labelDensity}),React.createElement(StyleTile,{vis,updVis})),React.createElement("div",{style:{flex:"1 1 0",minWidth:0,display:"flex",flexDirection:"column",gap:12}},clampedCount>0&&React.createElement("div",{role:"status",style:{padding:"8px 12px",borderRadius:8,background:"var(--warning-bg)",border:"1px solid var(--warning-border)",fontSize:11,color:"var(--warning-text)"}},"\u26A0\uFE0F ",clampedCount," feature",clampedCount===1?"":"s"," had p = 0; clamped to a finite floor for display so the y-axis stays bounded."),React.createElement("div",{style:{background:"var(--plot-card-bg)",border:"1.5px solid var(--plot-card-border)",borderRadius:10,padding:16}},React.createElement(VolcanoChart,{ref:chartRef,points,pFloor,fcCutoff:vis.fcCutoff,pCutoff:vis.pCutoff,xMin:vis.xMin,xMax:vis.xMax,yMin:vis.yMin,yMax:vis.yMax,xLabel,yLabel,title:vis.plotTitle,subtitle:"",colors:{up:vis.colorUp,down:vis.colorDown,ns:vis.colorNs},pointRadius:vis.pointRadius,pointAlpha:vis.pointAlpha,showRefLines:vis.showRefLines,showLabels:vis.showLabels,topNUp:vis.topNUp,topNDown:vis.topNDown,labelFontSize:vis.labelFontSize,showAxes:vis.showAxes,manualSelection,onPointClick:togglePointSelection,colorMap,colorMapLabel,sizeMap,sizeMapLabel,plotWidth:vis.plotWidth,plotBg:"#ffffff",onLabelLayoutInfo})),React.createElement(SummaryTile,{summary,fcCutoff:vis.fcCutoff,pCutoff:vis.pCutoff})))}function buildVolcanoRScript(args){let{points,fcCutoff,pCutoff,colors,xLabel,yLabel,plotTitle,yIsAdjusted,generatedAt}=args,now=generatedAt||new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),summary=summarize(points,fcCutoff,pCutoff),featureItems=[],log2fcItems=[],pItems=[];for(let pt of points){if(!Number.isFinite(pt.log2fc)||!Number.isFinite(pt.p))continue;let labelStr=pt.label!=null&&pt.label!==""?'"'+sanitizeRString(pt.label)+'"':'"feature_'+(pt.idx+1)+'"';featureItems.push(labelStr),log2fcItems.push(formatRNumber(pt.log2fc)),pItems.push(formatRNumber(pt.p))}let lines=[];return lines.push("# -----------------------------------------------------------------------------"),lines.push("# Pl\xF6ttr \u2014 Volcano plot R script export"),lines.push("# Generated: "+now),lines.push("#"),lines.push("# Reproduces the volcano plot you saw in the browser tool, using ggplot2."),lines.push("# Each point is one feature. Classification:"),lines.push("# up : log2FC > +"+formatRNumber(fcCutoff)+" AND p < "+formatRNumber(pCutoff)),lines.push("# down : log2FC < -"+formatRNumber(fcCutoff)+" AND p < "+formatRNumber(pCutoff)),lines.push("# ns : everything else"),lines.push("# Y-axis is -log10("+(yIsAdjusted?"adjusted p-value":"raw p-value")+") \u2014 pick the column that matches your downstream pipeline."),lines.push("#"),lines.push("# Toolbox reported: up="+summary.up+", down="+summary.down+", ns="+summary.ns+" (out of "+summary.total+" valid points; "+summary.discarded+" discarded for non-finite values)."),lines.push("# -----------------------------------------------------------------------------"),lines.push(""),lines.push('if (!requireNamespace("ggplot2", quietly = TRUE)) install.packages("ggplot2")'),lines.push("library(ggplot2)"),lines.push(""),lines.push("df <- data.frame("),lines.push(" feature = "+wrapRItems(featureItems,6)+","),lines.push(" log2FC = "+wrapRItems(log2fcItems,8)+","),lines.push(" pvalue = "+wrapRItems(pItems,8)+","),lines.push(" stringsAsFactors = FALSE"),lines.push(")"),lines.push(""),lines.push("# Clamp p = 0 to a finite floor (smallest non-zero / 10) so -log10 stays finite."),lines.push("p_floor <- min(df$pvalue[df$pvalue > 0], na.rm = TRUE) / 10"),lines.push("df$nlog10p <- -log10(pmax(df$pvalue, p_floor))"),lines.push("df$class <- with(df, ifelse("),lines.push(" pvalue < "+formatRNumber(pCutoff)+" & log2FC > "+formatRNumber(fcCutoff)+', "up",'),lines.push(" ifelse("),lines.push(" pvalue < "+formatRNumber(pCutoff)+" & log2FC < -"+formatRNumber(fcCutoff)+', "down", "ns"'),lines.push(" )"),lines.push("))"),lines.push('df$class <- factor(df$class, levels = c("ns", "down", "up"))'),lines.push(""),lines.push('plot_colors <- c(up = "'+colors.up+'", down = "'+colors.down+'", ns = "'+colors.ns+'")'),lines.push(""),lines.push("ggplot(df, aes(x = log2FC, y = nlog10p, color = class)) +"),lines.push(" geom_point(alpha = 0.7) +"),lines.push(" geom_vline(xintercept = c(-"+formatRNumber(fcCutoff)+", "+formatRNumber(fcCutoff)+'), linetype = "dashed", color = "grey60") +'),lines.push(" geom_hline(yintercept = -log10("+formatRNumber(pCutoff)+'), linetype = "dashed", color = "grey60") +'),lines.push(" scale_color_manual(values = plot_colors) +"),lines.push(' labs(x = "'+sanitizeRString(xLabel||"log2(fold change)")+'", y = "'+sanitizeRString(yLabel||"-log10(p)")+'"'+(plotTitle?', title = "'+sanitizeRString(plotTitle)+'"':"")+") +"),lines.push(" theme_classic() +"),lines.push(' theme(legend.position = "right")'),lines.push(""),lines.join(` `)}function buildVolcanoCsv(args){let{points,fcCutoff,pCutoff,yIsAdjusted}=args,headers=["feature","log2FC",yIsAdjusted?"p_adjusted":"p_value","neg_log10_p","class"],rows=[],minNonZero=1/0;for(let pt of points)Number.isFinite(pt.p)&&pt.p>0&&pt.p0?-Math.log10(safe):"",cls="ns";Number.isFinite(pt.log2fc)&&Number.isFinite(pt.p)&&pt.pfcCutoff?cls="up":pt.log2fc<-fcCutoff&&(cls="down")),(!Number.isFinite(pt.log2fc)||!Number.isFinite(pt.p))&&(cls="discarded"),rows.push([pt.label!=null?pt.label:"feature_"+(pt.idx+1),Number.isFinite(pt.log2fc)?String(pt.log2fc):"",Number.isFinite(pt.p)?String(pt.p):"",typeof nl=="number"?String(nl):"",cls])}return{headers,rows}}var{useState:useState2,useEffect:useEffect3,useMemo:useMemo3,useCallback,useRef:useRef2}=React,VIS_INIT_VOLCANO={fcCutoff:1,pCutoff:.05,topNUp:10,topNDown:10,showLabels:!0,showRefLines:!0,showAxes:!0,pointRadius:3,pointAlpha:.7,labelFontSize:11,colorMapPalette:"viridis",colorMapInvert:!1,sizeMapMinR:2,sizeMapMaxR:9,plotWidth:800,colorUp:VOLCANO_DEFAULT_COLORS.up,colorDown:VOLCANO_DEFAULT_COLORS.down,colorNs:VOLCANO_DEFAULT_COLORS.ns,discretePalette:"okabe-ito",xMin:null,xMax:null,yMin:null,yMax:null,plotTitle:""},EXAMPLE_TSV=`gene_symbol log2FoldChange padj pvalue baseMean CCA1 -3.42 1.2e-18 6.0e-19 158 LHY -3.15 4.5e-17 3.8e-17 2336 @@ -216,4 +216,4 @@ AT3G47620 1.48 6.4e-7 4.5e-7 158 AT4G02380 -1.21 6.4e-5 3.5e-5 2336 AT5G64750 1.34 1.6e-5 1.4e-5 34538 `;function App(){let shell=usePlotToolState("volcano",VIS_INIT_VOLCANO),{step,setStep,fileName,setFileName,setParseError,sepOverride,setSepOverride,setCommaFixed,setCommaFixCount,setInjectionWarning,vis,updVis}=shell,[parsed,setParsed]=useState2(null),[xCol,setXCol]=useState2(-1),[yCol,setYCol]=useState2(-1),[labelCol,setLabelCol]=useState2(-1),[yIsAdjusted,setYIsAdjusted]=useState2(!1),[rawText,setRawText]=useState2(null),sepRef=useRef2(""),[detectedSep,setDetectedSep]=useState2("");useEffect3(()=>{vis.colorNs!==VOLCANO_DEFAULT_COLORS.ns&&updVis({colorNs:VOLCANO_DEFAULT_COLORS.ns})},[vis.discretePalette]);let[manualSelection,setManualSelection]=useState2(()=>new Set),[colorMapCol,setColorMapCol]=useState2(-1),[sizeMapCol,setSizeMapCol]=useState2(-1),togglePointSelection=useCallback(idx=>{setManualSelection(prev=>{let next=new Set(prev);return next.has(idx)?next.delete(idx):next.add(idx),next})},[]),addToManualSelection=useCallback(indices=>{!indices||indices.length===0||setManualSelection(prev=>{let next=new Set(prev);for(let i of indices)next.add(i);return next})},[]),clearManualSelection=useCallback(()=>setManualSelection(new Set),[]),doParse=useCallback((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";sepRef.current=effectiveSep,setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count);let fixed=dc.text,out=parseData(fixed,effectiveSep);if(setInjectionWarning(out.injectionWarnings),out.headers.length<2||out.data.length===0){setParseError("The file appears empty or has fewer than two columns. Volcano expects at least a log2FC and a p-value column.");return}setParseError(null),setRawText(fixed),setParsed(out);let guess=autoDetectColumns(out.headers);setXCol(guess.xCol>=0?guess.xCol:0),setYCol(guess.yCol>=0?guess.yCol:Math.min(1,out.headers.length-1)),setLabelCol(guess.labelCol),setYIsAdjusted(guess.yIsAdjusted),setManualSelection(new Set),setColorMapCol(-1),setSizeMapCol(-1),setStep("configure")},[setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep]),handleFileLoad=useCallback((text,name)=>{setFileName(name),doParse(text,sepOverride)},[doParse,setFileName,sepOverride]),handleTextPaste=useCallback((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]),onLoadExample=useCallback(()=>{let ex=EXAMPLE_TSV;ex&&(setSepOverride(""),setFileName("volcano_example.tsv"),doParse(ex,""))},[doParse,setFileName,setSepOverride]),resetAll=useCallback(()=>{setRawText(null),setParsed(null),setXCol(-1),setYCol(-1),setLabelCol(-1),setYIsAdjusted(!1),setFileName(""),setInjectionWarning(null),setManualSelection(new Set),setColorMapCol(-1),setSizeMapCol(-1),setStep("upload")},[setFileName,setInjectionWarning,setStep]),points=useMemo3(()=>!parsed||xCol<0||yCol<0?[]:buildPoints(parsed.rawData,xCol,yCol,labelCol),[parsed,xCol,yCol,labelCol]),pFloor=useMemo3(()=>computePFloor(points),[points]),clampedCount=useMemo3(()=>countClamped(points),[points]),summary=useMemo3(()=>summarize(points,vis.fcCutoff,vis.pCutoff),[points,vis.fcCutoff,vis.pCutoff]),xLabel=parsed&&xCol>=0?parsed.headers[xCol]:"log\u2082(fold change)",yLabel=parsed&&yCol>=0?"\u2212log\u2081\u2080("+parsed.headers[yCol]+")":"\u2212log\u2081\u2080(p-value)",colorMap=useMemo3(()=>{if(!parsed||colorMapCol<0)return null;let baseStops=COLOR_PALETTES[vis.colorMapPalette]||COLOR_PALETTES.viridis,stops=vis.colorMapInvert?[...baseStops].reverse():baseStops,sigIndices=[];for(let pt of points)classifyPoint(pt.log2fc,pt.p,vis.fcCutoff,vis.pCutoff)!=="ns"&&sigIndices.push(pt.idx);return buildColorMap({rawData:parsed.rawData,pointIndices:sigIndices,col:colorMapCol,paletteStops:stops,paletteName:vis.colorMapPalette,discretePalette:PALETTE,interpolate:interpolateColor})},[parsed,colorMapCol,vis.colorMapPalette,vis.colorMapInvert,vis.fcCutoff,vis.pCutoff,points]),sizeMap=useMemo3(()=>!parsed||sizeMapCol<0?null:buildSizeMap(parsed.rawData,points.map(p=>p.idx),sizeMapCol,vis.sizeMapMinR,vis.sizeMapMaxR),[parsed,sizeMapCol,vis.sizeMapMinR,vis.sizeMapMaxR,points]),colorMapLabel=parsed&&colorMapCol>=0?parsed.headers[colorMapCol]:"",sizeMapLabel=parsed&&sizeMapCol>=0?parsed.headers[sizeMapCol]:"",chartRef=useRef2(null),[labelDensity,setLabelDensity]=useState2({forcedCount:0,attemptedCount:0}),onDownloadCsv=()=>{let{headers,rows}=buildVolcanoCsv({points,fcCutoff:vis.fcCutoff,pCutoff:vis.pCutoff,yIsAdjusted});downloadCsv(headers,rows,fileBaseName(fileName,"volcano")+"_classified.csv")},onDownloadR=()=>{let txt=buildVolcanoRScript({points,fcCutoff:vis.fcCutoff,pCutoff:vis.pCutoff,colors:{up:vis.colorUp,down:vis.colorDown,ns:vis.colorNs},xLabel,yLabel,plotTitle:vis.plotTitle,yIsAdjusted});downloadText(txt,fileBaseName(fileName,"volcano")+".R")};return React.createElement(PlotToolShell,{state:shell,toolName:"volcano",title:"Volcano Plot",visInit:VIS_INIT_VOLCANO,steps:["upload","configure","plot"],canNavigate:target=>target==="upload"?!0:target==="configure"?!!parsed:target==="plot"?!!parsed&&xCol>=0&&yCol>=0:!1},step==="upload"&&React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Mock DESeq2 results",subtitle:"200 features \xB7 plant circadian transcriptomics",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \xB7 one row per feature \xB7 expects log2FC + p-value columns \xB7 2 MB max"}),React.createElement(HowTo,{...VOLCANO_HOWTO})),step==="configure"&&parsed&&React.createElement(ConfigureStep,{parsed,fileName,detectedSep,xCol,yCol,labelCol,yIsAdjusted,setXCol,setYCol,setLabelCol,setYIsAdjusted}),step==="plot"&&parsed&&React.createElement(PlotStep,{chartRef,parsed,xCol,yCol,labelCol,points,pFloor,clampedCount,summary,xLabel,yLabel,vis,updVis,manualSelection,togglePointSelection,clearManualSelection,addToManualSelection,colorMapCol,setColorMapCol,colorMap,colorMapLabel,sizeMapCol,setSizeMapCol,sizeMap,sizeMapLabel,fileName,onDownloadCsv,onDownloadR,onReset:resetAll,labelDensity,onLabelLayoutInfo:setLabelDensity}))}export{App}; -//# sourceMappingURL=app-5CQNAWMD.js.map +//# sourceMappingURL=app-PCLQZVY3.js.map diff --git a/tools/_app/chunks/app-5CQNAWMD.js.map b/tools/_app/chunks/app-PCLQZVY3.js.map similarity index 100% rename from tools/_app/chunks/app-5CQNAWMD.js.map rename to tools/_app/chunks/app-PCLQZVY3.js.map diff --git a/tools/_app/chunks/app-NW63HSPZ.js b/tools/_app/chunks/app-RNTWQ7N2.js similarity index 99% rename from tools/_app/chunks/app-NW63HSPZ.js rename to tools/_app/chunks/app-RNTWQ7N2.js index cb1d803a..ab5ce321 100644 --- a/tools/_app/chunks/app-NW63HSPZ.js +++ b/tools/_app/chunks/app-RNTWQ7N2.js @@ -1,4 +1,4 @@ -import{makeTicks}from"./chunk-T4V5V5TK.js";import{BaseStyleControls,COLOR_PALETTES,ColorInput,ControlSection,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,NumberInput,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,SliderControl,UploadPanel,applyDiscretePalette,autoDetectSep,computeLegendHeight,downloadCsv,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,formatRVector,interpolateColor,isNumericValue,kendallTau,pStars,parseData,pearsonCorrelation,renderSvgLegend,resolveDiscretePalette,sampleMean,sampleSD,sanitizeRComment,sanitizeRString,scrollDisclosureIntoView,selectCorrelation,spearmanCorrelation,svgSafeId,usePlotToolState}from"./chunk-5F6S3FYW.js";function fmtTick(t){if(t===0)return"0";let abs=Math.abs(t);return abs>=1e4||abs<.01&&abs>0?t.toExponential(1):abs>=100?t.toFixed(0):parseFloat(t.toPrecision(3)).toString()}var SHAPES=["circle","triangle","cross","square"],MARGIN={top:28,right:28,bottom:56,left:70},VBW=800,VBH=500;function computeLinearRegression(rows,xCol,yCol){if(!rows||rows.length<2)return{valid:!1};let n=0,sx=0,sy=0,sxx=0,syy=0,sxy=0;for(let row of rows){let x=row[xCol],y=row[yCol];x==null||y==null||isNaN(x)||isNaN(y)||(n++,sx+=x,sy+=y,sxx+=x*x,syy+=y*y,sxy+=x*y)}if(n<2)return{valid:!1};let denomX=n*sxx-sx*sx;if(denomX===0)return{valid:!1};let slope=(n*sxy-sx*sy)/denomX,intercept=(sy-slope*sx)/n,denomY=n*syy-sy*sy,r2Raw=denomY===0?NaN:Math.pow(n*sxy-sx*sy,2)/(denomX*denomY),r2=Number.isFinite(r2Raw)?Math.min(1,Math.max(0,r2Raw)):r2Raw;return{valid:!0,slope,intercept,r2,n}}var CORR_TEST_LABELS={pearson:"Pearson r",spearman:"Spearman \u03C1",kendall:"Kendall \u03C4"},CORR_TEST_OPTIONS=["pearson","spearman","kendall"];function runCorrelation(test,xs,ys,opts){return test==="spearman"?{kind:"spearman",...spearmanCorrelation(xs,ys,opts)}:test==="kendall"?{kind:"kendall",...kendallTau(xs,ys,opts)}:{kind:"pearson",...pearsonCorrelation(xs,ys,opts)}}function correlationCoef(res){return res.kind==="spearman"?res.rho:res.kind==="kendall"?res.tau:res.r}function formatCorrStatShort(res){return!res||res.error?"\u2014":res.kind==="pearson"?`r = ${res.r.toFixed(3)}, t(${res.df}) = ${res.t.toFixed(3)}`:res.kind==="spearman"?`\u03C1 = ${res.rho.toFixed(3)}, t(${res.df}) = ${res.t.toFixed(3)}`:`\u03C4 = ${res.tau.toFixed(3)}, z = ${res.z.toFixed(3)}`}function formatCorrResultLine(res){if(!res)return"\u2014";if(res.error)return"\u26A0 "+res.error;if(res.kind==="pearson"){let ci=res.ci,ciStr=Number.isFinite(ci.lo)?`, 95% CI [${ci.lo.toFixed(3)}, ${ci.hi.toFixed(3)}]`:"";return`r = ${res.r.toFixed(3)}${ciStr}, t(${res.df}) = ${res.t.toFixed(3)}, p = ${formatP(res.p)}`}if(res.kind==="spearman"){let ci=res.ci,ciStr=Number.isFinite(ci.lo)?`, 95% CI [${ci.lo.toFixed(3)}, ${ci.hi.toFixed(3)}]`:"";return`\u03C1 = ${res.rho.toFixed(3)}${ciStr}, t(${res.df}) = ${res.t.toFixed(3)}, p = ${formatP(res.p)}`}return`\u03C4 = ${res.tau.toFixed(3)}, z = ${res.z.toFixed(3)}, p = ${formatP(res.p)}`}var SCATTER_HOWTO={toolName:"scatter",title:"Scatter Plot \u2014 How to use",subtitle:"XY scatter with optional colour / size / shape mapping",purpose:React.createElement(React.Fragment,null,"Plot one numeric column against another. Map a third (or fourth, or fifth) column to point colour, size, or shape to surface multivariate structure in a single view."),dataLayout:React.createElement(React.Fragment,null,"One row per point. At least two ",React.createElement("strong",null,"numeric")," columns (the X and Y axes). Extra columns \u2014 categorical or numeric \u2014 become aesthetic mappings or filters."),display:React.createElement(React.Fragment,null,"Optional colour-by-column (continuous gradient or discrete swatches), size-by-column, and shape-by-column. Add ",React.createElement("strong",null,"reference lines")," at fixed X / Y values, overlay a"," ",React.createElement("strong",null,"linear regression")," with R\xB2 + p-value. Filter rows by any categorical column.")};var aesTheme={color:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Color"},size:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Size"},shape:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"Shape"}};function AesBox({theme,children}){let t=aesTheme[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function UploadStep({sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Fisher's Iris dataset",subtitle:"150 flowers \xD7 4 measurements \xB7 3 species",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 one column per variable, one row per point \xB7 2 MB max"}),React.createElement(HowTo,{...SCATTER_HOWTO}))}function PaletteStrip({palette,width,height=12}){let stops=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,n=48;return React.createElement("div",{style:{display:"flex",width:width||"100%",height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border)"}},Array.from({length:n},(_,i)=>React.createElement("div",{key:i,style:{flex:1,background:interpolateColor(stops,i/(n-1))}})))}function renderPoint(shape,cx,cy,r,props){let{fill,fillOpacity,stroke,strokeWidth,key}=props;switch(shape){case"triangle":{let bx=r*.866,by=cy+r*.5;return React.createElement("polygon",{key,points:`${cx},${cy-r} ${cx-bx},${by} ${cx+bx},${by}`,fill,fillOpacity,stroke,strokeWidth})}case"square":{let s=r*1.4;return React.createElement("rect",{key,x:cx-s/2,y:cy-s/2,width:s,height:s,fill,fillOpacity,stroke,strokeWidth})}case"cross":{let t=r*.35;return React.createElement("path",{key,d:`M${cx-r},${cy-t}H${cx-t}V${cy-r}H${cx+t}V${cy-t}H${cx+r}V${cy+t}H${cx+t}V${cy+r}H${cx-t}V${cy+t}H${cx-r}Z`,fill,fillOpacity,stroke,strokeWidth})}default:return React.createElement("circle",{key,cx,cy,r,fill,fillOpacity,stroke,strokeWidth})}}function ShapePreview({shape,size=16,color="#666"}){return React.createElement("svg",{width:size,height:size,viewBox:"0 0 16 16",style:{display:"block",flexShrink:0},"aria-hidden":"true"},renderPoint(shape,8,8,6,{fill:color,fillOpacity:1,stroke:"none",strokeWidth:0}))}var{forwardRef,memo}=React,ScatterChart=memo(forwardRef(function({data,rawData,xCol,yCol,xMin,xMax,yMin,yMax,xLabel,yLabel,title,plotBg,showGrid,gridColor,refLines,regression,regressionStats,pointColor,pointSize,pointOpacity,strokeColor,strokeWidth,colorMapCol,colorMapType,colorMapPalette,colorMapDiscrete,colorMapRange,sizeMapCol,sizeMapType,sizeMapMin,sizeMapMax,sizeMapDiscrete,sizeMapRange,shapeMapCol,shapeMapDiscrete,svgLegend},ref){let w=VBW-MARGIN.left-MARGIN.right,h=VBH-MARGIN.top-MARGIN.bottom,legendItemWidth=block=>{if(!block.items)return 88;let maxLen=block.items.reduce((m,it)=>Math.max(m,(it.label||"").length),0);return Math.max(88,Math.min(260,maxLen*6.2+22))},legendH=computeLegendHeight(svgLegend,VBW-MARGIN.left-MARGIN.right,legendItemWidth),xRange=xMax-xMin||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xMin)/xRange*w,sy=v=>MARGIN.top+(1-(v-yMin)/yRange)*h,xTicks=makeTicks(xMin,xMax,8),yTicks=makeTicks(yMin,yMax,6),getColor=(xVal,yVal,rowIdx)=>{if(colorMapCol!=null&&rawData){let raw=rawData[rowIdx]?rawData[rowIdx][colorMapCol]:null;if(raw!=null&&raw!=="")if(colorMapType==="continuous"){let num=parseFloat(raw.replace(",","."));if(!isNaN(num)){let[cMin,cMax]=colorMapRange,t=Math.max(0,Math.min(1,(num-cMin)/(cMax-cMin||1)));return interpolateColor(COLOR_PALETTES[colorMapPalette]||COLOR_PALETTES.viridis,t)}}else return colorMapDiscrete[raw]||pointColor}return pointColor},getSize=rowIdx=>{if(sizeMapCol!=null&&rawData){let raw=rawData[rowIdx]?rawData[rowIdx][sizeMapCol]:null;if(raw!=null&&raw!=="")if(sizeMapType==="continuous"){let num=parseFloat(raw.replace(",","."));if(!isNaN(num)){let[sMin,sMax]=sizeMapRange,t=Math.max(0,Math.min(1,(num-sMin)/(sMax-sMin||1)));return sizeMapMin+t*(sizeMapMax-sizeMapMin)}}else return sizeMapDiscrete[raw]!==void 0?sizeMapDiscrete[raw]:pointSize}return pointSize},getShape=rowIdx=>{if(shapeMapCol!=null&&rawData){let raw=rawData[rowIdx]?rawData[rowIdx][shapeMapCol]:null;if(raw!=null&&raw!==""&&shapeMapDiscrete[raw]!==void 0)return shapeMapDiscrete[raw]}return"circle"};return React.createElement("svg",{ref,viewBox:`0 0 ${VBW} ${VBH+legendH}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":title||"Scatter plot"},React.createElement("title",null,title||"Scatter plot"),React.createElement("desc",null,`Scatter plot with ${data.length} data point${data.length!==1?"s":""}${xLabel?`, X: ${xLabel}`:""}${yLabel?`, Y: ${yLabel}`:""}`),React.createElement("defs",null,React.createElement("clipPath",{id:"sc-clip"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h}))),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:VBW,height:VBH,fill:plotBg||"#fff"})),React.createElement("g",{id:"plot-area-background"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"})),showGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`gy-${t}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"})),xTicks.map(t=>React.createElement("line",{key:`gx-${t}`,x1:sx(t),x2:sx(t),y1:MARGIN.top,y2:MARGIN.top+h,stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"}))),React.createElement("g",{id:"reference-lines",clipPath:"url(#sc-clip)"},refLines.map(rl=>{let isH=rl.dir==="h",x1=isH?MARGIN.left:sx(rl.value),x2=isH?MARGIN.left+w:sx(rl.value),y1=isH?sy(rl.value):MARGIN.top,y2=isH?sy(rl.value):MARGIN.top+h;return isH&&(rl.valueyMax)||!isH&&(rl.valuexMax)?null:React.createElement("line",{key:rl.id,x1,y1,x2,y2,stroke:rl.color||"#444",strokeWidth:rl.strokeWidth||1.5,strokeDasharray:rl.dashed?rl.dashArray||"7,4":"none"})})),React.createElement("g",{id:"reference-line-labels"},refLines.map(rl=>{if(!rl.label)return null;if(rl.dir==="h"){if(rl.valueyMax)return null;let lx=rl.labelSide==="left"?MARGIN.left+4:MARGIN.left+w-4;return React.createElement("text",{key:`lbl-${rl.id}`,x:lx,y:sy(rl.value)-4,textAnchor:rl.labelSide==="left"?"start":"end",fontSize:"10",fill:rl.color||"#444",fontFamily:"sans-serif",fontStyle:"italic"},rl.label)}else{if(rl.valuexMax)return null;let ly=rl.labelSide==="bottom"?MARGIN.top+h-4:MARGIN.top+12;return React.createElement("text",{key:`lbl-${rl.id}`,x:sx(rl.value)+4,y:ly,textAnchor:"start",fontSize:"10",fill:rl.color||"#444",fontFamily:"sans-serif",fontStyle:"italic"},rl.label)}})),React.createElement("g",{id:"data-points",clipPath:"url(#sc-clip)",role:"group","aria-label":`${data.length} data points`},data.map((row,ri)=>{let xVal=row[xCol],yVal=row[yCol];return xVal==null||yVal==null?null:renderPoint(getShape(ri),sx(xVal),sy(yVal),getSize(ri),{key:ri,fill:getColor(xVal,yVal,ri),fillOpacity:pointOpacity,stroke:strokeColor||"none",strokeWidth:strokeWidth||0})})),regression&®ression.on&®ressionStats&®ressionStats.valid&&React.createElement("g",{id:"regression-line",clipPath:"url(#sc-clip)",role:"img","aria-label":`Linear regression: slope ${regressionStats.slope.toFixed(3)}, intercept ${regressionStats.intercept.toFixed(3)}, R\xB2 ${regressionStats.r2.toFixed(3)}, n=${regressionStats.n}`},React.createElement("line",{x1:sx(xMin),y1:sy(regressionStats.slope*xMin+regressionStats.intercept),x2:sx(xMax),y2:sy(regressionStats.slope*xMax+regressionStats.intercept),stroke:regression.color||"#dc2626",strokeWidth:regression.strokeWidth||1.5,strokeDasharray:regression.dashed?"7,4":"none"})),regression&®ression.on&®ression.showStats&®ressionStats&®ressionStats.valid&&(()=>{let pos=regression.position||"tl",tx=pos.endsWith("r")?MARGIN.left+w-8:MARGIN.left+8,ty=pos.startsWith("b")?MARGIN.top+h-8-38:MARGIN.top+8,anchor=pos.endsWith("r")?"end":"start",s=regressionStats.slope,b=regressionStats.intercept,eq=`y = ${fmtTick(s)}\xB7x ${b>=0?"+":"\u2212"} ${fmtTick(Math.abs(b))}`,r2=`R\xB2 = ${Number.isFinite(regressionStats.r2)?regressionStats.r2.toFixed(4):"undefined"}`,nTxt=`n = ${regressionStats.n}`;return React.createElement("g",{id:"regression-stats",fontFamily:"sans-serif",fontSize:"11",fill:regression.color||"#dc2626"},React.createElement("text",{x:tx,y:ty+10,textAnchor:anchor},eq),React.createElement("text",{x:tx,y:ty+24,textAnchor:anchor},r2),React.createElement("text",{x:tx,y:ty+38,textAnchor:anchor,fill:"#888"},nTxt))})(),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{id:"plot-frame-top",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top}),React.createElement("line",{id:"plot-frame-right",x1:MARGIN.left+w,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:MARGIN.left,y1:MARGIN.top+h,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-left",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left,y2:MARGIN.top+h})),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:sx(t),x2:sx(t),y1:MARGIN.top+h,y2:MARGIN.top+h+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:MARGIN.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),xLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:VBH-6,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},xLabel)),yLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${MARGIN.top+h/2}) rotate(-90)`,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},yLabel)),title&&React.createElement("g",{id:"title"},React.createElement("text",{x:VBW/2,y:16,textAnchor:"middle",fontSize:"15",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},title)),renderSvgLegend(svgLegend,VBH+10,MARGIN.left,VBW-MARGIN.left-MARGIN.right,legendItemWidth,14))}));var CORR_TO_R_METHOD={pearson:"pearson",spearman:"spearman",kendall:"kendall"};function buildScatterSetTextBlock(row,xLabel,yLabel){let lines=[],res=row.testResult;lines.push("=".repeat(60)),lines.push(`Group: ${row.name||"\u2014"}`),lines.push("=".repeat(60)),lines.push(""),lines.push(`x: ${xLabel||"x"} y: ${yLabel||"y"}`),lines.push(`n (complete pairs): ${row.n}`),lines.push("");let rec=row.rec,recTest=rec?.recommendation?.test,reason=rec?.recommendation?.reason;lines.push(`Test: ${CORR_TEST_LABELS[row.chosenTest]}`),reason&&lines.push(`Reason: ${reason}`),lines.push(`Result: ${formatCorrResultLine(res)}`),recTest&&recTest!==row.chosenTest&&lines.push(` (Toolbox recommended ${CORR_TEST_LABELS[recTest]})`),lines.push("");let normality=rec?.normality??[];if(normality.length>0){let parts=normality.map(r=>{let axisLabel=r.axis==="x"?xLabel||"x":yLabel||"y",verdict=r.normal===!0?"normal":r.normal===!1?"not normal":"\u2014",wpStr=r.W!=null&&r.p!=null?` (W=${r.W.toFixed(3)}, p=${formatP(r.p)})`:"";return`${axisLabel}: ${verdict}${wpStr}`});lines.push(`Shapiro-Wilk: ${parts.join("; ")}`)}return res&&!res.error&&res.kind==="kendall"&&lines.push("(Kendall \u03C4 \u2014 no analytic CI shipped; bootstrap if needed.)"),lines.push(""),lines.join(` +import{makeTicks}from"./chunk-T4V5V5TK.js";import{BaseStyleControls,COLOR_PALETTES,ColorInput,ControlSection,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,NumberInput,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,SliderControl,UploadPanel,applyDiscretePalette,autoDetectSep,computeLegendHeight,downloadCsv,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,formatRVector,interpolateColor,isNumericValue,kendallTau,pStars,parseData,pearsonCorrelation,renderSvgLegend,resolveDiscretePalette,sampleMean,sampleSD,sanitizeRComment,sanitizeRString,scrollDisclosureIntoView,selectCorrelation,spearmanCorrelation,svgSafeId,usePlotToolState}from"./chunk-HFTDMS7M.js";function fmtTick(t){if(t===0)return"0";let abs=Math.abs(t);return abs>=1e4||abs<.01&&abs>0?t.toExponential(1):abs>=100?t.toFixed(0):parseFloat(t.toPrecision(3)).toString()}var SHAPES=["circle","triangle","cross","square"],MARGIN={top:28,right:28,bottom:56,left:70},VBW=800,VBH=500;function computeLinearRegression(rows,xCol,yCol){if(!rows||rows.length<2)return{valid:!1};let n=0,sx=0,sy=0,sxx=0,syy=0,sxy=0;for(let row of rows){let x=row[xCol],y=row[yCol];x==null||y==null||isNaN(x)||isNaN(y)||(n++,sx+=x,sy+=y,sxx+=x*x,syy+=y*y,sxy+=x*y)}if(n<2)return{valid:!1};let denomX=n*sxx-sx*sx;if(denomX===0)return{valid:!1};let slope=(n*sxy-sx*sy)/denomX,intercept=(sy-slope*sx)/n,denomY=n*syy-sy*sy,r2Raw=denomY===0?NaN:Math.pow(n*sxy-sx*sy,2)/(denomX*denomY),r2=Number.isFinite(r2Raw)?Math.min(1,Math.max(0,r2Raw)):r2Raw;return{valid:!0,slope,intercept,r2,n}}var CORR_TEST_LABELS={pearson:"Pearson r",spearman:"Spearman \u03C1",kendall:"Kendall \u03C4"},CORR_TEST_OPTIONS=["pearson","spearman","kendall"];function runCorrelation(test,xs,ys,opts){return test==="spearman"?{kind:"spearman",...spearmanCorrelation(xs,ys,opts)}:test==="kendall"?{kind:"kendall",...kendallTau(xs,ys,opts)}:{kind:"pearson",...pearsonCorrelation(xs,ys,opts)}}function correlationCoef(res){return res.kind==="spearman"?res.rho:res.kind==="kendall"?res.tau:res.r}function formatCorrStatShort(res){return!res||res.error?"\u2014":res.kind==="pearson"?`r = ${res.r.toFixed(3)}, t(${res.df}) = ${res.t.toFixed(3)}`:res.kind==="spearman"?`\u03C1 = ${res.rho.toFixed(3)}, t(${res.df}) = ${res.t.toFixed(3)}`:`\u03C4 = ${res.tau.toFixed(3)}, z = ${res.z.toFixed(3)}`}function formatCorrResultLine(res){if(!res)return"\u2014";if(res.error)return"\u26A0 "+res.error;if(res.kind==="pearson"){let ci=res.ci,ciStr=Number.isFinite(ci.lo)?`, 95% CI [${ci.lo.toFixed(3)}, ${ci.hi.toFixed(3)}]`:"";return`r = ${res.r.toFixed(3)}${ciStr}, t(${res.df}) = ${res.t.toFixed(3)}, p = ${formatP(res.p)}`}if(res.kind==="spearman"){let ci=res.ci,ciStr=Number.isFinite(ci.lo)?`, 95% CI [${ci.lo.toFixed(3)}, ${ci.hi.toFixed(3)}]`:"";return`\u03C1 = ${res.rho.toFixed(3)}${ciStr}, t(${res.df}) = ${res.t.toFixed(3)}, p = ${formatP(res.p)}`}return`\u03C4 = ${res.tau.toFixed(3)}, z = ${res.z.toFixed(3)}, p = ${formatP(res.p)}`}var SCATTER_HOWTO={toolName:"scatter",title:"Scatter Plot \u2014 How to use",subtitle:"XY scatter with optional colour / size / shape mapping",purpose:React.createElement(React.Fragment,null,"Plot one numeric column against another. Map a third (or fourth, or fifth) column to point colour, size, or shape to surface multivariate structure in a single view."),dataLayout:React.createElement(React.Fragment,null,"One row per point. At least two ",React.createElement("strong",null,"numeric")," columns (the X and Y axes). Extra columns \u2014 categorical or numeric \u2014 become aesthetic mappings or filters."),display:React.createElement(React.Fragment,null,"Optional colour-by-column (continuous gradient or discrete swatches), size-by-column, and shape-by-column. Add ",React.createElement("strong",null,"reference lines")," at fixed X / Y values, overlay a"," ",React.createElement("strong",null,"linear regression")," with R\xB2 + p-value. Filter rows by any categorical column.")};var aesTheme={color:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Color"},size:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Size"},shape:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"Shape"}};function AesBox({theme,children}){let t=aesTheme[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function UploadStep({sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Fisher's Iris dataset",subtitle:"150 flowers \xD7 4 measurements \xB7 3 species",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 one column per variable, one row per point \xB7 2 MB max"}),React.createElement(HowTo,{...SCATTER_HOWTO}))}function PaletteStrip({palette,width,height=12}){let stops=COLOR_PALETTES[palette]||COLOR_PALETTES.viridis,n=48;return React.createElement("div",{style:{display:"flex",width:width||"100%",height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border)"}},Array.from({length:n},(_,i)=>React.createElement("div",{key:i,style:{flex:1,background:interpolateColor(stops,i/(n-1))}})))}function renderPoint(shape,cx,cy,r,props){let{fill,fillOpacity,stroke,strokeWidth,key}=props;switch(shape){case"triangle":{let bx=r*.866,by=cy+r*.5;return React.createElement("polygon",{key,points:`${cx},${cy-r} ${cx-bx},${by} ${cx+bx},${by}`,fill,fillOpacity,stroke,strokeWidth})}case"square":{let s=r*1.4;return React.createElement("rect",{key,x:cx-s/2,y:cy-s/2,width:s,height:s,fill,fillOpacity,stroke,strokeWidth})}case"cross":{let t=r*.35;return React.createElement("path",{key,d:`M${cx-r},${cy-t}H${cx-t}V${cy-r}H${cx+t}V${cy-t}H${cx+r}V${cy+t}H${cx+t}V${cy+r}H${cx-t}V${cy+t}H${cx-r}Z`,fill,fillOpacity,stroke,strokeWidth})}default:return React.createElement("circle",{key,cx,cy,r,fill,fillOpacity,stroke,strokeWidth})}}function ShapePreview({shape,size=16,color="#666"}){return React.createElement("svg",{width:size,height:size,viewBox:"0 0 16 16",style:{display:"block",flexShrink:0},"aria-hidden":"true"},renderPoint(shape,8,8,6,{fill:color,fillOpacity:1,stroke:"none",strokeWidth:0}))}var{forwardRef,memo}=React,ScatterChart=memo(forwardRef(function({data,rawData,xCol,yCol,xMin,xMax,yMin,yMax,xLabel,yLabel,title,plotBg,showGrid,gridColor,refLines,regression,regressionStats,pointColor,pointSize,pointOpacity,strokeColor,strokeWidth,colorMapCol,colorMapType,colorMapPalette,colorMapDiscrete,colorMapRange,sizeMapCol,sizeMapType,sizeMapMin,sizeMapMax,sizeMapDiscrete,sizeMapRange,shapeMapCol,shapeMapDiscrete,svgLegend},ref){let w=VBW-MARGIN.left-MARGIN.right,h=VBH-MARGIN.top-MARGIN.bottom,legendItemWidth=block=>{if(!block.items)return 88;let maxLen=block.items.reduce((m,it)=>Math.max(m,(it.label||"").length),0);return Math.max(88,Math.min(260,maxLen*6.2+22))},legendH=computeLegendHeight(svgLegend,VBW-MARGIN.left-MARGIN.right,legendItemWidth),xRange=xMax-xMin||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xMin)/xRange*w,sy=v=>MARGIN.top+(1-(v-yMin)/yRange)*h,xTicks=makeTicks(xMin,xMax,8),yTicks=makeTicks(yMin,yMax,6),getColor=(xVal,yVal,rowIdx)=>{if(colorMapCol!=null&&rawData){let raw=rawData[rowIdx]?rawData[rowIdx][colorMapCol]:null;if(raw!=null&&raw!=="")if(colorMapType==="continuous"){let num=parseFloat(raw.replace(",","."));if(!isNaN(num)){let[cMin,cMax]=colorMapRange,t=Math.max(0,Math.min(1,(num-cMin)/(cMax-cMin||1)));return interpolateColor(COLOR_PALETTES[colorMapPalette]||COLOR_PALETTES.viridis,t)}}else return colorMapDiscrete[raw]||pointColor}return pointColor},getSize=rowIdx=>{if(sizeMapCol!=null&&rawData){let raw=rawData[rowIdx]?rawData[rowIdx][sizeMapCol]:null;if(raw!=null&&raw!=="")if(sizeMapType==="continuous"){let num=parseFloat(raw.replace(",","."));if(!isNaN(num)){let[sMin,sMax]=sizeMapRange,t=Math.max(0,Math.min(1,(num-sMin)/(sMax-sMin||1)));return sizeMapMin+t*(sizeMapMax-sizeMapMin)}}else return sizeMapDiscrete[raw]!==void 0?sizeMapDiscrete[raw]:pointSize}return pointSize},getShape=rowIdx=>{if(shapeMapCol!=null&&rawData){let raw=rawData[rowIdx]?rawData[rowIdx][shapeMapCol]:null;if(raw!=null&&raw!==""&&shapeMapDiscrete[raw]!==void 0)return shapeMapDiscrete[raw]}return"circle"};return React.createElement("svg",{ref,viewBox:`0 0 ${VBW} ${VBH+legendH}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":title||"Scatter plot"},React.createElement("title",null,title||"Scatter plot"),React.createElement("desc",null,`Scatter plot with ${data.length} data point${data.length!==1?"s":""}${xLabel?`, X: ${xLabel}`:""}${yLabel?`, Y: ${yLabel}`:""}`),React.createElement("defs",null,React.createElement("clipPath",{id:"sc-clip"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h}))),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:VBW,height:VBH,fill:plotBg||"#fff"})),React.createElement("g",{id:"plot-area-background"},React.createElement("rect",{x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"})),showGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`gy-${t}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"})),xTicks.map(t=>React.createElement("line",{key:`gx-${t}`,x1:sx(t),x2:sx(t),y1:MARGIN.top,y2:MARGIN.top+h,stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"}))),React.createElement("g",{id:"reference-lines",clipPath:"url(#sc-clip)"},refLines.map(rl=>{let isH=rl.dir==="h",x1=isH?MARGIN.left:sx(rl.value),x2=isH?MARGIN.left+w:sx(rl.value),y1=isH?sy(rl.value):MARGIN.top,y2=isH?sy(rl.value):MARGIN.top+h;return isH&&(rl.valueyMax)||!isH&&(rl.valuexMax)?null:React.createElement("line",{key:rl.id,x1,y1,x2,y2,stroke:rl.color||"#444",strokeWidth:rl.strokeWidth||1.5,strokeDasharray:rl.dashed?rl.dashArray||"7,4":"none"})})),React.createElement("g",{id:"reference-line-labels"},refLines.map(rl=>{if(!rl.label)return null;if(rl.dir==="h"){if(rl.valueyMax)return null;let lx=rl.labelSide==="left"?MARGIN.left+4:MARGIN.left+w-4;return React.createElement("text",{key:`lbl-${rl.id}`,x:lx,y:sy(rl.value)-4,textAnchor:rl.labelSide==="left"?"start":"end",fontSize:"10",fill:rl.color||"#444",fontFamily:"sans-serif",fontStyle:"italic"},rl.label)}else{if(rl.valuexMax)return null;let ly=rl.labelSide==="bottom"?MARGIN.top+h-4:MARGIN.top+12;return React.createElement("text",{key:`lbl-${rl.id}`,x:sx(rl.value)+4,y:ly,textAnchor:"start",fontSize:"10",fill:rl.color||"#444",fontFamily:"sans-serif",fontStyle:"italic"},rl.label)}})),React.createElement("g",{id:"data-points",clipPath:"url(#sc-clip)",role:"group","aria-label":`${data.length} data points`},data.map((row,ri)=>{let xVal=row[xCol],yVal=row[yCol];return xVal==null||yVal==null?null:renderPoint(getShape(ri),sx(xVal),sy(yVal),getSize(ri),{key:ri,fill:getColor(xVal,yVal,ri),fillOpacity:pointOpacity,stroke:strokeColor||"none",strokeWidth:strokeWidth||0})})),regression&®ression.on&®ressionStats&®ressionStats.valid&&React.createElement("g",{id:"regression-line",clipPath:"url(#sc-clip)",role:"img","aria-label":`Linear regression: slope ${regressionStats.slope.toFixed(3)}, intercept ${regressionStats.intercept.toFixed(3)}, R\xB2 ${regressionStats.r2.toFixed(3)}, n=${regressionStats.n}`},React.createElement("line",{x1:sx(xMin),y1:sy(regressionStats.slope*xMin+regressionStats.intercept),x2:sx(xMax),y2:sy(regressionStats.slope*xMax+regressionStats.intercept),stroke:regression.color||"#dc2626",strokeWidth:regression.strokeWidth||1.5,strokeDasharray:regression.dashed?"7,4":"none"})),regression&®ression.on&®ression.showStats&®ressionStats&®ressionStats.valid&&(()=>{let pos=regression.position||"tl",tx=pos.endsWith("r")?MARGIN.left+w-8:MARGIN.left+8,ty=pos.startsWith("b")?MARGIN.top+h-8-38:MARGIN.top+8,anchor=pos.endsWith("r")?"end":"start",s=regressionStats.slope,b=regressionStats.intercept,eq=`y = ${fmtTick(s)}\xB7x ${b>=0?"+":"\u2212"} ${fmtTick(Math.abs(b))}`,r2=`R\xB2 = ${Number.isFinite(regressionStats.r2)?regressionStats.r2.toFixed(4):"undefined"}`,nTxt=`n = ${regressionStats.n}`;return React.createElement("g",{id:"regression-stats",fontFamily:"sans-serif",fontSize:"11",fill:regression.color||"#dc2626"},React.createElement("text",{x:tx,y:ty+10,textAnchor:anchor},eq),React.createElement("text",{x:tx,y:ty+24,textAnchor:anchor},r2),React.createElement("text",{x:tx,y:ty+38,textAnchor:anchor,fill:"#888"},nTxt))})(),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{id:"plot-frame-top",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top}),React.createElement("line",{id:"plot-frame-right",x1:MARGIN.left+w,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:MARGIN.left,y1:MARGIN.top+h,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-left",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left,y2:MARGIN.top+h})),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:sx(t),x2:sx(t),y1:MARGIN.top+h,y2:MARGIN.top+h+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:MARGIN.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},fmtTick(t))))),xLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:VBH-6,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},xLabel)),yLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${MARGIN.top+h/2}) rotate(-90)`,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},yLabel)),title&&React.createElement("g",{id:"title"},React.createElement("text",{x:VBW/2,y:16,textAnchor:"middle",fontSize:"15",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},title)),renderSvgLegend(svgLegend,VBH+10,MARGIN.left,VBW-MARGIN.left-MARGIN.right,legendItemWidth,14))}));var CORR_TO_R_METHOD={pearson:"pearson",spearman:"spearman",kendall:"kendall"};function buildScatterSetTextBlock(row,xLabel,yLabel){let lines=[],res=row.testResult;lines.push("=".repeat(60)),lines.push(`Group: ${row.name||"\u2014"}`),lines.push("=".repeat(60)),lines.push(""),lines.push(`x: ${xLabel||"x"} y: ${yLabel||"y"}`),lines.push(`n (complete pairs): ${row.n}`),lines.push("");let rec=row.rec,recTest=rec?.recommendation?.test,reason=rec?.recommendation?.reason;lines.push(`Test: ${CORR_TEST_LABELS[row.chosenTest]}`),reason&&lines.push(`Reason: ${reason}`),lines.push(`Result: ${formatCorrResultLine(res)}`),recTest&&recTest!==row.chosenTest&&lines.push(` (Toolbox recommended ${CORR_TEST_LABELS[recTest]})`),lines.push("");let normality=rec?.normality??[];if(normality.length>0){let parts=normality.map(r=>{let axisLabel=r.axis==="x"?xLabel||"x":yLabel||"y",verdict=r.normal===!0?"normal":r.normal===!1?"not normal":"\u2014",wpStr=r.W!=null&&r.p!=null?` (W=${r.W.toFixed(3)}, p=${formatP(r.p)})`:"";return`${axisLabel}: ${verdict}${wpStr}`});lines.push(`Shapiro-Wilk: ${parts.join("; ")}`)}return res&&!res.error&&res.kind==="kendall"&&lines.push("(Kendall \u03C4 \u2014 no analytic CI shipped; bootstrap if needed.)"),lines.push(""),lines.join(` `)}function buildScatterAggregateReport(rows,xLabel,yLabel){return["Scatter \u2014 correlation analysis","Generated: "+new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),`Groups: ${rows.length}`,""].join(` `)+rows.map(r=>buildScatterSetTextBlock(r,xLabel,yLabel)).join("")}function buildScatterAggregateRScript(rows,xLabel,yLabel){if(!rows.length)return"";let now=new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),xName=sanitizeRComment(xLabel||"x"),yName=sanitizeRComment(yLabel||"y"),parts=[["# -----------------------------------------------------------------------------","# Pl\xF6ttr \u2014 Scatter correlation R script export","# Generated: "+now,`# x = ${xName}, y = ${yName}`,`# Groups: ${rows.length}. Each section redefines x / y and runs cor.test().`,"# -----------------------------------------------------------------------------",""].join(` `)];for(let i=0;iupdVis({pointColor:v}),[updVis]),pointSize=vis.pointSize??5,setPointSize=useCallback(v=>updVis({pointSize:v}),[updVis]),pointOpacity=vis.pointOpacity??.8,setPointOpacity=useCallback(v=>updVis({pointOpacity:v}),[updVis]),strokeColor=vis.strokeColor??"#000000",setStrokeColor=useCallback(v=>updVis({strokeColor:v}),[updVis]),strokeWidth=vis.strokeWidth??1,setStrokeWidth=useCallback(v=>updVis({strokeWidth:v}),[updVis]),[colorMapCol,setColorMapCol]=useState3(null),colorMapPalette=vis.colorMapPalette??"viridis",setColorMapPalette=useCallback(v=>updVis({colorMapPalette:v}),[updVis]),colorMapDiscrete=useMemo2(()=>vis.colorMapDiscrete||{},[vis.colorMapDiscrete]),setColorMapDiscrete=useCallback(updater=>updVis({colorMapDiscrete:typeof updater=="function"?updater(vis.colorMapDiscrete||{}):updater||{}}),[updVis,vis.colorMapDiscrete]),[sizeMapCol,setSizeMapCol]=useState3(null),sizeMapMin=vis.sizeMapMin??3,setSizeMapMin=useCallback(v=>updVis({sizeMapMin:v}),[updVis]),sizeMapMax=vis.sizeMapMax??15,setSizeMapMax=useCallback(v=>updVis({sizeMapMax:v}),[updVis]),sizeMapDiscrete=useMemo2(()=>vis.sizeMapDiscrete||{},[vis.sizeMapDiscrete]),setSizeMapDiscrete=useCallback(updater=>updVis({sizeMapDiscrete:typeof updater=="function"?updater(vis.sizeMapDiscrete||{}):updater||{}}),[updVis,vis.sizeMapDiscrete]),[shapeMapCol,setShapeMapCol]=useState3(null),shapeMapDiscrete=useMemo2(()=>vis.shapeMapDiscrete||{},[vis.shapeMapDiscrete]),setShapeMapDiscrete=useCallback(updater=>updVis({shapeMapDiscrete:typeof updater=="function"?updater(vis.shapeMapDiscrete||{}):updater||{}}),[updVis,vis.shapeMapDiscrete]),[filterState,setFilterState]=useState3({}),refLines=vis.refLines||[],setRefLines=useCallback(updater=>updVis({refLines:typeof updater=="function"?updater(vis.refLines||[]):updater||[]}),[updVis,vis.refLines]),regression=vis.regression||{on:!1,color:"#dc2626",strokeWidth:1.5,dashed:!1,showStats:!0,position:"tl"},updRegression=patch=>updVis({regression:{...regression,...patch}}),svgRef=useRef2(null),sepRef=useRef2(""),[detectedSep,setDetectedSep]=useState3(""),parsed=useMemo2(()=>rawText?parseData(rawText,sepRef.current):null,[rawText]),colIsNumeric=useMemo2(()=>parsed?parsed.headers.reduce((acc,_,i)=>{let vals=parsed.rawData.map(r=>r[i]).filter(v=>v!==""&&v!=null);return acc[i]=vals.length>0&&vals.filter(v=>isNumericValue(v)).length/vals.length>.5,acc},{}):{},[parsed]),numericCols=useMemo2(()=>parsed?parsed.headers.reduce((acc,_,i)=>colIsNumeric[i]?[...acc,i]:acc,[]):[],[parsed,colIsNumeric]),activeColIdxs=useMemo2(()=>parsed?parsed.headers.map((_,i)=>i):[],[parsed]),mappableCols=useMemo2(()=>parsed?parsed.headers.reduce((acc,_,i)=>i!==xCol&&i!==yCol?[...acc,i]:acc,[]):[],[parsed,xCol,yCol]),filterableCols=useMemo2(()=>parsed?mappableCols.filter(i=>{let vals=[...new Set(parsed.rawData.map(r=>r[i]).filter(v=>v!=null&&v!==""))];return vals.length>0&&vals.length<=30}):[],[parsed,mappableCols]),filteredIndices=useMemo2(()=>parsed?parsed.rawData.reduce((acc,row,ri)=>{for(let[ci,allowed]of Object.entries(filterState))if(allowed.length>0&&!allowed.includes(row[parseInt(ci)]))return acc;return acc.push(ri),acc},[]):[],[parsed,filterState]),filteredData=useMemo2(()=>parsed?filteredIndices.map(i=>parsed.data[i]):[],[parsed,filteredIndices]),filteredRawRows=useMemo2(()=>parsed?filteredIndices.map(i=>parsed.rawData[i]):[],[parsed,filteredIndices]),detectColType=useCallback(colIdx=>colIdx==null||!parsed?null:parsed.rawData.map(r=>r[colIdx]).filter(v=>v!=null&&v!=="").every(v=>isNumericValue(v))?"continuous":"discrete",[parsed]),colorMapType=useMemo2(()=>detectColType(colorMapCol),[colorMapCol,detectColType]),sizeMapType=useMemo2(()=>detectColType(sizeMapCol),[sizeMapCol,detectColType]),uniqueVals=useCallback(colIdx=>{if(colIdx==null||!parsed)return[];let vals=[...new Set(parsed.rawData.map(r=>r[colIdx]).filter(v=>v!=null&&v!==""))];if(!vals.every(v=>isNumericValue(v)))return vals.sort();let decorated=vals.map(v=>[v,parseFloat(v.replace(",","."))]);return decorated.sort((a,b)=>a[1]-b[1]),decorated.map(d=>d[0])},[parsed]),colorMapCategories=useMemo2(()=>colorMapType==="discrete"?uniqueVals(colorMapCol):[],[colorMapCol,colorMapType,uniqueVals]),sizeMapCategories=useMemo2(()=>sizeMapType==="discrete"?uniqueVals(sizeMapCol):[],[sizeMapCol,sizeMapType,uniqueVals]),shapeMapCategories=useMemo2(()=>shapeMapCol!=null?uniqueVals(shapeMapCol):[],[shapeMapCol,uniqueVals]),shapeWarning=useMemo2(()=>shapeMapCategories.length>4?`This column has ${shapeMapCategories.length} unique values \u2014 only 4 shapes are available. Categories beyond the 4th will cycle through the same shapes.`:null,[shapeMapCategories]),numericRange=useCallback(colIdx=>{if(colIdx==null||!parsed)return[0,1];let vals=parsed.rawData.map(r=>parseFloat((r[colIdx]||"").replace(",","."))).filter(v=>!isNaN(v));if(!vals.length)return[0,1];let mn=1/0,mx=-1/0;for(let v of vals)vmx&&(mx=v);return[mn,mx]},[parsed]),colorMapRange=useMemo2(()=>numericRange(colorMapCol),[colorMapCol,numericRange]),sizeMapRange=useMemo2(()=>numericRange(sizeMapCol),[sizeMapCol,numericRange]);useEffect2(()=>{if(colorMapCategories.length===0){setColorMapDiscrete({});return}setColorMapDiscrete(prev=>{let seed=resolveDiscretePalette(vis.discretePalette||"okabe-ito",colorMapCategories.length),next={};return colorMapCategories.forEach((cat,i)=>{next[cat]=prev[cat]||seed[i%Math.max(1,seed.length)]||PALETTE[i%PALETTE.length]}),next})},[colorMapCategories,setColorMapDiscrete,vis.discretePalette]),useEffect2(()=>{if(sizeMapCategories.length===0){setSizeMapDiscrete({});return}setSizeMapDiscrete(prev=>{let next={};return sizeMapCategories.forEach((cat,i)=>{next[cat]=prev[cat]!==void 0?prev[cat]:3+i*3}),next})},[sizeMapCategories,setSizeMapDiscrete]),useEffect2(()=>{if(shapeMapCategories.length===0){setShapeMapDiscrete({});return}setShapeMapDiscrete(prev=>{let next={};return shapeMapCategories.forEach((cat,i)=>{next[cat]=prev[cat]||SHAPES[i%SHAPES.length]}),next})},[shapeMapCategories,setShapeMapDiscrete]),useEffect2(()=>{!parsed||xCol==null||yCol==null||updVis({xMin:null,xMax:null,yMin:null,yMax:null,xLabel:parsed.headers[xCol],yLabel:parsed.headers[yCol]})},[xCol,yCol,parsed,updVis]);let autoAxis=useMemo2(()=>{if(!parsed||xCol==null||yCol==null)return{xMin:0,xMax:1,yMin:0,yMax:1};let data=parsed.data,xVals=data.map(r=>r[xCol]).filter(v=>v!=null),yVals=data.map(r=>r[yCol]).filter(v=>v!=null),xMn=1/0,xMx=-1/0,yMn=1/0,yMx=-1/0;for(let v of xVals)vxMx&&(xMx=v);for(let v of yVals)vyMx&&(yMx=v);let xPad=xVals.length>1?(xMx-xMn)*.05:.5,yPad=yVals.length>1?(yMx-yMn)*.05:.5;return{xMin:xVals.length?xMn-xPad:0,xMax:xVals.length?xMx+xPad:1,yMin:yVals.length?yMn-yPad:0,yMax:yVals.length?yMx+yPad:1}},[parsed,xCol,yCol]),regressionStats=useMemo2(()=>computeLinearRegression(filteredData,xCol,yCol),[filteredData,xCol,yCol]),statsSets=useMemo2(()=>{if(!parsed||xCol==null||yCol==null)return[];let xs=[],ys=[],rowCats=[],discreteColorMap=colorMapCol!=null&&colorMapType==="discrete"&&parsed.rawData?colorMapCol:null;for(let i=0;i0)for(let cat of colorMapCategories){let sx=[],sy=[];for(let i=0;ifileBaseName(fileName,"scatter")+"_scatter",[fileName]),effAxis={xMin:vis.xMin!=null?vis.xMin:autoAxis.xMin,xMax:vis.xMax!=null?vis.xMax:autoAxis.xMax,yMin:vis.yMin!=null?vis.yMin:autoAxis.yMin,yMax:vis.yMax!=null?vis.yMax:autoAxis.yMax};useEffect2(()=>{(colorMapCol===xCol||colorMapCol===yCol)&&setColorMapCol(null),(sizeMapCol===xCol||sizeMapCol===yCol)&&setSizeMapCol(null),(shapeMapCol===xCol||shapeMapCol===yCol)&&setShapeMapCol(null)},[xCol,yCol]);let svgLegend=useMemo2(()=>{let items=[];if(!parsed)return null;let hasColorMap=colorMapCol!=null,hasSizeMap=sizeMapCol!=null,hasShapeMap=shapeMapCol!=null;if(hasColorMap&&colorMapType==="continuous"){let stops=COLOR_PALETTES[colorMapPalette]||COLOR_PALETTES.viridis;items.push({id:"legend-color",title:parsed.headers[colorMapCol],gradient:{stops,min:colorMapRange[0].toFixed(2),max:colorMapRange[1].toFixed(2)}})}else hasColorMap&&colorMapType==="discrete"&&items.push({id:"legend-color",title:parsed.headers[colorMapCol],items:colorMapCategories.map(c=>({label:c,color:colorMapDiscrete[c]||"#999",shape:"dot"}))});if(hasSizeMap&&sizeMapType==="discrete")items.push({id:"legend-size",title:parsed.headers[sizeMapCol],sizeItems:sizeMapCategories.map(c=>({label:c,r:sizeMapDiscrete[c]||sizeMapMin}))});else if(hasSizeMap&&sizeMapType==="continuous"){let sizeItems=Array.from({length:4},(_,i)=>{let t=i/3;return{label:(sizeMapRange[0]+t*(sizeMapRange[1]-sizeMapRange[0])).toFixed(1),r:sizeMapMin+t*(sizeMapMax-sizeMapMin)}});items.push({id:"legend-size",title:parsed.headers[sizeMapCol],sizeItems})}return hasShapeMap&&items.push({id:"legend-shape",title:parsed.headers[shapeMapCol],items:shapeMapCategories.map(c=>({label:c,color:"var(--text-muted)",shape:shapeMapDiscrete[c]||"circle"}))}),items.length>0?items:null},[parsed,colorMapCol,colorMapType,colorMapPalette,colorMapDiscrete,colorMapCategories,colorMapRange,sizeMapCol,sizeMapType,sizeMapMin,sizeMapMax,sizeMapDiscrete,sizeMapCategories,sizeMapRange,shapeMapCol,shapeMapCategories,shapeMapDiscrete]),doParse=useCallback((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";sepRef.current=effectiveSep,setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count);let fixedText=dc.text,{headers,data,rawData,injectionWarnings}=parseData(fixedText,effectiveSep);if(setInjectionWarning(injectionWarnings),headers.length<2||data.length===0){setParseError("The file appears to be empty or has no data rows. Please check your file and try again.");return}setParseError(null),setRawText(fixedText);let isNum=idx=>{let vals=rawData.map(r=>r[idx]).filter(v=>v!==""&&v!=null);return vals.length>0&&vals.filter(v=>isNumericValue(v)).length/vals.length>.5},nums=headers.reduce((acc,_,i)=>isNum(i)?[...acc,i]:acc,[]);setXCol(nums[0]!==void 0?nums[0]:0),setYCol(nums[1]!==void 0?nums[1]:nums[0]!==void 0?nums[0]:1),setColorMapCol(null),setSizeMapCol(null),setShapeMapCol(null),setFilterState({}),setStep("plot")},[setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep]),handleFileLoad=useCallback((text,name)=>{setFileName(name),doParse(text,sepOverride)},[sepOverride,doParse,setFileName]),handleTextPaste=useCallback((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]),loadExample=useCallback(()=>{let text=EXAMPLE_CSV;text&&(setSepOverride(""),setFileName("iris.csv"),doParse(text,""))},[doParse,setFileName,setSepOverride]);return React.createElement(PlotToolShell,{state:shell,toolName:"scatter",title:"Scatter Plot",visInit:VIS_INIT_SCATTER,steps:["upload","plot"],canNavigate:s=>s==="upload"?!0:s==="plot"?!!parsed:!1},step==="upload"&&React.createElement(UploadStep,{sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample:loadExample}),step==="plot"&&parsed&&React.createElement(PlotStep,{parsed,fileName,detectedSep,filteredData,filteredRawRows,activeColIdxs,xCol,setXCol,yCol,setYCol,numericCols,pointColor,setPointColor,pointSize,setPointSize,pointOpacity,setPointOpacity,strokeColor,setStrokeColor,strokeWidth,setStrokeWidth,colorMapCol,setColorMapCol,colorMapType,colorMapPalette,setColorMapPalette,colorMapDiscrete,setColorMapDiscrete,colorMapCategories,colorMapRange,sizeMapCol,setSizeMapCol,sizeMapType,sizeMapMin,setSizeMapMin,sizeMapMax,setSizeMapMax,sizeMapDiscrete,setSizeMapDiscrete,sizeMapCategories,sizeMapRange,shapeMapCol,setShapeMapCol,shapeMapCategories,shapeMapDiscrete,setShapeMapDiscrete,shapeWarning,vis,updVis,autoAxis,effAxis,refLines,addRefLine:dir=>setRefLines(prev=>[...prev,{id:++refLineCounter,dir,value:0,color:"#dc2626",strokeWidth:1.5,dashed:!0,dashArray:"7,4",label:"",labelSide:dir==="h"?"right":"top"}]),updateRefLine:(id,key,val)=>setRefLines(prev=>prev.map(rl=>rl.id===id?{...rl,[key]:val}:rl)),removeRefLine:id=>setRefLines(prev=>prev.filter(rl=>rl.id!==id)),regression,updRegression,regressionStats,filterState,setFilterState,filterableCols,uniqueVals,mappableCols,resetAll:()=>{setRawText(null),setFileName(""),setInjectionWarning(null),setStep("upload")},svgRef,svgLegend,statsSets,fileStem}))}export{App}; -//# sourceMappingURL=app-NW63HSPZ.js.map +//# sourceMappingURL=app-RNTWQ7N2.js.map diff --git a/tools/_app/chunks/app-NW63HSPZ.js.map b/tools/_app/chunks/app-RNTWQ7N2.js.map similarity index 100% rename from tools/_app/chunks/app-NW63HSPZ.js.map rename to tools/_app/chunks/app-RNTWQ7N2.js.map diff --git a/tools/_app/chunks/app-BDRRNBIR.js b/tools/_app/chunks/app-TQWDLQEC.js similarity index 99% rename from tools/_app/chunks/app-BDRRNBIR.js rename to tools/_app/chunks/app-TQWDLQEC.js index 72526c6f..b4b3bc3a 100644 --- a/tools/_app/chunks/app-BDRRNBIR.js +++ b/tools/_app/chunks/app-TQWDLQEC.js @@ -1,4 +1,4 @@ -import{makeTicks}from"./chunk-T4V5V5TK.js";import{BaseStyleControls,CHART_MARGIN,ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,NumberInput,PlotSidebar,PlotToolShell,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K,STATS_TESTS_FOR_K2,STATS_TEST_REGISTRY,SliderControl,UploadPanel,applyDiscretePalette,autoDetectSep,bhAdjust,buildLineD,buildRScript,computeLegendHeight,computePowerFromData,downloadCsv,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,isNumericValue,pStars,parseData,postHocForTest,renderSvgLegend,resolveDiscretePalette,round2,round4,runPostHoc,runTest,sampleMean,sampleSD,sanitizeRComment,selectTest,svgSafeId,testStatistic,tinv,usePlotToolState}from"./chunk-5F6S3FYW.js";var MARGIN=CHART_MARGIN;var STAR_ROW_H=18,ERROR_KINDS=[{value:"none",label:"None"},{value:"sem",label:"SEM"},{value:"sd",label:"SD"},{value:"ci95",label:"95% CI"}];function formatX(x){return x==null||!Number.isFinite(x)||Number.isInteger(x)?String(x):String(round4(x))}function computeSeries(data,rawData,xCol,yCol,groupCol,groupColors,palette){let groupOrder=[],perGroup=new Map;for(let ri=0;ri{let xMap=perGroup.get(name),points=[...xMap.keys()].sort((a,b)=>a-b).map(x=>{let values=xMap.get(x),n=values.length,mean=sampleMean(values),sd=n>1?sampleSD(values):0,sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0;return{x,values,n,mean,sd,sem,ci95}});return{name,color:groupColors[name]||palette[idx%palette.length],points}})}function computePerXStats(series){let xSet=new Set;for(let s of series)for(let p of s.points)xSet.add(p.x);let xs=[...xSet].sort((a,b)=>a-b),rows=[];for(let x of xs){let groups=[];for(let s of series){let p=s.points.find(q=>q.x===x);p&&p.n>=2&&groups.push({name:s.name,values:p.values})}if(groups.length<2)continue;let values=groups.map(g=>g.values),names=groups.map(g=>g.name),rec=selectTest(values),chosenTest=rec&&rec.recommendation&&rec.recommendation.test?rec.recommendation.test:null,result=chosenTest?runTest(chosenTest,values):null;rows.push({x,names,values,chosenTest,result})}let validIdx=[],validPs=[];rows.forEach((r,i)=>{r.result&&!r.result.error&&r.result.p!=null&&Number.isFinite(r.result.p)&&(validIdx.push(i),validPs.push(r.result.p))});let adjPs=validPs.length>0?bhAdjust(validPs):[];return rows.forEach(r=>r.pAdj=null),validIdx.forEach((origIdx,j)=>rows[origIdx].pAdj=adjPs[j]),rows}var LINEPLOT_HOWTO={toolName:"lineplot",title:"Line Plot \u2014 How to use",subtitle:"Mean \xB1 error per group across an x-axis, with per-x significance",purpose:React.createElement(React.Fragment,null,"Plot how a measurement evolves across an x-axis variable (time, dose, concentration), one line per group. Replicates at the same X are averaged and their spread becomes the error bar."),dataLayout:React.createElement(React.Fragment,null,"Long format \u2014 one row per observation, with a numeric ",React.createElement("strong",null,"X"),", a numeric"," ",React.createElement("strong",null,"Y"),", and a categorical ",React.createElement("strong",null,"group")," column. Replicates share the same (X, group) pair. Error bars only render when a group has \u2265 2 replicates at that X."),display:React.createElement(React.Fragment,null,"Pick ",React.createElement("strong",null,"SEM")," (default), ",React.createElement("strong",null,"SD"),", or ",React.createElement("strong",null,"95% CI")," for error ribbons. At every X shared by \u2265 2 groups the right test is auto-routed (t / Welch / Mann-Whitney; ANOVA / Welch-ANOVA / Kruskal-Wallis); p-values are ",React.createElement("strong",null,"BH-adjusted")," ","across the X-axis and significance stars overlay the chart.")};function UploadStep({sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Bacterial growth curves",subtitle:"3 strains \xD7 5 timepoints \xD7 3 replicates",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 one row per observation, columns for X, Y, and grouping \xB7 2 MB max"}),React.createElement(HowTo,{...LINEPLOT_HOWTO}))}var LP_AES_THEMES={x:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"X axis"},y:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Y axis"},group:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"Group by"}};function LpAesBox({theme,children}){let t=LP_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function ConfigureStep({parsed,fileName,detectedSep,xCol,setXCol,yCol,setYCol,groupCol,setGroupCol,numericCols,categoricalCols}){let canPlot=xCol!=null&&yCol!=null&&numericCols.length>=2;return React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:16}},React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(220px, 1fr))",gap:12}},React.createElement(LpAesBox,{theme:"x"},React.createElement("select",{value:xCol??"",onChange:e=>setXCol(parseInt(e.target.value)),className:"dv-select",style:{width:"100%"}},numericCols.map(i=>React.createElement("option",{key:i,value:i},parsed.headers[i]))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column plotted along the X axis.")),React.createElement(LpAesBox,{theme:"y"},React.createElement("select",{value:yCol??"",onChange:e=>setYCol(parseInt(e.target.value)),className:"dv-select",style:{width:"100%"}},numericCols.map(i=>React.createElement("option",{key:i,value:i},parsed.headers[i]))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column plotted along the Y axis.")),React.createElement(LpAesBox,{theme:"group"},React.createElement("select",{value:groupCol??"",onChange:e=>setGroupCol(e.target.value===""?null:parseInt(e.target.value)),className:"dv-select",style:{width:"100%"}},React.createElement("option",{value:""},"\u2014 None (single line) \u2014"),categoricalCols.map(i=>React.createElement("option",{key:i,value:i},parsed.headers[i]))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Categorical column used to split the data into coloured lines."))),!canPlot&&React.createElement("p",{style:{margin:"4px 0 0",fontSize:11,color:"var(--warning-text)"}},"Need at least two numeric columns to plot."),React.createElement("div",{className:"dv-panel",style:{marginBottom:0}},React.createElement("p",{style:{margin:"0 0 8px",fontSize:13,color:"var(--text-muted)"}},"Loaded ",React.createElement("strong",{style:{color:"var(--text)"}},fileName||"pasted data")," \u2014"," ",parsed.rawData.length," rows \xD7 ",parsed.headers.length," columns",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement(DataPreview,{headers:parsed.headers,rows:parsed.rawData,maxRows:10})))}var{forwardRef,memo}=React,Chart=memo(forwardRef(function({series,perXStats,xMin,xMax,yMin,yMax,vbW,vbH,xLabel,yLabel,plotTitle,plotSubtitle,plotBg,showGrid,gridColor,lineWidth,pointRadius,errorStrokeWidth,errorCapWidth,errorType,svgLegend,showStars},ref){let itemW=b=>{let maxLen=Math.max(0,...(b.items||[]).map(i=>(i.label||"").length));return Math.max(110,maxLen*6+28)},legendH=computeLegendHeight(svgLegend,vbW-MARGIN.left-MARGIN.right,itemW),topPad=(plotTitle?20:0)+(plotSubtitle?16:0),starRowH=showStars&&perXStats.some(r=>r.pAdj!=null)?STAR_ROW_H:0,w=vbW-MARGIN.left-MARGIN.right,h=vbH-MARGIN.top-MARGIN.bottom,innerTop=MARGIN.top+starRowH,innerH=h-starRowH,xRange=xMax-xMin||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xMin)/xRange*w,sy=v=>innerTop+(1-(v-yMin)/yRange)*innerH,clampY=v=>Math.max(yMin,Math.min(yMax,v)),xTicks=makeTicks(xMin,xMax,8),yTicks=makeTicks(yMin,yMax,6),errOf=p=>errorType==="none"?null:errorType==="sd"?p.sd:errorType==="ci95"?p.ci95:p.sem;return React.createElement("svg",{ref,viewBox:`0 0 ${vbW} ${vbH+legendH+topPad}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"Line chart"},React.createElement("title",null,plotTitle||"Line chart"),React.createElement("desc",null,`Line chart with ${series.length} group${series.length===1?"":"s"}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:vbW,height:vbH+legendH+topPad,fill:plotBg||"#fff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:vbW/2,y:17,textAnchor:"middle",fontSize:"15",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:vbW/2,y:plotTitle?34:17,textAnchor:"middle",fontSize:"12",fill:"#888",fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"chart",transform:`translate(0, ${topPad})`},React.createElement("rect",{id:"plot-area-background",x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"}),showGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`gy-${t}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"})),xTicks.map(t=>React.createElement("line",{key:`gx-${t}`,x1:sx(t),x2:sx(t),y1:innerTop,y2:innerTop+innerH,stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"}))),React.createElement("g",{id:"traces"},series.map(s=>{let linePts=s.points.map(p=>({x:sx(p.x),y:p.mean!=null?sy(p.mean):null})),d=buildLineD(linePts);return d?React.createElement("path",{key:`line-${s.name}`,id:`trace-${svgSafeId(s.name)}`,d,fill:"none",stroke:s.color,strokeWidth:lineWidth,role:"img","aria-label":`${s.name}: ${s.points.length} x-point${s.points.length!==1?"s":""}`}):null})),React.createElement("g",{id:"error-bars"},series.map(s=>React.createElement("g",{key:`errs-${s.name}`,id:`errbars-${svgSafeId(s.name)}`},s.points.map((p,pi)=>{if(p.n<2||p.mean==null)return null;let e=errOf(p);if(!e||!Number.isFinite(e))return null;let cx=sx(p.x),yHi=sy(clampY(p.mean+e)),yLo=sy(clampY(p.mean-e)),cap=errorCapWidth/2;return React.createElement("g",{key:`err-${pi}`},React.createElement("line",{x1:cx,x2:cx,y1:yHi,y2:yLo,stroke:s.color,strokeWidth:errorStrokeWidth}),React.createElement("line",{x1:cx-cap,x2:cx+cap,y1:yHi,y2:yHi,stroke:s.color,strokeWidth:errorStrokeWidth}),React.createElement("line",{x1:cx-cap,x2:cx+cap,y1:yLo,y2:yLo,stroke:s.color,strokeWidth:errorStrokeWidth}))})))),React.createElement("g",{id:"data-points"},series.map(s=>React.createElement("g",{key:`pts-${s.name}`,id:`points-${svgSafeId(s.name)}`},s.points.map((p,pi)=>p.mean==null?null:React.createElement("circle",{key:`pt-${pi}`,cx:sx(p.x),cy:sy(p.mean),r:pointRadius,fill:s.color,stroke:"#fff",strokeWidth:"0.5"}))))),showStars&&starRowH>0&&React.createElement("g",{id:"significance-stars"},perXStats.map((r,i)=>{if(r.pAdj==null)return null;let s=pStars(r.pAdj);return!s||s==="ns"?null:React.createElement("text",{key:`star-${i}`,x:sx(r.x),y:MARGIN.top+14,textAnchor:"middle",fontSize:"13",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},s)})),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{x1:MARGIN.left,y1:innerTop,x2:MARGIN.left+w,y2:innerTop}),React.createElement("line",{x1:MARGIN.left+w,y1:innerTop,x2:MARGIN.left+w,y2:innerTop+innerH}),React.createElement("line",{x1:MARGIN.left,y1:innerTop+innerH,x2:MARGIN.left+w,y2:innerTop+innerH}),React.createElement("line",{x1:MARGIN.left,y1:innerTop,x2:MARGIN.left,y2:innerTop+innerH})),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:sx(t),x2:sx(t),y1:innerTop+innerH,y2:innerTop+innerH+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:innerTop+innerH+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t)))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t%1===0?t:t.toFixed(1))))),xLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:vbH-4,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},xLabel)),yLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${innerTop+innerH/2}) rotate(-90)`,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},yLabel)),renderSvgLegend(svgLegend,vbH+10,MARGIN.left,vbW-MARGIN.left-MARGIN.right,itemW,14)))}));var TEST_LABELS_LP=Object.fromEntries(Object.entries(STATS_TEST_REGISTRY).map(([id,entry])=>[id,entry.shortLabel||entry.label])),POSTHOC_LABELS_LP=Object.fromEntries(Object.entries(STATS_POSTHOC_REGISTRY).map(([id,entry])=>[id,entry.label]));function formatStat(testName,res){if(!testName||!res||res.error)return"\u2014";let num=v=>v;if(testName==="studentT"||testName==="welchT")return`t(${num(res.df).toFixed(2)}) = ${num(res.t).toFixed(3)}`;if(testName==="mannWhitney")return`U = ${num(res.U).toFixed(1)}`;if(testName==="oneWayANOVA"||testName==="welchANOVA"){let df2=typeof res.df2=="number"?res.df2.toFixed(2):res.df2;return`F(${res.df1}, ${df2}) = ${num(res.F).toFixed(3)}`}return testName==="kruskalWallis"?`H(${res.df}) = ${num(res.H).toFixed(3)}`:"\u2014"}function buildPerXTextBlock(row,xLabel){let lines=[],names=row.names,values=row.values,res=row.result??{};lines.push("=".repeat(60)),lines.push(`${xLabel||"x"} = ${formatX(row.x)}`),lines.push("=".repeat(60)),lines.push(""),lines.push("Groups:");for(let i=0;i1?sampleSD(vs):0,sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0,semStr=n>1?sem.toFixed(3):"\u2014",ciStr=n>1?`\xB1${ci95.toFixed(3)}`:"\u2014";lines.push(` ${names[i]}: n=${n}, mean=${mean.toFixed(3)}, SD=${sd.toFixed(3)}, SEM=${semStr}, 95% CI=${ciStr}`)}lines.push("");let rec=row.rec??{},recTest=rec.recommendation?.test??null,reason=rec.recommendation?.reason;lines.push(`Test: ${row.chosenTest?TEST_LABELS_LP[row.chosenTest]||row.chosenTest:"\u2014"}`),reason&&lines.push(`Reason: ${reason}`),res.error?lines.push(`Result: \u26A0 ${res.error}`):(lines.push(`Result: ${formatStat(row.chosenTest,res)}, p = ${formatP(res.p)}`),row.pAdj!=null&&lines.push(`BH-adjusted p (across x-axis): ${formatP(row.pAdj)}`)),recTest&&recTest!==row.chosenTest&&lines.push(` (Toolbox recommended ${TEST_LABELS_LP[recTest]||recTest})`),lines.push("");let norm=rec.normality??[];if(norm.length>0){let parts=norm.map(r=>{let label=names[r.group]||`g${r.group}`,verdict=r.normal===!0?"normal":r.normal===!1?"not normal":"\u2014";return`${label}: ${verdict}`});lines.push(`Shapiro-Wilk: ${parts.join("; ")}`)}let lev=rec.levene??{};lev.F!=null&&lines.push(`Levene: F(${lev.df1},${lev.df2}) = ${lev.F.toFixed(3)}, p = ${formatP(lev.p)} \u2192 ${lev.equalVar?"equal variance":"unequal variance"}`);let postHoc=row.postHocResult;if(names.length>=3&&postHoc&&!postHoc.error&&row.postHocName){lines.push(""),lines.push(`Post-hoc \u2014 ${POSTHOC_LABELS_LP[row.postHocName]||row.postHocName}:`);for(let pr of postHoc.pairs){let p=pr.pAdj!=null?pr.pAdj:pr.p,diff=pr.diff!=null?pr.diff.toFixed(3):pr.z!=null?`z=${pr.z.toFixed(3)}`:"\u2014";lines.push(` ${names[pr.i]} vs ${names[pr.j]}: ${diff}, p = ${formatP(p)} ${pStars(p)}`)}}let power=row.powerResult;if(power){lines.push("");let ciStr=power.effectCI?`, 95% CI [${power.effectCI.lo.toFixed(3)}, ${power.effectCI.hi.toFixed(3)}]`:"";lines.push(`Replication planning: ${power.effectLabel} = ${power.effect.toFixed(3)}${ciStr}`);for(let pr of power.rows){let nStr=pr.nForTarget!=null?`${pr.nForTarget} ${power.nLabel}`:"> 5000";lines.push(` \u03B1=${pr.alpha}: n for 80% power = ${nStr}`)}power.approximate&&lines.push(" (rank-based test \u2014 estimated from parametric analog)")}return lines.push(""),lines.join(` +import{makeTicks}from"./chunk-T4V5V5TK.js";import{BaseStyleControls,CHART_MARGIN,ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,DownloadTiles,HowTo,NumberInput,PlotSidebar,PlotToolShell,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K,STATS_TESTS_FOR_K2,STATS_TEST_REGISTRY,SliderControl,UploadPanel,applyDiscretePalette,autoDetectSep,bhAdjust,buildLineD,buildRScript,computeLegendHeight,computePowerFromData,downloadCsv,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,isNumericValue,pStars,parseData,postHocForTest,renderSvgLegend,resolveDiscretePalette,round2,round4,runPostHoc,runTest,sampleMean,sampleSD,sanitizeRComment,selectTest,svgSafeId,testStatistic,tinv,usePlotToolState}from"./chunk-HFTDMS7M.js";var MARGIN=CHART_MARGIN;var STAR_ROW_H=18,ERROR_KINDS=[{value:"none",label:"None"},{value:"sem",label:"SEM"},{value:"sd",label:"SD"},{value:"ci95",label:"95% CI"}];function formatX(x){return x==null||!Number.isFinite(x)||Number.isInteger(x)?String(x):String(round4(x))}function computeSeries(data,rawData,xCol,yCol,groupCol,groupColors,palette){let groupOrder=[],perGroup=new Map;for(let ri=0;ri{let xMap=perGroup.get(name),points=[...xMap.keys()].sort((a,b)=>a-b).map(x=>{let values=xMap.get(x),n=values.length,mean=sampleMean(values),sd=n>1?sampleSD(values):0,sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0;return{x,values,n,mean,sd,sem,ci95}});return{name,color:groupColors[name]||palette[idx%palette.length],points}})}function computePerXStats(series){let xSet=new Set;for(let s of series)for(let p of s.points)xSet.add(p.x);let xs=[...xSet].sort((a,b)=>a-b),rows=[];for(let x of xs){let groups=[];for(let s of series){let p=s.points.find(q=>q.x===x);p&&p.n>=2&&groups.push({name:s.name,values:p.values})}if(groups.length<2)continue;let values=groups.map(g=>g.values),names=groups.map(g=>g.name),rec=selectTest(values),chosenTest=rec&&rec.recommendation&&rec.recommendation.test?rec.recommendation.test:null,result=chosenTest?runTest(chosenTest,values):null;rows.push({x,names,values,chosenTest,result})}let validIdx=[],validPs=[];rows.forEach((r,i)=>{r.result&&!r.result.error&&r.result.p!=null&&Number.isFinite(r.result.p)&&(validIdx.push(i),validPs.push(r.result.p))});let adjPs=validPs.length>0?bhAdjust(validPs):[];return rows.forEach(r=>r.pAdj=null),validIdx.forEach((origIdx,j)=>rows[origIdx].pAdj=adjPs[j]),rows}var LINEPLOT_HOWTO={toolName:"lineplot",title:"Line Plot \u2014 How to use",subtitle:"Mean \xB1 error per group across an x-axis, with per-x significance",purpose:React.createElement(React.Fragment,null,"Plot how a measurement evolves across an x-axis variable (time, dose, concentration), one line per group. Replicates at the same X are averaged and their spread becomes the error bar."),dataLayout:React.createElement(React.Fragment,null,"Long format \u2014 one row per observation, with a numeric ",React.createElement("strong",null,"X"),", a numeric"," ",React.createElement("strong",null,"Y"),", and a categorical ",React.createElement("strong",null,"group")," column. Replicates share the same (X, group) pair. Error bars only render when a group has \u2265 2 replicates at that X."),display:React.createElement(React.Fragment,null,"Pick ",React.createElement("strong",null,"SEM")," (default), ",React.createElement("strong",null,"SD"),", or ",React.createElement("strong",null,"95% CI")," for error ribbons. At every X shared by \u2265 2 groups the right test is auto-routed (t / Welch / Mann-Whitney; ANOVA / Welch-ANOVA / Kruskal-Wallis); p-values are ",React.createElement("strong",null,"BH-adjusted")," ","across the X-axis and significance stars overlay the chart.")};function UploadStep({sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Bacterial growth curves",subtitle:"3 strains \xD7 5 timepoints \xD7 3 replicates",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \u2014 one row per observation, columns for X, Y, and grouping \xB7 2 MB max"}),React.createElement(HowTo,{...LINEPLOT_HOWTO}))}var LP_AES_THEMES={x:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"X axis"},y:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Y axis"},group:{bg:"var(--aes-shape-bg)",border:"var(--aes-shape-border)",header:"var(--aes-shape-header)",headerText:"var(--aes-shape-header-text)",label:"Group by"}};function LpAesBox({theme,children}){let t=LP_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",minHeight:40}},children))}function ConfigureStep({parsed,fileName,detectedSep,xCol,setXCol,yCol,setYCol,groupCol,setGroupCol,numericCols,categoricalCols}){let canPlot=xCol!=null&&yCol!=null&&numericCols.length>=2;return React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:16}},React.createElement("div",{style:{display:"grid",gridTemplateColumns:"repeat(auto-fit, minmax(220px, 1fr))",gap:12}},React.createElement(LpAesBox,{theme:"x"},React.createElement("select",{value:xCol??"",onChange:e=>setXCol(parseInt(e.target.value)),className:"dv-select",style:{width:"100%"}},numericCols.map(i=>React.createElement("option",{key:i,value:i},parsed.headers[i]))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column plotted along the X axis.")),React.createElement(LpAesBox,{theme:"y"},React.createElement("select",{value:yCol??"",onChange:e=>setYCol(parseInt(e.target.value)),className:"dv-select",style:{width:"100%"}},numericCols.map(i=>React.createElement("option",{key:i,value:i},parsed.headers[i]))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Numeric column plotted along the Y axis.")),React.createElement(LpAesBox,{theme:"group"},React.createElement("select",{value:groupCol??"",onChange:e=>setGroupCol(e.target.value===""?null:parseInt(e.target.value)),className:"dv-select",style:{width:"100%"}},React.createElement("option",{value:""},"\u2014 None (single line) \u2014"),categoricalCols.map(i=>React.createElement("option",{key:i,value:i},parsed.headers[i]))),React.createElement("div",{style:{marginTop:6,fontSize:10,color:"var(--text-faint)"}},"Categorical column used to split the data into coloured lines."))),!canPlot&&React.createElement("p",{style:{margin:"4px 0 0",fontSize:11,color:"var(--warning-text)"}},"Need at least two numeric columns to plot."),React.createElement("div",{className:"dv-panel",style:{marginBottom:0}},React.createElement("p",{style:{margin:"0 0 8px",fontSize:13,color:"var(--text-muted)"}},"Loaded ",React.createElement("strong",{style:{color:"var(--text)"}},fileName||"pasted data")," \u2014"," ",parsed.rawData.length," rows \xD7 ",parsed.headers.length," columns",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement(DataPreview,{headers:parsed.headers,rows:parsed.rawData,maxRows:10})))}var{forwardRef,memo}=React,Chart=memo(forwardRef(function({series,perXStats,xMin,xMax,yMin,yMax,vbW,vbH,xLabel,yLabel,plotTitle,plotSubtitle,plotBg,showGrid,gridColor,lineWidth,pointRadius,errorStrokeWidth,errorCapWidth,errorType,svgLegend,showStars},ref){let itemW=b=>{let maxLen=Math.max(0,...(b.items||[]).map(i=>(i.label||"").length));return Math.max(110,maxLen*6+28)},legendH=computeLegendHeight(svgLegend,vbW-MARGIN.left-MARGIN.right,itemW),topPad=(plotTitle?20:0)+(plotSubtitle?16:0),starRowH=showStars&&perXStats.some(r=>r.pAdj!=null)?STAR_ROW_H:0,w=vbW-MARGIN.left-MARGIN.right,h=vbH-MARGIN.top-MARGIN.bottom,innerTop=MARGIN.top+starRowH,innerH=h-starRowH,xRange=xMax-xMin||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xMin)/xRange*w,sy=v=>innerTop+(1-(v-yMin)/yRange)*innerH,clampY=v=>Math.max(yMin,Math.min(yMax,v)),xTicks=makeTicks(xMin,xMax,8),yTicks=makeTicks(yMin,yMax,6),errOf=p=>errorType==="none"?null:errorType==="sd"?p.sd:errorType==="ci95"?p.ci95:p.sem;return React.createElement("svg",{ref,viewBox:`0 0 ${vbW} ${vbH+legendH+topPad}`,style:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"Line chart"},React.createElement("title",null,plotTitle||"Line chart"),React.createElement("desc",null,`Line chart with ${series.length} group${series.length===1?"":"s"}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:vbW,height:vbH+legendH+topPad,fill:plotBg||"#fff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:vbW/2,y:17,textAnchor:"middle",fontSize:"15",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:vbW/2,y:plotTitle?34:17,textAnchor:"middle",fontSize:"12",fill:"#888",fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"chart",transform:`translate(0, ${topPad})`},React.createElement("rect",{id:"plot-area-background",x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"}),showGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`gy-${t}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"})),xTicks.map(t=>React.createElement("line",{key:`gx-${t}`,x1:sx(t),x2:sx(t),y1:innerTop,y2:innerTop+innerH,stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"}))),React.createElement("g",{id:"traces"},series.map(s=>{let linePts=s.points.map(p=>({x:sx(p.x),y:p.mean!=null?sy(p.mean):null})),d=buildLineD(linePts);return d?React.createElement("path",{key:`line-${s.name}`,id:`trace-${svgSafeId(s.name)}`,d,fill:"none",stroke:s.color,strokeWidth:lineWidth,role:"img","aria-label":`${s.name}: ${s.points.length} x-point${s.points.length!==1?"s":""}`}):null})),React.createElement("g",{id:"error-bars"},series.map(s=>React.createElement("g",{key:`errs-${s.name}`,id:`errbars-${svgSafeId(s.name)}`},s.points.map((p,pi)=>{if(p.n<2||p.mean==null)return null;let e=errOf(p);if(!e||!Number.isFinite(e))return null;let cx=sx(p.x),yHi=sy(clampY(p.mean+e)),yLo=sy(clampY(p.mean-e)),cap=errorCapWidth/2;return React.createElement("g",{key:`err-${pi}`},React.createElement("line",{x1:cx,x2:cx,y1:yHi,y2:yLo,stroke:s.color,strokeWidth:errorStrokeWidth}),React.createElement("line",{x1:cx-cap,x2:cx+cap,y1:yHi,y2:yHi,stroke:s.color,strokeWidth:errorStrokeWidth}),React.createElement("line",{x1:cx-cap,x2:cx+cap,y1:yLo,y2:yLo,stroke:s.color,strokeWidth:errorStrokeWidth}))})))),React.createElement("g",{id:"data-points"},series.map(s=>React.createElement("g",{key:`pts-${s.name}`,id:`points-${svgSafeId(s.name)}`},s.points.map((p,pi)=>p.mean==null?null:React.createElement("circle",{key:`pt-${pi}`,cx:sx(p.x),cy:sy(p.mean),r:pointRadius,fill:s.color,stroke:"#fff",strokeWidth:"0.5"}))))),showStars&&starRowH>0&&React.createElement("g",{id:"significance-stars"},perXStats.map((r,i)=>{if(r.pAdj==null)return null;let s=pStars(r.pAdj);return!s||s==="ns"?null:React.createElement("text",{key:`star-${i}`,x:sx(r.x),y:MARGIN.top+14,textAnchor:"middle",fontSize:"13",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},s)})),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{x1:MARGIN.left,y1:innerTop,x2:MARGIN.left+w,y2:innerTop}),React.createElement("line",{x1:MARGIN.left+w,y1:innerTop,x2:MARGIN.left+w,y2:innerTop+innerH}),React.createElement("line",{x1:MARGIN.left,y1:innerTop+innerH,x2:MARGIN.left+w,y2:innerTop+innerH}),React.createElement("line",{x1:MARGIN.left,y1:innerTop,x2:MARGIN.left,y2:innerTop+innerH})),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:sx(t),x2:sx(t),y1:innerTop+innerH,y2:innerTop+innerH+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:innerTop+innerH+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t)))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t%1===0?t:t.toFixed(1))))),xLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:vbH-4,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},xLabel)),yLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${innerTop+innerH/2}) rotate(-90)`,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},yLabel)),renderSvgLegend(svgLegend,vbH+10,MARGIN.left,vbW-MARGIN.left-MARGIN.right,itemW,14)))}));var TEST_LABELS_LP=Object.fromEntries(Object.entries(STATS_TEST_REGISTRY).map(([id,entry])=>[id,entry.shortLabel||entry.label])),POSTHOC_LABELS_LP=Object.fromEntries(Object.entries(STATS_POSTHOC_REGISTRY).map(([id,entry])=>[id,entry.label]));function formatStat(testName,res){if(!testName||!res||res.error)return"\u2014";let num=v=>v;if(testName==="studentT"||testName==="welchT")return`t(${num(res.df).toFixed(2)}) = ${num(res.t).toFixed(3)}`;if(testName==="mannWhitney")return`U = ${num(res.U).toFixed(1)}`;if(testName==="oneWayANOVA"||testName==="welchANOVA"){let df2=typeof res.df2=="number"?res.df2.toFixed(2):res.df2;return`F(${res.df1}, ${df2}) = ${num(res.F).toFixed(3)}`}return testName==="kruskalWallis"?`H(${res.df}) = ${num(res.H).toFixed(3)}`:"\u2014"}function buildPerXTextBlock(row,xLabel){let lines=[],names=row.names,values=row.values,res=row.result??{};lines.push("=".repeat(60)),lines.push(`${xLabel||"x"} = ${formatX(row.x)}`),lines.push("=".repeat(60)),lines.push(""),lines.push("Groups:");for(let i=0;i1?sampleSD(vs):0,sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0,semStr=n>1?sem.toFixed(3):"\u2014",ciStr=n>1?`\xB1${ci95.toFixed(3)}`:"\u2014";lines.push(` ${names[i]}: n=${n}, mean=${mean.toFixed(3)}, SD=${sd.toFixed(3)}, SEM=${semStr}, 95% CI=${ciStr}`)}lines.push("");let rec=row.rec??{},recTest=rec.recommendation?.test??null,reason=rec.recommendation?.reason;lines.push(`Test: ${row.chosenTest?TEST_LABELS_LP[row.chosenTest]||row.chosenTest:"\u2014"}`),reason&&lines.push(`Reason: ${reason}`),res.error?lines.push(`Result: \u26A0 ${res.error}`):(lines.push(`Result: ${formatStat(row.chosenTest,res)}, p = ${formatP(res.p)}`),row.pAdj!=null&&lines.push(`BH-adjusted p (across x-axis): ${formatP(row.pAdj)}`)),recTest&&recTest!==row.chosenTest&&lines.push(` (Toolbox recommended ${TEST_LABELS_LP[recTest]||recTest})`),lines.push("");let norm=rec.normality??[];if(norm.length>0){let parts=norm.map(r=>{let label=names[r.group]||`g${r.group}`,verdict=r.normal===!0?"normal":r.normal===!1?"not normal":"\u2014";return`${label}: ${verdict}`});lines.push(`Shapiro-Wilk: ${parts.join("; ")}`)}let lev=rec.levene??{};lev.F!=null&&lines.push(`Levene: F(${lev.df1},${lev.df2}) = ${lev.F.toFixed(3)}, p = ${formatP(lev.p)} \u2192 ${lev.equalVar?"equal variance":"unequal variance"}`);let postHoc=row.postHocResult;if(names.length>=3&&postHoc&&!postHoc.error&&row.postHocName){lines.push(""),lines.push(`Post-hoc \u2014 ${POSTHOC_LABELS_LP[row.postHocName]||row.postHocName}:`);for(let pr of postHoc.pairs){let p=pr.pAdj!=null?pr.pAdj:pr.p,diff=pr.diff!=null?pr.diff.toFixed(3):pr.z!=null?`z=${pr.z.toFixed(3)}`:"\u2014";lines.push(` ${names[pr.i]} vs ${names[pr.j]}: ${diff}, p = ${formatP(p)} ${pStars(p)}`)}}let power=row.powerResult;if(power){lines.push("");let ciStr=power.effectCI?`, 95% CI [${power.effectCI.lo.toFixed(3)}, ${power.effectCI.hi.toFixed(3)}]`:"";lines.push(`Replication planning: ${power.effectLabel} = ${power.effect.toFixed(3)}${ciStr}`);for(let pr of power.rows){let nStr=pr.nForTarget!=null?`${pr.nForTarget} ${power.nLabel}`:"> 5000";lines.push(` \u03B1=${pr.alpha}: n for 80% power = ${nStr}`)}power.approximate&&lines.push(" (rank-based test \u2014 estimated from parametric analog)")}return lines.push(""),lines.join(` `)}function buildAggregateReport(rows,xLabel){return["Line Plot \u2014 per-x statistical analysis","Generated: "+new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),`X axis: ${xLabel||"x"}`,`Eligible points: ${rows.length}`,"","P-values are BH-adjusted across the x-axis. Stars use the adjusted p.",""].join(` `)+rows.map(r=>buildPerXTextBlock(r,xLabel)).join("")}function buildAggregateRScript(rows,xLabel){if(!rows.length||typeof buildRScript!="function")return"";let now=new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),safeXLabel=sanitizeRComment(xLabel||"x"),parts=[["# -----------------------------------------------------------------------------","# Pl\xF6ttr \u2014 Line Plot R script export (combined per-x analysis)","# Generated: "+now,`# X axis: ${safeXLabel} \u2014 ${rows.length} eligible points.`,"# Each section redefines `df` for one x value and runs its assumption checks,","# chosen test, and post-hoc (if applicable).","# -----------------------------------------------------------------------------",""].join(` `)];for(let i=0;iupdVis({errorType:v}),[updVis]),showStars=vis.showStars??!0,setShowStars=useCallback(v=>updVis({showStars:v}),[updVis]),groupColors=useMemo2(()=>vis.groupColors||{},[vis.groupColors]),svgRef=useRef(null),sepRef=useRef(""),[detectedSep,setDetectedSep]=useState2(""),parsed=useMemo2(()=>rawText?parseData(rawText,sepRef.current):null,[rawText]),colIsNumeric=useMemo2(()=>parsed?parsed.headers.reduce((acc,_,i)=>{let vals=parsed.rawData.map(r=>r[i]).filter(v=>v!==""&&v!=null);return acc[i]=vals.length>0&&vals.filter(v=>isNumericValue(v)).length/vals.length>.5,acc},{}):{},[parsed]),numericCols=useMemo2(()=>parsed?parsed.headers.reduce((acc,_,i)=>colIsNumeric[i]?[...acc,i]:acc,[]):[],[parsed,colIsNumeric]),categoricalCols=useMemo2(()=>parsed?parsed.headers.reduce((acc,_,i)=>colIsNumeric[i]?acc:[...acc,i],[]):[],[parsed,colIsNumeric]),series=useMemo2(()=>{if(!parsed||xCol==null||yCol==null)return[];let groupCount=groupCol==null?1:Math.min(128,new Set(parsed.rawData.map(r=>String(r[groupCol]??""))).size||1),seedColors=resolveDiscretePalette(vis.discretePalette||"okabe-ito",groupCount);return computeSeries(parsed.data,parsed.rawData,xCol,yCol,groupCol,groupColors,seedColors)},[parsed,xCol,yCol,groupCol,groupColors,vis.discretePalette]),setGroupColor=useCallback((name,color)=>updVis({groupColors:{...vis.groupColors||{},[name]:color}}),[updVis,vis.groupColors]),statsRows=useMemo2(()=>series.length>=2?computePerXStats(series):[],[series]),autoAxis=useMemo2(()=>{if(series.length===0)return{xMin:0,xMax:1,yMin:0,yMax:1};let xMin=1/0,xMax=-1/0,yLo=1/0,yHi=-1/0;for(let s of series)for(let p of s.points){if(p.xxMax&&(xMax=p.x),p.mean==null)continue;let e=errorType==="none"?0:errorType==="sd"?p.sd:errorType==="ci95"?p.ci95:p.sem,hi=p.mean+(e||0),lo=p.mean-(e||0);loyHi&&(yHi=hi)}if(!Number.isFinite(xMin))return{xMin:0,xMax:1,yMin:0,yMax:1};let xPad=xMin===xMax?.5:(xMax-xMin)*.05,yPad=yLo===yHi?.5:(yHi-yLo)*.08;return{xMin:round2(xMin-xPad),xMax:round2(xMax+xPad),yMin:round2(yLo-yPad),yMax:round2(yHi+yPad)}},[series,errorType]),effAxis={xMin:vis.xMin!=null?vis.xMin:autoAxis.xMin,xMax:vis.xMax!=null?vis.xMax:autoAxis.xMax,yMin:vis.yMin!=null?vis.yMin:autoAxis.yMin,yMax:vis.yMax!=null?vis.yMax:autoAxis.yMax},svgLegend=useMemo2(()=>series.length===0||series.length===1&&series[0].name==="(all)"?null:[{id:"legend-group",title:groupCol!=null&&parsed?parsed.headers[groupCol]:"",items:series.map(s=>({label:s.name,color:s.color,shape:"dot"}))}],[series,groupCol,parsed]);useEffect(()=>{!parsed||xCol==null||yCol==null||updVis({xMin:null,xMax:null,yMin:null,yMax:null,xLabel:parsed.headers[xCol],yLabel:parsed.headers[yCol]})},[xCol,yCol,parsed,updVis]);let doParse=useCallback((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";sepRef.current=effectiveSep,setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count);let fixedText=dc.text,{headers,data,rawData,injectionWarnings}=parseData(fixedText,effectiveSep);if(setInjectionWarning(injectionWarnings),headers.length<2||data.length===0){setParseError("The file appears to be empty or has no data rows. Please check your file and try again.");return}setParseError(null),setRawText(fixedText);let isNum=idx=>{let vals=rawData.map(r=>r[idx]).filter(v=>v!==""&&v!=null);return vals.length>0&&vals.filter(v=>isNumericValue(v)).length/vals.length>.5},nums=headers.reduce((acc,_,i)=>isNum(i)?[...acc,i]:acc,[]),cats=headers.reduce((acc,_,i)=>isNum(i)?acc:[...acc,i],[]);setXCol(nums[0]!==void 0?nums[0]:0),setYCol(nums[1]!==void 0?nums[1]:nums[0]!==void 0?nums[0]:1),setGroupCol(cats[0]!==void 0?cats[0]:null),updVis({groupColors:{}}),setStep("configure")},[setCommaFixed,setCommaFixCount,setInjectionWarning,setParseError,setStep,updVis]),handleFileLoad=useCallback((text,name)=>{setFileName(name),doParse(text,sepOverride)},[sepOverride,doParse,setFileName]),handleTextPaste=useCallback((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]),loadExample=useCallback(()=>{let text=EXAMPLE_CSV;text&&(setSepOverride(""),setFileName("bacterial_growth.csv"),doParse(text,""))},[doParse,setFileName,setSepOverride]);return React.createElement(PlotToolShell,{state:shell,toolName:"lineplot",title:"Line Plot",visInit:VIS_INIT_LINEPLOT,steps:["upload","configure","plot"],canNavigate:s=>s==="upload"?!0:s==="configure"?!!parsed:s==="plot"?!!parsed&&xCol!=null&&yCol!=null:!1},step==="upload"&&React.createElement(UploadStep,{sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample:loadExample}),step==="configure"&&parsed&&React.createElement(ConfigureStep,{parsed,fileName,detectedSep,xCol,setXCol,yCol,setYCol,groupCol,setGroupCol,numericCols,categoricalCols}),step==="plot"&&parsed&&React.createElement(PlotStep,{parsed,fileName,series,statsRows,xCol,setXCol,yCol,setYCol,groupCol,setGroupCol,numericCols,categoricalCols,setGroupColor,vis,updVis,autoAxis,effAxis,errorType,setErrorType,showStars,setShowStars,svgRef,svgLegend,resetAll:()=>{setRawText(null),setFileName(""),setInjectionWarning(null),setStep("upload")}}))}export{App}; -//# sourceMappingURL=app-BDRRNBIR.js.map +//# sourceMappingURL=app-TQWDLQEC.js.map diff --git a/tools/_app/chunks/app-BDRRNBIR.js.map b/tools/_app/chunks/app-TQWDLQEC.js.map similarity index 100% rename from tools/_app/chunks/app-BDRRNBIR.js.map rename to tools/_app/chunks/app-TQWDLQEC.js.map diff --git a/tools/_app/chunks/app-ZT4GXXOQ.js b/tools/_app/chunks/app-VRVO3EYQ.js similarity index 99% rename from tools/_app/chunks/app-ZT4GXXOQ.js rename to tools/_app/chunks/app-VRVO3EYQ.js index f2daa785..902a776b 100644 --- a/tools/_app/chunks/app-ZT4GXXOQ.js +++ b/tools/_app/chunks/app-VRVO3EYQ.js @@ -1,4 +1,4 @@ -import{makeTicks}from"./chunk-T4V5V5TK.js";import{ActionsPanel,BaseStyleControls,CHART_MARGIN,CldLabels,ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,HowTo,NumberInput,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K,STATS_TESTS_FOR_K2,STATS_TEST_REGISTRY,SegToggle,SignificanceBrackets,SliderControl,UploadPanel,assignBracketLevels,autoDetectSep,buildLineD,buildRScript,compactLetterDisplay,computeLegendHeight,computePowerFromData,downloadCsv,downloadPng,downloadSvg,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,navigateToTool,pStars,parseData,postHocForTest,renderSvgLegend,resolveDiscretePalette,round2,runPostHoc,runTest,sampleMean,sampleSD,seededRandom,selectTest,setHandoff,svgSafeId,tinv,usePlotToolState}from"./chunk-5F6S3FYW.js";var DEFAULT_KR=7,DEFAULT_KTR=118,DEFAULT_KD=7,DEFAULT_HILL_N=3,TIME_UNITS=[{key:"ms",label:"milliseconds"},{key:"s",label:"seconds"},{key:"min",label:"minutes"},{key:"h",label:"hours"},{key:"d",label:"days"},{key:"w",label:"weeks"},{key:"mo",label:"months"},{key:"yr",label:"years"}],TO_SECONDS={ms:.001,s:1,min:60,h:3600,d:86400,w:604800,mo:2629800,yr:31557600};function convertTime(value,fromUnit,toUnit){return fromUnit===toUnit?value:value*TO_SECONDS[fromUnit]/TO_SECONDS[toUnit]}function calibrate(headers,data,Kr,Ktr){let nCols=headers.length,nRows=data.length,totals=new Array(nCols).fill(0);for(let r=0;r=1){row.push(null);continue}row.push(Kd*Math.cbrt(f/(1-f)))}cal.push(row)}return cal}function calibrateGeneralized(headers,data,Kr,Ktr,n){let nCols=headers.length,nRows=data.length,totals=new Array(nCols).fill(0);for(let r=0;r(nameOcc[h]=(nameOcc[h]||0)+1,nameOcc[h]));if(poolReplicates){let pm={};headers.forEach((h,i)=>{columnEnabled&&columnEnabled[i]===!1||(pm[h]||(pm[h]=[]),pm[h].push(i))});let entries=Object.entries(pm),seed=resolveDiscretePalette(paletteName,entries.length);return entries.map(([name,colIndices],idx)=>({prefix:name,label:name,color:seed[idx%Math.max(1,seed.length)]||PALETTE[idx%PALETTE.length],colIndices}))}else{let items=headers.map((h,i)=>({h,i,rep:repNums[i]})).filter(({i})=>!columnEnabled||columnEnabled[i]!==!1),seed=resolveDiscretePalette(paletteName,items.length);return items.map(({h,i,rep},ci)=>({prefix:`${h}__col${i}`,label:`${h}_rep${rep}`,color:seed[ci%Math.max(1,seed.length)]||PALETTE[ci%PALETTE.length],colIndices:[i]}))}}function smooth(arr,w){return w<=0?arr:arr.map((_,i)=>{let sum=0,n=0;for(let j=Math.max(0,i-w);j<=Math.min(arr.length-1,i+w);j++){let v=arr[j];v!=null&&(sum+=v,n++)}return n>0?sum/n:null})}function buildAreaD(pts){let valid=pts.filter(p=>p.yHi!=null&&p.yLo!=null);if(valid.length<2)return"";let fwd=valid.map(p=>`${p.x.toFixed(2)},${p.yHi.toFixed(2)}`),rev=valid.slice().reverse().map(p=>`${p.x.toFixed(2)},${p.yLo.toFixed(2)}`);return"M"+fwd.join("L")+"L"+rev.join("L")+"Z"}var MARGIN=CHART_MARGIN;function computeAutoYRange(calData,xStart,xEnd){if(!calData||calData.length===0)return null;let r0=Math.max(0,Math.floor(xStart)),r1=Math.min(calData.length-1,Math.ceil(xEnd)),lo=1/0,hi=-1/0;for(let r=r0;r<=r1;r++){let row=calData[r];if(row)for(let c=0;chi&&(hi=v))}}return!isFinite(lo)||!isFinite(hi)?null:{yMin:round2(Math.max(0,lo*.9)),yMax:round2(hi*1.1)}}function ribbonEdgeMatrix(stats){if(!stats||stats.length===0)return[];let rows=[];for(let s of stats)for(let r=0;r=1e6||abs<.001?v.toExponential(2):parseFloat(v.toFixed(4)).toString()}function Frac({num,den}){return React.createElement("span",{style:{display:"inline-flex",flexDirection:"column",alignItems:"center",verticalAlign:"middle",margin:"0 4px",lineHeight:1.2}},React.createElement("span",{style:{padding:"0 6px"}},num),React.createElement("span",{style:{borderTop:"1px solid currentColor",padding:"1px 6px 0",width:"100%",textAlign:"center"}},den))}var ITAL_F=React.createElement("span",{style:{fontStyle:"italic"}},"f");function CalibrationFormulaPreview({formula,Kr,Ktr,Kd,hillN}){if(formula==="none")return null;let expDen=formula==="generalized"?fmtCoef(hillN):"3",exp=React.createElement("sup",{style:{fontSize:"0.72em",lineHeight:0}},"1/",expDen),body;if(formula==="allen-blinks")body=React.createElement(React.Fragment,null,"[Ca\xB2\u207A] =",React.createElement(Frac,{num:React.createElement(React.Fragment,null,"(1 + ",fmtCoef(Ktr),")\xB7",ITAL_F,exp," \u2212 1"),den:React.createElement(React.Fragment,null,fmtCoef(Kr),"\xB7(1 \u2212 ",ITAL_F,exp,")")}));else if(formula==="hill")body=React.createElement(React.Fragment,null,"[Ca\xB2\u207A] = ",fmtCoef(Kd)," \xB7 (",React.createElement(Frac,{num:ITAL_F,den:React.createElement(React.Fragment,null,"1 \u2212 ",ITAL_F)}),")",React.createElement("sup",{style:{fontSize:"0.72em",lineHeight:0}},"1/3"));else if(formula==="generalized")body=React.createElement(React.Fragment,null,"[Ca\xB2\u207A] =",React.createElement(Frac,{num:React.createElement(React.Fragment,null,"(1 + ",fmtCoef(Ktr),")\xB7",ITAL_F,exp," \u2212 1"),den:React.createElement(React.Fragment,null,fmtCoef(Kr),"\xB7(1 \u2212 ",ITAL_F,exp,")")}));else return null;return React.createElement("div",{"aria-label":"Calibration formula with your parameter values substituted",style:{marginTop:12,padding:"10px 14px",background:"var(--surface-subtle)",border:"1px solid var(--border)",borderRadius:6,display:"flex",alignItems:"center",gap:14,flexWrap:"wrap"}},React.createElement("span",{style:{fontSize:10,fontWeight:700,textTransform:"uppercase",letterSpacing:"0.6px",color:"var(--text-muted)",whiteSpace:"nowrap"}},"With your values"),React.createElement("span",{style:{fontFamily:"ui-serif, Georgia, 'Times New Roman', serif",fontSize:15,color:"var(--text)",display:"inline-flex",alignItems:"center",flexWrap:"wrap",gap:4}},body),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)",marginLeft:"auto",whiteSpace:"nowrap"}},React.createElement("span",{style:{fontStyle:"italic"}},"f")," ="," ",React.createElement("span",{style:{fontStyle:"italic"}},"L"),"(",React.createElement("span",{style:{fontStyle:"italic"}},"t"),") / \u03A3",React.createElement("span",{style:{fontStyle:"italic"}},"L")))}function HowToSection(){return React.createElement(HowTo,{...AEQUORIN_HOWTO})}function UploadStep({sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Aequorin Ca\xB2\u207A time-course",subtitle:"Mutant vs WT response to a CO7 elicitor pulse",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 one column per sample, one row per time-point \xB7 2 MB max"}),React.createElement(HowToSection,null))}var AQ_AES_THEMES={calibration:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Aequorin calibration"},time:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Time axis"}};function AqAesBox({theme,children}){let t=AQ_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg,flex:"1 1 0",display:"flex",flexDirection:"column"}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",flex:1}},children))}function ConfigureStep({parsed,formula,setFormula,Kr,setKr,Ktr,setKtr,Kd,setKd,hillN,setHillN,vis,updVis,fileName,detectedSep,calData,columnEnabled,downloadCalibrated}){return React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:16,marginBottom:16,alignItems:"stretch"}},React.createElement(AqAesBox,{theme:"calibration"},React.createElement("div",{style:{display:"flex",gap:16,flexWrap:"wrap",alignItems:"flex-end"}},React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Formula"),React.createElement("select",{value:formula,onChange:e=>setFormula(e.target.value),className:"dv-select"},React.createElement("option",{value:"none"},"None (raw data)"),React.createElement("option",{value:"allen-blinks"},"Allen & Blinks (1978)"),React.createElement("option",{value:"hill"},"Hill equilibrium"),React.createElement("option",{value:"generalized"},"Generalised Allen & Blinks"))),(formula==="allen-blinks"||formula==="generalized")&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Kr"),React.createElement(NumberInput,{value:Kr,onChange:e=>setKr(Number(e.target.value)),step:"0.1"})),(formula==="allen-blinks"||formula==="generalized")&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Ktr"),React.createElement(NumberInput,{value:Ktr,onChange:e=>setKtr(Number(e.target.value)),step:"1"})),formula==="hill"&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Kd (\xB5M)"),React.createElement(NumberInput,{value:Kd,onChange:e=>setKd(Number(e.target.value)),step:"0.5",min:"0.1"})),formula==="generalized"&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"n (Hill exp.)"),React.createElement(NumberInput,{value:hillN,onChange:e=>setHillN(Number(e.target.value)),step:"0.5",min:"1"}))),React.createElement(CalibrationFormulaPreview,{formula,Kr,Ktr,Kd,hillN})),React.createElement(AqAesBox,{theme:"time"},React.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:12,alignItems:"flex-end"}},React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Time step (per row)"),React.createElement(NumberInput,{value:vis.timeStep,onChange:e=>updVis({timeStep:Number(e.target.value)||1}),style:{width:132},min:"0.001",step:"any"})),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Base unit"),React.createElement("select",{value:vis.baseUnit,onChange:e=>updVis({baseUnit:e.target.value}),className:"dv-select"},TIME_UNITS.map(u=>React.createElement("option",{key:u.key,value:u.key},u.label)))),parsed&&React.createElement("div",{style:{fontSize:12,color:"var(--text-faint)",paddingBottom:4}},"Range: 0 \u2013 ",(parsed.data.length*vis.timeStep).toFixed(3)," ",vis.baseUnit)))),React.createElement("div",{className:"dv-panel"},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:8}},React.createElement("p",{style:{margin:0,fontSize:13,color:"var(--text-muted)"}},"Loaded ",React.createElement("strong",{style:{color:"var(--text)"}},fileName)," \u2014"," ",parsed.headers.length," samples \xD7 ",parsed.data.length," time-points",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement("button",{onClick:e=>{downloadCalibrated(),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),calData&&parsed&&(()=>{let ei=parsed.headers.map((_,i)=>i).filter(i=>columnEnabled[i]!==!1);return React.createElement("div",{style:{marginTop:8}},React.createElement("p",{style:{margin:"0 0 6px",fontSize:12,fontWeight:600,color:"var(--text-muted)"}},"Preview \u2014 ",formula==="none"?"raw data":"calibrated data"," \xB7 ",ei.length," of"," ",parsed.headers.length," columns (first 15 rows):"),React.createElement(DataPreview,{headers:ei.map(i=>parsed.headers[i]),rows:calData.slice(0,15).map(r=>ei.map(i=>r[i]!=null?r[i]:"")),maxRows:15}))})()))}var{forwardRef,useRef,useState,useEffect,memo}=React,Chart=memo(forwardRef(function({series,xStart,xEnd,yMin,yMax,vbW,vbH,xLabel,yLabel,plotBg,showGrid,lineWidth,ribbonOpacity,gridColor,svgLegend,plotTitle,plotSubtitle,onBrush},ref){let aequorinItemW=b=>{let maxLen=Math.max(0,...(b.items||[]).map(i=>(i.label||"").length));return Math.max(110,maxLen*6+28)},legendH=computeLegendHeight(svgLegend,vbW-MARGIN.left-MARGIN.right,aequorinItemW),topPad=(plotTitle?20:0)+(plotSubtitle?16:0),w=vbW-MARGIN.left-MARGIN.right,h=vbH-MARGIN.top-MARGIN.bottom,xRange=xEnd-xStart||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xStart)/xRange*w,sy=v=>MARGIN.top+(1-(v-yMin)/yRange)*h,clamp=v=>Math.max(yMin,Math.min(yMax,v)),xTicks=makeTicks(xStart,xEnd,8),yTicks=makeTicks(yMin,yMax,6),paths=series.map(s=>{let areaPts=s.rows.map(r=>({x:sx(r.t),yHi:r.mean!=null&&r.sd!=null?sy(clamp(r.mean+r.sd)):null,yLo:r.mean!=null&&r.sd!=null?sy(clamp(r.mean-r.sd)):null})),linePts=s.rows.map(r=>({x:sx(r.t),y:r.mean!=null?sy(r.mean):null}));return{prefix:s.prefix,color:s.color,areaD:buildAreaD(areaPts),lineD:buildLineD(linePts)}}),[brush,setBrush]=useState(null),plotY0=topPad+MARGIN.top,plotY1=plotY0+h,clientToVb=(svg,clientX,clientY)=>{let bb=svg.getBoundingClientRect(),scale=vbW/(bb.width||1),x=(clientX-bb.left)*scale,y=(clientY-bb.top)*scale;return{x:Math.max(MARGIN.left,Math.min(MARGIN.left+w,x)),y}},vbToData=vx=>xStart+(vx-MARGIN.left)/(w||1)*(xEnd-xStart),onBrushDown=e=>{if(!onBrush||e.button!==0)return;let emit=onBrush,svg=e.currentTarget,start=clientToVb(svg,e.clientX,e.clientY);if(start.yplotY1)return;e.preventDefault(),setBrush({x0:start.x,x1:start.x});let move=ev=>{setBrush({x0:start.x,x1:clientToVb(svg,ev.clientX,ev.clientY).x})},up=ev=>{window.removeEventListener("mousemove",move),window.removeEventListener("mouseup",up);let endX=clientToVb(svg,ev.clientX,ev.clientY).x;if(setBrush(null),Math.abs(endX-start.x)<4)return;let a=vbToData(start.x),b=vbToData(endX);emit(Math.min(a,b),Math.max(a,b))};window.addEventListener("mousemove",move),window.addEventListener("mouseup",up)};return React.createElement("svg",{ref,viewBox:`0 0 ${vbW} ${vbH+legendH+topPad}`,style:onBrush?{width:"100%",height:"auto",display:"block",cursor:"crosshair",userSelect:"none"}:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"RLU timecourse chart",onMouseDown:onBrush?onBrushDown:void 0},React.createElement("title",null,plotTitle||"RLU timecourse chart"),React.createElement("desc",null,`Time series chart with ${series.length} series${xLabel?`, X: ${xLabel}`:""}${yLabel?`, Y: ${yLabel}`:""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:vbW,height:vbH+legendH+topPad,fill:plotBg||"#fff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:vbW/2,y:17,textAnchor:"middle",fontSize:"15",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:vbW/2,y:plotTitle?34:17,textAnchor:"middle",fontSize:"12",fill:"#888",fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"chart",transform:`translate(0, ${topPad})`},React.createElement("rect",{id:"plot-area-background",x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"}),showGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`gy-${t}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"})),xTicks.map(t=>React.createElement("line",{key:`gx-${t}`,x1:sx(t),x2:sx(t),y1:MARGIN.top,y2:MARGIN.top+h,stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"}))),React.createElement("g",{id:"ribbons"},paths.map(p=>p.areaD?React.createElement("path",{key:`area-${p.prefix}`,id:`ribbon-${svgSafeId(p.prefix)}`,d:p.areaD,fill:p.color,fillOpacity:ribbonOpacity,stroke:p.color,strokeOpacity:ribbonOpacity,strokeWidth:"0.5"}):null)),React.createElement("g",{id:"traces"},paths.map(p=>p.lineD?React.createElement("path",{key:`line-${p.prefix}`,id:`trace-${svgSafeId(p.prefix)}`,d:p.lineD,fill:"none",stroke:p.color,strokeWidth:lineWidth,role:"img","aria-label":`Trace: ${p.prefix}`}):null)),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{id:"plot-frame-top",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top}),React.createElement("line",{id:"plot-frame-right",x1:MARGIN.left+w,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:MARGIN.left,y1:MARGIN.top+h,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-left",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left,y2:MARGIN.top+h})),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:sx(t),x2:sx(t),y1:MARGIN.top+h,y2:MARGIN.top+h+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:MARGIN.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t)))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t%1===0?t:t.toFixed(1))))),xLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:vbH-4,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},xLabel)),yLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${MARGIN.top+h/2}) rotate(-90)`,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},yLabel)),renderSvgLegend(svgLegend,vbH+10,MARGIN.left,vbW-MARGIN.left-MARGIN.right,aequorinItemW),brush&&Math.abs(brush.x1-brush.x0)>.5&&React.createElement("g",{id:"brush-selection",pointerEvents:"none"},React.createElement("rect",{x:Math.min(brush.x0,brush.x1),y:MARGIN.top,width:Math.abs(brush.x1-brush.x0),height:h,fill:"#648fff",fillOpacity:"0.15",stroke:"#648fff",strokeOpacity:"0.6",strokeWidth:"1"}))))})),InsetBarplot=memo(forwardRef(function({series,insetFillOpacity,insetBarWidth,insetBarGap,insetYMin,insetYMax,insetW,insetH,insetErrorType,insetShowBarOutline,insetBarOutlineColor,insetBarStrokeWidth,insetShowGrid,insetGridColor,insetErrorStrokeWidth,insetXFontSize,insetYFontSize,insetXLabelAngle,plotBg,plotTitle,plotSubtitle,corrected,replicateSums,annotations,statsSummary,showPoints,pointSize,pointColor},ref){let refIW=insetW||400,iH=insetH||200,_compact=(100-(insetBarGap??0))/100,topPad=(plotTitle?20:0)+(plotSubtitle?16:0),xAngle=insetXLabelAngle||0,absAngle=Math.abs(xAngle),annotPairs=annotations&&annotations.kind==="brackets"?assignBracketLevels(annotations.pairs||[]):[],annotMaxLevel=annotPairs.reduce((m,pr)=>Math.max(m,pr._level||0),0),annotTopPad=annotations&&annotations.kind==="cld"?22:annotations&&annotations.kind==="brackets"&&annotPairs.length>0?(annotMaxLevel+1)*20+6:0,STATS_LINE_H=11,STATS_FONT=8,summaryLines=statsSummary?statsSummary.split(` +import{makeTicks}from"./chunk-T4V5V5TK.js";import{ActionsPanel,BaseStyleControls,CHART_MARGIN,CldLabels,ColorInput,ControlSection,DataPreview,DetectedSeparatorBadge,DiscretePaletteRow,HowTo,NumberInput,OnOffToggle,PALETTE,PlotSidebar,PlotToolShell,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K,STATS_TESTS_FOR_K2,STATS_TEST_REGISTRY,SegToggle,SignificanceBrackets,SliderControl,UploadPanel,assignBracketLevels,autoDetectSep,buildLineD,buildRScript,compactLetterDisplay,computeLegendHeight,computePowerFromData,downloadCsv,downloadPng,downloadSvg,downloadText,fileBaseName,fixDecimalCommas,flashSaved,formatP,navigateToTool,pStars,parseData,postHocForTest,renderSvgLegend,resolveDiscretePalette,round2,runPostHoc,runTest,sampleMean,sampleSD,seededRandom,selectTest,setHandoff,svgSafeId,tinv,usePlotToolState}from"./chunk-HFTDMS7M.js";var DEFAULT_KR=7,DEFAULT_KTR=118,DEFAULT_KD=7,DEFAULT_HILL_N=3,TIME_UNITS=[{key:"ms",label:"milliseconds"},{key:"s",label:"seconds"},{key:"min",label:"minutes"},{key:"h",label:"hours"},{key:"d",label:"days"},{key:"w",label:"weeks"},{key:"mo",label:"months"},{key:"yr",label:"years"}],TO_SECONDS={ms:.001,s:1,min:60,h:3600,d:86400,w:604800,mo:2629800,yr:31557600};function convertTime(value,fromUnit,toUnit){return fromUnit===toUnit?value:value*TO_SECONDS[fromUnit]/TO_SECONDS[toUnit]}function calibrate(headers,data,Kr,Ktr){let nCols=headers.length,nRows=data.length,totals=new Array(nCols).fill(0);for(let r=0;r=1){row.push(null);continue}row.push(Kd*Math.cbrt(f/(1-f)))}cal.push(row)}return cal}function calibrateGeneralized(headers,data,Kr,Ktr,n){let nCols=headers.length,nRows=data.length,totals=new Array(nCols).fill(0);for(let r=0;r(nameOcc[h]=(nameOcc[h]||0)+1,nameOcc[h]));if(poolReplicates){let pm={};headers.forEach((h,i)=>{columnEnabled&&columnEnabled[i]===!1||(pm[h]||(pm[h]=[]),pm[h].push(i))});let entries=Object.entries(pm),seed=resolveDiscretePalette(paletteName,entries.length);return entries.map(([name,colIndices],idx)=>({prefix:name,label:name,color:seed[idx%Math.max(1,seed.length)]||PALETTE[idx%PALETTE.length],colIndices}))}else{let items=headers.map((h,i)=>({h,i,rep:repNums[i]})).filter(({i})=>!columnEnabled||columnEnabled[i]!==!1),seed=resolveDiscretePalette(paletteName,items.length);return items.map(({h,i,rep},ci)=>({prefix:`${h}__col${i}`,label:`${h}_rep${rep}`,color:seed[ci%Math.max(1,seed.length)]||PALETTE[ci%PALETTE.length],colIndices:[i]}))}}function smooth(arr,w){return w<=0?arr:arr.map((_,i)=>{let sum=0,n=0;for(let j=Math.max(0,i-w);j<=Math.min(arr.length-1,i+w);j++){let v=arr[j];v!=null&&(sum+=v,n++)}return n>0?sum/n:null})}function buildAreaD(pts){let valid=pts.filter(p=>p.yHi!=null&&p.yLo!=null);if(valid.length<2)return"";let fwd=valid.map(p=>`${p.x.toFixed(2)},${p.yHi.toFixed(2)}`),rev=valid.slice().reverse().map(p=>`${p.x.toFixed(2)},${p.yLo.toFixed(2)}`);return"M"+fwd.join("L")+"L"+rev.join("L")+"Z"}var MARGIN=CHART_MARGIN;function computeAutoYRange(calData,xStart,xEnd){if(!calData||calData.length===0)return null;let r0=Math.max(0,Math.floor(xStart)),r1=Math.min(calData.length-1,Math.ceil(xEnd)),lo=1/0,hi=-1/0;for(let r=r0;r<=r1;r++){let row=calData[r];if(row)for(let c=0;chi&&(hi=v))}}return!isFinite(lo)||!isFinite(hi)?null:{yMin:round2(Math.max(0,lo*.9)),yMax:round2(hi*1.1)}}function ribbonEdgeMatrix(stats){if(!stats||stats.length===0)return[];let rows=[];for(let s of stats)for(let r=0;r=1e6||abs<.001?v.toExponential(2):parseFloat(v.toFixed(4)).toString()}function Frac({num,den}){return React.createElement("span",{style:{display:"inline-flex",flexDirection:"column",alignItems:"center",verticalAlign:"middle",margin:"0 4px",lineHeight:1.2}},React.createElement("span",{style:{padding:"0 6px"}},num),React.createElement("span",{style:{borderTop:"1px solid currentColor",padding:"1px 6px 0",width:"100%",textAlign:"center"}},den))}var ITAL_F=React.createElement("span",{style:{fontStyle:"italic"}},"f");function CalibrationFormulaPreview({formula,Kr,Ktr,Kd,hillN}){if(formula==="none")return null;let expDen=formula==="generalized"?fmtCoef(hillN):"3",exp=React.createElement("sup",{style:{fontSize:"0.72em",lineHeight:0}},"1/",expDen),body;if(formula==="allen-blinks")body=React.createElement(React.Fragment,null,"[Ca\xB2\u207A] =",React.createElement(Frac,{num:React.createElement(React.Fragment,null,"(1 + ",fmtCoef(Ktr),")\xB7",ITAL_F,exp," \u2212 1"),den:React.createElement(React.Fragment,null,fmtCoef(Kr),"\xB7(1 \u2212 ",ITAL_F,exp,")")}));else if(formula==="hill")body=React.createElement(React.Fragment,null,"[Ca\xB2\u207A] = ",fmtCoef(Kd)," \xB7 (",React.createElement(Frac,{num:ITAL_F,den:React.createElement(React.Fragment,null,"1 \u2212 ",ITAL_F)}),")",React.createElement("sup",{style:{fontSize:"0.72em",lineHeight:0}},"1/3"));else if(formula==="generalized")body=React.createElement(React.Fragment,null,"[Ca\xB2\u207A] =",React.createElement(Frac,{num:React.createElement(React.Fragment,null,"(1 + ",fmtCoef(Ktr),")\xB7",ITAL_F,exp," \u2212 1"),den:React.createElement(React.Fragment,null,fmtCoef(Kr),"\xB7(1 \u2212 ",ITAL_F,exp,")")}));else return null;return React.createElement("div",{"aria-label":"Calibration formula with your parameter values substituted",style:{marginTop:12,padding:"10px 14px",background:"var(--surface-subtle)",border:"1px solid var(--border)",borderRadius:6,display:"flex",alignItems:"center",gap:14,flexWrap:"wrap"}},React.createElement("span",{style:{fontSize:10,fontWeight:700,textTransform:"uppercase",letterSpacing:"0.6px",color:"var(--text-muted)",whiteSpace:"nowrap"}},"With your values"),React.createElement("span",{style:{fontFamily:"ui-serif, Georgia, 'Times New Roman', serif",fontSize:15,color:"var(--text)",display:"inline-flex",alignItems:"center",flexWrap:"wrap",gap:4}},body),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)",marginLeft:"auto",whiteSpace:"nowrap"}},React.createElement("span",{style:{fontStyle:"italic"}},"f")," ="," ",React.createElement("span",{style:{fontStyle:"italic"}},"L"),"(",React.createElement("span",{style:{fontStyle:"italic"}},"t"),") / \u03A3",React.createElement("span",{style:{fontStyle:"italic"}},"L")))}function HowToSection(){return React.createElement(HowTo,{...AEQUORIN_HOWTO})}function UploadStep({sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample}){return React.createElement("div",null,React.createElement(UploadPanel,{sepOverride,onSepChange:v=>{setSepOverride(v),rawText&&doParse(rawText,v)},onFileLoad:handleFileLoad,onTextPaste:handleTextPaste,autoDetect:!0,onLoadExample,exampleSummary:{title:"Aequorin Ca\xB2\u207A time-course",subtitle:"Mutant vs WT response to a CO7 elicitor pulse",buttonLabel:"Plot this example \u2192"},hint:"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 one column per sample, one row per time-point \xB7 2 MB max"}),React.createElement(HowToSection,null))}var AQ_AES_THEMES={calibration:{bg:"var(--aes-color-bg)",border:"var(--aes-color-border)",header:"var(--aes-color-header)",headerText:"var(--aes-color-header-text)",label:"Aequorin calibration"},time:{bg:"var(--aes-size-bg)",border:"var(--aes-size-border)",header:"var(--aes-size-header)",headerText:"var(--aes-size-header-text)",label:"Time axis"}};function AqAesBox({theme,children}){let t=AQ_AES_THEMES[theme];return React.createElement("div",{style:{borderRadius:10,border:`1.5px solid ${t.border}`,background:t.bg,flex:"1 1 0",display:"flex",flexDirection:"column"}},React.createElement("div",{style:{background:t.header,padding:"8px 14px",borderRadius:"8px 8px 0 0"}},React.createElement("span",{style:{color:t.headerText,fontWeight:700,fontSize:12,textTransform:"uppercase",letterSpacing:"0.8px"}},t.label)),React.createElement("div",{style:{padding:"12px 14px",flex:1}},children))}function ConfigureStep({parsed,formula,setFormula,Kr,setKr,Ktr,setKtr,Kd,setKd,hillN,setHillN,vis,updVis,fileName,detectedSep,calData,columnEnabled,downloadCalibrated}){return React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:16,marginBottom:16,alignItems:"stretch"}},React.createElement(AqAesBox,{theme:"calibration"},React.createElement("div",{style:{display:"flex",gap:16,flexWrap:"wrap",alignItems:"flex-end"}},React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Formula"),React.createElement("select",{value:formula,onChange:e=>setFormula(e.target.value),className:"dv-select"},React.createElement("option",{value:"none"},"None (raw data)"),React.createElement("option",{value:"allen-blinks"},"Allen & Blinks (1978)"),React.createElement("option",{value:"hill"},"Hill equilibrium"),React.createElement("option",{value:"generalized"},"Generalised Allen & Blinks"))),(formula==="allen-blinks"||formula==="generalized")&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Kr"),React.createElement(NumberInput,{value:Kr,onChange:e=>setKr(Number(e.target.value)),step:"0.1"})),(formula==="allen-blinks"||formula==="generalized")&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Ktr"),React.createElement(NumberInput,{value:Ktr,onChange:e=>setKtr(Number(e.target.value)),step:"1"})),formula==="hill"&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Kd (\xB5M)"),React.createElement(NumberInput,{value:Kd,onChange:e=>setKd(Number(e.target.value)),step:"0.5",min:"0.1"})),formula==="generalized"&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"n (Hill exp.)"),React.createElement(NumberInput,{value:hillN,onChange:e=>setHillN(Number(e.target.value)),step:"0.5",min:"1"}))),React.createElement(CalibrationFormulaPreview,{formula,Kr,Ktr,Kd,hillN})),React.createElement(AqAesBox,{theme:"time"},React.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:12,alignItems:"flex-end"}},React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Time step (per row)"),React.createElement(NumberInput,{value:vis.timeStep,onChange:e=>updVis({timeStep:Number(e.target.value)||1}),style:{width:132},min:"0.001",step:"any"})),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Base unit"),React.createElement("select",{value:vis.baseUnit,onChange:e=>updVis({baseUnit:e.target.value}),className:"dv-select"},TIME_UNITS.map(u=>React.createElement("option",{key:u.key,value:u.key},u.label)))),parsed&&React.createElement("div",{style:{fontSize:12,color:"var(--text-faint)",paddingBottom:4}},"Range: 0 \u2013 ",(parsed.data.length*vis.timeStep).toFixed(3)," ",vis.baseUnit)))),React.createElement("div",{className:"dv-panel"},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:8}},React.createElement("p",{style:{margin:0,fontSize:13,color:"var(--text-muted)"}},"Loaded ",React.createElement("strong",{style:{color:"var(--text)"}},fileName)," \u2014"," ",parsed.headers.length," samples \xD7 ",parsed.data.length," time-points",React.createElement(DetectedSeparatorBadge,{sep:detectedSep})),React.createElement("button",{onClick:e=>{downloadCalibrated(),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),calData&&parsed&&(()=>{let ei=parsed.headers.map((_,i)=>i).filter(i=>columnEnabled[i]!==!1);return React.createElement("div",{style:{marginTop:8}},React.createElement("p",{style:{margin:"0 0 6px",fontSize:12,fontWeight:600,color:"var(--text-muted)"}},"Preview \u2014 ",formula==="none"?"raw data":"calibrated data"," \xB7 ",ei.length," of"," ",parsed.headers.length," columns (first 15 rows):"),React.createElement(DataPreview,{headers:ei.map(i=>parsed.headers[i]),rows:calData.slice(0,15).map(r=>ei.map(i=>r[i]!=null?r[i]:"")),maxRows:15}))})()))}var{forwardRef,useRef,useState,useEffect,memo}=React,Chart=memo(forwardRef(function({series,xStart,xEnd,yMin,yMax,vbW,vbH,xLabel,yLabel,plotBg,showGrid,lineWidth,ribbonOpacity,gridColor,svgLegend,plotTitle,plotSubtitle,onBrush},ref){let aequorinItemW=b=>{let maxLen=Math.max(0,...(b.items||[]).map(i=>(i.label||"").length));return Math.max(110,maxLen*6+28)},legendH=computeLegendHeight(svgLegend,vbW-MARGIN.left-MARGIN.right,aequorinItemW),topPad=(plotTitle?20:0)+(plotSubtitle?16:0),w=vbW-MARGIN.left-MARGIN.right,h=vbH-MARGIN.top-MARGIN.bottom,xRange=xEnd-xStart||1,yRange=yMax-yMin||1,sx=v=>MARGIN.left+(v-xStart)/xRange*w,sy=v=>MARGIN.top+(1-(v-yMin)/yRange)*h,clamp=v=>Math.max(yMin,Math.min(yMax,v)),xTicks=makeTicks(xStart,xEnd,8),yTicks=makeTicks(yMin,yMax,6),paths=series.map(s=>{let areaPts=s.rows.map(r=>({x:sx(r.t),yHi:r.mean!=null&&r.sd!=null?sy(clamp(r.mean+r.sd)):null,yLo:r.mean!=null&&r.sd!=null?sy(clamp(r.mean-r.sd)):null})),linePts=s.rows.map(r=>({x:sx(r.t),y:r.mean!=null?sy(r.mean):null}));return{prefix:s.prefix,color:s.color,areaD:buildAreaD(areaPts),lineD:buildLineD(linePts)}}),[brush,setBrush]=useState(null),plotY0=topPad+MARGIN.top,plotY1=plotY0+h,clientToVb=(svg,clientX,clientY)=>{let bb=svg.getBoundingClientRect(),scale=vbW/(bb.width||1),x=(clientX-bb.left)*scale,y=(clientY-bb.top)*scale;return{x:Math.max(MARGIN.left,Math.min(MARGIN.left+w,x)),y}},vbToData=vx=>xStart+(vx-MARGIN.left)/(w||1)*(xEnd-xStart),onBrushDown=e=>{if(!onBrush||e.button!==0)return;let emit=onBrush,svg=e.currentTarget,start=clientToVb(svg,e.clientX,e.clientY);if(start.yplotY1)return;e.preventDefault(),setBrush({x0:start.x,x1:start.x});let move=ev=>{setBrush({x0:start.x,x1:clientToVb(svg,ev.clientX,ev.clientY).x})},up=ev=>{window.removeEventListener("mousemove",move),window.removeEventListener("mouseup",up);let endX=clientToVb(svg,ev.clientX,ev.clientY).x;if(setBrush(null),Math.abs(endX-start.x)<4)return;let a=vbToData(start.x),b=vbToData(endX);emit(Math.min(a,b),Math.max(a,b))};window.addEventListener("mousemove",move),window.addEventListener("mouseup",up)};return React.createElement("svg",{ref,viewBox:`0 0 ${vbW} ${vbH+legendH+topPad}`,style:onBrush?{width:"100%",height:"auto",display:"block",cursor:"crosshair",userSelect:"none"}:{width:"100%",height:"auto",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"RLU timecourse chart",onMouseDown:onBrush?onBrushDown:void 0},React.createElement("title",null,plotTitle||"RLU timecourse chart"),React.createElement("desc",null,`Time series chart with ${series.length} series${xLabel?`, X: ${xLabel}`:""}${yLabel?`, Y: ${yLabel}`:""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:vbW,height:vbH+legendH+topPad,fill:plotBg||"#fff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:vbW/2,y:17,textAnchor:"middle",fontSize:"15",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:vbW/2,y:plotTitle?34:17,textAnchor:"middle",fontSize:"12",fill:"#888",fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"chart",transform:`translate(0, ${topPad})`},React.createElement("rect",{id:"plot-area-background",x:MARGIN.left,y:MARGIN.top,width:w,height:h,fill:plotBg||"#fff"}),showGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`gy-${t}`,x1:MARGIN.left,x2:MARGIN.left+w,y1:sy(t),y2:sy(t),stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"})),xTicks.map(t=>React.createElement("line",{key:`gx-${t}`,x1:sx(t),x2:sx(t),y1:MARGIN.top,y2:MARGIN.top+h,stroke:gridColor||"#e0e0e0",strokeWidth:"0.5"}))),React.createElement("g",{id:"ribbons"},paths.map(p=>p.areaD?React.createElement("path",{key:`area-${p.prefix}`,id:`ribbon-${svgSafeId(p.prefix)}`,d:p.areaD,fill:p.color,fillOpacity:ribbonOpacity,stroke:p.color,strokeOpacity:ribbonOpacity,strokeWidth:"0.5"}):null)),React.createElement("g",{id:"traces"},paths.map(p=>p.lineD?React.createElement("path",{key:`line-${p.prefix}`,id:`trace-${svgSafeId(p.prefix)}`,d:p.lineD,fill:"none",stroke:p.color,strokeWidth:lineWidth,role:"img","aria-label":`Trace: ${p.prefix}`}):null)),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{id:"plot-frame-top",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top}),React.createElement("line",{id:"plot-frame-right",x1:MARGIN.left+w,y1:MARGIN.top,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:MARGIN.left,y1:MARGIN.top+h,x2:MARGIN.left+w,y2:MARGIN.top+h}),React.createElement("line",{id:"plot-frame-left",x1:MARGIN.left,y1:MARGIN.top,x2:MARGIN.left,y2:MARGIN.top+h})),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:sx(t),x2:sx(t),y1:MARGIN.top+h,y2:MARGIN.top+h+5,stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:sx(t),y:MARGIN.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t)))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:MARGIN.left-5,x2:MARGIN.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"1"}),React.createElement("text",{x:MARGIN.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t%1===0?t:t.toFixed(1))))),xLabel&&React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:MARGIN.left+w/2,y:vbH-4,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},xLabel)),yLabel&&React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${MARGIN.top+h/2}) rotate(-90)`,textAnchor:"middle",fontSize:"13",fill:"#444",fontFamily:"sans-serif"},yLabel)),renderSvgLegend(svgLegend,vbH+10,MARGIN.left,vbW-MARGIN.left-MARGIN.right,aequorinItemW),brush&&Math.abs(brush.x1-brush.x0)>.5&&React.createElement("g",{id:"brush-selection",pointerEvents:"none"},React.createElement("rect",{x:Math.min(brush.x0,brush.x1),y:MARGIN.top,width:Math.abs(brush.x1-brush.x0),height:h,fill:"#648fff",fillOpacity:"0.15",stroke:"#648fff",strokeOpacity:"0.6",strokeWidth:"1"}))))})),InsetBarplot=memo(forwardRef(function({series,insetFillOpacity,insetBarWidth,insetBarGap,insetYMin,insetYMax,insetW,insetH,insetErrorType,insetShowBarOutline,insetBarOutlineColor,insetBarStrokeWidth,insetShowGrid,insetGridColor,insetErrorStrokeWidth,insetXFontSize,insetYFontSize,insetXLabelAngle,plotBg,plotTitle,plotSubtitle,corrected,replicateSums,annotations,statsSummary,showPoints,pointSize,pointColor},ref){let refIW=insetW||400,iH=insetH||200,_compact=(100-(insetBarGap??0))/100,topPad=(plotTitle?20:0)+(plotSubtitle?16:0),xAngle=insetXLabelAngle||0,absAngle=Math.abs(xAngle),annotPairs=annotations&&annotations.kind==="brackets"?assignBracketLevels(annotations.pairs||[]):[],annotMaxLevel=annotPairs.reduce((m,pr)=>Math.max(m,pr._level||0),0),annotTopPad=annotations&&annotations.kind==="cld"?22:annotations&&annotations.kind==="brackets"&&annotPairs.length>0?(annotMaxLevel+1)*20+6:0,STATS_LINE_H=11,STATS_FONT=8,summaryLines=statsSummary?statsSummary.split(` `):[],summaryH=summaryLines.length>0?summaryLines.length*STATS_LINE_H+14:0,M={top:24,right:24,bottom:60+(absAngle>0?absAngle*.8:0),left:62},iW=Math.max(M.left+M.right+40,Math.round((refIW-M.left-M.right)*_compact)+M.left+M.right),w=iW-M.left-M.right,h=iH-M.top-M.bottom,totalH=iH+summaryH,bars=series.map(s=>{let repData=replicateSums?replicateSums.find(r=>r.prefix===s.prefix):null,vals=repData&&repData.repSums.length>0?repData.repSums.map(r=>corrected?r.corrSum:r.rawSum):null,n=vals?vals.length:0,barMean=vals&&n>0?vals.reduce((a,b)=>a+b,0)/n:0,variance=vals&&n>1?vals.reduce((a,v)=>a+(v-barMean)**2,0)/(n-1):0,sd=Math.sqrt(variance),sem=n>1?sd/Math.sqrt(n):0,ci95=n>1&&typeof tinv=="function"?tinv(.975,n-1)*sem:0;return{label:s.label,prefix:s.prefix,fillColor:s.color,barMean,sd,sem,ci95,n,vals}}),errBars=bars.map(b=>insetErrorType==="sd"?b.sd:insetErrorType==="sem"?b.sem:insetErrorType==="ci95"?b.ci95:0),dataMax=Math.max(...bars.map((b,i)=>b.barMean+(errBars[i]||0)),.001),yMin2=insetYMin??0,yMax2=insetYMax??dataMax*1.15;annotTopPad>0&&h>annotTopPad+10&&(yMax2=yMin2+(yMax2-yMin2)*h/(h-annotTopPad));let yRange=yMax2-yMin2||1,bandW=w/bars.length,bx=i=>M.left+i*bandW+bandW/2,sy=v=>M.top+(1-(v-yMin2)/yRange)*h,yTicks=makeTicks(yMin2,yMax2,8),halfBar=(insetBarWidth!=null?insetBarWidth/100:.7)*bandW*.5,fOp=insetFillOpacity??.7;return React.createElement("svg",{ref,viewBox:`0 0 ${iW} ${totalH+topPad}`,style:{width:iW,maxWidth:"100%",height:"auto",display:"block",margin:"0 auto"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":plotTitle||"Bar plot"},React.createElement("title",null,plotTitle||"Inset bar plot"),React.createElement("desc",null,`Inset bar plot of ${series.length} condition${series.length!==1?"s":""}`),React.createElement("g",{id:"background"},React.createElement("rect",{x:0,y:0,width:iW,height:totalH+topPad,fill:plotBg||"#fff"})),plotTitle&&React.createElement("g",{id:"title"},React.createElement("text",{x:iW/2,y:15,textAnchor:"middle",fontSize:"11",fontWeight:"700",fill:"#222",fontFamily:"sans-serif"},plotTitle)),plotSubtitle&&React.createElement("g",{id:"subtitle"},React.createElement("text",{x:iW/2,y:plotTitle?28:15,textAnchor:"middle",fontSize:"9",fill:"#888",fontFamily:"sans-serif"},plotSubtitle)),React.createElement("g",{id:"chart",transform:`translate(0, ${topPad})`},React.createElement("rect",{id:"plot-area-background",x:M.left,y:M.top,width:w,height:h,fill:plotBg||"#fff"}),insetShowGrid&&React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:t,x1:M.left,x2:M.left+w,y1:sy(t),y2:sy(t),stroke:insetGridColor||"#e0e0e0",strokeWidth:"0.4"}))),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("g",{key:t},React.createElement("line",{x1:M.left-3,x2:M.left,y1:sy(t),y2:sy(t),stroke:"#333",strokeWidth:"0.5"}),React.createElement("text",{x:M.left-5,y:sy(t)+3,textAnchor:"end",fontSize:insetYFontSize||7,fill:"#555",fontFamily:"sans-serif"},t%1===0?t:t.toFixed(1))))),React.createElement("g",{id:"bars"},bars.map((b,i)=>{let val=b.barMean,barTop=sy(Math.min(val,yMax2)),baseline=sy(Math.max(0,yMin2)),errVal=errBars[i]||0,capW=halfBar*.4,hiVal=val+errVal,loVal=val-errVal,hiValC=Math.min(hiVal,yMax2),loValC=Math.max(loVal,yMin2),drawErrBar=insetErrorType!=="none"&&errVal>0&&hiValC>loValC,drawHiCap=drawErrBar&&hiVal<=yMax2,drawLoCap=drawErrBar&&loVal>=yMin2;return React.createElement("g",{key:b.prefix,id:`bar-${svgSafeId(b.prefix)}`},React.createElement("rect",{x:bx(i)-halfBar,y:barTop,width:Math.max(0,halfBar*2),height:Math.max(0,baseline-barTop),fill:b.fillColor,fillOpacity:fOp,stroke:insetShowBarOutline?insetBarOutlineColor||b.fillColor:"none",strokeWidth:insetShowBarOutline?insetBarStrokeWidth||1:0,rx:"1"}),drawErrBar&&React.createElement(React.Fragment,null,React.createElement("line",{x1:bx(i),x2:bx(i),y1:sy(hiValC),y2:sy(loValC),stroke:"#333",strokeWidth:insetErrorStrokeWidth}),drawHiCap&&React.createElement("line",{x1:bx(i)-capW,x2:bx(i)+capW,y1:sy(hiValC),y2:sy(hiValC),stroke:"#333",strokeWidth:insetErrorStrokeWidth}),drawLoCap&&React.createElement("line",{x1:bx(i)-capW,x2:bx(i)+capW,y1:sy(loValC),y2:sy(loValC),stroke:"#333",strokeWidth:insetErrorStrokeWidth})),showPoints&&b.vals&&b.vals.map((v,vi)=>{let jitter=(seededRandom(i*1e3+vi+42)()-.5)*halfBar*1.2;return React.createElement("circle",{key:`pt-${i}-${vi}`,cx:bx(i)+jitter,cy:sy(v),r:pointSize||3,fill:pointColor||"#333",fillOpacity:.6,stroke:pointColor||"#333",strokeOpacity:.75,strokeWidth:"0.3"})}),absAngle===0?React.createElement("text",{x:bx(i),y:M.top+h+12,textAnchor:"middle",fontSize:insetXFontSize||7,fill:"#333",fontFamily:"sans-serif",fontWeight:"600"},b.label):React.createElement("text",{x:bx(i),y:M.top+h+10,transform:`rotate(${xAngle}, ${bx(i)}, ${M.top+h+10})`,textAnchor:"end",dominantBaseline:"middle",fontSize:insetXFontSize||7,fill:"#333",fontFamily:"sans-serif",fontWeight:"600"},b.label))})),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"0.5"},React.createElement("line",{id:"plot-frame-top",x1:M.left,y1:M.top,x2:M.left+w,y2:M.top}),React.createElement("line",{id:"plot-frame-right",x1:M.left+w,y1:M.top,x2:M.left+w,y2:M.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:M.left,y1:M.top+h,x2:M.left+w,y2:M.top+h}),React.createElement("line",{id:"plot-frame-left",x1:M.left,y1:M.top,x2:M.left,y2:M.top+h})),annotations&&annotations.kind==="cld"&&annotations.labels&&React.createElement(CldLabels,{labels:annotations.labels.map((lbl,i)=>i>=bars.length?null:lbl),axisCoord:bx,crossCoord:M.top+15,orientation:"vertical-top"}),annotations&&annotations.kind==="brackets"&&React.createElement(SignificanceBrackets,{pairs:annotPairs,axisCoord:bx,baseline:M.top+annotTopPad-6,orientation:"vertical-top"}),React.createElement("g",{id:"y-axis-label"},React.createElement("text",{transform:`translate(14,${M.top+h/2}) rotate(-90)`,textAnchor:"middle",fontSize:insetYFontSize||12,fill:"#444",fontFamily:"sans-serif"},corrected?"\u03A3 (baseline-corrected)":"\u03A3 (raw)"))),summaryLines.length>0&&React.createElement("g",{id:"stats-summary"},summaryLines.map((line,i)=>React.createElement("text",{key:`ss-${i}`,x:M.left,y:iH+topPad+10+i*STATS_LINE_H,fontSize:STATS_FONT,fill:"#aaa",fontFamily:"monospace"},line))))})),FacetChartItem=memo(function({s,facetRefs,chartProps}){let localRef=useRef(null);return useEffect(()=>{let refs=facetRefs.current;return refs[s.prefix]=localRef.current,()=>{delete refs[s.prefix]}},[s.prefix,facetRefs]),React.createElement("div",{className:"dv-plot-card",style:{background:"var(--plot-card-bg)",borderRadius:8,padding:12,border:"1px solid var(--plot-card-border)"}},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,fontWeight:600,color:s.color}},s.label," ",React.createElement("span",{style:{fontSize:11,fontWeight:400,color:"var(--text-faint)"}},"number of repeats used = ",s.n)),React.createElement(Chart,{ref:localRef,...chartProps}))});var TEST_LABELS_AQ=Object.fromEntries(Object.entries(STATS_TEST_REGISTRY).map(([id,entry])=>[id,entry.label])),POSTHOC_LABELS_AQ=Object.fromEntries(Object.entries(STATS_POSTHOC_REGISTRY).map(([id,entry])=>[id,entry.label])),TEST_OPTIONS_AQ_2=STATS_TESTS_FOR_K2,TEST_OPTIONS_AQ_K=STATS_TESTS_FOR_K,AQ_ERROR_BAR_LABELS={none:"None",sd:"SD",sem:"SEM",ci95:"95% CI"},numCast=v=>v;function formatAqStatShort(testName,res){if(!testName||!res||res.error)return"\u2014";if(testName==="studentT"||testName==="welchT")return`t(${numCast(res.df).toFixed(2)}) = ${numCast(res.t).toFixed(3)}`;if(testName==="mannWhitney")return`U = ${numCast(res.U).toFixed(1)}`;if(testName==="oneWayANOVA"||testName==="welchANOVA"){let df2=typeof res.df2=="number"?res.df2.toFixed(2):res.df2;return`F(${res.df1}, ${df2}) = ${numCast(res.F).toFixed(3)}`}return testName==="kruskalWallis"?`H(${numCast(res.df)}) = ${numCast(res.H).toFixed(3)}`:"\u2014"}function formatAqResultLine(testName,res){if(!testName||!res)return"\u2014";if(res.error)return"\u26A0 "+res.error;if(testName==="studentT"||testName==="welchT")return`t(${numCast(res.df).toFixed(2)}) = ${numCast(res.t).toFixed(3)}, p = ${formatP(res.p)}`;if(testName==="mannWhitney")return`U = ${numCast(res.U).toFixed(1)}, z = ${numCast(res.z).toFixed(3)}, p = ${formatP(res.p)}`;if(testName==="oneWayANOVA"||testName==="welchANOVA"){let df2=typeof res.df2=="number"?res.df2.toFixed(2):res.df2;return`F(${res.df1}, ${df2}) = ${numCast(res.F).toFixed(3)}, p = ${formatP(res.p)}`}return testName==="kruskalWallis"?`H(${numCast(res.df)}) = ${numCast(res.H).toFixed(3)}, p = ${formatP(res.p)}`:"\u2014"}function computeAqAnnotationSpec(row,displayMode,showNs){if(displayMode==="none"||!row||row.skip)return null;let{k,names,testResult,postHocResult}=row;if(k<2)return null;if(k===2){let p=testResult&&!testResult.error?testResult.p:null;return p==null||!showNs&&p>=.05?null:{kind:"brackets",pairs:[{i:0,j:1,p,label:pStars(p)}],groupNames:names}}if(!postHocResult||postHocResult.error)return null;if(displayMode==="cld")return{kind:"cld",labels:compactLetterDisplay(postHocResult.pairs,k),groupNames:names};let pairs=postHocResult.pairs.map(pr=>({i:pr.i,j:pr.j,p:pr.pAdj!=null?pr.pAdj:pr.p})).map(pr=>({...pr,label:pStars(pr.p)})).filter(pr=>showNs||pr.p<.05);return pairs.length===0?null:{kind:"brackets",pairs,groupNames:names}}function summariseAqNormality(norm){if(!Array.isArray(norm)||norm.length===0)return"\u2014";let hasTrue=!1,hasFalse=!1;for(let r of norm)r.normal===!0?hasTrue=!0:r.normal===!1&&(hasFalse=!0);return hasFalse?"no":hasTrue?"yes":"\u2014"}function summariseAqEqualVariance(lev){return!lev||"error"in lev||lev.F==null?"\u2014":lev.equalVar?"yes":"no"}function computeAqSummaryText(row,showSummary,errorBarLabel){if(!showSummary||!row||row.skip)return null;let{chosenTest,testResult,k,postHocName,rec}=row;if(!chosenTest||!testResult||testResult.error)return null;let lines=[`Normality: ${summariseAqNormality(rec?.normality)}`,`Equal variance: ${summariseAqEqualVariance(rec?.levene)}`,`Test: ${TEST_LABELS_AQ[chosenTest]||chosenTest}`];return k>2&&postHocName&&lines.push(`Post-hoc: ${POSTHOC_LABELS_AQ[postHocName]||postHocName}`),errorBarLabel&&lines.push(`Error bars: ${errorBarLabel}`),lines.join(` `)}function buildAqSetTextBlock(row){let lines=[],names=row.names,values=row.values,res=row.testResult??{};lines.push("Groups:");for(let i=0;i1?sampleSD(vs):0,sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0,semStr=n>1?sem.toFixed(3):"\u2014",ciStr=n>1?`\xB1${ci95.toFixed(3)}`:"\u2014";lines.push(` ${names[i]}: n=${n}, mean=${mean.toFixed(3)}, SD=${sd.toFixed(3)}, SEM=${semStr}, 95% CI=${ciStr}`)}lines.push("");let rec=row.rec,recTest=rec?.recommendation?.test,reason=rec?.recommendation?.reason;lines.push(`Test: ${row.chosenTest?TEST_LABELS_AQ[row.chosenTest]||row.chosenTest:"\u2014"}`),reason&&lines.push(`Reason: ${reason}`),res.error?lines.push(`Result: \u26A0 ${res.error}`):row.chosenTest&&lines.push(`Result: ${formatAqResultLine(row.chosenTest,res)}`),recTest&&recTest!==row.chosenTest&&lines.push(` (Toolbox recommended ${TEST_LABELS_AQ[recTest]||recTest})`),lines.push("");let norm=rec?.normality??[];if(norm.length>0){let parts=norm.map(r=>{let label=names[r.group]||`g${r.group}`,verdict=r.normal===!0?"normal":r.normal===!1?"not normal":"\u2014";return`${label}: ${verdict}`});lines.push(`Shapiro-Wilk: ${parts.join("; ")}`)}let lev=rec?.levene??{};lev.F!=null&&lines.push(`Levene: F(${lev.df1}, ${lev.df2}) = ${lev.F.toFixed(3)}, p = ${formatP(lev.p)} \u2192 ${lev.equalVar?"equal variance":"unequal variance"}`);let postHoc=row.postHocResult;if(names.length>=3&&postHoc&&!postHoc.error&&row.postHocName){lines.push(""),lines.push(`Post-hoc \u2014 ${POSTHOC_LABELS_AQ[row.postHocName]||row.postHocName}:`);for(let pr of postHoc.pairs){let p=pr.pAdj!=null?pr.pAdj:pr.p,diff=pr.diff!=null?pr.diff.toFixed(3):pr.z!=null?`z=${pr.z.toFixed(3)}`:"\u2014";lines.push(` ${names[pr.i]} vs ${names[pr.j]}: ${diff}, p = ${formatP(p)} ${pStars(p)}`)}}let power=row.powerResult;if(power){lines.push("");let ciStr=power.effectCI?`, 95% CI [${power.effectCI.lo.toFixed(3)}, ${power.effectCI.hi.toFixed(3)}]`:"";lines.push(`Replication planning: ${power.effectLabel} = ${power.effect.toFixed(3)}${ciStr}`);for(let pr of power.rows){let nStr=pr.nForTarget!=null?`${pr.nForTarget} ${power.nLabel}`:"> 5000";lines.push(` \u03B1=${pr.alpha}: n for 80% power = ${nStr}`)}power.approximate&&lines.push(" (rank-based test \u2014 estimated from parametric analog)")}return lines.push(""),lines.join(` `)}function buildAqAggregateReport(rows){return["RLU timecourse \u2014 statistical analysis","Generated: "+new Date().toISOString().replace(/\.\d{3}Z$/,"Z"),""].join(` @@ -1207,4 +1207,4 @@ import{makeTicks}from"./chunk-T4V5V5TK.js";import{ActionsPanel,BaseStyleControls 2536 1204 523 3140 2280 910 854 2551 1240 526 3174 2319 887 886`;function App(){let shell=usePlotToolState("aequorin",VIS_INIT_AEQUORIN),{step,setStep,fileName,setFileName,sepOverride,setSepOverride,setCommaFixed,setCommaFixCount,setInjectionWarning,vis,updVis}=shell,[parseMessage,setParseMessage]=useState4(null),[detectedSep,setDetectedSep]=useState4(""),[rawText,setRawText]=useState4(null),[formula,setFormula]=useState4("none"),[Kr,setKr]=useState4(DEFAULT_KR),[Ktr,setKtr]=useState4(DEFAULT_KTR),[Kd,setKd]=useState4(DEFAULT_KD),[hillN,setHillN]=useState4(DEFAULT_HILL_N),[conditions,setConditions]=useState4([]),[poolReplicates,setPoolReplicates]=useState4(!0),[columnEnabled,setColumnEnabled]=useState4({}),parsed=useMemo3(()=>rawText?parseData(rawText):null,[rawText]),calData=useMemo3(()=>parsed?formula==="none"?parsed.data:formula==="hill"?calibrateHill(parsed.headers,parsed.data,Kd):formula==="generalized"?calibrateGeneralized(parsed.headers,parsed.data,Kr,Ktr,hillN):calibrate(parsed.headers,parsed.data,Kr,Ktr):null,[parsed,formula,Kr,Ktr,Kd,hillN]),conditionsNumericKey=conditions.map(c=>`${c.prefix}:${(c.activeColIndices||c.colIndices).join(",")}`).join("|"),numericStatsByPrefix=useMemo3(()=>{if(!calData||!parsed||conditions.length===0)return{};let nRows=calData.length,out={};for(let cond of conditions){let idxs=cond.activeColIndices||cond.colIndices,means=[],sds=[];for(let r=0;rcalData[r][i]).filter(v=>v!=null);if(vals.length===0){means.push(null),sds.push(null);continue}let m=vals.reduce((a,b)=>a+b,0)/vals.length;means.push(m),sds.push(vals.length<2?0:Math.sqrt(vals.reduce((a,v)=>a+(v-m)**2,0)/(vals.length-1)))}out[cond.prefix]={means,sds}}return out},[calData,parsed,conditionsNumericKey]),stats=useMemo3(()=>conditions.map(cond=>({...cond,...numericStatsByPrefix[cond.prefix]||{means:[],sds:[]}})),[conditions,numericStatsByPrefix]),replicateSumsByPrefix=useMemo3(()=>{if(!calData||conditions.length===0)return{};let r0=Math.max(0,Math.floor(vis.xStart)),r1=Math.min(calData.length-1,Math.ceil(vis.xEnd)),out={};for(let cond of conditions){let repSums=(cond.activeColIndices||cond.colIndices).map(ci=>{let vals=[];for(let r=r0;r<=r1;r++){let v=calData[r]?calData[r][ci]:null;v!=null&&vals.push(v)}let rawSum=vals.reduce((a,b)=>a+b,0),minVal=vals.length>0?Math.min(...vals):0,corrSum=rawSum-vals.length*minVal;return{colIndex:ci,rawSum,corrSum}});out[cond.prefix]=repSums}return out},[calData,conditionsNumericKey,vis.xStart,vis.xEnd]),replicateSums=useMemo3(()=>stats.map(s=>({prefix:s.prefix,label:s.label,repSums:replicateSumsByPrefix[s.prefix]||[]})),[stats,replicateSumsByPrefix]),effYRange=useMemo3(()=>vis.autoYRange?computeAutoYRange(ribbonEdgeMatrix(stats),vis.xStart,vis.xEnd)??{yMin:vis.yMin,yMax:vis.yMax}:{yMin:vis.yMin,yMax:vis.yMax},[vis.autoYRange,vis.yMin,vis.yMax,vis.xStart,vis.xEnd,stats]);React.useEffect(()=>{vis.autoYRange&&(effYRange.yMin===vis.yMin&&effYRange.yMax===vis.yMax||updVis({yMin:effYRange.yMin,yMax:effYRange.yMax}))},[effYRange,vis.autoYRange,vis.yMin,vis.yMax,updVis]);let colInfo=useMemo3(()=>{if(!parsed)return[];let nameOcc={},nameCount={};return parsed.headers.forEach(h=>{nameCount[h]=(nameCount[h]||0)+1}),parsed.headers.map((h,i)=>(nameOcc[h]=(nameOcc[h]||0)+1,{h,i,rep:nameOcc[h],isDup:nameCount[h]>1}))},[parsed]),applyGrouping=(pool,ce,prevConds)=>{let prevMap=Object.fromEntries(prevConds.map(c=>[c.prefix,c])),allConds=detectConditions(parsed.headers,pool,null,vis.discretePalette||"okabe-ito").map(c=>{let activeCols=c.colIndices.filter(ci=>ce[ci]!==!1),prev=prevMap[c.prefix],prevWasForcedOff=prev&&(prev.activeColIndices?prev.activeColIndices.length===0:!1),enabled=activeCols.length>0&&(prev&&!prevWasForcedOff?prev.enabled:!0);return{...c,activeColIndices:activeCols,enabled,label:prev?.label??c.label,color:prev?.color??c.color}});setConditions(allConds)},handlePoolChange=pool=>{setPoolReplicates(pool),applyGrouping(pool,columnEnabled,conditions)},handleColumnToggle=(i,val)=>{let ce={...columnEnabled,[i]:val};setColumnEnabled(ce),applyGrouping(poolReplicates,ce,conditions)},handleConditionsChange=newConds=>{let ce={...columnEnabled},updated=newConds.map((c,idx)=>{let prev=conditions[idx];return prev&&c.enabled!==prev.enabled?(c.colIndices.forEach(ci=>{ce[ci]=!!c.enabled}),{...c,activeColIndices:c.enabled?c.colIndices:[]}):c});setConditions(updated),setColumnEnabled(ce)},plotPanelRef=useRef4(null),doParse=useCallback2((text,sep)=>{let resolved=autoDetectSep(text,sep),effectiveSep=typeof resolved=="string"?resolved:"";setDetectedSep(effectiveSep);let dc=fixDecimalCommas(text,effectiveSep);setCommaFixed(dc.commaFixed),setCommaFixCount(dc.count),setRawText(dc.text);let{headers,data,injectionWarnings}=parseData(dc.text,effectiveSep);if(setInjectionWarning(injectionWarnings),!headers.length||!data.length){setParseMessage("The file appears to be empty or has no data rows. Please check your file and try again.");return}if(headers.length===1){setParseMessage("Only one column detected \u2014 this tool expects wide-format data with one column per sample. Check your separator setting or file format.");return}let totalCells=data.length*headers.length,numericCells=data.reduce((n,row)=>n+row.filter(v=>v!=null).length,0);if((totalCells>0?numericCells/totalCells:0)<.3){setParseMessage("Less than 30% of values are numeric. This tool expects a numeric matrix (one column per sample, one row per time-point). Your file may be in long format or contain mostly text.");return}let colTypes=headers.map((_,ci)=>data.filter(r=>r[ci]!=null).length/data.length>.8?"num":"text"),numCols=colTypes.filter(t=>t==="num").length,textCols=colTypes.filter(t=>t==="text").length,warnings=[];headers.length<=3&&textCols>=1&&numCols>=1&&warnings.push("\u26A0\uFE0F This looks like it could be long-format data (few columns, mix of text and numbers). This tool expects wide format \u2014 one column per sample, one row per time-point.");let colLengths=headers.map((_,ci)=>data.filter(r=>r[ci]!=null).length),maxLen=Math.max(...colLengths),minLen=Math.min(...colLengths);maxLen>0&&minLen0?warnings.join(` `):null);let ce={};headers.forEach((_,i)=>{ce[i]=!0}),setColumnEnabled(ce),setPoolReplicates(!0);let detectedConds=detectConditions(headers,!0,ce,vis.discretePalette||"okabe-ito").map(c=>({...c,enabled:!0}));setConditions(detectedConds),updVis({xStart:0,xEnd:data.length,faceted:!1,autoYRange:!0}),setStep("configure")},[setCommaFixed,setCommaFixCount,setInjectionWarning,setStep,updVis,vis.discretePalette]),handleFileLoad=useCallback2((text,name)=>{setFileName(name),doParse(text,sepOverride)},[sepOverride,doParse,setFileName]),handleTextPaste=useCallback2((text,name)=>{setFileName(name),setSepOverride(""),doParse(text,"")},[doParse,setFileName,setSepOverride]),loadExample=useCallback2(()=>{let text=EXAMPLE_TSV;if(!text){setParseMessage("Example dataset not loaded. Please try uploading a file instead.");return}setSepOverride(""),setFileName("rlu_timecourse_example.tsv"),doParse(text,"")},[doParse,setFileName,setSepOverride]),resetAll=()=>{setRawText(null),setFileName(""),setInjectionWarning(null),setStep("upload")},downloadCalibrated=()=>{if(!calData||!parsed)return;let enabledIdx=parsed.headers.map((_,i)=>i).filter(i=>columnEnabled[i]!==!1),headers=enabledIdx.map(i=>parsed.headers[i]),rows=calData.map(r=>enabledIdx.map(i=>r[i]!=null?r[i]:""));downloadCsv(headers,rows,`${fileBaseName(fileName,"rlu_timecourse")}_calibrated.csv`)};return React.createElement(PlotToolShell,{state:shell,toolName:"aequorin",title:"RLU Timecourse",visInit:VIS_INIT_AEQUORIN,steps:["upload","configure","plot"],canNavigate:s=>s==="upload"||!!parsed&&s!=="upload"},parseMessage&&React.createElement("div",{style:{marginBottom:16,padding:"10px 14px",borderRadius:8,background:parseMessage.startsWith("\u26A0\uFE0F")?"var(--warning-bg)":"var(--danger-bg)",border:`1px solid ${parseMessage.startsWith("\u26A0\uFE0F")?"var(--warning-border)":"var(--danger-border)"}`,display:"flex",alignItems:"flex-start",gap:8}},!parseMessage.startsWith("\u26A0\uFE0F")&&React.createElement("span",{style:{fontSize:16}},"\u{1F6AB}"),React.createElement("span",{style:{fontSize:12,color:parseMessage.startsWith("\u26A0\uFE0F")?"var(--warning-text)":"var(--danger-text)",fontWeight:600,whiteSpace:"pre-line"}},parseMessage)),step==="upload"&&React.createElement(UploadStep,{sepOverride,setSepOverride,rawText,doParse,handleFileLoad,handleTextPaste,onLoadExample:loadExample}),step==="configure"&&parsed&&React.createElement(ConfigureStep,{parsed,formula,setFormula,Kr,setKr,Ktr,setKtr,Kd,setKd,hillN,setHillN,vis,updVis,fileName,detectedSep,calData,columnEnabled,downloadCalibrated}),step==="plot"&&parsed&&calData&&React.createElement("div",null,React.createElement("div",{style:{display:"flex",gap:20,alignItems:"flex-start"}},React.createElement(PlotControls,{conditions,setConditions:handleConditionsChange,vis,updVis,plotPanelRef,downloadCalibrated,resetAll}),React.createElement("div",{className:"dv-aeq-chart-area",style:{flex:1,minWidth:0,position:"relative"}},React.createElement("div",{className:"dv-aeq-toggles-group"},React.createElement("div",{style:{display:"flex",flexDirection:"column",alignItems:"flex-end"}},React.createElement("span",{style:{fontSize:9,fontWeight:700,letterSpacing:"0.04em",textTransform:"uppercase",color:"var(--text-faint)",marginBottom:2}},"Series"),React.createElement("div",{role:"group","aria-label":"Series definition",style:{display:"inline-flex",border:"1px solid var(--step-active-border)",borderRadius:8,overflow:"hidden",background:"var(--surface)"}},React.createElement("button",{onClick:()=>handlePoolChange(!0),"aria-pressed":poolReplicates,style:{padding:"6px 14px",fontSize:12,fontWeight:600,fontFamily:"inherit",cursor:"pointer",background:poolReplicates?"var(--step-active-bg)":"transparent",color:poolReplicates?"var(--on-accent)":"var(--text-faint)",border:"none",borderRight:"1px solid var(--step-active-border)"}},"Pool by name"),React.createElement("button",{onClick:()=>handlePoolChange(!1),"aria-pressed":!poolReplicates,style:{padding:"6px 14px",fontSize:12,fontWeight:600,fontFamily:"inherit",cursor:"pointer",background:poolReplicates?"transparent":"var(--step-active-bg)",color:poolReplicates?"var(--text-faint)":"var(--on-accent)",border:"none"}},"Individual"))),React.createElement("div",{style:{display:"flex",flexDirection:"column",alignItems:"flex-end"}},React.createElement("span",{style:{fontSize:9,fontWeight:700,letterSpacing:"0.04em",textTransform:"uppercase",color:"var(--text-faint)",marginBottom:2}},"Layout"),React.createElement("div",{role:"group","aria-label":"Plot layout",style:{display:"inline-flex",border:"1px solid var(--step-active-border)",borderRadius:8,overflow:"hidden",background:"var(--surface)"}},React.createElement("button",{onClick:()=>updVis({faceted:!1}),"aria-pressed":!vis.faceted,style:{padding:"6px 14px",fontSize:12,fontWeight:600,fontFamily:"inherit",cursor:"pointer",background:vis.faceted?"transparent":"var(--step-active-bg)",color:vis.faceted?"var(--text-faint)":"var(--on-accent)",border:"none",borderRight:"1px solid var(--step-active-border)"}},"Combined"),React.createElement("button",{onClick:()=>updVis({faceted:!0}),"aria-pressed":vis.faceted,style:{padding:"6px 14px",fontSize:12,fontWeight:600,fontFamily:"inherit",cursor:"pointer",background:vis.faceted?"var(--step-active-bg)":"transparent",color:vis.faceted?"var(--on-accent)":"var(--text-faint)",border:"none"}},"Faceted")))),React.createElement("div",{style:{position:"sticky",top:0,zIndex:20,marginBottom:10,display:"flex",alignItems:"flex-start",gap:12,width:"fit-content"}},React.createElement(SampleSelectionOverlay,{showColumnOverlay:vis.showColumnOverlay,setShowColumnOverlay:v=>updVis({showColumnOverlay:v}),poolReplicates,colInfo,columnEnabled,handleColumnToggle,conditions})),React.createElement(PlotPanel,{ref:plotPanelRef,stats,xStart:vis.xStart,xEnd:vis.xEnd,yMin:effYRange.yMin,yMax:effYRange.yMax,faceted:vis.faceted,title:vis.plotTitle,subtitle:vis.plotSubtitle,smoothWidth:vis.smoothWidth,formula,replicateSums,fileName,plotBg:vis.plotBg,showGrid:vis.showGrid,lineWidth:vis.lineWidth,ribbonOpacity:vis.ribbonOpacity,gridColor:vis.gridColor,plotHeight:vis.plotHeight,timeStep:vis.timeStep,baseUnit:vis.baseUnit,displayUnit:vis.displayUnit,showInset:vis.showInset,insetFillOpacity:vis.insetFillOpacity,insetBarWidth:vis.insetBarWidth,insetBarGap:vis.insetBarGap,insetYMin:vis.insetYMinCustom!==""?Number(vis.insetYMinCustom):null,insetYMax:vis.insetYMaxCustom!==""?Number(vis.insetYMaxCustom):null,insetW:vis.insetW,insetH:vis.insetH,insetErrorType:vis.insetErrorType,insetShowBarOutline:vis.insetShowBarOutline,insetBarOutlineColor:vis.insetBarOutlineColor,insetBarStrokeWidth:vis.insetBarStrokeWidth,insetShowGrid:vis.insetShowGrid,insetGridColor:vis.insetGridColor,insetErrorStrokeWidth:vis.insetErrorStrokeWidth,insetXFontSize:vis.insetXFontSize,insetYFontSize:vis.insetYFontSize,insetXLabelAngle:vis.insetXLabelAngle,insetShowPoints:vis.insetShowPoints,insetPointSize:vis.insetPointSize,insetPointColor:vis.insetPointColor,onXRangeChange:(xStartRow,xEndRow)=>updVis({xStart:xStartRow,xEnd:xEndRow}),onResetXRange:()=>updVis({xStart:0,xEnd:calData.length,autoYRange:!0}),xZoomed:vis.xStart>0||vis.xEndN)return NaN;p*=n_i/N}for(let n_j of outsideSizes){if(!Number.isFinite(n_j)||n_j<0||n_j>N)return NaN;p*=1-n_j/N}return N*p}function multisetExclusiveP(xObs,insideSizes,outsideSizes,N,opts){if(!Number.isFinite(N)||N<=0)return NaN;let Nint=Math.floor(N),expected=multisetExclusiveExpected(insideSizes,outsideSizes,Nint);if(!Number.isFinite(expected))return NaN;let p=expected/Nint;return(opts&&opts.tail==="lower"?"lower":"upper")==="upper"?xObs<=0?1:xObs>Nint||p<=0?0:p>=1?1:betai(xObs,Nint-xObs+1,p):xObs<0?0:xObs>=Nint||p<=0?1:p>=1?0:betai(Nint-xObs,xObs+1,1-p)}export{multisetExclusiveExpected,multisetExclusiveP}; -//# sourceMappingURL=chunk-J6277AEU.js.map +import{betai}from"./chunk-HFTDMS7M.js";function multisetExclusiveExpected(insideSizes,outsideSizes,N){if(!Number.isFinite(N)||N<=0)return NaN;let p=1;for(let n_i of insideSizes){if(!Number.isFinite(n_i)||n_i<0||n_i>N)return NaN;p*=n_i/N}for(let n_j of outsideSizes){if(!Number.isFinite(n_j)||n_j<0||n_j>N)return NaN;p*=1-n_j/N}return N*p}function multisetExclusiveP(xObs,insideSizes,outsideSizes,N,opts){if(!Number.isFinite(N)||N<=0)return NaN;let Nint=Math.floor(N),expected=multisetExclusiveExpected(insideSizes,outsideSizes,Nint);if(!Number.isFinite(expected))return NaN;let p=expected/Nint;return(opts&&opts.tail==="lower"?"lower":"upper")==="upper"?xObs<=0?1:xObs>Nint||p<=0?0:p>=1?1:betai(xObs,Nint-xObs+1,p):xObs<0?0:xObs>=Nint||p<=0?1:p>=1?0:betai(Nint-xObs,xObs+1,1-p)}export{multisetExclusiveExpected,multisetExclusiveP}; +//# sourceMappingURL=chunk-DT6WX6AK.js.map diff --git a/tools/_app/chunks/chunk-J6277AEU.js.map b/tools/_app/chunks/chunk-DT6WX6AK.js.map similarity index 100% rename from tools/_app/chunks/chunk-J6277AEU.js.map rename to tools/_app/chunks/chunk-DT6WX6AK.js.map diff --git a/tools/_app/chunks/chunk-5F6S3FYW.js b/tools/_app/chunks/chunk-HFTDMS7M.js similarity index 98% rename from tools/_app/chunks/chunk-5F6S3FYW.js rename to tools/_app/chunks/chunk-HFTDMS7M.js index 4251ad8f..6adc87e6 100644 --- a/tools/_app/chunks/chunk-5F6S3FYW.js +++ b/tools/_app/chunks/chunk-HFTDMS7M.js @@ -17,8 +17,8 @@ summary(fit)`:chosenTest==="welchANOVA"?call="oneway.test(value ~ group, data = `)+` `;lines.push(call+"(");let body=_pwrCallBody(testKey,s);for(let i=0;ionSepChange(e.target.value),className:"dv-select-sep"},h8("option",{value:""},"\u2014 Select \u2014"),h8("option",{value:","},"Comma (,)"),h8("option",{value:";"},"Semicolon (;)"),h8("option",{value:" "},"Tab (\\t)"),h8("option",{value:" "},"Space"))),sepOverride?h8(FileDropZone,{onFileLoad,accept:".csv,.tsv,.txt,.dat,.tab",hint:hint||"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 2 MB max"}):h8("div",{style:{border:"2px dashed var(--border-strong)",borderRadius:12,padding:"48px 24px",textAlign:"center",background:"var(--surface-sunken)",opacity:.5}},h8("div",{style:{fontSize:40,marginBottom:8},"aria-hidden":"true"},"\u{1F6AB}"),h8("p",{style:{margin:0,fontSize:15,color:"var(--text-faint)"}},"Pick a column separator above to enable file loading")),onLoadExample?h8("div",{style:{marginTop:12,display:"flex",alignItems:"center",justifyContent:"center",gap:8,fontSize:12,color:"var(--text-muted)"}},h8("span",null,"Try sample data:"),h8("button",{type:"button",className:"dv-btn dv-btn-secondary",onClick:onLoadExample,"data-testid":"load-example"},exampleLabel||"Load example \u2192")):null)}function AutoDetectUploadPanel(props){let{sepOverride,onSepChange,onFileLoad,onLoadExample,exampleLabel,exampleSummary,hint,onTextPaste}=props,[overrideOpen,setOverrideOpen]=useState6(sepOverride!==""),[pasteText,setPasteText]=useState6(""),[pasteError,setPasteError]=useState6(null),[pasteWarn,setPasteWarn]=useState6(null),renderSamplePromo=()=>{if(!onLoadExample)return null;let summary=exampleSummary??{title:"Try a sample dataset",subtitle:typeof exampleLabel=="string"?exampleLabel:void 0},buttonLabel=summary.buttonLabel??"Plot this example \u2192";return React.createElement("div",{style:{marginBottom:16,padding:"14px 18px",background:"var(--success-bg)",borderRadius:12,border:"1.5px solid var(--success-border)",display:"flex",alignItems:"center",gap:14},"data-testid":"sample-promo"},React.createElement("span",{style:{display:"inline-flex",alignItems:"center",lineHeight:1,flexShrink:0,opacity:.7},"aria-hidden":"true"},summary.icon??React.createElement(DatasheetIcon,{size:36})),React.createElement("div",{style:{flex:1,minWidth:0}},React.createElement("div",{style:{fontSize:10,fontWeight:700,textTransform:"uppercase",letterSpacing:"0.8px",color:"var(--success-text)",opacity:.75,marginBottom:2}},"New here? Quick start"),React.createElement("div",{style:{fontSize:14,fontWeight:700,color:"var(--success-text)"}},summary.title),summary.subtitle&&React.createElement("div",{style:{fontSize:12,color:"var(--success-text)",opacity:.85,marginTop:2}},summary.subtitle)),React.createElement("button",{type:"button",onClick:onLoadExample,className:"dv-btn dv-btn-primary",style:{flexShrink:0,fontSize:13,fontWeight:700,padding:"8px 14px"},"data-testid":"load-example"},buttonLabel))},submitPaste=()=>{setPasteError(null),setPasteWarn(null);let text=pasteText;if(!text||text.trim()===""){setPasteError("Paste some data first \u2014 copy a selection from Excel, Sheets, or any CSV.");return}let bytes=new Blob([text]).size;if(bytes>FILE_LIMIT_BYTES){setPasteError(`Pasted data too large (${(bytes/1024/1024).toFixed(1)} MB). Maximum is 2 MB \u2014 split the data or sample rows and try again.`);return}bytes>FILE_WARN_BYTES&&setPasteWarn(`Large paste (${(bytes/1024/1024).toFixed(1)} MB) \u2014 parsing may take a moment.`),onTextPaste&&onTextPaste(text,"pasted_data.csv")},cardWrap={borderRadius:12,border:"1.5px solid var(--border)",background:"var(--surface)",overflow:"hidden",display:"flex",flexDirection:"column"},cardHeader={background:"var(--surface-subtle)",padding:"10px 14px",borderBottom:"1px solid var(--border)",display:"flex",alignItems:"center",gap:8},cardHeaderLabel={fontSize:12,fontWeight:700,color:"var(--text)",textTransform:"uppercase",letterSpacing:"0.8px"},cardBody={padding:14,flex:1};return React.createElement("div",{className:"dv-panel"},renderSamplePromo(),React.createElement("div",{style:{marginBottom:12,padding:"10px 14px",background:"var(--info-bg)",borderRadius:8,border:"1.5px solid var(--info-border)",fontSize:12,color:"var(--info-text)"}},React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8}},React.createElement("span",{"aria-hidden":"true"},"\u2139"),React.createElement("span",null,"Pl\xF6ttr auto-detects the column separator (comma, tab, semicolon, \u2026) from the data."),React.createElement("button",{type:"button",onClick:()=>setOverrideOpen(v=>!v),className:"dv-btn dv-btn-secondary",style:{marginLeft:"auto",padding:"2px 8px",fontSize:11,fontWeight:600},"aria-expanded":overrideOpen},overrideOpen?"Hide override \u25B4":"Override \u25BE")),overrideOpen&&React.createElement("div",{style:{marginTop:10,paddingTop:10,borderTop:"1px solid var(--info-border)",display:"flex",alignItems:"center",gap:10}},React.createElement("label",{htmlFor:"dv-separator-select",style:{fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Force separator"),React.createElement("select",{id:"dv-separator-select",value:sepOverride,onChange:e=>onSepChange(e.target.value),className:"dv-select-sep"},React.createElement("option",{value:""},"Auto-detect"),React.createElement("option",{value:","},"Comma (,)"),React.createElement("option",{value:";"},"Semicolon (;)"),React.createElement("option",{value:" "},"Tab (\\t)"),React.createElement("option",{value:" "},"Space")),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"Only needed when the detector picks the wrong delimiter."))),React.createElement("div",{style:{display:"grid",gridTemplateColumns:onTextPaste?"1fr 1fr":"1fr",gap:16,alignItems:"stretch"}},React.createElement("div",{style:cardWrap},React.createElement("div",{style:cardHeader},React.createElement("span",{"aria-hidden":"true"},"\u{1F4C2}"),React.createElement("span",{style:cardHeaderLabel},"Drop a file")),React.createElement("div",{style:cardBody},React.createElement(FileDropZone,{onFileLoad,accept:".csv,.tsv,.txt,.dat,.tab",hint:hint||"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 2 MB max"}))),onTextPaste&&React.createElement("div",{style:cardWrap},React.createElement("div",{style:cardHeader},React.createElement("span",{"aria-hidden":"true"},"\u{1F4CB}"),React.createElement("span",{style:cardHeaderLabel},"Paste data")),React.createElement("div",{style:cardBody},React.createElement("textarea",{value:pasteText,onChange:e=>setPasteText(e.target.value),placeholder:`Paste comma-, tab-, or semicolon-separated rows here. +`}function DatasheetIcon({size=36}){return React.createElement("svg",{width:size,height:size,viewBox:"0 0 44 44",fill:"none",stroke:"currentColor",strokeWidth:1.7,strokeLinecap:"round",strokeLinejoin:"round","aria-hidden":"true",style:{display:"block"}},React.createElement("path",{d:"M10 6 L29 6 L37 14 L37 38 L10 38 Z"}),React.createElement("path",{d:"M29 6 L29 14 L37 14"}),React.createElement("path",{d:"M14 20 Q22.5 19.3 33 20"}),React.createElement("path",{d:"M14 25 Q22.5 24.6 33 25"}),React.createElement("path",{d:"M14 30 Q22.5 29.5 33 30"}),React.createElement("path",{d:"M14 34 Q19 33.6 26 34"}))}var h8=React.createElement,{useState:useState6}=React;function UploadPanel(props){if(props.autoDetect)return React.createElement(AutoDetectUploadPanel,{...props});let{sepOverride,onSepChange,onFileLoad,onLoadExample,exampleLabel,hint}=props;return h8("div",{className:"dv-panel"},h8("div",{style:{marginBottom:12,padding:"12px 16px",background:"var(--info-bg)",borderRadius:8,border:"1.5px solid var(--info-border)",display:"flex",alignItems:"center",gap:10}},h8("label",{htmlFor:"dv-separator-select",style:{fontSize:13,fontWeight:600,color:"var(--accent-primary)"}},"Column separator"),h8("select",{id:"dv-separator-select",value:sepOverride,onChange:e=>onSepChange(e.target.value),className:"dv-select-sep"},h8("option",{value:""},"\u2014 Select \u2014"),h8("option",{value:","},"Comma (,)"),h8("option",{value:";"},"Semicolon (;)"),h8("option",{value:" "},"Tab (\\t)"),h8("option",{value:" "},"Space"))),sepOverride?h8(FileDropZone,{onFileLoad,accept:".csv,.tsv,.txt,.dat,.tab",hint:hint||"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 2 MB max"}):h8("div",{style:{border:"2px dashed var(--border-strong)",borderRadius:12,padding:"48px 24px",textAlign:"center",background:"var(--surface-sunken)",opacity:.5}},h8("div",{style:{fontSize:40,marginBottom:8},"aria-hidden":"true"},"\u{1F6AB}"),h8("p",{style:{margin:0,fontSize:15,color:"var(--text-faint)"}},"Pick a column separator above to enable file loading")),onLoadExample?h8("div",{style:{marginTop:12,display:"flex",alignItems:"center",justifyContent:"center",gap:8,fontSize:12,color:"var(--text-muted)"}},h8("span",null,"Try sample data:"),h8("button",{type:"button",className:"dv-btn dv-btn-secondary",onClick:onLoadExample,"data-testid":"load-example"},exampleLabel||"Load example \u2192")):null)}function AutoDetectUploadPanel(props){let{sepOverride,onSepChange,onFileLoad,onLoadExample,exampleLabel,exampleSummary,hint,onTextPaste}=props,[overrideOpen,setOverrideOpen]=useState6(sepOverride!==""),[pasteText,setPasteText]=useState6(""),[pasteError,setPasteError]=useState6(null),[pasteWarn,setPasteWarn]=useState6(null),renderSamplePromo=()=>{if(!onLoadExample)return null;let summary=exampleSummary??{title:"Try a sample dataset",subtitle:typeof exampleLabel=="string"?exampleLabel:void 0},buttonLabel=summary.buttonLabel??"Plot this example \u2192";return React.createElement("div",{style:{marginBottom:16,padding:"14px 18px",background:"var(--success-bg)",borderRadius:12,border:"1.5px solid var(--success-border)",display:"flex",alignItems:"center",gap:14},"data-testid":"sample-promo"},React.createElement("span",{style:{display:"inline-flex",alignItems:"center",lineHeight:1,flexShrink:0,opacity:.7},"aria-hidden":"true"},summary.icon??React.createElement(DatasheetIcon,{size:36})),React.createElement("div",{style:{flex:1,minWidth:0}},React.createElement("div",{style:{fontSize:10,fontWeight:700,textTransform:"uppercase",letterSpacing:"0.8px",color:"var(--success-text)",opacity:.75,marginBottom:2}},"New here? Quick start"),React.createElement("div",{style:{fontSize:14,fontWeight:700,color:"var(--success-text)"}},summary.title),summary.subtitle&&React.createElement("div",{style:{fontSize:12,color:"var(--success-text)",opacity:.85,marginTop:2}},summary.subtitle)),React.createElement("button",{type:"button",onClick:onLoadExample,className:"dv-btn dv-btn-primary",style:{flexShrink:0,fontSize:13,fontWeight:700,padding:"8px 14px"},"data-testid":"load-example"},buttonLabel))},submitPaste=()=>{setPasteError(null),setPasteWarn(null);let text=pasteText;if(!text||text.trim()===""){setPasteError("Paste some data first \u2014 copy a selection from Excel, Sheets, or any CSV.");return}let bytes=new Blob([text]).size;if(bytes>FILE_LIMIT_BYTES){setPasteError(`Pasted data too large (${(bytes/1024/1024).toFixed(1)} MB). Maximum is 2 MB \u2014 split the data or sample rows and try again.`);return}bytes>FILE_WARN_BYTES&&setPasteWarn(`Large paste (${(bytes/1024/1024).toFixed(1)} MB) \u2014 parsing may take a moment.`),onTextPaste&&onTextPaste(text,"pasted_data.csv")},cardWrap={borderRadius:12,border:"1.5px solid var(--border)",background:"var(--surface)",overflow:"hidden",display:"flex",flexDirection:"column"},cardHeader={background:"var(--surface-subtle)",padding:"10px 14px",borderBottom:"1px solid var(--border)",display:"flex",alignItems:"center",gap:8},cardHeaderLabel={fontSize:12,fontWeight:700,color:"var(--text)",textTransform:"uppercase",letterSpacing:"0.8px"},cardBody={padding:14,flex:1};return React.createElement("div",{className:"dv-panel"},renderSamplePromo(),React.createElement("div",{style:{marginBottom:12,padding:"10px 14px",background:"var(--info-bg)",borderRadius:8,border:"1.5px solid var(--info-border)",fontSize:12,color:"var(--info-text)"}},React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8}},React.createElement("svg",{width:"15",height:"15",viewBox:"0 0 16 16","aria-hidden":"true",style:{flexShrink:0}},React.createElement("circle",{cx:"8",cy:"8",r:"7",fill:"none",stroke:"currentColor",strokeWidth:"1.4"}),React.createElement("circle",{cx:"8",cy:"4.6",r:"1",fill:"currentColor"}),React.createElement("rect",{x:"7.15",y:"6.8",width:"1.7",height:"5",rx:"0.85",fill:"currentColor"})),React.createElement("span",null,"Pl\xF6ttr auto-detects the column separator (comma, tab, semicolon, \u2026) from the data."),React.createElement("button",{type:"button",onClick:()=>setOverrideOpen(v=>!v),className:"dv-btn dv-btn-secondary",style:{marginLeft:"auto",padding:"2px 8px",fontSize:11,fontWeight:600},"aria-expanded":overrideOpen},overrideOpen?"Hide override \u25B4":"Override \u25BE")),overrideOpen&&React.createElement("div",{style:{marginTop:10,paddingTop:10,borderTop:"1px solid var(--info-border)",display:"flex",alignItems:"center",gap:10}},React.createElement("label",{htmlFor:"dv-separator-select",style:{fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Force separator"),React.createElement("select",{id:"dv-separator-select",value:sepOverride,onChange:e=>onSepChange(e.target.value),className:"dv-select-sep"},React.createElement("option",{value:""},"Auto-detect"),React.createElement("option",{value:","},"Comma (,)"),React.createElement("option",{value:";"},"Semicolon (;)"),React.createElement("option",{value:" "},"Tab (\\t)"),React.createElement("option",{value:" "},"Space")),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"Only needed when the detector picks the wrong delimiter."))),React.createElement("div",{style:{display:"grid",gridTemplateColumns:onTextPaste?"1fr 1fr":"1fr",gap:16,alignItems:"stretch"}},React.createElement("div",{style:cardWrap},React.createElement("div",{style:cardHeader},React.createElement("span",{"aria-hidden":"true"},"\u{1F4C2}"),React.createElement("span",{style:cardHeaderLabel},"Drop a file")),React.createElement("div",{style:cardBody},React.createElement(FileDropZone,{onFileLoad,accept:".csv,.tsv,.txt,.dat,.tab",hint:hint||"CSV \xB7 TSV \xB7 TXT \xB7 DAT \u2014 2 MB max"}))),onTextPaste&&React.createElement("div",{style:cardWrap},React.createElement("div",{style:cardHeader},React.createElement("span",{"aria-hidden":"true"},"\u{1F4CB}"),React.createElement("span",{style:cardHeaderLabel},"Paste data")),React.createElement("div",{style:cardBody},React.createElement("textarea",{value:pasteText,onChange:e=>setPasteText(e.target.value),placeholder:`Paste comma-, tab-, or semicolon-separated rows here. Tip: a selection copied from Excel or Google Sheets becomes tab-separated automatically.`,className:"dv-input",spellCheck:!1,style:{width:"100%",minHeight:160,boxSizing:"border-box",fontFamily:"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace",fontSize:12,lineHeight:1.4,padding:"10px 12px",resize:"vertical",background:"var(--surface)",color:"var(--text)"},"aria-label":"Paste tabular data"}),React.createElement("div",{style:{marginTop:8,display:"flex",alignItems:"center",gap:8,flexWrap:"wrap"}},React.createElement("button",{type:"button",onClick:submitPaste,className:"dv-btn dv-btn-primary",disabled:pasteText.trim()==="","data-testid":"paste-parse"},"Parse pasted data"),React.createElement("button",{type:"button",onClick:()=>{setPasteText(""),setPasteError(null),setPasteWarn(null)},className:"dv-btn dv-btn-secondary",disabled:pasteText===""},"Clear"),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"2 MB max")),pasteError&&React.createElement("div",{role:"alert",style:{marginTop:10,padding:"10px 14px",borderRadius:8,background:"var(--danger-bg)",border:"1px solid var(--danger-border)",display:"flex",alignItems:"center",gap:8,fontSize:12,color:"var(--danger-text)",fontWeight:600}},React.createElement("span",{"aria-hidden":"true"},"\u{1F6AB}"),React.createElement("span",null,pasteError)),pasteWarn&&React.createElement("div",{role:"status",style:{marginTop:10,padding:"10px 14px",borderRadius:8,background:"var(--warning-bg)",border:"1px solid var(--warning-border)",display:"flex",alignItems:"center",gap:8,fontSize:12,color:"var(--warning-text)"}},React.createElement("span",{"aria-hidden":"true"},"\u26A0\uFE0F"),React.createElement("span",null,pasteWarn))))))}var KEY="dataviz-handoff";function setHandoff(payload){try{localStorage.setItem(KEY,JSON.stringify(payload))}catch{return!1}try{window.dispatchEvent(new CustomEvent("plottr-handoff",{detail:{key:KEY}}))}catch{}return!0}function consumeHandoff(targetTool){let raw=null;try{raw=localStorage.getItem(KEY)}catch{return null}if(!raw)return null;try{localStorage.removeItem(KEY)}catch{}try{let payload=JSON.parse(raw);return!payload||payload.tool!==targetTool?null:payload}catch{return null}}function navigateToTool(toolKey){if(typeof toolKey!="string"||toolKey==="")return;let spaNav=window.__plottrSpaNavigate;if(typeof spaNav=="function")try{spaNav(toolKey);return}catch{}try{window.location.assign(toolKey+".html")}catch{}}function detectLongFormat(headers,rows){let fail={isLong:!1,col1Distinct:0,col2Distinct:0,col2Repeats:0};if(!Array.isArray(headers)||headers.length!==2||!Array.isArray(rows)||rows.length===0)return fail;let col1=[],col2=[];for(let r of rows){let v1=(r[0]||"").trim(),v2=(r[1]||"").trim();v1&&col1.push(v1),v2&&col2.push(v2)}let col1Distinct=new Set(col1).size,col2Distinct=new Set(col2).size,col2Repeats=0;if(col2.length>0){let counts={};for(let v of col2)counts[v]=(counts[v]||0)+1;for(let v of col2)counts[v]>=2&&col2Repeats++}let enoughRows=col2.length>=3,boundedSetCount=col2Distinct>=2&&col2Distinct<=20,heavyRepetition=col2.length>0&&col2Repeats*2>=col2.length,col1MostlyUnique=col1Distinct>=Math.max(3,col1.length*.7);return{isLong:enoughRows&&boundedSetCount&&heavyRepetition&&col1MostlyUnique,col1Distinct,col2Distinct,col2Repeats}}var round2=v=>Math.round(v*100)/100,round4=v=>Math.round(v*1e4)/1e4;var{useState:useState7,useEffect:useEffect5}=React;function useIsMobile(breakpoint=600){let[mobile,setMobile]=useState7(window.innerWidth{let handler=()=>setMobile(window.innerWidthwindow.removeEventListener("resize",handler)},[breakpoint]),mobile}var{useState:useState8,useReducer,useEffect:useEffect6}=React;function usePlotToolState(toolKey,initialVis){let[fileName,setFileName]=useState8(""),[step,setStep]=useState8("upload"),[parseError,setParseError]=useState8(null),[sepOverride,setSepOverride]=useState8(""),[commaFixed,setCommaFixed]=useState8(!1),[commaFixCount,setCommaFixCount]=useState8(0),[injectionWarning,setInjectionWarning]=useState8(null),[vis,updVis]=useReducer((s,a)=>"_reset"in a&&a._reset?{...initialVis}:{...s,...a},initialVis,init=>loadAutoPrefs(toolKey,init));return useEffect6(()=>{saveAutoPrefs(toolKey,vis)},[toolKey,vis]),{step,setStep,fileName,setFileName,parseError,setParseError,sepOverride,setSepOverride,commaFixed,setCommaFixed,commaFixCount,setCommaFixCount,injectionWarning,setInjectionWarning,vis,updVis}}var h9=React.createElement;function ActionsPanel(props){let downloads=[];props.onDownloadSvg&&downloads.push({label:"SVG",title:"Download the plot as SVG \u2014 vector graphics, editable in Inkscape or Illustrator",onClick:props.onDownloadSvg}),props.onDownloadPng&&downloads.push({label:"PNG",title:"Download the plot as PNG \u2014 2\xD7 raster at the plot's native resolution",onClick:props.onDownloadPng}),props.extraDownloads&&props.extraDownloads.forEach(d=>downloads.push(d));let dlButtons=downloads.map((d,i)=>h9("button",{key:"dl"+i,title:d.title||void 0,onClick:e=>{d.onClick(e),flashSaved(e.currentTarget)},className:"dv-btn dv-btn-dl",style:{flex:"1 1 0"}},"\u2B07 "+d.label));return h9("div",{className:"dv-panel"},h9("p",{className:"dv-tile-title",style:{margin:"0 0 8px"}},"Actions"),dlButtons.length>0?h9("div",{style:{display:"flex",flexWrap:"wrap",gap:6,marginBottom:8}},dlButtons):null,h9("button",{onClick:props.onReset,title:"Clear all data, controls, and current session \u2014 returns to the upload step",className:"dv-btn dv-btn-danger"},"\u21BA Start over"))}var{useState:useState9,useEffect:useEffect7}=React;function normalizeHexColor(v){if(typeof v!="string")return null;if(/^#[0-9a-fA-F]{6}$/.test(v))return v.toLowerCase();if(/^#[0-9a-fA-F]{3}$/.test(v)){let s=v.toLowerCase();return"#"+s[1]+s[1]+s[2]+s[2]+s[3]+s[3]}return null}function ColorInput({value,onChange,size=22}){let[text,setText]=useState9(value);useEffect7(()=>{normalizeHexColor(text)!==value&&setText(value)},[value]);let commit=v=>{let n=normalizeHexColor(v);n&&onChange(n)};return React.createElement("div",{style:{display:"flex",alignItems:"center",gap:4}},React.createElement("input",{type:"color",value,onChange:e=>onChange(e.target.value),style:{width:size,height:size,border:"1px solid var(--border-strong)",borderRadius:4,cursor:"pointer",padding:0,flexShrink:0}}),React.createElement("input",{type:"text",value:text,onChange:e=>{setText(e.target.value),commit(e.target.value)},onBlur:e=>{let n=normalizeHexColor(e.target.value);n?onChange(n):setText(value)},maxLength:7,spellCheck:!1,style:{width:64,fontFamily:"monospace",fontSize:11,border:"1px solid var(--border-strong)",borderRadius:4,padding:"2px 5px",color:"var(--text)",background:"var(--surface)"}}))}function SegToggle({options,value,onChange,ariaLabel,disabled,style}){return React.createElement("div",{className:"dv-seg",role:"group","aria-label":ariaLabel,style},options.map(opt=>{let active=opt.value===value;return React.createElement("button",{key:opt.value,type:"button",className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),"aria-pressed":active,title:opt.title,disabled:disabled||opt.disabled,onClick:()=>onChange(opt.value)},opt.label)}))}function OnOffToggle({value,onChange,offLabel="Off",onLabel="On",ariaLabel,disabled,style}){return React.createElement(SegToggle,{value:value?"on":"off",onChange:v=>onChange(v==="on"),options:[{value:"off",label:offLabel},{value:"on",label:onLabel}],ariaLabel,disabled,style})}var h10=React.createElement;function BaseStyleControls(props){let plotBg=props.plotBg,onPlotBgChange=props.onPlotBgChange,showGrid=props.showGrid,onShowGridChange=props.onShowGridChange,gridColor=props.gridColor,onGridColorChange=props.onGridColorChange,children=[h10("div",{key:"bg",style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},h10("span",{className:"dv-label"},"Background"),h10(ColorInput,{value:plotBg,onChange:onPlotBgChange,size:24})),h10("div",{key:"grid"},h10("span",{className:"dv-label"},"Grid"),h10(OnOffToggle,{value:showGrid,onChange:onShowGridChange,ariaLabel:"Grid"}))];return showGrid&&children.push(h10("div",{key:"gc",style:{display:"flex",alignItems:"center",justifyContent:"space-between"}},h10("span",{className:"dv-label"},"Grid color"),h10(ColorInput,{value:gridColor,onChange:onGridColorChange,size:24}))),h10("div",{style:{display:"flex",flexDirection:"column",gap:8}},children)}var CLD_DEFAULTS={fontSize:13,fontWeight:700,fontFamily:"sans-serif",textColor:"#222"};function CldLabels({labels,axisCoord,crossCoord,orientation,fontSize=CLD_DEFAULTS.fontSize,fontWeight=CLD_DEFAULTS.fontWeight,fontFamily=CLD_DEFAULTS.fontFamily,textColor=CLD_DEFAULTS.textColor}){if(!labels||labels.length===0)return null;let horizontal=orientation==="horizontal-right";return React.createElement("g",{id:"cld-annotations"},labels.map((lbl,i)=>lbl!=null?React.createElement("text",{key:`cld-${i}`,x:horizontal?crossCoord:axisCoord(i),y:horizontal?axisCoord(i):crossCoord,textAnchor:horizontal?"end":"middle",...horizontal?{dominantBaseline:"middle"}:{},fontSize,fontWeight,fill:textColor,fontFamily},lbl):null))}function scrollIntoViewWithinAncestor(el,pad,extraBottom){if(!el)return;let padding=pad??8,extra=extraBottom||0,parent=el.parentElement;for(;parent;){let ov=getComputedStyle(parent).overflowY;if((ov==="auto"||ov==="scroll")&&parent.scrollHeight>parent.clientHeight){let parentRect=parent.getBoundingClientRect(),elRect2=el.getBoundingClientRect(),revealBottom2=elRect2.bottom+extra;if(revealBottom2>parentRect.bottom-padding){let delta=revealBottom2-parentRect.bottom+padding,maxDelta=elRect2.top-parentRect.top-padding;parent.scrollBy({top:Math.min(delta,Math.max(0,maxDelta)),behavior:"smooth"})}else elRect2.topviewportBottom-padding){let delta=revealBottom-viewportBottom+padding,maxDelta=Math.max(0,elRect.top-padding);window.scrollBy({top:Math.min(delta,maxDelta),behavior:"smooth"})}else elRect.top{open&&requestAnimationFrame(()=>scrollDisclosureIntoView(rootRef.current))},[open]),React.createElement("div",{ref:rootRef,className:"dv-panel",style:{padding:0}},React.createElement("div",{style:{display:"flex",alignItems:"center",width:"100%",padding:"7px 10px",gap:8}},React.createElement("button",{onClick:()=>setOpen(!open),className:"dv-tile-title",style:{display:"flex",alignItems:"center",gap:6,flex:1,padding:0,background:"none",border:"none",cursor:"pointer",textAlign:"left"}},React.createElement("span",{className:"dv-disclosure"+(open?" dv-disclosure-open":""),"aria-hidden":"true"}),title),headerRight),open&&React.createElement("div",{style:{padding:"0 10px 10px",display:"flex",flexDirection:"column",gap:8}},children))}var{useState:useState11}=React,RUNTIME_PALETTE_PREVIEW_N=10;function naturalPaletteLength(name){let def=DISCRETE_PALETTES[name];return!def||def.length===0||def.length===1&&def[0]==="*"?RUNTIME_PALETTE_PREVIEW_N:def.length}function DiscreteSwatchStrip({palette,n,height=18,width="100%"}){let count=typeof n=="number"&&n>0?n:naturalPaletteLength(palette),colours=resolveDiscretePalette(palette,count),[copied,setCopied]=useState11(null),handleCopy=hex=>{typeof navigator<"u"&&navigator.clipboard?navigator.clipboard.writeText(hex).then(()=>{setCopied(hex),setTimeout(()=>setCopied(null),1500)},()=>{setCopied(hex),setTimeout(()=>setCopied(null),2500)}):(setCopied(hex),setTimeout(()=>setCopied(null),2500))};return React.createElement("div",null,React.createElement("div",{style:{display:"flex",width,height,borderRadius:3,overflow:"hidden",border:"1px solid var(--border-strong)"}},colours.map((hex,i)=>React.createElement("button",{key:i,type:"button",onClick:()=>handleCopy(hex),title:hex+" \u2014 click to copy","aria-label":"Copy "+hex+" to clipboard",style:{flex:1,background:hex,border:"none",padding:0,margin:0,cursor:"pointer",minWidth:0}}))),React.createElement("div",{"aria-live":"polite",style:{fontSize:10,lineHeight:1.4,marginTop:3,color:copied?"var(--success-text)":"var(--text-faint)"}},copied?"\u2713 Copied "+copied:"Click a swatch to copy its hex"))}function DiscretePaletteSelect({value,onChange}){let keys=Object.keys(DISCRETE_PALETTES);return React.createElement("div",null,React.createElement("select",{value,onChange:e=>onChange(e.target.value),className:"dv-select",style:{width:"100%",fontSize:11,margin:"2px 0 6px"},title:"Pick a discrete palette. Picking a palette overwrites every group's colour. \u{1F441} marks colour-blind-safe palettes."},keys.map(k=>React.createElement("option",{key:k,value:k},k+(COLORBLIND_SAFE_PALETTES.has(k)?" \u{1F441}":"")))),React.createElement(DiscreteSwatchStrip,{palette:value}))}function DiscretePaletteRow({value,onChange,names,applyColors}){let list=Array.isArray(names)?names:[];return React.createElement("div",{style:{marginBottom:6}},React.createElement("div",{className:"dv-label",style:{fontSize:11,marginBottom:2}},"Palette"),React.createElement(DiscretePaletteSelect,{value,onChange:next=>{if(onChange(next),typeof applyColors=="function"){let resolved=resolveDiscretePalette(next,list.length||8);applyColors(resolved)}}}))}function DownloadTiles({chartRef,fileStem,pngScale=2,extraDownloads,onReset}){return React.createElement(ActionsPanel,{onDownloadSvg:()=>downloadSvg(chartRef.current,`${fileStem}.svg`),onDownloadPng:()=>downloadPng(chartRef.current,`${fileStem}.png`,pngScale),onReset,extraDownloads})}var ErrorBoundary=class extends React.Component{constructor(props){super(props),this.state={error:null,info:null}}static getDerivedStateFromError(error){return{error}}componentDidCatch(error,info){this.setState({info}),typeof console<"u"&&console.error&&console.error("Tool crashed:",error,info)}render(){if(!this.state.error)return this.props.children;let err=this.state.error,info=this.state.info,msg=err&&err.message?err.message:String(err),stack=err&&err.stack?err.stack:msg,compStack=info&&info.componentStack?info.componentStack:"",details=stack+(compStack?` Component stack:`+compStack:""),reload=()=>{typeof window<"u"&&window.location&&window.location.reload()},copy=()=>{typeof navigator<"u"&&navigator.clipboard&&navigator.clipboard.writeText(details).catch(()=>{})},toolName=this.props.toolName||"This tool";return React.createElement("div",{role:"alert",style:{maxWidth:720,margin:"40px auto",padding:24,fontFamily:"system-ui, -apple-system, sans-serif",color:"var(--text)"}},React.createElement("h2",{style:{marginTop:0,color:"var(--danger-text)",fontSize:20}},"Something went wrong"),React.createElement("p",{style:{fontSize:14,lineHeight:1.5}},toolName+" hit an unexpected error and can't continue. Your data is still on your machine \u2014 nothing was sent anywhere. Try reloading; if it keeps crashing, use \u201CCopy error details\u201D and open an issue."),React.createElement("pre",{style:{background:"var(--danger-bg)",border:"1px solid var(--danger-border)",borderRadius:6,padding:12,fontSize:12,color:"var(--danger-text)",overflow:"auto",maxHeight:200,whiteSpace:"pre-wrap",wordBreak:"break-word"}},msg),React.createElement("details",{style:{marginBottom:16}},React.createElement("summary",{style:{cursor:"pointer",fontSize:13,color:"var(--text-muted)"}},"Technical details"),React.createElement("pre",{style:{background:"var(--surface-subtle)",border:"1px solid var(--border)",borderRadius:6,padding:12,fontSize:11,color:"var(--text-muted)",overflow:"auto",maxHeight:300,whiteSpace:"pre-wrap",wordBreak:"break-word",marginTop:8}},details)),React.createElement("div",{style:{display:"flex",gap:10,flexWrap:"wrap"}},React.createElement("button",{type:"button",onClick:reload,className:"dv-btn dv-btn-primary"},"Reload tool"),React.createElement("button",{type:"button",onClick:copy,className:"dv-btn dv-btn-secondary"},"Copy error details")))}};var h11=React.createElement;function FilterCheckboxPanel(props){let headers=props.headers,colNames=props.colNames,colRoles=props.colRoles,filters=props.filters,filteredCount=props.filteredCount,totalCount=props.totalCount,onToggle=props.onToggle,onToggleAll=props.onToggleAll;return h11("div",{style:{flex:1,borderRadius:10,padding:16,border:"1px solid var(--info-border)",background:"var(--info-bg)",display:"flex",flexDirection:"column"}},h11("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--info-text)"}},"Filter rows ("+filteredCount+"/"+totalCount+")"),h11("div",{style:{display:"flex",gap:16,flexWrap:"wrap",alignItems:"stretch",flex:1}},headers.map((_hdr,i)=>{if(colRoles[i]==="ignore"||colRoles[i]==="value")return null;let u=filters[i]?Array.from(filters[i].unique):[];return u.length>0&&u.filter(v=>isNumericValue(v)).length/u.length>.5&&colRoles[i]!=="filter"?h11("div",{key:"col-"+i,style:{minWidth:140,flex:1,background:"var(--surface)",borderRadius:6,border:"1px solid var(--border)",padding:10}},h11("div",{style:{display:"flex",alignItems:"center",gap:6,flexWrap:"wrap"}},h11("p",{style:{fontSize:11,fontWeight:600,color:"var(--text)",margin:0}},colNames[i]),h11("button",{onClick:()=>onToggleAll(i,!0),style:{fontSize:9,padding:"2px 6px",background:"var(--surface-sunken)",border:"1px solid var(--border-strong)",borderRadius:3,cursor:"pointer",fontFamily:"inherit",color:"var(--text-muted)"}},"All")),h11("p",{style:{fontSize:10,color:"var(--text-faint)",margin:"4px 0 0",fontStyle:"italic"}},"numeric \u2014 use axis range in plot")):h11("div",{key:"col-"+i,style:{minWidth:140,flex:1,background:"var(--surface)",borderRadius:6,border:"1px solid var(--border)",padding:10}},h11("p",{style:{fontSize:11,fontWeight:600,color:"var(--text)",marginBottom:4}},colNames[i]),h11("div",{style:{display:"flex",gap:6,marginBottom:4}},h11("button",{onClick:()=>onToggleAll(i,!0),style:{fontSize:9,padding:"2px 6px",background:"var(--surface-sunken)",border:"1px solid var(--border-strong)",borderRadius:3,cursor:"pointer",fontFamily:"inherit",color:"var(--text-muted)"}},"All"),h11("button",{onClick:()=>onToggleAll(i,!1),style:{fontSize:9,padding:"2px 6px",background:"var(--surface-sunken)",border:"1px solid var(--border-strong)",borderRadius:3,cursor:"pointer",fontFamily:"inherit",color:"var(--text-muted)"}},"None")),u.map(v=>{let checked=!!(filters[i]&&filters[i].included&&filters[i].included.has(v));return h11("label",{key:v,style:{display:"flex",alignItems:"center",gap:4,fontSize:11,color:"var(--text-muted)",cursor:"pointer",marginBottom:2}},h11("input",{type:"checkbox",checked,onChange:()=>onToggle(i,v),style:{accentColor:"var(--cta-primary-bg)"}}),v||h11("em",{style:{color:"var(--text-faint)"}},"(empty)"))}))})))}var h12=React.createElement;function GroupColorEditor(props){let groups=props.groups,onColorChange=props.onColorChange,onNameChange=props.onNameChange,onToggle=props.onToggle;return h12("div",{style:{display:"flex",flexDirection:"column",gap:4}},groups.map((g,i)=>{let enabled=g.enabled!==!1,children=[];return onToggle&&children.push(h12("input",{key:"cb",type:"checkbox",checked:enabled,onChange:()=>onToggle(i),style:{accentColor:g.color,flexShrink:0,cursor:"pointer"}})),children.push(h12(ColorInput,{key:"clr",value:g.color,onChange:c=>onColorChange(i,c),size:18})),children.push(h12("input",{key:"nm",value:g.displayName||g.name,onChange:e=>{onNameChange&&onNameChange(i,e.target.value)},style:{flex:1,minWidth:0,background:"var(--surface)",border:"1px solid var(--border-strong)",borderRadius:4,color:"var(--text)",padding:"2px 4px",fontSize:11,fontFamily:"inherit"}})),children.push(h12("span",{key:"n",style:{color:"var(--text-faint)",fontSize:10,flexShrink:0}},"n="+(g.stats?g.stats.n:0))),h12("div",{key:g.name,style:{display:"flex",alignItems:"center",gap:6,padding:"3px 8px",borderRadius:6,fontSize:12,background:enabled?"var(--surface-sunken)":"var(--surface-subtle)",opacity:enabled?1:.4,border:"1px solid var(--border-strong)"}},children)}))}function PlotSidebar({children,sticky=!0,width=279}){return React.createElement("div",{className:"dv-sidebar",style:{width,flexShrink:0,...sticky?{position:"sticky",top:24,maxHeight:"calc(100vh - 90px)",overflowY:"auto"}:{},display:"flex",flexDirection:"column",gap:10}},children)}var h13=React.createElement;function RenameReorderPanel(props){let headers=props.headers,colNames=props.colNames,colRoles=props.colRoles,filters=props.filters,valueRenames=props.valueRenames,orderableCols=props.orderableCols||{},applyRename=props.applyRename,onRenameVal=props.onRenameVal,dragState=props.dragState,onDragStart=props.onDragStart,onDragEnd=props.onDragEnd;return h13("div",{style:{flex:1,borderRadius:10,padding:16,border:"1px solid var(--border)",background:"var(--surface-subtle)"}},h13("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Rename values & reorder groups ",h13("span",{style:{fontSize:10,color:"var(--text-faint)",fontWeight:400}},"(drag \u2630 to reorder groups on plot)")),h13("div",{style:{display:"flex",gap:16,flexWrap:"wrap"}},headers.map((_hdr,i)=>{if(colRoles[i]!=="group"&&colRoles[i]!=="filter")return null;let u=(filters[i]?Array.from(filters[i].unique):[]).filter(v=>filters[i]&&filters[i].included&&filters[i].included.has(v)),colOrder=orderableCols[i],isOrderable=!!colOrder,renamedU=u.map(v=>({orig:v,renamed:applyRename(i,v)})),orderedU=isOrderable&&colOrder.order?colOrder.order.map(g=>renamedU.find(x=>x.renamed===g)).filter(x=>!!x):renamedU,displayList=orderedU.length>0?orderedU:renamedU,dragIdxForCol=dragState&&dragState.col===i?dragState.idx:null;return h13("div",{key:"col-"+i,style:{minWidth:200,background:"var(--surface)",borderRadius:6,border:"1px solid var(--border)",padding:10}},h13("p",{style:{fontSize:11,fontWeight:600,color:"var(--text)",marginBottom:6}},colNames[i]),displayList.map((item,vi)=>{let v=item.orig;return h13("div",{key:v,draggable:isOrderable,onDragStart:()=>onDragStart({col:i,idx:vi}),onDragOver:e=>{e.preventDefault()},onDrop:()=>{if(!isOrderable||dragIdxForCol===null||dragIdxForCol===vi){onDragEnd();return}let cur=displayList.map(x=>x.renamed),moved=cur[dragIdxForCol];cur.splice(dragIdxForCol,1),cur.splice(vi,0,moved),colOrder.onReorder(cur),onDragEnd()},onDragEnd:()=>onDragEnd(),style:{display:"flex",gap:4,alignItems:"center",marginBottom:3,padding:"3px 4px",borderRadius:4,background:dragIdxForCol===vi?"var(--info-bg)":"transparent",cursor:isOrderable?"grab":"default",borderLeft:isOrderable?"3px solid var(--accent-primary)":"3px solid transparent"}},isOrderable?h13("span",{style:{fontSize:11,color:"var(--text-faint)",cursor:"grab"}},"\u2630"):null,h13("span",{style:{fontSize:10,color:"var(--text-muted)",minWidth:55,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}},v||"(empty)"),h13("span",{style:{fontSize:10,color:"var(--text-faint)"}},"\u2192"),h13("input",{value:valueRenames[i]&&valueRenames[i][v]!=null?valueRenames[i][v]:v,onChange:e=>onRenameVal(i,v,e.target.value),className:"dv-input",style:{width:100,fontSize:11}}))}))})))}var BRACKET_DEFAULTS={stroke:"#333",strokeWidth:1,fontSize:12,fontWeight:700,fontFamily:"sans-serif",textColor:"#222",levelStep:20,tickHeight:4};function SignificanceBrackets({pairs,axisCoord,baseline,orientation,levelStep=BRACKET_DEFAULTS.levelStep,tickHeight=BRACKET_DEFAULTS.tickHeight,stroke=BRACKET_DEFAULTS.stroke,strokeWidth=BRACKET_DEFAULTS.strokeWidth,fontSize=BRACKET_DEFAULTS.fontSize,fontWeight=BRACKET_DEFAULTS.fontWeight,fontFamily=BRACKET_DEFAULTS.fontFamily,textColor=BRACKET_DEFAULTS.textColor}){return!pairs||pairs.length===0?null:React.createElement("g",{id:"significance-brackets"},pairs.map((pr,idx)=>{let lvl=pr._level||0,a=axisCoord(pr.i),b=axisCoord(pr.j);if(orientation==="horizontal-right"){let xLine=baseline+lvl*levelStep;return React.createElement("g",{key:`br-${idx}`},React.createElement("path",{d:`M${xLine-tickHeight},${a} L${xLine},${a} L${xLine},${b} L${xLine-tickHeight},${b}`,stroke,strokeWidth,fill:"none"}),React.createElement("text",{x:xLine+6,y:(a+b)/2,textAnchor:"middle",dominantBaseline:"middle",fontSize,fontWeight,fill:textColor,fontFamily,transform:`rotate(90,${xLine+6},${(a+b)/2})`},pr.label))}let yLine=baseline-lvl*levelStep;return React.createElement("g",{key:`br-${idx}`},React.createElement("path",{d:`M${a},${yLine+tickHeight} L${a},${yLine} L${b},${yLine} L${b},${yLine+tickHeight}`,stroke,strokeWidth,fill:"none"}),React.createElement("text",{x:(a+b)/2,y:yLine-2,textAnchor:"middle",fontSize,fontWeight,fill:textColor,fontFamily},pr.label))}))}var h14=React.createElement,{memo}=React;function _SliderControlImpl(props){let{label,value,displayValue,min,max,step,onChange}=props,dv=displayValue??value,pct=(value-min)/(max-min)*100,grad="linear-gradient(to right, var(--accent-primary) "+pct+"%, var(--slider-track) "+pct+"%)";return h14("div",null,h14("div",{style:{display:"flex",justifyContent:"space-between",marginBottom:2}},h14("span",{className:"dv-label"},label),h14("span",{style:{fontSize:10,color:"var(--text-faint)"}},dv)),h14("input",{type:"range",min,max,step,value,onChange:e=>onChange(Number(e.target.value)),style:{width:"100%",background:grad}}))}var SliderControl=memo(_SliderControlImpl,(prev,next)=>prev.value===next.value&&prev.min===next.min&&prev.max===next.max&&prev.step===next.step&&prev.label===next.label&&prev.displayValue===next.displayValue);var h15=React.createElement;function StatsTable({stats,groupLabel}){if(!stats||stats.length===0)return null;let headers=["Group","n","Mean","Median","SD","SEM","Min","Max"];return h15("div",{className:"dv-panel"},h15("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},`Summary \u2014 grouped by "${groupLabel}"`),h15("div",{style:{overflowX:"auto"}},h15("table",{style:{borderCollapse:"collapse",fontSize:12,width:"100%"}},h15("thead",null,h15("tr",{style:{borderBottom:"2px solid var(--border-strong)"}},headers.map(hdr=>h15("th",{key:hdr,style:{padding:"4px 10px",textAlign:"left",color:"var(--text-muted)",fontWeight:600}},hdr)))),h15("tbody",null,stats.map((s,i)=>h15("tr",{key:s.name,style:{borderBottom:"1px solid var(--border)"}},h15("td",{style:{padding:"4px 10px",fontWeight:600,color:PALETTE[i%PALETTE.length]}},s.name),h15("td",{style:{padding:"4px 10px"}},s.n),h15("td",{style:{padding:"4px 10px"}},s.mean!=null?s.mean.toFixed(4):"\u2014"),h15("td",{style:{padding:"4px 10px"}},s.median!=null?s.median.toFixed(4):"\u2014"),h15("td",{style:{padding:"4px 10px"}},s.sd!=null?s.sd.toFixed(4):"\u2014"),h15("td",{style:{padding:"4px 10px"}},s.sem!=null?s.sem.toFixed(4):"\u2014"),h15("td",{style:{padding:"4px 10px"}},s.min!=null?s.min.toFixed(4):"\u2014"),h15("td",{style:{padding:"4px 10px"}},s.max!=null?s.max.toFixed(4):"\u2014")))))))}function computePowerFromData(chosenTest,values){if(!chosenTest||!values||values.length<2)return null;let alphas=[.05,.01,.001],target=.8;if(chosenTest==="studentT"||chosenTest==="welchT"||chosenTest==="mannWhitney"){let x=values[0],y=values[1],n1=x.length,n2=y.length;if(n1<2||n2<2)return null;let m1=sampleMean(x),m2=sampleMean(y),s1=sampleSD(x),s2=sampleSD(y),denom,label;chosenTest==="welchT"?(denom=(s1+s2)/2,label="Cohen's d_av"):(denom=Math.sqrt(((n1-1)*s1*s1+(n2-1)*s2*s2)/(n1+n2-2)),label="Cohen's d");let dAbs=denom>0?Math.abs(m1-m2)/denom:0,dSigned=denom>0?(m1-m2)/denom:0,effectCI=chosenTest==="mannWhitney"?null:denom>0?cohenDCI(dSigned,n1,n2):null,rows=alphas.map(alpha=>{let needed=null;if(dAbs>0){for(let n=2;n<=5e3;n++)if(powerTwoSample(dAbs,n,alpha,2)>=target){needed=n;break}}return{alpha,nForTarget:needed}});return{effectLabel:label,effect:dSigned,effectCI,rows,targetPower:target,nLabel:"per group",approximate:chosenTest==="mannWhitney"}}if(chosenTest==="oneWayANOVA"||chosenTest==="welchANOVA"||chosenTest==="kruskalWallis"){let kk=values.length;if(kk<2)return null;let means=values.map(sampleMean);if(values.some(v=>v.length<2))return null;let ssW=0,totalN=0,weightedSum=0;for(let i=0;i0?weightedSum/totalN:0,ssB=0;for(let i=0;i0?Math.sqrt(ssB/ssW):0,rows=alphas.map(alpha=>{let needed=null;if(f>0){for(let n=2;n<=5e3;n++)if(powerAnova(f,n,alpha,kk)>=target){needed=n;break}}return{alpha,nForTarget:needed}});return{effectLabel:"Cohen's f",effect:f,effectCI:null,rows,targetPower:target,nLabel:"per group",approximate:chosenTest==="kruskalWallis"}}return null}function assignBracketLevels(pairs){let enriched=pairs.map((pr,idx)=>({...pr,_span:Math.abs(pr.j-pr.i),_orig:idx,_level:0}));enriched.sort((a,b)=>a._span-b._span);let placed=[];for(let pr of enriched){let lvl=0;for(;placed.some(q=>q._level===lvl&&Math.max(Math.min(q.i,q.j),Math.min(pr.i,pr.j))<=Math.min(Math.max(q.i,q.j),Math.max(pr.i,pr.j)));)lvl++;pr._level=lvl,placed.push(pr)}return placed.sort((a,b)=>a._orig-b._orig),placed.map(({_orig:_o,_span:_s,...rest})=>rest)}var CHART_MARGIN={top:20,right:20,bottom:48,left:62};function buildLineD(pts){let valid=pts.filter(p=>p.y!=null);return valid.length<2?"":"M"+valid.map(p=>`${p.x.toFixed(2)},${p.y.toFixed(2)}`).join("L")}function testStatistic(res){return!res||res.error?null:res.t??res.U??res.F??res.H??null}function runTest(name,values){let entry=STATS_TEST_REGISTRY[name];if(!entry)return{error:"unknown test"};try{return entry.run(values)}catch(e){return{error:String(e&&e.message||e)}}}function runPostHoc(name,values){let entry=STATS_POSTHOC_REGISTRY[name];if(!entry)return null;try{return entry.run(values)}catch(e){return{pairs:[],error:String(e&&e.message||e)}}}function postHocForTest(testName){if(testName==null)return null;let entry=STATS_TEST_REGISTRY[testName];return entry?entry.postHoc:null}var h16=React.createElement;function computeLegendHeight(blocks,usableW,itemWidth){if(!blocks||!blocks.length)return 0;let IH=18,TH=15,iw=itemWidth||88,t=10;return blocks.forEach((b,bi)=>{if(b.title&&(t+=TH),b.items){let bIW=typeof iw=="function"?iw(b):iw;t+=Math.ceil(b.items.length/Math.max(1,Math.floor(usableW/bIW)))*IH}if(b.gradient&&(t+=30),b.sizeItems&&b.sizeItems.length){let mr=Math.max(...b.sizeItems.map(i=>i.r),3),rowH=mr*2+4,cx=0,rows=1;b.sizeItems.forEach((item,ii)=>{let labelW=(item.label||"").length*5.6+6,itemW=mr*2+4+labelW+12;ii>0&&cx+itemW>usableW&&(rows++,cx=0),cx+=itemW}),t+=rows*rowH}bii.r).concat([3]));off+=mr*2+4}off+=8,blockOffsets.push(off)}let blockGroups=blocks.map((block,bi)=>{let bIW=typeof iw=="function"?iw(block):iw,blockY=startY+blockOffsets[bi],itemsPerRow=Math.max(1,Math.floor(usableW/bIW)),children=[];if(block.title&&children.push(h16("text",{key:"title",fontSize:"10",fill:"#666",fontFamily:"sans-serif",y:10},block.title)),block.items&&block.items.forEach((item,ii)=>{let row=Math.floor(ii/itemsPerRow),col=ii%itemsPerRow,label=item.label||"";truncateLabel&&label.length>truncateLabel&&(label=label.slice(0,truncateLabel-2)+"\u2026");let shape;item.shape==="line"?shape=h16("line",{key:"s",x1:0,x2:14,y1:7,y2:7,stroke:item.color,strokeWidth:"2.5"}):item.shape==="triangle"?shape=h16("polygon",{key:"s",points:"6,1 1,12 11,12",fill:item.color}):item.shape==="square"?shape=h16("rect",{key:"s",x:1,y:2,width:10,height:10,fill:item.color}):item.shape==="cross"?shape=h16("path",{key:"s",d:"M4,0 H8 V4 H12 V8 H8 V12 H4 V8 H0 V4 H4 Z",fill:item.color}):shape=h16("circle",{key:"s",cx:6,cy:7,r:5,fill:item.color});let text=h16("text",{key:"t",x:item.shape==="line"?18:14,y:11,fontSize:"10",fill:"#444",fontFamily:"sans-serif"},label);children.push(h16("g",{key:"i"+ii,transform:"translate("+col*bIW+", "+((block.title?TH:0)+row*IH)+")"},shape,text))}),block.gradient){let gw=Math.min(usableW*.6,200),gh=12,th=block.title?TH:0,gradId="svggrad-"+bi,stops=block.gradient.stops.map((c,si)=>h16("stop",{key:si,offset:si/(block.gradient.stops.length-1)*100+"%",stopColor:c}));children.push(h16("g",{key:"grad",transform:"translate(0, "+th+")"},h16("defs",null,h16("linearGradient",{id:gradId,x1:"0%",y1:"0%",x2:"100%",y2:"0%"},stops)),h16("rect",{x:0,y:0,width:gw,height:gh,fill:"url(#"+gradId+")",rx:"2"}),h16("text",{x:0,y:gh+13,fontSize:"9",fill:"#555",fontFamily:"sans-serif",textAnchor:"start"},block.gradient.min),h16("text",{x:gw,y:gh+13,fontSize:"9",fill:"#555",fontFamily:"sans-serif",textAnchor:"end"},block.gradient.max)))}if(block.sizeItems&&block.sizeItems.length){let sth=block.title?TH:0,maxR=Math.max(...block.sizeItems.map(i=>i.r),3),rowH=maxR*2+4,cx=0,row=0,sizeChildren=block.sizeItems.map((item,ii)=>{let labelW=(item.label||"").length*5.6+6,itemW=maxR*2+4+labelW+12;ii>0&&cx+itemW>usableW&&(row++,cx=0);let tx=cx;return cx+=itemW,h16("g",{key:ii,transform:"translate("+tx+", "+row*rowH+")"},h16("circle",{cx:maxR,cy:0,r:item.r,fill:"#888",fillOpacity:"0.35",stroke:"#888",strokeWidth:"0.8"}),h16("text",{x:maxR*2+4,y:4,fontSize:"9",fill:"#444",fontFamily:"sans-serif"},item.label))});children.push(h16("g",{key:"size",transform:"translate(0, "+(sth+maxR)+")"},sizeChildren))}let blockId=block.id||(block.title&&typeof svgSafeId=="function"?"legend-"+svgSafeId(block.title):"legend-block-"+bi);return h16("g",{key:bi,id:blockId,transform:"translate("+leftX+", "+blockY+")"},children)});return h16("g",{id:"legend"},blockGroups)}export{pStars,formatP,betai,tinv,bisect,powerTwoSample,powerPaired,powerOneSample,powerAnova,powerCorrelation,powerChi2,fFromGroupMeans,sampleMean,sampleSD,pearsonCorrelation,spearmanCorrelation,kendallTau,selectCorrelation,bhAdjust,compactLetterDisplay,selectTest,getPointColors,PALETTE,COLOR_PALETTES,DIVERGING_PALETTES,interpolateColor,roleColors,TOOL_ACCENT,tintIcon,isNumericValue,toNumericValue,seededRandom,autoDetectSep,fixDecimalCommas,parseRaw,guessColumnType,detectWideFormat,parseWideMatrix,parseData,wideToLong,reshapeWide,parseSetData,parseLongFormatSets,svgSafeId,registerSvgExportMutator,unregisterSvgExportMutator,fileBaseName,flashSaved,downloadSvg,downloadPng,downloadText,downloadCsv,ActionsPanel,ColorInput,SegToggle,OnOffToggle,BaseStyleControls,CldLabels,scrollDisclosureIntoView,ControlSection,DataPreview,DetectedSeparatorBadge,resolveDiscretePalette,applyDiscretePalette,DiscretePaletteRow,DownloadTiles,ErrorBoundary,FILE_LIMIT_BYTES,FilterCheckboxPanel,GroupColorEditor,HowTo,NumberInput,PageHeader,PlotSidebar,PlotToolShell,RenameReorderPanel,ScrollablePlotCard,SignificanceBrackets,SliderControl,StatsTable,STATS_TEST_REGISTRY,STATS_POSTHOC_REGISTRY,STATS_TESTS_FOR_K2,STATS_TESTS_FOR_K,sanitizeRString,sanitizeRComment,formatRNumber,wrapRItems,formatRVector,buildRScript,buildRScriptForPower,computePowerFromData,UploadPanel,assignBracketLevels,CHART_MARGIN,buildLineD,setHandoff,consumeHandoff,navigateToTool,detectLongFormat,round2,round4,useIsMobile,testStatistic,runTest,runPostHoc,postHocForTest,computeLegendHeight,renderSvgLegend,usePlotToolState}; -//# sourceMappingURL=chunk-5F6S3FYW.js.map +//# sourceMappingURL=chunk-HFTDMS7M.js.map diff --git a/tools/_app/chunks/chunk-5F6S3FYW.js.map b/tools/_app/chunks/chunk-HFTDMS7M.js.map similarity index 72% rename from tools/_app/chunks/chunk-5F6S3FYW.js.map rename to tools/_app/chunks/chunk-HFTDMS7M.js.map index 45e73401..17fd9eec 100644 --- a/tools/_app/chunks/chunk-5F6S3FYW.js.map +++ b/tools/_app/chunks/chunk-HFTDMS7M.js.map @@ -1,7 +1,7 @@ { "version": 3, "sources": ["../../_core/stats/dist.ts", "../../_core/stats/format.ts", "../../_core/stats/tests.ts", "../../_core/stats/posthoc.ts", "../../_core/color.ts", "../../_core/numeric.ts", "../../_core/csv.ts", "../../_core/svg-export.ts", "../../_core/download.ts", "../../_shell/DataPreview.tsx", "../../_shell/DetectedSeparatorBadge.tsx", "../../_shell/discrete-palette.ts", "../../_shell/FileDropZone.tsx", "../../_shell/HowToCard.tsx", "../../_shell/HowTo.tsx", "../../_shell/NumberInput.tsx", "../../_core/icons.tsx", "../../_shell/PageHeader.tsx", "../../_shell/prefs-store.ts", "../../_shell/PrefsPanel.tsx", "../../_shell/StepNavBar.tsx", "../../_shell/CommaFixBanner.tsx", "../../_shell/FormulaInjectionBanner.tsx", "../../_shell/ParseErrorBanner.tsx", "../../_shell/PlotToolShell.tsx", "../../_shell/ScrollablePlotCard.tsx", "../../_shell/stats-registry.ts", "../../_shell/r-export.ts", "../../_shell/DatasheetIcon.tsx", "../../_shell/UploadPanel.tsx", "../../_shell/handoff.ts", "../../_shell/long-format-detect.ts", "../../_shell/round.ts", "../../_shell/use-is-mobile.ts", "../../_shell/usePlotToolState.ts", "../../_shell/ActionsPanel.tsx", "../../_shell/ColorInput.tsx", "../../_shell/SegToggle.tsx", "../../_shell/BaseStyleControls.tsx", "../../_shell/CldLabels.tsx", "../../_shell/scroll-helpers.ts", "../../_shell/ControlSection.tsx", "../../_shell/DiscretePaletteRow.tsx", "../../_shell/DownloadTiles.tsx", "../../_shell/ErrorBoundary.tsx", "../../_shell/FilterCheckboxPanel.tsx", "../../_shell/GroupColorEditor.tsx", "../../_shell/PlotSidebar.tsx", "../../_shell/RenameReorderPanel.tsx", "../../_shell/SignificanceBrackets.tsx", "../../_shell/SliderControl.tsx", "../../_shell/StatsTable.tsx", "../../_shell/power-from-data.ts", "../../_shell/bracket-levels.ts", "../../_shell/chart-layout.ts", "../../_shell/stats-dispatch.ts", "../../_shell/svg-legend.ts"], - "sourcesContent": ["// stats/dist.ts \u2014 probability distributions, special functions, and power calcs.\n//\n// Layout:\n// 1. Distribution functions \u2014 normal, gamma, beta, t, F, chi-square,\n// noncentral t/F/chi-square\n// 2. Generic helpers \u2014 bisect\n// 2b. Power functions \u2014 t / paired / ANOVA / correlation / chi\u00B2\n//\n// All functions are benchmarked against R output in tests/stats.test.js\n// with a \u00B10.5 % tolerance bar.\n\n// \u2500\u2500 1. Distribution functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Normal CDF. Uses A&S 26.2.17 (max error 7.5e-8) for moderate |x|, and\n// switches to the tail-accurate `normsf` for |x| \u2265 7 \u2014 avoids the 1 \u2212 tiny\n// representation collapse at the edges of double precision.\nexport function normcdf(x: number): number {\n if (x === 0) return 0.5;\n if (x >= 7) return 1 - normsf(x);\n if (x <= -7) return normsf(-x);\n const t = 1 / (1 + 0.2316419 * Math.abs(x));\n const d = 0.3989422804014327; // 1/sqrt(2*pi)\n const poly =\n t *\n (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n const p = 1 - d * Math.exp(-0.5 * x * x) * poly;\n return x > 0 ? p : 1 - p;\n}\n\n// Normal survival 1 - \u03A6(x) \u2014 tail-accurate. For |x| < 7 we use the A&S 26.2.17\n// polynomial directly (no 1 \u2212 normcdf cancellation). For |x| \u2265 7 we switch to\n// the asymptotic expansion \u03A6\u0304(x) \u2248 \u03C6(x)/x \u00B7 (1 \u2212 1/x\u00B2 + 3/x\u2074 \u2212 15/x\u2076 + ...)\n// which stays accurate down to ~1e-300 where A&S 26.2.17 has already lost all\n// meaningful digits.\nexport function normsf(x: number): number {\n if (x === 0) return 0.5;\n if (x < 0) return 1 - normsf(-x);\n const d = 0.3989422804014327; // 1/sqrt(2*pi)\n if (x < 7) {\n const t = 1 / (1 + 0.2316419 * x);\n const poly =\n t *\n (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n return d * Math.exp(-0.5 * x * x) * poly;\n }\n // Asymptotic series. Truncate when a term is \u2264 1e-16 \u00D7 running sum.\n const x2 = x * x;\n let term = 1;\n let sum = 1;\n let n = 1;\n while (n < 40) {\n term *= -(2 * n - 1) / x2;\n const next = sum + term;\n if (Math.abs(term) <= 1e-16 * Math.abs(next)) {\n sum = next;\n break;\n }\n sum = next;\n n++;\n }\n return (d * Math.exp(-0.5 * x2) * sum) / x;\n}\n\n// Inverse normal CDF \u2014 Peter Acklam's rational approximation (max error 1.15e-9)\nexport function norminv(p: number): number {\n if (p <= 0) return -Infinity;\n if (p >= 1) return Infinity;\n // equiv-mutant: redundant \u2014 the p\u2264pHigh rational form already yields exactly 0 at p=0.5 (q=p\u22120.5=0)\n if (p === 0.5) return 0;\n const a = [\n -3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.38357751867269e2,\n -3.066479806614716e1, 2.506628277459239,\n ];\n const b = [\n -5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2, 6.680131188771972e1,\n -1.328068155288572e1,\n ];\n const c = [\n -7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838, -2.549732539343734,\n 4.374664141464968, 2.938163982698783,\n ];\n const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996, 3.754408661907416];\n const pLow = 0.02425,\n pHigh = 1 - pLow;\n let q: number, r: number;\n if (p < pLow) {\n q = Math.sqrt(-2 * Math.log(p));\n return (\n (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /\n ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)\n );\n }\n if (p <= pHigh) {\n q = p - 0.5;\n r = q * q;\n return (\n ((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q) /\n (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1)\n );\n }\n q = Math.sqrt(-2 * Math.log(1 - p));\n return (\n -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /\n ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)\n );\n}\n\n// Log gamma \u2014 Lanczos approximation\nexport function gammaln(x: number): number {\n const g = 7;\n const coef = [\n 0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,\n -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,\n 1.5056327351493116e-7,\n ];\n if (x < 0.5) return Math.log(Math.PI / Math.sin(Math.PI * x)) - gammaln(1 - x);\n x -= 1;\n let a = coef[0];\n const t = x + g + 0.5;\n for (let i = 1; i < g + 2; i++) a += coef[i] / (x + i);\n return 0.5 * Math.log(2 * Math.PI) + (x + 0.5) * Math.log(t) - t + Math.log(a);\n}\n\n// Regularized incomplete beta function I_x(a, b) via continued fraction.\n//\n// Log-space final exponentiation: the prior `exp(logFront) * cf / a` path\n// underflowed `logFront` to 0 whenever `a*log(x) + b*log(1-x) - lnBeta` went\n// below \u2248 \u2212745 (double-precision exp floor). That silently destroyed deep-tail\n// p-values \u2014 the small-t branch for df/2 \u2265 30 with |t| > 5 routinely lands\n// at logFront \u2248 \u2212200 to \u2212140, well within representable range but wiped to 0\n// if we exp() too early.\nexport function betai(a: number, b: number, x: number): number {\n if (x < 0 || x > 1) return 0;\n if (x === 0) return 0;\n if (x === 1) return 1;\n const lnBeta = gammaln(a) + gammaln(b) - gammaln(a + b);\n const logFront = Math.log(x) * a + Math.log(1 - x) * b - lnBeta;\n if (x < (a + 1) / (a + b + 2)) {\n const cf = betacf(a, b, x);\n return Math.exp(logFront + Math.log(cf / a));\n }\n const cf = betacf(b, a, 1 - x);\n return 1 - Math.exp(logFront + Math.log(cf / b));\n}\n\n// Upper-tail of the regularized incomplete beta: 1 \u2212 I_x(a, b). Computing\n// 1 \u2212 betai() directly cancels when betai is near 1; this helper returns the\n// upper tail via the opposite branch of the continued fraction, so callers\n// like `fcdf_upper` and `tcdf_upper` stay accurate for tiny p-values.\nexport function betai_upper(a: number, b: number, x: number): number {\n if (x <= 0) return 1;\n if (x >= 1) return 0;\n const lnBeta = gammaln(a) + gammaln(b) - gammaln(a + b);\n const logFront = Math.log(x) * a + Math.log(1 - x) * b - lnBeta;\n if (x < (a + 1) / (a + b + 2)) {\n // betai is small \u2192 1 \u2212 tiny; direct subtraction is fine.\n const cf = betacf(a, b, x);\n return 1 - Math.exp(logFront + Math.log(cf / a));\n }\n // betai is \u2248 1 \u2192 upper tail = exp(logFront + log(cf/b)), no cancellation.\n const cf = betacf(b, a, 1 - x);\n return Math.exp(logFront + Math.log(cf / b));\n}\n\n// \u2500\u2500 Continued-fraction primitives \u2014 Cephes-derived \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// betacf, gammainc, and gammainc_upper below are ported from the Cephes\n// Mathematical Library (Stephen L. Moshier), which the author dedicated to\n// the public domain. See `incbet.c` and `igam.c` at:\n// https://www.netlib.org/cephes/\n// \"Cephes Math Library Release 2.8\" \u2014 Stephen L. Moshier, 2000\n//\n// Cephes uses a three-term recurrence (pkm1/pkm2/qkm1/qkm2) with periodic\n// big/biginv rescaling to keep partial numerators/denominators inside\n// representable range. This is structurally distinct from Lentz's modified\n// per-step floor clamp; both converge to the same value within machine\n// precision for the input range we use here. The Pl\u00F6ttr-specific polish\n// (log-space final exponentiation in gammainc / gammainc_upper, and the\n// \u221Aa-scaled iteration cap so chi2cdf / ptukey stay accurate at huge df)\n// is layered on top of the Cephes recurrence.\n//\n// Constants below match Cephes' incbet.c / igam.c literally.\nconst CEPHES_BIG = 4.503599627370496e15; // 2^52\nconst CEPHES_BIGINV = 2.220446049250313e-16; // 2^-52\nconst CEPHES_MACHEP = 1.1102230246251565e-16;\n\n// Continued fraction for the regularized incomplete beta \u2014 forward form\n// (Cephes' incbcf). Returns the bare CF value `pk/qk`; betai / betai_upper\n// then multiply by the log-space prefactor.\nexport function betacf(a: number, b: number, x: number): number {\n let k1 = a;\n let k2 = a + b;\n let k3 = a;\n let k4 = a + 1;\n let k5 = 1;\n let k6 = b - 1;\n let k7 = k4;\n let k8 = a + 2;\n\n let pkm2 = 0;\n let qkm2 = 1;\n let pkm1 = 1;\n let qkm1 = 1;\n let ans = 1;\n let r = 1;\n const thresh = 3 * CEPHES_MACHEP;\n\n for (let n = 0; n < 300; n++) {\n let xk = -((x * k1 * k2) / (k3 * k4));\n let pk = pkm1 + pkm2 * xk;\n let qk = qkm1 + qkm2 * xk;\n pkm2 = pkm1;\n pkm1 = pk;\n qkm2 = qkm1;\n qkm1 = qk;\n\n xk = (x * k5 * k6) / (k7 * k8);\n pk = pkm1 + pkm2 * xk;\n qk = qkm1 + qkm2 * xk;\n pkm2 = pkm1;\n pkm1 = pk;\n qkm2 = qkm1;\n qkm1 = qk;\n\n let t: number;\n if (qk !== 0) r = pk / qk;\n if (r !== 0) {\n t = Math.abs((ans - r) / r);\n ans = r;\n } else {\n t = 1;\n }\n if (t < thresh) return ans;\n\n k1 += 1;\n k2 += 1;\n k3 += 2;\n k4 += 2;\n k5 += 1;\n k6 -= 1;\n k7 += 2;\n k8 += 2;\n\n if (Math.abs(qk) + Math.abs(pk) > CEPHES_BIG) {\n pkm2 *= CEPHES_BIGINV;\n pkm1 *= CEPHES_BIGINV;\n qkm2 *= CEPHES_BIGINV;\n qkm1 *= CEPHES_BIGINV;\n }\n if (Math.abs(qk) < CEPHES_BIGINV || Math.abs(pk) < CEPHES_BIGINV) {\n pkm2 *= CEPHES_BIG;\n pkm1 *= CEPHES_BIG;\n qkm2 *= CEPHES_BIG;\n qkm1 *= CEPHES_BIG;\n }\n }\n return ans;\n}\n\n// Regularized lower incomplete gamma P(a, x) \u2014 Cephes' igam series form.\n// Cephes uses `r += 1; c *= x/r; ans += c` with a `c/ans < MACHEP`\n// termination test. We layer a \u221Aa-scaled iteration cap on top because the\n// series converges in ~O(\u221Aa) steps when x \u2248 a (Poisson-like concentration\n// around n \u2248 x \u2212 a with width \u221Aa) \u2014 chi2cdf / ptukey at huge df otherwise\n// truncate before convergence. Switchover threshold (`x > 1 && x > a`)\n// matches Cephes literally, not NR's `x < a + 1`.\nexport function gammainc(a: number, x: number): number {\n if (x < 0) return 0;\n if (x === 0) return 0;\n if (x > 1 && x > a) return 1 - gammainc_upper(a, x);\n\n let r = a;\n let c = 1;\n let ans = 1;\n const maxIter = Math.max(700, Math.ceil(20 * Math.sqrt(a + 1)));\n for (let i = 0; i < maxIter; i++) {\n r += 1;\n c *= x / r;\n ans += c;\n if (c / ans < CEPHES_MACHEP) break;\n }\n // Log-space final exponentiation: the prefactor `exp(a*ln(x) - x - ln\u0393(a))`\n // can underflow at large a even when ans*ax/a is well within range.\n return Math.exp(Math.log(ans / a) + a * Math.log(x) - x - gammaln(a));\n}\n\n// Regularized upper incomplete gamma Q(a, x) = 1 \u2212 P(a, x) \u2014 Cephes' igamc\n// continued-fraction form. Three-term recurrence on (pkm, qkm) with the\n// `pk = pkm1*z \u2212 pkm2*yc` update; `c` and `y` advance by integer steps each\n// iteration. Big/biginv rescaling keeps |pk| from running away.\nexport function gammainc_upper(a: number, x: number): number {\n let y = 1 - a;\n let z = x + y + 1;\n let c = 0;\n let pkm2 = 1;\n let qkm2 = x;\n let pkm1 = x + 1;\n let qkm1 = z * x;\n let ans = pkm1 / qkm1;\n\n const maxIter = Math.max(300, Math.ceil(20 * Math.sqrt(a + 1)));\n for (let i = 0; i < maxIter; i++) {\n c += 1;\n y += 1;\n z += 2;\n const yc = y * c;\n const pk = pkm1 * z - pkm2 * yc;\n const qk = qkm1 * z - qkm2 * yc;\n let t: number;\n if (qk !== 0) {\n const r = pk / qk;\n t = Math.abs((ans - r) / r);\n ans = r;\n } else {\n t = 1;\n }\n pkm2 = pkm1;\n pkm1 = pk;\n qkm2 = qkm1;\n qkm1 = qk;\n if (Math.abs(pk) > CEPHES_BIG) {\n pkm2 *= CEPHES_BIGINV;\n pkm1 *= CEPHES_BIGINV;\n qkm2 *= CEPHES_BIGINV;\n qkm1 *= CEPHES_BIGINV;\n }\n if (t < CEPHES_MACHEP) break;\n }\n // Log-space final exponentiation \u2014 see comment in gammainc.\n return Math.exp(Math.log(ans) + a * Math.log(x) - x - gammaln(a));\n}\n\n// t-distribution CDF\nexport function tcdf(t: number, df: number): number {\n if (df <= 0) return NaN;\n const x = df / (df + t * t);\n const p = 0.5 * betai(df / 2, 0.5, x);\n return t >= 0 ? 1 - p : p;\n}\n\n// Upper-tail 1 \u2212 tcdf(t, df). Avoids the `1 \u2212 (near-1)` cancellation that\n// silently drove `tTest` p-values to 0 for |t| > ~9 at large df \u2014 e.g. iris\n// SL setosa vs versicolor (t \u2248 10.5, df = 98) where R reports p \u2248 9e-18 but\n// JS underflowed to 0 because tcdf returned a float-rounded 1.0.\nexport function tcdf_upper(t: number, df: number): number {\n if (df <= 0) return NaN;\n const x = df / (df + t * t);\n const tail = 0.5 * betai(df / 2, 0.5, x);\n return t >= 0 ? tail : 1 - tail;\n}\n\n// t-distribution PDF (computed in log space, used by tinv Newton-Raphson)\nexport function tpdf(x: number, df: number): number {\n const logpdf =\n gammaln((df + 1) / 2) -\n gammaln(df / 2) -\n 0.5 * Math.log(Math.PI * df) -\n ((df + 1) / 2) * Math.log(1 + (x * x) / df);\n return Math.exp(logpdf);\n}\n\n// Inverse t CDF.\n// Strategy:\n// \u2022 df = 1 and df = 2 have closed forms \u2014 use them directly.\n// \u2022 df \u2265 3: Newton-Raphson seeded with a Cornish-Fisher correction to the\n// normal quantile, with a damped step and a bisection fallback if Newton\n// fails to converge. Bracket bounds expand outward (doubling) to handle\n// heavy-tailed cases where |t| can exceed hundreds or thousands at\n// extreme p (e.g. qt(1e-10, 5) \u2248 \u2212157).\n// \u2022 Work in the left tail (leftP \u2264 0.5) and flip sign at the end, so that\n// inputs like p = 1 \u2212 1e-15 don't suffer catastrophic cancellation.\nexport function tinv(p: number, df: number): number {\n if (df <= 0) return NaN;\n if (p <= 0) return -Infinity;\n if (p >= 1) return Infinity;\n // equiv-mutant: redundant \u2014 every df branch already converges to 0 at p=0.5 (leftP=0.5)\n if (p === 0.5) return 0;\n\n const upper = p > 0.5;\n const leftP = upper ? 1 - p : p;\n\n let tLeft: number;\n\n if (df === 1) {\n // Cauchy: F\u207B\u00B9(u) = tan(\u03C0(u \u2212 0.5)) = \u2212cot(\u03C0\u00B7u).\n tLeft = -1 / Math.tan(Math.PI * leftP);\n } else if (df === 2) {\n // Closed form: t = (2u\u22121) / \u221A(2u(1\u2212u)).\n tLeft = (2 * leftP - 1) / Math.sqrt(2 * leftP * (1 - leftP));\n } else {\n // Cornish-Fisher seed: first-order correction to the normal quantile.\n const z = norminv(leftP);\n let x = z + (z * (z * z + 1)) / (4 * df);\n\n let converged = false;\n for (let i = 0; i < 60; i++) {\n const cdf = tcdf(x, df);\n const pdf = tpdf(x, df);\n if (!Number.isFinite(cdf) || !(pdf > 0)) break;\n let step = (cdf - leftP) / pdf;\n // Damp the step so we can't jump past a flat tail where PDF \u2192 0.\n const cap = Math.abs(x) + 1;\n if (step > cap) step = cap;\n else if (step < -cap) step = -cap;\n x -= step;\n if (Math.abs(step) < 1e-13 * (Math.abs(x) + 1)) {\n converged = true;\n break;\n }\n }\n\n if (converged && Number.isFinite(x)) {\n tLeft = x;\n } else {\n // Bisection fallback: expand the lower bound until leftP is bracketed.\n let lo = -1;\n const hi = 0;\n for (let i = 0; i < 4000 && tcdf(lo, df) > leftP; i++) lo *= 2;\n let a = lo,\n b = hi;\n for (let i = 0; i < 200; i++) {\n const mid = (a + b) / 2;\n if (tcdf(mid, df) < leftP) a = mid;\n else b = mid;\n if (b - a < 1e-12 * (Math.abs(a) + 1)) break;\n }\n tLeft = (a + b) / 2;\n }\n }\n\n return upper ? -tLeft : tLeft;\n}\n\n// F-distribution CDF\nexport function fcdf(f: number, d1: number, d2: number): number {\n if (d1 <= 0 || d2 <= 0) return NaN;\n // equiv-mutant: subsumed \u2014 for f<=0 the x argument lands outside (0,1) and betai returns 0\n if (f <= 0) return 0;\n return betai(d1 / 2, d2 / 2, (d1 * f) / (d1 * f + d2));\n}\n\n// Upper-tail 1 \u2212 fcdf(f, d1, d2). Same rationale as `tcdf_upper` \u2014 direct\n// computation avoids the 1 \u2212 (near-1) cancellation that underflows ANOVA /\n// Welch-ANOVA p-values at F > ~50.\nexport function fcdf_upper(f: number, d1: number, d2: number): number {\n if (d1 <= 0 || d2 <= 0) return NaN;\n // equiv-mutant: subsumed \u2014 for f<=0 the x argument lands outside (0,1) and betai_upper returns 1\n if (f <= 0) return 1;\n return betai_upper(d1 / 2, d2 / 2, (d1 * f) / (d1 * f + d2));\n}\n\n// Chi-square CDF\nexport function chi2cdf(x: number, k: number): number {\n if (k <= 0) return NaN;\n // equiv-mutant: subsumed \u2014 gammainc returns 0 for the x/2<=0 that x<=0 produces\n if (x <= 0) return 0;\n return gammainc(k / 2, x / 2);\n}\n\n// Chi-square PDF \u2014 used as the Newton derivative in chi2inv. Built in log\n// space to avoid overflow at large k or tiny x.\nexport function chi2pdf(x: number, k: number): number {\n if (k <= 0) return NaN;\n if (x <= 0) return 0;\n const halfK = k / 2;\n return Math.exp((halfK - 1) * Math.log(x) - x / 2 - halfK * Math.log(2) - gammaln(halfK));\n}\n\n// Inverse chi-square CDF \u2014 Newton-Raphson on the central body, bisection\n// fallback for the saturated tails where the \u03C7\u00B2 CDF derivative collapses\n// to ~10\u207B\u00B9\u00B2 and Newton overshoots wildly. The historical implementation was\n// pure bisection with a doubling upper bound (~50 iterations for 1e-10\n// tolerance); this version is much faster on typical inputs while remaining\n// correct on the tails. Uses the Wilson-Hilferty cubic-normal approximation\n// (X/k)^(1/3) \u2248 N(1 \u2212 2/(9k), 2/(9k)) to seed both Newton and the bracket.\nexport function chi2inv(p: number, k: number): number {\n if (p <= 0) return 0;\n if (p >= 1) return Infinity;\n if (k <= 0) return NaN;\n const z = norminv(p);\n const h = 2 / (9 * k);\n let x = k * Math.pow(1 - h + z * Math.sqrt(h), 3);\n if (!(x > 0)) x = Math.max(1e-6, k * 0.01);\n // Newton inside a wide guard: only accept iterations that stay positive\n // and don't exceed twice the current point. If Newton makes <10\u207B\u00B9\u00B2 PDF\n // (deep in the saturated tail) or the step would leave the guard, drop\n // straight to bisection.\n for (let i = 0; i < 30; i++) {\n const f = chi2cdf(x, k) - p;\n if (Math.abs(f) < 1e-12) return x;\n const fp = chi2pdf(x, k);\n if (!(fp > 1e-12)) break;\n const step = f / fp;\n const xNew = x - step;\n if (!(xNew > 0) || xNew > 2 * x || xNew < x / 2) break;\n if (Math.abs(xNew - x) < 1e-12 * Math.max(1, x)) return xNew;\n x = xNew;\n }\n // Bisection fallback. Use WH seed to set a tight initial bracket; expand\n // by doubling if it doesn't cover p (bisect itself refuses unbracketed\n // targets, so this guarantees progress).\n const lo = 0;\n let hi = Math.max(x * 2, k + 10 * Math.sqrt(2 * k));\n for (let i = 0; i < 40 && chi2cdf(hi, k) < p; i++) hi *= 2;\n return bisect((q) => chi2cdf(q, k), p, lo, hi, 1e-10);\n}\n\n// Gauss-Legendre quadrature nodes and weights (computed once, cached)\ninterface GLCache {\n nodes: number[];\n weights: number[];\n n: number;\n}\nlet _glCache: GLCache | undefined;\nexport function _gaussLegendre(n: number): GLCache {\n if (_glCache && _glCache.n === n) return _glCache;\n const nodes = new Array(n),\n weights = new Array(n);\n for (let i = 0; i < Math.ceil(n / 2); i++) {\n let x = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));\n for (let it = 0; it < 100; it++) {\n let pm1 = 1,\n p = x;\n for (let j = 2; j <= n; j++) {\n const pp = ((2 * j - 1) * x * p - (j - 1) * pm1) / j;\n pm1 = p;\n p = pp;\n }\n const dp = (n * (x * p - pm1)) / (x * x - 1);\n const dx = p / dp;\n x -= dx;\n if (Math.abs(dx) < 1e-15) break;\n }\n let pm1 = 1,\n p = x;\n for (let j = 2; j <= n; j++) {\n const pp = ((2 * j - 1) * x * p - (j - 1) * pm1) / j;\n pm1 = p;\n p = pp;\n }\n const dp = (n * (x * p - pm1)) / (x * x - 1);\n const w = 2 / ((1 - x * x) * dp * dp);\n nodes[i] = -x;\n nodes[n - 1 - i] = x;\n weights[i] = w;\n weights[n - 1 - i] = w;\n }\n return (_glCache = { nodes, weights, n });\n}\n\n// Noncentral t CDF \u2014 Gauss-Legendre quadrature of chi-square mixture.\n// P(T \u2264 t | \u03BD, \u03B4) = \u222B\u2080^\u221E \u03A6(t\u221A(s/\u03BD) \u2212 \u03B4) \u00B7 f_\u03C7\u00B2(s; \u03BD) ds\n// Substitution u = \u221As removes density singularity for small \u03BD, giving:\n// = \u222B\u2080^\u221E \u03A6(tu/\u221A\u03BD \u2212 \u03B4) \u00B7 2u^{\u03BD\u22121} e^{\u2212u\u00B2/2} / (2^{\u03BD/2} \u0393(\u03BD/2)) du\nexport function nctcdf(t: number, df: number, delta: number): number {\n if (df <= 0) return NaN;\n if (Math.abs(delta) < 1e-14) return tcdf(t, df);\n const halfDf = df / 2;\n const logC = halfDf * Math.log(2) + gammaln(halfDf);\n const sqrtDf = Math.sqrt(df);\n const uLo = Math.max(0, sqrtDf - 8);\n const uHi = sqrtDf + 8;\n const gl = _gaussLegendre(48);\n const half = (uHi - uLo) / 2,\n mid = (uHi + uLo) / 2;\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const u = mid + half * gl.nodes[i];\n if (u <= 0) continue;\n const logH = Math.log(2) + (df - 1) * Math.log(u) - (u * u) / 2 - logC;\n sum += half * gl.weights[i] * normcdf((t * u) / sqrtDf - delta) * Math.exp(logH);\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// Noncentral F survival P(F' > f) \u2014 Poisson mixture\n// F' = (\u03C7\u00B2_{d1+2J}/d1) / (\u03C7\u00B2_{d2}/d2), J~Poisson(\u03BB/2)\n// P(F'>f) = \u03A3_j P(J=j) \u00D7 P(F(d1+2j, d2) > f \u00D7 d1/(d1+2j))\n// Starts at Poisson mode to avoid underflow for large \u03BB.\nexport function ncf_sf(f: number, d1: number, d2: number, lambda: number): number {\n if (d1 <= 0 || d2 <= 0) return NaN;\n if (f <= 0) return 1;\n if (lambda <= 0) return fcdf_upper(f, d1, d2);\n const halfLam = lambda / 2;\n\n // Large-\u03BB short circuit: the Poisson mass around the mode has width\n // \u03C3 = \u221A(\u03BB/2), so a fixed 500-step window leaves most of the mass\n // unaccounted for when \u03BB is huge. In that regime, P(F'>f) is either\n // \u2248 1 or \u2248 0 for f outside a few \u03C3 of the NCF mean \u2014 use closed-form\n // mean / variance of the NCF (valid for d2 > 4) and short-circuit via\n // normal approximation when f is clearly in the far tail. Without this,\n // power-analysis sample-size solvers probe huge n values (e.g. via\n // bisect up to hi=100000) and get misleading truncated-sum answers,\n // breaking the monotonic-in-n assumption the solver depends on.\n if (halfLam > 500 && d2 > 4) {\n const mean = (d2 / (d2 - 2)) * ((d1 + lambda) / d1);\n const v =\n (2 *\n Math.pow(d2 / (d2 - 2), 2) *\n ((d1 + lambda) * (d1 + lambda) + (d1 + 2 * lambda) * (d2 - 2))) /\n (d1 * d1 * (d2 - 4));\n const sd = Math.sqrt(v);\n const z = (f - mean) / sd;\n if (z < -6) return 1;\n if (z > 6) return 0;\n }\n\n const jMode = Math.max(0, Math.floor(halfLam));\n // Widen the Poisson window for larger \u03BB so we cover \u00B18\u03C3 of the mass.\n const maxSteps = Math.max(500, Math.ceil(8 * Math.sqrt(halfLam + 1)));\n\n function sfTerm(j: number): number {\n const d1j = d1 + 2 * j;\n return fcdf_upper((f * d1) / d1j, d1j, d2);\n }\n\n const logPMode = -halfLam + (jMode > 0 ? jMode * Math.log(halfLam) - gammaln(jMode + 1) : 0);\n const pTerm = Math.exp(logPMode);\n let sum = pTerm * sfTerm(jMode);\n\n let pUp = pTerm;\n for (let j = jMode + 1; j < jMode + maxSteps; j++) {\n pUp *= halfLam / j;\n const contrib = pUp * sfTerm(j);\n sum += contrib;\n if (j - jMode > 10 && pUp < 1e-14) break;\n }\n\n let pDown = pTerm;\n for (let j = jMode - 1; j >= 0 && jMode - j < maxSteps; j--) {\n pDown *= (j + 1) / halfLam;\n const contrib = pDown * sfTerm(j);\n sum += contrib;\n if (jMode - j > 10 && pDown < 1e-14) break;\n }\n\n return Math.min(1, Math.max(0, sum));\n}\n\n// Noncentral chi-square CDF \u2014 Poisson mixture (starts at mode to avoid underflow)\nexport function ncchi2cdf(x: number, k: number, lambda: number): number {\n if (k <= 0) return NaN;\n if (x <= 0) return 0;\n if (lambda <= 0) return chi2cdf(x, k);\n const halfLam = lambda / 2;\n // Large-\u03BB short circuit: use normal approximation for the far tails.\n // Noncentral \u03C7\u00B2 has mean k+\u03BB and variance 2(k+2\u03BB); a |z|>6 tail is < 1e-9.\n if (halfLam > 500) {\n const mean = k + lambda;\n const sd = Math.sqrt(2 * (k + 2 * lambda));\n const z = (x - mean) / sd;\n if (z < -6) return 0;\n if (z > 6) return 1;\n }\n const jMode = Math.max(0, Math.floor(halfLam));\n // Poisson mixture width is \u221A\u03BB/2; widen the scan window with \u221AhalfLam so huge\n // \u03BB doesn't silently truncate the sum (mirrors the ncf_sf fix).\n const maxSteps = Math.max(500, Math.ceil(8 * Math.sqrt(halfLam + 1)));\n\n function cdfTerm(j: number): number {\n return gammainc(k / 2 + j, x / 2);\n }\n\n const logPMode = -halfLam + (jMode > 0 ? jMode * Math.log(halfLam) - gammaln(jMode + 1) : 0);\n const pTerm = Math.exp(logPMode);\n let sum = pTerm * cdfTerm(jMode);\n\n let pUp = pTerm;\n for (let j = jMode + 1; j < jMode + maxSteps; j++) {\n pUp *= halfLam / j;\n const contrib = pUp * cdfTerm(j);\n sum += contrib;\n if (j > jMode + 5 && pUp < 1e-14) break;\n }\n\n let pDown = pTerm;\n for (let j = jMode - 1; j >= 0; j--) {\n pDown *= (j + 1) / halfLam;\n const contrib = pDown * cdfTerm(j);\n sum += contrib;\n if (jMode - j > 5 && pDown < 1e-14) break;\n }\n\n return Math.min(1, Math.max(0, sum));\n}\n\n// \u2500\u2500 2. Generic helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Generic bisection solver: find x in [lo, hi] such that fn(x) \u2248 target.\n// Assumes fn is monotone non-decreasing. Returns NaN if target is not\n// bracketed \u2014 this is intentional: silent clamping to a bracket boundary\n// would let downstream code use a number that isn't actually a root. All\n// existing callers are responsible for expanding hi (or shrinking lo)\n// before delegating to bisect when their target might fall outside.\nexport function bisect(\n fn: (x: number) => number,\n target: number,\n lo: number,\n hi: number,\n tol = 1e-6,\n maxIter = 200\n): number {\n if (fn(lo) > target || fn(hi) < target) return NaN;\n for (let i = 0; i < maxIter; i++) {\n const mid = (lo + hi) / 2;\n if (fn(mid) < target) lo = mid;\n else hi = mid;\n if (hi - lo < tol) break;\n }\n return (lo + hi) / 2;\n}\n\n// \u2500\u2500 2b. Power functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Keyed by test shape, same formulas used by tools/power.tsx. Kept here so\n// the StatsTile (group plot) can report achieved power and the\n// n-per-group needed for 80 % power directly from the observed data.\n\nexport function powerTwoSample(d: number, n: number, alpha: number, tails: number): number {\n const df = 2 * n - 2;\n // Power is the power to detect an effect of this *magnitude* \u2014 match R's\n // pwr::pwr.t.test, which takes |d|. Without abs, a one-tailed call with a\n // negative d (routine when group 2 > group 1) returns ~0, reading as \"this\n // study is hopeless.\" (Two-tailed is already sign-symmetric, so abs is a\n // no-op there.)\n const delta = Math.abs(d) * Math.sqrt(n / 2);\n const tCrit = tinv(1 - alpha / tails, df);\n if (tails === 2) return 1 - nctcdf(tCrit, df, delta) + nctcdf(-tCrit, df, delta);\n return 1 - nctcdf(tCrit, df, delta);\n}\n\nexport function powerPaired(d: number, n: number, alpha: number, tails: number): number {\n const df = n - 1;\n // Magnitude convention \u2014 see powerTwoSample.\n const delta = Math.abs(d) * Math.sqrt(n);\n const tCrit = tinv(1 - alpha / tails, df);\n if (tails === 2) return 1 - nctcdf(tCrit, df, delta) + nctcdf(-tCrit, df, delta);\n return 1 - nctcdf(tCrit, df, delta);\n}\n\nexport function powerOneSample(d: number, n: number, alpha: number, tails: number): number {\n return powerPaired(d, n, alpha, tails);\n}\n\nexport function powerAnova(f: number, n: number, alpha: number, k: number): number {\n const df1 = k - 1,\n df2 = k * (n - 1);\n if (df2 < 1) return 0;\n const lambda = n * k * f * f;\n // Central F at small df has very heavy tails \u2014 e.g. F(1, 1) only reaches\n // p = 0.955 at x = 200 \u2014 so a fixed upper bracket of 200 silently clamps\n // fCrit for \u03B1 \u2264 0.045 at (df1, df2) = (1, 1). Expand the bracket until it\n // covers 1 \u2212 \u03B1 (20 doublings \u2192 ~2\u00B710\u2078, comfortably beyond any realistic\n // fCrit) before delegating to bisect. bisect itself now refuses on an\n // unbracketed target, so we propagate NaN when the expansion fails.\n let hi = 200;\n for (let i = 0; i < 20 && fcdf(hi, df1, df2) < 1 - alpha; i++) hi *= 2;\n const fCrit = bisect((x) => fcdf(x, df1, df2), 1 - alpha, 0, hi);\n if (!Number.isFinite(fCrit)) return NaN;\n return ncf_sf(fCrit, df1, df2, lambda);\n}\n\nexport function powerCorrelation(r: number, n: number, alpha: number, tails: number): number {\n const zr = Math.atanh(r);\n const se = 1 / Math.sqrt(Math.max(1, n - 3));\n const zCrit = norminv(1 - alpha / tails);\n if (tails === 2) return normcdf(Math.abs(zr) / se - zCrit) + normcdf(-Math.abs(zr) / se - zCrit);\n // Magnitude convention \u2014 see powerTwoSample. A negative r with a one-tailed\n // test would otherwise return ~0.\n return normcdf(Math.abs(zr) / se - zCrit);\n}\n\nexport function powerChi2(w: number, n: number, alpha: number, df: number): number {\n const lambda = n * w * w;\n const chiCrit = chi2inv(1 - alpha, df);\n return 1 - ncchi2cdf(chiCrit, df, lambda);\n}\n\n// Cohen's f for ANOVA from group means + pooled within-SD.\n//\n// f = \u03C3_means / \u03C3_within, where \u03C3_means is the **population-style** SD of\n// the group means \u2014 i.e. divides by k, not (k\u22121) \u2014 treating the supplied\n// means as the entire population of cell means rather than a sample. This\n// matches Cohen (1988) and R's pwr::pwr.anova.test, which is what the\n// downstream power calculator is calibrated against. Callers that have a\n// sample SD of group means should multiply by \u221A((k\u22121)/k) before passing\n// in, or recompute from the raw means.\nexport function fFromGroupMeans(meansArr: number[], sd: number): number {\n if (!meansArr.length || sd <= 0) return 0;\n const grandMean = meansArr.reduce((a, b) => a + b, 0) / meansArr.length;\n const sigmaMeans = Math.sqrt(\n meansArr.reduce((s, m) => s + (m - grandMean) * (m - grandMean), 0) / meansArr.length\n );\n return sigmaMeans / sd;\n}\n", "// stats/format.ts \u2014 small zero-dep formatters for p-values and significance.\n//\n// Lives alone so `tests.ts` and `posthoc.ts` can both consume them without\n// drawing a cycle (selectCorrelation in tests.ts wants formatP, selectTest in\n// posthoc.ts wants formatP \u2014 both originally pulled it from the same global\n// surface).\n\n// Map a p-value to the 4-level significance stars used on plots.\n// Non-finite or missing p \u2192 empty string so callers can suppress the label.\nexport function pStars(p: number | null | undefined): string {\n // equiv-mutant: p == null is subsumed by !Number.isFinite(p) \u2014 null/undefined fail it too\n if (p == null || !Number.isFinite(p)) return \"\";\n if (p < 0.0001) return \"****\";\n if (p < 0.001) return \"***\";\n if (p < 0.01) return \"**\";\n if (p < 0.05) return \"*\";\n return \"ns\";\n}\n\n// Format a p-value for display next to a test statistic. Uses scientific\n// notation below 1e-3 (where fixed-point would round to 0) and keeps 3\n// significant digits otherwise.\nexport function formatP(p: number | null | undefined): string {\n // equiv-mutant: p == null is subsumed by !Number.isFinite(p) \u2014 null/undefined fail it too\n if (p == null || !Number.isFinite(p)) return \"\u2014\";\n if (p < 1e-4) return p.toExponential(1);\n if (p < 1e-3) return p.toExponential(2);\n return p.toFixed(4);\n}\n", "// stats/tests.ts \u2014 descriptive helpers + parametric / nonparametric tests +\n// effect sizes.\n//\n// Layout:\n// 3. Sample helpers \u2014 mean, variance, sd, rank-with-ties\n// 4. Shapiro-Wilk normality \u2014 Royston 1995, AS R94\n// 5. Levene / Brown-Forsythe equal-variance test\n// 6. Two-sample location \u2014 t-test (Student / Welch), Mann-Whitney U\n// 7. Two-sample effect sizes \u2014 Cohen's d, Hedges' g, rank-biserial\n// 8. k-sample location \u2014 one-way / Welch ANOVA, Kruskal-Wallis\n// 9. Correlation \u2014 Pearson r, Spearman \u03C1, Kendall \u03C4-b,\n// selectCorrelation auto-picker\n// 10. k-sample effect sizes \u2014 \u03B7\u00B2, \u03B5\u00B2\n\nimport { bisect, chi2cdf, fcdf_upper, gammaln, nctcdf, norminv, normsf, tcdf_upper } from \"./dist\";\nimport { formatP } from \"./format\";\nimport type {\n ANOVAResult,\n KendallResult,\n KruskalWallisResult,\n LeveneResult,\n MannWhitneyResult,\n PairwiseComplete,\n PearsonResult,\n RankWithTies,\n ShapiroWilkResult,\n SpearmanResult,\n TTestResult,\n} from \"./types\";\n\n// \u2500\u2500 3. Sample helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function sampleMean(x: number[]): number {\n const n = x.length;\n if (n === 0) return NaN;\n let s = 0;\n for (let i = 0; i < n; i++) s += x[i];\n return s / n;\n}\n\n// Sample variance with (n-1) denominator (Bessel-corrected). Welford's\n// online algorithm: one pass, no need to compute the mean separately, and\n// numerically more robust than the naive E[X\u00B2] \u2212 E[X]\u00B2 formula (which\n// catastrophically cancels when the data have a large offset). The classic\n// two-pass algorithm is comparable in accuracy on most inputs, but Welford\n// avoids a second loop and pre-computed mean, which is cleaner and slightly\n// faster on large arrays.\nexport function sampleVariance(x: number[]): number {\n const n = x.length;\n if (n < 2) return NaN;\n let mean = 0,\n M2 = 0;\n for (let i = 0; i < n; i++) {\n const d = x[i] - mean;\n mean += d / (i + 1);\n M2 += d * (x[i] - mean);\n }\n return M2 / (n - 1);\n}\n\nexport function sampleSD(x: number[]): number {\n return Math.sqrt(sampleVariance(x));\n}\n\n// Midrank assignment with ties \u2014 returns ranks[i] = 1-based rank of x[i],\n// averaging ranks for tied values. Also returns the sum of (t\u00B3\u2212t)/12 for each\n// tie group, needed as the tie-correction term in Mann-Whitney / Kruskal-Wallis.\nexport function rankWithTies(x: number[]): RankWithTies {\n const n = x.length;\n const idx: [number, number][] = x\n .map((v, i): [number, number] => [v, i])\n .sort((a, b) => a[0] - b[0]);\n const ranks = new Array(n);\n let tieCorrection = 0;\n let i = 0;\n while (i < n) {\n let j = i;\n while (j + 1 < n && idx[j + 1][0] === idx[i][0]) j++;\n const avgRank = (i + j + 2) / 2; // 1-based average of ranks [i+1 .. j+1]\n for (let k = i; k <= j; k++) ranks[idx[k][1]] = avgRank;\n const t = j - i + 1;\n if (t > 1) tieCorrection += t * t * t - t;\n i = j + 1;\n }\n return { ranks, tieCorrection };\n}\n\n// \u2500\u2500 4. Normality: Shapiro-Wilk (Royston 1995, AS R94) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function shapiroWilk(x: number[]): ShapiroWilkResult {\n const n = x.length;\n if (n < 3 || n > 5000) {\n return { W: NaN, p: NaN, error: \"Shapiro-Wilk requires 3 \u2264 n \u2264 5000\" };\n }\n const sorted = [...x].sort((a, b) => a - b);\n\n // Expected values of normal order statistics via Blom's approximation:\n // m_i \u2248 \u03A6\u207B\u00B9((i \u2212 3/8) / (n + 1/4))\n // These are the \"m\" vector in AS R94.\n const m = new Array(n);\n for (let i = 0; i < n; i++) {\n m[i] = norminv((i + 1 - 3 / 8) / (n + 1 / 4));\n }\n let mm = 0;\n for (let i = 0; i < n; i++) mm += m[i] * m[i];\n\n // Coefficients a_i (Royston 1992 analytical form, AS R94).\n const a = new Array(n);\n const u = 1 / Math.sqrt(n);\n const sqrtMm = Math.sqrt(mm);\n\n if (n === 3) {\n // Exact for n = 3: a_1 = \u221A(1/2), a_3 = \u2212a_1, a_2 = 0.\n a[0] = -Math.SQRT1_2;\n a[1] = 0;\n a[2] = Math.SQRT1_2;\n } else {\n const an =\n m[n - 1] / sqrtMm +\n 0.221157 * u -\n 0.147981 * u * u -\n 2.07119 * u * u * u +\n 4.434685 * u * u * u * u -\n 2.706056 * u * u * u * u * u;\n a[n - 1] = an;\n a[0] = -an;\n\n if (n >= 6) {\n const an1 =\n m[n - 2] / sqrtMm +\n 0.042981 * u -\n 0.293762 * u * u -\n 1.75246 * u * u * u +\n 5.682633 * u * u * u * u -\n 3.582633 * u * u * u * u * u;\n a[n - 2] = an1;\n a[1] = -an1;\n\n // Epsilon correction for middle coefficients (AS R94).\n const phi =\n (mm - 2 * m[n - 1] * m[n - 1] - 2 * m[n - 2] * m[n - 2]) /\n (1 - 2 * an * an - 2 * an1 * an1);\n const sqrtPhi = Math.sqrt(phi);\n for (let i = 2; i < n - 2; i++) a[i] = m[i] / sqrtPhi;\n } else {\n // n = 4 or 5: only tail coefficients, simpler correction.\n const phi = (mm - 2 * m[n - 1] * m[n - 1]) / (1 - 2 * an * an);\n const sqrtPhi = Math.sqrt(phi);\n for (let i = 1; i < n - 1; i++) a[i] = m[i] / sqrtPhi;\n }\n }\n\n // W = (\u03A3 a_i \u00B7 x_(i))\u00B2 / \u03A3 (x_i \u2212 x\u0304)\u00B2\n let xbar = 0;\n for (let i = 0; i < n; i++) xbar += sorted[i];\n xbar /= n;\n let num = 0,\n den = 0;\n for (let i = 0; i < n; i++) {\n num += a[i] * sorted[i];\n const d = sorted[i] - xbar;\n den += d * d;\n }\n if (den === 0) return { W: NaN, p: NaN, error: \"Zero variance\" };\n // Clamp to [0, 1] \u2014 floating-point can produce W = 1 + \u03B5 for a perfect\n // linear sample (e.g. n=3 [1,2,3]), which would break asin(\u221AW) below.\n const W = Math.max(0, Math.min(1, (num * num) / den));\n\n // P-value via Royston 1995 normalizing transform.\n let p: number;\n if (n === 3) {\n // Exact distribution at n=3: p = 6 \u00B7 (asin(\u221AW) \u2212 asin(\u221A(3/4))) / \u03C0\n const pi6 = 6 / Math.PI;\n const stqr = Math.asin(Math.sqrt(3 / 4));\n p = pi6 * (Math.asin(Math.sqrt(W)) - stqr);\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n } else if (n <= 11) {\n // Polynomial in n for gamma and mu, log-transform on (gamma \u2212 ln(1\u2212W)).\n const gamma = -2.273 + 0.459 * n;\n const mu = 0.544 - 0.39978 * n + 0.025054 * n * n - 0.0006714 * n * n * n;\n const sigma = Math.exp(1.3822 - 0.77857 * n + 0.062767 * n * n - 0.0020322 * n * n * n);\n const y = -Math.log(gamma - Math.log(1 - W));\n const z = (y - mu) / sigma;\n p = normsf(z);\n } else {\n // n \u2265 12: log(1\u2212W) is approximately normal after transform.\n const lnN = Math.log(n);\n const mu = -1.5861 - 0.31082 * lnN - 0.083751 * lnN * lnN + 0.0038915 * lnN * lnN * lnN;\n const sigma = Math.exp(-0.4803 - 0.082676 * lnN + 0.0030302 * lnN * lnN);\n const y = Math.log(1 - W);\n const z = (y - mu) / sigma;\n p = normsf(z);\n }\n\n return { W, p };\n}\n\n// \u2500\u2500 5. Equal-variance tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function leveneTest(groups: number[][]): LeveneResult {\n if (groups.length < 2) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"\u22652 groups required\" };\n // A singleton group has median = its own value, so every absolute deviation\n // is exactly 0: it carries no dispersion information and would silently\n // inflate the error df. car::leveneTest requires n\u22652 per group, so drop the\n // undersized ones before computing the deviation ANOVA.\n const valid = groups.filter((g) => g.length >= 2);\n if (valid.length < 2) {\n return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"Not enough observations\" };\n }\n const k = valid.length;\n // Absolute deviations from the group median.\n const devs = valid.map((g) => {\n const s = [...g].sort((a, b) => a - b);\n const n = s.length;\n const med = n % 2 === 0 ? (s[n / 2 - 1] + s[n / 2]) / 2 : s[Math.floor(n / 2)];\n return g.map((v) => Math.abs(v - med));\n });\n // One-way ANOVA on deviations.\n let Ntot = 0;\n let grandSum = 0;\n for (const d of devs) {\n Ntot += d.length;\n for (const v of d) grandSum += v;\n }\n const grandMean = grandSum / Ntot;\n let ssBetween = 0,\n ssWithin = 0;\n for (const d of devs) {\n const n = d.length;\n if (n === 0) continue;\n let s = 0;\n for (const v of d) s += v;\n const m = s / n;\n ssBetween += n * (m - grandMean) * (m - grandMean);\n for (const v of d) ssWithin += (v - m) * (v - m);\n }\n const df1 = k - 1;\n const df2 = Ntot - k;\n if (ssWithin === 0) {\n return {\n F: NaN,\n df1,\n df2,\n p: NaN,\n error: \"Data are essentially constant (zero within-group dispersion)\",\n };\n }\n const F = ssBetween / df1 / (ssWithin / df2);\n const p = fcdf_upper(F, df1, df2);\n return { F, df1, df2, p };\n}\n\n// \u2500\u2500 6. Two-sample location tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function tTest(x: number[], y: number[], opts: { equalVar?: boolean } = {}): TTestResult {\n const equalVar = opts.equalVar !== false;\n const n1 = x.length,\n n2 = y.length;\n if (n1 < 2 || n2 < 2) {\n return { t: NaN, df: 0, p: NaN, error: \"Each group needs n\u22652\" };\n }\n const m1 = sampleMean(x),\n m2 = sampleMean(y);\n const v1 = sampleVariance(x),\n v2 = sampleVariance(y);\n if (v1 === 0 && v2 === 0) {\n return {\n t: NaN,\n df: NaN,\n p: NaN,\n mean1: m1,\n mean2: m2,\n var1: v1,\n var2: v2,\n n1,\n n2,\n error: \"Data are essentially constant (zero variance in both groups)\",\n };\n }\n let t: number, df: number;\n if (equalVar) {\n const sp2 = ((n1 - 1) * v1 + (n2 - 1) * v2) / (n1 + n2 - 2);\n const se = Math.sqrt(sp2 * (1 / n1 + 1 / n2));\n t = (m1 - m2) / se;\n df = n1 + n2 - 2;\n } else {\n const se = Math.sqrt(v1 / n1 + v2 / n2);\n t = (m1 - m2) / se;\n const num = (v1 / n1 + v2 / n2) ** 2;\n const den = (v1 / n1) ** 2 / (n1 - 1) + (v2 / n2) ** 2 / (n2 - 1);\n df = num / den;\n }\n const p = 2 * tcdf_upper(Math.abs(t), df);\n return { t, df, p, mean1: m1, mean2: m2, var1: v1, var2: v2, n1, n2 };\n}\n\nexport function mannWhitneyU(x: number[], y: number[]): MannWhitneyResult {\n const n1 = x.length,\n n2 = y.length;\n if (n1 < 1 || n2 < 1) return { U: NaN, z: NaN, p: NaN, error: \"Empty group\" };\n const all = x.concat(y);\n // All observations identical \u2192 the tie-corrected variance collapses to\n // exactly 0, the test is undefined, and an unguarded run reports z = 0,\n // p = 1 (\"not significant\") \u2014 a false negative. Mirror the kruskalWallis\n // guard and surface an error instead.\n let allTied = true;\n for (let i = 1; i < all.length; i++) {\n if (all[i] !== all[0]) {\n allTied = false;\n break;\n }\n }\n if (allTied) return { U: NaN, z: NaN, p: NaN, error: \"Data are essentially constant\" };\n const { ranks, tieCorrection } = rankWithTies(all);\n let R1 = 0;\n for (let i = 0; i < n1; i++) R1 += ranks[i];\n const U1 = R1 - (n1 * (n1 + 1)) / 2;\n const U2 = n1 * n2 - U1;\n const U = Math.min(U1, U2);\n const N = n1 + n2;\n const muU = (n1 * n2) / 2;\n // \u03C3_U\u00B2 with tie correction (Lehmann 1975):\n // \u03C3\u00B2 = n1\u00B7n2/12 \u00B7 [(N+1) \u2212 \u03A3(t\u00B3\u2212t) / (N(N\u22121))]\n const sigma2 = ((n1 * n2) / 12) * (N + 1 - tieCorrection / (N * (N - 1)));\n const sigmaU = Math.sqrt(sigma2);\n const diff = U1 - muU;\n let z: number;\n if (sigmaU === 0) z = 0;\n else if (diff > 0) z = (diff - 0.5) / sigmaU;\n else if (diff < 0) z = (diff + 0.5) / sigmaU;\n else z = 0;\n const p = 2 * normsf(Math.abs(z));\n return { U, U1, U2, z, p, n1, n2 };\n}\n\n// \u2500\u2500 7. Two-sample effect sizes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function cohenD(x: number[], y: number[]): number {\n const n1 = x.length,\n n2 = y.length;\n if (n1 < 2 || n2 < 2) return NaN;\n const m1 = sampleMean(x),\n m2 = sampleMean(y);\n const v1 = sampleVariance(x),\n v2 = sampleVariance(y);\n const sp2 = ((n1 - 1) * v1 + (n2 - 1) * v2) / (n1 + n2 - 2);\n if (sp2 === 0) return NaN;\n return (m1 - m2) / Math.sqrt(sp2);\n}\n\nexport function hedgesG(x: number[], y: number[]): number {\n const d = cohenD(x, y);\n if (!Number.isFinite(d)) return d;\n const df = x.length + y.length - 2;\n if (df < 1) return NaN;\n // Exact small-sample correction factor:\n // J(df) = \u0393(df/2) / (\u0393((df\u22121)/2) \u00B7 \u221A(df/2))\n const J = Math.exp(gammaln(df / 2) - gammaln((df - 1) / 2) - 0.5 * Math.log(df / 2));\n return d * J;\n}\n\n// Rank-biserial correlation from Mann-Whitney U (Kerby 2014).\nexport function rankBiserial(U1: number, n1: number, n2: number): number {\n if (n1 * n2 === 0) return NaN;\n return 1 - (2 * U1) / (n1 * n2);\n}\n\n// 95 % confidence interval for Cohen's d (or d_av) on two independent samples,\n// using the noncentral-t pivot of Cumming & Finch 2001.\nexport function cohenDCI(\n d: number,\n n1: number,\n n2: number,\n conf?: number\n): { lo: number; hi: number } {\n const c = conf == null ? 0.95 : conf;\n if (!Number.isFinite(d) || n1 < 2 || n2 < 2 || c <= 0 || c >= 1) {\n return { lo: NaN, hi: NaN };\n }\n const alpha = 1 - c;\n const seFactor = Math.sqrt(1 / n1 + 1 / n2);\n const tObs = d / seFactor;\n const df = n1 + n2 - 2;\n const halfRange = Math.abs(tObs) + 20;\n const negNct = (lam: number): number => -nctcdf(tObs, df, lam);\n const lambdaLo = bisect(negNct, -(1 - alpha / 2), -halfRange, halfRange);\n const lambdaHi = bisect(negNct, -alpha / 2, -halfRange, halfRange);\n if (!Number.isFinite(lambdaLo) || !Number.isFinite(lambdaHi)) {\n return { lo: NaN, hi: NaN };\n }\n return { lo: lambdaLo * seFactor, hi: lambdaHi * seFactor };\n}\n\n// \u2500\u2500 8. k-sample location tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function oneWayANOVA(groups: number[][]): ANOVAResult {\n const k = groups.length;\n if (k < 2) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"\u22652 groups required\" };\n let Ntot = 0;\n let grandSum = 0;\n for (const g of groups) {\n Ntot += g.length;\n for (const v of g) grandSum += v;\n }\n if (Ntot <= k) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"Not enough observations\" };\n const grandMean = grandSum / Ntot;\n let ssBetween = 0,\n ssWithin = 0;\n for (const g of groups) {\n const n = g.length;\n if (n === 0) continue;\n let s = 0;\n for (const v of g) s += v;\n const m = s / n;\n ssBetween += n * (m - grandMean) * (m - grandMean);\n for (const v of g) ssWithin += (v - m) * (v - m);\n }\n const df1 = k - 1;\n const df2 = Ntot - k;\n if (ssWithin === 0) {\n return {\n F: NaN,\n df1,\n df2,\n p: NaN,\n ssBetween,\n ssWithin,\n grandMean,\n error: \"Data are essentially constant (zero within-group dispersion)\",\n };\n }\n const F = ssBetween / df1 / (ssWithin / df2);\n const p = fcdf_upper(F, df1, df2);\n return { F, df1, df2, p, ssBetween, ssWithin, grandMean };\n}\n\nexport function welchANOVA(groups: number[][]): ANOVAResult {\n const k = groups.length;\n if (k < 2) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"\u22652 groups required\" };\n const ns = groups.map((g) => g.length);\n if (ns.some((n) => n < 2)) {\n return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"Each group needs n\u22652\" };\n }\n const means = groups.map(sampleMean);\n const vars_ = groups.map(sampleVariance);\n if (vars_.some((v) => v === 0)) {\n return {\n F: NaN,\n df1: k - 1,\n df2: NaN,\n p: NaN,\n error: \"Data are essentially constant (zero variance in at least one group)\",\n };\n }\n const w = vars_.map((v, i) => ns[i] / v);\n const Wsum = w.reduce((a, b) => a + b, 0);\n const m = w.reduce((a, wi, i) => a + wi * means[i], 0) / Wsum;\n let num = 0;\n for (let i = 0; i < k; i++) num += w[i] * (means[i] - m) * (means[i] - m);\n num /= k - 1;\n let h = 0;\n for (let i = 0; i < k; i++) {\n const term = 1 - w[i] / Wsum;\n h += (term * term) / (ns[i] - 1);\n }\n const den = 1 + ((2 * (k - 2)) / (k * k - 1)) * h;\n const F = num / den;\n const df1 = k - 1;\n const df2 = (k * k - 1) / (3 * h);\n const p = fcdf_upper(F, df1, df2);\n return { F, df1, df2, p };\n}\n\nexport function kruskalWallis(groups: number[][]): KruskalWallisResult {\n const k = groups.length;\n if (k < 2) return { H: NaN, df: 0, p: NaN, error: \"\u22652 groups required\" };\n const all: number[] = [];\n const owner: number[] = [];\n for (let i = 0; i < k; i++) {\n for (const v of groups[i]) {\n all.push(v);\n owner.push(i);\n }\n }\n const N = all.length;\n if (N <= k) return { H: NaN, df: 0, p: NaN, error: \"Not enough observations\" };\n let allTied = true;\n for (let i = 1; i < N; i++) {\n if (all[i] !== all[0]) {\n allTied = false;\n break;\n }\n }\n if (allTied) {\n return { H: NaN, df: k - 1, p: NaN, error: \"Data are essentially constant\" };\n }\n const { ranks, tieCorrection } = rankWithTies(all);\n const R = new Array(k).fill(0);\n for (let i = 0; i < N; i++) R[owner[i]] += ranks[i];\n let sumR2n = 0;\n for (let i = 0; i < k; i++) sumR2n += (R[i] * R[i]) / groups[i].length;\n let H = (12 / (N * (N + 1))) * sumR2n - 3 * (N + 1);\n const C = 1 - tieCorrection / (N * N * N - N);\n if (C > 0) H /= C;\n const df = k - 1;\n const p = 1 - chi2cdf(H, df);\n return { H, df, p };\n}\n\n// \u2500\u2500 9. Correlation (paired bivariate) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Drop rows with non-finite x or y; return the cleaned pair.\nfunction _pairwiseComplete(x: number[], y: number[]): PairwiseComplete {\n const n = Math.min(x.length, y.length);\n const xs: number[] = [];\n const ys: number[] = [];\n for (let i = 0; i < n; i++) {\n const xv = x[i],\n yv = y[i];\n if (Number.isFinite(xv) && Number.isFinite(yv)) {\n xs.push(xv);\n ys.push(yv);\n }\n }\n return { xs, ys, n: xs.length };\n}\n\nexport function pearsonCorrelation(\n x: number[],\n y: number[],\n opts: { conf?: number } = {}\n): PearsonResult {\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return {\n r: NaN,\n t: NaN,\n df: 0,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: \"Need \u22653 complete pairs\",\n };\n }\n const mx = sampleMean(xs);\n const my = sampleMean(ys);\n let S2x = 0,\n S2y = 0,\n Sxy = 0;\n for (let i = 0; i < n; i++) {\n const dx = xs[i] - mx;\n const dy = ys[i] - my;\n S2x += dx * dx;\n S2y += dy * dy;\n Sxy += dx * dy;\n }\n if (S2x === 0 || S2y === 0) {\n return {\n r: NaN,\n t: NaN,\n df: n - 2,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: \"Data are essentially constant (zero variance in x or y)\",\n };\n }\n let r = Sxy / Math.sqrt(S2x * S2y);\n if (r > 1) r = 1;\n if (r < -1) r = -1;\n const df = n - 2;\n const conf = opts.conf == null ? 0.95 : opts.conf;\n const ci = { lo: NaN, hi: NaN };\n if (n >= 4) {\n const z = Math.atanh(r);\n const se = 1 / Math.sqrt(n - 3);\n const zcrit = norminv(1 - (1 - conf) / 2);\n ci.lo = Math.tanh(z - zcrit * se);\n ci.hi = Math.tanh(z + zcrit * se);\n }\n const oneMinusR2 = Math.max(0, 1 - r * r);\n const t = oneMinusR2 === 0 ? (r > 0 ? Infinity : -Infinity) : r * Math.sqrt(df / oneMinusR2);\n const p = oneMinusR2 === 0 ? 0 : 2 * tcdf_upper(Math.abs(t), df);\n return { r, t, df, p, n, ci };\n}\n\nexport function spearmanCorrelation(\n x: number[],\n y: number[],\n opts: { conf?: number } = {}\n): SpearmanResult {\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return {\n rho: NaN,\n t: NaN,\n df: 0,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: \"Need \u22653 complete pairs\",\n };\n }\n const { ranks: rx } = rankWithTies(xs);\n const { ranks: ry } = rankWithTies(ys);\n // Reuse the Pearson implementation on the rank vectors.\n const pearsonOnRanks = pearsonCorrelation(rx, ry, opts);\n if (pearsonOnRanks.error) {\n return {\n rho: NaN,\n t: NaN,\n df: n - 2,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: pearsonOnRanks.error,\n };\n }\n const rho = pearsonOnRanks.r;\n const df = n - 2;\n const oneMinusRho2 = Math.max(0, 1 - rho * rho);\n const t =\n oneMinusRho2 === 0 ? (rho > 0 ? Infinity : -Infinity) : rho * Math.sqrt(df / oneMinusRho2);\n const p = oneMinusRho2 === 0 ? 0 : 2 * tcdf_upper(Math.abs(t), df);\n const conf = opts.conf == null ? 0.95 : opts.conf;\n const ci = { lo: NaN, hi: NaN };\n if (n >= 4) {\n const z = Math.atanh(rho);\n const se = Math.sqrt((1 + (rho * rho) / 2) / (n - 3));\n const zcrit = norminv(1 - (1 - conf) / 2);\n ci.lo = Math.tanh(z - zcrit * se);\n ci.hi = Math.tanh(z + zcrit * se);\n }\n return { rho, t, df, p, n, ci };\n}\n\nexport function kendallTau(\n x: number[],\n y: number[],\n opts: Record = {}\n): KendallResult {\n void opts;\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return { tau: NaN, z: NaN, p: NaN, n, S: 0, error: \"Need \u22653 complete pairs\" };\n }\n let S = 0;\n for (let i = 0; i < n - 1; i++) {\n const xi = xs[i],\n yi = ys[i];\n for (let j = i + 1; j < n; j++) {\n const dx = xs[j] - xi;\n const dy = ys[j] - yi;\n if (dx === 0 || dy === 0) continue;\n S += dx > 0 === dy > 0 ? 1 : -1;\n }\n }\n const tieGroups = (arr: number[]): number[] => {\n // equiv-mutant: .slice() copy keeps xs intact; in-place sort is harmless only as xs is unused after\n const sorted = arr.slice().sort((a, b) => a - b);\n const groups: number[] = [];\n let i = 0;\n while (i < n) {\n let j = i;\n while (j + 1 < n && sorted[j + 1] === sorted[i]) j++;\n const t = j - i + 1;\n if (t > 1) groups.push(t);\n i = j + 1;\n }\n return groups;\n };\n const tx = tieGroups(xs);\n const ty = tieGroups(ys);\n const sumPairs = (g: number[]): number => g.reduce((s, t) => s + (t * (t - 1)) / 2, 0);\n const n0 = (n * (n - 1)) / 2;\n const n1 = sumPairs(tx);\n const n2 = sumPairs(ty);\n if (n0 - n1 <= 0 || n0 - n2 <= 0) {\n return {\n tau: NaN,\n z: NaN,\n p: NaN,\n n,\n S,\n error: \"Data are essentially constant (all ties in x or y)\",\n };\n }\n const tau = S / Math.sqrt((n0 - n1) * (n0 - n2));\n let varS = (n * (n - 1) * (2 * n + 5)) / 18;\n for (const t of tx) varS -= (t * (t - 1) * (2 * t + 5)) / 18;\n for (const t of ty) varS -= (t * (t - 1) * (2 * t + 5)) / 18;\n if (tx.length && ty.length && n >= 3) {\n const sx3 = tx.reduce((s, t) => s + t * (t - 1) * (t - 2), 0);\n const sy3 = ty.reduce((s, t) => s + t * (t - 1) * (t - 2), 0);\n varS += (sx3 * sy3) / (9 * n * (n - 1) * (n - 2));\n const sx2 = tx.reduce((s, t) => s + t * (t - 1), 0);\n const sy2 = ty.reduce((s, t) => s + t * (t - 1), 0);\n varS += (sx2 * sy2) / (2 * n * (n - 1));\n }\n if (varS <= 0) {\n return { tau, z: NaN, p: NaN, n, S };\n }\n const z = S / Math.sqrt(varS);\n const p = 2 * normsf(Math.abs(z));\n return { tau, z, p, n, S };\n}\n\n// Diagnostic + recommendation for a paired scatter dataset.\ninterface SelectCorrelationAxis {\n axis: \"x\" | \"y\";\n n: number;\n W: number | null;\n p: number | null;\n normal: boolean | null;\n note?: string;\n}\ninterface SelectCorrelationResult {\n n: number;\n normality: SelectCorrelationAxis[];\n allNormal: boolean;\n recommendation: { test: \"pearson\"; reason: string };\n suggestion?: { test: \"spearman\"; reason: string };\n}\n\nexport function selectCorrelation(\n x: number[],\n y: number[],\n opts: { alphaNormality?: number } = {}\n): SelectCorrelationResult {\n const alphaN = opts.alphaNormality != null ? opts.alphaNormality : 0.05;\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return {\n n,\n normality: [],\n allNormal: false,\n recommendation: { test: \"pearson\", reason: \"Need \u22653 complete pairs to test a correlation.\" },\n };\n }\n const swCheck = (arr: number[], axis: \"x\" | \"y\"): SelectCorrelationAxis => {\n if (arr.length < 3) {\n return { axis, n: arr.length, W: null, p: null, normal: null, note: \"n<3\" };\n }\n const sw = shapiroWilk(arr);\n if (sw.error) {\n return { axis, n: arr.length, W: null, p: null, normal: null, note: sw.error };\n }\n return { axis, n: arr.length, W: sw.W, p: sw.p, normal: sw.p >= alphaN };\n };\n const normality: SelectCorrelationAxis[] = [swCheck(xs, \"x\"), swCheck(ys, \"y\")];\n const allKnownNormal = normality.every((r) => r.normal === true);\n const flagged = normality.filter((r) => r.normal === false);\n const test = \"pearson\" as const;\n const baseDefault =\n \"Default pick: Pearson product-moment correlation. Pearson is the most powerful test when both axes are approximately normal; Spearman and Kendall stay available as rank-based alternatives.\";\n const swNarrative = ((): string => {\n if (flagged.length === 0 && allKnownNormal) {\n return `Shapiro-Wilk did not reject normality on x or y at \u03B1 = ${alphaN}.`;\n }\n if (flagged.length > 0) {\n const labels = flagged\n .map((r) => `${r.axis} (W=${(r.W as number).toFixed(3)}, p=${formatP(r.p)})`)\n .join(\", \");\n return `Shapiro-Wilk flagged ${labels} as non-normal at \u03B1 = ${alphaN}.`;\n }\n return \"Shapiro-Wilk could not run on one or both axes (n < 3).\";\n })();\n let suggestion: { test: \"spearman\"; reason: string } | undefined;\n let suggestionNarrative = \"\";\n if (flagged.length > 0) {\n suggestion = {\n test: \"spearman\",\n reason:\n \"Shapiro-Wilk flagged at least one axis as non-normal. Spearman ranks both axes before correlating, which is more robust to heavy tails and outliers than Pearson.\",\n };\n suggestionNarrative =\n \" If the non-normality looks substantive (heavy tails, strong skew, ordinal data), consider switching to Spearman \u03C1 from the test dropdown; for very small or heavily-tied samples Kendall \u03C4-b is a further alternative.\";\n }\n const overrideHint =\n \" You can override this pick from the stats panel's per-test dropdown; the trace below shows the diagnostics the recommendation is based on.\";\n const out: SelectCorrelationResult = {\n n,\n normality,\n allNormal: allKnownNormal,\n recommendation: {\n test,\n reason: `${baseDefault} ${swNarrative}${suggestionNarrative}${overrideHint}`,\n },\n };\n if (suggestion) out.suggestion = suggestion;\n return out;\n}\n\n// \u2500\u2500 10. k-sample effect sizes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function etaSquared(groups: number[][]): number {\n const a = oneWayANOVA(groups);\n if (a.error) return NaN;\n const ssTotal = (a.ssBetween as number) + (a.ssWithin as number);\n return ssTotal === 0 ? 0 : (a.ssBetween as number) / ssTotal;\n}\n\nexport function epsilonSquared(groups: number[][]): number {\n const kw = kruskalWallis(groups);\n if (kw.error) return NaN;\n let N = 0;\n for (const g of groups) N += g.length;\n return N > 1 ? kw.H / (N - 1) : NaN;\n}\n", "// stats/posthoc.ts \u2014 studentized-range distribution, all-pairs post-hocs,\n// p-adjust, compact-letter-display, and the auto test-picker (`selectTest`).\n//\n// Layout:\n// 10. Studentized range distribution \u2014 _wprob, ptukey, qtukey\n// 11. Post-hoc tests \u2014 Tukey HSD, Games-Howell, Dunn, BH\n// 12. Compact letter display\n// 13. Automatic test selection \u2014 selectTest\n\nimport { _gaussLegendre, chi2inv, gammaln, normcdf, normsf } from \"./dist\";\nimport { formatP } from \"./format\";\nimport {\n leveneTest,\n oneWayANOVA,\n rankWithTies,\n sampleMean,\n sampleVariance,\n shapiroWilk,\n} from \"./tests\";\nimport type {\n DunnPair,\n DunnResult,\n GamesHowellPair,\n GamesHowellResult,\n NormalityResult,\n SelectTestKind,\n SelectTestPostHoc,\n SelectTestResult,\n TukeyPair,\n TukeyResult,\n} from \"./types\";\n\n// \u2500\u2500 10. Studentized range distribution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// P(range of k standard normals > w). Tail-accurate companion to `_wprob`.\nexport function _wprob_upper(w: number, k: number): number {\n if (w <= 0) return 1;\n const gl = _gaussLegendre(48);\n const peak = -w / 2;\n const lo = Math.min(-8, peak - 8);\n const hi = Math.max(8, peak + 8);\n const half = (hi - lo) / 2,\n mid = (hi + lo) / 2;\n const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const u = mid + half * gl.nodes[i];\n const phiU = invSqrt2Pi * Math.exp(-0.5 * u * u);\n const a = normsf(u);\n const aMinusB = normsf(u + w);\n if (a <= 0 || aMinusB <= 0) continue;\n const b = a - aMinusB;\n let geoSum = 0;\n let term = Math.pow(a, k - 2);\n const ratio = b / a;\n for (let j = 0; j < k - 1; j++) {\n geoSum += term;\n term *= ratio;\n }\n const integrand = k * phiU * aMinusB * geoSum;\n sum += half * gl.weights[i] * integrand;\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// P(range of k standard normals \u2264 w).\nexport function _wprob(w: number, k: number): number {\n if (w <= 0) return 0;\n const gl = _gaussLegendre(48);\n const lo = -8,\n hi = 8;\n const half = (hi - lo) / 2,\n mid = (hi + lo) / 2;\n const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const u = mid + half * gl.nodes[i];\n const phiU = invSqrt2Pi * Math.exp(-0.5 * u * u);\n let diff: number;\n // equiv-mutant: the `else if (u+w<=0)` branch is byte-identical to `else` \u2014 the split is vestigial\n if (u >= 0) {\n diff = normsf(u) - normsf(u + w);\n } else if (u + w <= 0) {\n diff = normcdf(u + w) - normcdf(u);\n } else {\n diff = normcdf(u + w) - normcdf(u);\n }\n if (diff <= 0) continue;\n const term = k * phiU * Math.pow(diff, k - 1);\n sum += half * gl.weights[i] * term;\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// P(Q \u2264 q | k groups, df error degrees of freedom)\nexport function ptukey(q: number, k: number, df: number): number {\n if (q <= 0) return 0;\n if (k < 2 || df < 1) return NaN;\n const gl = _gaussLegendre(48);\n const chiLo = chi2inv(1e-10, df);\n const chiHi = chi2inv(1 - 1e-10, df);\n const yLo = 0.5 * Math.log(Math.max(chiLo, 1e-300) / df);\n const yHi = 0.5 * Math.log(chiHi / df);\n const halfDf = df / 2;\n const logConst = Math.log(2) + halfDf * Math.log(halfDf) - gammaln(halfDf);\n const halfW = (yHi - yLo) / 2;\n const midW = (yHi + yLo) / 2;\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const y = midW + halfW * gl.nodes[i];\n const s = Math.exp(y);\n const logFS = logConst + df * y - (df * s * s) / 2;\n const fSds = Math.exp(logFS);\n if (!Number.isFinite(fSds) || fSds === 0) continue;\n sum += halfW * gl.weights[i] * fSds * _wprob(q * s, k);\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// P(Q > q | k groups, df error degrees of freedom).\nexport function ptukey_upper(q: number, k: number, df: number): number {\n if (q <= 0) return 1;\n if (k < 2 || df < 1) return NaN;\n const gl = _gaussLegendre(48);\n const chiLo = chi2inv(1e-10, df);\n const chiHi = chi2inv(1 - 1e-10, df);\n const yLo = 0.5 * Math.log(Math.max(chiLo, 1e-300) / df);\n const yHi = 0.5 * Math.log(chiHi / df);\n const halfDf = df / 2;\n const logConst = Math.log(2) + halfDf * Math.log(halfDf) - gammaln(halfDf);\n const halfW = (yHi - yLo) / 2;\n const midW = (yHi + yLo) / 2;\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const y = midW + halfW * gl.nodes[i];\n const s = Math.exp(y);\n const logFS = logConst + df * y - (df * s * s) / 2;\n const fSds = Math.exp(logFS);\n if (!Number.isFinite(fSds) || fSds === 0) continue;\n sum += halfW * gl.weights[i] * fSds * _wprob_upper(q * s, k);\n }\n return Math.max(0, Math.min(1, sum));\n}\n\nexport function qtukey(p: number, k: number, df: number): number {\n if (p <= 0) return 0;\n if (p >= 1) return Infinity;\n if (k < 2 || df < 1) return NaN;\n let lo = 0.01,\n hi = 100;\n for (let i = 0; i < 20 && ptukey(hi, k, df) < p; i++) {\n lo = hi;\n hi *= 2;\n }\n if (ptukey(hi, k, df) < p) return NaN;\n for (let i = 0; i < 200; i++) {\n const mid = (lo + hi) / 2;\n if (ptukey(mid, k, df) < p) lo = mid;\n else hi = mid;\n if (hi - lo < 1e-7 * (Math.abs(lo) + 1)) break;\n }\n return (lo + hi) / 2;\n}\n\n// \u2500\u2500 11. Post-hoc tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function tukeyHSD(groups: number[][], opts: { alpha?: number } = {}): TukeyResult {\n const alpha = opts.alpha != null ? opts.alpha : 0.05;\n const k = groups.length;\n if (k < 2) return { pairs: [], error: \"\u22652 groups required\" };\n const anova = oneWayANOVA(groups);\n if (anova.error) return { pairs: [], error: anova.error };\n const dfErr = anova.df2;\n const mse = (anova.ssWithin as number) / dfErr;\n // equiv-mutant: mse === 0 is unreachable \u2014 oneWayANOVA already errors above when ssWithin is 0\n if (mse === 0) {\n return {\n pairs: [],\n k,\n df: dfErr,\n mse,\n error: \"Cannot compute Tukey HSD \u2014 zero within-group variance (data essentially constant)\",\n };\n }\n const means = groups.map(sampleMean);\n const ns = groups.map((g) => g.length);\n const qCrit = qtukey(1 - alpha, k, dfErr);\n const inPathologicalEnvelope = dfErr <= 2 && 1 - alpha >= 0.95 && k >= 10;\n let qCritWarning: string | null = null;\n if (!Number.isFinite(qCrit)) {\n qCritWarning = `Tukey HSD CI bounds unavailable \u2014 qtukey(${1 - alpha}, k=${k}, df=${dfErr}) returned NaN (outside the bracket-expansion envelope). p-values are still reliable; the per-pair lwr / upr are NaN.`;\n } else if (inPathologicalEnvelope) {\n qCritWarning = `Tukey HSD CI bounds approximate \u2014 qtukey(${1 - alpha}, k=${k}, df=${dfErr}) lies in the studentized-range design envelope (df \u2264 2 with k \u2265 10 at 1\u2212\u03B1 \u2265 0.95), where the reference (R, SciPy) disagree by ~5 % with each other. p-values stay reliable; the per-pair lwr / upr are accurate to within ~5 %. Consider Games-Howell (Welch-aware, less sensitive to small df) if this matches your real data.`;\n }\n const pairs: TukeyPair[] = [];\n for (let i = 0; i < k - 1; i++) {\n for (let j = i + 1; j < k; j++) {\n const diff = means[j] - means[i];\n const se = Math.sqrt((mse * (1 / ns[i] + 1 / ns[j])) / 2);\n const q = Math.abs(diff) / se;\n const p = ptukey_upper(q, k, dfErr);\n const margin = qCrit * se;\n pairs.push({\n i,\n j,\n diff,\n se,\n q,\n p,\n lwr: diff - margin,\n upr: diff + margin,\n });\n }\n }\n const result: TukeyResult = { pairs, k, df: dfErr, mse };\n if (qCritWarning) result.warning = qCritWarning;\n return result;\n}\n\nexport function gamesHowell(groups: number[][]): GamesHowellResult {\n const k = groups.length;\n if (k < 2) return { pairs: [], error: \"\u22652 groups required\" };\n const means = groups.map(sampleMean);\n const vars_ = groups.map(sampleVariance);\n const ns = groups.map((g) => g.length);\n // Each group needs n \u2265 2: a singleton has no sample variance\n // (sampleVariance returns NaN), which slips past the zero-variance guard\n // below \u2014 `NaN === 0` is false \u2014 and would surface as a silent NaN p.\n if (ns.some((n) => n < 2)) {\n return { pairs: [], k, error: \"Each group needs n\u22652 for Games-Howell\" };\n }\n if (vars_.some((v) => v === 0)) {\n return {\n pairs: [],\n k,\n error: \"Cannot compute Games-Howell \u2014 at least one group has zero variance\",\n };\n }\n const pairs: GamesHowellPair[] = [];\n for (let i = 0; i < k - 1; i++) {\n for (let j = i + 1; j < k; j++) {\n const vi = vars_[i] / ns[i];\n const vj = vars_[j] / ns[j];\n const se = Math.sqrt(vi + vj);\n const diff = means[j] - means[i];\n const q = Math.abs(diff) / (se / Math.SQRT2);\n const num = (vi + vj) * (vi + vj);\n const den = (vi * vi) / (ns[i] - 1) + (vj * vj) / (ns[j] - 1);\n // The Welch\u2013Satterthwaite df is bounded below by min(n\u1D62\u22121, n\u2C7C\u22121) \u2265 1\n // once every group has n \u2265 2; clamp to absorb sub-ULP rounding so\n // ptukey_upper never receives a df < 1 (which it answers with NaN).\n const df = Math.max(1, num / den);\n const p = ptukey_upper(q, k, df);\n pairs.push({ i, j, diff, se, q, df, p });\n }\n }\n return { pairs, k };\n}\n\nexport function bhAdjust(ps: number[]): number[] {\n const m = ps.length;\n const order: [number, number][] = ps\n .map((p, i): [number, number] => [p, i])\n .sort((a, b) => a[0] - b[0]);\n const adj = new Array(m);\n let running = 1;\n for (let rank = m; rank >= 1; rank--) {\n const [p, origIdx] = order[rank - 1];\n const q = (p * m) / rank;\n running = Math.min(running, q);\n adj[origIdx] = Math.min(1, running);\n }\n return adj;\n}\n\nexport function dunnTest(groups: number[][]): DunnResult {\n const k = groups.length;\n if (k < 2) return { pairs: [], error: \"\u22652 groups required\" };\n const all: number[] = [];\n const owner: number[] = [];\n for (let i = 0; i < k; i++) {\n for (const v of groups[i]) {\n all.push(v);\n owner.push(i);\n }\n }\n const N = all.length;\n // Mirror the kruskalWallis precondition (Dunn is its post-hoc): reject\n // when there are too few observations to rank, or any group is empty \u2014\n // either case otherwise yields a silent NaN p-value (an N\u22121 = 0 divisor\n // in sigma2, or 0/0 mean ranks for an empty group).\n if (N <= k || groups.some((g) => g.length < 1)) {\n return { pairs: [], error: \"Not enough observations\" };\n }\n const { ranks, tieCorrection } = rankWithTies(all);\n const meanR = new Array(k).fill(0);\n const ns = groups.map((g) => g.length);\n const sumR = new Array(k).fill(0);\n for (let idx = 0; idx < N; idx++) sumR[owner[idx]] += ranks[idx];\n for (let i = 0; i < k; i++) meanR[i] = sumR[i] / ns[i];\n const sigma2 = (N * (N + 1)) / 12 - tieCorrection / (12 * (N - 1));\n const rawPs: number[] = [];\n const pairs: DunnPair[] = [];\n for (let i = 0; i < k - 1; i++) {\n for (let j = i + 1; j < k; j++) {\n const se = Math.sqrt(sigma2 * (1 / ns[i] + 1 / ns[j]));\n const z = (meanR[i] - meanR[j]) / se;\n const p = 2 * normsf(Math.abs(z));\n pairs.push({ i, j, z, p });\n rawPs.push(p);\n }\n }\n const adj = bhAdjust(rawPs);\n for (let i = 0; i < pairs.length; i++) pairs[i].pAdj = adj[i];\n return { pairs, method: \"Benjamini-Hochberg\" };\n}\n\n// \u2500\u2500 12. Compact letter display \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function compactLetterDisplay(\n pairs: { i: number; j: number; p: number; pAdj?: number | null }[],\n k: number,\n alpha = 0.05\n): string[] {\n if (k <= 0) return [];\n let letters: Set[] = [new Set(Array.from({ length: k }, (_, i) => i))];\n for (const pr of pairs) {\n const p = pr.pAdj != null ? pr.pAdj : pr.p;\n if (!Number.isFinite(p) || p >= alpha) continue;\n const { i, j } = pr;\n const newLetters: Set[] = [];\n for (const L of letters) {\n if (L.has(i) && L.has(j)) {\n const L1 = new Set(L);\n L1.delete(i);\n const L2 = new Set(L);\n L2.delete(j);\n if (L1.size > 0) newLetters.push(L1);\n if (L2.size > 0) newLetters.push(L2);\n } else {\n newLetters.push(L);\n }\n }\n const filtered: Set[] = [];\n for (const L of newLetters) {\n let absorbed = false;\n for (const M of newLetters) {\n if (L === M) continue;\n if (L.size < M.size) {\n let sub = true;\n for (const v of L) {\n if (!M.has(v)) {\n sub = false;\n break;\n }\n }\n if (sub) {\n absorbed = true;\n break;\n }\n }\n }\n if (!absorbed && !filtered.some((F) => F.size === L.size && [...F].every((v) => L.has(v)))) {\n filtered.push(L);\n }\n }\n letters = filtered;\n }\n letters.sort((A, B) => Math.min(...A) - Math.min(...B));\n const labels = \"abcdefghijklmnopqrstuvwxyz\";\n const out: string[] = Array.from({ length: k }, () => \"\");\n for (let li = 0; li < letters.length; li++) {\n const lbl = labels[li] || `[${li}]`;\n for (const g of letters[li]) out[g] += lbl;\n }\n return out;\n}\n\n// \u2500\u2500 13. Automatic test selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function selectTest(\n groups: number[][],\n opts: { alphaNormality?: number; alphaVariance?: number } = {}\n): SelectTestResult {\n const alphaN = opts.alphaNormality != null ? opts.alphaNormality : 0.05;\n const alphaV = opts.alphaVariance != null ? opts.alphaVariance : 0.05;\n const k = groups.length;\n if (k < 2) {\n return { error: \"\u22652 groups required\", k };\n }\n\n const normality: NormalityResult[] = groups.map((g, i) => {\n if (g.length < 3) {\n return { group: i, n: g.length, W: null, p: null, normal: null, note: \"n<3\" };\n }\n const sw = shapiroWilk(g);\n if (sw.error) {\n return { group: i, n: g.length, W: null, p: null, normal: null, note: sw.error };\n }\n if (!Number.isFinite(sw.W) || !Number.isFinite(sw.p)) {\n // Non-finite W/p (e.g. a group carrying \u00B1Infinity) \u2014 report it as\n // \"unknown\" rather than letting `NaN >= alphaN` collapse to a false\n // \"non-normal\" verdict.\n return { group: i, n: g.length, W: null, p: null, normal: null, note: \"non-finite\" };\n }\n return { group: i, n: g.length, W: sw.W, p: sw.p, normal: sw.p >= alphaN };\n });\n const allKnownNormal = normality.every((r) => r.normal === true);\n const flagged = normality.filter((r) => r.normal === false);\n const anyFlagged = flagged.length > 0;\n\n const lev = leveneTest(groups);\n // A non-finite F/p (a group carrying \u00B1Infinity) is as unusable as an\n // explicit error \u2014 treat it the same so the narrative never asserts a\n // variance verdict derived from NaN.\n const levUsable = !lev.error && Number.isFinite(lev.F) && Number.isFinite(lev.p);\n const equalVar = levUsable ? lev.p >= alphaV : null;\n\n const test: SelectTestKind = k === 2 ? \"welchT\" : \"welchANOVA\";\n const postHoc: SelectTestPostHoc = k === 2 ? null : \"gamesHowell\";\n\n const baseDefault =\n k === 2\n ? \"Default pick: Welch's t-test. Welch's t is the recommended default for two independent groups (Rasch, Kubinger & Moder 2011; Zimmerman 2004) \u2014 it does not assume equal variances and matches Student's t closely when variances are in fact equal.\"\n : \"Default pick: Welch's ANOVA with Games-Howell post-hoc. Welch's ANOVA is the recommended default for k \u2265 3 independent groups (Delacre et al. 2019; Rasch et al. 2011) \u2014 it does not assume equal variances across groups and matches one-way ANOVA closely when variances are in fact equal.\";\n\n const swNarrative = ((): string => {\n if (flagged.length === 0 && allKnownNormal) {\n return \"Shapiro-Wilk did not reject normality in any group at \u03B1 = \" + alphaN + \".\";\n }\n if (flagged.length > 0) {\n const labels = flagged\n .map((r) => `group ${r.group + 1} (W=${(r.W as number).toFixed(3)}, p=${formatP(r.p)})`)\n .join(\", \");\n return `Shapiro-Wilk flagged ${flagged.length} of ${k} group(s) as non-normal at \u03B1 = ${alphaN}: ${labels}.`;\n }\n return \"Shapiro-Wilk could not run on every group (n < 3 in at least one).\";\n })();\n\n const levNarrative = !levUsable\n ? `Levene (Brown-Forsythe) could not run: ${lev.error || \"non-finite result\"}.`\n : equalVar === false\n ? `Levene (Brown-Forsythe) rejected equal variances (F=${lev.F.toFixed(3)}, p=${formatP(lev.p)}); Welch handles this without further intervention.`\n : `Levene (Brown-Forsythe) did not reject equal variances (F=${lev.F.toFixed(3)}, p=${formatP(lev.p)}); Welch is still the safe default and matches the equal-variance test closely here.`;\n\n let suggestion: SelectTestResult[\"suggestion\"];\n let suggestionNarrative = \"\";\n if (anyFlagged) {\n suggestion = {\n test: k === 2 ? \"mannWhitney\" : \"kruskalWallis\",\n postHoc: k === 2 ? null : \"dunn\",\n reason:\n \"Shapiro-Wilk flagged at least one group as non-normal. Pl\u00F6ttr keeps Welch as the default (pre-screening with SW is a known anti-pattern) but a rank-based test may be more appropriate for genuinely heavy-tailed data.\",\n };\n suggestionNarrative = ` If the non-normality looks substantive (heavy tails, strong skew, ordinal data), consider switching to ${suggestion.test === \"mannWhitney\" ? \"Mann-Whitney U\" : \"Kruskal-Wallis + Dunn (BH)\"} from the test dropdown.`;\n }\n\n const overrideHint =\n \" You can override this pick from the stats panel's per-test dropdown; the trace below shows the diagnostics the recommendation is based on.\";\n\n const reason = `${baseDefault} ${swNarrative} ${levNarrative}${suggestionNarrative}${overrideHint}`;\n\n const out: SelectTestResult = {\n k,\n normality,\n allNormal: allKnownNormal,\n levene: !levUsable\n ? { error: lev.error || \"non-finite result\" }\n : { F: lev.F, df1: lev.df1, df2: lev.df2, p: lev.p, equalVar },\n recommendation: { test, postHoc, reason },\n };\n if (suggestion) out.suggestion = suggestion;\n return out;\n}\n", "// _core/color.ts \u2014 color helpers, palettes, and role colours.\n//\n// \u2500\u2500 Color helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function hexToRgb(hex: string): [number, number, number] {\n const h = hex.replace(\"#\", \"\");\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n}\n\nexport function rgbToHex(r: number, g: number, b: number): string {\n return (\n \"#\" +\n [r, g, b]\n .map((v) =>\n Math.max(0, Math.min(255, Math.round(v)))\n .toString(16)\n .padStart(2, \"0\")\n )\n .join(\"\")\n );\n}\n\nexport function shadeColor(hex: string, factor: number): string {\n const [r, g, b] = hexToRgb(hex);\n if (factor > 0)\n return rgbToHex(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);\n return rgbToHex(r * (1 + factor), g * (1 + factor), b * (1 + factor));\n}\n\nexport function getPointColors(baseColor: string, nSources: number): string[] {\n if (nSources <= 1) return [baseColor];\n const colors: string[] = [];\n for (let i = 0; i < nSources; i++) {\n const t = nSources === 1 ? 0 : Math.min(1, i / (nSources - 1));\n colors.push(shadeColor(baseColor, -0.4 + t * 0.7));\n }\n return colors;\n}\n\n// \u2500\u2500 Color palette (Okabe-Ito colorblind-safe; Wong 2011, Nature Methods) \u2500\u2500\u2500\u2500\n\nexport const PALETTE: readonly string[] = [\n \"#E69F00\",\n \"#56B4E9\",\n \"#009E73\",\n \"#F0E442\",\n \"#0072B2\",\n \"#D55E00\",\n \"#CC79A7\",\n \"#000000\",\n \"#88CCEE\",\n \"#AA4499\",\n];\n\n// \u2500\u2500 Continuous colour palettes (shared by scatter + heatmap) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const COLOR_PALETTES: Record = {\n viridis: [\"#440154\", \"#3b528b\", \"#21908c\", \"#5dc963\", \"#fde725\"],\n plasma: [\"#0d0887\", \"#7e03a8\", \"#cc4778\", \"#f89540\", \"#f0f921\"],\n magma: [\"#000004\", \"#3b0f70\", \"#8c2981\", \"#de4968\", \"#fe9f6d\", \"#fcfdbf\"],\n inferno: [\"#000004\", \"#420a68\", \"#932667\", \"#dd513a\", \"#fca50a\", \"#fcffa4\"],\n cividis: [\"#00204c\", \"#213d6b\", \"#555b6c\", \"#7b7a77\", \"#a59c74\", \"#d3c064\", \"#ffe945\"],\n rdbu: [\"#b2182b\", \"#ef8a62\", \"#fddbc7\", \"#f7f7f7\", \"#d1e5f0\", \"#67a9cf\", \"#2166ac\"],\n bwr: [\"#0000ff\", \"#8888ff\", \"#ffffff\", \"#ff8888\", \"#ff0000\"],\n rdylbu: [\"#a50026\", \"#f46d43\", \"#fee090\", \"#ffffbf\", \"#e0f3f8\", \"#74add1\", \"#313695\"],\n reds: [\"#fff5f0\", \"#fcbba1\", \"#fb6a4a\", \"#cb181d\", \"#67000d\"],\n blues: [\"#f7fbff\", \"#c6dbef\", \"#6baed6\", \"#2171b5\", \"#08306b\"],\n greens: [\"#f7fcf5\", \"#c7e9c0\", \"#74c476\", \"#238b45\", \"#00441b\"],\n spectral: [\"#9e0142\", \"#f46d43\", \"#fee08b\", \"#e6f598\", \"#66c2a5\", \"#3288bd\", \"#5e4fa2\"],\n};\n\n// Diverging palettes should be anchored at 0 when rendered (symmetric vmin/vmax).\nexport const DIVERGING_PALETTES: Set = new Set([\"rdbu\", \"bwr\", \"rdylbu\", \"spectral\"]);\n\nexport function interpolateColor(stops: string[], t: number): string {\n if (!stops || stops.length === 0) return \"#000000\";\n if (stops.length === 1) return stops[0];\n if (t <= 0 || !Number.isFinite(t)) return stops[0];\n if (t >= 1) return stops[stops.length - 1];\n const seg = (stops.length - 1) * t;\n const i = Math.floor(seg);\n const f = seg - i;\n const [r1, g1, b1] = hexToRgb(stops[i]);\n const [r2, g2, b2] = hexToRgb(stops[i + 1]);\n return rgbToHex(r1 + (r2 - r1) * f, g1 + (g2 - g1) * f, b1 + (b2 - b1) * f);\n}\n\n// \u2500\u2500 Role colours \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Chrome styling lives in `tools/components.css` via `dv-*` classes; only\n// `roleColors` remains here because the column-role chips read it directly.\n\nexport const roleColors: Record = {\n group: \"#0072B2\",\n value: \"#009E73\",\n filter: \"#E69F00\",\n ignore: \"var(--border-strong)\",\n};\n", "// _core/numeric.ts \u2014 locale-aware numeric detection + seeded PRNG.\n//\n// Unicode-aware normalisation before numeric parsing. Excel on macOS, PDFs,\n// Word docs, and statistical-paper copy-paste all routinely embed non-ASCII\n// minus-ish and whitespace-ish characters into number cells. These are\n// visually identical to the ASCII forms but break `Number()` parsing \u2014\n// `Number(\"\u22125\")` (U+2212 minus sign) is `NaN`, not `-5`. Normalise them\n// before regex + Number() so we don't silently drop legitimate values.\n\nconst UNICODE_MINUS_CHARS = /[\\u2212\\u2013\\u2014]/g; // \u2212 (minus) \u2013 (en-dash) \u2014 (em-dash)\nconst UNICODE_SPACE_CHARS = /[\\u00A0\\u2009\\u202F]/g; // NBSP, thin space, narrow NBSP\n\nexport function normalizeNumericString(v: unknown): unknown {\n if (typeof v !== \"string\") return v;\n return v.replace(UNICODE_MINUS_CHARS, \"-\").replace(UNICODE_SPACE_CHARS, \"\");\n}\n\n// Returns true only for strings that are entirely a valid finite number.\n// Rejects:\n// - alphanumeric (\"6wpi\", \"12abc\", \"0xFF\"),\n// - Number() specials (\"Infinity\", \"NaN\"),\n// - overflow strings that coerce to \u00B1Infinity (\"1e999\"),\n// - leading-zero integer IDs (\"007\", \"000123\") that silently lose their\n// zero-padded form when coerced \u2014 well plates, accession numbers, and\n// LIMS codes commonly use this shape.\n// Accepts normalised Unicode variants: \"\u22125\" (U+2212), \"\u20135\" (en-dash), and\n// numbers containing NBSP / thin spaces from copy-paste.\nexport function isNumericValue(v: unknown): boolean {\n if (typeof v !== \"string\") return false;\n const s = (normalizeNumericString(v) as string).trim();\n if (s.length === 0) return false;\n if (/^-?0\\d/.test(s)) return false;\n if (!/^-?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$/.test(s)) return false;\n return Number.isFinite(Number(s));\n}\n\n// Convenience: normalise + parse. Callers that already know `isNumericValue`\n// is true should route through this instead of `Number(v)` directly so\n// Unicode-minus / NBSP values parse correctly.\nexport function toNumericValue(v: unknown): number {\n return isNumericValue(v) ? Number((normalizeNumericString(v) as string).trim()) : NaN;\n}\n\n// \u2500\u2500 Seeded random \u2014 Park-Miller LCG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function seededRandom(seed: number): () => number {\n let s = seed;\n // Burn one LCG step before returning so the first observable output is\n // properly mixed. Park-Miller's first step on a small seed produces\n // roughly `seed * 16807 / 2^31`, so adjacent small seeds (e.g. the\n // per-source `gi*1000 + si*100 + 42` pattern the boxplot jitter uses)\n // would otherwise all return near-identical small first values \u2014 a\n // single-point source would then render its dot at the leftmost edge\n // of the jitter band. One warm-up advances the state into a\n // properly-mixed region of the cycle while keeping the function\n // deterministic (re-renders don't re-jitter).\n s = (s * 16807) % 2147483647;\n return () => {\n s = (s * 16807) % 2147483647;\n return s / 2147483647;\n };\n}\n", "// _core/csv.ts \u2014 CSV / TSV parsing, tokenizing, separator detection, format\n// reshaping, and formula-injection scanning.\n//\nimport { isNumericValue, toNumericValue } from \"./numeric\";\n\n// \u2500\u2500 Separator detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Picks the delimiter most likely to produce a consistent column count. The\n// naive \"count occurrences across the first 2 kB\" heuristic fails on TSVs\n// whose header has commas in free-text (\"Sample, Description, Notes\") \u2014\n// those header-level commas can out-count the tabs in data rows and flip\n// the detection to `,`, collapsing 20 data columns to 1.\n//\n// Strategy: for each candidate (`\\t`, `;`, `,`), measure how uniform the\n// per-line count is across the first ~50 non-empty lines. A real delimiter\n// partitions every line into the SAME number of columns, so its per-line\n// count has low variance and a median \u2265 1. A separator that sneaks into\n// quoted text or headers only produces inconsistent per-line counts.\n\nexport function autoDetectSep(text: string, override: string = \"\"): string | RegExp {\n if (override !== \"\") return override;\n const h = text.slice(0, 2000);\n const lines = h.split(/\\r?\\n/).filter((l) => l.trim() !== \"\");\n const head = lines.slice(0, Math.min(50, lines.length));\n if (head.length === 0) return /\\s+/;\n\n const CANDIDATES = [\"\\t\", \";\", \",\"];\n const scores = CANDIDATES.map((sep) => {\n const countPerLine = head.map((line) => {\n let n = 0;\n let inQuoted = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === '\"') {\n inQuoted = !inQuoted;\n continue;\n }\n if (!inQuoted && ch === sep) n++;\n }\n return n;\n });\n const total = countPerLine.reduce((a, b) => a + b, 0);\n const sorted = countPerLine.slice().sort((a, b) => a - b);\n const median = sorted[Math.floor(sorted.length / 2)];\n const mean = total / countPerLine.length;\n let variance = 0;\n if (countPerLine.length > 1) {\n for (const n of countPerLine) variance += (n - mean) * (n - mean);\n variance /= countPerLine.length - 1;\n }\n const cv = mean > 0 ? Math.sqrt(variance) / mean : Infinity;\n return { sep, total, median, cv };\n });\n\n const viable = scores.filter((s) => s.median >= 1);\n if (viable.length === 0) {\n const best = scores.reduce((a, b) => (b.total > a.total ? b : a));\n return best.total === 0 ? /\\s+/ : best.sep;\n }\n viable.sort((a, b) => a.cv - b.cv || b.total - a.total);\n return viable[0].sep;\n}\n\n// \u2500\u2500 Delimited-text tokenizer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// RFC 4180-style state machine. Handles quoted fields with embedded\n// separators, embedded `\\n` / `\\r\\n` inside quoted cells, escaped `\"\"`\n// pairs, a leading BOM, and mixed CRLF/LF line endings. A `\"` in the\n// middle of an unquoted field is preserved literally so `5\"` as an inch\n// measurement survives \u2014 only a leading `\"` at the start of a field opens\n// a quoted run.\n\nexport function tokenizeDelimited(text: string, sep: string | RegExp): string[][] {\n if (typeof text !== \"string\" || text.length === 0) return [];\n let s = text;\n if (s.charCodeAt(0) === 0xfeff) s = s.slice(1);\n if (typeof sep !== \"string\") {\n return s\n .split(/\\r?\\n/)\n .filter((l) => l.trim() !== \"\")\n .map((l) => l.trim().split(sep));\n }\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let inQuoted = false;\n let fieldStarted = false;\n const flushRow = (): void => {\n row.push(field);\n if (!(row.length === 1 && row[0] === \"\")) {\n for (let c = 0; c < row.length; c++) row[c] = row[c].trim();\n rows.push(row);\n }\n row = [];\n field = \"\";\n fieldStarted = false;\n };\n const n = s.length;\n let i = 0;\n while (i < n) {\n const ch = s[i];\n if (inQuoted) {\n if (ch === '\"') {\n if (i + 1 < n && s[i + 1] === '\"') {\n field += '\"';\n i += 2;\n } else {\n inQuoted = false;\n i++;\n }\n } else {\n field += ch;\n i++;\n }\n } else if (!fieldStarted && ch === '\"') {\n inQuoted = true;\n fieldStarted = true;\n i++;\n } else if (ch === sep) {\n row.push(field);\n field = \"\";\n fieldStarted = false;\n i++;\n } else if (ch === \"\\n\" || ch === \"\\r\") {\n flushRow();\n if (ch === \"\\r\" && i + 1 < n && s[i + 1] === \"\\n\") i += 2;\n else i++;\n } else {\n field += ch;\n fieldStarted = true;\n i++;\n }\n }\n if (field !== \"\" || row.length > 0) {\n flushRow();\n }\n for (let r = 0; r < rows.length; r++) {\n for (let c = 0; c < rows[r].length; c++) rows[r][c] = rows[r][c].trim();\n }\n return rows;\n}\n\n// \u2500\u2500 Decimal comma fix (per-column) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function fixDecimalCommas(\n text: string,\n sep: string | RegExp\n): { text: string; commaFixed: boolean; count: number } {\n if (typeof text !== \"string\" || text.length === 0) {\n return { text, commaFixed: false, count: 0 };\n }\n if (typeof sep !== \"string\") return { text, commaFixed: false, count: 0 };\n if (sep === \",\") return { text, commaFixed: false, count: 0 };\n if (sep === \"\") return { text, commaFixed: false, count: 0 };\n const rows = tokenizeDelimited(text, sep);\n if (rows.length < 2) return { text, commaFixed: false, count: 0 };\n const nCols = Math.max(...rows.map((r) => r.length));\n const decimalCommaCols = new Set();\n for (let c = 0; c < nCols; c++) {\n let numericAsIs = 0;\n let numericIfFixed = 0;\n let hasComma = 0;\n let nonEmpty = 0;\n let anyNonThousandish = false;\n for (let r = 1; r < rows.length; r++) {\n const v = rows[r][c];\n if (v == null || v === \"\") continue;\n nonEmpty++;\n if (v.indexOf(\",\") !== -1) {\n hasComma++;\n const last = v.lastIndexOf(\",\");\n const after = v.slice(last + 1);\n const m = after.match(/^\\d+/);\n if (!m || m[0].length !== 3) anyNonThousandish = true;\n }\n if (isNumericValue(v)) numericAsIs++;\n if (isNumericValue(v.replace(/,/g, \".\"))) numericIfFixed++;\n }\n if (nonEmpty > 0 && hasComma > 0 && numericIfFixed > numericAsIs && anyNonThousandish) {\n decimalCommaCols.add(c);\n }\n }\n if (decimalCommaCols.size === 0) return { text, commaFixed: false, count: 0 };\n let count = 0;\n for (let r = 0; r < rows.length; r++) {\n for (const c of decimalCommaCols) {\n const v = rows[r][c];\n if (v && v.indexOf(\",\") !== -1) {\n const fixed = v.replace(/,/g, \".\");\n if (isNumericValue(fixed)) {\n rows[r][c] = fixed;\n count++;\n }\n }\n }\n }\n if (count === 0) return { text, commaFixed: false, count: 0 };\n const q = (cell: string): string => {\n if (\n cell.indexOf(sep as string) !== -1 ||\n cell.indexOf('\"') !== -1 ||\n cell.indexOf(\"\\n\") !== -1 ||\n cell.indexOf(\"\\r\") !== -1\n ) {\n return '\"' + cell.replace(/\"/g, '\"\"') + '\"';\n }\n return cell;\n };\n const rebuilt = rows.map((r) => r.map(q).join(sep as string)).join(\"\\n\");\n return { text: rebuilt, commaFixed: true, count };\n}\n\n// \u2500\u2500 Parsing helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function detectHeader(rows: string[][]): boolean {\n if (rows.length < 2) return true;\n const a = rows[0].filter((v) => !isNumericValue(v) && v.trim() !== \"\").length;\n const b = rows[1].filter((v) => !isNumericValue(v) && v.trim() !== \"\").length;\n if (a > b) return true;\n if (a === b && a > 0) {\n let reps = 0;\n for (let i = 1; i < Math.min(rows.length, 20); i++)\n for (let c = 0; c < rows[0].length; c++)\n if (rows[i][c] && rows[i][c].trim() === rows[0][c].trim()) reps++;\n return reps < rows[0].length;\n }\n return a > 0;\n}\n\nexport interface FormulaInjectionWarning {\n count: number;\n headers: Array<{ idx: number; value: string }>;\n cells: Array<{ row: number; col: number; header: string | null; value: string }>;\n}\n\nexport interface ParseRawResult {\n headers: string[];\n rows: string[][];\n hasHeader: boolean;\n injectionWarnings: FormulaInjectionWarning | null;\n}\n\nexport function parseRaw(text: string, sepOv: string = \"\"): ParseRawResult {\n const sep = autoDetectSep(text, sepOv);\n const all = tokenizeDelimited(text, sep);\n if (all.length < 1) return { headers: [], rows: [], hasHeader: false, injectionWarnings: null };\n const mx = Math.max(...all.map((r) => r.length));\n const pad = all.map((r) => {\n while (r.length < mx) r.push(\"\");\n return r;\n });\n const hh = detectHeader(pad);\n let headers: string[], rows: string[][];\n if (hh) {\n headers = pad[0];\n rows = pad.slice(1);\n } else {\n headers = pad[0].map((_, i) => `Col_${i + 1}`);\n rows = pad;\n }\n const scan = scanForFormulaInjection(headers, rows);\n return {\n headers,\n rows,\n hasHeader: !!hh,\n injectionWarnings: scan.count > 0 ? scan : null,\n };\n}\n\nexport type ColumnRole = \"group\" | \"value\" | \"filter\" | \"ignore\";\n\nexport function guessColumnType(vals: string[]): ColumnRole {\n const ne = vals.filter((v) => v != null && v !== \"\");\n if (ne.length === 0) return \"ignore\";\n if (ne.filter((v) => isNumericValue(v)).length / ne.length > 0.8) {\n const u = new Set(ne);\n if (u.size <= 12 && u.size < ne.length * 0.3) {\n const allIntegers = ne.every((v) => {\n const n = Number(String(v).trim());\n return Number.isFinite(n) && Number.isInteger(n);\n });\n if (allIntegers) return \"group\";\n }\n return \"value\";\n }\n const u = new Set(ne);\n if (u.size <= 20 && u.size < ne.length * 0.5) return \"group\";\n return \"filter\";\n}\n\nexport function detectWideFormat(headers: string[], rows: string[][]): boolean {\n if (headers.length < 2 || rows.length < 2) return false;\n const numericCols = headers.map((_, ci) => {\n const vals = rows.map((r) => r[ci]).filter((v) => v !== \"\");\n return vals.length > 0 && vals.filter((v) => isNumericValue(v)).length / vals.length > 0.8;\n });\n return numericCols.every(Boolean);\n}\n\nexport interface ParseWideMatrixResult {\n rowLabels: string[];\n colLabels: string[];\n matrix: number[][];\n warnings: { nonNumeric: number };\n injectionWarnings: FormulaInjectionWarning | null;\n}\n\nexport function parseWideMatrix(text: string, sepOv: string = \"\"): ParseWideMatrixResult {\n const parsed = parseRaw(text, sepOv);\n const { headers, rows, injectionWarnings } = parsed;\n if (headers.length < 2 || rows.length < 1) {\n return {\n rowLabels: [],\n colLabels: [],\n matrix: [],\n warnings: { nonNumeric: 0 },\n injectionWarnings: null,\n };\n }\n const colLabels = headers.slice(1);\n const rowLabels: string[] = [];\n const matrix: number[][] = [];\n let nonNumeric = 0;\n rows.forEach((r) => {\n const label = r[0] == null ? \"\" : String(r[0]);\n const values: number[] = new Array(colLabels.length);\n for (let ci = 0; ci < colLabels.length; ci++) {\n const raw = r[ci + 1];\n if (raw == null || raw === \"\") {\n values[ci] = NaN;\n } else if (isNumericValue(raw)) {\n values[ci] = toNumericValue(raw);\n } else {\n values[ci] = NaN;\n nonNumeric++;\n }\n }\n rowLabels.push(label);\n matrix.push(values);\n });\n return { rowLabels, colLabels, matrix, warnings: { nonNumeric }, injectionWarnings };\n}\n\n// \u2500\u2500 Unified data parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ParseDataResult {\n headers: string[];\n data: (number | null)[][];\n rawData: string[][];\n injectionWarnings: FormulaInjectionWarning | null;\n}\n\nexport function parseData(text: string, sepOv: string = \"\"): ParseDataResult {\n const sep = autoDetectSep(text, sepOv);\n const all = tokenizeDelimited(text, sep);\n if (all.length < 2) return { headers: [], data: [], rawData: [], injectionWarnings: null };\n const headers = all[0];\n const nCols = headers.length;\n const data: (number | null)[][] = [];\n const rawData: string[][] = [];\n for (let i = 1; i < all.length; i++) {\n const rawVals = all[i].slice();\n while (rawVals.length < nCols) rawVals.push(\"\");\n if (rawVals.every((v) => v === \"\")) continue;\n data.push(rawVals.map((s) => (isNumericValue(s) ? toNumericValue(s) : null)));\n rawData.push(rawVals);\n }\n const scan = scanForFormulaInjection(headers, rawData);\n return { headers, data, rawData, injectionWarnings: scan.count > 0 ? scan : null };\n}\n\nexport function dataToColumns(data: (number | null)[][], nCols: number): number[][] {\n const columns: number[][] = Array.from({ length: nCols }, () => []);\n for (const row of data) {\n for (let c = 0; c < nCols; c++) {\n if (row[c] != null) columns[c].push(row[c] as number);\n }\n }\n return columns;\n}\n\n// \u2500\u2500 Wide / long format helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function wideToLong(\n headers: string[],\n rows: string[][]\n): { headers: string[]; rows: string[][]; skipped: number } {\n const longRows: string[][] = [];\n let skipped = 0;\n rows.forEach((r) => {\n headers.forEach((h, ci) => {\n if (r[ci] !== \"\" && isNumericValue(r[ci])) {\n longRows.push([h, r[ci]]);\n } else {\n skipped++;\n }\n });\n });\n return { headers: [\"Group\", \"Value\"], rows: longRows, skipped };\n}\n\nexport function reshapeWide(\n rows: string[][],\n gi: number,\n vi: number\n): { headers: string[]; rows: string[][]; unlabelled: number } {\n const g: Record = {};\n let unlabelled = 0;\n rows.forEach((r) => {\n const raw = r[gi];\n if (raw === \"\" || raw == null) unlabelled++;\n const k = raw || \"?\";\n if (!g[k]) g[k] = [];\n g[k].push(r[vi]);\n });\n const vals = Object.values(g);\n if (vals.length === 0) return { headers: [], rows: [], unlabelled };\n const mx = Math.max(...vals.map((v) => v.length));\n const names = Object.keys(g);\n const w: string[][] = [];\n for (let i = 0; i < mx; i++) w.push(names.map((n) => (g[n][i] != null ? g[n][i] : \"\")));\n return { headers: names, rows: w, unlabelled };\n}\n\n// \u2500\u2500 Set-membership parsing (Venn / UpSet) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function parseSetData(\n headers: string[],\n rows: string[][]\n): { setNames: string[]; sets: Map> } {\n const sets = new Map>();\n for (let ci = 0; ci < headers.length; ci++) {\n const s = new Set();\n for (const r of rows) {\n const v = (r[ci] || \"\").trim();\n if (v) s.add(v);\n }\n if (s.size > 0) sets.set(headers[ci], s);\n }\n const setNames = [...sets.keys()];\n return { setNames, sets };\n}\n\nexport function parseLongFormatSets(\n headers: string[],\n rows: string[][]\n): { setNames: string[]; sets: Map> } {\n if (!headers || headers.length !== 2) {\n throw new Error(\"Long format requires exactly 2 columns (item, set).\");\n }\n const sets = new Map>();\n for (const r of rows) {\n const item = (r[0] || \"\").trim();\n const setName = (r[1] || \"\").trim();\n if (!item || !setName) continue;\n if (!sets.has(setName)) sets.set(setName, new Set());\n sets.get(setName)!.add(item);\n }\n const setNames = [...sets.keys()];\n return { setNames, sets };\n}\n\n// \u2500\u2500 CSV building + formula-injection scanning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Cells whose first character is one of these get a leading single-quote\n// before the CSV escape. Excel / LibreOffice / Google Sheets all treat `=`,\n// `+`, `-`, `@` (and a leading tab / CR before any of those) as a formula\n// trigger; the prepended `'` tells the spreadsheet engine to treat the cell\n// as text. This is the standard OWASP CSV-injection mitigation.\n\nconst _CSV_FORMULA_LEAD = /^[=+\\-@\\t\\r]/;\n\nfunction _isFormulaInjection(s: string): boolean {\n return _CSV_FORMULA_LEAD.test(s) && !isNumericValue(s);\n}\n\nfunction _escapeCsvCell(v: unknown): string {\n let s = String(v);\n if (_isFormulaInjection(s)) s = \"'\" + s;\n return `\"${s.replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function buildCsvString(headers: unknown[], rows: unknown[][]): string {\n return [\n headers.map(_escapeCsvCell).join(\",\"),\n ...rows.map((r) => r.map(_escapeCsvCell).join(\",\")),\n ].join(\"\\n\");\n}\n\nexport function scanForFormulaInjection(\n headers: unknown[] | null | undefined,\n rows: unknown[][] | null | undefined,\n opts?: { cap?: number }\n): FormulaInjectionWarning {\n const cap = (opts && opts.cap) || 8;\n const out: FormulaInjectionWarning = { count: 0, headers: [], cells: [] };\n if (Array.isArray(headers)) {\n for (let i = 0; i < headers.length; i++) {\n const v = headers[i];\n if (typeof v !== \"string\" || !_isFormulaInjection(v)) continue;\n out.count++;\n if (out.headers.length < cap) out.headers.push({ idx: i, value: v });\n }\n }\n if (Array.isArray(rows)) {\n for (let r = 0; r < rows.length; r++) {\n const row = rows[r];\n if (!Array.isArray(row)) continue;\n for (let c = 0; c < row.length; c++) {\n const v = row[c];\n if (typeof v !== \"string\" || !_isFormulaInjection(v)) continue;\n out.count++;\n if (out.cells.length < cap) {\n out.cells.push({\n row: r,\n col: c,\n header: headers && headers[c] != null ? String(headers[c]) : null,\n value: v,\n });\n }\n }\n }\n }\n return out;\n}\n", "// _core/svg-export.ts \u2014 SVG export pipeline + `Pl\u00F6ttr v` attribution\n// band + `svgSafeId` NCName sanitiser.\n//\n// Sanitize an arbitrary string into an SVG-safe id fragment so exported\n// values are valid NCNames and show up as readable group\n// names in Inkscape's Objects panel / XML editor. Non-alphanumerics\n// become hyphens, runs are collapsed, edges trimmed, and a leading digit\n// is prefixed with \"_\".\nexport function svgSafeId(s: string | null | undefined): string {\n if (s == null) return \"unnamed\";\n const cleaned = String(s)\n .replace(/[^A-Za-z0-9_.-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (!cleaned) return \"unnamed\";\n return /^[A-Za-z_]/.test(cleaned) ? cleaned : \"_\" + cleaned;\n}\n\n// Height of the reserved bottom band added to every export so the\n// `Pl\u00F6ttr v\u2026` attribution sits outside the plot area. Used by both\n// `appendPlottrAttribution` (which extends the viewBox) and the PNG\n// downloader (which sizes its raster canvas to the post-watermark\n// dimensions, since the live SVG element still has the pre-watermark\n// viewBox).\nexport const PLOTTR_ATTRIBUTION_PAD = 14;\n\n// \u2500\u2500 SVG export mutators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Charts that paint to canvas for performance (currently only the volcano\n// data layer above POINT_RASTERIZE_THRESHOLD) need to substitute a vector\n// representation in the *exported* file, otherwise the user downloads a\n// fuzzy embedded PNG instead of a crisp SVG. The chart registers a\n// `mutator(clone)` callback against its live SVG element via\n// `registerSvgExportMutator`; `buildExportSvg` invokes it on the clone\n// before attribution is appended, after the standard style / shape-\n// rendering scrub. The mutator owns the swap (typically: remove the\n// raster , append elements built from the same render\n// records the canvas painted from).\n//\n// The registry is a WeakMap so an unmounting chart whose SVG element goes\n// out of scope releases its entry without explicit cleanup \u2014 and the\n// optional explicit `unregisterSvgExportMutator` lets a chart that\n// transitions out of raster mode clear its registration immediately\n// rather than wait for GC.\n\ntype ExportMutator = (svgClone: SVGElement) => void;\n// The registry lives on `globalThis` rather than module scope so that two\n// separately-bundled copies of `_core/svg-export.ts` (e.g. the SPA bundle\n// and a tool's standalone test bundle) share one WeakMap. Otherwise a\n// chart registers into closure-A and the export call reads from closure-B,\n// missing the mutator entirely.\nconst _gMut = globalThis as { __plottrSvgMutators?: WeakMap | null };\nif (_gMut.__plottrSvgMutators === undefined) {\n _gMut.__plottrSvgMutators =\n typeof WeakMap === \"function\" ? new WeakMap() : null;\n}\nconst _svgExportMutators: WeakMap | null =\n _gMut.__plottrSvgMutators as WeakMap | null;\n\nexport function registerSvgExportMutator(svgEl: SVGElement, mutator: ExportMutator): void {\n if (!_svgExportMutators || !svgEl || typeof mutator !== \"function\") return;\n _svgExportMutators.set(svgEl, mutator);\n}\n\nexport function unregisterSvgExportMutator(svgEl: SVGElement): void {\n if (!_svgExportMutators || !svgEl) return;\n _svgExportMutators.delete(svgEl);\n}\n\n// Build a clone of `svgEl` ready for export and append the permanent\n// `Pl\u00F6ttr v` attribution band at the bottom. Returns the cloned\n// element \u2014 callers can serialize it to a string or read its\n// updated `viewBox` / `width` / `height` attributes.\n//\n// Three pre-watermark transformations:\n// 1. Strip the root inline `style=\"max-width:100%;\u2026\"` so Inkscape's\n// CSS engine doesn't collapse the computed viewport on `height: auto`.\n// 2. Strip every `shape-rendering=\"crispEdges\"` attribute (heatmap cells\n// hit Inkscape \u22651.1's cairo-renderer bug otherwise).\n// 3. Run any chart-registered export mutator (see registry comment above).\n//\n// The watermark itself extends the viewBox (and `height` attribute, if\n// present) downward by PLOTTR_ATTRIBUTION_PAD px so the plot area, axes\n// and existing margins stay pixel-identical \u2014 only the canvas grows.\nexport function buildExportSvg(svgEl: SVGElement): SVGElement {\n const clone = svgEl.cloneNode(true) as SVGElement;\n clone.removeAttribute(\"style\");\n clone.querySelectorAll(\"[shape-rendering]\").forEach((el) => {\n el.removeAttribute(\"shape-rendering\");\n });\n const mutator = _svgExportMutators ? _svgExportMutators.get(svgEl) : null;\n if (typeof mutator === \"function\") {\n try {\n mutator(clone);\n } catch (err) {\n if (typeof console !== \"undefined\" && console.error) {\n console.error(\"[plottr] SVG export mutator failed:\", err);\n }\n }\n }\n appendPlottrAttribution(clone);\n return clone;\n}\n\nexport function serializeSvgForExport(svgEl: SVGElement): string {\n return new XMLSerializer().serializeToString(buildExportSvg(svgEl));\n}\n\n// Append the `` wrapper containing a small\n// italic `Pl\u00F6ttr v` text to the bottom-right of the SVG canvas.\n// Mutates the passed-in element \u2014 pass a clone, not the live DOM node.\n// Idempotent: a second call removes the prior attribution before\n// re-appending, so the canvas can't grow twice.\nexport function appendPlottrAttribution(svgEl: SVGElement): void {\n if (!svgEl || typeof svgEl.setAttribute !== \"function\") return;\n const prior = svgEl.querySelector(\"#plottr-attribution\");\n const hadPrior = !!(prior && prior.parentNode === svgEl);\n if (hadPrior && prior) prior.parentNode!.removeChild(prior);\n\n const vbParts = (svgEl.getAttribute(\"viewBox\") || \"\").split(/[\\s,]+/).map(parseFloat);\n let vbX = 0;\n let vbY = 0;\n let vbW = 0;\n let vbH = 0;\n if (vbParts.length >= 4 && vbParts.every((n) => Number.isFinite(n))) {\n vbX = vbParts[0];\n vbY = vbParts[1];\n vbW = vbParts[2];\n vbH = vbParts[3];\n } else {\n const wAttr = parseFloat(svgEl.getAttribute(\"width\") || \"\");\n const hAttr = parseFloat(svgEl.getAttribute(\"height\") || \"\");\n vbW = Number.isFinite(wAttr) ? wAttr : 0;\n vbH = Number.isFinite(hAttr) ? hAttr : 0;\n }\n if (hadPrior) vbH -= PLOTTR_ATTRIBUTION_PAD;\n if (!(vbW > 0) || !(vbH > 0)) return;\n\n const newH = vbH + PLOTTR_ATTRIBUTION_PAD;\n svgEl.setAttribute(\"viewBox\", `${vbX} ${vbY} ${vbW} ${newH}`);\n const heightAttr = parseFloat(svgEl.getAttribute(\"height\") || \"\");\n if (Number.isFinite(heightAttr)) {\n const baseHeight = hadPrior ? heightAttr - PLOTTR_ATTRIBUTION_PAD : heightAttr;\n svgEl.setAttribute(\"height\", String(baseHeight + PLOTTR_ATTRIBUTION_PAD));\n }\n\n const version =\n (typeof window !== \"undefined\" &&\n typeof (window as Window & { __APP_VERSION__?: string }).__APP_VERSION__ === \"string\"\n ? (window as Window & { __APP_VERSION__?: string }).__APP_VERSION__\n : null) || \"v?\";\n const doc = svgEl.ownerDocument || (typeof document !== \"undefined\" ? document : null);\n if (!doc || typeof doc.createElementNS !== \"function\") return;\n const NS = \"http://www.w3.org/2000/svg\";\n const g = doc.createElementNS(NS, \"g\");\n g.setAttribute(\"id\", \"plottr-attribution\");\n g.setAttribute(\"data-plottr-version\", version);\n const text = doc.createElementNS(NS, \"text\");\n text.setAttribute(\"x\", String(vbX + vbW - 5));\n text.setAttribute(\"y\", String(vbY + newH - 4));\n text.setAttribute(\"font-size\", \"8\");\n text.setAttribute(\"font-style\", \"italic\");\n text.setAttribute(\"fill\", \"#999\");\n text.setAttribute(\"text-anchor\", \"end\");\n text.setAttribute(\"font-family\", \"system-ui, -apple-system, sans-serif\");\n text.textContent = `Pl\u00F6ttr ${version}`;\n g.appendChild(text);\n svgEl.appendChild(g);\n}\n", "// _core/download.ts \u2014 file-save helpers (save-picker + legacy anchor\n// fallback), CSV / SVG / PNG / text downloads, filename utilities.\n\nimport { buildCsvString } from \"./csv\";\nimport { buildExportSvg, PLOTTR_ATTRIBUTION_PAD, serializeSvgForExport } from \"./svg-export\";\n\n// \u2500\u2500 Filename helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function fileBaseName(fileName: unknown, fallback?: string): string {\n if (typeof fileName === \"string\" && fileName.trim()) return fileName.replace(/\\.[^.]+$/, \"\");\n return fallback || \"data\";\n}\n\n// Accepts any HTMLElement (call sites use either a button or a wider\n// `EventTarget & HTMLElement` from React's MouseEvent.currentTarget).\n// `disabled` is set only on elements that have the property \u2014 most\n// commonly buttons; non-button calls just get the innerHTML swap.\nexport function flashSaved(btn: HTMLElement | null): void {\n if (!btn) return;\n const original = btn.innerHTML;\n btn.innerHTML = \"\u2713 Saved\";\n if (\"disabled\" in btn) (btn as HTMLButtonElement).disabled = true;\n setTimeout(() => {\n btn.innerHTML = original;\n if (\"disabled\" in btn) (btn as HTMLButtonElement).disabled = false;\n }, 1500);\n}\n\n// \u2500\u2500 File-save picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface SavePickerType {\n mime: string;\n description: string;\n}\nconst _SAVE_PICKER_TYPES: Record = {\n \".svg\": { mime: \"image/svg+xml\", description: \"SVG image\" },\n \".png\": { mime: \"image/png\", description: \"PNG image\" },\n \".csv\": { mime: \"text/csv\", description: \"CSV file\" },\n \".tsv\": { mime: \"text/tab-separated-values\", description: \"TSV file\" },\n \".txt\": { mime: \"text/plain\", description: \"Text file\" },\n \".r\": { mime: \"text/plain\", description: \"R script\" },\n \".json\": { mime: \"application/json\", description: \"JSON file\" },\n};\n\ninterface SaveFilePickerWindow extends Window {\n showSaveFilePicker?: (opts: {\n suggestedName: string;\n types: Array<{ description: string; accept: Record }>;\n }) => Promise<{\n createWritable: () => Promise<{\n write: (b: Blob) => Promise;\n close: () => Promise;\n }>;\n }>;\n}\n\n// Save a Blob to disk. Tries the File System Access API\n// (`window.showSaveFilePicker`) first so the user can pick a target folder\n// + filename \u2014 supported in Chromium-based browsers (Chrome, Edge, Opera)\n// on HTTPS / localhost. Falls back to the classic `` anchor\n// click on Firefox / Safari, which routes the file to the browser's\n// default Downloads folder.\n//\n// The fallback is also taken when the picker call throws anything other\n// than the user-cancelled `AbortError`. User cancel returns silently; we\n// don't downgrade to the fallback in that case because the user explicitly\n// chose \"Cancel\".\nexport async function saveBlob(blob: Blob, filename: string): Promise {\n if (\n typeof window !== \"undefined\" &&\n typeof (window as SaveFilePickerWindow).showSaveFilePicker === \"function\"\n ) {\n try {\n const dot = filename.lastIndexOf(\".\");\n const ext = dot >= 0 ? filename.slice(dot).toLowerCase() : \"\";\n const meta = _SAVE_PICKER_TYPES[ext] || {\n mime: blob.type || \"application/octet-stream\",\n description: \"File\",\n };\n const handle = await (window as SaveFilePickerWindow).showSaveFilePicker!({\n suggestedName: filename,\n types: [{ description: meta.description, accept: { [meta.mime]: [ext || \"\"] } }],\n });\n const writable = await handle.createWritable();\n await writable.write(blob);\n await writable.close();\n return;\n } catch (err) {\n if (err && (err as { name?: string }).name === \"AbortError\") return;\n }\n }\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n// \u2500\u2500 Format-specific downloaders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function downloadSvg(svgEl: SVGElement | null, filename: string): void {\n if (!svgEl) return;\n const svgStr = serializeSvgForExport(svgEl);\n const blob = new Blob([svgStr], { type: \"image/svg+xml\" });\n saveBlob(blob, filename);\n}\n\nexport function downloadPng(svgEl: SVGElement | null, filename: string, scale?: number): void {\n if (!svgEl) return;\n const s = scale || 2;\n const exportSvg = buildExportSvg(svgEl);\n const svgStr = new XMLSerializer().serializeToString(exportSvg);\n const vb = exportSvg.getAttribute(\"viewBox\");\n const parts = vb ? vb.split(/[\\s,]+/) : [];\n const w = parts.length >= 4 ? parseFloat(parts[2]) : (svgEl as SVGSVGElement).clientWidth || 800;\n const h =\n parts.length >= 4\n ? parseFloat(parts[3])\n : ((svgEl as SVGSVGElement).clientHeight || 600) + PLOTTR_ATTRIBUTION_PAD;\n const canvas = document.createElement(\"canvas\");\n canvas.width = w * s;\n canvas.height = h * s;\n const ctx = canvas.getContext(\"2d\")!;\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const img = new Image();\n const blob = new Blob([svgStr], { type: \"image/svg+xml;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n img.onload = function () {\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n URL.revokeObjectURL(url);\n canvas.toBlob(function (pngBlob) {\n if (pngBlob) saveBlob(pngBlob, filename);\n }, \"image/png\");\n };\n // Without this, a serialized SVG the rasterizer rejects leaves onload\n // unfired: no PNG, no feedback, and the object URL leaks.\n img.onerror = function () {\n URL.revokeObjectURL(url);\n console.error(\"[plottr] PNG export failed: the chart SVG could not be rasterized\");\n };\n img.src = url;\n}\n\nexport function downloadText(text: string, filename: string): void {\n const blob = new Blob([text], { type: \"text/plain\" });\n saveBlob(blob, filename);\n}\n\nexport function downloadCsv(headers: unknown[], rows: unknown[][], filename: string): void {\n const blob = new Blob([buildCsvString(headers, rows)], { type: \"text/csv\" });\n saveBlob(blob, filename);\n}\n", "// `DataPreview` \u2014 compact table preview for parsed CSV/TSV data, used in\n// every tool's upload + configure steps. Renders the first `maxRows` rows\n// (default 10) plus a \"\u2026 N more\" footer when truncated.\n\ninterface DataPreviewProps {\n headers: string[];\n // Cell values are coerced to strings at render time; accept strings,\n // numbers, or null / empty for sparse rows.\n rows: Array>;\n // Maximum rows rendered before the \"\u2026 N more\" footer kicks in. Defaults to 10.\n maxRows?: number;\n}\n\nexport function DataPreview({ headers, rows, maxRows }: DataPreviewProps) {\n const limit = maxRows || 10;\n const d = rows.slice(0, limit);\n return (\n \n \n \n \n \n #\n \n {headers.map((h, i) => (\n \n {h}\n \n ))}\n \n \n \n {d.map((r, ri) => (\n \n \n {ri + 1}\n \n {r.map((v, ci) => (\n \n {v}\n \n ))}\n \n ))}\n \n
\n {rows.length > limit ? (\n \n {`\u2026 ${rows.length - limit} more (${rows.length} total)`}\n

\n ) : null}\n \n );\n}\n", "// `DetectedSeparatorBadge` \u2014 small inline reminder of which delimiter the\n// auto-detector picked on the most recent parse, surfaced on each tool's\n// post-upload step (configure / preview / plot) so users can sanity-check\n// the pick before reading too much into the chart. Renders nothing when\n// `sep` is empty (no parse yet) or the empty-string whitespace fallback.\n//\n// Designed to slot into an existing muted text line as a trailing fragment,\n// e.g. ` \u2014 4 cols \u00D7 72 rows {}` \u2014\n// styled neutrally on `var(--text-faint)` so it doesn't compete with the\n// file name or counts.\n\nexport function describeSeparator(sep: string): string {\n if (sep === \",\") return \"comma\";\n if (sep === \";\") return \"semicolon\";\n if (sep === \"\\t\") return \"tab\";\n if (sep === \" \") return \"space\";\n return \"whitespace\";\n}\n\ninterface DetectedSeparatorBadgeProps {\n sep: string;\n}\n\nexport function DetectedSeparatorBadge({ sep }: DetectedSeparatorBadgeProps) {\n if (!sep) return null;\n return (\n \n \u00B7 detected: {describeSeparator(sep)}-separated\n \n );\n}\n", "// Discrete colour palette catalogue + helpers shared across plot tools, the\n// discrete analogue of the continuous COLOR_PALETTES / PaletteStrip pattern\n// used by scatter / heatmap / volcano. Used by every per-group / per-category\n// colouring slot (boxplot groups, lineplot series, scatter discrete\n// categories, venn sets, aequorin conditions, volcano up/down/ns).\n//\n// `okabe-ito` is byte-identical to the global PALETTE in shared.js \u2014 every\n// tool defaults its `vis.discretePalette` to \"okabe-ito\" so existing\n// behaviour is preserved exactly.\n//\n// Attribution:\n// ColorBrewer palettes (set1/set2/set3/dark2/paired/pastel1/pastel2)\n// \u00A9 Cynthia Brewer, Mark Harrower, and Penn State University.\n// Licensed under the Apache License, Version 2.0\n// (http://www.apache.org/licenses/LICENSE-2.0).\n//\n// Okabe-Ito: Wong 2011, Nature Methods 8(6):441.\n// Tableau10: \u00A9Tableau Software; reproduced under fair-use widely.\n// ggplot2-hue: derived at runtime from hcl(h, l=65, c=100), no licence.\n// viridis-d: even-spaced sample of the continuous viridis (CC0).\n//\n// Note: viridis-d (discrete sample) is intentionally distinct from\n// COLOR_PALETTES.viridis (continuous): the discrete form generates exactly\n// `n = names.length` colours via interpolateColor at runtime.\n\nimport { COLOR_PALETTES, interpolateColor } from \"../_core/color\";\n\nexport const DISCRETE_PALETTES: Record = {\n // Okabe-Ito (Wong 2011) \u2014 colour-blind safe. Same as PALETTE in shared.js.\n \"okabe-ito\": [\n \"#E69F00\",\n \"#56B4E9\",\n \"#009E73\",\n \"#F0E442\",\n \"#0072B2\",\n \"#D55E00\",\n \"#CC79A7\",\n \"#000000\",\n \"#88CCEE\",\n \"#AA4499\",\n ],\n tab10: [\n \"#4E79A7\",\n \"#F28E2B\",\n \"#E15759\",\n \"#76B7B2\",\n \"#59A14F\",\n \"#EDC948\",\n \"#B07AA1\",\n \"#FF9DA7\",\n \"#9C755F\",\n \"#BAB0AC\",\n ],\n set1: [\n \"#E41A1C\",\n \"#377EB8\",\n \"#4DAF4A\",\n \"#984EA3\",\n \"#FF7F00\",\n \"#FFFF33\",\n \"#A65628\",\n \"#F781BF\",\n \"#999999\",\n ],\n set2: [\"#66C2A5\", \"#FC8D62\", \"#8DA0CB\", \"#E78AC3\", \"#A6D854\", \"#FFD92F\", \"#E5C494\", \"#B3B3B3\"],\n set3: [\n \"#8DD3C7\",\n \"#FFFFB3\",\n \"#BEBADA\",\n \"#FB8072\",\n \"#80B1D3\",\n \"#FDB462\",\n \"#B3DE69\",\n \"#FCCDE5\",\n \"#D9D9D9\",\n \"#BC80BD\",\n \"#CCEBC5\",\n \"#FFED6F\",\n ],\n dark2: [\"#1B9E77\", \"#D95F02\", \"#7570B3\", \"#E7298A\", \"#66A61E\", \"#E6AB02\", \"#A6761D\", \"#666666\"],\n paired: [\n \"#A6CEE3\",\n \"#1F78B4\",\n \"#B2DF8A\",\n \"#33A02C\",\n \"#FB9A99\",\n \"#E31A1C\",\n \"#FDBF6F\",\n \"#FF7F00\",\n \"#CAB2D6\",\n \"#6A3D9A\",\n \"#FFFF99\",\n \"#B15928\",\n ],\n pastel1: [\n \"#FBB4AE\",\n \"#B3CDE3\",\n \"#CCEBC5\",\n \"#DECBE4\",\n \"#FED9A6\",\n \"#FFFFCC\",\n \"#E5D8BD\",\n \"#FDDAEC\",\n \"#F2F2F2\",\n ],\n pastel2: [\"#B3E2CD\", \"#FDCDAC\", \"#CBD5E8\", \"#F4CAE4\", \"#E6F5C9\", \"#FFF2AE\", \"#F1E2CC\", \"#CCCCCC\"],\n // Sentinel \"*\" means \"generate at runtime sized to names.length\"; consumers\n // call buildGgplot2Hue / buildViridisDiscrete via resolveDiscretePalette.\n \"ggplot2-hue\": [\"*\"],\n \"viridis-d\": [\"*\"],\n};\n\n// Subset considered colour-blind-safe (no red/green adjacency, sufficient\n// luminance contrast). Surfaced as a \uD83D\uDC41 glyph in the dropdown so users can\n// quickly tell. Conservative on purpose \u2014 Set2 / Pastel2 are arguably\n// borderline; only the unambiguous ones are marked.\nexport const COLORBLIND_SAFE_PALETTES = new Set([\"okabe-ito\", \"dark2\", \"paired\", \"viridis-d\"]);\n\n// HCL \u2192 sRGB conversion for ggplot2-hue. Implementation matches grDevices::hcl\n// closely enough for visual parity (D65 white point, 2\u00B0 observer). Full\n// agreement with R's hcl() output is not the goal \u2014 we just need the iconic\n// ggplot2 look.\nfunction hclToHex(h: number, c: number, l: number): string {\n const hRad = (h * Math.PI) / 180;\n const a = Math.cos(hRad) * c;\n const b = Math.sin(hRad) * c;\n // Lab \u2192 XYZ (D65)\n const Y = (l + 16) / 116;\n const X = a / 500 + Y;\n const Z = Y - b / 200;\n const fInv = (t: number): number => (t * t * t > 0.008856 ? t * t * t : (t - 16 / 116) / 7.787);\n const Xn = 95.047,\n Yn = 100.0,\n Zn = 108.883;\n const x = Xn * fInv(X);\n const y = Yn * fInv(Y);\n const z = Zn * fInv(Z);\n // XYZ \u2192 linear sRGB (D65)\n let r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;\n let g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;\n let bl = (x * 0.0557 + y * -0.204 + z * 1.057) / 100;\n // Linear \u2192 sRGB gamma\n const gam = (u: number): number =>\n u > 0.0031308 ? 1.055 * Math.pow(u, 1 / 2.4) - 0.055 : 12.92 * u;\n r = Math.max(0, Math.min(1, gam(r)));\n g = Math.max(0, Math.min(1, gam(g)));\n bl = Math.max(0, Math.min(1, gam(bl)));\n const toHex = (u: number): string =>\n Math.round(u * 255)\n .toString(16)\n .padStart(2, \"0\");\n return \"#\" + toHex(r) + toHex(g) + toHex(bl);\n}\n\n// Generate ggplot2's default hue scale for n colours.\nexport function buildGgplot2Hue(n: number): string[] {\n if (n <= 0) return [];\n const out: string[] = [];\n // hues = seq(15, 375, length.out = n + 1)[1:n]\n for (let i = 0; i < n; i++) {\n const h = 15 + (360 * i) / n;\n out.push(hclToHex(h, 100, 65));\n }\n return out;\n}\n\n// Generate evenly-spaced viridis samples for n colours by interpolating\n// COLOR_PALETTES.viridis. Falls back to the raw stops if interpolateColor\n// isn't yet on the global scope (defensive \u2014 they live in shared.js which\n// loads first in the bundle).\nexport function buildViridisDiscrete(n: number): string[] {\n if (n <= 0) return [];\n if (typeof interpolateColor !== \"function\" || typeof COLOR_PALETTES !== \"object\") {\n return [];\n }\n const stops = COLOR_PALETTES.viridis;\n if (n === 1) return [stops[Math.floor(stops.length / 2)]];\n const out: string[] = [];\n for (let i = 0; i < n; i++) {\n out.push(interpolateColor(stops, i / (n - 1)));\n }\n return out;\n}\n\n// Resolve a palette name to a concrete hex array sized for `n` slots.\n// Recycles modulo for fixed-length palettes, generates exactly n for the\n// runtime palettes (ggplot2-hue / viridis-d). Unknown names fall back to\n// okabe-ito so a stale localStorage value never produces an empty palette.\nexport function resolveDiscretePalette(name: string, n: number): string[] {\n if (name === \"ggplot2-hue\") return buildGgplot2Hue(Math.max(1, n || 1));\n if (name === \"viridis-d\") return buildViridisDiscrete(Math.max(1, n || 1));\n const stops = DISCRETE_PALETTES[name] || DISCRETE_PALETTES[\"okabe-ito\"];\n if (!stops || stops.length === 0) return [];\n const count = Math.max(1, n || stops.length);\n const out: string[] = [];\n for (let i = 0; i < count; i++) out.push(stops[i % stops.length]);\n return out;\n}\n\n// Build a `{ name \u2192 hex }` record for an ordered array of group/category\n// names using the named palette. Recycles modulo for fixed palettes,\n// generates exact for runtime palettes.\nexport function applyDiscretePalette(name: string, names: string[]): Record {\n const list = Array.isArray(names) ? names : [];\n const resolved = resolveDiscretePalette(name, list.length);\n const out: Record = {};\n list.forEach((nm, i) => {\n out[nm] = resolved[i % Math.max(1, resolved.length)] || \"#000000\";\n });\n return out;\n}\n", "// `FileDropZone` \u2014 drag-and-drop / click-to-browse upload widget shared by\n// every tool's UploadStep. Gates on the canonical `FILE_LIMIT_BYTES`\n// (2 MB hard reject) and `FILE_WARN_BYTES` (1 MB warn) constants exported\n// here too \u2014 any new ingest surface (paste textarea, URL fetch, \u2026) must\n// gate on the same constants and surface the same red-banner UX.\n\nconst { useState, useRef } = React;\n\n// 2 MB \u2014 hard reject. Any new ingest surface (paste textarea, URL fetch,\n// clipboard image OCR, \u2026) must gate on this and the warning threshold\n// below; don't redeclare a local 2-MB number, don't pick your own.\nexport const FILE_LIMIT_BYTES = 2 * 1024 * 1024;\n// 1 MB \u2014 show warning but allow.\nexport const FILE_WARN_BYTES = 1 * 1024 * 1024;\n\ninterface FileDropZoneProps {\n onFileLoad: (text: string, fileName: string) => void;\n // Comma-separated extension list passed to the hidden ``.\n // Default covers the canonical text formats the parsers handle.\n accept?: string;\n // Sub-text under the main \"Drop CSV, TSV, or TXT\" line. Override per-tool\n // to spell out additional accepted extensions or constraints.\n hint?: string;\n}\n\nexport function FileDropZone({\n onFileLoad,\n accept = \".csv,.tsv,.txt,.dat\",\n hint = \"CSV \u00B7 TSV \u00B7 TXT \u00B7 DAT \u2014 2 MB max\",\n}: FileDropZoneProps) {\n const [drag, setDrag] = useState(false);\n const [focus, setFocus] = useState(false);\n const [sizeError, setSizeError] = useState(null);\n const [sizeWarn, setSizeWarn] = useState(null);\n const [readError, setReadError] = useState(null);\n const [reading, setReading] = useState(false);\n const inputRef = useRef(null);\n\n const handle = (file: File) => {\n setSizeError(null);\n setSizeWarn(null);\n setReadError(null);\n if (file.size > FILE_LIMIT_BYTES) {\n setSizeError(\n `File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Maximum is 2 MB \u2014 split the file or sample rows and try again.`\n );\n return;\n }\n if (file.size > FILE_WARN_BYTES) {\n setSizeWarn(\n `Large file (${(file.size / 1024 / 1024).toFixed(1)} MB) \u2014 parsing may take a moment.`\n );\n }\n const reader = new FileReader();\n // Both `onload` and `onerror` must be wired \u2014 a corrupt file or blocked\n // read would otherwise leave the user staring at the drop zone with no\n // feedback. `setReading` gives the UI a \"Reading\u2026\" affordance until one\n // of the two callbacks fires.\n setReading(true);\n reader.onload = (e) => {\n setReading(false);\n onFileLoad(String(e.target?.result ?? \"\"), file.name);\n };\n reader.onerror = () => {\n setReading(false);\n const msg = (reader.error && (reader.error.message || reader.error.name)) || \"unknown error\";\n setReadError(`Couldn't read the file (${msg}). Check permissions and try again.`);\n };\n reader.readAsText(file);\n };\n\n const openPicker = () => inputRef.current && inputRef.current.click();\n\n return (\n
\n {\n e.preventDefault();\n setDrag(true);\n }}\n onDragLeave={() => setDrag(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDrag(false);\n if (e.dataTransfer.files[0]) handle(e.dataTransfer.files[0]);\n }}\n onClick={openPicker}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n openPicker();\n }\n }}\n onFocus={() => setFocus(true)}\n onBlur={() => setFocus(false)}\n style={{\n border: `2px dashed ${\n drag ? \"var(--accent-primary)\" : sizeError ? \"var(--danger-text)\" : \"var(--text-faint)\"\n }`,\n borderRadius: 12,\n padding: \"48px 24px\",\n textAlign: \"center\",\n cursor: \"pointer\",\n background: drag\n ? \"var(--accent-primary-weak)\"\n : sizeError\n ? \"var(--danger-bg)\"\n : \"transparent\",\n transition: \"all .2s\",\n outline: focus ? \"2px solid var(--accent-primary)\" : \"none\",\n outlineOffset: 2,\n }}\n >\n {\n if (e.target.files && e.target.files[0]) handle(e.target.files[0]);\n e.target.value = \"\";\n }}\n />\n
\n \uD83D\uDCC2\n
\n

\n Drop CSV, TSV, or TXT \u2014 or click to browse\n

\n

{hint}

\n
\n {sizeError && (\n \n \n \uD83D\uDEAB\n \n \n {sizeError}\n \n \n )}\n {sizeWarn && (\n \n \n \u26A0\uFE0F\n \n {sizeWarn}\n \n )}\n {readError && (\n \n \n \uD83D\uDEAB\n \n \n {readError}\n \n \n )}\n {reading && (\n \n Reading file\u2026\n \n )}\n \n );\n}\n", "// `HowToCard` \u2014 collapsible \"How to use\" card shared across every plot\n// tool's upload step. Open state persists under `dv-howto-`\n// in localStorage; open by default on first visit, then follows\n// whatever the user last chose.\n\nconst h = React.createElement;\n\nconst { useState, useMemo } = React;\n\ninterface HowToCardProps {\n toolName: string;\n title: React.ReactNode;\n subtitle?: React.ReactNode;\n children?: React.ReactNode;\n}\n\nexport function HowToCard(props: HowToCardProps) {\n const { toolName, title, subtitle, children } = props;\n const storageKey = \"dv-howto-\" + toolName;\n const initialOpen = useMemo(() => {\n try {\n const v = localStorage.getItem(storageKey);\n if (v === \"1\") return true;\n if (v === \"0\") return false;\n } catch {\n /* ignore */\n }\n return true;\n }, [storageKey]);\n const [open, setOpen] = useState(initialOpen);\n const bodyId = \"dv-howto-body-\" + toolName;\n const toggle = () => {\n setOpen((prev) => {\n const next = !prev;\n try {\n localStorage.setItem(storageKey, next ? \"1\" : \"0\");\n } catch {\n /* ignore */\n }\n return next;\n });\n };\n return h(\n \"section\",\n {\n style: {\n marginTop: 24,\n borderRadius: 14,\n overflow: \"hidden\",\n border: \"2px solid var(--howto-border)\",\n boxShadow: \"var(--howto-shadow)\",\n },\n },\n h(\n \"button\",\n {\n type: \"button\",\n onClick: toggle,\n \"aria-expanded\": open ? \"true\" : \"false\",\n \"aria-controls\": bodyId,\n style: {\n width: \"100%\",\n background: \"linear-gradient(135deg,var(--howto-header-from),var(--howto-header-to))\",\n padding: \"14px 24px\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n border: \"none\",\n cursor: \"pointer\",\n textAlign: \"left\",\n font: \"inherit\",\n color: \"inherit\",\n },\n },\n // Leading chevron \u2014 reads as \"click to expand\". Tool identity is\n // carried by the topbar + the sample-dataset banner above, so the\n // chevron alone keeps the header uncluttered.\n h(\n \"span\",\n {\n \"aria-hidden\": \"true\",\n style: {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"var(--on-accent)\",\n transition: \"transform .18s ease\",\n transform: open ? \"rotate(90deg)\" : \"rotate(0deg)\",\n flexShrink: 0,\n },\n },\n h(\n \"svg\",\n { width: 22, height: 22, viewBox: \"0 0 24 24\", style: { display: \"block\" } },\n h(\"path\", {\n d: \"M9 5l7 7-7 7\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2.6,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n })\n )\n ),\n h(\n \"div\",\n { style: { flex: 1, minWidth: 0 } },\n h(\"div\", { style: { color: \"var(--on-accent)\", fontWeight: 700, fontSize: 15 } }, title),\n subtitle\n ? h(\n \"div\",\n { style: { color: \"var(--on-accent-muted)\", fontSize: 11, marginTop: 2 } },\n subtitle\n )\n : null\n )\n ),\n open\n ? h(\n \"div\",\n {\n id: bodyId,\n style: {\n background: \"var(--info-bg)\",\n padding: \"20px 24px\",\n display: \"grid\",\n gridTemplateColumns: \"1fr 1fr\",\n gap: 14,\n },\n },\n children\n )\n : null\n );\n}\n", "// Shared \"How to use\" tile for the upload step of every plot tool.\n//\n// Wraps the existing `HowToCard` (collapsible header with a leading\n// expand/collapse chevron) and renders a uniform body \u2014 three required\n// sub-cards (Purpose / Data layout / Display) plus an optional Tips\n// card \u2014 driven by a `HowToContent` object passed by the caller. That\n// moves per-tool prose out of long `steps.tsx` / `index.tsx` files\n// and into tiny per-tool `howto.tsx` constants, and keeps the visual\n// depth consistent across tools (no more wall-of-text in one place +\n// one-liner in another).\n//\n// The `toolName` field on `HowToContent` is used by `HowToCard` for the\n// localStorage collapse-state key (`dv-howto-`).\n//\n// The rendering layer (sub-card header style, grid layout, pill list)\n// lives here. Tool-specific content lives in `tools//howto.tsx`.\n\nimport type { ReactNode } from \"react\";\nimport { HowToCard } from \"./HowToCard\";\n\nexport interface HowToContent {\n /** Drives the tool icon + the collapsible's localStorage key. */\n toolName: string;\n /** First-line title \u2014 e.g. \"Volcano Plot \u2014 How to use\". */\n title: string;\n /** One-line \"TL;DR\" subtitle below the title. */\n subtitle: string;\n /** What is this tool for, in user-language? Keep to 1\u20132 sentences. */\n purpose: ReactNode;\n /** What shape of data does it accept? Keep to 1\u20132 sentences. */\n dataLayout: ReactNode;\n /** Key display options + tool-specific specifics. 2\u20133 sentences. */\n display: ReactNode;\n /**\n * Optional tips / power-user hints (e.g. \"click points to label\").\n * When present, renders as a 4th full-width sub-card below the trio.\n */\n tips?: ReactNode;\n /**\n * Optional capability tags. Renders as a wrapped pill list at the\n * bottom \u2014 short label per pill, no styling overrides. Skip for\n * tools that don't need a marketing-style summary.\n */\n capabilities?: string[];\n}\n\n// Sub-card visuals \u2014 kept in lockstep across tools so a user moving\n// between volcano and lineplot doesn't perceive a different \"depth\"\n// of help. Header label style + body padding mirror the existing\n// HowToCard sub-card pattern (lineplot was the canonical one).\nfunction SubCard({\n label,\n children,\n fullWidth = false,\n}: {\n label: string;\n children: ReactNode;\n fullWidth?: boolean;\n}) {\n return (\n \n \n {label}\n \n
{children}
\n \n );\n}\n\nexport function HowTo(props: HowToContent) {\n return (\n \n \n {props.purpose}\n \n {props.dataLayout}\n {props.display}\n {props.tips && (\n \n {props.tips}\n \n )}\n {props.capabilities && props.capabilities.length > 0 && (\n
\n {props.capabilities.map((t) => (\n \n {t}\n \n ))}\n
\n )}\n
\n );\n}\n", "// `NumberInput` \u2014 compact numeric entry with \u2212/+ buttons replacing the\n// native stacked spinner. Mimics the native `` API:\n// `value`, `onChange(e)` where `e.target.value` is a string. The \u2212/+\n// buttons fire a synthetic event with the next value as a string so\n// existing `(e) => setX(e.target.value)` handlers keep working.\n//\n// Press-and-hold: button repeats at an accelerating cadence (first bump\n// immediate, 400 ms before repeat kicks in, then 80 ms for a handful of\n// ticks, then 40 ms). Release anywhere on the page stops the hold via a\n// window-level pointerup listener so the user can drag off the button\n// mid-hold without breaking the gesture.\n\nconst h = React.createElement;\n\nconst { useRef, useEffect } = React;\n\ninterface NumberInputProps {\n value: number | string | null | undefined;\n onChange: (e: { target: { value: string } }) => void;\n min?: number | string;\n max?: number | string;\n step?: number | string;\n disabled?: boolean;\n placeholder?: string;\n className?: string;\n style?: React.CSSProperties;\n inputStyle?: React.CSSProperties;\n}\n\ninterface HoldState {\n dir: number;\n next: number;\n timerId: ReturnType | null;\n onUp: (() => void) | null;\n}\n\nexport function NumberInput(props: NumberInputProps) {\n const value = props.value != null ? props.value : \"\";\n const onChange = props.onChange;\n const min = props.min != null ? Number(props.min) : null;\n const max = props.max != null ? Number(props.max) : null;\n const rawStep = props.step != null && props.step !== \"any\" ? Number(props.step) : 1;\n const step = isNaN(rawStep) || rawStep <= 0 ? 1 : rawStep;\n const disabled = !!props.disabled;\n const placeholder = props.placeholder;\n const className = \"dv-num\" + (disabled ? \" dv-num-disabled\" : \"\");\n\n // Hold state lives in a ref so setTimeout chains survive React re-renders\n // triggered by each fireChange.\n const holdRef = useRef(null);\n\n const fireChange = (newValueStr: string) => {\n if (!onChange) return;\n onChange({ target: { value: newValueStr } });\n };\n\n const clamp = (n: number): number => {\n if (min != null && n < min) n = min;\n if (max != null && n > max) n = max;\n // Round to the step's decimal precision to avoid 0.1+0.2 float noise.\n const decimals = (String(step).split(\".\")[1] || \"\").length;\n return decimals ? Number(n.toFixed(decimals)) : n;\n };\n\n const numericValue = Number(value);\n const minusDisabled = disabled || (min != null && !isNaN(numericValue) && numericValue <= min);\n const plusDisabled = disabled || (max != null && !isNaN(numericValue) && numericValue >= max);\n\n const stopRepeat = () => {\n const state = holdRef.current;\n if (!state) return;\n if (state.timerId) clearTimeout(state.timerId);\n if (state.onUp && typeof window !== \"undefined\") {\n window.removeEventListener(\"pointerup\", state.onUp);\n window.removeEventListener(\"pointercancel\", state.onUp);\n }\n holdRef.current = null;\n };\n\n const startRepeat = (dir: number) => {\n if ((dir < 0 && minusDisabled) || (dir > 0 && plusDisabled)) return;\n // Seed from the current prop value; every subsequent tick walks our own\n // ref so we stay correct even if React hasn't flushed the re-render yet.\n const seed = isNaN(Number(value)) ? (min != null ? min : 0) : Number(value);\n const state: HoldState = { dir, next: seed, timerId: null, onUp: null };\n holdRef.current = state;\n\n const doStep = () => {\n if (holdRef.current !== state) return;\n const n = clamp(state.next + dir * step);\n state.next = n;\n fireChange(String(n));\n if ((dir < 0 && min != null && n <= min) || (dir > 0 && max != null && n >= max)) {\n stopRepeat();\n }\n };\n\n // First step fires immediately so a quick click still bumps once.\n doStep();\n if (holdRef.current !== state) return;\n\n let ticks = 0;\n const tick = () => {\n if (holdRef.current !== state) return;\n doStep();\n if (holdRef.current !== state) return;\n ticks += 1;\n const delay = ticks > 8 ? 40 : 80;\n state.timerId = setTimeout(tick, delay);\n };\n // 400 ms dwell before the repeat kicks in (standard stepper feel).\n state.timerId = setTimeout(tick, 400);\n\n // Release anywhere on the page ends the hold \u2014 survives the user\n // dragging off the button mid-gesture.\n state.onUp = () => stopRepeat();\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pointerup\", state.onUp);\n window.addEventListener(\"pointercancel\", state.onUp);\n }\n };\n\n // Clean up if the component unmounts during a hold.\n useEffect(() => {\n return () => stopRepeat();\n }, []);\n\n const onMinusDown = (e: React.PointerEvent) => {\n if (e.button != null && e.button !== 0) return;\n e.preventDefault();\n startRepeat(-1);\n };\n const onPlusDown = (e: React.PointerEvent) => {\n if (e.button != null && e.button !== 0) return;\n e.preventDefault();\n startRepeat(1);\n };\n\n return h(\n \"div\",\n { className, style: props.style },\n h(\n \"button\",\n {\n type: \"button\",\n className: \"dv-num-btn dv-num-btn-minus\",\n onPointerDown: onMinusDown,\n disabled: minusDisabled,\n tabIndex: -1,\n \"aria-label\": \"Decrement\",\n },\n \"\u2212\"\n ),\n h(\"input\", {\n type: \"number\",\n className: \"dv-num-input\",\n value,\n min: props.min,\n max: props.max,\n step: props.step != null ? props.step : 1,\n disabled,\n placeholder,\n onChange: (e: React.ChangeEvent) => fireChange(e.target.value),\n style: props.inputStyle,\n }),\n h(\n \"button\",\n {\n type: \"button\",\n className: \"dv-num-btn dv-num-btn-plus\",\n onPointerDown: onPlusDown,\n disabled: plusDisabled,\n tabIndex: -1,\n \"aria-label\": \"Increment\",\n },\n \"+\"\n )\n );\n}\n", "// _core/icons.tsx \u2014 `TOOL_ICONS` catalogue + the `toolIcon` React helper.\n//\n// Lives as `.tsx` (not `.ts`) because `toolIcon` returns a ReactElement \u2014\n// the call site uses `React.createElement` rather than JSX literals, but\n// the .tsx extension makes the React surface unambiguous to esbuild and\n// future contributors who reach for JSX here.\n\nexport const TOOL_ICONS: Record = {\n aequorin:\n '',\n boxplot:\n '',\n scatter:\n '',\n venn: '',\n molarity:\n '',\n power:\n '',\n lineplot:\n '',\n heatmap:\n '',\n upset:\n '',\n volcano:\n '',\n};\n\n// Per-tool accent colours \u2014 a MIRROR of the landing tile `--tile-accent`\n// values in index.html. `toolIcon` retints each glyph's base hue to its\n// tool's accent so the topbar / upload-panel icon carries the same per-tool\n// colour as the landing tile. Volcano has no base-blue in its glyph, so it\n// keeps its intrinsic multi-tone (matching the landing). Keep this table in\n// sync with the `--tile-accent` literals in index.html if either changes.\nexport const TOOL_ACCENT: Record = {\n boxplot: \"#648fff\",\n scatter: \"#1ba89b\",\n lineplot: \"#fe6100\",\n aequorin: \"#dc267f\",\n venn: \"#d9a400\",\n upset: \"#785ef0\",\n heatmap: \"#0072b2\",\n volcano: \"#d55e00\",\n power: \"#dc267f\",\n molarity: \"#1ba89b\",\n};\n\n// Retint an icon SVG string's base blue (and the secondary violet a couple of\n// glyphs use) to the tool's accent. Glyphs with neither \u2014 e.g. volcano \u2014 pass\n// through unchanged. Used for both the size-injected `toolIcon` glyph and the\n// registry `iconSvg` strings rendered in the topbar quick-jumps. Icons drawn\n// with `currentColor` are unaffected here and instead inherit the accent from\n// the rendering element's `color`.\nexport function tintIcon(svg: string, name: string): string {\n const accent = TOOL_ACCENT[name];\n return accent ? svg.replace(/#648FFF/gi, accent).replace(/#785EF0/gi, accent) : svg;\n}\n\nexport function toolIcon(\n name: string,\n size?: number,\n opts?: { circle?: boolean }\n): React.ReactElement | null {\n const sz = size || 22;\n const o = opts || {};\n if (!TOOL_ICONS[name]) return null;\n const sized = TOOL_ICONS[name].replace(\"\n h(\"div\", {\n key,\n \"aria-hidden\": \"true\",\n style: {\n flex: \"0 0 auto\",\n width: 1,\n alignSelf: \"stretch\",\n background: \"var(--border-strong)\",\n },\n });\n const rowChildren: React.ReactNode[] = [\n h(\n \"h1\",\n {\n key: \"title\",\n style: {\n margin: 0,\n fontSize: 22,\n fontWeight: 700,\n color: \"var(--text)\",\n flex: \"0 0 auto\",\n display: \"flex\",\n alignItems: \"center\",\n },\n },\n // Bumped from the toolIcon default (22) to give the per-tool art\n // a bit more presence next to the H1 \u2014 the topbar is the canonical\n // place to see \"which tool am I in\" and a heftier icon reads more\n // confidently. Title font-size stays at 22 so the icon now slightly\n // exceeds the cap-height (intentional visual hierarchy).\n toolIcon(props.toolName, 28),\n props.title\n ),\n ];\n if (props.middle) {\n rowChildren.push(vbar(\"vbar-middle\"));\n rowChildren.push(\n h(\n \"div\",\n {\n key: \"middle\",\n style: { flex: \"1 1 auto\", minWidth: 0, display: \"flex\", alignItems: \"center\" },\n },\n props.middle\n )\n );\n } else {\n rowChildren.push(h(\"div\", { key: \"spacer\", style: { flex: \"1 1 auto\" } }));\n }\n if (props.right) {\n rowChildren.push(vbar(\"vbar-right\"));\n rowChildren.push(\n h(\n \"div\",\n {\n key: \"right\",\n style: { flex: \"0 0 auto\", display: \"flex\", alignItems: \"center\" },\n },\n props.right\n )\n );\n }\n return h(\n \"div\",\n {\n style: {\n marginBottom: 28,\n borderBottom: \"1px solid var(--border-strong)\",\n paddingBottom: 16,\n },\n },\n h(\n \"div\",\n {\n style: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 16,\n minHeight: 40,\n },\n },\n rowChildren\n )\n );\n}\n", "// Per-tool plot-render-settings persistence \u2014 pure data layer (no JSX).\n// `PrefsPanel.tsx` wraps these in the gear-menu UI; `usePlotToolState.ts`\n// calls `loadAutoPrefs` on init and `saveAutoPrefs` on every vis update so\n// per-slider drags persist across reloads.\n//\n// Storage layout (one localStorage key per tool, prefix `dataviz-prefs-`):\n// { tool, version, savedAt, settings: { ...visStyleKeys } }\n// Label keys (`*Title` / `*Subtitle` / `*AxisLabel`) are NOT auto-persisted\n// \u2014 auto-filling them with stale text across sessions is annoying. They\n// ARE written to and read from the exported JSON file (explicit user opt-in\n// via Save / Load to file in PrefsPanel).\n//\n// `downloadText` is imported directly from `_core/download` for the\n// production path (`PrefsPanel` calls `exportPrefsFile(tool, vis)`). The\n// test loader (`tests/helpers/prefs-loader.js`) passes an explicit capture\n// callback as the third argument to avoid relying on a global stub.\n// Browser globals (`setTimeout` / `clearTimeout` / `localStorage` /\n// `document` / `FileReader`) are still stubbed by the test loader's\n// vm context.\n\nimport { downloadText as defaultDownloadText } from \"../_core/download\";\n\nconst PREFS_LABEL_KEY_RE = /(?:Title|Subtitle|AxisLabel)$/;\nexport function isLabelKey(key: string): boolean {\n return PREFS_LABEL_KEY_RE.test(key);\n}\n\nconst PREFS_STORAGE_PREFIX = \"dataviz-prefs-\";\nexport const PREFS_SCHEMA_VERSION = 1;\n\nfunction prefsStorageKey(toolName: string): string {\n return PREFS_STORAGE_PREFIX + toolName;\n}\n\n// Internal-only working type for the data layer. Public functions accept\n// any `T extends object` (so typed `VolcanoVis` / `BoxplotVis` shapes pass\n// without index-signature gymnastics) and cast to PrefsRecord at the\n// `Object.keys(...)` / `vis[key]` boundary inside.\ntype PrefsRecord = Record;\n\n// Schema migration. Today every persisted blob is v1, so the function is a\n// pass-through. When a future bump renames or restructures keys, add a\n// `case 1: settings = migrate1To2(settings); fromVersion = 2;` step and\n// fall through. Returns null to signal \"unrecognised future version, drop\"\n// \u2014 better than mixing old + new under a whitelist merge.\n//\n// Why: 22-04-2026 audit (M2) flagged that the version field was written at\n// save time but never validated at load time. Without this scaffolding the\n// next schema bump would silently mix old keys that happen to overlap with\n// new ones.\nexport function migratePrefs(settings: PrefsRecord, fromVersion: unknown): PrefsRecord | null {\n let v: number;\n if (typeof fromVersion !== \"number\" || !Number.isFinite(fromVersion)) {\n // No version field at all \u2014 treat as v1 for back-compat with blobs\n // written before the schema was numbered (none should exist today,\n // but cheap defensive default).\n v = 1;\n } else {\n v = fromVersion;\n }\n if (v > PREFS_SCHEMA_VERSION) return null; // future version we can't read\n const s = settings;\n // Migration steps land here. Example for the next bump:\n // if (v === 1) { s = migrate1To2(s); v = 2; }\n while (v < PREFS_SCHEMA_VERSION) {\n // No migration steps yet \u2014 every supported version IS the current one.\n // Reaching this with v < current means a step is missing; bail safely.\n return null;\n }\n return s;\n}\n\n// Type-check a candidate value against the default in visInit. Accepts exact\n// type matches; for null defaults (e.g. lineplot.xMin = null meaning \"auto\"),\n// also accepts null or any finite number.\nexport function isPrefValueCompatible(candidate: unknown, defaultVal: unknown): boolean {\n if (defaultVal === null) {\n if (candidate === null) return true;\n return typeof candidate === \"number\" && Number.isFinite(candidate);\n }\n if (candidate === null || candidate === undefined) return false;\n const tDefault = typeof defaultVal;\n const tCand = typeof candidate;\n if (tDefault !== tCand) return false;\n if (tDefault === \"number\" && !Number.isFinite(candidate as number)) return false;\n return true;\n}\n\n// Merge a candidate settings object into a clone of visInit, keeping only\n// whitelisted keys with compatible types. `opts.onlyStyle` drops label keys.\nexport function mergePrefsSettings(\n visInit: T,\n candidate: unknown,\n opts?: { onlyStyle?: boolean }\n): T {\n const onlyStyle = !!(opts && opts.onlyStyle);\n const visRec = visInit as unknown as PrefsRecord;\n const out: PrefsRecord = { ...visRec };\n if (!candidate || typeof candidate !== \"object\") return out as unknown as T;\n const cand = candidate as PrefsRecord;\n for (const key of Object.keys(visRec)) {\n if (!(key in cand)) continue;\n if (onlyStyle && isLabelKey(key)) continue;\n const val = cand[key];\n if (isPrefValueCompatible(val, visRec[key])) {\n out[key] = val;\n }\n }\n return out as unknown as T;\n}\n\n// Build the style-only subset of a full `vis` state (strips label keys).\nexport function extractStylePrefs(vis: T): PrefsRecord {\n const out: PrefsRecord = {};\n const visRec = vis as unknown as PrefsRecord;\n for (const key of Object.keys(visRec)) {\n if (isLabelKey(key)) continue;\n out[key] = visRec[key];\n }\n return out;\n}\n\nexport function loadAutoPrefs(toolName: string, visInit: T): T {\n try {\n const raw = localStorage.getItem(prefsStorageKey(toolName));\n if (!raw) return { ...visInit };\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") return { ...visInit };\n if (parsed.tool && parsed.tool !== toolName) return { ...visInit };\n const rawSettings: PrefsRecord = parsed.settings || parsed;\n // Run through the migration scaffold so the next schema bump has a\n // single seam to thread through. Today this is a pass-through for\n // version === 1; a stranger or unsupported future version returns\n // null, falling back to defaults.\n const settings = migratePrefs(rawSettings, parsed.version);\n if (!settings) return { ...visInit };\n return mergePrefsSettings(visInit, settings, { onlyStyle: true });\n } catch (_e) {\n return { ...visInit };\n }\n}\n\n// Per-tool debounce so rapid slider drags don't thrash localStorage.\nconst prefsSaveTimers: Record | null> = {};\nexport function saveAutoPrefs(toolName: string, vis: T): void {\n const existing = prefsSaveTimers[toolName];\n if (existing) {\n clearTimeout(existing);\n }\n prefsSaveTimers[toolName] = setTimeout(() => {\n try {\n const payload = {\n tool: toolName,\n version: PREFS_SCHEMA_VERSION,\n savedAt: new Date().toISOString(),\n settings: extractStylePrefs(vis),\n };\n localStorage.setItem(prefsStorageKey(toolName), JSON.stringify(payload));\n } catch (_e) {\n /* quota or private-mode \u2014 silent fallback */\n }\n prefsSaveTimers[toolName] = null;\n }, 300);\n}\n\n// Synchronous flush (used by tests and by clearAutoPrefs so storage state is\n// deterministic). Not called from production code paths directly.\nexport function flushAutoPrefs(toolName: string, vis: T): void {\n const existing = prefsSaveTimers[toolName];\n if (existing) {\n clearTimeout(existing);\n prefsSaveTimers[toolName] = null;\n }\n try {\n const payload = {\n tool: toolName,\n version: PREFS_SCHEMA_VERSION,\n savedAt: new Date().toISOString(),\n settings: extractStylePrefs(vis),\n };\n localStorage.setItem(prefsStorageKey(toolName), JSON.stringify(payload));\n } catch (_e) {\n /* silent */\n }\n}\n\nexport function clearAutoPrefs(toolName: string): void {\n const existing = prefsSaveTimers[toolName];\n if (existing) {\n clearTimeout(existing);\n prefsSaveTimers[toolName] = null;\n }\n try {\n localStorage.removeItem(prefsStorageKey(toolName));\n } catch (_e) {\n /* silent */\n }\n}\n\n// Build the JSON payload and trigger a browser download. Includes label keys\n// because this is an explicit user action and the resulting file is portable.\n//\n// The optional `downloader` parameter exists so the prefs test loader can\n// inject a capture function without relying on a global stub. Production\n// callers (`PrefsPanel.tsx`) omit it and get the default `_core/download`\n// `downloadText`.\nexport function exportPrefsFile(\n toolName: string,\n vis: T,\n downloader: (text: string, filename: string) => void = defaultDownloadText\n): void {\n const payload = {\n tool: toolName,\n version: PREFS_SCHEMA_VERSION,\n savedAt: new Date().toISOString(),\n settings: { ...vis },\n };\n const text = JSON.stringify(payload, null, 2);\n downloader(text, toolName + \"-settings.json\");\n}\n\nexport type ImportPrefsCallback = (\n merged: T | null,\n errorMessage: string | null\n) => void;\n\n// Open a file picker, read the selected file, validate, call cb with the\n// merged settings or an error message.\nexport function importPrefsFile(\n toolName: string,\n visInit: T,\n cb: ImportPrefsCallback\n): void {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".json,application/json\";\n input.style.display = \"none\";\n const cleanup = () => {\n if (input.parentNode) input.parentNode.removeChild(input);\n };\n input.addEventListener(\"change\", () => {\n const file = input.files && input.files[0];\n if (!file) {\n cleanup();\n return;\n }\n const reader = new FileReader();\n reader.onload = (ev) => {\n cleanup();\n let parsed: unknown;\n try {\n parsed = JSON.parse(String(ev.target?.result ?? \"\"));\n } catch (_e) {\n cb(null, \"Not a valid JSON file.\");\n return;\n }\n if (!parsed || typeof parsed !== \"object\") {\n cb(null, \"Not a valid settings file.\");\n return;\n }\n const obj = parsed as { tool?: string; settings?: PrefsRecord };\n if (obj.tool && obj.tool !== toolName) {\n cb(null, `This file is for the ${obj.tool} tool, not ${toolName}.`);\n return;\n }\n const settings: PrefsRecord = obj.settings || (obj as PrefsRecord);\n const merged = mergePrefsSettings(visInit, settings, { onlyStyle: false });\n cb(merged, null);\n };\n reader.onerror = () => {\n cleanup();\n cb(null, \"Could not read the selected file.\");\n };\n reader.readAsText(file);\n });\n // Some browsers need the input in the DOM for programmatic click to work.\n document.body.appendChild(input);\n input.click();\n}\n", "// Save / Load / Reset controls for plot render settings \u2014 rendered as a\n// single gear icon in PageHeader that toggles a small popover with the three\n// actions + any error message. The click-to-reveal design keeps power-user\n// chrome out of the header until asked for. Style tweaks still auto-persist\n// to localStorage in the background (see `usePlotToolState.ts` \u2192\n// `loadAutoPrefs` / `saveAutoPrefs`); these buttons cover the explicit\n// save-to-file / load-from-file / reset flow.\n\nimport { clearAutoPrefs, exportPrefsFile, importPrefsFile } from \"./prefs-store\";\n\nconst { useState, useRef, useEffect } = React;\n\ninterface PrefsPanelProps {\n // Tool name, matches the localStorage key written by `loadAutoPrefs`.\n tool: string;\n // Current vis state (passed verbatim into the exported JSON file).\n vis: T;\n // Defaults \u2014 used as the whitelist when validating an imported file.\n visInit: T;\n // Reducer dispatch; supports a `_reset: true` sentinel handled by\n // `usePlotToolState`'s reducer.\n updVis: (patch: Partial | { _reset: true }) => void;\n}\n\nexport function PrefsPanel({ tool, vis, visInit, updVis }: PrefsPanelProps) {\n const [error, setError] = useState(\"\");\n const [open, setOpen] = useState(false);\n const wrapRef = useRef(null);\n\n // Close on outside-click or Escape while the popover is open.\n useEffect(() => {\n if (!open) return undefined;\n const onDown = (ev: MouseEvent) => {\n if (wrapRef.current && !wrapRef.current.contains(ev.target as Node)) setOpen(false);\n };\n const onKey = (ev: KeyboardEvent) => {\n if (ev.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"mousedown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"mousedown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open]);\n\n const handleSave = () => {\n exportPrefsFile(tool, vis);\n setError(\"\");\n setOpen(false);\n };\n const handleLoad = () => {\n importPrefsFile(tool, visInit, (merged, errMsg) => {\n if (errMsg || !merged) {\n // Keep popover open to show the error inline.\n setError(errMsg ?? \"Could not load settings file.\");\n setOpen(true);\n return;\n }\n updVis(merged);\n setError(\"\");\n setOpen(false);\n });\n };\n const handleReset = () => {\n updVis({ _reset: true });\n clearAutoPrefs(tool);\n setError(\"\");\n setOpen(false);\n };\n\n const menuBtnStyle = {\n padding: \"6px 10px\",\n fontSize: 12,\n lineHeight: 1.2,\n textAlign: \"left\" as const,\n };\n\n return (\n
\n setOpen((x) => !x)}\n aria-label=\"Visual plot settings\"\n aria-expanded={open}\n aria-haspopup=\"menu\"\n title=\"Visual plot settings\"\n style={{\n width: 40,\n height: 40,\n padding: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: open ? \"var(--surface-sunken)\" : \"transparent\",\n border: \"1px solid var(--border)\",\n borderRadius: 6,\n cursor: \"pointer\",\n color: \"var(--text-muted)\",\n }}\n >\n {/* Gear glyph \u2014 feather-style, painted through currentColor so it\n inherits the button's themed text color. */}\n \n \n \n \n \n {open && (\n \n \n Visual plot settings\n \n \n Save to file\n \n \n Load from file\n \n \n Reset to defaults\n \n {error && (\n \n {error}\n

\n )}\n
\n )}\n \n );\n}\n", "// `StepNavBar` \u2014 horizontal stepper with circles + labels + connector\n// line. Past steps render a \u2713 on a filled --step-ready circle; the\n// current step renders its number on --step-active-bg chrome;\n// reachable-unvisited steps render a --step-ready outline; locked\n// steps render a neutral outline. Connector line between circles\n// fills --step-ready up to the last completed step.\n\nconst h = React.createElement;\n\ninterface StepNavBarProps {\n steps: string[];\n currentStep: string;\n onStepChange: (s: string) => void;\n canNavigate?: (s: string) => boolean;\n // Optional override for the visible label of a step key. Keys remain\n // the stable identifier used by navigation state; labels can be\n // dynamic (e.g. venn showing \"Import check\" vs \"Configure\").\n stepLabels?: Record;\n}\n\nexport function StepNavBar(props: StepNavBarProps) {\n const { steps, currentStep, onStepChange } = props;\n const canNavigate = props.canNavigate;\n const stepLabels = props.stepLabels || {};\n const currentIdx = steps.indexOf(currentStep);\n const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);\n const labelFor = (s: string) => stepLabels[s] || capitalize(s);\n const cells = steps.map((s, i) => {\n const enabled = canNavigate ? canNavigate(s) : true;\n const isCurrent = i === currentIdx;\n const isPast = i < currentIdx;\n const isReachableUnvisited = enabled && !isCurrent && !isPast;\n\n let circleBg: string;\n let circleBorder: string;\n let circleColor: string;\n let circleContent: React.ReactNode;\n if (isPast) {\n circleBg = \"var(--step-ready)\";\n circleBorder = \"none\";\n circleColor = \"var(--on-accent)\";\n circleContent = h(\n \"svg\",\n {\n key: \"check\",\n width: 18,\n height: 18,\n viewBox: \"0 0 24 24\",\n \"aria-hidden\": \"true\",\n style: { display: \"block\" },\n },\n h(\"path\", {\n d: \"M5 12.5l4.2 4.2L19 7\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n })\n );\n } else if (isCurrent) {\n circleBg = \"var(--step-active-bg)\";\n circleBorder = \"1px solid var(--step-active-border)\";\n circleColor = \"var(--on-accent)\";\n circleContent = String(i + 1);\n } else if (isReachableUnvisited) {\n circleBg = \"var(--surface)\";\n circleBorder = \"2px solid var(--step-ready)\";\n circleColor = \"var(--step-ready)\";\n circleContent = String(i + 1);\n } else {\n circleBg = \"var(--surface)\";\n circleBorder = \"1px solid var(--border)\";\n circleColor = \"var(--text-faint)\";\n circleContent = String(i + 1);\n }\n\n const labelColor = isCurrent\n ? \"var(--text)\"\n : isPast\n ? \"var(--text-muted)\"\n : isReachableUnvisited\n ? \"var(--text-faint)\"\n : \"var(--border)\";\n const labelWeight = isCurrent ? 600 : 500;\n\n const connector =\n i < steps.length - 1\n ? h(\"div\", {\n key: \"conn\",\n \"aria-hidden\": \"true\",\n style: {\n position: \"absolute\",\n top: 18,\n left: \"50%\",\n right: \"-50%\",\n height: 2,\n background: isPast ? \"var(--step-ready)\" : \"var(--border-strong)\",\n zIndex: 0,\n transition: \"background 160ms ease-out\",\n },\n })\n : null;\n\n // Visual weight scales with progress: past + current circles are large\n // (36 px) and reachable / locked future circles are smaller (28 px) so\n // the user's place in the flow is obvious at a glance. The current step\n // gets an additional outer ring (rendered as a CSS outline so it never\n // affects layout) in the same accent colour as the filled body \u2014\n // distinct from the 1 px border by virtue of the 2 px offset gap.\n const isLarge = isCurrent || isPast;\n const size = isLarge ? 36 : 28;\n const numberFontSize = isLarge ? 16 : 13;\n\n const circle = h(\n \"span\",\n {\n key: \"circle\",\n style: {\n position: \"relative\",\n zIndex: 1,\n width: size,\n height: size,\n borderRadius: \"50%\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: circleBg,\n border: circleBorder,\n color: circleColor,\n fontSize: numberFontSize,\n fontWeight: 600,\n lineHeight: 1,\n boxSizing: \"border-box\",\n // Current-step halo: an outline sits OUTSIDE the box without\n // contributing to layout (unlike `border`), so the surrounding\n // circles stay anchored to the same baseline. `outlineOffset`\n // creates the visible gap between the filled body and the ring.\n ...(isCurrent\n ? {\n outline: \"2px solid var(--step-active-border)\",\n outlineOffset: \"2px\",\n }\n : null),\n transition:\n \"background 160ms ease-out, border-color 160ms ease-out, color 160ms ease-out, width 160ms ease-out, height 160ms ease-out, outline-color 160ms ease-out\",\n },\n },\n circleContent\n );\n\n // Fixed 36 px row containing the circle so smaller (future) circles stay\n // vertically centred on the connector line and labels keep a consistent\n // baseline across the row.\n const circleRow = h(\n \"div\",\n {\n key: \"circle-row\",\n style: {\n height: 36,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n position: \"relative\",\n zIndex: 1,\n },\n },\n circle\n );\n\n const label = h(\n \"span\",\n {\n key: \"label\",\n style: {\n marginTop: 6,\n fontSize: 11,\n fontWeight: labelWeight,\n color: labelColor,\n textTransform: \"capitalize\",\n letterSpacing: 0.2,\n whiteSpace: \"nowrap\",\n },\n },\n labelFor(s)\n );\n\n const button = h(\n \"button\",\n {\n key: \"btn\",\n type: \"button\",\n onClick: enabled ? () => onStepChange(s) : undefined,\n disabled: !enabled,\n \"aria-current\": isCurrent ? \"step\" : undefined,\n \"aria-label\": \"Step \" + (i + 1) + \" of \" + steps.length + \": \" + labelFor(s),\n // Stable test handle. The previous e2e selector\n // `getByRole(\"button\", { name: /Plot$/ })` matched both this pill\n // and the SPA topbar's tool-icon buttons (which also end in\n // \"... Plot\"), so .first() picked the wrong one. Tests now use\n // `getByTestId(\"step-plot\")` etc.\n \"data-testid\": \"step-\" + s,\n style: {\n all: \"unset\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n cursor: enabled ? \"pointer\" : \"default\",\n position: \"relative\",\n zIndex: 1,\n },\n },\n circleRow,\n label\n );\n\n return h(\n \"div\",\n {\n key: \"step-\" + s,\n style: {\n flex: \"1 1 0\",\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n minWidth: 0,\n },\n },\n connector,\n button\n );\n });\n\n return h(\n \"div\",\n {\n style: {\n display: \"flex\",\n alignItems: \"flex-start\",\n padding: \"8px 0 4px\",\n width: \"100%\",\n },\n },\n cells\n );\n}\n", "// `CommaFixBanner` \u2014 yellow status banner shown when the parser auto-\n// converted decimal commas to dots during ingest. `commaFixed` is the\n// boolean toggle, `commaFixCount` is the number of values fixed.\n\nconst h = React.createElement;\n\ninterface CommaFixBannerProps {\n commaFixed: boolean;\n commaFixCount: number;\n}\n\nexport function CommaFixBanner(props: CommaFixBannerProps) {\n if (!props.commaFixed) return null;\n return h(\n \"div\",\n {\n className: \"dv-panel\",\n role: \"status\",\n style: {\n background: \"var(--warning-bg)\",\n borderColor: \"var(--warning-border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n padding: \"10px 16px\",\n },\n },\n h(\"span\", { style: { fontSize: 18 }, \"aria-hidden\": \"true\" }, \"\uD83D\uDD04\"),\n h(\n \"div\",\n { style: { flex: 1 } },\n h(\n \"p\",\n { style: { margin: 0, fontSize: 12, color: \"var(--warning-text)\", fontWeight: 600 } },\n \"Decimal commas automatically converted to dots\"\n ),\n h(\n \"p\",\n {\n style: { margin: \"2px 0 0\", fontSize: 11, color: \"var(--warning-text)\", opacity: 0.85 },\n },\n props.commaFixCount +\n \" value\" +\n (props.commaFixCount > 1 ? \"s\" : \"\") +\n ' had commas as decimal separators (e.g. \"0,5\" \u2192 \"0.5\").'\n )\n )\n );\n}\n", "import type { FormulaInjectionWarning } from \"../_core/csv\";\n\n// `FormulaInjectionBanner` \u2014 yellow alert banner that surfaces the\n// result of `scanForFormulaInjection` at ingest time. Shows up when the\n// uploaded file contains cells / headers that would trigger formula\n// evaluation in Excel / LibreOffice / Sheets. Pl\u00F6ttr exports prefix\n// these with a leading apostrophe to neutralise them, but the original\n// file is unchanged so flagging the input is the safer signal.\n\nconst h = React.createElement;\n\ninterface FormulaInjectionBannerProps {\n warning: FormulaInjectionWarning | null;\n}\n\nexport function FormulaInjectionBanner(props: FormulaInjectionBannerProps) {\n const w = props.warning;\n if (!w || !w.count) return null;\n const trim = (v: unknown) => {\n const s = String(v);\n return s.length > 80 ? s.slice(0, 80) + \"\u2026\" : s;\n };\n const fmtCell = (c: { header?: string | null; row: number; col: number; value: unknown }) => {\n const where = c.header\n ? \"\u201C\" + c.header + \"\u201D row \" + (c.row + 1)\n : \"row \" + (c.row + 1) + \" col \" + (c.col + 1);\n return where + \": \" + trim(c.value);\n };\n const fmtHeader = (hdr: { idx: number; value: unknown }) => {\n return \"column \" + (hdr.idx + 1) + \": \" + trim(hdr.value);\n };\n const examples: string[] = [];\n for (let i = 0; i < w.headers.length; i++) examples.push(\"Header \u2014 \" + fmtHeader(w.headers[i]));\n for (let i = 0; i < w.cells.length; i++) examples.push(fmtCell(w.cells[i]));\n const shown = examples.length;\n const overflow = w.count - shown;\n return h(\n \"div\",\n {\n role: \"alert\",\n style: {\n marginBottom: 16,\n padding: \"10px 14px\",\n borderRadius: 8,\n background: \"var(--warning-bg)\",\n border: \"1px solid var(--warning-border)\",\n display: \"flex\",\n alignItems: \"flex-start\",\n gap: 10,\n },\n },\n h(\"span\", { style: { fontSize: 18, lineHeight: \"20px\" }, \"aria-hidden\": \"true\" }, \"\u26A0\uFE0F\"),\n h(\n \"div\",\n { style: { flex: 1, minWidth: 0 } },\n h(\n \"p\",\n { style: { margin: 0, fontSize: 12, color: \"var(--warning-text)\", fontWeight: 700 } },\n \"Suspicious cells in uploaded data (\" + w.count + (w.count === 1 ? \" cell\" : \" cells\") + \")\"\n ),\n h(\n \"p\",\n {\n style: {\n margin: \"2px 0 6px\",\n fontSize: 11,\n color: \"var(--warning-text)\",\n opacity: 0.9,\n },\n },\n \"Cells starting with \" +\n \"= + - @ tab CR\" +\n \" are treated as formulas by Excel / LibreOffice / Sheets and could exfiltrate or run code if you re-open this data there. Pl\u00F6ttr exports prefix them with a leading apostrophe to neutralise them \u2014 but the original file is unchanged, so handle with care.\"\n ),\n h(\n \"ul\",\n {\n style: {\n margin: 0,\n paddingLeft: 18,\n fontSize: 11,\n color: \"var(--warning-text)\",\n fontFamily:\n 'ui-monospace, SFMono-Regular, Menlo, Monaco, \"Cascadia Mono\", \"Liberation Mono\", monospace',\n wordBreak: \"break-all\",\n },\n },\n examples.map((e, i) => h(\"li\", { key: i }, e))\n ),\n overflow > 0\n ? h(\n \"p\",\n {\n style: {\n margin: \"4px 0 0\",\n fontSize: 11,\n color: \"var(--warning-text)\",\n opacity: 0.85,\n },\n },\n \"\u2026and \" + overflow + \" more.\"\n )\n : null\n )\n );\n}\n", "// `ParseErrorBanner` \u2014 red alert banner shown when the parser raises\n// an error string. Renders nothing when `error` is null/undefined.\n\nconst h = React.createElement;\n\ninterface ParseErrorBannerProps {\n error: string | null | undefined;\n}\n\nexport function ParseErrorBanner(props: ParseErrorBannerProps) {\n if (!props.error) return null;\n return h(\n \"div\",\n {\n role: \"alert\",\n style: {\n marginBottom: 16,\n padding: \"10px 14px\",\n borderRadius: 8,\n background: \"var(--danger-bg)\",\n border: \"1px solid var(--danger-border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n },\n },\n h(\"span\", { style: { fontSize: 16 }, \"aria-hidden\": \"true\" }, \"\uD83D\uDEAB\"),\n h(\n \"span\",\n { style: { fontSize: 12, color: \"var(--danger-text)\", fontWeight: 600 } },\n props.error\n )\n );\n}\n", "// Outer page frame shared by every plot tool. Renders PageHeader (with the\n// PrefsPanel in the top-right slot), the StepNavBar, the comma-fix banner,\n// the parse-error banner, and delegates everything else to children \u2014 each\n// tool still owns the per-step content (UploadStep, ConfigureStep, plot\n// layout, etc.) since those differ per tool.\n//\nimport type { PlotToolState } from \"./usePlotToolState\";\nimport { PrefsPanel } from \"./PrefsPanel\";\nimport { PageHeader } from \"./PageHeader\";\nimport { StepNavBar } from \"./StepNavBar\";\nimport { CommaFixBanner } from \"./CommaFixBanner\";\nimport { FormulaInjectionBanner } from \"./FormulaInjectionBanner\";\nimport { ParseErrorBanner } from \"./ParseErrorBanner\";\n\nconst { useEffect, useRef, useState } = React;\n\ninterface PlotToolShellProps {\n state: PlotToolState;\n toolName: string;\n title: string;\n visInit: TVis;\n steps: string[];\n canNavigate: (target: string) => boolean;\n // Optional override for the step label shown in the nav bar. Useful when a\n // step's *purpose* varies with dataset shape (e.g. venn's step 2 is just an\n // import check with \u2264 3 sets but becomes a real configure flow with \u2265 4).\n // Keys = step identifiers (unchanged); values = human-readable labels.\n stepLabels?: Record;\n children: React.ReactNode;\n}\n\n// 120 ms opacity fade on step change. Tracks the current step in a ref so the\n// first mount doesn't fade in, and respects prefers-reduced-motion (no fade).\nfunction StepFade({ step, children }: { step: string; children: React.ReactNode }) {\n const [opacity, setOpacity] = useState(1);\n const prevStep = useRef(step);\n const reduceMotion =\n typeof window !== \"undefined\" &&\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n useEffect(() => {\n if (prevStep.current === step || reduceMotion) {\n prevStep.current = step;\n return undefined;\n }\n prevStep.current = step;\n setOpacity(0);\n const id = window.setTimeout(() => setOpacity(1), 10);\n return () => window.clearTimeout(id);\n }, [step, reduceMotion]);\n return (\n
\n {children}\n
\n );\n}\n\nexport function PlotToolShell({\n state,\n toolName,\n title,\n visInit,\n steps,\n canNavigate,\n stepLabels,\n children,\n}: PlotToolShellProps) {\n return (\n
\n \n }\n right={\n state.step === \"plot\" ? (\n \n ) : null\n }\n />\n \n \n \n {children}\n
\n );\n}\n", "// Plot card that adds horizontal-scroll affordances when its SVG content is\n// wider than the viewport: a left fade when scrolled away from the start,\n// a right fade when more content remains, and a transient \"Scroll for more\"\n// pill that hides once the user has scrolled. Every affordance is driven by\n// a ResizeObserver on the scroll container + its first child so the\n// overlays stay accurate across window resizes and SVG re-renders.\n//\n// Used only by upset.tsx today. Lives in `_shell/` because the lifted\n// behaviour was extracted from the original inline implementation in\n// upset.tsx; venn and heatmap don't need the affordance (their charts\n// auto-fit). Keep here as the canonical home for any future tool that grows\n// horizontally-scrolling content.\n\nconst { useState, useRef, useEffect, useCallback } = React;\n\ninterface ScrollablePlotCardProps {\n children: React.ReactNode;\n}\n\nexport function ScrollablePlotCard({ children }: ScrollablePlotCardProps) {\n const scrollRef = useRef(null);\n const [hasOverflow, setHasOverflow] = useState(false);\n const [atStart, setAtStart] = useState(true);\n const [atEnd, setAtEnd] = useState(true);\n const [userScrolled, setUserScrolled] = useState(false);\n\n const measure = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n const overflows = el.scrollWidth > el.clientWidth + 1;\n setHasOverflow(overflows);\n setAtStart(el.scrollLeft <= 1);\n setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1);\n }, []);\n\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return undefined;\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n const content = el.firstElementChild;\n if (content) ro.observe(content);\n return () => ro.disconnect();\n }, [measure]);\n\n const onScroll = () => {\n measure();\n if (!userScrolled) setUserScrolled(true);\n };\n\n return (\n
\n \n {children}\n
\n {hasOverflow && !atStart && (\n \n )}\n {hasOverflow && !atEnd && (\n \n )}\n {hasOverflow && !userScrolled && (\n \n Scroll for more \u2192\n \n )}\n \n );\n}\n", "// Single source of truth for the inferential tests `selectTest()` can\n// recommend, plus the post-hoc family that pairs with each k\u22653 test.\n//\n// Pre-registry, the same test-id \u2192 (function, label, post-hoc, k=2\n// membership) mapping lived in 4+ places (`_shell/stats-dispatch.ts`,\n// `shared-stats-tile.js`, `aequorin/reports.ts`, the R-export switch in\n// `shared-r-export.js`); every site string-matched on identifiers like\n// `\"welchT\"` independently. The registry collapses dispatch + label\n// lookup + post-hoc routing + arity classification into one declarative\n// table.\n//\n// Naming asymmetry note: `selectTest()` returns `\"welchT\"` for k=2 even\n// though no `welchT` function exists \u2014 it dispatches to\n// `tTest(x, y, { equalVar: false })`. `\"welchANOVA\"` does match a\n// function. The registry is the only place that reconciles the\n// asymmetry; consumers don't need to know about it.\n\nimport { dunnTest, gamesHowell, tukeyHSD } from \"../_core/stats/posthoc\";\nimport { kruskalWallis, mannWhitneyU, oneWayANOVA, tTest, welchANOVA } from \"../_core/stats/tests\";\n\n// `RecommendedTest`, `RecommendedPostHoc`, `TestArity`, `StatsTestEntry`,\n// `StatsPostHocEntry` types stay declared in `types/globals.d.ts`\n// (ambient). selectTest() in stats-posthoc.js (still plain-JS) uses\n// them as ambient types; migrating the types out of globals.d.ts would\n// break that file.\n\nexport const STATS_TEST_REGISTRY: Record = {\n studentT: {\n label: \"Student's t-test\",\n shortLabel: \"Student's t\",\n arity: 2,\n postHoc: null,\n run: (values) => tTest(values[0], values[1], { equalVar: true }),\n },\n welchT: {\n label: \"Welch's t-test\",\n shortLabel: \"Welch's t\",\n arity: 2,\n postHoc: null,\n run: (values) => tTest(values[0], values[1], { equalVar: false }),\n },\n mannWhitney: {\n label: \"Mann-Whitney U\",\n arity: 2,\n postHoc: null,\n run: (values) => mannWhitneyU(values[0], values[1]),\n },\n oneWayANOVA: {\n label: \"One-way ANOVA\",\n arity: \"k\",\n postHoc: \"tukeyHSD\",\n run: (values) => oneWayANOVA(values),\n },\n welchANOVA: {\n label: \"Welch's ANOVA\",\n arity: \"k\",\n postHoc: \"gamesHowell\",\n run: (values) => welchANOVA(values),\n },\n kruskalWallis: {\n label: \"Kruskal-Wallis\",\n arity: \"k\",\n postHoc: \"dunn\",\n run: (values) => kruskalWallis(values),\n },\n};\n\nexport const STATS_POSTHOC_REGISTRY: Record<\n Exclude,\n StatsPostHocEntry\n> = {\n tukeyHSD: {\n label: \"Tukey HSD\",\n run: (values) => tukeyHSD(values),\n },\n gamesHowell: {\n label: \"Games-Howell\",\n run: (values) => gamesHowell(values),\n },\n dunn: {\n label: \"Dunn (BH-adjusted)\",\n run: (values) => dunnTest(values),\n },\n};\n\n// Used by UI dropdowns that surface \"what tests apply to two groups?\"\n// vs \"what tests apply to k\u22653 groups?\". Computed from the registry so\n// a new test entry with the right `arity` lights up the UI without a\n// second edit site.\nexport const STATS_TESTS_FOR_K2: RecommendedTest[] = (\n Object.keys(STATS_TEST_REGISTRY) as RecommendedTest[]\n).filter((id) => STATS_TEST_REGISTRY[id].arity === 2);\nexport const STATS_TESTS_FOR_K: RecommendedTest[] = (\n Object.keys(STATS_TEST_REGISTRY) as RecommendedTest[]\n).filter((id) => STATS_TEST_REGISTRY[id].arity === \"k\");\n", "// Generates a runnable R script from the StatsTile decision trace so the\n// user can paste it into RStudio and reproduce the exact same tests in a\n// \"real\" statistics environment. Closes the \"no reproducibility trail\"\n// gap called out in the README's Scope & limitations section.\n//\n// Public exports:\n// buildRScript(ctx) \u2014 for data-driven stats (boxplot, aequorin)\n// buildRScriptForPower(state) \u2014 for the power-analysis tool\n// sanitizeRString(s) \u2014 escape \" and \\ for safe R string inlining\n// sanitizeRComment(s) \u2014 strip line terminators for `#` comments\n// formatRNumber(n) \u2014 Number \u2192 R literal (period decimals,\n// NA for non-finite)\n// formatRVector(arr, perLine?) \u2014 number[] \u2192 wrapped \"c(...)\" literal\n// wrapRItems(items, perLine?) \u2014 pre-formatted items \u2192 wrapped \"c(...)\";\n// the single line-wrapping helper, capped\n// at R_MAX_LINE characters per line\n// R_MAX_LINE \u2014 hard per-line char cap for emitted R\n//\n// The ctx shape matches the StatsTile's internal report-builder shape so\n// the R-script chip can reuse the exact same context object without\n// threading new props.\n\nimport { STATS_TEST_REGISTRY, STATS_POSTHOC_REGISTRY } from \"./stats-registry\";\n\n// Test / post-hoc display labels are sourced from the shared registry \u2014\n// same labels the StatsTile uses on screen, so the R script's\n// `# Welch's t-test` comment matches the test name shown to the user.\n// The R-code generation below still has per-test branches because each\n// test produces a different R function call (`t.test`, `wilcox.test`,\n// `oneway.test`, \u2026) \u2014 the dispatch lives in this file because the\n// emitted strings are R-specific, not part of the JS dispatcher\n// surface that the registry collapses.\nconst _R_TEST_LABELS: Record = Object.fromEntries(\n Object.entries(STATS_TEST_REGISTRY).map((entry) => [entry[0], entry[1].label])\n);\n\nconst _R_POSTHOC_LABELS: Record = Object.fromEntries(\n Object.entries(STATS_POSTHOC_REGISTRY).map((entry) => [entry[0], entry[1].label])\n);\n\nexport function sanitizeRString(s: unknown): string {\n // Escape backslashes first, then double-quotes. All line terminators\n // (LF, CR, NEL, LS, PS) are flattened to a single space \u2014 a multi-line\n // factor level is almost certainly a paste accident and would break\n // the one-line data.frame layout. The CR strip is also a\n // security-relevant defence: R's lexer treats `\\r` as a statement\n // terminator inside source files, so a column name like\n // `\"foo\\rsystem('cmd')\"` would otherwise escape the surrounding R\n // string in some contexts.\n return String(s)\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/[\\r\\n\\u0085\\u2028\\u2029]/g, \" \");\n}\n\n// For any user-supplied string that lands in a `# ...` R comment line.\n// `sanitizeRString` is for *quoted* string literals; comments need a\n// stricter scrub \u2014 a CR or LF inside a comment ends the comment, so any\n// embedded line terminator must be flattened. Backslashes / quotes are\n// left alone (they're harmless inside a comment).\nexport function sanitizeRComment(s: unknown): string {\n return String(s).replace(/[\\r\\n\\u0085\\u2028\\u2029]/g, \" \");\n}\n\nexport function formatRNumber(n: number | null | undefined): string {\n if (n == null || !Number.isFinite(n)) return \"NA\";\n return String(n);\n}\n\n// Hard cap on the length of any single line of generated R source. R's\n// parser rejects an input line longer than 4094 characters; 120 keeps\n// the output well clear of that and roughly in line with the repo's own\n// formatting width. Every line of a `c(...)` literal returned by\n// `wrapRItems` is guaranteed to be within this cap.\nexport const R_MAX_LINE = 120;\n\n// Margin subtracted from R_MAX_LINE when wrapRItems decides whether a\n// vector is short enough to keep on a single line. Callers prepend an\n// assignment prefix (`x <- `, `row_labels <- `, ` group = `, \u2026) that\n// the helper cannot see; reserving 20 chars keeps the compact form's\n// physical line within R_MAX_LINE for every prefix the codebase emits.\nconst _R_PREFIX_RESERVE = 20;\n\n// The single line-wrapping helper for inlined R vectors. Packs the\n// pre-formatted `items` into a `c(...)` literal, breaking to a new\n// indented line whenever either `perLine` items have been placed or the\n// next item would push the line past R_MAX_LINE. `formatRVector`, the\n// long-format data.frame builder, the heatmap label / matrix emit and\n// volcano's data columns all route through here \u2014 there is no other\n// wrap path.\n//\n// `perLine` (default 8) is a soft hint; the R_MAX_LINE cap always wins,\n// so a caller may pass a large `perLine` (e.g. one heatmap matrix row\n// per line) and still get a re-wrap when that row is too wide. A single\n// item longer than the cap unavoidably gets an over-long line of its\n// own \u2014 an R token cannot be split.\nexport function wrapRItems(items: string[], perLine?: number): string {\n if (items.length === 0) return \"c()\";\n const P = perLine && perLine > 0 ? perLine : 8;\n const indent = \" \";\n // A wrapped content line carries a trailing \",\" (all but the last),\n // so budget one char for it when measuring against the cap.\n const lineBudget = R_MAX_LINE - indent.length - 1;\n const rows: string[] = [];\n let cur: string[] = [];\n let curLen = 0; // length of cur.join(\", \")\n for (const item of items) {\n if (cur.length > 0 && (cur.length >= P || curLen + 2 + item.length > lineBudget)) {\n rows.push(indent + cur.join(\", \"));\n cur = [];\n curLen = 0;\n }\n curLen += cur.length === 0 ? item.length : 2 + item.length;\n cur.push(item);\n }\n if (cur.length > 0) rows.push(indent + cur.join(\", \"));\n if (rows.length === 1) {\n const oneLine = \"c(\" + items.join(\", \") + \")\";\n if (oneLine.length <= R_MAX_LINE - _R_PREFIX_RESERVE) return oneLine;\n }\n return \"c(\\n\" + rows.join(\",\\n\") + \"\\n )\";\n}\n\n// number[] \u2192 a `c(...)` literal, line-wrapped via wrapRItems.\nexport function formatRVector(arr: Array, perLine?: number): string {\n return wrapRItems(arr.map(formatRNumber), perLine);\n}\n\n// Build a long-format data.frame literal: one row per observation, columns\n// `group` (character, re-factored with the tile's display order) and `value`.\nfunction _longFormatDataFrame(names: string[], values: number[][], varName?: string): string {\n const vn = varName || \"df\";\n const groupEntries: string[] = [];\n const valueEntries: string[] = [];\n for (let i = 0; i < names.length; i++) {\n const quoted = '\"' + sanitizeRString(names[i]) + '\"';\n const vs = values[i] || [];\n for (let j = 0; j < vs.length; j++) {\n groupEntries.push(quoted);\n valueEntries.push(formatRNumber(vs[j]));\n }\n }\n // The factor-level vector is emitted as its own assignment rather\n // than inlined into factor(): a long `levels = c(...)` argument would\n // carry a ~40-char `factor(...)` prefix that wrapRItems cannot see, so\n // a standalone assignment keeps every line within R_MAX_LINE.\n const levelItems = names.map((n) => '\"' + sanitizeRString(n) + '\"');\n return [\n vn + \" <- data.frame(\",\n \" group = \" + wrapRItems(groupEntries) + \",\",\n \" value = \" + wrapRItems(valueEntries) + \",\",\n \" stringsAsFactors = FALSE\",\n \")\",\n vn + \"_levels <- \" + wrapRItems(levelItems),\n vn + \"$group <- factor(\" + vn + \"$group, levels = \" + vn + \"_levels)\",\n ].join(\"\\n\");\n}\n\nfunction _rPackagesFor(postHocName: string | null | undefined): string[] {\n const pkgs = [\"car\"];\n if (postHocName === \"gamesHowell\" || postHocName === \"dunn\") pkgs.push(\"rstatix\");\n return pkgs;\n}\n\nfunction _headerComment(generated: string, dataNote: string | null | undefined): string {\n const lines = [\n \"# -----------------------------------------------------------------------------\",\n \"# Pl\u00F6ttr \u2014 R script export\",\n \"# Generated: \" + generated,\n \"#\",\n \"# This script reproduces the statistical tests run in the browser tool.\",\n \"# Plots are intentionally omitted \u2014 regenerate them in ggplot2 from `df` below.\",\n ];\n if (dataNote) {\n lines.push(\"#\");\n // Split on every line-terminator R recognises so a hostile multi-line\n // `dataNote` becomes multiple comment lines \u2014 each one then run through\n // sanitizeRComment so a stray terminator the split missed still can't\n // escape the comment.\n const noteLines = String(dataNote).split(/\\r\\n|[\\r\\n\\u0085\\u2028\\u2029]/);\n for (let i = 0; i < noteLines.length; i++) {\n lines.push(\"# \" + sanitizeRComment(noteLines[i]));\n }\n }\n lines.push(\"# -----------------------------------------------------------------------------\");\n return lines.join(\"\\n\");\n}\n\nfunction _mainTestBlock(chosenTest: string | null | undefined): string {\n const header = \"# --- Main test ---------------------------------------------------------------\";\n const label = (chosenTest && _R_TEST_LABELS[chosenTest]) || chosenTest || \"\u2014\";\n const pickComment = \"# Toolbox picked: \" + label;\n let call: string;\n if (chosenTest === \"studentT\") {\n call = \"t.test(value ~ group, data = df, var.equal = TRUE)\";\n } else if (chosenTest === \"welchT\") {\n call = \"t.test(value ~ group, data = df, var.equal = FALSE)\";\n } else if (chosenTest === \"mannWhitney\") {\n call = \"wilcox.test(value ~ group, data = df, exact = FALSE)\";\n } else if (chosenTest === \"oneWayANOVA\") {\n call = \"fit <- aov(value ~ group, data = df)\\nsummary(fit)\";\n } else if (chosenTest === \"welchANOVA\") {\n call = \"oneway.test(value ~ group, data = df, var.equal = FALSE)\";\n } else if (chosenTest === \"kruskalWallis\") {\n call = \"kruskal.test(value ~ group, data = df)\";\n } else {\n call = \"# (no inferential test was run)\";\n }\n return [header, pickComment, call].join(\"\\n\");\n}\n\nfunction _postHocBlock(postHocName: string | null | undefined, k: number): string {\n if (!postHocName || k < 3) return \"\";\n const header = \"# --- Post-hoc ----------------------------------------------------------------\";\n const label = _R_POSTHOC_LABELS[postHocName] || postHocName;\n const pickComment = \"# Toolbox picked: \" + label;\n let call: string;\n if (postHocName === \"tukeyHSD\") {\n call = \"TukeyHSD(aov(value ~ group, data = df))\";\n } else if (postHocName === \"gamesHowell\") {\n call = \"rstatix::games_howell_test(df, value ~ group)\";\n } else if (postHocName === \"dunn\") {\n call = 'rstatix::dunn_test(df, value ~ group, p.adjust.method = \"BH\")';\n } else {\n call = \"# (unknown post-hoc)\";\n }\n return [header, pickComment, call].join(\"\\n\");\n}\n\nexport interface BuildRScriptCtx {\n names?: string[];\n values?: number[][];\n recommendation?: { recommendation?: { reason?: string } } | null;\n chosenTest?: string | null;\n postHocName?: string | null;\n dataNote?: string | null;\n generatedAt?: string;\n}\n\nexport function buildRScript(ctx: BuildRScriptCtx | null | undefined): string {\n const names = (ctx && ctx.names) || [];\n const values = (ctx && ctx.values) || [];\n const recommendation = ctx && ctx.recommendation;\n const chosenTest = ctx && ctx.chosenTest;\n const postHocName = ctx && ctx.postHocName;\n const dataNote = ctx && ctx.dataNote;\n const generated = (ctx && ctx.generatedAt) || new Date().toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n\n const k = names.length;\n const pkgs = _rPackagesFor(postHocName);\n\n const parts: string[] = [];\n parts.push(_headerComment(generated, dataNote));\n parts.push(\"\");\n parts.push(\"# install.packages(c(\" + pkgs.map((p) => '\"' + p + '\"').join(\", \") + \"))\");\n for (let i = 0; i < pkgs.length; i++) parts.push(\"library(\" + pkgs[i] + \")\");\n parts.push(\"\");\n\n parts.push(_longFormatDataFrame(names, values, \"df\"));\n parts.push(\"\");\n\n parts.push(\"# --- Descriptive statistics --------------------------------------------------\");\n parts.push(\"desc <- do.call(rbind, by(df$value, df$group, function(v) {\");\n parts.push(\" n <- length(v)\");\n parts.push(\" m <- mean(v)\");\n parts.push(\" s <- if (n > 1) sd(v) else 0\");\n parts.push(\" sem <- if (n > 1) s / sqrt(n) else 0\");\n parts.push(\" ci95 <- if (n > 1) qt(0.975, n - 1) * sem else 0\");\n parts.push(\" data.frame(n = n, mean = m, sd = s, sem = sem, ci95 = ci95)\");\n parts.push(\"}))\");\n parts.push(\"print(desc)\");\n parts.push(\"\");\n\n parts.push(\"# --- Assumptions -------------------------------------------------------------\");\n parts.push(\"by(df$value, df$group, shapiro.test)\");\n parts.push('car::leveneTest(value ~ group, data = df, center = \"median\")');\n parts.push(\"\");\n\n parts.push(_mainTestBlock(chosenTest));\n\n const ph = _postHocBlock(postHocName, k);\n if (ph) {\n parts.push(\"\");\n parts.push(ph);\n }\n\n const reason =\n recommendation && recommendation.recommendation && recommendation.recommendation.reason;\n if (reason) {\n parts.push(\"\");\n parts.push(\"# Decision-tree rationale (from the toolbox):\");\n const rLines = String(reason).split(/\\r\\n|[\\r\\n\\u0085\\u2028\\u2029]/);\n for (let i = 0; i < rLines.length; i++) parts.push(\"# \" + sanitizeRComment(rLines[i]));\n }\n\n return parts.join(\"\\n\") + \"\\n\";\n}\n\n// \u2500\u2500 Power analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst _R_POWER_TEST_LABELS: Record = {\n \"t-ind\": \"Two-sample t-test (independent)\",\n \"t-paired\": \"Paired t-test\",\n \"t-one\": \"One-sample t-test\",\n anova: \"One-way ANOVA\",\n chi2: \"Chi-square test\",\n correlation: \"Correlation test\",\n};\n\nfunction _tTypeForTestKey(testKey: string): string | null {\n if (testKey === \"t-ind\") return \"two.sample\";\n if (testKey === \"t-paired\") return \"paired\";\n if (testKey === \"t-one\") return \"one.sample\";\n return null;\n}\n\nfunction _alternativeForTails(tails: number | undefined): string {\n return tails === 1 ? \"one.sided\" : \"two.sided\";\n}\n\nfunction _pwrArg(\n arg: string,\n value: number | null | undefined,\n solveFor: string | undefined,\n annotation?: string\n): string {\n const isSolveTarget =\n (arg === \"n\" && solveFor === \"n\") || (arg === \"power\" && solveFor === \"power\");\n const rhs = isSolveTarget ? \"NULL\" : formatRNumber(value);\n const suffix = isSolveTarget ? \" # <- solve for this\" : annotation ? \" # \" + annotation : \"\";\n return \" \" + arg + \" = \" + rhs + \",\" + suffix;\n}\n\nfunction _pwrArgString(arg: string, value: string | null): string {\n return \" \" + arg + ' = \"' + value + '\",';\n}\n\nexport interface PowerScriptState {\n testKey?: string;\n solveFor?: string;\n es?: number;\n n?: number;\n alpha?: number;\n power?: number;\n tails?: number;\n k?: number;\n df?: number;\n result?: number | null;\n generatedAt?: string;\n}\n\nfunction _pwrCallBody(testKey: string, state: PowerScriptState): string[] {\n const solveFor = state.solveFor;\n const tTypeArg = _tTypeForTestKey(testKey);\n const lines: string[] = [];\n if (testKey === \"t-ind\" || testKey === \"t-paired\" || testKey === \"t-one\") {\n lines.push(_pwrArg(\"n\", state.n, solveFor));\n lines.push(_pwrArg(\"d\", state.es, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n lines.push(_pwrArgString(\"type\", tTypeArg));\n lines.push(_pwrArgString(\"alternative\", _alternativeForTails(state.tails)));\n } else if (testKey === \"anova\") {\n lines.push(_pwrArg(\"k\", state.k, solveFor));\n lines.push(_pwrArg(\"n\", state.n, solveFor));\n lines.push(_pwrArg(\"f\", state.es, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n } else if (testKey === \"chi2\") {\n lines.push(_pwrArg(\"w\", state.es, solveFor));\n // pwr::pwr.chisq.test uses N (not n) for the total sample size.\n const Ntag = solveFor === \"n\" ? \"NULL\" : formatRNumber(state.n);\n const Nsuffix = solveFor === \"n\" ? \" # <- solve for this\" : \"\";\n lines.push(\" N = \" + Ntag + \",\" + Nsuffix);\n lines.push(_pwrArg(\"df\", state.df, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n } else if (testKey === \"correlation\") {\n lines.push(_pwrArg(\"n\", state.n, solveFor));\n lines.push(_pwrArg(\"r\", state.es, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n lines.push(_pwrArgString(\"alternative\", _alternativeForTails(state.tails)));\n }\n // Drop the trailing comma from whatever the last non-comment line is.\n for (let i = lines.length - 1; i >= 0; i--) {\n const ln = lines[i];\n const commentIdx = ln.indexOf(\"#\");\n const beforeComment = commentIdx >= 0 ? ln.slice(0, commentIdx) : ln;\n const trimmed = beforeComment.replace(/,\\s*$/, \" \");\n lines[i] = commentIdx >= 0 ? trimmed + ln.slice(commentIdx) : trimmed.replace(/\\s+$/, \"\");\n break;\n }\n return lines;\n}\n\nfunction _pwrCallName(testKey: string | undefined): string | null {\n if (testKey === \"t-ind\" || testKey === \"t-paired\" || testKey === \"t-one\") {\n return \"pwr::pwr.t.test\";\n }\n if (testKey === \"anova\") return \"pwr::pwr.anova.test\";\n if (testKey === \"chi2\") return \"pwr::pwr.chisq.test\";\n if (testKey === \"correlation\") return \"pwr::pwr.r.test\";\n return null;\n}\n\nexport function buildRScriptForPower(state: PowerScriptState | null | undefined): string {\n const s = state || {};\n const testKey = s.testKey;\n const call = testKey ? _pwrCallName(testKey) : null;\n const generated = s.generatedAt || new Date().toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n\n const lines: string[] = [];\n lines.push(\"# -----------------------------------------------------------------------------\");\n lines.push(\"# Pl\u00F6ttr \u2014 Power analysis R export\");\n lines.push(\"# Generated: \" + generated);\n lines.push(\"#\");\n lines.push(\n \"# Test: \" + ((testKey && _R_POWER_TEST_LABELS[testKey]) || testKey || \"(unknown)\")\n );\n lines.push(\n \"# Solving for: \" +\n (s.solveFor === \"n\"\n ? \"sample size (n)\"\n : s.solveFor === \"power\"\n ? \"power\"\n : s.solveFor || \"(unknown)\")\n );\n lines.push(\"#\");\n lines.push(\"# Required package:\");\n lines.push('# install.packages(\"pwr\")');\n lines.push(\"# -----------------------------------------------------------------------------\");\n lines.push(\"\");\n lines.push(\"library(pwr)\");\n lines.push(\"\");\n\n if (!call || !testKey) {\n lines.push(\"# (unknown test id \u2014 no pwr call emitted)\");\n return lines.join(\"\\n\") + \"\\n\";\n }\n\n lines.push(call + \"(\");\n const body = _pwrCallBody(testKey, s);\n for (let i = 0; i < body.length; i++) lines.push(body[i]);\n lines.push(\")\");\n\n if (s.result != null && Number.isFinite(s.result)) {\n lines.push(\"\");\n if (s.solveFor === \"n\") {\n lines.push(\"# Toolbox reported: n = \" + s.result);\n } else if (s.solveFor === \"power\") {\n lines.push(\"# Toolbox reported: power = \" + (s.result * 100).toFixed(1) + \"%\");\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n", "// `DatasheetIcon` \u2014 single shared hand-drawn notepad icon for the\n// sample-dataset banner on every plot tool's upload step. Replaced\n// the per-tool `TOOL_ICONS` art that briefly lived in that slot \u2014\n// repeating the tool icon there made the upload page top-heavy with\n// the same glyph (topbar + sample banner + how-to chevron) and over-\n// claimed visual centre; a plain datasheet sketch signals \"load the\n// example data\" without competing with the topbar's branding role.\n//\n// Drawing notes:\n// - Dog-eared page outline (top-right corner folded under) so it\n// reads as a paper / notebook rather than a generic rectangle.\n// - Four horizontal \"rows\" inside, the last one short on purpose to\n// suggest a trailing partial row in a real spreadsheet.\n// - Subtle hand-drawn wobble on the rows via quadratic curves (~0.5\n// px control-point offset). Outline is straight \u2014 over-sketched\n// edges read as cartoony at small sizes, the wobble belongs in the\n// interior strokes only.\n// - `stroke=\"currentColor\"` so the icon inherits the surrounding\n// text colour (e.g. `var(--success-text)` inside the sample\n// banner) and themes automatically in dark mode.\n\ninterface DatasheetIconProps {\n size?: number;\n}\n\nexport function DatasheetIcon({ size = 36 }: DatasheetIconProps) {\n return (\n \n {/* Page outline with dog-eared corner */}\n \n {/* The fold itself \u2014 short L joining the page edge to the dog-ear */}\n \n {/* Four rows with subtle hand-drawn wobble. Last row deliberately\n short so the spreadsheet \"feels\" alive rather than printed. */}\n \n \n \n \n \n );\n}\n", "// `UploadPanel` \u2014 separator selector + FileDropZone combo for the\n// upload step. Disables the drop zone until a separator is picked\n// (legacy mode). When `autoDetect` is on, the picker is hidden by\n// default \u2014 the tool's `doParse` calls `autoDetectSep` instead \u2014\n// and an \"Override separator \u25BE\" disclosure exposes the picker for\n// the edge cases the auto-detector can't resolve. With `onTextPaste`\n// set, Drop and Paste render side-by-side as equally-prominent cards\n// so the paste path is discoverable without a toggle.\n//\n// When `exampleSummary` is supplied alongside `autoDetect`, the\n// sample-dataset CTA promotes from the cramped \"Try sample data:\" line\n// beneath the cards into a prominent banner at the very top of the\n// upload step \u2014 icon, title, dataset description, primary button \u2014\n// because the legacy treatment is invisible under the two large cards.\n//\n// All three of these behaviours are opt-in props so the 7 other plot\n// tools keep their existing UX untouched while we evaluate the\n// auto-detect + paste + prominent-sample flow on boxplot.\n\nimport { DatasheetIcon } from \"./DatasheetIcon\";\nimport { FILE_LIMIT_BYTES, FILE_WARN_BYTES, FileDropZone } from \"./FileDropZone\";\n\nconst h = React.createElement;\nconst { useState } = React;\n\n// Structured payload for the prominent sample-dataset banner in\n// auto-detect mode. `icon` accepts any React node so callers can pass\n// the polished per-tool SVG via `toolIcon(, 32, { circle: true })`\n// from `shared.js` \u2014 that's the canonical pro-looking choice and what\n// every plot tool currently uses. Legacy emoji strings still render\n// fine (React treats them as text children). When this prop is\n// missing, the banner falls back to a generic \"Try a sample dataset\"\n// pitch with `exampleLabel` as the subtitle (legacy compat).\nexport interface ExampleSummary {\n title: string;\n subtitle?: string;\n icon?: React.ReactNode;\n buttonLabel?: string;\n}\n\ninterface UploadPanelProps {\n sepOverride: string;\n onSepChange: (s: string) => void;\n onFileLoad: (text: string, fileName: string) => void;\n onLoadExample?: () => void;\n exampleLabel?: React.ReactNode;\n hint?: string;\n // Opt-in: hide the separator picker behind an \"Override\" disclosure\n // and enable the drop zone immediately. The tool's `doParse` is\n // responsible for running `autoDetectSep(text, sepOverride)` itself\n // (sepOverride stays \"\" until the user expands and picks a value).\n autoDetect?: boolean;\n // Opt-in: surface a \"Paste data\" card alongside \"Drop file\" that\n // routes the textarea contents through the same parse pipeline as\n // a dropped file. Gates on FILE_LIMIT_BYTES / FILE_WARN_BYTES per\n // the ingest size policy in tools/CLAUDE.md.\n onTextPaste?: (text: string, fileName: string) => void;\n // Opt-in (autoDetect only): structured description for the prominent\n // sample-dataset banner at the top of the upload step. Replaces the\n // tiny bottom-of-card \"Try sample data: [button]\" affordance whose\n // visibility collapsed once Drop + Paste landed side-by-side.\n exampleSummary?: ExampleSummary;\n}\n\nexport function UploadPanel(props: UploadPanelProps) {\n if (props.autoDetect) {\n return ;\n }\n const { sepOverride, onSepChange, onFileLoad, onLoadExample, exampleLabel, hint } = props;\n return h(\n \"div\",\n { className: \"dv-panel\" },\n h(\n \"div\",\n {\n style: {\n marginBottom: 12,\n padding: \"12px 16px\",\n background: \"var(--info-bg)\",\n borderRadius: 8,\n border: \"1.5px solid var(--info-border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n },\n },\n h(\n \"label\",\n {\n htmlFor: \"dv-separator-select\",\n style: { fontSize: 13, fontWeight: 600, color: \"var(--accent-primary)\" },\n },\n \"Column separator\"\n ),\n h(\n \"select\",\n {\n id: \"dv-separator-select\",\n value: sepOverride,\n onChange: (e: React.ChangeEvent) => onSepChange(e.target.value),\n className: \"dv-select-sep\",\n },\n h(\"option\", { value: \"\" }, \"\u2014 Select \u2014\"),\n h(\"option\", { value: \",\" }, \"Comma (,)\"),\n h(\"option\", { value: \";\" }, \"Semicolon (;)\"),\n h(\"option\", { value: \"\\t\" }, \"Tab (\\\\t)\"),\n h(\"option\", { value: \" \" }, \"Space\")\n )\n ),\n !sepOverride\n ? h(\n \"div\",\n {\n style: {\n border: \"2px dashed var(--border-strong)\",\n borderRadius: 12,\n padding: \"48px 24px\",\n textAlign: \"center\",\n background: \"var(--surface-sunken)\",\n opacity: 0.5,\n },\n },\n h(\"div\", { style: { fontSize: 40, marginBottom: 8 }, \"aria-hidden\": \"true\" }, \"\uD83D\uDEAB\"),\n h(\n \"p\",\n { style: { margin: 0, fontSize: 15, color: \"var(--text-faint)\" } },\n \"Pick a column separator above to enable file loading\"\n )\n )\n : h(FileDropZone, {\n onFileLoad,\n accept: \".csv,.tsv,.txt,.dat,.tab\",\n hint: hint || \"CSV \u00B7 TSV \u00B7 TXT \u00B7 DAT \u2014 2 MB max\",\n }),\n onLoadExample\n ? h(\n \"div\",\n {\n style: {\n marginTop: 12,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 8,\n fontSize: 12,\n color: \"var(--text-muted)\",\n },\n },\n h(\"span\", null, \"Try sample data:\"),\n h(\n \"button\",\n {\n type: \"button\",\n className: \"dv-btn dv-btn-secondary\",\n onClick: onLoadExample,\n \"data-testid\": \"load-example\",\n },\n exampleLabel || \"Load example \u2192\"\n )\n )\n : null\n );\n}\n\n// New auto-detect + side-by-side variant. Behaviour:\n// - The separator picker is collapsed behind an \"Override \u25BE\" disclosure\n// that starts closed. The drop zone is enabled immediately; the tool's\n// parse pipeline calls autoDetectSep(text, sepOverride) so empty\n// sepOverride means \"auto\".\n// - When `onTextPaste` is provided, the panel renders Drop / Paste as\n// two equally-prominent bordered cards in a 2-column grid \u2014 both\n// always visible, no tab toggle to discover. The Paste card holds a\n// textarea + \"Parse pasted data\" button; size is measured with\n// `new Blob([text]).size` to match FileDropZone's FILE_LIMIT_BYTES\n// gate exactly.\n// - \"Try sample data\" stays in the same position as the legacy panel so\n// muscle memory and the load-example test selector still work.\nfunction AutoDetectUploadPanel(props: UploadPanelProps) {\n const {\n sepOverride,\n onSepChange,\n onFileLoad,\n onLoadExample,\n exampleLabel,\n exampleSummary,\n hint,\n onTextPaste,\n } = props;\n const [overrideOpen, setOverrideOpen] = useState(sepOverride !== \"\");\n const [pasteText, setPasteText] = useState(\"\");\n const [pasteError, setPasteError] = useState(null);\n const [pasteWarn, setPasteWarn] = useState(null);\n\n // Prominent sample-dataset banner. Renders only when the tool provides\n // `onLoadExample`; pulls structured title / subtitle / icon from\n // `exampleSummary`, with `exampleLabel` as a plain-string fallback.\n const renderSamplePromo = () => {\n if (!onLoadExample) return null;\n const summary: ExampleSummary = exampleSummary ?? {\n title: \"Try a sample dataset\",\n subtitle: typeof exampleLabel === \"string\" ? exampleLabel : undefined,\n };\n const buttonLabel = summary.buttonLabel ?? \"Plot this example \u2192\";\n return (\n \n \n {summary.icon ?? }\n \n
\n \n New here? Quick start\n
\n
\n {summary.title}\n
\n {summary.subtitle && (\n \n {summary.subtitle}\n \n )}\n \n \n {buttonLabel}\n \n \n );\n };\n\n const submitPaste = () => {\n setPasteError(null);\n setPasteWarn(null);\n const text = pasteText;\n if (!text || text.trim() === \"\") {\n setPasteError(\"Paste some data first \u2014 copy a selection from Excel, Sheets, or any CSV.\");\n return;\n }\n const bytes = new Blob([text]).size;\n if (bytes > FILE_LIMIT_BYTES) {\n setPasteError(\n `Pasted data too large (${(bytes / 1024 / 1024).toFixed(1)} MB). Maximum is 2 MB \u2014 split the data or sample rows and try again.`\n );\n return;\n }\n if (bytes > FILE_WARN_BYTES) {\n setPasteWarn(\n `Large paste (${(bytes / 1024 / 1024).toFixed(1)} MB) \u2014 parsing may take a moment.`\n );\n }\n if (onTextPaste) onTextPaste(text, \"pasted_data.csv\");\n };\n\n const cardWrap: React.CSSProperties = {\n borderRadius: 12,\n border: \"1.5px solid var(--border)\",\n background: \"var(--surface)\",\n overflow: \"hidden\",\n display: \"flex\",\n flexDirection: \"column\",\n };\n const cardHeader: React.CSSProperties = {\n background: \"var(--surface-subtle)\",\n padding: \"10px 14px\",\n borderBottom: \"1px solid var(--border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n };\n const cardHeaderLabel: React.CSSProperties = {\n fontSize: 12,\n fontWeight: 700,\n color: \"var(--text)\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.8px\",\n };\n const cardBody: React.CSSProperties = { padding: 14, flex: 1 };\n\n return (\n
\n {renderSamplePromo()}\n \n
\n \u2139\n \n Pl\u00F6ttr auto-detects the column separator (comma, tab, semicolon, \u2026) from the data.\n \n setOverrideOpen((v) => !v)}\n className=\"dv-btn dv-btn-secondary\"\n style={{\n marginLeft: \"auto\",\n padding: \"2px 8px\",\n fontSize: 11,\n fontWeight: 600,\n }}\n aria-expanded={overrideOpen}\n >\n {overrideOpen ? \"Hide override \u25B4\" : \"Override \u25BE\"}\n \n
\n {overrideOpen && (\n \n \n Force separator\n \n onSepChange(e.target.value)}\n className=\"dv-select-sep\"\n >\n \n \n \n \n \n \n \n Only needed when the detector picks the wrong delimiter.\n \n
\n )}\n \n\n \n
\n
\n \uD83D\uDCC2\n Drop a file\n
\n
\n \n
\n
\n\n {onTextPaste && (\n
\n
\n \uD83D\uDCCB\n Paste data\n
\n
\n setPasteText(e.target.value)}\n placeholder={\n \"Paste comma-, tab-, or semicolon-separated rows here.\\n\" +\n \"Tip: a selection copied from Excel or Google Sheets becomes tab-separated automatically.\"\n }\n className=\"dv-input\"\n spellCheck={false}\n style={{\n width: \"100%\",\n minHeight: 160,\n boxSizing: \"border-box\",\n fontFamily: \"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace\",\n fontSize: 12,\n lineHeight: 1.4,\n padding: \"10px 12px\",\n resize: \"vertical\",\n background: \"var(--surface)\",\n color: \"var(--text)\",\n }}\n aria-label=\"Paste tabular data\"\n />\n \n \n Parse pasted data\n \n {\n setPasteText(\"\");\n setPasteError(null);\n setPasteWarn(null);\n }}\n className=\"dv-btn dv-btn-secondary\"\n disabled={pasteText === \"\"}\n >\n Clear\n \n 2 MB max\n
\n {pasteError && (\n \n \uD83D\uDEAB\n {pasteError}\n
\n )}\n {pasteWarn && (\n \n \u26A0\uFE0F\n {pasteWarn}\n \n )}\n \n \n )}\n \n\n {/* Sample-dataset CTA moved to the prominent banner at the top\n (renderSamplePromo). The old bottom-of-card affordance is gone\n in auto-detect mode \u2014 it was unreadable beneath the side-by-side\n Drop + Paste cards, which was the whole point of the move. */}\n \n );\n}\n", "// One-shot inter-tool data hand-off. A source tool (e.g. RLU timecourse's\n// \u03A3 barplot tile) writes a small payload into localStorage under a single\n// fixed key, then top-level-navigates the browser to the destination\n// tool. The destination tool's App() reads-and-clears the payload on\n// mount; if it matches the expected target tool, the payload is consumed\n// instead of running through the regular upload step.\n//\n// Why localStorage and not postMessage / URL fragments:\n// \u2022 postMessage requires both pages to be alive simultaneously plus a\n// coordinator iframe; tool navigation hops the top-level frame, so\n// the source page is gone by the time the destination mounts.\n// \u2022 URL fragments hit the ~2 KB browser limit on payloads and leak\n// data into history / referers.\n// \u2022 localStorage is per-origin, synchronous, and the payload lives only\n// for the milliseconds between A's setHandoff and B's consumeHandoff\n// because consume always clears first (even if parsing later fails).\n//\n// Payload shape is intentionally loose \u2014 each consumer decides how to\n// parse the `csv` string and react to `source`. Conventional fields\n// listed in the HandoffPayload interface; tool-specific extensions\n// (e.g. `tools/upset/helpers.ts` \u2192 `UpsetHandoffPayload`) extend it.\n\nexport interface HandoffPayload {\n // Routing key \u2014 required. Destination tool's `consumeHandoff(targetTool)`\n // call only returns a payload whose `tool` matches.\n tool: string;\n // Long- or wide-format CSV/TSV the destination should parse.\n csv?: string;\n // Hint for the destination's parse path; optional.\n mode?: \"long\" | \"wide\";\n // Human-readable origin string; surfaced in banners / breadcrumbs.\n source?: string;\n // Suggested filename for download chrome on the destination.\n fileName?: string;\n // Suggested y-axis label for the destination tool's plot view, used\n // when the auto-derived label (typically the value column's name)\n // is an implementation detail rather than the standard scientific\n // term. Aequorin sends \"A.U.C.\" when handing off \u03A3-of-luminescence\n // data so the boxplot reads as area-under-curve, not \"Raw Sum\".\n // Consumers should treat a present value as authoritative \u2014 apply\n // it after their own auto-sync logic so it isn't immediately\n // clobbered. Optional; the destination's own auto-derivation runs\n // when this is absent.\n yLabel?: string;\n // Pre-assigned column-role hints; optional.\n colRoles?: string[];\n // Tool-specific consumers extend with their own fields (e.g. upset's\n // intersection-payload variant) via a structural-typing widening.\n [key: string]: unknown;\n}\n\ndeclare global {\n interface Window {\n __plottrSpaNavigate?: (toolKey: string) => void;\n }\n}\n\nconst KEY = \"dataviz-handoff\";\n\nexport function setHandoff(payload: HandoffPayload): boolean {\n try {\n localStorage.setItem(KEY, JSON.stringify(payload));\n } catch (_e) {\n return false;\n }\n // Notify same-tab consumers. The browser `storage` event only fires\n // across tabs / iframes, so under the SPA's keep-alive routing (target\n // tool already mounted) it never reaches the consumer. Dispatching a\n // synchronous CustomEvent gives the already-mounted destination a\n // chance to re-run consumeHandoff.\n try {\n window.dispatchEvent(new CustomEvent(\"plottr-handoff\", { detail: { key: KEY } }));\n } catch (_e) {\n /* swallow */\n }\n return true;\n}\n\nexport function consumeHandoff(targetTool: string): HandoffPayload | null {\n let raw: string | null = null;\n try {\n raw = localStorage.getItem(KEY);\n } catch (_e) {\n return null;\n }\n if (!raw) return null;\n // Always remove first \u2014 even if parsing or routing later fails, we\n // don't want this payload to resurrect on the next mount.\n try {\n localStorage.removeItem(KEY);\n } catch (_e) {\n /* swallow */\n }\n try {\n const payload = JSON.parse(raw) as HandoffPayload | null;\n if (!payload || payload.tool !== targetTool) return null;\n return payload;\n } catch (_e) {\n return null;\n }\n}\n\n// SPA-aware tool navigation. Source tools (e.g. RLU timecourse \u2192\n// Group Plot) call this after `setHandoff(...)` to switch the visible\n// view. Two paths:\n// 1. SPA mode (the production deploy): `window.__plottrSpaNavigate` is\n// registered by `tools/_app/index.tsx` on boot. Calling it changes\n// `location.hash` and the SPA router re-renders the new tool\n// in-place. The destination tool's mount-time `consumeHandoff()`\n// finds the localStorage payload we just wrote and applies it.\n// 2. Standalone-page fallback: `__plottrSpaNavigate` is undefined,\n// so we fall back to a top-level navigation to `.html`. The\n// destination tool boots fresh and its mount-time `consumeHandoff()`\n// does the same.\n// Call sites stay shell-agnostic \u2014 they just call\n// `setHandoff(payload); navigateToTool(payload.tool);`.\nexport function navigateToTool(toolKey: string): void {\n if (typeof toolKey !== \"string\" || toolKey === \"\") return;\n const spaNav = window.__plottrSpaNavigate;\n if (typeof spaNav === \"function\") {\n try {\n spaNav(toolKey);\n return;\n } catch (_e) {\n // Fall through to standalone-page navigation if the SPA path errors.\n }\n }\n try {\n window.location.assign(toolKey + \".html\");\n } catch (_e) {\n /* swallow */\n }\n}\n", "// Long-format detection for set-membership inputs. Given a 2-column\n// table, decide whether it's a wide 2-set file (col 1 = items in set A,\n// col 2 = items in set B) or a long-format (item, set_label) stack.\n//\n// Originally introduced for Venn (audit medium M2 replaced an earlier\n// heuristic that false-positived on duplicated col-2 items and\n// false-negatived on small long-format files); lifted to `_shell/` so\n// UpSet can share the same logic and the wide/long upfront picker can\n// go away there too. Both tools now route their `doParse` through this\n// helper for any 2-column input.\n//\n// Signal requirements (must all hold for `isLong = true`):\n// 1. At least 3 non-empty col-2 cells (anything smaller is too\n// ambiguous to auto-detect; leave it as wide and let the user\n// re-label if wrong).\n// 2. col 2 has \u2265 2 and \u2264 20 distinct values \u2014 \"set labels\" are\n// bounded (a user with > 20 sets is on UpSet, not Venn; either\n// way \u2264 20 is plenty).\n// 3. Heavy repetition: cells whose value appears \u2265 2 times in col 2\n// make up at least half of col 2. A single-duplicate wide file\n// gives 2/20 = 0.1 which fails; a 3-row 2-set long-format gives\n// 2/3 = 0.67.\n// 4. col 1 is mostly distinct (\u2265 70 % unique). A wide file with\n// heavy column-2 duplication would usually have column-1\n// duplication too; long-format has one row per (item, set) pair\n// so col-1 dupes are rare.\n//\n// Pure helper, no React / DOM dependency \u2014 pinned by\n// `tests/venn.test.js` (existing) and `tests/upset.test.js`.\n\nexport interface LongFormatDetection {\n isLong: boolean;\n col1Distinct: number;\n col2Distinct: number;\n col2Repeats: number;\n}\n\nexport function detectLongFormat(headers: string[], rows: string[][]): LongFormatDetection {\n const fail = {\n isLong: false,\n col1Distinct: 0,\n col2Distinct: 0,\n col2Repeats: 0,\n };\n if (!Array.isArray(headers) || headers.length !== 2) return fail;\n if (!Array.isArray(rows) || rows.length === 0) return fail;\n\n const col1: string[] = [];\n const col2: string[] = [];\n for (const r of rows) {\n const v1 = (r[0] || \"\").trim();\n const v2 = (r[1] || \"\").trim();\n if (v1) col1.push(v1);\n if (v2) col2.push(v2);\n }\n\n const col1Distinct = new Set(col1).size;\n const col2Distinct = new Set(col2).size;\n\n let col2Repeats = 0;\n if (col2.length > 0) {\n const counts: Record = {};\n for (const v of col2) counts[v] = (counts[v] || 0) + 1;\n for (const v of col2) if (counts[v] >= 2) col2Repeats++;\n }\n\n const enoughRows = col2.length >= 3;\n const boundedSetCount = col2Distinct >= 2 && col2Distinct <= 20;\n const heavyRepetition = col2.length > 0 && col2Repeats * 2 >= col2.length;\n const col1MostlyUnique = col1Distinct >= Math.max(3, col1.length * 0.7);\n\n const isLong = enoughRows && boundedSetCount && heavyRepetition && col1MostlyUnique;\n\n return { isLong, col1Distinct, col2Distinct, col2Repeats };\n}\n", "// Tiny numeric rounding helpers shared between plot tools. Used to keep\n// auto-axis bounds and per-x stat readouts at a stable decimal precision\n// across re-renders (raw FP would surface 1.4000000000000001 in the\n// padded axis bounds the user pastes back into the override field).\n\nexport const round2 = (v: number): number => Math.round(v * 100) / 100;\nexport const round4 = (v: number): number => Math.round(v * 10000) / 10000;\n", "// Narrow-viewport detection for the calculators. Plot tools are desktop-\n// first and skip this hook entirely; the calculators (`molarity-app.tsx`,\n// `power-app.tsx`) collapse layout below their respective breakpoints.\n// Default of 600 px matches molarity; power passes 900 explicitly.\n\nconst { useState, useEffect } = React;\n\nexport function useIsMobile(breakpoint: number = 600): boolean {\n const [mobile, setMobile] = useState(window.innerWidth < breakpoint);\n useEffect(() => {\n const handler = () => setMobile(window.innerWidth < breakpoint);\n window.addEventListener(\"resize\", handler);\n return () => window.removeEventListener(\"resize\", handler);\n }, [breakpoint]);\n return mobile;\n}\n", "// Shared state hook for every plot tool. Owns the upload-step state\n// (step, rawText/fileName, separator override, decimal-comma fix flags,\n// parse error) plus the `vis` reducer with auto-prefs persistence \u2014\n// everything every plot tool's `App()` needs.\n//\n// Tool-specific state (selectedMask, parsed rows, pendingSelection, etc.)\n// stays outside this hook \u2014 the goal is a common scaffold, not a kitchen\n// sink.\n\nimport { loadAutoPrefs, saveAutoPrefs } from \"./prefs-store\";\n\nimport type { FormulaInjectionWarning } from \"../_core/csv\";\nconst { useState, useReducer, useEffect } = React;\n\nexport interface PlotToolState {\n step: string;\n setStep: (s: string) => void;\n fileName: string;\n setFileName: (n: string) => void;\n parseError: string | null;\n setParseError: (e: string | null) => void;\n sepOverride: string;\n setSepOverride: (s: string) => void;\n commaFixed: boolean;\n setCommaFixed: (b: boolean) => void;\n commaFixCount: number;\n setCommaFixCount: (n: number) => void;\n // Result of scanForFormulaInjection on the most recent ingest. Set by each\n // tool's parse handler; null when the dataset is clean. Surfaced via\n // FormulaInjectionBanner inside PlotToolShell.\n injectionWarning: FormulaInjectionWarning | null;\n setInjectionWarning: (w: FormulaInjectionWarning | null) => void;\n vis: TVis;\n updVis: (patch: Partial | { _reset: true }) => void;\n}\n\nexport function usePlotToolState(\n toolKey: string,\n initialVis: TVis\n): PlotToolState {\n const [fileName, setFileName] = useState(\"\");\n const [step, setStep] = useState(\"upload\");\n const [parseError, setParseError] = useState(null);\n const [sepOverride, setSepOverride] = useState(\"\");\n const [commaFixed, setCommaFixed] = useState(false);\n const [commaFixCount, setCommaFixCount] = useState(0);\n const [injectionWarning, setInjectionWarning] = useState(null);\n\n // The reducer captures `initialVis` by reference; React only invokes the\n // third argument (the initializer) once, so a new inline initialVis object\n // per render doesn't re-seed state. `_reset` resets to the initial shape\n // without touching persisted auto-prefs.\n const [vis, updVis] = useReducer(\n (s: TVis, a: Partial | { _reset: true }) =>\n \"_reset\" in a && a._reset ? { ...initialVis } : { ...s, ...(a as Partial) },\n initialVis,\n (init) => loadAutoPrefs(toolKey, init)\n );\n\n useEffect(() => {\n saveAutoPrefs(toolKey, vis);\n }, [toolKey, vis]);\n\n return {\n step,\n setStep,\n fileName,\n setFileName,\n parseError,\n setParseError,\n sepOverride,\n setSepOverride,\n commaFixed,\n setCommaFixed,\n commaFixCount,\n setCommaFixCount,\n injectionWarning,\n setInjectionWarning,\n vis,\n updVis,\n };\n}\n", "// `ActionsPanel` \u2014 actions tile for the plot step. Renders a wrapping\n// row of unified download chips (SVG / PNG / + any `extraDownloads`\n// like CSV/TXT/R) followed by a full-width Start-over button. Each\n// chip flex-grows so 1/2/3 fit evenly; a 4th wraps. Every button gets\n// a native `title` tooltip; SVG/PNG/Start-over carry fixed built-in\n// strings, each `extraDownloads` entry passes its own `title`.\n\nimport { flashSaved } from \"../_core/download\";\n\nconst h = React.createElement;\n\nexport interface ActionsPanelDownload {\n label: string;\n title?: string;\n onClick: (e: React.MouseEvent) => void;\n}\n\ninterface ActionsPanelProps {\n onDownloadSvg?: (e: React.MouseEvent) => void;\n onDownloadPng?: (e: React.MouseEvent) => void;\n extraDownloads?: ActionsPanelDownload[];\n onReset: () => void;\n}\n\nexport function ActionsPanel(props: ActionsPanelProps) {\n const downloads: ActionsPanelDownload[] = [];\n if (props.onDownloadSvg) {\n downloads.push({\n label: \"SVG\",\n title: \"Download the plot as SVG \u2014 vector graphics, editable in Inkscape or Illustrator\",\n onClick: props.onDownloadSvg,\n });\n }\n if (props.onDownloadPng) {\n downloads.push({\n label: \"PNG\",\n title: \"Download the plot as PNG \u2014 2\u00D7 raster at the plot's native resolution\",\n onClick: props.onDownloadPng,\n });\n }\n if (props.extraDownloads) {\n props.extraDownloads.forEach((d) => downloads.push(d));\n }\n const dlButtons = downloads.map((d, i) =>\n h(\n \"button\",\n {\n key: \"dl\" + i,\n title: d.title || undefined,\n onClick: (e: React.MouseEvent) => {\n d.onClick(e);\n flashSaved(e.currentTarget);\n },\n className: \"dv-btn dv-btn-dl\",\n style: { flex: \"1 1 0\" },\n },\n \"\u2B07 \" + d.label\n )\n );\n return h(\n \"div\",\n { className: \"dv-panel\" },\n h(\n \"p\",\n {\n className: \"dv-tile-title\",\n style: { margin: \"0 0 8px\" },\n },\n \"Actions\"\n ),\n dlButtons.length > 0\n ? h(\n \"div\",\n {\n style: {\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 6,\n marginBottom: 8,\n },\n },\n dlButtons\n )\n : null,\n h(\n \"button\",\n {\n onClick: props.onReset,\n title: \"Clear all data, controls, and current session \u2014 returns to the upload step\",\n className: \"dv-btn dv-btn-danger\",\n },\n \"\u21BA Start over\"\n )\n );\n}\n", "// `ColorInput` \u2014 paired native `` swatch + hex text\n// field, with mid-keystroke guarding against value/text round-trip\n// clobber. Used in every per-group / per-category colour picker across\n// the plot tools.\n//\n// `normalizeHexColor` accepts `#rgb` or `#rrggbb` (case-insensitive) and\n// returns the lowercased 6-char form, or null if the string is not a\n// valid hex colour. Exported separately so other call sites can validate\n// hex strings without instantiating the component.\n\nconst { useState, useEffect } = React;\n\nexport function normalizeHexColor(v: unknown): string | null {\n if (typeof v !== \"string\") return null;\n if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toLowerCase();\n if (/^#[0-9a-fA-F]{3}$/.test(v)) {\n const s = v.toLowerCase();\n return \"#\" + s[1] + s[1] + s[2] + s[2] + s[3] + s[3];\n }\n return null;\n}\n\ninterface ColorInputProps {\n value: string;\n onChange: (next: string) => void;\n // Square edge length in pixels for the colour-picker swatch. Default 22.\n size?: number;\n}\n\nexport function ColorInput({ value, onChange, size = 22 }: ColorInputProps) {\n const [text, setText] = useState(value);\n // Only sync local text from parent `value` when the current text doesn't\n // already normalise to that value. Without this guard, typing `#abc` would\n // commit `#aabbcc` upstream, which round-trips back through this effect and\n // rewrites the text field to the 6-char form mid-keystroke \u2014 the cursor\n // jumps and the user's `#abc` shorthand gets auto-expanded against their\n // will. With the guard, external value changes (preset swap, palette reset)\n // still sync, but self-commits don't clobber what the user typed.\n useEffect(() => {\n if (normalizeHexColor(text) !== value) setText(value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value]);\n const commit = (v: string) => {\n const n = normalizeHexColor(v);\n if (n) onChange(n);\n };\n return (\n
\n onChange(e.target.value)}\n style={{\n width: size,\n height: size,\n border: \"1px solid var(--border-strong)\",\n borderRadius: 4,\n cursor: \"pointer\",\n padding: 0,\n flexShrink: 0,\n }}\n />\n {\n setText(e.target.value);\n commit(e.target.value);\n }}\n onBlur={(e) => {\n const n = normalizeHexColor(e.target.value);\n if (n) onChange(n);\n else setText(value);\n }}\n maxLength={7}\n spellCheck={false}\n style={{\n width: 64,\n fontFamily: \"monospace\",\n fontSize: 11,\n border: \"1px solid var(--border-strong)\",\n borderRadius: 4,\n padding: \"2px 5px\",\n color: \"var(--text)\",\n background: \"var(--surface)\",\n }}\n />\n
\n );\n}\n", "// Accessible segmented toggle \u2014 the canonical replacement for the inline\n// `flex + overflow:hidden + two/three styled \n );\n })}\n \n );\n}\n\nexport interface OnOffToggleProps {\n value: boolean;\n onChange: (value: boolean) => void;\n offLabel?: string;\n onLabel?: string;\n ariaLabel?: string;\n disabled?: boolean;\n style?: React.CSSProperties;\n}\n\nexport function OnOffToggle({\n value,\n onChange,\n offLabel = \"Off\",\n onLabel = \"On\",\n ariaLabel,\n disabled,\n style,\n}: OnOffToggleProps): React.ReactElement {\n return (\n \n value={value ? \"on\" : \"off\"}\n onChange={(v) => onChange(v === \"on\")}\n options={[\n { value: \"off\", label: offLabel },\n { value: \"on\", label: onLabel },\n ]}\n ariaLabel={ariaLabel}\n disabled={disabled}\n style={style}\n />\n );\n}\n", "// `BaseStyleControls` \u2014 background colour + grid on/off + grid\n// colour controls. Used in the plot-style tile of every group-aware\n// plot tool's sidebar.\n\nimport { ColorInput } from \"./ColorInput\";\nimport { OnOffToggle } from \"./SegToggle\";\n\nconst h = React.createElement;\n\ninterface BaseStyleControlsProps {\n plotBg: string;\n onPlotBgChange: (hex: string) => void;\n showGrid: boolean;\n onShowGridChange: (v: boolean) => void;\n gridColor: string;\n onGridColorChange: (hex: string) => void;\n}\n\nexport function BaseStyleControls(props: BaseStyleControlsProps) {\n const plotBg = props.plotBg;\n const onPlotBgChange = props.onPlotBgChange;\n const showGrid = props.showGrid;\n const onShowGridChange = props.onShowGridChange;\n const gridColor = props.gridColor;\n const onGridColorChange = props.onGridColorChange;\n const children: React.ReactNode[] = [\n h(\n \"div\",\n {\n key: \"bg\",\n style: { display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\" },\n },\n h(\"span\", { className: \"dv-label\" }, \"Background\"),\n h(ColorInput, { value: plotBg, onChange: onPlotBgChange, size: 24 })\n ),\n h(\n \"div\",\n { key: \"grid\" },\n h(\"span\", { className: \"dv-label\" }, \"Grid\"),\n h(OnOffToggle, { value: showGrid, onChange: onShowGridChange, ariaLabel: \"Grid\" })\n ),\n ];\n if (showGrid) {\n children.push(\n h(\n \"div\",\n {\n key: \"gc\",\n style: { display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\" },\n },\n h(\"span\", { className: \"dv-label\" }, \"Grid color\"),\n h(ColorInput, { value: gridColor, onChange: onGridColorChange, size: 24 })\n )\n );\n }\n return h(\"div\", { style: { display: \"flex\", flexDirection: \"column\", gap: 8 } }, children);\n}\n", "// `CldLabels` \u2014 Compact-Letter-Display labels for k\u22653 group annotations.\n// Emits one `` per group with the letter assignment from\n// `compactLetterDisplay` upstream. Pairs with `SignificanceBrackets` as\n// the two annotation primitives every chart uses.\n\nconst CLD_DEFAULTS = {\n fontSize: 13,\n fontWeight: 700,\n fontFamily: \"sans-serif\",\n textColor: \"#222\",\n};\n\ninterface CldLabelsProps {\n labels: (string | null)[];\n axisCoord: (i: number) => number;\n // Perpendicular coord \u2014 y for vertical-top, x for horizontal-right.\n crossCoord: number;\n orientation: \"vertical-top\" | \"horizontal-right\";\n fontSize?: number;\n fontWeight?: number | string;\n fontFamily?: string;\n textColor?: string;\n}\n\nexport function CldLabels({\n labels,\n axisCoord,\n crossCoord,\n orientation,\n fontSize = CLD_DEFAULTS.fontSize,\n fontWeight = CLD_DEFAULTS.fontWeight,\n fontFamily = CLD_DEFAULTS.fontFamily,\n textColor = CLD_DEFAULTS.textColor,\n}: CldLabelsProps) {\n if (!labels || labels.length === 0) return null;\n const horizontal = orientation === \"horizontal-right\";\n return (\n \n {labels.map((lbl, i) =>\n lbl != null ? (\n \n {lbl}\n \n ) : null\n )}\n
\n );\n}\n", "// `scrollDisclosureIntoView` \u2014 scroll-affordance helper used by\n// ControlSection / collapsible disclosures across plot tools. Picks one\n// scrollable ancestor (typically a sticky sidebar with its own\n// overflow-y) and only moves that one \u2014 does NOT use\n// Element.scrollIntoView() because that bubbles up and scrolls every\n// scrollable ancestor including the page. The lower-level\n// `scrollIntoViewWithinAncestor` is kept module-internal; the\n// disclosure-specific wrapper is the only public consumer.\n\nfunction scrollIntoViewWithinAncestor(\n el: Element | null,\n pad?: number,\n extraBottom?: number\n): void {\n if (!el) return;\n const padding = pad == null ? 8 : pad;\n const extra = extraBottom || 0;\n let parent: Element | null = el.parentElement;\n while (parent) {\n const style = getComputedStyle(parent);\n const ov = style.overflowY;\n if ((ov === \"auto\" || ov === \"scroll\") && parent.scrollHeight > parent.clientHeight) {\n const parentRect = parent.getBoundingClientRect();\n const elRect = el.getBoundingClientRect();\n const revealBottom = elRect.bottom + extra;\n if (revealBottom > parentRect.bottom - padding) {\n const delta = revealBottom - parentRect.bottom + padding;\n const maxDelta = elRect.top - parentRect.top - padding;\n parent.scrollBy({ top: Math.min(delta, Math.max(0, maxDelta)), behavior: \"smooth\" });\n } else if (elRect.top < parentRect.top + padding) {\n parent.scrollBy({ top: elRect.top - parentRect.top - padding, behavior: \"smooth\" });\n }\n return;\n }\n parent = parent.parentElement;\n }\n // No scrollable ancestor \u2014 the page itself is what scrolls (heatmap case).\n const elRect = el.getBoundingClientRect();\n const viewportBottom = window.innerHeight;\n const revealBottom = elRect.bottom + extra;\n if (revealBottom > viewportBottom - padding) {\n const delta = revealBottom - viewportBottom + padding;\n const maxDelta = Math.max(0, elRect.top - padding);\n window.scrollBy({ top: Math.min(delta, maxDelta), behavior: \"smooth\" });\n } else if (elRect.top < padding) {\n window.scrollBy({ top: elRect.top - padding, behavior: \"smooth\" });\n }\n}\n\n// `scrollDisclosureIntoView` \u2014 disclosure-specific wrapper. Measures\n// where the next section's header sits relative to the expanded section,\n// and reveals its bottom edge plus ~14 px of clearance below so the\n// next header lands comfortably inside the viewport instead of flush at\n// the bottom edge.\nconst DISCLOSURE_TRAILING_CLEARANCE = 40;\nexport function scrollDisclosureIntoView(el: Element | null, pad?: number): void {\n if (!el) return;\n const next = el.nextElementSibling;\n const nextHeader = next && next.firstElementChild;\n let extra = 0;\n if (nextHeader) {\n const elRect = el.getBoundingClientRect();\n const nhRect = nextHeader.getBoundingClientRect();\n extra = Math.max(0, nhRect.bottom + DISCLOSURE_TRAILING_CLEARANCE - elRect.bottom);\n }\n scrollIntoViewWithinAncestor(el, pad == null ? 8 : pad, extra);\n}\n", "// Collapsible sidebar tile \u2014 the canonical disclosure section every plot\n// tool's control panel is built from. Previously each tool kept its own\n// byte-identical copy; this is the single shared implementation.\n//\n// The header row carries the `dv-disclosure` chevron + title and an optional\n// `headerRight` slot (e.g. a section-level on/off pill) that stays visible\n// when the body is folded. With no `headerRight` the row renders exactly like\n// the plain \"button is the whole header\" variant the simpler tools used, so\n// this is a drop-in replacement for both shapes.\n\nimport { scrollDisclosureIntoView } from \"./scroll-helpers\";\n\nconst { useState, useRef, useEffect } = React;\n\nexport interface ControlSectionProps {\n title: React.ReactNode;\n defaultOpen?: boolean;\n /** Optional inline control rendered to the right of the title; survives folds. */\n headerRight?: React.ReactNode;\n children?: React.ReactNode;\n}\n\nexport function ControlSection({\n title,\n defaultOpen = false,\n headerRight,\n children,\n}: ControlSectionProps) {\n const [open, setOpen] = useState(defaultOpen);\n const rootRef = useRef(null);\n useEffect(() => {\n if (!open) return;\n requestAnimationFrame(() => scrollDisclosureIntoView(rootRef.current));\n }, [open]);\n return (\n
\n \n setOpen(!open)}\n className=\"dv-tile-title\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n flex: 1,\n padding: 0,\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n textAlign: \"left\",\n }}\n >\n \n {title}\n \n {headerRight}\n
\n {open && (\n
\n {children}\n
\n )}\n \n );\n}\n", "// Dropdown of palette keys with an inline preview strip \u2014 used in every\n// plot tool's controls sidebar to pick a discrete palette for groups /\n// categories / sets. Stateless: parent owns `value`, listens to onChange,\n// optionally seeds per-group colours via `applyColors(hexArray)` (a 3-line\n// lambda that maps `hexArray[i]` into whatever shape the tool stores).\n//\n// Splitting the dropdown into Select + SwatchStrip keeps the visual preview\n// updating live with the picked palette without forcing the parent to\n// re-render.\n\nimport {\n DISCRETE_PALETTES,\n COLORBLIND_SAFE_PALETTES,\n resolveDiscretePalette,\n} from \"./discrete-palette\";\n\nconst { useState } = React;\n\n// Sensible swatch count for the runtime-generated palettes (\"ggplot2-hue\",\n// \"viridis-d\") whose `DISCRETE_PALETTES` entry is a single \"*\" sentinel.\n// 10 stops is enough to read as a continuous-ish gradient and still leaves\n// each swatch clickable at sidebar widths.\nconst RUNTIME_PALETTE_PREVIEW_N = 10;\n\n// \"Natural length\" of a palette \u2014 what the swatch strip renders. For static\n// palettes this is the catalogue length (10 for okabe-ito, 12 for set3,\n// etc.); for the runtime \"*\" palettes we sample `RUNTIME_PALETTE_PREVIEW_N`\n// evenly-spaced stops.\nfunction naturalPaletteLength(name: string): number {\n const def = DISCRETE_PALETTES[name];\n if (!def || def.length === 0) return RUNTIME_PALETTE_PREVIEW_N;\n if (def.length === 1 && def[0] === \"*\") return RUNTIME_PALETTE_PREVIEW_N;\n return def.length;\n}\n\ninterface DiscreteSwatchStripProps {\n palette: string;\n // Optional override; defaults to the palette's natural length so the\n // strip always shows the full catalogue and users can copy any hex,\n // not just the ones currently bound to a group / category.\n n?: number;\n height?: number;\n width?: number | string;\n}\n\n// Full-palette swatch strip with click-to-copy. Each swatch is a real\n// `\n \n \n \n );\n }\n}\n", "// `FilterCheckboxPanel` \u2014 per-column filter grid for the filter step.\n// Renders one panel per categorical / filter column with checkboxes\n// for each unique value (and All / None bulk-toggles). Numeric\n// columns get a placeholder note instead.\n//\n// `isNumericValue` is read off the ambient browser globals.\n\nimport { isNumericValue } from \"../_core/numeric\";\n\nimport type { ColumnRole } from \"../_core/csv\";\nconst h = React.createElement;\n\ninterface FilterCheckboxPanelProps {\n headers: string[];\n colNames: string[];\n colRoles: ColumnRole[];\n filters: Record;\n filteredCount: number;\n totalCount: number;\n onToggle: (i: number, value: string) => void;\n onToggleAll: (i: number, allOn: boolean) => void;\n}\n\nexport function FilterCheckboxPanel(props: FilterCheckboxPanelProps) {\n const headers = props.headers;\n const colNames = props.colNames;\n const colRoles = props.colRoles;\n const filters = props.filters;\n const filteredCount = props.filteredCount;\n const totalCount = props.totalCount;\n const onToggle = props.onToggle;\n const onToggleAll = props.onToggleAll;\n return h(\n \"div\",\n {\n style: {\n flex: 1,\n borderRadius: 10,\n padding: 16,\n border: \"1px solid var(--info-border)\",\n background: \"var(--info-bg)\",\n display: \"flex\",\n flexDirection: \"column\",\n },\n },\n h(\n \"p\",\n { style: { margin: \"0 0 10px\", fontSize: 13, fontWeight: 600, color: \"var(--info-text)\" } },\n \"Filter rows (\" + filteredCount + \"/\" + totalCount + \")\"\n ),\n h(\n \"div\",\n { style: { display: \"flex\", gap: 16, flexWrap: \"wrap\", alignItems: \"stretch\", flex: 1 } },\n headers.map((_hdr, i) => {\n // Hide the value column's tile here too: it was assigned in Configure\n // and is guaranteed to stay (rows are kept if their value is numeric);\n // a filter tile adds no affordance and the old \"numeric \u2014 use axis\n // range in plot\" placeholder was pure noise.\n if (colRoles[i] === \"ignore\" || colRoles[i] === \"value\") return null;\n const u: string[] = filters[i] ? Array.from(filters[i].unique) : [];\n const isNumCol = u.length > 0 && u.filter((v) => isNumericValue(v)).length / u.length > 0.5;\n if (isNumCol && colRoles[i] !== \"filter\") {\n return h(\n \"div\",\n {\n key: \"col-\" + i,\n style: {\n minWidth: 140,\n flex: 1,\n background: \"var(--surface)\",\n borderRadius: 6,\n border: \"1px solid var(--border)\",\n padding: 10,\n },\n },\n h(\n \"div\",\n { style: { display: \"flex\", alignItems: \"center\", gap: 6, flexWrap: \"wrap\" } },\n h(\n \"p\",\n { style: { fontSize: 11, fontWeight: 600, color: \"var(--text)\", margin: 0 } },\n colNames[i]\n ),\n h(\n \"button\",\n {\n onClick: () => onToggleAll(i, true),\n style: {\n fontSize: 9,\n padding: \"2px 6px\",\n background: \"var(--surface-sunken)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 3,\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n color: \"var(--text-muted)\",\n },\n },\n \"All\"\n )\n ),\n h(\n \"p\",\n {\n style: {\n fontSize: 10,\n color: \"var(--text-faint)\",\n margin: \"4px 0 0\",\n fontStyle: \"italic\",\n },\n },\n \"numeric \u2014 use axis range in plot\"\n )\n );\n }\n return h(\n \"div\",\n {\n key: \"col-\" + i,\n style: {\n minWidth: 140,\n flex: 1,\n background: \"var(--surface)\",\n borderRadius: 6,\n border: \"1px solid var(--border)\",\n padding: 10,\n },\n },\n h(\n \"p\",\n { style: { fontSize: 11, fontWeight: 600, color: \"var(--text)\", marginBottom: 4 } },\n colNames[i]\n ),\n h(\n \"div\",\n { style: { display: \"flex\", gap: 6, marginBottom: 4 } },\n h(\n \"button\",\n {\n onClick: () => onToggleAll(i, true),\n style: {\n fontSize: 9,\n padding: \"2px 6px\",\n background: \"var(--surface-sunken)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 3,\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n color: \"var(--text-muted)\",\n },\n },\n \"All\"\n ),\n h(\n \"button\",\n {\n onClick: () => onToggleAll(i, false),\n style: {\n fontSize: 9,\n padding: \"2px 6px\",\n background: \"var(--surface-sunken)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 3,\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n color: \"var(--text-muted)\",\n },\n },\n \"None\"\n )\n ),\n u.map((v) => {\n const checked = !!(filters[i] && filters[i].included && filters[i].included.has(v));\n return h(\n \"label\",\n {\n key: v,\n style: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n fontSize: 11,\n color: \"var(--text-muted)\",\n cursor: \"pointer\",\n marginBottom: 2,\n },\n },\n h(\"input\", {\n type: \"checkbox\",\n checked,\n onChange: () => onToggle(i, v),\n style: { accentColor: \"var(--cta-primary-bg)\" },\n }),\n v || h(\"em\", { style: { color: \"var(--text-faint)\" } }, \"(empty)\")\n );\n })\n );\n })\n )\n );\n}\n", "// `GroupColorEditor` \u2014 list of {colour swatch, name input, optional\n// toggle, `n=\u2026`} rows for editing per-group colours and display\n// names. Used in every plot tool's plot-step sidebar that exposes\n// per-group colour overrides.\n\nimport { ColorInput } from \"./ColorInput\";\n\nconst h = React.createElement;\n\ninterface GroupColorEditorGroup {\n name: string;\n color: string;\n displayName?: string;\n enabled?: boolean;\n stats?: { n: number } | null;\n}\n\ninterface GroupColorEditorProps {\n groups: GroupColorEditorGroup[];\n onColorChange: (i: number, color: string) => void;\n onNameChange?: (i: number, name: string) => void;\n onToggle?: (i: number) => void;\n}\n\nexport function GroupColorEditor(props: GroupColorEditorProps) {\n const groups = props.groups;\n const onColorChange = props.onColorChange;\n const onNameChange = props.onNameChange;\n const onToggle = props.onToggle;\n return h(\n \"div\",\n { style: { display: \"flex\", flexDirection: \"column\", gap: 4 } },\n groups.map((g, i) => {\n const enabled = g.enabled !== false;\n const children: React.ReactNode[] = [];\n if (onToggle) {\n children.push(\n h(\"input\", {\n key: \"cb\",\n type: \"checkbox\",\n checked: enabled,\n onChange: () => onToggle(i),\n style: { accentColor: g.color, flexShrink: 0, cursor: \"pointer\" },\n })\n );\n }\n children.push(\n h(ColorInput, {\n key: \"clr\",\n value: g.color,\n onChange: (c: string) => onColorChange(i, c),\n size: 18,\n })\n );\n children.push(\n h(\"input\", {\n key: \"nm\",\n value: g.displayName || g.name,\n onChange: (e: React.ChangeEvent) => {\n if (onNameChange) onNameChange(i, e.target.value);\n },\n style: {\n flex: 1,\n minWidth: 0,\n background: \"var(--surface)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 4,\n color: \"var(--text)\",\n padding: \"2px 4px\",\n fontSize: 11,\n fontFamily: \"inherit\",\n },\n })\n );\n children.push(\n h(\n \"span\",\n { key: \"n\", style: { color: \"var(--text-faint)\", fontSize: 10, flexShrink: 0 } },\n \"n=\" + (g.stats ? g.stats.n : 0)\n )\n );\n return h(\n \"div\",\n {\n key: g.name,\n style: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"3px 8px\",\n borderRadius: 6,\n fontSize: 12,\n background: enabled ? \"var(--surface-sunken)\" : \"var(--surface-subtle)\",\n opacity: enabled ? 1 : 0.4,\n border: \"1px solid var(--border-strong)\",\n },\n },\n children\n );\n })\n );\n}\n", "// Shared sidebar frame for every plot tool: a fixed-width column that hosts\n// the ActionsPanel and all collapsible control tiles. Sticky by default so\n// the controls follow the viewport while the plot scrolls; opt out with\n// `sticky={false}` for tools whose sidebar is short enough that sticky\n// scrolling would be distracting.\n\ninterface PlotSidebarProps {\n children: React.ReactNode;\n sticky?: boolean;\n width?: number;\n}\n\nexport function PlotSidebar({ children, sticky = true, width = 279 }: PlotSidebarProps) {\n return (\n ` tiles inherit the CSS-level\n // 16px margin-bottom and stack on top of the flex `gap: 10` \u2014\n // giving 26px between bare panels but 10px between ControlSections\n // (which override the margin inline). The class makes the\n // between-panel spacing uniformly 10px regardless of tile type.\n className=\"dv-sidebar\"\n style={{\n width,\n flexShrink: 0,\n ...(sticky\n ? {\n position: \"sticky\",\n top: 24,\n maxHeight: \"calc(100vh - 90px)\",\n overflowY: \"auto\",\n }\n : {}),\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n }}\n >\n {children}\n \n );\n}\n", "import type { ColumnRole } from \"../_core/csv\";\n\n// `RenameReorderPanel` \u2014 per-column rename + drag-to-reorder list. Used\n// in the configure step of long-format tools to (a) rename observed\n// values for plot display, (b) reorder groups along a categorical axis.\n// Drag state is scoped per column so dragging on one column doesn't\n// highlight rows in a sibling column with the same positional index.\n\nconst h = React.createElement;\n\ninterface RenameReorderPanelProps {\n headers: string[];\n colNames: string[];\n colRoles: ColumnRole[];\n filters: Record;\n valueRenames: Record>;\n // Map { [colIdx]: { order, onReorder } } \u2014 every column whose index appears\n // here gets a drag handle and per-column independent reorder state. Columns\n // absent from the map are rendered as rename-only.\n orderableCols?: Record void }>;\n applyRename: (i: number, value: string) => string;\n onRenameVal: (i: number, origValue: string, newValue: string) => void;\n // Drag state is scoped per column so dragging on one column doesn't\n // highlight a row in a neighbouring column with the same positional index.\n dragState: { col: number; idx: number } | null;\n onDragStart: (state: { col: number; idx: number }) => void;\n onDragEnd: () => void;\n}\n\nexport function RenameReorderPanel(props: RenameReorderPanelProps) {\n const headers = props.headers;\n const colNames = props.colNames;\n const colRoles = props.colRoles;\n const filters = props.filters;\n const valueRenames = props.valueRenames;\n const orderableCols = props.orderableCols || {};\n const applyRename = props.applyRename;\n const onRenameVal = props.onRenameVal;\n const dragState = props.dragState;\n const onDragStart = props.onDragStart;\n const onDragEnd = props.onDragEnd;\n return h(\n \"div\",\n {\n style: {\n flex: 1,\n borderRadius: 10,\n padding: 16,\n border: \"1px solid var(--border)\",\n background: \"var(--surface-subtle)\",\n },\n },\n h(\n \"p\",\n { style: { margin: \"0 0 10px\", fontSize: 13, fontWeight: 600, color: \"var(--text-muted)\" } },\n \"Rename values & reorder groups \",\n h(\n \"span\",\n { style: { fontSize: 10, color: \"var(--text-faint)\", fontWeight: 400 } },\n \"(drag \u2630 to reorder groups on plot)\"\n )\n ),\n h(\n \"div\",\n { style: { display: \"flex\", gap: 16, flexWrap: \"wrap\" } },\n headers.map((_hdr, i) => {\n if (colRoles[i] !== \"group\" && colRoles[i] !== \"filter\") return null;\n const u: string[] = (filters[i] ? Array.from(filters[i].unique) : []).filter(\n (v) => filters[i] && filters[i].included && filters[i].included.has(v)\n );\n const colOrder = orderableCols[i];\n const isOrderable = !!colOrder;\n const renamedU = u.map((v) => ({ orig: v, renamed: applyRename(i, v) }));\n const orderedU =\n isOrderable && colOrder.order\n ? colOrder.order\n .map((g) => renamedU.find((x) => x.renamed === g))\n .filter((x): x is { orig: string; renamed: string } => Boolean(x))\n : renamedU;\n const displayList = orderedU.length > 0 ? orderedU : renamedU;\n const dragIdxForCol = dragState && dragState.col === i ? dragState.idx : null;\n return h(\n \"div\",\n {\n key: \"col-\" + i,\n style: {\n minWidth: 200,\n background: \"var(--surface)\",\n borderRadius: 6,\n border: \"1px solid var(--border)\",\n padding: 10,\n },\n },\n h(\n \"p\",\n { style: { fontSize: 11, fontWeight: 600, color: \"var(--text)\", marginBottom: 6 } },\n colNames[i]\n ),\n displayList.map((item, vi) => {\n const v = item.orig;\n return h(\n \"div\",\n {\n key: v,\n draggable: isOrderable,\n onDragStart: () => onDragStart({ col: i, idx: vi }),\n onDragOver: (e: { preventDefault(): void }) => {\n e.preventDefault();\n },\n onDrop: () => {\n if (!isOrderable || dragIdxForCol === null || dragIdxForCol === vi) {\n onDragEnd();\n return;\n }\n const cur = displayList.map((x) => x.renamed);\n const moved = cur[dragIdxForCol];\n cur.splice(dragIdxForCol, 1);\n cur.splice(vi, 0, moved);\n colOrder!.onReorder(cur);\n onDragEnd();\n },\n onDragEnd: () => onDragEnd(),\n style: {\n display: \"flex\",\n gap: 4,\n alignItems: \"center\",\n marginBottom: 3,\n padding: \"3px 4px\",\n borderRadius: 4,\n background: dragIdxForCol === vi ? \"var(--info-bg)\" : \"transparent\",\n cursor: isOrderable ? \"grab\" : \"default\",\n borderLeft: isOrderable\n ? \"3px solid var(--accent-primary)\"\n : \"3px solid transparent\",\n },\n },\n isOrderable\n ? h(\n \"span\",\n { style: { fontSize: 11, color: \"var(--text-faint)\", cursor: \"grab\" } },\n \"\u2630\"\n )\n : null,\n h(\n \"span\",\n {\n style: {\n fontSize: 10,\n color: \"var(--text-muted)\",\n minWidth: 55,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n },\n },\n v || \"(empty)\"\n ),\n h(\"span\", { style: { fontSize: 10, color: \"var(--text-faint)\" } }, \"\u2192\"),\n h(\"input\", {\n value: valueRenames[i] && valueRenames[i][v] != null ? valueRenames[i][v] : v,\n onChange: (e: React.ChangeEvent) =>\n onRenameVal(i, v, e.target.value),\n className: \"dv-input\",\n style: { width: 100, fontSize: 11 },\n })\n );\n })\n );\n })\n )\n );\n}\n", "// `SignificanceBrackets` \u2014 pair-wise significance bracket renderer used by\n// boxplot's main chart (vertical + horizontal) and aequorin's inset bar\n// chart (vertical only). The caller pre-computes per-pair levels (via\n// `assignBracketLevels`) and per-pair labels (e.g. `pStars(p)` upstream\n// in the annotation-spec builder); this component only translates\n// (i, j, _level) \u2192 SVG geometry.\n//\n// Coordinate system parameterised through `axisCoord(i)` so the same\n// component works for box-axis, line-x, and any future categorical axis.\n// Orientation drives the layout fork:\n// - \"vertical-top\": brackets arc above the data, levels stack upward\n// - \"horizontal-right\": brackets arc to the right, levels stack rightward\n\nconst BRACKET_DEFAULTS = {\n stroke: \"#333\",\n strokeWidth: 1,\n fontSize: 12,\n fontWeight: 700,\n fontFamily: \"sans-serif\",\n textColor: \"#222\",\n levelStep: 20,\n tickHeight: 4,\n};\n\n// Same shape as the ambient BracketPair declared in types/globals.d.ts \u2014\n// label may be omitted (the renderer falls back to a default string), so\n// the local prop interface accepts `string | undefined` to be assignable\n// from the global type that StatsTile produces.\ninterface ChartAnnotationBracketPair {\n i: number;\n j: number;\n label?: string;\n _level?: number;\n}\n\ninterface SignificanceBracketsProps {\n pairs: ChartAnnotationBracketPair[];\n // Group-center coord along the categorical axis.\n axisCoord: (i: number) => number;\n // Anchor for level=0 along the perpendicular axis. Vertical-top: top edge\n // of bracket band (e.g. `M.top + annotTopPad - 6`). Horizontal-right: left\n // edge of bracket band (e.g. `M.left + w - annotTopPad + 6`).\n baseline: number;\n orientation: \"vertical-top\" | \"horizontal-right\";\n levelStep?: number;\n tickHeight?: number;\n stroke?: string;\n strokeWidth?: number;\n fontSize?: number;\n fontWeight?: number | string;\n fontFamily?: string;\n textColor?: string;\n}\n\nexport function SignificanceBrackets({\n pairs,\n axisCoord,\n baseline,\n orientation,\n levelStep = BRACKET_DEFAULTS.levelStep,\n tickHeight = BRACKET_DEFAULTS.tickHeight,\n stroke = BRACKET_DEFAULTS.stroke,\n strokeWidth = BRACKET_DEFAULTS.strokeWidth,\n fontSize = BRACKET_DEFAULTS.fontSize,\n fontWeight = BRACKET_DEFAULTS.fontWeight,\n fontFamily = BRACKET_DEFAULTS.fontFamily,\n textColor = BRACKET_DEFAULTS.textColor,\n}: SignificanceBracketsProps) {\n if (!pairs || pairs.length === 0) return null;\n return (\n \n {pairs.map((pr, idx) => {\n const lvl = pr._level || 0;\n const a = axisCoord(pr.i);\n const b = axisCoord(pr.j);\n if (orientation === \"horizontal-right\") {\n const xLine = baseline + lvl * levelStep;\n return (\n \n \n \n {pr.label}\n \n \n );\n }\n const yLine = baseline - lvl * levelStep;\n return (\n \n \n \n {pr.label}\n \n \n );\n })}\n \n );\n}\n", "// `SliderControl` \u2014 labelled range slider with the value displayed on top.\n//\n// Wrapped in `React.memo` with a custom comparator that intentionally\n// ignores the `onChange` prop. Without this, dragging one slider\n// re-renders every other slider in the same sidebar (sliders all live\n// under the same parent reducer), because each call site passes an\n// inline `onChange={(v) => updVis({...})}` that gets a fresh function\n// reference every render. Ignoring onChange is safe HERE because every\n// call site closes the inline arrow over a `useReducer` dispatch (or\n// other stable setter) plus a literal patch object \u2014 no captured state\n// that could go stale between renders.\n\nconst h = React.createElement;\n\nconst { memo } = React;\n\ninterface SliderControlProps {\n label: React.ReactNode;\n value: number;\n displayValue?: React.ReactNode;\n min: number;\n max: number;\n step?: number;\n onChange: (v: number) => void;\n}\n\nfunction _SliderControlImpl(props: SliderControlProps) {\n const { label, value, displayValue, min, max, step, onChange } = props;\n const dv = displayValue != null ? displayValue : value;\n const pct = ((value - min) / (max - min)) * 100;\n const grad =\n \"linear-gradient(to right, var(--accent-primary) \" +\n pct +\n \"%, var(--slider-track) \" +\n pct +\n \"%)\";\n return h(\n \"div\",\n null,\n h(\n \"div\",\n { style: { display: \"flex\", justifyContent: \"space-between\", marginBottom: 2 } },\n h(\"span\", { className: \"dv-label\" }, label),\n h(\"span\", { style: { fontSize: 10, color: \"var(--text-faint)\" } }, dv)\n ),\n h(\"input\", {\n type: \"range\",\n min,\n max,\n step,\n value,\n onChange: (e: React.ChangeEvent) => onChange(Number(e.target.value)),\n style: { width: \"100%\", background: grad },\n })\n );\n}\n\nexport const SliderControl = memo(_SliderControlImpl, (prev, next) => {\n return (\n prev.value === next.value &&\n prev.min === next.min &&\n prev.max === next.max &&\n prev.step === next.step &&\n prev.label === next.label &&\n prev.displayValue === next.displayValue\n );\n});\n", "// `StatsTable` \u2014 summary stats table (n / mean / median / SD / SEM /\n// min / max per group) used in the output step of group-plot tools.\n//\n// `PALETTE` is read off the ambient browser globals.\n\nimport { PALETTE } from \"../_core/color\";\n\nimport type { GroupStats } from \"../_core/descriptive\";\nconst h = React.createElement;\n\ninterface StatsTableProps {\n stats: GroupStats[] | null | undefined;\n groupLabel: string;\n}\n\nexport function StatsTable({ stats, groupLabel }: StatsTableProps) {\n if (!stats || stats.length === 0) return null;\n const headers = [\"Group\", \"n\", \"Mean\", \"Median\", \"SD\", \"SEM\", \"Min\", \"Max\"];\n return h(\n \"div\",\n { className: \"dv-panel\" },\n h(\n \"p\",\n { style: { margin: \"0 0 10px\", fontSize: 13, fontWeight: 600, color: \"var(--text-muted)\" } },\n `Summary \u2014 grouped by \"${groupLabel}\"`\n ),\n h(\n \"div\",\n { style: { overflowX: \"auto\" } },\n h(\n \"table\",\n { style: { borderCollapse: \"collapse\", fontSize: 12, width: \"100%\" } },\n h(\n \"thead\",\n null,\n h(\n \"tr\",\n { style: { borderBottom: \"2px solid var(--border-strong)\" } },\n headers.map((hdr) =>\n h(\n \"th\",\n {\n key: hdr,\n style: {\n padding: \"4px 10px\",\n textAlign: \"left\",\n color: \"var(--text-muted)\",\n fontWeight: 600,\n },\n },\n hdr\n )\n )\n )\n ),\n h(\n \"tbody\",\n null,\n stats.map((s, i) =>\n h(\n \"tr\",\n { key: s.name, style: { borderBottom: \"1px solid var(--border)\" } },\n h(\n \"td\",\n {\n style: {\n padding: \"4px 10px\",\n fontWeight: 600,\n color: PALETTE[i % PALETTE.length],\n },\n },\n s.name\n ),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.n),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.mean != null ? s.mean.toFixed(4) : \"\u2014\"),\n h(\n \"td\",\n { style: { padding: \"4px 10px\" } },\n s.median != null ? s.median.toFixed(4) : \"\u2014\"\n ),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.sd != null ? s.sd.toFixed(4) : \"\u2014\"),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.sem != null ? s.sem.toFixed(4) : \"\u2014\"),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.min != null ? s.min.toFixed(4) : \"\u2014\"),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.max != null ? s.max.toFixed(4) : \"\u2014\")\n )\n )\n )\n )\n )\n );\n}\n", "// `computePowerFromData` \u2014 n-needed-for-80%-power from observed data,\n// dispatched per test family (t-tests / ANOVA / KW). Forward-looking\n// replication planning: given the effect size we just saw, how many\n// samples per group would a future study need to reach 80% power at\n// each \u03B1? For rank-based tests (Mann-Whitney / Kruskal-Wallis) we\n// report the parametric analog as an approximation \u2014 flagged in the\n// returned `approximate` flag.\n//\n// Deliberately does NOT return \"achieved\" / observed power. Post-hoc\n// power is a deterministic transformation of the p-value (Hoenig &\n// Heisey 2001) \u2014 it adds zero information beyond what p already tells\n// the reader, and a coloured \"% achieved\" cell nudges users toward\n// interpreting non-significant tests as \"underpowered\" when the honest\n// framing is \"we observed effect size X; for that effect, a replication\n// would need n=Y.\"\n\nimport { powerAnova, powerTwoSample } from \"../_core/stats/dist\";\nimport { cohenDCI, sampleMean, sampleSD } from \"../_core/stats/tests\";\n\nexport interface PowerFromDataRow {\n alpha: number;\n nForTarget: number | null;\n}\n\n// 95 % CI on the effect size (Cohen's d / d_av). Present for two-group\n// parametric tests (Student / Welch), null for ANOVA k\u22653 and rank-based\n// tests where a single closed-form CI on the effect isn't as clean \u2014\n// callers can either bootstrap or rely on the existing point estimate\n// for those cases.\nexport interface PowerFromDataResult {\n effectLabel: string;\n effect: number;\n effectCI: { lo: number; hi: number } | null;\n rows: PowerFromDataRow[];\n targetPower: number;\n nLabel: string;\n approximate: boolean;\n}\n\nexport function computePowerFromData(\n chosenTest: string | null | undefined,\n values: number[][] | null | undefined\n): PowerFromDataResult | null {\n if (!chosenTest || !values || values.length < 2) return null;\n const alphas = [0.05, 0.01, 0.001];\n const target = 0.8;\n\n if (chosenTest === \"studentT\" || chosenTest === \"welchT\" || chosenTest === \"mannWhitney\") {\n const x = values[0];\n const y = values[1];\n const n1 = x.length;\n const n2 = y.length;\n if (n1 < 2 || n2 < 2) return null;\n const m1 = sampleMean(x);\n const m2 = sampleMean(y);\n const s1 = sampleSD(x);\n const s2 = sampleSD(y);\n // Effect-size denominator picks based on the chosen test's\n // variance assumption (Lakens 2013):\n // - Student's t / Mann-Whitney: pooled SD (d_s). Both assume\n // a single common variance; the pooled estimator is the\n // maximum-likelihood denominator consistent with that.\n // - Welch's t: mean of unpooled SDs (d_av). Welch denies the\n // equal-variance assumption; the pooled denominator embeds\n // it back and is methodologically inconsistent. d_av takes\n // each group's SD on its own merits, symmetric in both.\n // Label changes too so users can tell which denominator was used.\n let denom: number;\n let label: string;\n if (chosenTest === \"welchT\") {\n denom = (s1 + s2) / 2;\n label = \"Cohen's d_av\";\n } else {\n denom = Math.sqrt(((n1 - 1) * s1 * s1 + (n2 - 1) * s2 * s2) / (n1 + n2 - 2));\n label = \"Cohen's d\";\n }\n // d / d_av return the absolute magnitude here for the power-curve\n // input. The CI is computed on the *signed* d (so direction is\n // preserved when the panel displays \"d = -2.05, 95% CI [-2.50, -1.58]\").\n const dAbs = denom > 0 ? Math.abs(m1 - m2) / denom : 0;\n const dSigned = denom > 0 ? (m1 - m2) / denom : 0;\n const effectCI =\n chosenTest === \"mannWhitney\"\n ? null // MWU is rank-based; the d analog has no clean closed-form CI\n : denom > 0\n ? cohenDCI(dSigned, n1, n2)\n : null;\n const rows = alphas.map((alpha) => {\n let needed: number | null = null;\n if (dAbs > 0) {\n for (let n = 2; n <= 5000; n++) {\n if (powerTwoSample(dAbs, n, alpha, 2) >= target) {\n needed = n;\n break;\n }\n }\n }\n return { alpha, nForTarget: needed };\n });\n return {\n effectLabel: label,\n effect: dSigned,\n effectCI,\n rows,\n targetPower: target,\n nLabel: \"per group\",\n approximate: chosenTest === \"mannWhitney\",\n };\n }\n\n if (\n chosenTest === \"oneWayANOVA\" ||\n chosenTest === \"welchANOVA\" ||\n chosenTest === \"kruskalWallis\"\n ) {\n const kk = values.length;\n if (kk < 2) return null;\n const means = values.map(sampleMean);\n if (values.some((v) => v.length < 2)) return null;\n let ssW = 0;\n let totalN = 0;\n let weightedSum = 0;\n for (let i = 0; i < kk; i++) {\n const m = means[i];\n const ni = values[i].length;\n for (let j = 0; j < ni; j++) ssW += (values[i][j] - m) * (values[i][j] - m);\n totalN += ni;\n weightedSum += ni * m;\n }\n // Cohen's f via \u03B7\u00B2-based formula (the canonical noncentrality\n // input for powerAnova, equivalent to `effectsize::cohens_f` in R):\n // f = sqrt(\u03B7\u00B2 / (1 - \u03B7\u00B2)) = sqrt(ssB / ssW)\n // where ssB is weighted by group sizes around the *weighted* grand\n // mean. Pl\u00F6ttr's `fFromGroupMeans(means, sd)` global uses an\n // unweighted SD_means / SD_pooled form that's equivalent at equal n\n // but diverges by ~10 % at unequal n (e.g. ChickWeight Diet 1\u20134\n // post-hoc). Doing the computation inline here keeps the global\n // backward-compatible for `power-app.tsx`'s a-priori calculator\n // (which assumes equal n by design) while ensuring post-hoc\n // replication-planning matches R `effectsize::cohens_f` to FP\n // precision on real observed data.\n const grandMean = totalN > 0 ? weightedSum / totalN : 0;\n let ssB = 0;\n for (let i = 0; i < kk; i++) {\n ssB += values[i].length * (means[i] - grandMean) * (means[i] - grandMean);\n }\n const f = ssW > 0 ? Math.sqrt(ssB / ssW) : 0;\n const rows = alphas.map((alpha) => {\n let needed: number | null = null;\n if (f > 0) {\n for (let n = 2; n <= 5000; n++) {\n if (powerAnova(f, n, alpha, kk) >= target) {\n needed = n;\n break;\n }\n }\n }\n return { alpha, nForTarget: needed };\n });\n return {\n effectLabel: \"Cohen's f\",\n effect: f,\n effectCI: null,\n rows,\n targetPower: target,\n nLabel: \"per group\",\n approximate: chosenTest === \"kruskalWallis\",\n };\n }\n\n return null;\n}\n", "// `assignBracketLevels` \u2014 pure layout helper that assigns a vertical\n// stack level (0 = lowest) to each {i, j} pair so significance brackets\n// at overlapping spans don't collide. Greedy by ascending span width.\n// Used by chart renderers (boxplot/layout, aequorin/chart) and by\n// `StatsTile` when it builds its own annotation specs.\n//\n// Generic over T to preserve any additional fields the caller threads\n// through (label, p, etc.). Lives in its own module so consumers that\n// only need the layout math don't pull in the full StatsTile bundle.\n\nexport function assignBracketLevels(\n pairs: T[]\n): Array {\n const enriched = pairs.map((pr, idx) => ({\n ...pr,\n _span: Math.abs(pr.j - pr.i),\n _orig: idx,\n _level: 0,\n }));\n enriched.sort((a, b) => a._span - b._span);\n const placed: typeof enriched = [];\n for (const pr of enriched) {\n let lvl = 0;\n while (\n placed.some(\n (q) =>\n q._level === lvl &&\n Math.max(Math.min(q.i, q.j), Math.min(pr.i, pr.j)) <=\n Math.min(Math.max(q.i, q.j), Math.max(pr.i, pr.j))\n )\n ) {\n lvl++;\n }\n pr._level = lvl;\n placed.push(pr);\n }\n // Restore original input order so the parent can match up labels.\n placed.sort((a, b) => a._orig - b._orig);\n return placed.map(({ _orig: _o, _span: _s, ...rest }) => rest as T & { _level: number });\n}\n", "// Shared typed chart-layout helpers used by more than one tool \u2014 currently\n// `lineplot/helpers.ts` and `aequorin/helpers.ts`, both of which re-export\n// from here. Only pure functions and data shapes belong here; anything\n// with per-tool behaviour stays in `tools//helpers.ts`.\n\n// Default chart-margin for line-trace charts (lineplot + aequorin). Matches\n// the axis-label / tick-label footprints both tools use.\nexport const CHART_MARGIN = { top: 20, right: 20, bottom: 48, left: 62 };\n\n// Build an SVG polyline `d` attribute from an array of `{x, y}` points.\n// Skips any point whose `y` is null (missing/interpolated gaps). Returns an\n// empty string when fewer than 2 valid points remain \u2014 the caller should\n// suppress rendering rather than emit an empty path.\nexport function buildLineD(pts: { x: number; y: number | null }[]): string {\n const valid = pts.filter((p): p is { x: number; y: number } => p.y != null);\n if (valid.length < 2) return \"\";\n return \"M\" + valid.map((p) => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(\"L\");\n}\n", "// Shared test / post-hoc dispatchers for every plot tool that runs\n// inferential stats (boxplot, lineplot, aequorin). Thin wrappers over\n// `STATS_TEST_REGISTRY` and `STATS_POSTHOC_REGISTRY` (defined in\n// `./stats-registry`) \u2014 the registry is the single source of truth for\n// the test-id \u2192 (label, runner, post-hoc, arity) mapping.\n//\n// Pre-registry, this file carried its own switch chain on test names,\n// duplicated across `shared-stats-tile.js` and `aequorin/reports.ts` \u2014\n// adding a new test meant editing 4+ string-matching sites with no\n// compile-time guarantee they stayed in sync. The registry collapses\n// the dispatch surface to one table.\n//\n// All three functions return `{ error }`-shaped values on failure so\n// callers don't have to branch on the test name.\n\nimport { STATS_TEST_REGISTRY, STATS_POSTHOC_REGISTRY } from \"./stats-registry\";\n\ntype GroupValues = number[][];\n\n// Union shape of every `STATS_TEST_REGISTRY[*].run(values)` return. Each\n// underlying test (tTest / mannWhitneyU / oneWayANOVA / welchANOVA /\n// kruskalWallis \u2014 see `tools/stats-tests.js`) populates only its own\n// statistic field; `p`, `df*`, and `error` are common. Kept as a single\n// shape with optional fields rather than a discriminated union because\n// the call sites probe by field presence (`if (res.t != null)`), which\n// works directly without per-test narrowing.\nexport interface TestResult {\n // Test-specific statistic \u2014 exactly one of these is present on success.\n t?: number; // Student / Welch t\n U?: number; // Mann-Whitney U\n U1?: number;\n U2?: number;\n z?: number;\n F?: number; // one-way ANOVA / Welch's ANOVA\n ssBetween?: number;\n ssWithin?: number;\n grandMean?: number;\n H?: number; // Kruskal-Wallis\n\n // Common fields (populated by whichever test ran).\n df?: number;\n df1?: number;\n df2?: number;\n n1?: number;\n n2?: number;\n mean1?: number;\n mean2?: number;\n var1?: number;\n var2?: number;\n p?: number;\n\n // Set when the underlying test threw or refused (n<2, k<2, \u2026).\n error?: string;\n}\n\n// Pull whichever test statistic the result carries \u2014 for stats CSV\n// export and similar display contexts that don't care which test ran.\n// Returns null when the result is missing, errored, or carries no\n// recognisable statistic.\nexport function testStatistic(res: TestResult | null | undefined): number | null {\n if (!res || res.error) return null;\n return res.t ?? res.U ?? res.F ?? res.H ?? null;\n}\n\n// Union shape of every post-hoc pair across tukeyHSD / gamesHowell /\n// dunnTest. Same rationale as `TestResult` \u2014 callers probe by field\n// presence (`pr.diff != null`, `pr.z != null`) rather than discriminating\n// on a method tag.\nexport interface PostHocPair {\n i: number;\n j: number;\n\n // Tukey / Games-Howell carry diff/se/q (and Tukey adds lwr/upr).\n diff?: number;\n se?: number;\n q?: number;\n df?: number;\n lwr?: number;\n upr?: number;\n\n // Dunn carries z.\n z?: number;\n\n // Common.\n p: number;\n pAdj?: number | null;\n}\n\nexport interface PostHocResult {\n pairs: PostHocPair[];\n k?: number;\n df?: number;\n mse?: number;\n method?: string;\n error?: string;\n // Soft warning \u2014 the result is still usable, but one or more derived\n // quantities (typically Tukey HSD CI bounds at small df with large k\n // and 1\u2212\u03B1 \u2265 0.95) were silently NaN. See `qtukey`'s design-envelope\n // comment in `tools/stats-posthoc.js`.\n warning?: string;\n}\n\nexport function runTest(name: RecommendedTest | string, values: GroupValues): TestResult {\n const entry = STATS_TEST_REGISTRY[name as RecommendedTest];\n if (!entry) return { error: \"unknown test\" };\n try {\n return entry.run(values) as TestResult;\n } catch (e) {\n return { error: String((e && (e as Error).message) || e) };\n }\n}\n\nexport function runPostHoc(\n name: Exclude | string,\n values: GroupValues\n): PostHocResult | null {\n const entry = STATS_POSTHOC_REGISTRY[name as Exclude];\n if (!entry) return null;\n try {\n // Cast via `unknown`: the registry signature is the open\n // `Record` shape so the runtime can also carry\n // tool-specific metadata; the static narrow to `PostHocResult` is\n // safe because `tukeyHSD` / `gamesHowell` / `dunnTest` always\n // return `{ pairs, ... }`.\n return entry.run(values) as unknown as PostHocResult;\n } catch (e) {\n return { pairs: [], error: String((e && (e as Error).message) || e) };\n }\n}\n\nexport function postHocForTest(\n testName: RecommendedTest | string | null | undefined\n): Exclude | null {\n if (testName == null) return null;\n const entry = STATS_TEST_REGISTRY[testName as RecommendedTest];\n return entry ? entry.postHoc : null;\n}\n", "// SVG legend renderer shared across chart components. Builds ``\n// blocks with the right layout for three block shapes:\n// - categorical `items` (circle / line / triangle / square / cross + label)\n// - continuous `gradient` (linearGradient stops + min/max labels)\n// - sized circles `sizeItems` (scatter aesthetic \u2014 label-aware row wrapping)\n// `computeLegendHeight` mirrors the layout math so callers can size the\n// reserved bottom band before rendering.\n//\n// Stays in `React.createElement` form (no JSX) because the SVG output is\n// nested deeply enough that JSX would be lower-density without saving any\n// real complexity. Hand-typed as `ReactNode | null` rather than a precise\n// element-tree type.\n//\n// Hex literals inside the SVG output are intentional (chart internals must\n// stay as literal hex per the chrome / SVG split \u2014 see\n// `tools/CLAUDE.md` \u00A7 Theming and the no-css-var-in-svg lint rule).\n//\nimport { svgSafeId } from \"../_core/svg-export\";\n\nconst h = React.createElement;\n\nexport interface LegendBlock {\n // Optional title rendered above the items / gradient / sizeItems.\n // Accepts null in addition to undefined because the runtime guard is\n // `if (block.title)` (see renderSvgLegend), and existing call sites\n // pass `title: null` for the no-header case.\n title?: string | null;\n // Optional explicit DOM id for the rendered `` (otherwise derived from\n // title via svgSafeId). Useful when a tool needs a stable hook into the\n // exported SVG.\n id?: string;\n // Categorical legend items (circle / line / triangle / square / cross).\n items?: Array<{ label: string; color: string; shape?: string }>;\n // Continuous colour-bar block.\n gradient?: { stops: string[]; min: string | number; max: string | number };\n // Scatter aesthetic \u2014 variable-radius circles with labels.\n sizeItems?: Array<{ r: number; label?: string }>;\n // Tools occasionally extend with their own per-block keys.\n [k: string]: unknown;\n}\n\nexport type LegendItemWidth = number | ((block: LegendBlock) => number);\n\nexport function computeLegendHeight(\n blocks: LegendBlock[] | null | undefined,\n usableW: number,\n itemWidth: LegendItemWidth\n): number {\n if (!blocks || !blocks.length) return 0;\n const IH = 18,\n TH = 15;\n const iw: LegendItemWidth = itemWidth || 88;\n let t = 10;\n blocks.forEach((b, bi) => {\n if (b.title) t += TH;\n if (b.items) {\n const bIW = typeof iw === \"function\" ? iw(b) : iw;\n t += Math.ceil(b.items.length / Math.max(1, Math.floor(usableW / bIW))) * IH;\n }\n if (b.gradient) t += 30;\n if (b.sizeItems && b.sizeItems.length) {\n const mr = Math.max(...b.sizeItems.map((i) => i.r), 3);\n const rowH = mr * 2 + 4;\n // Compute per-item widths and wrap into rows.\n let cx = 0,\n rows = 1;\n b.sizeItems.forEach((item, ii) => {\n const labelW = (item.label || \"\").length * 5.6 + 6;\n const itemW = mr * 2 + 4 + labelW + 12;\n if (ii > 0 && cx + itemW > usableW) {\n rows++;\n cx = 0;\n }\n cx += itemW;\n });\n t += rows * rowH;\n }\n if (bi < blocks.length - 1) t += 8;\n });\n return t + 6;\n}\n\nexport function renderSvgLegend(\n blocks: LegendBlock[] | null | undefined,\n startY: number,\n leftX: number,\n usableW: number,\n itemWidth: LegendItemWidth,\n truncateLabel?: number\n): React.ReactNode {\n if (!blocks || !blocks.length) return null;\n const IH = 18,\n TH = 15;\n const iw: LegendItemWidth = itemWidth || 88;\n\n // Pre-compute block Y offsets in a single pass (avoids O(n\u00B2) slice+reduce).\n const blockOffsets: number[] = [0];\n for (let bi = 0; bi < blocks.length - 1; bi++) {\n const b = blocks[bi];\n let off = blockOffsets[bi];\n if (b.title) off += TH;\n if (b.items) {\n const w = typeof iw === \"function\" ? iw(b) : iw;\n off += Math.ceil(b.items.length / Math.max(1, Math.floor(usableW / w))) * IH;\n }\n if (b.gradient) off += 30;\n if (b.sizeItems && b.sizeItems.length) {\n const mr = Math.max(...b.sizeItems.map((i) => i.r).concat([3]));\n off += mr * 2 + 4;\n }\n off += 8;\n blockOffsets.push(off);\n }\n\n const blockGroups = blocks.map((block, bi) => {\n const bIW = typeof iw === \"function\" ? iw(block) : iw;\n const blockY = startY + blockOffsets[bi];\n const itemsPerRow = Math.max(1, Math.floor(usableW / bIW));\n const children: React.ReactNode[] = [];\n\n if (block.title) {\n children.push(\n h(\n \"text\",\n { key: \"title\", fontSize: \"10\", fill: \"#666\", fontFamily: \"sans-serif\", y: 10 },\n block.title\n )\n );\n }\n\n if (block.items) {\n block.items.forEach((item, ii) => {\n const row = Math.floor(ii / itemsPerRow);\n const col = ii % itemsPerRow;\n let label = item.label || \"\";\n if (truncateLabel && label.length > truncateLabel) {\n label = label.slice(0, truncateLabel - 2) + \"\u2026\";\n }\n let shape: React.ReactNode;\n if (item.shape === \"line\") {\n shape = h(\"line\", {\n key: \"s\",\n x1: 0,\n x2: 14,\n y1: 7,\n y2: 7,\n stroke: item.color,\n strokeWidth: \"2.5\",\n });\n } else if (item.shape === \"triangle\") {\n shape = h(\"polygon\", { key: \"s\", points: \"6,1 1,12 11,12\", fill: item.color });\n } else if (item.shape === \"square\") {\n shape = h(\"rect\", { key: \"s\", x: 1, y: 2, width: 10, height: 10, fill: item.color });\n } else if (item.shape === \"cross\") {\n shape = h(\"path\", {\n key: \"s\",\n d: \"M4,0 H8 V4 H12 V8 H8 V12 H4 V8 H0 V4 H4 Z\",\n fill: item.color,\n });\n } else {\n shape = h(\"circle\", { key: \"s\", cx: 6, cy: 7, r: 5, fill: item.color });\n }\n const text = h(\n \"text\",\n {\n key: \"t\",\n x: item.shape === \"line\" ? 18 : 14,\n y: 11,\n fontSize: \"10\",\n fill: \"#444\",\n fontFamily: \"sans-serif\",\n },\n label\n );\n children.push(\n h(\n \"g\",\n {\n key: \"i\" + ii,\n transform:\n \"translate(\" + col * bIW + \", \" + ((block.title ? TH : 0) + row * IH) + \")\",\n },\n shape,\n text\n )\n );\n });\n }\n\n if (block.gradient) {\n const gw = Math.min(usableW * 0.6, 200),\n gh = 12;\n const th = block.title ? TH : 0;\n const gradId = \"svggrad-\" + bi;\n const stops = block.gradient.stops.map((c, si) =>\n h(\"stop\", {\n key: si,\n offset: (si / (block.gradient!.stops.length - 1)) * 100 + \"%\",\n stopColor: c,\n })\n );\n children.push(\n h(\n \"g\",\n { key: \"grad\", transform: \"translate(0, \" + th + \")\" },\n h(\n \"defs\",\n null,\n h(\"linearGradient\", { id: gradId, x1: \"0%\", y1: \"0%\", x2: \"100%\", y2: \"0%\" }, stops)\n ),\n h(\"rect\", { x: 0, y: 0, width: gw, height: gh, fill: \"url(#\" + gradId + \")\", rx: \"2\" }),\n h(\n \"text\",\n {\n x: 0,\n y: gh + 13,\n fontSize: \"9\",\n fill: \"#555\",\n fontFamily: \"sans-serif\",\n textAnchor: \"start\",\n },\n block.gradient.min\n ),\n h(\n \"text\",\n {\n x: gw,\n y: gh + 13,\n fontSize: \"9\",\n fill: \"#555\",\n fontFamily: \"sans-serif\",\n textAnchor: \"end\",\n },\n block.gradient.max\n )\n )\n );\n }\n\n // Size items (scatter) \u2014 label-aware spacing with row wrapping.\n if (block.sizeItems && block.sizeItems.length) {\n const sth = block.title ? TH : 0;\n const maxR = Math.max(...block.sizeItems.map((i) => i.r), 3);\n const rowH = maxR * 2 + 4;\n let cx = 0,\n row = 0;\n const sizeChildren = block.sizeItems.map((item, ii) => {\n const labelW = (item.label || \"\").length * 5.6 + 6;\n const itemW = maxR * 2 + 4 + labelW + 12;\n if (ii > 0 && cx + itemW > usableW) {\n row++;\n cx = 0;\n }\n const tx = cx;\n cx += itemW;\n return h(\n \"g\",\n { key: ii, transform: \"translate(\" + tx + \", \" + row * rowH + \")\" },\n h(\"circle\", {\n cx: maxR,\n cy: 0,\n r: item.r,\n fill: \"#888\",\n fillOpacity: \"0.35\",\n stroke: \"#888\",\n strokeWidth: \"0.8\",\n }),\n h(\n \"text\",\n { x: maxR * 2 + 4, y: 4, fontSize: \"9\", fill: \"#444\", fontFamily: \"sans-serif\" },\n item.label\n )\n );\n });\n children.push(\n h(\"g\", { key: \"size\", transform: \"translate(0, \" + (sth + maxR) + \")\" }, sizeChildren)\n );\n }\n\n const blockId =\n block.id ||\n (block.title && typeof svgSafeId === \"function\"\n ? \"legend-\" + svgSafeId(block.title)\n : \"legend-block-\" + bi);\n return h(\n \"g\",\n {\n key: bi,\n id: blockId,\n transform: \"translate(\" + leftX + \", \" + blockY + \")\",\n },\n children\n );\n });\n return h(\"g\", { id: \"legend\" }, blockGroups);\n}\n"], - "mappings": "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", + "sourcesContent": ["// stats/dist.ts \u2014 probability distributions, special functions, and power calcs.\n//\n// Layout:\n// 1. Distribution functions \u2014 normal, gamma, beta, t, F, chi-square,\n// noncentral t/F/chi-square\n// 2. Generic helpers \u2014 bisect\n// 2b. Power functions \u2014 t / paired / ANOVA / correlation / chi\u00B2\n//\n// All functions are benchmarked against R output in tests/stats.test.js\n// with a \u00B10.5 % tolerance bar.\n\n// \u2500\u2500 1. Distribution functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Normal CDF. Uses A&S 26.2.17 (max error 7.5e-8) for moderate |x|, and\n// switches to the tail-accurate `normsf` for |x| \u2265 7 \u2014 avoids the 1 \u2212 tiny\n// representation collapse at the edges of double precision.\nexport function normcdf(x: number): number {\n if (x === 0) return 0.5;\n if (x >= 7) return 1 - normsf(x);\n if (x <= -7) return normsf(-x);\n const t = 1 / (1 + 0.2316419 * Math.abs(x));\n const d = 0.3989422804014327; // 1/sqrt(2*pi)\n const poly =\n t *\n (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n const p = 1 - d * Math.exp(-0.5 * x * x) * poly;\n return x > 0 ? p : 1 - p;\n}\n\n// Normal survival 1 - \u03A6(x) \u2014 tail-accurate. For |x| < 7 we use the A&S 26.2.17\n// polynomial directly (no 1 \u2212 normcdf cancellation). For |x| \u2265 7 we switch to\n// the asymptotic expansion \u03A6\u0304(x) \u2248 \u03C6(x)/x \u00B7 (1 \u2212 1/x\u00B2 + 3/x\u2074 \u2212 15/x\u2076 + ...)\n// which stays accurate down to ~1e-300 where A&S 26.2.17 has already lost all\n// meaningful digits.\nexport function normsf(x: number): number {\n if (x === 0) return 0.5;\n if (x < 0) return 1 - normsf(-x);\n const d = 0.3989422804014327; // 1/sqrt(2*pi)\n if (x < 7) {\n const t = 1 / (1 + 0.2316419 * x);\n const poly =\n t *\n (0.31938153 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429))));\n return d * Math.exp(-0.5 * x * x) * poly;\n }\n // Asymptotic series. Truncate when a term is \u2264 1e-16 \u00D7 running sum.\n const x2 = x * x;\n let term = 1;\n let sum = 1;\n let n = 1;\n while (n < 40) {\n term *= -(2 * n - 1) / x2;\n const next = sum + term;\n if (Math.abs(term) <= 1e-16 * Math.abs(next)) {\n sum = next;\n break;\n }\n sum = next;\n n++;\n }\n return (d * Math.exp(-0.5 * x2) * sum) / x;\n}\n\n// Inverse normal CDF \u2014 Peter Acklam's rational approximation (max error 1.15e-9)\nexport function norminv(p: number): number {\n if (p <= 0) return -Infinity;\n if (p >= 1) return Infinity;\n // equiv-mutant: redundant \u2014 the p\u2264pHigh rational form already yields exactly 0 at p=0.5 (q=p\u22120.5=0)\n if (p === 0.5) return 0;\n const a = [\n -3.969683028665376e1, 2.209460984245205e2, -2.759285104469687e2, 1.38357751867269e2,\n -3.066479806614716e1, 2.506628277459239,\n ];\n const b = [\n -5.447609879822406e1, 1.615858368580409e2, -1.556989798598866e2, 6.680131188771972e1,\n -1.328068155288572e1,\n ];\n const c = [\n -7.784894002430293e-3, -3.223964580411365e-1, -2.400758277161838, -2.549732539343734,\n 4.374664141464968, 2.938163982698783,\n ];\n const d = [7.784695709041462e-3, 3.224671290700398e-1, 2.445134137142996, 3.754408661907416];\n const pLow = 0.02425,\n pHigh = 1 - pLow;\n let q: number, r: number;\n if (p < pLow) {\n q = Math.sqrt(-2 * Math.log(p));\n return (\n (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /\n ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)\n );\n }\n if (p <= pHigh) {\n q = p - 0.5;\n r = q * q;\n return (\n ((((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q) /\n (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1)\n );\n }\n q = Math.sqrt(-2 * Math.log(1 - p));\n return (\n -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) /\n ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1)\n );\n}\n\n// Log gamma \u2014 Lanczos approximation\nexport function gammaln(x: number): number {\n const g = 7;\n const coef = [\n 0.99999999999980993, 676.5203681218851, -1259.1392167224028, 771.32342877765313,\n -176.61502916214059, 12.507343278686905, -0.13857109526572012, 9.9843695780195716e-6,\n 1.5056327351493116e-7,\n ];\n if (x < 0.5) return Math.log(Math.PI / Math.sin(Math.PI * x)) - gammaln(1 - x);\n x -= 1;\n let a = coef[0];\n const t = x + g + 0.5;\n for (let i = 1; i < g + 2; i++) a += coef[i] / (x + i);\n return 0.5 * Math.log(2 * Math.PI) + (x + 0.5) * Math.log(t) - t + Math.log(a);\n}\n\n// Regularized incomplete beta function I_x(a, b) via continued fraction.\n//\n// Log-space final exponentiation: the prior `exp(logFront) * cf / a` path\n// underflowed `logFront` to 0 whenever `a*log(x) + b*log(1-x) - lnBeta` went\n// below \u2248 \u2212745 (double-precision exp floor). That silently destroyed deep-tail\n// p-values \u2014 the small-t branch for df/2 \u2265 30 with |t| > 5 routinely lands\n// at logFront \u2248 \u2212200 to \u2212140, well within representable range but wiped to 0\n// if we exp() too early.\nexport function betai(a: number, b: number, x: number): number {\n if (x < 0 || x > 1) return 0;\n if (x === 0) return 0;\n if (x === 1) return 1;\n const lnBeta = gammaln(a) + gammaln(b) - gammaln(a + b);\n const logFront = Math.log(x) * a + Math.log(1 - x) * b - lnBeta;\n if (x < (a + 1) / (a + b + 2)) {\n const cf = betacf(a, b, x);\n return Math.exp(logFront + Math.log(cf / a));\n }\n const cf = betacf(b, a, 1 - x);\n return 1 - Math.exp(logFront + Math.log(cf / b));\n}\n\n// Upper-tail of the regularized incomplete beta: 1 \u2212 I_x(a, b). Computing\n// 1 \u2212 betai() directly cancels when betai is near 1; this helper returns the\n// upper tail via the opposite branch of the continued fraction, so callers\n// like `fcdf_upper` and `tcdf_upper` stay accurate for tiny p-values.\nexport function betai_upper(a: number, b: number, x: number): number {\n if (x <= 0) return 1;\n if (x >= 1) return 0;\n const lnBeta = gammaln(a) + gammaln(b) - gammaln(a + b);\n const logFront = Math.log(x) * a + Math.log(1 - x) * b - lnBeta;\n if (x < (a + 1) / (a + b + 2)) {\n // betai is small \u2192 1 \u2212 tiny; direct subtraction is fine.\n const cf = betacf(a, b, x);\n return 1 - Math.exp(logFront + Math.log(cf / a));\n }\n // betai is \u2248 1 \u2192 upper tail = exp(logFront + log(cf/b)), no cancellation.\n const cf = betacf(b, a, 1 - x);\n return Math.exp(logFront + Math.log(cf / b));\n}\n\n// \u2500\u2500 Continued-fraction primitives \u2014 Cephes-derived \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// betacf, gammainc, and gammainc_upper below are ported from the Cephes\n// Mathematical Library (Stephen L. Moshier), which the author dedicated to\n// the public domain. See `incbet.c` and `igam.c` at:\n// https://www.netlib.org/cephes/\n// \"Cephes Math Library Release 2.8\" \u2014 Stephen L. Moshier, 2000\n//\n// Cephes uses a three-term recurrence (pkm1/pkm2/qkm1/qkm2) with periodic\n// big/biginv rescaling to keep partial numerators/denominators inside\n// representable range. This is structurally distinct from Lentz's modified\n// per-step floor clamp; both converge to the same value within machine\n// precision for the input range we use here. The Pl\u00F6ttr-specific polish\n// (log-space final exponentiation in gammainc / gammainc_upper, and the\n// \u221Aa-scaled iteration cap so chi2cdf / ptukey stay accurate at huge df)\n// is layered on top of the Cephes recurrence.\n//\n// Constants below match Cephes' incbet.c / igam.c literally.\nconst CEPHES_BIG = 4.503599627370496e15; // 2^52\nconst CEPHES_BIGINV = 2.220446049250313e-16; // 2^-52\nconst CEPHES_MACHEP = 1.1102230246251565e-16;\n\n// Continued fraction for the regularized incomplete beta \u2014 forward form\n// (Cephes' incbcf). Returns the bare CF value `pk/qk`; betai / betai_upper\n// then multiply by the log-space prefactor.\nexport function betacf(a: number, b: number, x: number): number {\n let k1 = a;\n let k2 = a + b;\n let k3 = a;\n let k4 = a + 1;\n let k5 = 1;\n let k6 = b - 1;\n let k7 = k4;\n let k8 = a + 2;\n\n let pkm2 = 0;\n let qkm2 = 1;\n let pkm1 = 1;\n let qkm1 = 1;\n let ans = 1;\n let r = 1;\n const thresh = 3 * CEPHES_MACHEP;\n\n for (let n = 0; n < 300; n++) {\n let xk = -((x * k1 * k2) / (k3 * k4));\n let pk = pkm1 + pkm2 * xk;\n let qk = qkm1 + qkm2 * xk;\n pkm2 = pkm1;\n pkm1 = pk;\n qkm2 = qkm1;\n qkm1 = qk;\n\n xk = (x * k5 * k6) / (k7 * k8);\n pk = pkm1 + pkm2 * xk;\n qk = qkm1 + qkm2 * xk;\n pkm2 = pkm1;\n pkm1 = pk;\n qkm2 = qkm1;\n qkm1 = qk;\n\n let t: number;\n if (qk !== 0) r = pk / qk;\n if (r !== 0) {\n t = Math.abs((ans - r) / r);\n ans = r;\n } else {\n t = 1;\n }\n if (t < thresh) return ans;\n\n k1 += 1;\n k2 += 1;\n k3 += 2;\n k4 += 2;\n k5 += 1;\n k6 -= 1;\n k7 += 2;\n k8 += 2;\n\n if (Math.abs(qk) + Math.abs(pk) > CEPHES_BIG) {\n pkm2 *= CEPHES_BIGINV;\n pkm1 *= CEPHES_BIGINV;\n qkm2 *= CEPHES_BIGINV;\n qkm1 *= CEPHES_BIGINV;\n }\n if (Math.abs(qk) < CEPHES_BIGINV || Math.abs(pk) < CEPHES_BIGINV) {\n pkm2 *= CEPHES_BIG;\n pkm1 *= CEPHES_BIG;\n qkm2 *= CEPHES_BIG;\n qkm1 *= CEPHES_BIG;\n }\n }\n return ans;\n}\n\n// Regularized lower incomplete gamma P(a, x) \u2014 Cephes' igam series form.\n// Cephes uses `r += 1; c *= x/r; ans += c` with a `c/ans < MACHEP`\n// termination test. We layer a \u221Aa-scaled iteration cap on top because the\n// series converges in ~O(\u221Aa) steps when x \u2248 a (Poisson-like concentration\n// around n \u2248 x \u2212 a with width \u221Aa) \u2014 chi2cdf / ptukey at huge df otherwise\n// truncate before convergence. Switchover threshold (`x > 1 && x > a`)\n// matches Cephes literally, not NR's `x < a + 1`.\nexport function gammainc(a: number, x: number): number {\n if (x < 0) return 0;\n if (x === 0) return 0;\n if (x > 1 && x > a) return 1 - gammainc_upper(a, x);\n\n let r = a;\n let c = 1;\n let ans = 1;\n const maxIter = Math.max(700, Math.ceil(20 * Math.sqrt(a + 1)));\n for (let i = 0; i < maxIter; i++) {\n r += 1;\n c *= x / r;\n ans += c;\n if (c / ans < CEPHES_MACHEP) break;\n }\n // Log-space final exponentiation: the prefactor `exp(a*ln(x) - x - ln\u0393(a))`\n // can underflow at large a even when ans*ax/a is well within range.\n return Math.exp(Math.log(ans / a) + a * Math.log(x) - x - gammaln(a));\n}\n\n// Regularized upper incomplete gamma Q(a, x) = 1 \u2212 P(a, x) \u2014 Cephes' igamc\n// continued-fraction form. Three-term recurrence on (pkm, qkm) with the\n// `pk = pkm1*z \u2212 pkm2*yc` update; `c` and `y` advance by integer steps each\n// iteration. Big/biginv rescaling keeps |pk| from running away.\nexport function gammainc_upper(a: number, x: number): number {\n let y = 1 - a;\n let z = x + y + 1;\n let c = 0;\n let pkm2 = 1;\n let qkm2 = x;\n let pkm1 = x + 1;\n let qkm1 = z * x;\n let ans = pkm1 / qkm1;\n\n const maxIter = Math.max(300, Math.ceil(20 * Math.sqrt(a + 1)));\n for (let i = 0; i < maxIter; i++) {\n c += 1;\n y += 1;\n z += 2;\n const yc = y * c;\n const pk = pkm1 * z - pkm2 * yc;\n const qk = qkm1 * z - qkm2 * yc;\n let t: number;\n if (qk !== 0) {\n const r = pk / qk;\n t = Math.abs((ans - r) / r);\n ans = r;\n } else {\n t = 1;\n }\n pkm2 = pkm1;\n pkm1 = pk;\n qkm2 = qkm1;\n qkm1 = qk;\n if (Math.abs(pk) > CEPHES_BIG) {\n pkm2 *= CEPHES_BIGINV;\n pkm1 *= CEPHES_BIGINV;\n qkm2 *= CEPHES_BIGINV;\n qkm1 *= CEPHES_BIGINV;\n }\n if (t < CEPHES_MACHEP) break;\n }\n // Log-space final exponentiation \u2014 see comment in gammainc.\n return Math.exp(Math.log(ans) + a * Math.log(x) - x - gammaln(a));\n}\n\n// t-distribution CDF\nexport function tcdf(t: number, df: number): number {\n if (df <= 0) return NaN;\n const x = df / (df + t * t);\n const p = 0.5 * betai(df / 2, 0.5, x);\n return t >= 0 ? 1 - p : p;\n}\n\n// Upper-tail 1 \u2212 tcdf(t, df). Avoids the `1 \u2212 (near-1)` cancellation that\n// silently drove `tTest` p-values to 0 for |t| > ~9 at large df \u2014 e.g. iris\n// SL setosa vs versicolor (t \u2248 10.5, df = 98) where R reports p \u2248 9e-18 but\n// JS underflowed to 0 because tcdf returned a float-rounded 1.0.\nexport function tcdf_upper(t: number, df: number): number {\n if (df <= 0) return NaN;\n const x = df / (df + t * t);\n const tail = 0.5 * betai(df / 2, 0.5, x);\n return t >= 0 ? tail : 1 - tail;\n}\n\n// t-distribution PDF (computed in log space, used by tinv Newton-Raphson)\nexport function tpdf(x: number, df: number): number {\n const logpdf =\n gammaln((df + 1) / 2) -\n gammaln(df / 2) -\n 0.5 * Math.log(Math.PI * df) -\n ((df + 1) / 2) * Math.log(1 + (x * x) / df);\n return Math.exp(logpdf);\n}\n\n// Inverse t CDF.\n// Strategy:\n// \u2022 df = 1 and df = 2 have closed forms \u2014 use them directly.\n// \u2022 df \u2265 3: Newton-Raphson seeded with a Cornish-Fisher correction to the\n// normal quantile, with a damped step and a bisection fallback if Newton\n// fails to converge. Bracket bounds expand outward (doubling) to handle\n// heavy-tailed cases where |t| can exceed hundreds or thousands at\n// extreme p (e.g. qt(1e-10, 5) \u2248 \u2212157).\n// \u2022 Work in the left tail (leftP \u2264 0.5) and flip sign at the end, so that\n// inputs like p = 1 \u2212 1e-15 don't suffer catastrophic cancellation.\nexport function tinv(p: number, df: number): number {\n if (df <= 0) return NaN;\n if (p <= 0) return -Infinity;\n if (p >= 1) return Infinity;\n // equiv-mutant: redundant \u2014 every df branch already converges to 0 at p=0.5 (leftP=0.5)\n if (p === 0.5) return 0;\n\n const upper = p > 0.5;\n const leftP = upper ? 1 - p : p;\n\n let tLeft: number;\n\n if (df === 1) {\n // Cauchy: F\u207B\u00B9(u) = tan(\u03C0(u \u2212 0.5)) = \u2212cot(\u03C0\u00B7u).\n tLeft = -1 / Math.tan(Math.PI * leftP);\n } else if (df === 2) {\n // Closed form: t = (2u\u22121) / \u221A(2u(1\u2212u)).\n tLeft = (2 * leftP - 1) / Math.sqrt(2 * leftP * (1 - leftP));\n } else {\n // Cornish-Fisher seed: first-order correction to the normal quantile.\n const z = norminv(leftP);\n let x = z + (z * (z * z + 1)) / (4 * df);\n\n let converged = false;\n for (let i = 0; i < 60; i++) {\n const cdf = tcdf(x, df);\n const pdf = tpdf(x, df);\n if (!Number.isFinite(cdf) || !(pdf > 0)) break;\n let step = (cdf - leftP) / pdf;\n // Damp the step so we can't jump past a flat tail where PDF \u2192 0.\n const cap = Math.abs(x) + 1;\n if (step > cap) step = cap;\n else if (step < -cap) step = -cap;\n x -= step;\n if (Math.abs(step) < 1e-13 * (Math.abs(x) + 1)) {\n converged = true;\n break;\n }\n }\n\n if (converged && Number.isFinite(x)) {\n tLeft = x;\n } else {\n // Bisection fallback: expand the lower bound until leftP is bracketed.\n let lo = -1;\n const hi = 0;\n for (let i = 0; i < 4000 && tcdf(lo, df) > leftP; i++) lo *= 2;\n let a = lo,\n b = hi;\n for (let i = 0; i < 200; i++) {\n const mid = (a + b) / 2;\n if (tcdf(mid, df) < leftP) a = mid;\n else b = mid;\n if (b - a < 1e-12 * (Math.abs(a) + 1)) break;\n }\n tLeft = (a + b) / 2;\n }\n }\n\n return upper ? -tLeft : tLeft;\n}\n\n// F-distribution CDF\nexport function fcdf(f: number, d1: number, d2: number): number {\n if (d1 <= 0 || d2 <= 0) return NaN;\n // equiv-mutant: subsumed \u2014 for f<=0 the x argument lands outside (0,1) and betai returns 0\n if (f <= 0) return 0;\n return betai(d1 / 2, d2 / 2, (d1 * f) / (d1 * f + d2));\n}\n\n// Upper-tail 1 \u2212 fcdf(f, d1, d2). Same rationale as `tcdf_upper` \u2014 direct\n// computation avoids the 1 \u2212 (near-1) cancellation that underflows ANOVA /\n// Welch-ANOVA p-values at F > ~50.\nexport function fcdf_upper(f: number, d1: number, d2: number): number {\n if (d1 <= 0 || d2 <= 0) return NaN;\n // equiv-mutant: subsumed \u2014 for f<=0 the x argument lands outside (0,1) and betai_upper returns 1\n if (f <= 0) return 1;\n return betai_upper(d1 / 2, d2 / 2, (d1 * f) / (d1 * f + d2));\n}\n\n// Chi-square CDF\nexport function chi2cdf(x: number, k: number): number {\n if (k <= 0) return NaN;\n // equiv-mutant: subsumed \u2014 gammainc returns 0 for the x/2<=0 that x<=0 produces\n if (x <= 0) return 0;\n return gammainc(k / 2, x / 2);\n}\n\n// Chi-square PDF \u2014 used as the Newton derivative in chi2inv. Built in log\n// space to avoid overflow at large k or tiny x.\nexport function chi2pdf(x: number, k: number): number {\n if (k <= 0) return NaN;\n if (x <= 0) return 0;\n const halfK = k / 2;\n return Math.exp((halfK - 1) * Math.log(x) - x / 2 - halfK * Math.log(2) - gammaln(halfK));\n}\n\n// Inverse chi-square CDF \u2014 Newton-Raphson on the central body, bisection\n// fallback for the saturated tails where the \u03C7\u00B2 CDF derivative collapses\n// to ~10\u207B\u00B9\u00B2 and Newton overshoots wildly. The historical implementation was\n// pure bisection with a doubling upper bound (~50 iterations for 1e-10\n// tolerance); this version is much faster on typical inputs while remaining\n// correct on the tails. Uses the Wilson-Hilferty cubic-normal approximation\n// (X/k)^(1/3) \u2248 N(1 \u2212 2/(9k), 2/(9k)) to seed both Newton and the bracket.\nexport function chi2inv(p: number, k: number): number {\n if (p <= 0) return 0;\n if (p >= 1) return Infinity;\n if (k <= 0) return NaN;\n const z = norminv(p);\n const h = 2 / (9 * k);\n let x = k * Math.pow(1 - h + z * Math.sqrt(h), 3);\n if (!(x > 0)) x = Math.max(1e-6, k * 0.01);\n // Newton inside a wide guard: only accept iterations that stay positive\n // and don't exceed twice the current point. If Newton makes <10\u207B\u00B9\u00B2 PDF\n // (deep in the saturated tail) or the step would leave the guard, drop\n // straight to bisection.\n for (let i = 0; i < 30; i++) {\n const f = chi2cdf(x, k) - p;\n if (Math.abs(f) < 1e-12) return x;\n const fp = chi2pdf(x, k);\n if (!(fp > 1e-12)) break;\n const step = f / fp;\n const xNew = x - step;\n if (!(xNew > 0) || xNew > 2 * x || xNew < x / 2) break;\n if (Math.abs(xNew - x) < 1e-12 * Math.max(1, x)) return xNew;\n x = xNew;\n }\n // Bisection fallback. Use WH seed to set a tight initial bracket; expand\n // by doubling if it doesn't cover p (bisect itself refuses unbracketed\n // targets, so this guarantees progress).\n const lo = 0;\n let hi = Math.max(x * 2, k + 10 * Math.sqrt(2 * k));\n for (let i = 0; i < 40 && chi2cdf(hi, k) < p; i++) hi *= 2;\n return bisect((q) => chi2cdf(q, k), p, lo, hi, 1e-10);\n}\n\n// Gauss-Legendre quadrature nodes and weights (computed once, cached)\ninterface GLCache {\n nodes: number[];\n weights: number[];\n n: number;\n}\nlet _glCache: GLCache | undefined;\nexport function _gaussLegendre(n: number): GLCache {\n if (_glCache && _glCache.n === n) return _glCache;\n const nodes = new Array(n),\n weights = new Array(n);\n for (let i = 0; i < Math.ceil(n / 2); i++) {\n let x = Math.cos((Math.PI * (i + 0.75)) / (n + 0.5));\n for (let it = 0; it < 100; it++) {\n let pm1 = 1,\n p = x;\n for (let j = 2; j <= n; j++) {\n const pp = ((2 * j - 1) * x * p - (j - 1) * pm1) / j;\n pm1 = p;\n p = pp;\n }\n const dp = (n * (x * p - pm1)) / (x * x - 1);\n const dx = p / dp;\n x -= dx;\n if (Math.abs(dx) < 1e-15) break;\n }\n let pm1 = 1,\n p = x;\n for (let j = 2; j <= n; j++) {\n const pp = ((2 * j - 1) * x * p - (j - 1) * pm1) / j;\n pm1 = p;\n p = pp;\n }\n const dp = (n * (x * p - pm1)) / (x * x - 1);\n const w = 2 / ((1 - x * x) * dp * dp);\n nodes[i] = -x;\n nodes[n - 1 - i] = x;\n weights[i] = w;\n weights[n - 1 - i] = w;\n }\n return (_glCache = { nodes, weights, n });\n}\n\n// Noncentral t CDF \u2014 Gauss-Legendre quadrature of chi-square mixture.\n// P(T \u2264 t | \u03BD, \u03B4) = \u222B\u2080^\u221E \u03A6(t\u221A(s/\u03BD) \u2212 \u03B4) \u00B7 f_\u03C7\u00B2(s; \u03BD) ds\n// Substitution u = \u221As removes density singularity for small \u03BD, giving:\n// = \u222B\u2080^\u221E \u03A6(tu/\u221A\u03BD \u2212 \u03B4) \u00B7 2u^{\u03BD\u22121} e^{\u2212u\u00B2/2} / (2^{\u03BD/2} \u0393(\u03BD/2)) du\nexport function nctcdf(t: number, df: number, delta: number): number {\n if (df <= 0) return NaN;\n if (Math.abs(delta) < 1e-14) return tcdf(t, df);\n const halfDf = df / 2;\n const logC = halfDf * Math.log(2) + gammaln(halfDf);\n const sqrtDf = Math.sqrt(df);\n const uLo = Math.max(0, sqrtDf - 8);\n const uHi = sqrtDf + 8;\n const gl = _gaussLegendre(48);\n const half = (uHi - uLo) / 2,\n mid = (uHi + uLo) / 2;\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const u = mid + half * gl.nodes[i];\n if (u <= 0) continue;\n const logH = Math.log(2) + (df - 1) * Math.log(u) - (u * u) / 2 - logC;\n sum += half * gl.weights[i] * normcdf((t * u) / sqrtDf - delta) * Math.exp(logH);\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// Noncentral F survival P(F' > f) \u2014 Poisson mixture\n// F' = (\u03C7\u00B2_{d1+2J}/d1) / (\u03C7\u00B2_{d2}/d2), J~Poisson(\u03BB/2)\n// P(F'>f) = \u03A3_j P(J=j) \u00D7 P(F(d1+2j, d2) > f \u00D7 d1/(d1+2j))\n// Starts at Poisson mode to avoid underflow for large \u03BB.\nexport function ncf_sf(f: number, d1: number, d2: number, lambda: number): number {\n if (d1 <= 0 || d2 <= 0) return NaN;\n if (f <= 0) return 1;\n if (lambda <= 0) return fcdf_upper(f, d1, d2);\n const halfLam = lambda / 2;\n\n // Large-\u03BB short circuit: the Poisson mass around the mode has width\n // \u03C3 = \u221A(\u03BB/2), so a fixed 500-step window leaves most of the mass\n // unaccounted for when \u03BB is huge. In that regime, P(F'>f) is either\n // \u2248 1 or \u2248 0 for f outside a few \u03C3 of the NCF mean \u2014 use closed-form\n // mean / variance of the NCF (valid for d2 > 4) and short-circuit via\n // normal approximation when f is clearly in the far tail. Without this,\n // power-analysis sample-size solvers probe huge n values (e.g. via\n // bisect up to hi=100000) and get misleading truncated-sum answers,\n // breaking the monotonic-in-n assumption the solver depends on.\n if (halfLam > 500 && d2 > 4) {\n const mean = (d2 / (d2 - 2)) * ((d1 + lambda) / d1);\n const v =\n (2 *\n Math.pow(d2 / (d2 - 2), 2) *\n ((d1 + lambda) * (d1 + lambda) + (d1 + 2 * lambda) * (d2 - 2))) /\n (d1 * d1 * (d2 - 4));\n const sd = Math.sqrt(v);\n const z = (f - mean) / sd;\n if (z < -6) return 1;\n if (z > 6) return 0;\n }\n\n const jMode = Math.max(0, Math.floor(halfLam));\n // Widen the Poisson window for larger \u03BB so we cover \u00B18\u03C3 of the mass.\n const maxSteps = Math.max(500, Math.ceil(8 * Math.sqrt(halfLam + 1)));\n\n function sfTerm(j: number): number {\n const d1j = d1 + 2 * j;\n return fcdf_upper((f * d1) / d1j, d1j, d2);\n }\n\n const logPMode = -halfLam + (jMode > 0 ? jMode * Math.log(halfLam) - gammaln(jMode + 1) : 0);\n const pTerm = Math.exp(logPMode);\n let sum = pTerm * sfTerm(jMode);\n\n let pUp = pTerm;\n for (let j = jMode + 1; j < jMode + maxSteps; j++) {\n pUp *= halfLam / j;\n const contrib = pUp * sfTerm(j);\n sum += contrib;\n if (j - jMode > 10 && pUp < 1e-14) break;\n }\n\n let pDown = pTerm;\n for (let j = jMode - 1; j >= 0 && jMode - j < maxSteps; j--) {\n pDown *= (j + 1) / halfLam;\n const contrib = pDown * sfTerm(j);\n sum += contrib;\n if (jMode - j > 10 && pDown < 1e-14) break;\n }\n\n return Math.min(1, Math.max(0, sum));\n}\n\n// Noncentral chi-square CDF \u2014 Poisson mixture (starts at mode to avoid underflow)\nexport function ncchi2cdf(x: number, k: number, lambda: number): number {\n if (k <= 0) return NaN;\n if (x <= 0) return 0;\n if (lambda <= 0) return chi2cdf(x, k);\n const halfLam = lambda / 2;\n // Large-\u03BB short circuit: use normal approximation for the far tails.\n // Noncentral \u03C7\u00B2 has mean k+\u03BB and variance 2(k+2\u03BB); a |z|>6 tail is < 1e-9.\n if (halfLam > 500) {\n const mean = k + lambda;\n const sd = Math.sqrt(2 * (k + 2 * lambda));\n const z = (x - mean) / sd;\n if (z < -6) return 0;\n if (z > 6) return 1;\n }\n const jMode = Math.max(0, Math.floor(halfLam));\n // Poisson mixture width is \u221A\u03BB/2; widen the scan window with \u221AhalfLam so huge\n // \u03BB doesn't silently truncate the sum (mirrors the ncf_sf fix).\n const maxSteps = Math.max(500, Math.ceil(8 * Math.sqrt(halfLam + 1)));\n\n function cdfTerm(j: number): number {\n return gammainc(k / 2 + j, x / 2);\n }\n\n const logPMode = -halfLam + (jMode > 0 ? jMode * Math.log(halfLam) - gammaln(jMode + 1) : 0);\n const pTerm = Math.exp(logPMode);\n let sum = pTerm * cdfTerm(jMode);\n\n let pUp = pTerm;\n for (let j = jMode + 1; j < jMode + maxSteps; j++) {\n pUp *= halfLam / j;\n const contrib = pUp * cdfTerm(j);\n sum += contrib;\n if (j > jMode + 5 && pUp < 1e-14) break;\n }\n\n let pDown = pTerm;\n for (let j = jMode - 1; j >= 0; j--) {\n pDown *= (j + 1) / halfLam;\n const contrib = pDown * cdfTerm(j);\n sum += contrib;\n if (jMode - j > 5 && pDown < 1e-14) break;\n }\n\n return Math.min(1, Math.max(0, sum));\n}\n\n// \u2500\u2500 2. Generic helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Generic bisection solver: find x in [lo, hi] such that fn(x) \u2248 target.\n// Assumes fn is monotone non-decreasing. Returns NaN if target is not\n// bracketed \u2014 this is intentional: silent clamping to a bracket boundary\n// would let downstream code use a number that isn't actually a root. All\n// existing callers are responsible for expanding hi (or shrinking lo)\n// before delegating to bisect when their target might fall outside.\nexport function bisect(\n fn: (x: number) => number,\n target: number,\n lo: number,\n hi: number,\n tol = 1e-6,\n maxIter = 200\n): number {\n if (fn(lo) > target || fn(hi) < target) return NaN;\n for (let i = 0; i < maxIter; i++) {\n const mid = (lo + hi) / 2;\n if (fn(mid) < target) lo = mid;\n else hi = mid;\n if (hi - lo < tol) break;\n }\n return (lo + hi) / 2;\n}\n\n// \u2500\u2500 2b. Power functions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Keyed by test shape, same formulas used by tools/power.tsx. Kept here so\n// the StatsTile (group plot) can report achieved power and the\n// n-per-group needed for 80 % power directly from the observed data.\n\nexport function powerTwoSample(d: number, n: number, alpha: number, tails: number): number {\n const df = 2 * n - 2;\n // Power is the power to detect an effect of this *magnitude* \u2014 match R's\n // pwr::pwr.t.test, which takes |d|. Without abs, a one-tailed call with a\n // negative d (routine when group 2 > group 1) returns ~0, reading as \"this\n // study is hopeless.\" (Two-tailed is already sign-symmetric, so abs is a\n // no-op there.)\n const delta = Math.abs(d) * Math.sqrt(n / 2);\n const tCrit = tinv(1 - alpha / tails, df);\n if (tails === 2) return 1 - nctcdf(tCrit, df, delta) + nctcdf(-tCrit, df, delta);\n return 1 - nctcdf(tCrit, df, delta);\n}\n\nexport function powerPaired(d: number, n: number, alpha: number, tails: number): number {\n const df = n - 1;\n // Magnitude convention \u2014 see powerTwoSample.\n const delta = Math.abs(d) * Math.sqrt(n);\n const tCrit = tinv(1 - alpha / tails, df);\n if (tails === 2) return 1 - nctcdf(tCrit, df, delta) + nctcdf(-tCrit, df, delta);\n return 1 - nctcdf(tCrit, df, delta);\n}\n\nexport function powerOneSample(d: number, n: number, alpha: number, tails: number): number {\n return powerPaired(d, n, alpha, tails);\n}\n\nexport function powerAnova(f: number, n: number, alpha: number, k: number): number {\n const df1 = k - 1,\n df2 = k * (n - 1);\n if (df2 < 1) return 0;\n const lambda = n * k * f * f;\n // Central F at small df has very heavy tails \u2014 e.g. F(1, 1) only reaches\n // p = 0.955 at x = 200 \u2014 so a fixed upper bracket of 200 silently clamps\n // fCrit for \u03B1 \u2264 0.045 at (df1, df2) = (1, 1). Expand the bracket until it\n // covers 1 \u2212 \u03B1 (20 doublings \u2192 ~2\u00B710\u2078, comfortably beyond any realistic\n // fCrit) before delegating to bisect. bisect itself now refuses on an\n // unbracketed target, so we propagate NaN when the expansion fails.\n let hi = 200;\n for (let i = 0; i < 20 && fcdf(hi, df1, df2) < 1 - alpha; i++) hi *= 2;\n const fCrit = bisect((x) => fcdf(x, df1, df2), 1 - alpha, 0, hi);\n if (!Number.isFinite(fCrit)) return NaN;\n return ncf_sf(fCrit, df1, df2, lambda);\n}\n\nexport function powerCorrelation(r: number, n: number, alpha: number, tails: number): number {\n const zr = Math.atanh(r);\n const se = 1 / Math.sqrt(Math.max(1, n - 3));\n const zCrit = norminv(1 - alpha / tails);\n if (tails === 2) return normcdf(Math.abs(zr) / se - zCrit) + normcdf(-Math.abs(zr) / se - zCrit);\n // Magnitude convention \u2014 see powerTwoSample. A negative r with a one-tailed\n // test would otherwise return ~0.\n return normcdf(Math.abs(zr) / se - zCrit);\n}\n\nexport function powerChi2(w: number, n: number, alpha: number, df: number): number {\n const lambda = n * w * w;\n const chiCrit = chi2inv(1 - alpha, df);\n return 1 - ncchi2cdf(chiCrit, df, lambda);\n}\n\n// Cohen's f for ANOVA from group means + pooled within-SD.\n//\n// f = \u03C3_means / \u03C3_within, where \u03C3_means is the **population-style** SD of\n// the group means \u2014 i.e. divides by k, not (k\u22121) \u2014 treating the supplied\n// means as the entire population of cell means rather than a sample. This\n// matches Cohen (1988) and R's pwr::pwr.anova.test, which is what the\n// downstream power calculator is calibrated against. Callers that have a\n// sample SD of group means should multiply by \u221A((k\u22121)/k) before passing\n// in, or recompute from the raw means.\nexport function fFromGroupMeans(meansArr: number[], sd: number): number {\n if (!meansArr.length || sd <= 0) return 0;\n const grandMean = meansArr.reduce((a, b) => a + b, 0) / meansArr.length;\n const sigmaMeans = Math.sqrt(\n meansArr.reduce((s, m) => s + (m - grandMean) * (m - grandMean), 0) / meansArr.length\n );\n return sigmaMeans / sd;\n}\n", "// stats/format.ts \u2014 small zero-dep formatters for p-values and significance.\n//\n// Lives alone so `tests.ts` and `posthoc.ts` can both consume them without\n// drawing a cycle (selectCorrelation in tests.ts wants formatP, selectTest in\n// posthoc.ts wants formatP \u2014 both originally pulled it from the same global\n// surface).\n\n// Map a p-value to the 4-level significance stars used on plots.\n// Non-finite or missing p \u2192 empty string so callers can suppress the label.\nexport function pStars(p: number | null | undefined): string {\n // equiv-mutant: p == null is subsumed by !Number.isFinite(p) \u2014 null/undefined fail it too\n if (p == null || !Number.isFinite(p)) return \"\";\n if (p < 0.0001) return \"****\";\n if (p < 0.001) return \"***\";\n if (p < 0.01) return \"**\";\n if (p < 0.05) return \"*\";\n return \"ns\";\n}\n\n// Format a p-value for display next to a test statistic. Uses scientific\n// notation below 1e-3 (where fixed-point would round to 0) and keeps 3\n// significant digits otherwise.\nexport function formatP(p: number | null | undefined): string {\n // equiv-mutant: p == null is subsumed by !Number.isFinite(p) \u2014 null/undefined fail it too\n if (p == null || !Number.isFinite(p)) return \"\u2014\";\n if (p < 1e-4) return p.toExponential(1);\n if (p < 1e-3) return p.toExponential(2);\n return p.toFixed(4);\n}\n", "// stats/tests.ts \u2014 descriptive helpers + parametric / nonparametric tests +\n// effect sizes.\n//\n// Layout:\n// 3. Sample helpers \u2014 mean, variance, sd, rank-with-ties\n// 4. Shapiro-Wilk normality \u2014 Royston 1995, AS R94\n// 5. Levene / Brown-Forsythe equal-variance test\n// 6. Two-sample location \u2014 t-test (Student / Welch), Mann-Whitney U\n// 7. Two-sample effect sizes \u2014 Cohen's d, Hedges' g, rank-biserial\n// 8. k-sample location \u2014 one-way / Welch ANOVA, Kruskal-Wallis\n// 9. Correlation \u2014 Pearson r, Spearman \u03C1, Kendall \u03C4-b,\n// selectCorrelation auto-picker\n// 10. k-sample effect sizes \u2014 \u03B7\u00B2, \u03B5\u00B2\n\nimport { bisect, chi2cdf, fcdf_upper, gammaln, nctcdf, norminv, normsf, tcdf_upper } from \"./dist\";\nimport { formatP } from \"./format\";\nimport type {\n ANOVAResult,\n KendallResult,\n KruskalWallisResult,\n LeveneResult,\n MannWhitneyResult,\n PairwiseComplete,\n PearsonResult,\n RankWithTies,\n ShapiroWilkResult,\n SpearmanResult,\n TTestResult,\n} from \"./types\";\n\n// \u2500\u2500 3. Sample helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function sampleMean(x: number[]): number {\n const n = x.length;\n if (n === 0) return NaN;\n let s = 0;\n for (let i = 0; i < n; i++) s += x[i];\n return s / n;\n}\n\n// Sample variance with (n-1) denominator (Bessel-corrected). Welford's\n// online algorithm: one pass, no need to compute the mean separately, and\n// numerically more robust than the naive E[X\u00B2] \u2212 E[X]\u00B2 formula (which\n// catastrophically cancels when the data have a large offset). The classic\n// two-pass algorithm is comparable in accuracy on most inputs, but Welford\n// avoids a second loop and pre-computed mean, which is cleaner and slightly\n// faster on large arrays.\nexport function sampleVariance(x: number[]): number {\n const n = x.length;\n if (n < 2) return NaN;\n let mean = 0,\n M2 = 0;\n for (let i = 0; i < n; i++) {\n const d = x[i] - mean;\n mean += d / (i + 1);\n M2 += d * (x[i] - mean);\n }\n return M2 / (n - 1);\n}\n\nexport function sampleSD(x: number[]): number {\n return Math.sqrt(sampleVariance(x));\n}\n\n// Midrank assignment with ties \u2014 returns ranks[i] = 1-based rank of x[i],\n// averaging ranks for tied values. Also returns the sum of (t\u00B3\u2212t)/12 for each\n// tie group, needed as the tie-correction term in Mann-Whitney / Kruskal-Wallis.\nexport function rankWithTies(x: number[]): RankWithTies {\n const n = x.length;\n const idx: [number, number][] = x\n .map((v, i): [number, number] => [v, i])\n .sort((a, b) => a[0] - b[0]);\n const ranks = new Array(n);\n let tieCorrection = 0;\n let i = 0;\n while (i < n) {\n let j = i;\n while (j + 1 < n && idx[j + 1][0] === idx[i][0]) j++;\n const avgRank = (i + j + 2) / 2; // 1-based average of ranks [i+1 .. j+1]\n for (let k = i; k <= j; k++) ranks[idx[k][1]] = avgRank;\n const t = j - i + 1;\n if (t > 1) tieCorrection += t * t * t - t;\n i = j + 1;\n }\n return { ranks, tieCorrection };\n}\n\n// \u2500\u2500 4. Normality: Shapiro-Wilk (Royston 1995, AS R94) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function shapiroWilk(x: number[]): ShapiroWilkResult {\n const n = x.length;\n if (n < 3 || n > 5000) {\n return { W: NaN, p: NaN, error: \"Shapiro-Wilk requires 3 \u2264 n \u2264 5000\" };\n }\n const sorted = [...x].sort((a, b) => a - b);\n\n // Expected values of normal order statistics via Blom's approximation:\n // m_i \u2248 \u03A6\u207B\u00B9((i \u2212 3/8) / (n + 1/4))\n // These are the \"m\" vector in AS R94.\n const m = new Array(n);\n for (let i = 0; i < n; i++) {\n m[i] = norminv((i + 1 - 3 / 8) / (n + 1 / 4));\n }\n let mm = 0;\n for (let i = 0; i < n; i++) mm += m[i] * m[i];\n\n // Coefficients a_i (Royston 1992 analytical form, AS R94).\n const a = new Array(n);\n const u = 1 / Math.sqrt(n);\n const sqrtMm = Math.sqrt(mm);\n\n if (n === 3) {\n // Exact for n = 3: a_1 = \u221A(1/2), a_3 = \u2212a_1, a_2 = 0.\n a[0] = -Math.SQRT1_2;\n a[1] = 0;\n a[2] = Math.SQRT1_2;\n } else {\n const an =\n m[n - 1] / sqrtMm +\n 0.221157 * u -\n 0.147981 * u * u -\n 2.07119 * u * u * u +\n 4.434685 * u * u * u * u -\n 2.706056 * u * u * u * u * u;\n a[n - 1] = an;\n a[0] = -an;\n\n if (n >= 6) {\n const an1 =\n m[n - 2] / sqrtMm +\n 0.042981 * u -\n 0.293762 * u * u -\n 1.75246 * u * u * u +\n 5.682633 * u * u * u * u -\n 3.582633 * u * u * u * u * u;\n a[n - 2] = an1;\n a[1] = -an1;\n\n // Epsilon correction for middle coefficients (AS R94).\n const phi =\n (mm - 2 * m[n - 1] * m[n - 1] - 2 * m[n - 2] * m[n - 2]) /\n (1 - 2 * an * an - 2 * an1 * an1);\n const sqrtPhi = Math.sqrt(phi);\n for (let i = 2; i < n - 2; i++) a[i] = m[i] / sqrtPhi;\n } else {\n // n = 4 or 5: only tail coefficients, simpler correction.\n const phi = (mm - 2 * m[n - 1] * m[n - 1]) / (1 - 2 * an * an);\n const sqrtPhi = Math.sqrt(phi);\n for (let i = 1; i < n - 1; i++) a[i] = m[i] / sqrtPhi;\n }\n }\n\n // W = (\u03A3 a_i \u00B7 x_(i))\u00B2 / \u03A3 (x_i \u2212 x\u0304)\u00B2\n let xbar = 0;\n for (let i = 0; i < n; i++) xbar += sorted[i];\n xbar /= n;\n let num = 0,\n den = 0;\n for (let i = 0; i < n; i++) {\n num += a[i] * sorted[i];\n const d = sorted[i] - xbar;\n den += d * d;\n }\n if (den === 0) return { W: NaN, p: NaN, error: \"Zero variance\" };\n // Clamp to [0, 1] \u2014 floating-point can produce W = 1 + \u03B5 for a perfect\n // linear sample (e.g. n=3 [1,2,3]), which would break asin(\u221AW) below.\n const W = Math.max(0, Math.min(1, (num * num) / den));\n\n // P-value via Royston 1995 normalizing transform.\n let p: number;\n if (n === 3) {\n // Exact distribution at n=3: p = 6 \u00B7 (asin(\u221AW) \u2212 asin(\u221A(3/4))) / \u03C0\n const pi6 = 6 / Math.PI;\n const stqr = Math.asin(Math.sqrt(3 / 4));\n p = pi6 * (Math.asin(Math.sqrt(W)) - stqr);\n if (p < 0) p = 0;\n if (p > 1) p = 1;\n } else if (n <= 11) {\n // Polynomial in n for gamma and mu, log-transform on (gamma \u2212 ln(1\u2212W)).\n const gamma = -2.273 + 0.459 * n;\n const mu = 0.544 - 0.39978 * n + 0.025054 * n * n - 0.0006714 * n * n * n;\n const sigma = Math.exp(1.3822 - 0.77857 * n + 0.062767 * n * n - 0.0020322 * n * n * n);\n const y = -Math.log(gamma - Math.log(1 - W));\n const z = (y - mu) / sigma;\n p = normsf(z);\n } else {\n // n \u2265 12: log(1\u2212W) is approximately normal after transform.\n const lnN = Math.log(n);\n const mu = -1.5861 - 0.31082 * lnN - 0.083751 * lnN * lnN + 0.0038915 * lnN * lnN * lnN;\n const sigma = Math.exp(-0.4803 - 0.082676 * lnN + 0.0030302 * lnN * lnN);\n const y = Math.log(1 - W);\n const z = (y - mu) / sigma;\n p = normsf(z);\n }\n\n return { W, p };\n}\n\n// \u2500\u2500 5. Equal-variance tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function leveneTest(groups: number[][]): LeveneResult {\n if (groups.length < 2) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"\u22652 groups required\" };\n // A singleton group has median = its own value, so every absolute deviation\n // is exactly 0: it carries no dispersion information and would silently\n // inflate the error df. car::leveneTest requires n\u22652 per group, so drop the\n // undersized ones before computing the deviation ANOVA.\n const valid = groups.filter((g) => g.length >= 2);\n if (valid.length < 2) {\n return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"Not enough observations\" };\n }\n const k = valid.length;\n // Absolute deviations from the group median.\n const devs = valid.map((g) => {\n const s = [...g].sort((a, b) => a - b);\n const n = s.length;\n const med = n % 2 === 0 ? (s[n / 2 - 1] + s[n / 2]) / 2 : s[Math.floor(n / 2)];\n return g.map((v) => Math.abs(v - med));\n });\n // One-way ANOVA on deviations.\n let Ntot = 0;\n let grandSum = 0;\n for (const d of devs) {\n Ntot += d.length;\n for (const v of d) grandSum += v;\n }\n const grandMean = grandSum / Ntot;\n let ssBetween = 0,\n ssWithin = 0;\n for (const d of devs) {\n const n = d.length;\n if (n === 0) continue;\n let s = 0;\n for (const v of d) s += v;\n const m = s / n;\n ssBetween += n * (m - grandMean) * (m - grandMean);\n for (const v of d) ssWithin += (v - m) * (v - m);\n }\n const df1 = k - 1;\n const df2 = Ntot - k;\n if (ssWithin === 0) {\n return {\n F: NaN,\n df1,\n df2,\n p: NaN,\n error: \"Data are essentially constant (zero within-group dispersion)\",\n };\n }\n const F = ssBetween / df1 / (ssWithin / df2);\n const p = fcdf_upper(F, df1, df2);\n return { F, df1, df2, p };\n}\n\n// \u2500\u2500 6. Two-sample location tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function tTest(x: number[], y: number[], opts: { equalVar?: boolean } = {}): TTestResult {\n const equalVar = opts.equalVar !== false;\n const n1 = x.length,\n n2 = y.length;\n if (n1 < 2 || n2 < 2) {\n return { t: NaN, df: 0, p: NaN, error: \"Each group needs n\u22652\" };\n }\n const m1 = sampleMean(x),\n m2 = sampleMean(y);\n const v1 = sampleVariance(x),\n v2 = sampleVariance(y);\n if (v1 === 0 && v2 === 0) {\n return {\n t: NaN,\n df: NaN,\n p: NaN,\n mean1: m1,\n mean2: m2,\n var1: v1,\n var2: v2,\n n1,\n n2,\n error: \"Data are essentially constant (zero variance in both groups)\",\n };\n }\n let t: number, df: number;\n if (equalVar) {\n const sp2 = ((n1 - 1) * v1 + (n2 - 1) * v2) / (n1 + n2 - 2);\n const se = Math.sqrt(sp2 * (1 / n1 + 1 / n2));\n t = (m1 - m2) / se;\n df = n1 + n2 - 2;\n } else {\n const se = Math.sqrt(v1 / n1 + v2 / n2);\n t = (m1 - m2) / se;\n const num = (v1 / n1 + v2 / n2) ** 2;\n const den = (v1 / n1) ** 2 / (n1 - 1) + (v2 / n2) ** 2 / (n2 - 1);\n df = num / den;\n }\n const p = 2 * tcdf_upper(Math.abs(t), df);\n return { t, df, p, mean1: m1, mean2: m2, var1: v1, var2: v2, n1, n2 };\n}\n\nexport function mannWhitneyU(x: number[], y: number[]): MannWhitneyResult {\n const n1 = x.length,\n n2 = y.length;\n if (n1 < 1 || n2 < 1) return { U: NaN, z: NaN, p: NaN, error: \"Empty group\" };\n const all = x.concat(y);\n // All observations identical \u2192 the tie-corrected variance collapses to\n // exactly 0, the test is undefined, and an unguarded run reports z = 0,\n // p = 1 (\"not significant\") \u2014 a false negative. Mirror the kruskalWallis\n // guard and surface an error instead.\n let allTied = true;\n for (let i = 1; i < all.length; i++) {\n if (all[i] !== all[0]) {\n allTied = false;\n break;\n }\n }\n if (allTied) return { U: NaN, z: NaN, p: NaN, error: \"Data are essentially constant\" };\n const { ranks, tieCorrection } = rankWithTies(all);\n let R1 = 0;\n for (let i = 0; i < n1; i++) R1 += ranks[i];\n const U1 = R1 - (n1 * (n1 + 1)) / 2;\n const U2 = n1 * n2 - U1;\n const U = Math.min(U1, U2);\n const N = n1 + n2;\n const muU = (n1 * n2) / 2;\n // \u03C3_U\u00B2 with tie correction (Lehmann 1975):\n // \u03C3\u00B2 = n1\u00B7n2/12 \u00B7 [(N+1) \u2212 \u03A3(t\u00B3\u2212t) / (N(N\u22121))]\n const sigma2 = ((n1 * n2) / 12) * (N + 1 - tieCorrection / (N * (N - 1)));\n const sigmaU = Math.sqrt(sigma2);\n const diff = U1 - muU;\n let z: number;\n if (sigmaU === 0) z = 0;\n else if (diff > 0) z = (diff - 0.5) / sigmaU;\n else if (diff < 0) z = (diff + 0.5) / sigmaU;\n else z = 0;\n const p = 2 * normsf(Math.abs(z));\n return { U, U1, U2, z, p, n1, n2 };\n}\n\n// \u2500\u2500 7. Two-sample effect sizes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function cohenD(x: number[], y: number[]): number {\n const n1 = x.length,\n n2 = y.length;\n if (n1 < 2 || n2 < 2) return NaN;\n const m1 = sampleMean(x),\n m2 = sampleMean(y);\n const v1 = sampleVariance(x),\n v2 = sampleVariance(y);\n const sp2 = ((n1 - 1) * v1 + (n2 - 1) * v2) / (n1 + n2 - 2);\n if (sp2 === 0) return NaN;\n return (m1 - m2) / Math.sqrt(sp2);\n}\n\nexport function hedgesG(x: number[], y: number[]): number {\n const d = cohenD(x, y);\n if (!Number.isFinite(d)) return d;\n const df = x.length + y.length - 2;\n if (df < 1) return NaN;\n // Exact small-sample correction factor:\n // J(df) = \u0393(df/2) / (\u0393((df\u22121)/2) \u00B7 \u221A(df/2))\n const J = Math.exp(gammaln(df / 2) - gammaln((df - 1) / 2) - 0.5 * Math.log(df / 2));\n return d * J;\n}\n\n// Rank-biserial correlation from Mann-Whitney U (Kerby 2014).\nexport function rankBiserial(U1: number, n1: number, n2: number): number {\n if (n1 * n2 === 0) return NaN;\n return 1 - (2 * U1) / (n1 * n2);\n}\n\n// 95 % confidence interval for Cohen's d (or d_av) on two independent samples,\n// using the noncentral-t pivot of Cumming & Finch 2001.\nexport function cohenDCI(\n d: number,\n n1: number,\n n2: number,\n conf?: number\n): { lo: number; hi: number } {\n const c = conf == null ? 0.95 : conf;\n if (!Number.isFinite(d) || n1 < 2 || n2 < 2 || c <= 0 || c >= 1) {\n return { lo: NaN, hi: NaN };\n }\n const alpha = 1 - c;\n const seFactor = Math.sqrt(1 / n1 + 1 / n2);\n const tObs = d / seFactor;\n const df = n1 + n2 - 2;\n const halfRange = Math.abs(tObs) + 20;\n const negNct = (lam: number): number => -nctcdf(tObs, df, lam);\n const lambdaLo = bisect(negNct, -(1 - alpha / 2), -halfRange, halfRange);\n const lambdaHi = bisect(negNct, -alpha / 2, -halfRange, halfRange);\n if (!Number.isFinite(lambdaLo) || !Number.isFinite(lambdaHi)) {\n return { lo: NaN, hi: NaN };\n }\n return { lo: lambdaLo * seFactor, hi: lambdaHi * seFactor };\n}\n\n// \u2500\u2500 8. k-sample location tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function oneWayANOVA(groups: number[][]): ANOVAResult {\n const k = groups.length;\n if (k < 2) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"\u22652 groups required\" };\n let Ntot = 0;\n let grandSum = 0;\n for (const g of groups) {\n Ntot += g.length;\n for (const v of g) grandSum += v;\n }\n if (Ntot <= k) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"Not enough observations\" };\n const grandMean = grandSum / Ntot;\n let ssBetween = 0,\n ssWithin = 0;\n for (const g of groups) {\n const n = g.length;\n if (n === 0) continue;\n let s = 0;\n for (const v of g) s += v;\n const m = s / n;\n ssBetween += n * (m - grandMean) * (m - grandMean);\n for (const v of g) ssWithin += (v - m) * (v - m);\n }\n const df1 = k - 1;\n const df2 = Ntot - k;\n if (ssWithin === 0) {\n return {\n F: NaN,\n df1,\n df2,\n p: NaN,\n ssBetween,\n ssWithin,\n grandMean,\n error: \"Data are essentially constant (zero within-group dispersion)\",\n };\n }\n const F = ssBetween / df1 / (ssWithin / df2);\n const p = fcdf_upper(F, df1, df2);\n return { F, df1, df2, p, ssBetween, ssWithin, grandMean };\n}\n\nexport function welchANOVA(groups: number[][]): ANOVAResult {\n const k = groups.length;\n if (k < 2) return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"\u22652 groups required\" };\n const ns = groups.map((g) => g.length);\n if (ns.some((n) => n < 2)) {\n return { F: NaN, df1: 0, df2: 0, p: NaN, error: \"Each group needs n\u22652\" };\n }\n const means = groups.map(sampleMean);\n const vars_ = groups.map(sampleVariance);\n if (vars_.some((v) => v === 0)) {\n return {\n F: NaN,\n df1: k - 1,\n df2: NaN,\n p: NaN,\n error: \"Data are essentially constant (zero variance in at least one group)\",\n };\n }\n const w = vars_.map((v, i) => ns[i] / v);\n const Wsum = w.reduce((a, b) => a + b, 0);\n const m = w.reduce((a, wi, i) => a + wi * means[i], 0) / Wsum;\n let num = 0;\n for (let i = 0; i < k; i++) num += w[i] * (means[i] - m) * (means[i] - m);\n num /= k - 1;\n let h = 0;\n for (let i = 0; i < k; i++) {\n const term = 1 - w[i] / Wsum;\n h += (term * term) / (ns[i] - 1);\n }\n const den = 1 + ((2 * (k - 2)) / (k * k - 1)) * h;\n const F = num / den;\n const df1 = k - 1;\n const df2 = (k * k - 1) / (3 * h);\n const p = fcdf_upper(F, df1, df2);\n return { F, df1, df2, p };\n}\n\nexport function kruskalWallis(groups: number[][]): KruskalWallisResult {\n const k = groups.length;\n if (k < 2) return { H: NaN, df: 0, p: NaN, error: \"\u22652 groups required\" };\n const all: number[] = [];\n const owner: number[] = [];\n for (let i = 0; i < k; i++) {\n for (const v of groups[i]) {\n all.push(v);\n owner.push(i);\n }\n }\n const N = all.length;\n if (N <= k) return { H: NaN, df: 0, p: NaN, error: \"Not enough observations\" };\n let allTied = true;\n for (let i = 1; i < N; i++) {\n if (all[i] !== all[0]) {\n allTied = false;\n break;\n }\n }\n if (allTied) {\n return { H: NaN, df: k - 1, p: NaN, error: \"Data are essentially constant\" };\n }\n const { ranks, tieCorrection } = rankWithTies(all);\n const R = new Array(k).fill(0);\n for (let i = 0; i < N; i++) R[owner[i]] += ranks[i];\n let sumR2n = 0;\n for (let i = 0; i < k; i++) sumR2n += (R[i] * R[i]) / groups[i].length;\n let H = (12 / (N * (N + 1))) * sumR2n - 3 * (N + 1);\n const C = 1 - tieCorrection / (N * N * N - N);\n if (C > 0) H /= C;\n const df = k - 1;\n const p = 1 - chi2cdf(H, df);\n return { H, df, p };\n}\n\n// \u2500\u2500 9. Correlation (paired bivariate) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// Drop rows with non-finite x or y; return the cleaned pair.\nfunction _pairwiseComplete(x: number[], y: number[]): PairwiseComplete {\n const n = Math.min(x.length, y.length);\n const xs: number[] = [];\n const ys: number[] = [];\n for (let i = 0; i < n; i++) {\n const xv = x[i],\n yv = y[i];\n if (Number.isFinite(xv) && Number.isFinite(yv)) {\n xs.push(xv);\n ys.push(yv);\n }\n }\n return { xs, ys, n: xs.length };\n}\n\nexport function pearsonCorrelation(\n x: number[],\n y: number[],\n opts: { conf?: number } = {}\n): PearsonResult {\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return {\n r: NaN,\n t: NaN,\n df: 0,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: \"Need \u22653 complete pairs\",\n };\n }\n const mx = sampleMean(xs);\n const my = sampleMean(ys);\n let S2x = 0,\n S2y = 0,\n Sxy = 0;\n for (let i = 0; i < n; i++) {\n const dx = xs[i] - mx;\n const dy = ys[i] - my;\n S2x += dx * dx;\n S2y += dy * dy;\n Sxy += dx * dy;\n }\n if (S2x === 0 || S2y === 0) {\n return {\n r: NaN,\n t: NaN,\n df: n - 2,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: \"Data are essentially constant (zero variance in x or y)\",\n };\n }\n let r = Sxy / Math.sqrt(S2x * S2y);\n if (r > 1) r = 1;\n if (r < -1) r = -1;\n const df = n - 2;\n const conf = opts.conf == null ? 0.95 : opts.conf;\n const ci = { lo: NaN, hi: NaN };\n if (n >= 4) {\n const z = Math.atanh(r);\n const se = 1 / Math.sqrt(n - 3);\n const zcrit = norminv(1 - (1 - conf) / 2);\n ci.lo = Math.tanh(z - zcrit * se);\n ci.hi = Math.tanh(z + zcrit * se);\n }\n const oneMinusR2 = Math.max(0, 1 - r * r);\n const t = oneMinusR2 === 0 ? (r > 0 ? Infinity : -Infinity) : r * Math.sqrt(df / oneMinusR2);\n const p = oneMinusR2 === 0 ? 0 : 2 * tcdf_upper(Math.abs(t), df);\n return { r, t, df, p, n, ci };\n}\n\nexport function spearmanCorrelation(\n x: number[],\n y: number[],\n opts: { conf?: number } = {}\n): SpearmanResult {\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return {\n rho: NaN,\n t: NaN,\n df: 0,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: \"Need \u22653 complete pairs\",\n };\n }\n const { ranks: rx } = rankWithTies(xs);\n const { ranks: ry } = rankWithTies(ys);\n // Reuse the Pearson implementation on the rank vectors.\n const pearsonOnRanks = pearsonCorrelation(rx, ry, opts);\n if (pearsonOnRanks.error) {\n return {\n rho: NaN,\n t: NaN,\n df: n - 2,\n p: NaN,\n n,\n ci: { lo: NaN, hi: NaN },\n error: pearsonOnRanks.error,\n };\n }\n const rho = pearsonOnRanks.r;\n const df = n - 2;\n const oneMinusRho2 = Math.max(0, 1 - rho * rho);\n const t =\n oneMinusRho2 === 0 ? (rho > 0 ? Infinity : -Infinity) : rho * Math.sqrt(df / oneMinusRho2);\n const p = oneMinusRho2 === 0 ? 0 : 2 * tcdf_upper(Math.abs(t), df);\n const conf = opts.conf == null ? 0.95 : opts.conf;\n const ci = { lo: NaN, hi: NaN };\n if (n >= 4) {\n const z = Math.atanh(rho);\n const se = Math.sqrt((1 + (rho * rho) / 2) / (n - 3));\n const zcrit = norminv(1 - (1 - conf) / 2);\n ci.lo = Math.tanh(z - zcrit * se);\n ci.hi = Math.tanh(z + zcrit * se);\n }\n return { rho, t, df, p, n, ci };\n}\n\nexport function kendallTau(\n x: number[],\n y: number[],\n opts: Record = {}\n): KendallResult {\n void opts;\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return { tau: NaN, z: NaN, p: NaN, n, S: 0, error: \"Need \u22653 complete pairs\" };\n }\n let S = 0;\n for (let i = 0; i < n - 1; i++) {\n const xi = xs[i],\n yi = ys[i];\n for (let j = i + 1; j < n; j++) {\n const dx = xs[j] - xi;\n const dy = ys[j] - yi;\n if (dx === 0 || dy === 0) continue;\n S += dx > 0 === dy > 0 ? 1 : -1;\n }\n }\n const tieGroups = (arr: number[]): number[] => {\n // equiv-mutant: .slice() copy keeps xs intact; in-place sort is harmless only as xs is unused after\n const sorted = arr.slice().sort((a, b) => a - b);\n const groups: number[] = [];\n let i = 0;\n while (i < n) {\n let j = i;\n while (j + 1 < n && sorted[j + 1] === sorted[i]) j++;\n const t = j - i + 1;\n if (t > 1) groups.push(t);\n i = j + 1;\n }\n return groups;\n };\n const tx = tieGroups(xs);\n const ty = tieGroups(ys);\n const sumPairs = (g: number[]): number => g.reduce((s, t) => s + (t * (t - 1)) / 2, 0);\n const n0 = (n * (n - 1)) / 2;\n const n1 = sumPairs(tx);\n const n2 = sumPairs(ty);\n if (n0 - n1 <= 0 || n0 - n2 <= 0) {\n return {\n tau: NaN,\n z: NaN,\n p: NaN,\n n,\n S,\n error: \"Data are essentially constant (all ties in x or y)\",\n };\n }\n const tau = S / Math.sqrt((n0 - n1) * (n0 - n2));\n let varS = (n * (n - 1) * (2 * n + 5)) / 18;\n for (const t of tx) varS -= (t * (t - 1) * (2 * t + 5)) / 18;\n for (const t of ty) varS -= (t * (t - 1) * (2 * t + 5)) / 18;\n if (tx.length && ty.length && n >= 3) {\n const sx3 = tx.reduce((s, t) => s + t * (t - 1) * (t - 2), 0);\n const sy3 = ty.reduce((s, t) => s + t * (t - 1) * (t - 2), 0);\n varS += (sx3 * sy3) / (9 * n * (n - 1) * (n - 2));\n const sx2 = tx.reduce((s, t) => s + t * (t - 1), 0);\n const sy2 = ty.reduce((s, t) => s + t * (t - 1), 0);\n varS += (sx2 * sy2) / (2 * n * (n - 1));\n }\n if (varS <= 0) {\n return { tau, z: NaN, p: NaN, n, S };\n }\n const z = S / Math.sqrt(varS);\n const p = 2 * normsf(Math.abs(z));\n return { tau, z, p, n, S };\n}\n\n// Diagnostic + recommendation for a paired scatter dataset.\ninterface SelectCorrelationAxis {\n axis: \"x\" | \"y\";\n n: number;\n W: number | null;\n p: number | null;\n normal: boolean | null;\n note?: string;\n}\ninterface SelectCorrelationResult {\n n: number;\n normality: SelectCorrelationAxis[];\n allNormal: boolean;\n recommendation: { test: \"pearson\"; reason: string };\n suggestion?: { test: \"spearman\"; reason: string };\n}\n\nexport function selectCorrelation(\n x: number[],\n y: number[],\n opts: { alphaNormality?: number } = {}\n): SelectCorrelationResult {\n const alphaN = opts.alphaNormality != null ? opts.alphaNormality : 0.05;\n const { xs, ys, n } = _pairwiseComplete(x, y);\n if (n < 3) {\n return {\n n,\n normality: [],\n allNormal: false,\n recommendation: { test: \"pearson\", reason: \"Need \u22653 complete pairs to test a correlation.\" },\n };\n }\n const swCheck = (arr: number[], axis: \"x\" | \"y\"): SelectCorrelationAxis => {\n if (arr.length < 3) {\n return { axis, n: arr.length, W: null, p: null, normal: null, note: \"n<3\" };\n }\n const sw = shapiroWilk(arr);\n if (sw.error) {\n return { axis, n: arr.length, W: null, p: null, normal: null, note: sw.error };\n }\n return { axis, n: arr.length, W: sw.W, p: sw.p, normal: sw.p >= alphaN };\n };\n const normality: SelectCorrelationAxis[] = [swCheck(xs, \"x\"), swCheck(ys, \"y\")];\n const allKnownNormal = normality.every((r) => r.normal === true);\n const flagged = normality.filter((r) => r.normal === false);\n const test = \"pearson\" as const;\n const baseDefault =\n \"Default pick: Pearson product-moment correlation. Pearson is the most powerful test when both axes are approximately normal; Spearman and Kendall stay available as rank-based alternatives.\";\n const swNarrative = ((): string => {\n if (flagged.length === 0 && allKnownNormal) {\n return `Shapiro-Wilk did not reject normality on x or y at \u03B1 = ${alphaN}.`;\n }\n if (flagged.length > 0) {\n const labels = flagged\n .map((r) => `${r.axis} (W=${(r.W as number).toFixed(3)}, p=${formatP(r.p)})`)\n .join(\", \");\n return `Shapiro-Wilk flagged ${labels} as non-normal at \u03B1 = ${alphaN}.`;\n }\n return \"Shapiro-Wilk could not run on one or both axes (n < 3).\";\n })();\n let suggestion: { test: \"spearman\"; reason: string } | undefined;\n let suggestionNarrative = \"\";\n if (flagged.length > 0) {\n suggestion = {\n test: \"spearman\",\n reason:\n \"Shapiro-Wilk flagged at least one axis as non-normal. Spearman ranks both axes before correlating, which is more robust to heavy tails and outliers than Pearson.\",\n };\n suggestionNarrative =\n \" If the non-normality looks substantive (heavy tails, strong skew, ordinal data), consider switching to Spearman \u03C1 from the test dropdown; for very small or heavily-tied samples Kendall \u03C4-b is a further alternative.\";\n }\n const overrideHint =\n \" You can override this pick from the stats panel's per-test dropdown; the trace below shows the diagnostics the recommendation is based on.\";\n const out: SelectCorrelationResult = {\n n,\n normality,\n allNormal: allKnownNormal,\n recommendation: {\n test,\n reason: `${baseDefault} ${swNarrative}${suggestionNarrative}${overrideHint}`,\n },\n };\n if (suggestion) out.suggestion = suggestion;\n return out;\n}\n\n// \u2500\u2500 10. k-sample effect sizes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function etaSquared(groups: number[][]): number {\n const a = oneWayANOVA(groups);\n if (a.error) return NaN;\n const ssTotal = (a.ssBetween as number) + (a.ssWithin as number);\n return ssTotal === 0 ? 0 : (a.ssBetween as number) / ssTotal;\n}\n\nexport function epsilonSquared(groups: number[][]): number {\n const kw = kruskalWallis(groups);\n if (kw.error) return NaN;\n let N = 0;\n for (const g of groups) N += g.length;\n return N > 1 ? kw.H / (N - 1) : NaN;\n}\n", "// stats/posthoc.ts \u2014 studentized-range distribution, all-pairs post-hocs,\n// p-adjust, compact-letter-display, and the auto test-picker (`selectTest`).\n//\n// Layout:\n// 10. Studentized range distribution \u2014 _wprob, ptukey, qtukey\n// 11. Post-hoc tests \u2014 Tukey HSD, Games-Howell, Dunn, BH\n// 12. Compact letter display\n// 13. Automatic test selection \u2014 selectTest\n\nimport { _gaussLegendre, chi2inv, gammaln, normcdf, normsf } from \"./dist\";\nimport { formatP } from \"./format\";\nimport {\n leveneTest,\n oneWayANOVA,\n rankWithTies,\n sampleMean,\n sampleVariance,\n shapiroWilk,\n} from \"./tests\";\nimport type {\n DunnPair,\n DunnResult,\n GamesHowellPair,\n GamesHowellResult,\n NormalityResult,\n SelectTestKind,\n SelectTestPostHoc,\n SelectTestResult,\n TukeyPair,\n TukeyResult,\n} from \"./types\";\n\n// \u2500\u2500 10. Studentized range distribution \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n// P(range of k standard normals > w). Tail-accurate companion to `_wprob`.\nexport function _wprob_upper(w: number, k: number): number {\n if (w <= 0) return 1;\n const gl = _gaussLegendre(48);\n const peak = -w / 2;\n const lo = Math.min(-8, peak - 8);\n const hi = Math.max(8, peak + 8);\n const half = (hi - lo) / 2,\n mid = (hi + lo) / 2;\n const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const u = mid + half * gl.nodes[i];\n const phiU = invSqrt2Pi * Math.exp(-0.5 * u * u);\n const a = normsf(u);\n const aMinusB = normsf(u + w);\n if (a <= 0 || aMinusB <= 0) continue;\n const b = a - aMinusB;\n let geoSum = 0;\n let term = Math.pow(a, k - 2);\n const ratio = b / a;\n for (let j = 0; j < k - 1; j++) {\n geoSum += term;\n term *= ratio;\n }\n const integrand = k * phiU * aMinusB * geoSum;\n sum += half * gl.weights[i] * integrand;\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// P(range of k standard normals \u2264 w).\nexport function _wprob(w: number, k: number): number {\n if (w <= 0) return 0;\n const gl = _gaussLegendre(48);\n const lo = -8,\n hi = 8;\n const half = (hi - lo) / 2,\n mid = (hi + lo) / 2;\n const invSqrt2Pi = 1 / Math.sqrt(2 * Math.PI);\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const u = mid + half * gl.nodes[i];\n const phiU = invSqrt2Pi * Math.exp(-0.5 * u * u);\n let diff: number;\n // equiv-mutant: the `else if (u+w<=0)` branch is byte-identical to `else` \u2014 the split is vestigial\n if (u >= 0) {\n diff = normsf(u) - normsf(u + w);\n } else if (u + w <= 0) {\n diff = normcdf(u + w) - normcdf(u);\n } else {\n diff = normcdf(u + w) - normcdf(u);\n }\n if (diff <= 0) continue;\n const term = k * phiU * Math.pow(diff, k - 1);\n sum += half * gl.weights[i] * term;\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// P(Q \u2264 q | k groups, df error degrees of freedom)\nexport function ptukey(q: number, k: number, df: number): number {\n if (q <= 0) return 0;\n if (k < 2 || df < 1) return NaN;\n const gl = _gaussLegendre(48);\n const chiLo = chi2inv(1e-10, df);\n const chiHi = chi2inv(1 - 1e-10, df);\n const yLo = 0.5 * Math.log(Math.max(chiLo, 1e-300) / df);\n const yHi = 0.5 * Math.log(chiHi / df);\n const halfDf = df / 2;\n const logConst = Math.log(2) + halfDf * Math.log(halfDf) - gammaln(halfDf);\n const halfW = (yHi - yLo) / 2;\n const midW = (yHi + yLo) / 2;\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const y = midW + halfW * gl.nodes[i];\n const s = Math.exp(y);\n const logFS = logConst + df * y - (df * s * s) / 2;\n const fSds = Math.exp(logFS);\n if (!Number.isFinite(fSds) || fSds === 0) continue;\n sum += halfW * gl.weights[i] * fSds * _wprob(q * s, k);\n }\n return Math.max(0, Math.min(1, sum));\n}\n\n// P(Q > q | k groups, df error degrees of freedom).\nexport function ptukey_upper(q: number, k: number, df: number): number {\n if (q <= 0) return 1;\n if (k < 2 || df < 1) return NaN;\n const gl = _gaussLegendre(48);\n const chiLo = chi2inv(1e-10, df);\n const chiHi = chi2inv(1 - 1e-10, df);\n const yLo = 0.5 * Math.log(Math.max(chiLo, 1e-300) / df);\n const yHi = 0.5 * Math.log(chiHi / df);\n const halfDf = df / 2;\n const logConst = Math.log(2) + halfDf * Math.log(halfDf) - gammaln(halfDf);\n const halfW = (yHi - yLo) / 2;\n const midW = (yHi + yLo) / 2;\n let sum = 0;\n for (let i = 0; i < 48; i++) {\n const y = midW + halfW * gl.nodes[i];\n const s = Math.exp(y);\n const logFS = logConst + df * y - (df * s * s) / 2;\n const fSds = Math.exp(logFS);\n if (!Number.isFinite(fSds) || fSds === 0) continue;\n sum += halfW * gl.weights[i] * fSds * _wprob_upper(q * s, k);\n }\n return Math.max(0, Math.min(1, sum));\n}\n\nexport function qtukey(p: number, k: number, df: number): number {\n if (p <= 0) return 0;\n if (p >= 1) return Infinity;\n if (k < 2 || df < 1) return NaN;\n let lo = 0.01,\n hi = 100;\n for (let i = 0; i < 20 && ptukey(hi, k, df) < p; i++) {\n lo = hi;\n hi *= 2;\n }\n if (ptukey(hi, k, df) < p) return NaN;\n for (let i = 0; i < 200; i++) {\n const mid = (lo + hi) / 2;\n if (ptukey(mid, k, df) < p) lo = mid;\n else hi = mid;\n if (hi - lo < 1e-7 * (Math.abs(lo) + 1)) break;\n }\n return (lo + hi) / 2;\n}\n\n// \u2500\u2500 11. Post-hoc tests \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function tukeyHSD(groups: number[][], opts: { alpha?: number } = {}): TukeyResult {\n const alpha = opts.alpha != null ? opts.alpha : 0.05;\n const k = groups.length;\n if (k < 2) return { pairs: [], error: \"\u22652 groups required\" };\n const anova = oneWayANOVA(groups);\n if (anova.error) return { pairs: [], error: anova.error };\n const dfErr = anova.df2;\n const mse = (anova.ssWithin as number) / dfErr;\n // equiv-mutant: mse === 0 is unreachable \u2014 oneWayANOVA already errors above when ssWithin is 0\n if (mse === 0) {\n return {\n pairs: [],\n k,\n df: dfErr,\n mse,\n error: \"Cannot compute Tukey HSD \u2014 zero within-group variance (data essentially constant)\",\n };\n }\n const means = groups.map(sampleMean);\n const ns = groups.map((g) => g.length);\n const qCrit = qtukey(1 - alpha, k, dfErr);\n const inPathologicalEnvelope = dfErr <= 2 && 1 - alpha >= 0.95 && k >= 10;\n let qCritWarning: string | null = null;\n if (!Number.isFinite(qCrit)) {\n qCritWarning = `Tukey HSD CI bounds unavailable \u2014 qtukey(${1 - alpha}, k=${k}, df=${dfErr}) returned NaN (outside the bracket-expansion envelope). p-values are still reliable; the per-pair lwr / upr are NaN.`;\n } else if (inPathologicalEnvelope) {\n qCritWarning = `Tukey HSD CI bounds approximate \u2014 qtukey(${1 - alpha}, k=${k}, df=${dfErr}) lies in the studentized-range design envelope (df \u2264 2 with k \u2265 10 at 1\u2212\u03B1 \u2265 0.95), where the reference (R, SciPy) disagree by ~5 % with each other. p-values stay reliable; the per-pair lwr / upr are accurate to within ~5 %. Consider Games-Howell (Welch-aware, less sensitive to small df) if this matches your real data.`;\n }\n const pairs: TukeyPair[] = [];\n for (let i = 0; i < k - 1; i++) {\n for (let j = i + 1; j < k; j++) {\n const diff = means[j] - means[i];\n const se = Math.sqrt((mse * (1 / ns[i] + 1 / ns[j])) / 2);\n const q = Math.abs(diff) / se;\n const p = ptukey_upper(q, k, dfErr);\n const margin = qCrit * se;\n pairs.push({\n i,\n j,\n diff,\n se,\n q,\n p,\n lwr: diff - margin,\n upr: diff + margin,\n });\n }\n }\n const result: TukeyResult = { pairs, k, df: dfErr, mse };\n if (qCritWarning) result.warning = qCritWarning;\n return result;\n}\n\nexport function gamesHowell(groups: number[][]): GamesHowellResult {\n const k = groups.length;\n if (k < 2) return { pairs: [], error: \"\u22652 groups required\" };\n const means = groups.map(sampleMean);\n const vars_ = groups.map(sampleVariance);\n const ns = groups.map((g) => g.length);\n // Each group needs n \u2265 2: a singleton has no sample variance\n // (sampleVariance returns NaN), which slips past the zero-variance guard\n // below \u2014 `NaN === 0` is false \u2014 and would surface as a silent NaN p.\n if (ns.some((n) => n < 2)) {\n return { pairs: [], k, error: \"Each group needs n\u22652 for Games-Howell\" };\n }\n if (vars_.some((v) => v === 0)) {\n return {\n pairs: [],\n k,\n error: \"Cannot compute Games-Howell \u2014 at least one group has zero variance\",\n };\n }\n const pairs: GamesHowellPair[] = [];\n for (let i = 0; i < k - 1; i++) {\n for (let j = i + 1; j < k; j++) {\n const vi = vars_[i] / ns[i];\n const vj = vars_[j] / ns[j];\n const se = Math.sqrt(vi + vj);\n const diff = means[j] - means[i];\n const q = Math.abs(diff) / (se / Math.SQRT2);\n const num = (vi + vj) * (vi + vj);\n const den = (vi * vi) / (ns[i] - 1) + (vj * vj) / (ns[j] - 1);\n // The Welch\u2013Satterthwaite df is bounded below by min(n\u1D62\u22121, n\u2C7C\u22121) \u2265 1\n // once every group has n \u2265 2; clamp to absorb sub-ULP rounding so\n // ptukey_upper never receives a df < 1 (which it answers with NaN).\n const df = Math.max(1, num / den);\n const p = ptukey_upper(q, k, df);\n pairs.push({ i, j, diff, se, q, df, p });\n }\n }\n return { pairs, k };\n}\n\nexport function bhAdjust(ps: number[]): number[] {\n const m = ps.length;\n const order: [number, number][] = ps\n .map((p, i): [number, number] => [p, i])\n .sort((a, b) => a[0] - b[0]);\n const adj = new Array(m);\n let running = 1;\n for (let rank = m; rank >= 1; rank--) {\n const [p, origIdx] = order[rank - 1];\n const q = (p * m) / rank;\n running = Math.min(running, q);\n adj[origIdx] = Math.min(1, running);\n }\n return adj;\n}\n\nexport function dunnTest(groups: number[][]): DunnResult {\n const k = groups.length;\n if (k < 2) return { pairs: [], error: \"\u22652 groups required\" };\n const all: number[] = [];\n const owner: number[] = [];\n for (let i = 0; i < k; i++) {\n for (const v of groups[i]) {\n all.push(v);\n owner.push(i);\n }\n }\n const N = all.length;\n // Mirror the kruskalWallis precondition (Dunn is its post-hoc): reject\n // when there are too few observations to rank, or any group is empty \u2014\n // either case otherwise yields a silent NaN p-value (an N\u22121 = 0 divisor\n // in sigma2, or 0/0 mean ranks for an empty group).\n if (N <= k || groups.some((g) => g.length < 1)) {\n return { pairs: [], error: \"Not enough observations\" };\n }\n const { ranks, tieCorrection } = rankWithTies(all);\n const meanR = new Array(k).fill(0);\n const ns = groups.map((g) => g.length);\n const sumR = new Array(k).fill(0);\n for (let idx = 0; idx < N; idx++) sumR[owner[idx]] += ranks[idx];\n for (let i = 0; i < k; i++) meanR[i] = sumR[i] / ns[i];\n const sigma2 = (N * (N + 1)) / 12 - tieCorrection / (12 * (N - 1));\n const rawPs: number[] = [];\n const pairs: DunnPair[] = [];\n for (let i = 0; i < k - 1; i++) {\n for (let j = i + 1; j < k; j++) {\n const se = Math.sqrt(sigma2 * (1 / ns[i] + 1 / ns[j]));\n const z = (meanR[i] - meanR[j]) / se;\n const p = 2 * normsf(Math.abs(z));\n pairs.push({ i, j, z, p });\n rawPs.push(p);\n }\n }\n const adj = bhAdjust(rawPs);\n for (let i = 0; i < pairs.length; i++) pairs[i].pAdj = adj[i];\n return { pairs, method: \"Benjamini-Hochberg\" };\n}\n\n// \u2500\u2500 12. Compact letter display \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function compactLetterDisplay(\n pairs: { i: number; j: number; p: number; pAdj?: number | null }[],\n k: number,\n alpha = 0.05\n): string[] {\n if (k <= 0) return [];\n let letters: Set[] = [new Set(Array.from({ length: k }, (_, i) => i))];\n for (const pr of pairs) {\n const p = pr.pAdj != null ? pr.pAdj : pr.p;\n if (!Number.isFinite(p) || p >= alpha) continue;\n const { i, j } = pr;\n const newLetters: Set[] = [];\n for (const L of letters) {\n if (L.has(i) && L.has(j)) {\n const L1 = new Set(L);\n L1.delete(i);\n const L2 = new Set(L);\n L2.delete(j);\n if (L1.size > 0) newLetters.push(L1);\n if (L2.size > 0) newLetters.push(L2);\n } else {\n newLetters.push(L);\n }\n }\n const filtered: Set[] = [];\n for (const L of newLetters) {\n let absorbed = false;\n for (const M of newLetters) {\n if (L === M) continue;\n if (L.size < M.size) {\n let sub = true;\n for (const v of L) {\n if (!M.has(v)) {\n sub = false;\n break;\n }\n }\n if (sub) {\n absorbed = true;\n break;\n }\n }\n }\n if (!absorbed && !filtered.some((F) => F.size === L.size && [...F].every((v) => L.has(v)))) {\n filtered.push(L);\n }\n }\n letters = filtered;\n }\n letters.sort((A, B) => Math.min(...A) - Math.min(...B));\n const labels = \"abcdefghijklmnopqrstuvwxyz\";\n const out: string[] = Array.from({ length: k }, () => \"\");\n for (let li = 0; li < letters.length; li++) {\n const lbl = labels[li] || `[${li}]`;\n for (const g of letters[li]) out[g] += lbl;\n }\n return out;\n}\n\n// \u2500\u2500 13. Automatic test selection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function selectTest(\n groups: number[][],\n opts: { alphaNormality?: number; alphaVariance?: number } = {}\n): SelectTestResult {\n const alphaN = opts.alphaNormality != null ? opts.alphaNormality : 0.05;\n const alphaV = opts.alphaVariance != null ? opts.alphaVariance : 0.05;\n const k = groups.length;\n if (k < 2) {\n return { error: \"\u22652 groups required\", k };\n }\n\n const normality: NormalityResult[] = groups.map((g, i) => {\n if (g.length < 3) {\n return { group: i, n: g.length, W: null, p: null, normal: null, note: \"n<3\" };\n }\n const sw = shapiroWilk(g);\n if (sw.error) {\n return { group: i, n: g.length, W: null, p: null, normal: null, note: sw.error };\n }\n if (!Number.isFinite(sw.W) || !Number.isFinite(sw.p)) {\n // Non-finite W/p (e.g. a group carrying \u00B1Infinity) \u2014 report it as\n // \"unknown\" rather than letting `NaN >= alphaN` collapse to a false\n // \"non-normal\" verdict.\n return { group: i, n: g.length, W: null, p: null, normal: null, note: \"non-finite\" };\n }\n return { group: i, n: g.length, W: sw.W, p: sw.p, normal: sw.p >= alphaN };\n });\n const allKnownNormal = normality.every((r) => r.normal === true);\n const flagged = normality.filter((r) => r.normal === false);\n const anyFlagged = flagged.length > 0;\n\n const lev = leveneTest(groups);\n // A non-finite F/p (a group carrying \u00B1Infinity) is as unusable as an\n // explicit error \u2014 treat it the same so the narrative never asserts a\n // variance verdict derived from NaN.\n const levUsable = !lev.error && Number.isFinite(lev.F) && Number.isFinite(lev.p);\n const equalVar = levUsable ? lev.p >= alphaV : null;\n\n const test: SelectTestKind = k === 2 ? \"welchT\" : \"welchANOVA\";\n const postHoc: SelectTestPostHoc = k === 2 ? null : \"gamesHowell\";\n\n const baseDefault =\n k === 2\n ? \"Default pick: Welch's t-test. Welch's t is the recommended default for two independent groups (Rasch, Kubinger & Moder 2011; Zimmerman 2004) \u2014 it does not assume equal variances and matches Student's t closely when variances are in fact equal.\"\n : \"Default pick: Welch's ANOVA with Games-Howell post-hoc. Welch's ANOVA is the recommended default for k \u2265 3 independent groups (Delacre et al. 2019; Rasch et al. 2011) \u2014 it does not assume equal variances across groups and matches one-way ANOVA closely when variances are in fact equal.\";\n\n const swNarrative = ((): string => {\n if (flagged.length === 0 && allKnownNormal) {\n return \"Shapiro-Wilk did not reject normality in any group at \u03B1 = \" + alphaN + \".\";\n }\n if (flagged.length > 0) {\n const labels = flagged\n .map((r) => `group ${r.group + 1} (W=${(r.W as number).toFixed(3)}, p=${formatP(r.p)})`)\n .join(\", \");\n return `Shapiro-Wilk flagged ${flagged.length} of ${k} group(s) as non-normal at \u03B1 = ${alphaN}: ${labels}.`;\n }\n return \"Shapiro-Wilk could not run on every group (n < 3 in at least one).\";\n })();\n\n const levNarrative = !levUsable\n ? `Levene (Brown-Forsythe) could not run: ${lev.error || \"non-finite result\"}.`\n : equalVar === false\n ? `Levene (Brown-Forsythe) rejected equal variances (F=${lev.F.toFixed(3)}, p=${formatP(lev.p)}); Welch handles this without further intervention.`\n : `Levene (Brown-Forsythe) did not reject equal variances (F=${lev.F.toFixed(3)}, p=${formatP(lev.p)}); Welch is still the safe default and matches the equal-variance test closely here.`;\n\n let suggestion: SelectTestResult[\"suggestion\"];\n let suggestionNarrative = \"\";\n if (anyFlagged) {\n suggestion = {\n test: k === 2 ? \"mannWhitney\" : \"kruskalWallis\",\n postHoc: k === 2 ? null : \"dunn\",\n reason:\n \"Shapiro-Wilk flagged at least one group as non-normal. Pl\u00F6ttr keeps Welch as the default (pre-screening with SW is a known anti-pattern) but a rank-based test may be more appropriate for genuinely heavy-tailed data.\",\n };\n suggestionNarrative = ` If the non-normality looks substantive (heavy tails, strong skew, ordinal data), consider switching to ${suggestion.test === \"mannWhitney\" ? \"Mann-Whitney U\" : \"Kruskal-Wallis + Dunn (BH)\"} from the test dropdown.`;\n }\n\n const overrideHint =\n \" You can override this pick from the stats panel's per-test dropdown; the trace below shows the diagnostics the recommendation is based on.\";\n\n const reason = `${baseDefault} ${swNarrative} ${levNarrative}${suggestionNarrative}${overrideHint}`;\n\n const out: SelectTestResult = {\n k,\n normality,\n allNormal: allKnownNormal,\n levene: !levUsable\n ? { error: lev.error || \"non-finite result\" }\n : { F: lev.F, df1: lev.df1, df2: lev.df2, p: lev.p, equalVar },\n recommendation: { test, postHoc, reason },\n };\n if (suggestion) out.suggestion = suggestion;\n return out;\n}\n", "// _core/color.ts \u2014 color helpers, palettes, and role colours.\n//\n// \u2500\u2500 Color helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function hexToRgb(hex: string): [number, number, number] {\n const h = hex.replace(\"#\", \"\");\n return [parseInt(h.slice(0, 2), 16), parseInt(h.slice(2, 4), 16), parseInt(h.slice(4, 6), 16)];\n}\n\nexport function rgbToHex(r: number, g: number, b: number): string {\n return (\n \"#\" +\n [r, g, b]\n .map((v) =>\n Math.max(0, Math.min(255, Math.round(v)))\n .toString(16)\n .padStart(2, \"0\")\n )\n .join(\"\")\n );\n}\n\nexport function shadeColor(hex: string, factor: number): string {\n const [r, g, b] = hexToRgb(hex);\n if (factor > 0)\n return rgbToHex(r + (255 - r) * factor, g + (255 - g) * factor, b + (255 - b) * factor);\n return rgbToHex(r * (1 + factor), g * (1 + factor), b * (1 + factor));\n}\n\nexport function getPointColors(baseColor: string, nSources: number): string[] {\n if (nSources <= 1) return [baseColor];\n const colors: string[] = [];\n for (let i = 0; i < nSources; i++) {\n const t = nSources === 1 ? 0 : Math.min(1, i / (nSources - 1));\n colors.push(shadeColor(baseColor, -0.4 + t * 0.7));\n }\n return colors;\n}\n\n// \u2500\u2500 Color palette (Okabe-Ito colorblind-safe; Wong 2011, Nature Methods) \u2500\u2500\u2500\u2500\n\nexport const PALETTE: readonly string[] = [\n \"#E69F00\",\n \"#56B4E9\",\n \"#009E73\",\n \"#F0E442\",\n \"#0072B2\",\n \"#D55E00\",\n \"#CC79A7\",\n \"#000000\",\n \"#88CCEE\",\n \"#AA4499\",\n];\n\n// \u2500\u2500 Continuous colour palettes (shared by scatter + heatmap) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport const COLOR_PALETTES: Record = {\n viridis: [\"#440154\", \"#3b528b\", \"#21908c\", \"#5dc963\", \"#fde725\"],\n plasma: [\"#0d0887\", \"#7e03a8\", \"#cc4778\", \"#f89540\", \"#f0f921\"],\n magma: [\"#000004\", \"#3b0f70\", \"#8c2981\", \"#de4968\", \"#fe9f6d\", \"#fcfdbf\"],\n inferno: [\"#000004\", \"#420a68\", \"#932667\", \"#dd513a\", \"#fca50a\", \"#fcffa4\"],\n cividis: [\"#00204c\", \"#213d6b\", \"#555b6c\", \"#7b7a77\", \"#a59c74\", \"#d3c064\", \"#ffe945\"],\n rdbu: [\"#b2182b\", \"#ef8a62\", \"#fddbc7\", \"#f7f7f7\", \"#d1e5f0\", \"#67a9cf\", \"#2166ac\"],\n bwr: [\"#0000ff\", \"#8888ff\", \"#ffffff\", \"#ff8888\", \"#ff0000\"],\n rdylbu: [\"#a50026\", \"#f46d43\", \"#fee090\", \"#ffffbf\", \"#e0f3f8\", \"#74add1\", \"#313695\"],\n reds: [\"#fff5f0\", \"#fcbba1\", \"#fb6a4a\", \"#cb181d\", \"#67000d\"],\n blues: [\"#f7fbff\", \"#c6dbef\", \"#6baed6\", \"#2171b5\", \"#08306b\"],\n greens: [\"#f7fcf5\", \"#c7e9c0\", \"#74c476\", \"#238b45\", \"#00441b\"],\n spectral: [\"#9e0142\", \"#f46d43\", \"#fee08b\", \"#e6f598\", \"#66c2a5\", \"#3288bd\", \"#5e4fa2\"],\n};\n\n// Diverging palettes should be anchored at 0 when rendered (symmetric vmin/vmax).\nexport const DIVERGING_PALETTES: Set = new Set([\"rdbu\", \"bwr\", \"rdylbu\", \"spectral\"]);\n\nexport function interpolateColor(stops: string[], t: number): string {\n if (!stops || stops.length === 0) return \"#000000\";\n if (stops.length === 1) return stops[0];\n if (t <= 0 || !Number.isFinite(t)) return stops[0];\n if (t >= 1) return stops[stops.length - 1];\n const seg = (stops.length - 1) * t;\n const i = Math.floor(seg);\n const f = seg - i;\n const [r1, g1, b1] = hexToRgb(stops[i]);\n const [r2, g2, b2] = hexToRgb(stops[i + 1]);\n return rgbToHex(r1 + (r2 - r1) * f, g1 + (g2 - g1) * f, b1 + (b2 - b1) * f);\n}\n\n// \u2500\u2500 Role colours \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Chrome styling lives in `tools/components.css` via `dv-*` classes; only\n// `roleColors` remains here because the column-role chips read it directly.\n\nexport const roleColors: Record = {\n group: \"#0072B2\",\n value: \"#009E73\",\n filter: \"#E69F00\",\n ignore: \"var(--border-strong)\",\n};\n", "// _core/numeric.ts \u2014 locale-aware numeric detection + seeded PRNG.\n//\n// Unicode-aware normalisation before numeric parsing. Excel on macOS, PDFs,\n// Word docs, and statistical-paper copy-paste all routinely embed non-ASCII\n// minus-ish and whitespace-ish characters into number cells. These are\n// visually identical to the ASCII forms but break `Number()` parsing \u2014\n// `Number(\"\u22125\")` (U+2212 minus sign) is `NaN`, not `-5`. Normalise them\n// before regex + Number() so we don't silently drop legitimate values.\n\nconst UNICODE_MINUS_CHARS = /[\\u2212\\u2013\\u2014]/g; // \u2212 (minus) \u2013 (en-dash) \u2014 (em-dash)\nconst UNICODE_SPACE_CHARS = /[\\u00A0\\u2009\\u202F]/g; // NBSP, thin space, narrow NBSP\n\nexport function normalizeNumericString(v: unknown): unknown {\n if (typeof v !== \"string\") return v;\n return v.replace(UNICODE_MINUS_CHARS, \"-\").replace(UNICODE_SPACE_CHARS, \"\");\n}\n\n// Returns true only for strings that are entirely a valid finite number.\n// Rejects:\n// - alphanumeric (\"6wpi\", \"12abc\", \"0xFF\"),\n// - Number() specials (\"Infinity\", \"NaN\"),\n// - overflow strings that coerce to \u00B1Infinity (\"1e999\"),\n// - leading-zero integer IDs (\"007\", \"000123\") that silently lose their\n// zero-padded form when coerced \u2014 well plates, accession numbers, and\n// LIMS codes commonly use this shape.\n// Accepts normalised Unicode variants: \"\u22125\" (U+2212), \"\u20135\" (en-dash), and\n// numbers containing NBSP / thin spaces from copy-paste.\nexport function isNumericValue(v: unknown): boolean {\n if (typeof v !== \"string\") return false;\n const s = (normalizeNumericString(v) as string).trim();\n if (s.length === 0) return false;\n if (/^-?0\\d/.test(s)) return false;\n if (!/^-?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$/.test(s)) return false;\n return Number.isFinite(Number(s));\n}\n\n// Convenience: normalise + parse. Callers that already know `isNumericValue`\n// is true should route through this instead of `Number(v)` directly so\n// Unicode-minus / NBSP values parse correctly.\nexport function toNumericValue(v: unknown): number {\n return isNumericValue(v) ? Number((normalizeNumericString(v) as string).trim()) : NaN;\n}\n\n// \u2500\u2500 Seeded random \u2014 Park-Miller LCG \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function seededRandom(seed: number): () => number {\n let s = seed;\n // Burn one LCG step before returning so the first observable output is\n // properly mixed. Park-Miller's first step on a small seed produces\n // roughly `seed * 16807 / 2^31`, so adjacent small seeds (e.g. the\n // per-source `gi*1000 + si*100 + 42` pattern the boxplot jitter uses)\n // would otherwise all return near-identical small first values \u2014 a\n // single-point source would then render its dot at the leftmost edge\n // of the jitter band. One warm-up advances the state into a\n // properly-mixed region of the cycle while keeping the function\n // deterministic (re-renders don't re-jitter).\n s = (s * 16807) % 2147483647;\n return () => {\n s = (s * 16807) % 2147483647;\n return s / 2147483647;\n };\n}\n", "// _core/csv.ts \u2014 CSV / TSV parsing, tokenizing, separator detection, format\n// reshaping, and formula-injection scanning.\n//\nimport { isNumericValue, toNumericValue } from \"./numeric\";\n\n// \u2500\u2500 Separator detection \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Picks the delimiter most likely to produce a consistent column count. The\n// naive \"count occurrences across the first 2 kB\" heuristic fails on TSVs\n// whose header has commas in free-text (\"Sample, Description, Notes\") \u2014\n// those header-level commas can out-count the tabs in data rows and flip\n// the detection to `,`, collapsing 20 data columns to 1.\n//\n// Strategy: for each candidate (`\\t`, `;`, `,`), measure how uniform the\n// per-line count is across the first ~50 non-empty lines. A real delimiter\n// partitions every line into the SAME number of columns, so its per-line\n// count has low variance and a median \u2265 1. A separator that sneaks into\n// quoted text or headers only produces inconsistent per-line counts.\n\nexport function autoDetectSep(text: string, override: string = \"\"): string | RegExp {\n if (override !== \"\") return override;\n const h = text.slice(0, 2000);\n const lines = h.split(/\\r?\\n/).filter((l) => l.trim() !== \"\");\n const head = lines.slice(0, Math.min(50, lines.length));\n if (head.length === 0) return /\\s+/;\n\n const CANDIDATES = [\"\\t\", \";\", \",\"];\n const scores = CANDIDATES.map((sep) => {\n const countPerLine = head.map((line) => {\n let n = 0;\n let inQuoted = false;\n for (let i = 0; i < line.length; i++) {\n const ch = line[i];\n if (ch === '\"') {\n inQuoted = !inQuoted;\n continue;\n }\n if (!inQuoted && ch === sep) n++;\n }\n return n;\n });\n const total = countPerLine.reduce((a, b) => a + b, 0);\n const sorted = countPerLine.slice().sort((a, b) => a - b);\n const median = sorted[Math.floor(sorted.length / 2)];\n const mean = total / countPerLine.length;\n let variance = 0;\n if (countPerLine.length > 1) {\n for (const n of countPerLine) variance += (n - mean) * (n - mean);\n variance /= countPerLine.length - 1;\n }\n const cv = mean > 0 ? Math.sqrt(variance) / mean : Infinity;\n return { sep, total, median, cv };\n });\n\n const viable = scores.filter((s) => s.median >= 1);\n if (viable.length === 0) {\n const best = scores.reduce((a, b) => (b.total > a.total ? b : a));\n return best.total === 0 ? /\\s+/ : best.sep;\n }\n viable.sort((a, b) => a.cv - b.cv || b.total - a.total);\n return viable[0].sep;\n}\n\n// \u2500\u2500 Delimited-text tokenizer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// RFC 4180-style state machine. Handles quoted fields with embedded\n// separators, embedded `\\n` / `\\r\\n` inside quoted cells, escaped `\"\"`\n// pairs, a leading BOM, and mixed CRLF/LF line endings. A `\"` in the\n// middle of an unquoted field is preserved literally so `5\"` as an inch\n// measurement survives \u2014 only a leading `\"` at the start of a field opens\n// a quoted run.\n\nexport function tokenizeDelimited(text: string, sep: string | RegExp): string[][] {\n if (typeof text !== \"string\" || text.length === 0) return [];\n let s = text;\n if (s.charCodeAt(0) === 0xfeff) s = s.slice(1);\n if (typeof sep !== \"string\") {\n return s\n .split(/\\r?\\n/)\n .filter((l) => l.trim() !== \"\")\n .map((l) => l.trim().split(sep));\n }\n const rows: string[][] = [];\n let row: string[] = [];\n let field = \"\";\n let inQuoted = false;\n let fieldStarted = false;\n const flushRow = (): void => {\n row.push(field);\n if (!(row.length === 1 && row[0] === \"\")) {\n for (let c = 0; c < row.length; c++) row[c] = row[c].trim();\n rows.push(row);\n }\n row = [];\n field = \"\";\n fieldStarted = false;\n };\n const n = s.length;\n let i = 0;\n while (i < n) {\n const ch = s[i];\n if (inQuoted) {\n if (ch === '\"') {\n if (i + 1 < n && s[i + 1] === '\"') {\n field += '\"';\n i += 2;\n } else {\n inQuoted = false;\n i++;\n }\n } else {\n field += ch;\n i++;\n }\n } else if (!fieldStarted && ch === '\"') {\n inQuoted = true;\n fieldStarted = true;\n i++;\n } else if (ch === sep) {\n row.push(field);\n field = \"\";\n fieldStarted = false;\n i++;\n } else if (ch === \"\\n\" || ch === \"\\r\") {\n flushRow();\n if (ch === \"\\r\" && i + 1 < n && s[i + 1] === \"\\n\") i += 2;\n else i++;\n } else {\n field += ch;\n fieldStarted = true;\n i++;\n }\n }\n if (field !== \"\" || row.length > 0) {\n flushRow();\n }\n for (let r = 0; r < rows.length; r++) {\n for (let c = 0; c < rows[r].length; c++) rows[r][c] = rows[r][c].trim();\n }\n return rows;\n}\n\n// \u2500\u2500 Decimal comma fix (per-column) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function fixDecimalCommas(\n text: string,\n sep: string | RegExp\n): { text: string; commaFixed: boolean; count: number } {\n if (typeof text !== \"string\" || text.length === 0) {\n return { text, commaFixed: false, count: 0 };\n }\n if (typeof sep !== \"string\") return { text, commaFixed: false, count: 0 };\n if (sep === \",\") return { text, commaFixed: false, count: 0 };\n if (sep === \"\") return { text, commaFixed: false, count: 0 };\n const rows = tokenizeDelimited(text, sep);\n if (rows.length < 2) return { text, commaFixed: false, count: 0 };\n const nCols = Math.max(...rows.map((r) => r.length));\n const decimalCommaCols = new Set();\n for (let c = 0; c < nCols; c++) {\n let numericAsIs = 0;\n let numericIfFixed = 0;\n let hasComma = 0;\n let nonEmpty = 0;\n let anyNonThousandish = false;\n for (let r = 1; r < rows.length; r++) {\n const v = rows[r][c];\n if (v == null || v === \"\") continue;\n nonEmpty++;\n if (v.indexOf(\",\") !== -1) {\n hasComma++;\n const last = v.lastIndexOf(\",\");\n const after = v.slice(last + 1);\n const m = after.match(/^\\d+/);\n if (!m || m[0].length !== 3) anyNonThousandish = true;\n }\n if (isNumericValue(v)) numericAsIs++;\n if (isNumericValue(v.replace(/,/g, \".\"))) numericIfFixed++;\n }\n if (nonEmpty > 0 && hasComma > 0 && numericIfFixed > numericAsIs && anyNonThousandish) {\n decimalCommaCols.add(c);\n }\n }\n if (decimalCommaCols.size === 0) return { text, commaFixed: false, count: 0 };\n let count = 0;\n for (let r = 0; r < rows.length; r++) {\n for (const c of decimalCommaCols) {\n const v = rows[r][c];\n if (v && v.indexOf(\",\") !== -1) {\n const fixed = v.replace(/,/g, \".\");\n if (isNumericValue(fixed)) {\n rows[r][c] = fixed;\n count++;\n }\n }\n }\n }\n if (count === 0) return { text, commaFixed: false, count: 0 };\n const q = (cell: string): string => {\n if (\n cell.indexOf(sep as string) !== -1 ||\n cell.indexOf('\"') !== -1 ||\n cell.indexOf(\"\\n\") !== -1 ||\n cell.indexOf(\"\\r\") !== -1\n ) {\n return '\"' + cell.replace(/\"/g, '\"\"') + '\"';\n }\n return cell;\n };\n const rebuilt = rows.map((r) => r.map(q).join(sep as string)).join(\"\\n\");\n return { text: rebuilt, commaFixed: true, count };\n}\n\n// \u2500\u2500 Parsing helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function detectHeader(rows: string[][]): boolean {\n if (rows.length < 2) return true;\n const a = rows[0].filter((v) => !isNumericValue(v) && v.trim() !== \"\").length;\n const b = rows[1].filter((v) => !isNumericValue(v) && v.trim() !== \"\").length;\n if (a > b) return true;\n if (a === b && a > 0) {\n let reps = 0;\n for (let i = 1; i < Math.min(rows.length, 20); i++)\n for (let c = 0; c < rows[0].length; c++)\n if (rows[i][c] && rows[i][c].trim() === rows[0][c].trim()) reps++;\n return reps < rows[0].length;\n }\n return a > 0;\n}\n\nexport interface FormulaInjectionWarning {\n count: number;\n headers: Array<{ idx: number; value: string }>;\n cells: Array<{ row: number; col: number; header: string | null; value: string }>;\n}\n\nexport interface ParseRawResult {\n headers: string[];\n rows: string[][];\n hasHeader: boolean;\n injectionWarnings: FormulaInjectionWarning | null;\n}\n\nexport function parseRaw(text: string, sepOv: string = \"\"): ParseRawResult {\n const sep = autoDetectSep(text, sepOv);\n const all = tokenizeDelimited(text, sep);\n if (all.length < 1) return { headers: [], rows: [], hasHeader: false, injectionWarnings: null };\n const mx = Math.max(...all.map((r) => r.length));\n const pad = all.map((r) => {\n while (r.length < mx) r.push(\"\");\n return r;\n });\n const hh = detectHeader(pad);\n let headers: string[], rows: string[][];\n if (hh) {\n headers = pad[0];\n rows = pad.slice(1);\n } else {\n headers = pad[0].map((_, i) => `Col_${i + 1}`);\n rows = pad;\n }\n const scan = scanForFormulaInjection(headers, rows);\n return {\n headers,\n rows,\n hasHeader: !!hh,\n injectionWarnings: scan.count > 0 ? scan : null,\n };\n}\n\nexport type ColumnRole = \"group\" | \"value\" | \"filter\" | \"ignore\";\n\nexport function guessColumnType(vals: string[]): ColumnRole {\n const ne = vals.filter((v) => v != null && v !== \"\");\n if (ne.length === 0) return \"ignore\";\n if (ne.filter((v) => isNumericValue(v)).length / ne.length > 0.8) {\n const u = new Set(ne);\n if (u.size <= 12 && u.size < ne.length * 0.3) {\n const allIntegers = ne.every((v) => {\n const n = Number(String(v).trim());\n return Number.isFinite(n) && Number.isInteger(n);\n });\n if (allIntegers) return \"group\";\n }\n return \"value\";\n }\n const u = new Set(ne);\n if (u.size <= 20 && u.size < ne.length * 0.5) return \"group\";\n return \"filter\";\n}\n\nexport function detectWideFormat(headers: string[], rows: string[][]): boolean {\n if (headers.length < 2 || rows.length < 2) return false;\n const numericCols = headers.map((_, ci) => {\n const vals = rows.map((r) => r[ci]).filter((v) => v !== \"\");\n return vals.length > 0 && vals.filter((v) => isNumericValue(v)).length / vals.length > 0.8;\n });\n return numericCols.every(Boolean);\n}\n\nexport interface ParseWideMatrixResult {\n rowLabels: string[];\n colLabels: string[];\n matrix: number[][];\n warnings: { nonNumeric: number };\n injectionWarnings: FormulaInjectionWarning | null;\n}\n\nexport function parseWideMatrix(text: string, sepOv: string = \"\"): ParseWideMatrixResult {\n const parsed = parseRaw(text, sepOv);\n const { headers, rows, injectionWarnings } = parsed;\n if (headers.length < 2 || rows.length < 1) {\n return {\n rowLabels: [],\n colLabels: [],\n matrix: [],\n warnings: { nonNumeric: 0 },\n injectionWarnings: null,\n };\n }\n const colLabels = headers.slice(1);\n const rowLabels: string[] = [];\n const matrix: number[][] = [];\n let nonNumeric = 0;\n rows.forEach((r) => {\n const label = r[0] == null ? \"\" : String(r[0]);\n const values: number[] = new Array(colLabels.length);\n for (let ci = 0; ci < colLabels.length; ci++) {\n const raw = r[ci + 1];\n if (raw == null || raw === \"\") {\n values[ci] = NaN;\n } else if (isNumericValue(raw)) {\n values[ci] = toNumericValue(raw);\n } else {\n values[ci] = NaN;\n nonNumeric++;\n }\n }\n rowLabels.push(label);\n matrix.push(values);\n });\n return { rowLabels, colLabels, matrix, warnings: { nonNumeric }, injectionWarnings };\n}\n\n// \u2500\u2500 Unified data parsing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport interface ParseDataResult {\n headers: string[];\n data: (number | null)[][];\n rawData: string[][];\n injectionWarnings: FormulaInjectionWarning | null;\n}\n\nexport function parseData(text: string, sepOv: string = \"\"): ParseDataResult {\n const sep = autoDetectSep(text, sepOv);\n const all = tokenizeDelimited(text, sep);\n if (all.length < 2) return { headers: [], data: [], rawData: [], injectionWarnings: null };\n const headers = all[0];\n const nCols = headers.length;\n const data: (number | null)[][] = [];\n const rawData: string[][] = [];\n for (let i = 1; i < all.length; i++) {\n const rawVals = all[i].slice();\n while (rawVals.length < nCols) rawVals.push(\"\");\n if (rawVals.every((v) => v === \"\")) continue;\n data.push(rawVals.map((s) => (isNumericValue(s) ? toNumericValue(s) : null)));\n rawData.push(rawVals);\n }\n const scan = scanForFormulaInjection(headers, rawData);\n return { headers, data, rawData, injectionWarnings: scan.count > 0 ? scan : null };\n}\n\nexport function dataToColumns(data: (number | null)[][], nCols: number): number[][] {\n const columns: number[][] = Array.from({ length: nCols }, () => []);\n for (const row of data) {\n for (let c = 0; c < nCols; c++) {\n if (row[c] != null) columns[c].push(row[c] as number);\n }\n }\n return columns;\n}\n\n// \u2500\u2500 Wide / long format helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function wideToLong(\n headers: string[],\n rows: string[][]\n): { headers: string[]; rows: string[][]; skipped: number } {\n const longRows: string[][] = [];\n let skipped = 0;\n rows.forEach((r) => {\n headers.forEach((h, ci) => {\n if (r[ci] !== \"\" && isNumericValue(r[ci])) {\n longRows.push([h, r[ci]]);\n } else {\n skipped++;\n }\n });\n });\n return { headers: [\"Group\", \"Value\"], rows: longRows, skipped };\n}\n\nexport function reshapeWide(\n rows: string[][],\n gi: number,\n vi: number\n): { headers: string[]; rows: string[][]; unlabelled: number } {\n const g: Record = {};\n let unlabelled = 0;\n rows.forEach((r) => {\n const raw = r[gi];\n if (raw === \"\" || raw == null) unlabelled++;\n const k = raw || \"?\";\n if (!g[k]) g[k] = [];\n g[k].push(r[vi]);\n });\n const vals = Object.values(g);\n if (vals.length === 0) return { headers: [], rows: [], unlabelled };\n const mx = Math.max(...vals.map((v) => v.length));\n const names = Object.keys(g);\n const w: string[][] = [];\n for (let i = 0; i < mx; i++) w.push(names.map((n) => (g[n][i] != null ? g[n][i] : \"\")));\n return { headers: names, rows: w, unlabelled };\n}\n\n// \u2500\u2500 Set-membership parsing (Venn / UpSet) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function parseSetData(\n headers: string[],\n rows: string[][]\n): { setNames: string[]; sets: Map> } {\n const sets = new Map>();\n for (let ci = 0; ci < headers.length; ci++) {\n const s = new Set();\n for (const r of rows) {\n const v = (r[ci] || \"\").trim();\n if (v) s.add(v);\n }\n if (s.size > 0) sets.set(headers[ci], s);\n }\n const setNames = [...sets.keys()];\n return { setNames, sets };\n}\n\nexport function parseLongFormatSets(\n headers: string[],\n rows: string[][]\n): { setNames: string[]; sets: Map> } {\n if (!headers || headers.length !== 2) {\n throw new Error(\"Long format requires exactly 2 columns (item, set).\");\n }\n const sets = new Map>();\n for (const r of rows) {\n const item = (r[0] || \"\").trim();\n const setName = (r[1] || \"\").trim();\n if (!item || !setName) continue;\n if (!sets.has(setName)) sets.set(setName, new Set());\n sets.get(setName)!.add(item);\n }\n const setNames = [...sets.keys()];\n return { setNames, sets };\n}\n\n// \u2500\u2500 CSV building + formula-injection scanning \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Cells whose first character is one of these get a leading single-quote\n// before the CSV escape. Excel / LibreOffice / Google Sheets all treat `=`,\n// `+`, `-`, `@` (and a leading tab / CR before any of those) as a formula\n// trigger; the prepended `'` tells the spreadsheet engine to treat the cell\n// as text. This is the standard OWASP CSV-injection mitigation.\n\nconst _CSV_FORMULA_LEAD = /^[=+\\-@\\t\\r]/;\n\nfunction _isFormulaInjection(s: string): boolean {\n return _CSV_FORMULA_LEAD.test(s) && !isNumericValue(s);\n}\n\nfunction _escapeCsvCell(v: unknown): string {\n let s = String(v);\n if (_isFormulaInjection(s)) s = \"'\" + s;\n return `\"${s.replace(/\"/g, '\"\"')}\"`;\n}\n\nexport function buildCsvString(headers: unknown[], rows: unknown[][]): string {\n return [\n headers.map(_escapeCsvCell).join(\",\"),\n ...rows.map((r) => r.map(_escapeCsvCell).join(\",\")),\n ].join(\"\\n\");\n}\n\nexport function scanForFormulaInjection(\n headers: unknown[] | null | undefined,\n rows: unknown[][] | null | undefined,\n opts?: { cap?: number }\n): FormulaInjectionWarning {\n const cap = (opts && opts.cap) || 8;\n const out: FormulaInjectionWarning = { count: 0, headers: [], cells: [] };\n if (Array.isArray(headers)) {\n for (let i = 0; i < headers.length; i++) {\n const v = headers[i];\n if (typeof v !== \"string\" || !_isFormulaInjection(v)) continue;\n out.count++;\n if (out.headers.length < cap) out.headers.push({ idx: i, value: v });\n }\n }\n if (Array.isArray(rows)) {\n for (let r = 0; r < rows.length; r++) {\n const row = rows[r];\n if (!Array.isArray(row)) continue;\n for (let c = 0; c < row.length; c++) {\n const v = row[c];\n if (typeof v !== \"string\" || !_isFormulaInjection(v)) continue;\n out.count++;\n if (out.cells.length < cap) {\n out.cells.push({\n row: r,\n col: c,\n header: headers && headers[c] != null ? String(headers[c]) : null,\n value: v,\n });\n }\n }\n }\n }\n return out;\n}\n", "// _core/svg-export.ts \u2014 SVG export pipeline + `Pl\u00F6ttr v` attribution\n// band + `svgSafeId` NCName sanitiser.\n//\n// Sanitize an arbitrary string into an SVG-safe id fragment so exported\n// values are valid NCNames and show up as readable group\n// names in Inkscape's Objects panel / XML editor. Non-alphanumerics\n// become hyphens, runs are collapsed, edges trimmed, and a leading digit\n// is prefixed with \"_\".\nexport function svgSafeId(s: string | null | undefined): string {\n if (s == null) return \"unnamed\";\n const cleaned = String(s)\n .replace(/[^A-Za-z0-9_.-]+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n if (!cleaned) return \"unnamed\";\n return /^[A-Za-z_]/.test(cleaned) ? cleaned : \"_\" + cleaned;\n}\n\n// Height of the reserved bottom band added to every export so the\n// `Pl\u00F6ttr v\u2026` attribution sits outside the plot area. Used by both\n// `appendPlottrAttribution` (which extends the viewBox) and the PNG\n// downloader (which sizes its raster canvas to the post-watermark\n// dimensions, since the live SVG element still has the pre-watermark\n// viewBox).\nexport const PLOTTR_ATTRIBUTION_PAD = 14;\n\n// \u2500\u2500 SVG export mutators \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n//\n// Charts that paint to canvas for performance (currently only the volcano\n// data layer above POINT_RASTERIZE_THRESHOLD) need to substitute a vector\n// representation in the *exported* file, otherwise the user downloads a\n// fuzzy embedded PNG instead of a crisp SVG. The chart registers a\n// `mutator(clone)` callback against its live SVG element via\n// `registerSvgExportMutator`; `buildExportSvg` invokes it on the clone\n// before attribution is appended, after the standard style / shape-\n// rendering scrub. The mutator owns the swap (typically: remove the\n// raster , append elements built from the same render\n// records the canvas painted from).\n//\n// The registry is a WeakMap so an unmounting chart whose SVG element goes\n// out of scope releases its entry without explicit cleanup \u2014 and the\n// optional explicit `unregisterSvgExportMutator` lets a chart that\n// transitions out of raster mode clear its registration immediately\n// rather than wait for GC.\n\ntype ExportMutator = (svgClone: SVGElement) => void;\n// The registry lives on `globalThis` rather than module scope so that two\n// separately-bundled copies of `_core/svg-export.ts` (e.g. the SPA bundle\n// and a tool's standalone test bundle) share one WeakMap. Otherwise a\n// chart registers into closure-A and the export call reads from closure-B,\n// missing the mutator entirely.\nconst _gMut = globalThis as { __plottrSvgMutators?: WeakMap | null };\nif (_gMut.__plottrSvgMutators === undefined) {\n _gMut.__plottrSvgMutators =\n typeof WeakMap === \"function\" ? new WeakMap() : null;\n}\nconst _svgExportMutators: WeakMap | null =\n _gMut.__plottrSvgMutators as WeakMap | null;\n\nexport function registerSvgExportMutator(svgEl: SVGElement, mutator: ExportMutator): void {\n if (!_svgExportMutators || !svgEl || typeof mutator !== \"function\") return;\n _svgExportMutators.set(svgEl, mutator);\n}\n\nexport function unregisterSvgExportMutator(svgEl: SVGElement): void {\n if (!_svgExportMutators || !svgEl) return;\n _svgExportMutators.delete(svgEl);\n}\n\n// Build a clone of `svgEl` ready for export and append the permanent\n// `Pl\u00F6ttr v` attribution band at the bottom. Returns the cloned\n// element \u2014 callers can serialize it to a string or read its\n// updated `viewBox` / `width` / `height` attributes.\n//\n// Three pre-watermark transformations:\n// 1. Strip the root inline `style=\"max-width:100%;\u2026\"` so Inkscape's\n// CSS engine doesn't collapse the computed viewport on `height: auto`.\n// 2. Strip every `shape-rendering=\"crispEdges\"` attribute (heatmap cells\n// hit Inkscape \u22651.1's cairo-renderer bug otherwise).\n// 3. Run any chart-registered export mutator (see registry comment above).\n//\n// The watermark itself extends the viewBox (and `height` attribute, if\n// present) downward by PLOTTR_ATTRIBUTION_PAD px so the plot area, axes\n// and existing margins stay pixel-identical \u2014 only the canvas grows.\nexport function buildExportSvg(svgEl: SVGElement): SVGElement {\n const clone = svgEl.cloneNode(true) as SVGElement;\n clone.removeAttribute(\"style\");\n clone.querySelectorAll(\"[shape-rendering]\").forEach((el) => {\n el.removeAttribute(\"shape-rendering\");\n });\n const mutator = _svgExportMutators ? _svgExportMutators.get(svgEl) : null;\n if (typeof mutator === \"function\") {\n try {\n mutator(clone);\n } catch (err) {\n if (typeof console !== \"undefined\" && console.error) {\n console.error(\"[plottr] SVG export mutator failed:\", err);\n }\n }\n }\n appendPlottrAttribution(clone);\n return clone;\n}\n\nexport function serializeSvgForExport(svgEl: SVGElement): string {\n return new XMLSerializer().serializeToString(buildExportSvg(svgEl));\n}\n\n// Append the `` wrapper containing a small\n// italic `Pl\u00F6ttr v` text to the bottom-right of the SVG canvas.\n// Mutates the passed-in element \u2014 pass a clone, not the live DOM node.\n// Idempotent: a second call removes the prior attribution before\n// re-appending, so the canvas can't grow twice.\nexport function appendPlottrAttribution(svgEl: SVGElement): void {\n if (!svgEl || typeof svgEl.setAttribute !== \"function\") return;\n const prior = svgEl.querySelector(\"#plottr-attribution\");\n const hadPrior = !!(prior && prior.parentNode === svgEl);\n if (hadPrior && prior) prior.parentNode!.removeChild(prior);\n\n const vbParts = (svgEl.getAttribute(\"viewBox\") || \"\").split(/[\\s,]+/).map(parseFloat);\n let vbX = 0;\n let vbY = 0;\n let vbW = 0;\n let vbH = 0;\n if (vbParts.length >= 4 && vbParts.every((n) => Number.isFinite(n))) {\n vbX = vbParts[0];\n vbY = vbParts[1];\n vbW = vbParts[2];\n vbH = vbParts[3];\n } else {\n const wAttr = parseFloat(svgEl.getAttribute(\"width\") || \"\");\n const hAttr = parseFloat(svgEl.getAttribute(\"height\") || \"\");\n vbW = Number.isFinite(wAttr) ? wAttr : 0;\n vbH = Number.isFinite(hAttr) ? hAttr : 0;\n }\n if (hadPrior) vbH -= PLOTTR_ATTRIBUTION_PAD;\n if (!(vbW > 0) || !(vbH > 0)) return;\n\n const newH = vbH + PLOTTR_ATTRIBUTION_PAD;\n svgEl.setAttribute(\"viewBox\", `${vbX} ${vbY} ${vbW} ${newH}`);\n const heightAttr = parseFloat(svgEl.getAttribute(\"height\") || \"\");\n if (Number.isFinite(heightAttr)) {\n const baseHeight = hadPrior ? heightAttr - PLOTTR_ATTRIBUTION_PAD : heightAttr;\n svgEl.setAttribute(\"height\", String(baseHeight + PLOTTR_ATTRIBUTION_PAD));\n }\n\n const version =\n (typeof window !== \"undefined\" &&\n typeof (window as Window & { __APP_VERSION__?: string }).__APP_VERSION__ === \"string\"\n ? (window as Window & { __APP_VERSION__?: string }).__APP_VERSION__\n : null) || \"v?\";\n const doc = svgEl.ownerDocument || (typeof document !== \"undefined\" ? document : null);\n if (!doc || typeof doc.createElementNS !== \"function\") return;\n const NS = \"http://www.w3.org/2000/svg\";\n const g = doc.createElementNS(NS, \"g\");\n g.setAttribute(\"id\", \"plottr-attribution\");\n g.setAttribute(\"data-plottr-version\", version);\n const text = doc.createElementNS(NS, \"text\");\n text.setAttribute(\"x\", String(vbX + vbW - 5));\n text.setAttribute(\"y\", String(vbY + newH - 4));\n text.setAttribute(\"font-size\", \"8\");\n text.setAttribute(\"font-style\", \"italic\");\n text.setAttribute(\"fill\", \"#999\");\n text.setAttribute(\"text-anchor\", \"end\");\n text.setAttribute(\"font-family\", \"system-ui, -apple-system, sans-serif\");\n text.textContent = `Pl\u00F6ttr ${version}`;\n g.appendChild(text);\n svgEl.appendChild(g);\n}\n", "// _core/download.ts \u2014 file-save helpers (save-picker + legacy anchor\n// fallback), CSV / SVG / PNG / text downloads, filename utilities.\n\nimport { buildCsvString } from \"./csv\";\nimport { buildExportSvg, PLOTTR_ATTRIBUTION_PAD, serializeSvgForExport } from \"./svg-export\";\n\n// \u2500\u2500 Filename helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function fileBaseName(fileName: unknown, fallback?: string): string {\n if (typeof fileName === \"string\" && fileName.trim()) return fileName.replace(/\\.[^.]+$/, \"\");\n return fallback || \"data\";\n}\n\n// Accepts any HTMLElement (call sites use either a button or a wider\n// `EventTarget & HTMLElement` from React's MouseEvent.currentTarget).\n// `disabled` is set only on elements that have the property \u2014 most\n// commonly buttons; non-button calls just get the innerHTML swap.\nexport function flashSaved(btn: HTMLElement | null): void {\n if (!btn) return;\n const original = btn.innerHTML;\n btn.innerHTML = \"\u2713 Saved\";\n if (\"disabled\" in btn) (btn as HTMLButtonElement).disabled = true;\n setTimeout(() => {\n btn.innerHTML = original;\n if (\"disabled\" in btn) (btn as HTMLButtonElement).disabled = false;\n }, 1500);\n}\n\n// \u2500\u2500 File-save picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ninterface SavePickerType {\n mime: string;\n description: string;\n}\nconst _SAVE_PICKER_TYPES: Record = {\n \".svg\": { mime: \"image/svg+xml\", description: \"SVG image\" },\n \".png\": { mime: \"image/png\", description: \"PNG image\" },\n \".csv\": { mime: \"text/csv\", description: \"CSV file\" },\n \".tsv\": { mime: \"text/tab-separated-values\", description: \"TSV file\" },\n \".txt\": { mime: \"text/plain\", description: \"Text file\" },\n \".r\": { mime: \"text/plain\", description: \"R script\" },\n \".json\": { mime: \"application/json\", description: \"JSON file\" },\n};\n\ninterface SaveFilePickerWindow extends Window {\n showSaveFilePicker?: (opts: {\n suggestedName: string;\n types: Array<{ description: string; accept: Record }>;\n }) => Promise<{\n createWritable: () => Promise<{\n write: (b: Blob) => Promise;\n close: () => Promise;\n }>;\n }>;\n}\n\n// Save a Blob to disk. Tries the File System Access API\n// (`window.showSaveFilePicker`) first so the user can pick a target folder\n// + filename \u2014 supported in Chromium-based browsers (Chrome, Edge, Opera)\n// on HTTPS / localhost. Falls back to the classic `` anchor\n// click on Firefox / Safari, which routes the file to the browser's\n// default Downloads folder.\n//\n// The fallback is also taken when the picker call throws anything other\n// than the user-cancelled `AbortError`. User cancel returns silently; we\n// don't downgrade to the fallback in that case because the user explicitly\n// chose \"Cancel\".\nexport async function saveBlob(blob: Blob, filename: string): Promise {\n if (\n typeof window !== \"undefined\" &&\n typeof (window as SaveFilePickerWindow).showSaveFilePicker === \"function\"\n ) {\n try {\n const dot = filename.lastIndexOf(\".\");\n const ext = dot >= 0 ? filename.slice(dot).toLowerCase() : \"\";\n const meta = _SAVE_PICKER_TYPES[ext] || {\n mime: blob.type || \"application/octet-stream\",\n description: \"File\",\n };\n const handle = await (window as SaveFilePickerWindow).showSaveFilePicker!({\n suggestedName: filename,\n types: [{ description: meta.description, accept: { [meta.mime]: [ext || \"\"] } }],\n });\n const writable = await handle.createWritable();\n await writable.write(blob);\n await writable.close();\n return;\n } catch (err) {\n if (err && (err as { name?: string }).name === \"AbortError\") return;\n }\n }\n const url = URL.createObjectURL(blob);\n const a = document.createElement(\"a\");\n a.href = url;\n a.download = filename;\n a.style.display = \"none\";\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n// \u2500\u2500 Format-specific downloaders \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nexport function downloadSvg(svgEl: SVGElement | null, filename: string): void {\n if (!svgEl) return;\n const svgStr = serializeSvgForExport(svgEl);\n const blob = new Blob([svgStr], { type: \"image/svg+xml\" });\n saveBlob(blob, filename);\n}\n\nexport function downloadPng(svgEl: SVGElement | null, filename: string, scale?: number): void {\n if (!svgEl) return;\n const s = scale || 2;\n const exportSvg = buildExportSvg(svgEl);\n const svgStr = new XMLSerializer().serializeToString(exportSvg);\n const vb = exportSvg.getAttribute(\"viewBox\");\n const parts = vb ? vb.split(/[\\s,]+/) : [];\n const w = parts.length >= 4 ? parseFloat(parts[2]) : (svgEl as SVGSVGElement).clientWidth || 800;\n const h =\n parts.length >= 4\n ? parseFloat(parts[3])\n : ((svgEl as SVGSVGElement).clientHeight || 600) + PLOTTR_ATTRIBUTION_PAD;\n const canvas = document.createElement(\"canvas\");\n canvas.width = w * s;\n canvas.height = h * s;\n const ctx = canvas.getContext(\"2d\")!;\n ctx.fillStyle = \"#ffffff\";\n ctx.fillRect(0, 0, canvas.width, canvas.height);\n const img = new Image();\n const blob = new Blob([svgStr], { type: \"image/svg+xml;charset=utf-8\" });\n const url = URL.createObjectURL(blob);\n img.onload = function () {\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n URL.revokeObjectURL(url);\n canvas.toBlob(function (pngBlob) {\n if (pngBlob) saveBlob(pngBlob, filename);\n }, \"image/png\");\n };\n // Without this, a serialized SVG the rasterizer rejects leaves onload\n // unfired: no PNG, no feedback, and the object URL leaks.\n img.onerror = function () {\n URL.revokeObjectURL(url);\n console.error(\"[plottr] PNG export failed: the chart SVG could not be rasterized\");\n };\n img.src = url;\n}\n\nexport function downloadText(text: string, filename: string): void {\n const blob = new Blob([text], { type: \"text/plain\" });\n saveBlob(blob, filename);\n}\n\nexport function downloadCsv(headers: unknown[], rows: unknown[][], filename: string): void {\n const blob = new Blob([buildCsvString(headers, rows)], { type: \"text/csv\" });\n saveBlob(blob, filename);\n}\n", "// `DataPreview` \u2014 compact table preview for parsed CSV/TSV data, used in\n// every tool's upload + configure steps. Renders the first `maxRows` rows\n// (default 10) plus a \"\u2026 N more\" footer when truncated.\n\ninterface DataPreviewProps {\n headers: string[];\n // Cell values are coerced to strings at render time; accept strings,\n // numbers, or null / empty for sparse rows.\n rows: Array>;\n // Maximum rows rendered before the \"\u2026 N more\" footer kicks in. Defaults to 10.\n maxRows?: number;\n}\n\nexport function DataPreview({ headers, rows, maxRows }: DataPreviewProps) {\n const limit = maxRows || 10;\n const d = rows.slice(0, limit);\n return (\n \n \n \n \n \n #\n \n {headers.map((h, i) => (\n \n {h}\n \n ))}\n \n \n \n {d.map((r, ri) => (\n \n \n {ri + 1}\n \n {r.map((v, ci) => (\n \n {v}\n \n ))}\n \n ))}\n \n
\n {rows.length > limit ? (\n \n {`\u2026 ${rows.length - limit} more (${rows.length} total)`}\n

\n ) : null}\n \n );\n}\n", "// `DetectedSeparatorBadge` \u2014 small inline reminder of which delimiter the\n// auto-detector picked on the most recent parse, surfaced on each tool's\n// post-upload step (configure / preview / plot) so users can sanity-check\n// the pick before reading too much into the chart. Renders nothing when\n// `sep` is empty (no parse yet) or the empty-string whitespace fallback.\n//\n// Designed to slot into an existing muted text line as a trailing fragment,\n// e.g. ` \u2014 4 cols \u00D7 72 rows {}` \u2014\n// styled neutrally on `var(--text-faint)` so it doesn't compete with the\n// file name or counts.\n\nexport function describeSeparator(sep: string): string {\n if (sep === \",\") return \"comma\";\n if (sep === \";\") return \"semicolon\";\n if (sep === \"\\t\") return \"tab\";\n if (sep === \" \") return \"space\";\n return \"whitespace\";\n}\n\ninterface DetectedSeparatorBadgeProps {\n sep: string;\n}\n\nexport function DetectedSeparatorBadge({ sep }: DetectedSeparatorBadgeProps) {\n if (!sep) return null;\n return (\n \n \u00B7 detected: {describeSeparator(sep)}-separated\n \n );\n}\n", "// Discrete colour palette catalogue + helpers shared across plot tools, the\n// discrete analogue of the continuous COLOR_PALETTES / PaletteStrip pattern\n// used by scatter / heatmap / volcano. Used by every per-group / per-category\n// colouring slot (boxplot groups, lineplot series, scatter discrete\n// categories, venn sets, aequorin conditions, volcano up/down/ns).\n//\n// `okabe-ito` is byte-identical to the global PALETTE in shared.js \u2014 every\n// tool defaults its `vis.discretePalette` to \"okabe-ito\" so existing\n// behaviour is preserved exactly.\n//\n// Attribution:\n// ColorBrewer palettes (set1/set2/set3/dark2/paired/pastel1/pastel2)\n// \u00A9 Cynthia Brewer, Mark Harrower, and Penn State University.\n// Licensed under the Apache License, Version 2.0\n// (http://www.apache.org/licenses/LICENSE-2.0).\n//\n// Okabe-Ito: Wong 2011, Nature Methods 8(6):441.\n// Tableau10: \u00A9Tableau Software; reproduced under fair-use widely.\n// ggplot2-hue: derived at runtime from hcl(h, l=65, c=100), no licence.\n// viridis-d: even-spaced sample of the continuous viridis (CC0).\n//\n// Note: viridis-d (discrete sample) is intentionally distinct from\n// COLOR_PALETTES.viridis (continuous): the discrete form generates exactly\n// `n = names.length` colours via interpolateColor at runtime.\n\nimport { COLOR_PALETTES, interpolateColor } from \"../_core/color\";\n\nexport const DISCRETE_PALETTES: Record = {\n // Okabe-Ito (Wong 2011) \u2014 colour-blind safe. Same as PALETTE in shared.js.\n \"okabe-ito\": [\n \"#E69F00\",\n \"#56B4E9\",\n \"#009E73\",\n \"#F0E442\",\n \"#0072B2\",\n \"#D55E00\",\n \"#CC79A7\",\n \"#000000\",\n \"#88CCEE\",\n \"#AA4499\",\n ],\n tab10: [\n \"#4E79A7\",\n \"#F28E2B\",\n \"#E15759\",\n \"#76B7B2\",\n \"#59A14F\",\n \"#EDC948\",\n \"#B07AA1\",\n \"#FF9DA7\",\n \"#9C755F\",\n \"#BAB0AC\",\n ],\n set1: [\n \"#E41A1C\",\n \"#377EB8\",\n \"#4DAF4A\",\n \"#984EA3\",\n \"#FF7F00\",\n \"#FFFF33\",\n \"#A65628\",\n \"#F781BF\",\n \"#999999\",\n ],\n set2: [\"#66C2A5\", \"#FC8D62\", \"#8DA0CB\", \"#E78AC3\", \"#A6D854\", \"#FFD92F\", \"#E5C494\", \"#B3B3B3\"],\n set3: [\n \"#8DD3C7\",\n \"#FFFFB3\",\n \"#BEBADA\",\n \"#FB8072\",\n \"#80B1D3\",\n \"#FDB462\",\n \"#B3DE69\",\n \"#FCCDE5\",\n \"#D9D9D9\",\n \"#BC80BD\",\n \"#CCEBC5\",\n \"#FFED6F\",\n ],\n dark2: [\"#1B9E77\", \"#D95F02\", \"#7570B3\", \"#E7298A\", \"#66A61E\", \"#E6AB02\", \"#A6761D\", \"#666666\"],\n paired: [\n \"#A6CEE3\",\n \"#1F78B4\",\n \"#B2DF8A\",\n \"#33A02C\",\n \"#FB9A99\",\n \"#E31A1C\",\n \"#FDBF6F\",\n \"#FF7F00\",\n \"#CAB2D6\",\n \"#6A3D9A\",\n \"#FFFF99\",\n \"#B15928\",\n ],\n pastel1: [\n \"#FBB4AE\",\n \"#B3CDE3\",\n \"#CCEBC5\",\n \"#DECBE4\",\n \"#FED9A6\",\n \"#FFFFCC\",\n \"#E5D8BD\",\n \"#FDDAEC\",\n \"#F2F2F2\",\n ],\n pastel2: [\"#B3E2CD\", \"#FDCDAC\", \"#CBD5E8\", \"#F4CAE4\", \"#E6F5C9\", \"#FFF2AE\", \"#F1E2CC\", \"#CCCCCC\"],\n // Sentinel \"*\" means \"generate at runtime sized to names.length\"; consumers\n // call buildGgplot2Hue / buildViridisDiscrete via resolveDiscretePalette.\n \"ggplot2-hue\": [\"*\"],\n \"viridis-d\": [\"*\"],\n};\n\n// Subset considered colour-blind-safe (no red/green adjacency, sufficient\n// luminance contrast). Surfaced as a \uD83D\uDC41 glyph in the dropdown so users can\n// quickly tell. Conservative on purpose \u2014 Set2 / Pastel2 are arguably\n// borderline; only the unambiguous ones are marked.\nexport const COLORBLIND_SAFE_PALETTES = new Set([\"okabe-ito\", \"dark2\", \"paired\", \"viridis-d\"]);\n\n// HCL \u2192 sRGB conversion for ggplot2-hue. Implementation matches grDevices::hcl\n// closely enough for visual parity (D65 white point, 2\u00B0 observer). Full\n// agreement with R's hcl() output is not the goal \u2014 we just need the iconic\n// ggplot2 look.\nfunction hclToHex(h: number, c: number, l: number): string {\n const hRad = (h * Math.PI) / 180;\n const a = Math.cos(hRad) * c;\n const b = Math.sin(hRad) * c;\n // Lab \u2192 XYZ (D65)\n const Y = (l + 16) / 116;\n const X = a / 500 + Y;\n const Z = Y - b / 200;\n const fInv = (t: number): number => (t * t * t > 0.008856 ? t * t * t : (t - 16 / 116) / 7.787);\n const Xn = 95.047,\n Yn = 100.0,\n Zn = 108.883;\n const x = Xn * fInv(X);\n const y = Yn * fInv(Y);\n const z = Zn * fInv(Z);\n // XYZ \u2192 linear sRGB (D65)\n let r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;\n let g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;\n let bl = (x * 0.0557 + y * -0.204 + z * 1.057) / 100;\n // Linear \u2192 sRGB gamma\n const gam = (u: number): number =>\n u > 0.0031308 ? 1.055 * Math.pow(u, 1 / 2.4) - 0.055 : 12.92 * u;\n r = Math.max(0, Math.min(1, gam(r)));\n g = Math.max(0, Math.min(1, gam(g)));\n bl = Math.max(0, Math.min(1, gam(bl)));\n const toHex = (u: number): string =>\n Math.round(u * 255)\n .toString(16)\n .padStart(2, \"0\");\n return \"#\" + toHex(r) + toHex(g) + toHex(bl);\n}\n\n// Generate ggplot2's default hue scale for n colours.\nexport function buildGgplot2Hue(n: number): string[] {\n if (n <= 0) return [];\n const out: string[] = [];\n // hues = seq(15, 375, length.out = n + 1)[1:n]\n for (let i = 0; i < n; i++) {\n const h = 15 + (360 * i) / n;\n out.push(hclToHex(h, 100, 65));\n }\n return out;\n}\n\n// Generate evenly-spaced viridis samples for n colours by interpolating\n// COLOR_PALETTES.viridis. Falls back to the raw stops if interpolateColor\n// isn't yet on the global scope (defensive \u2014 they live in shared.js which\n// loads first in the bundle).\nexport function buildViridisDiscrete(n: number): string[] {\n if (n <= 0) return [];\n if (typeof interpolateColor !== \"function\" || typeof COLOR_PALETTES !== \"object\") {\n return [];\n }\n const stops = COLOR_PALETTES.viridis;\n if (n === 1) return [stops[Math.floor(stops.length / 2)]];\n const out: string[] = [];\n for (let i = 0; i < n; i++) {\n out.push(interpolateColor(stops, i / (n - 1)));\n }\n return out;\n}\n\n// Resolve a palette name to a concrete hex array sized for `n` slots.\n// Recycles modulo for fixed-length palettes, generates exactly n for the\n// runtime palettes (ggplot2-hue / viridis-d). Unknown names fall back to\n// okabe-ito so a stale localStorage value never produces an empty palette.\nexport function resolveDiscretePalette(name: string, n: number): string[] {\n if (name === \"ggplot2-hue\") return buildGgplot2Hue(Math.max(1, n || 1));\n if (name === \"viridis-d\") return buildViridisDiscrete(Math.max(1, n || 1));\n const stops = DISCRETE_PALETTES[name] || DISCRETE_PALETTES[\"okabe-ito\"];\n if (!stops || stops.length === 0) return [];\n const count = Math.max(1, n || stops.length);\n const out: string[] = [];\n for (let i = 0; i < count; i++) out.push(stops[i % stops.length]);\n return out;\n}\n\n// Build a `{ name \u2192 hex }` record for an ordered array of group/category\n// names using the named palette. Recycles modulo for fixed palettes,\n// generates exact for runtime palettes.\nexport function applyDiscretePalette(name: string, names: string[]): Record {\n const list = Array.isArray(names) ? names : [];\n const resolved = resolveDiscretePalette(name, list.length);\n const out: Record = {};\n list.forEach((nm, i) => {\n out[nm] = resolved[i % Math.max(1, resolved.length)] || \"#000000\";\n });\n return out;\n}\n", "// `FileDropZone` \u2014 drag-and-drop / click-to-browse upload widget shared by\n// every tool's UploadStep. Gates on the canonical `FILE_LIMIT_BYTES`\n// (2 MB hard reject) and `FILE_WARN_BYTES` (1 MB warn) constants exported\n// here too \u2014 any new ingest surface (paste textarea, URL fetch, \u2026) must\n// gate on the same constants and surface the same red-banner UX.\n\nconst { useState, useRef } = React;\n\n// 2 MB \u2014 hard reject. Any new ingest surface (paste textarea, URL fetch,\n// clipboard image OCR, \u2026) must gate on this and the warning threshold\n// below; don't redeclare a local 2-MB number, don't pick your own.\nexport const FILE_LIMIT_BYTES = 2 * 1024 * 1024;\n// 1 MB \u2014 show warning but allow.\nexport const FILE_WARN_BYTES = 1 * 1024 * 1024;\n\ninterface FileDropZoneProps {\n onFileLoad: (text: string, fileName: string) => void;\n // Comma-separated extension list passed to the hidden ``.\n // Default covers the canonical text formats the parsers handle.\n accept?: string;\n // Sub-text under the main \"Drop CSV, TSV, or TXT\" line. Override per-tool\n // to spell out additional accepted extensions or constraints.\n hint?: string;\n}\n\nexport function FileDropZone({\n onFileLoad,\n accept = \".csv,.tsv,.txt,.dat\",\n hint = \"CSV \u00B7 TSV \u00B7 TXT \u00B7 DAT \u2014 2 MB max\",\n}: FileDropZoneProps) {\n const [drag, setDrag] = useState(false);\n const [focus, setFocus] = useState(false);\n const [sizeError, setSizeError] = useState(null);\n const [sizeWarn, setSizeWarn] = useState(null);\n const [readError, setReadError] = useState(null);\n const [reading, setReading] = useState(false);\n const inputRef = useRef(null);\n\n const handle = (file: File) => {\n setSizeError(null);\n setSizeWarn(null);\n setReadError(null);\n if (file.size > FILE_LIMIT_BYTES) {\n setSizeError(\n `File too large (${(file.size / 1024 / 1024).toFixed(1)} MB). Maximum is 2 MB \u2014 split the file or sample rows and try again.`\n );\n return;\n }\n if (file.size > FILE_WARN_BYTES) {\n setSizeWarn(\n `Large file (${(file.size / 1024 / 1024).toFixed(1)} MB) \u2014 parsing may take a moment.`\n );\n }\n const reader = new FileReader();\n // Both `onload` and `onerror` must be wired \u2014 a corrupt file or blocked\n // read would otherwise leave the user staring at the drop zone with no\n // feedback. `setReading` gives the UI a \"Reading\u2026\" affordance until one\n // of the two callbacks fires.\n setReading(true);\n reader.onload = (e) => {\n setReading(false);\n onFileLoad(String(e.target?.result ?? \"\"), file.name);\n };\n reader.onerror = () => {\n setReading(false);\n const msg = (reader.error && (reader.error.message || reader.error.name)) || \"unknown error\";\n setReadError(`Couldn't read the file (${msg}). Check permissions and try again.`);\n };\n reader.readAsText(file);\n };\n\n const openPicker = () => inputRef.current && inputRef.current.click();\n\n return (\n
\n {\n e.preventDefault();\n setDrag(true);\n }}\n onDragLeave={() => setDrag(false)}\n onDrop={(e) => {\n e.preventDefault();\n setDrag(false);\n if (e.dataTransfer.files[0]) handle(e.dataTransfer.files[0]);\n }}\n onClick={openPicker}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n openPicker();\n }\n }}\n onFocus={() => setFocus(true)}\n onBlur={() => setFocus(false)}\n style={{\n border: `2px dashed ${\n drag ? \"var(--accent-primary)\" : sizeError ? \"var(--danger-text)\" : \"var(--text-faint)\"\n }`,\n borderRadius: 12,\n padding: \"48px 24px\",\n textAlign: \"center\",\n cursor: \"pointer\",\n background: drag\n ? \"var(--accent-primary-weak)\"\n : sizeError\n ? \"var(--danger-bg)\"\n : \"transparent\",\n transition: \"all .2s\",\n outline: focus ? \"2px solid var(--accent-primary)\" : \"none\",\n outlineOffset: 2,\n }}\n >\n {\n if (e.target.files && e.target.files[0]) handle(e.target.files[0]);\n e.target.value = \"\";\n }}\n />\n
\n \uD83D\uDCC2\n
\n

\n Drop CSV, TSV, or TXT \u2014 or click to browse\n

\n

{hint}

\n
\n {sizeError && (\n \n \n \uD83D\uDEAB\n \n \n {sizeError}\n \n \n )}\n {sizeWarn && (\n \n \n \u26A0\uFE0F\n \n {sizeWarn}\n \n )}\n {readError && (\n \n \n \uD83D\uDEAB\n \n \n {readError}\n \n \n )}\n {reading && (\n \n Reading file\u2026\n \n )}\n \n );\n}\n", "// `HowToCard` \u2014 collapsible \"How to use\" card shared across every plot\n// tool's upload step. Open state persists under `dv-howto-`\n// in localStorage; open by default on first visit, then follows\n// whatever the user last chose.\n\nconst h = React.createElement;\n\nconst { useState, useMemo } = React;\n\ninterface HowToCardProps {\n toolName: string;\n title: React.ReactNode;\n subtitle?: React.ReactNode;\n children?: React.ReactNode;\n}\n\nexport function HowToCard(props: HowToCardProps) {\n const { toolName, title, subtitle, children } = props;\n const storageKey = \"dv-howto-\" + toolName;\n const initialOpen = useMemo(() => {\n try {\n const v = localStorage.getItem(storageKey);\n if (v === \"1\") return true;\n if (v === \"0\") return false;\n } catch {\n /* ignore */\n }\n return true;\n }, [storageKey]);\n const [open, setOpen] = useState(initialOpen);\n const bodyId = \"dv-howto-body-\" + toolName;\n const toggle = () => {\n setOpen((prev) => {\n const next = !prev;\n try {\n localStorage.setItem(storageKey, next ? \"1\" : \"0\");\n } catch {\n /* ignore */\n }\n return next;\n });\n };\n return h(\n \"section\",\n {\n style: {\n marginTop: 24,\n borderRadius: 14,\n overflow: \"hidden\",\n border: \"2px solid var(--howto-border)\",\n boxShadow: \"var(--howto-shadow)\",\n },\n },\n h(\n \"button\",\n {\n type: \"button\",\n onClick: toggle,\n \"aria-expanded\": open ? \"true\" : \"false\",\n \"aria-controls\": bodyId,\n style: {\n width: \"100%\",\n background: \"linear-gradient(135deg,var(--howto-header-from),var(--howto-header-to))\",\n padding: \"14px 24px\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n border: \"none\",\n cursor: \"pointer\",\n textAlign: \"left\",\n font: \"inherit\",\n color: \"inherit\",\n },\n },\n // Leading chevron \u2014 reads as \"click to expand\". Tool identity is\n // carried by the topbar + the sample-dataset banner above, so the\n // chevron alone keeps the header uncluttered.\n h(\n \"span\",\n {\n \"aria-hidden\": \"true\",\n style: {\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n color: \"var(--on-accent)\",\n transition: \"transform .18s ease\",\n transform: open ? \"rotate(90deg)\" : \"rotate(0deg)\",\n flexShrink: 0,\n },\n },\n h(\n \"svg\",\n { width: 22, height: 22, viewBox: \"0 0 24 24\", style: { display: \"block\" } },\n h(\"path\", {\n d: \"M9 5l7 7-7 7\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 2.6,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n })\n )\n ),\n h(\n \"div\",\n { style: { flex: 1, minWidth: 0 } },\n h(\"div\", { style: { color: \"var(--on-accent)\", fontWeight: 700, fontSize: 15 } }, title),\n subtitle\n ? h(\n \"div\",\n { style: { color: \"var(--on-accent-muted)\", fontSize: 11, marginTop: 2 } },\n subtitle\n )\n : null\n )\n ),\n open\n ? h(\n \"div\",\n {\n id: bodyId,\n style: {\n background: \"var(--info-bg)\",\n padding: \"20px 24px\",\n display: \"grid\",\n gridTemplateColumns: \"1fr 1fr\",\n gap: 14,\n },\n },\n children\n )\n : null\n );\n}\n", "// Shared \"How to use\" tile for the upload step of every plot tool.\n//\n// Wraps the existing `HowToCard` (collapsible header with a leading\n// expand/collapse chevron) and renders a uniform body \u2014 three required\n// sub-cards (Purpose / Data layout / Display) plus an optional Tips\n// card \u2014 driven by a `HowToContent` object passed by the caller. That\n// moves per-tool prose out of long `steps.tsx` / `index.tsx` files\n// and into tiny per-tool `howto.tsx` constants, and keeps the visual\n// depth consistent across tools (no more wall-of-text in one place +\n// one-liner in another).\n//\n// The `toolName` field on `HowToContent` is used by `HowToCard` for the\n// localStorage collapse-state key (`dv-howto-`).\n//\n// The rendering layer (sub-card header style, grid layout, pill list)\n// lives here. Tool-specific content lives in `tools//howto.tsx`.\n\nimport type { ReactNode } from \"react\";\nimport { HowToCard } from \"./HowToCard\";\n\nexport interface HowToContent {\n /** Drives the tool icon + the collapsible's localStorage key. */\n toolName: string;\n /** First-line title \u2014 e.g. \"Volcano Plot \u2014 How to use\". */\n title: string;\n /** One-line \"TL;DR\" subtitle below the title. */\n subtitle: string;\n /** What is this tool for, in user-language? Keep to 1\u20132 sentences. */\n purpose: ReactNode;\n /** What shape of data does it accept? Keep to 1\u20132 sentences. */\n dataLayout: ReactNode;\n /** Key display options + tool-specific specifics. 2\u20133 sentences. */\n display: ReactNode;\n /**\n * Optional tips / power-user hints (e.g. \"click points to label\").\n * When present, renders as a 4th full-width sub-card below the trio.\n */\n tips?: ReactNode;\n /**\n * Optional capability tags. Renders as a wrapped pill list at the\n * bottom \u2014 short label per pill, no styling overrides. Skip for\n * tools that don't need a marketing-style summary.\n */\n capabilities?: string[];\n}\n\n// Sub-card visuals \u2014 kept in lockstep across tools so a user moving\n// between volcano and lineplot doesn't perceive a different \"depth\"\n// of help. Header label style + body padding mirror the existing\n// HowToCard sub-card pattern (lineplot was the canonical one).\nfunction SubCard({\n label,\n children,\n fullWidth = false,\n}: {\n label: string;\n children: ReactNode;\n fullWidth?: boolean;\n}) {\n return (\n \n \n {label}\n \n
{children}
\n \n );\n}\n\nexport function HowTo(props: HowToContent) {\n return (\n \n \n {props.purpose}\n \n {props.dataLayout}\n {props.display}\n {props.tips && (\n \n {props.tips}\n \n )}\n {props.capabilities && props.capabilities.length > 0 && (\n
\n {props.capabilities.map((t) => (\n \n {t}\n \n ))}\n
\n )}\n
\n );\n}\n", "// `NumberInput` \u2014 compact numeric entry with \u2212/+ buttons replacing the\n// native stacked spinner. Mimics the native `` API:\n// `value`, `onChange(e)` where `e.target.value` is a string. The \u2212/+\n// buttons fire a synthetic event with the next value as a string so\n// existing `(e) => setX(e.target.value)` handlers keep working.\n//\n// Press-and-hold: button repeats at an accelerating cadence (first bump\n// immediate, 400 ms before repeat kicks in, then 80 ms for a handful of\n// ticks, then 40 ms). Release anywhere on the page stops the hold via a\n// window-level pointerup listener so the user can drag off the button\n// mid-hold without breaking the gesture.\n\nconst h = React.createElement;\n\nconst { useRef, useEffect } = React;\n\ninterface NumberInputProps {\n value: number | string | null | undefined;\n onChange: (e: { target: { value: string } }) => void;\n min?: number | string;\n max?: number | string;\n step?: number | string;\n disabled?: boolean;\n placeholder?: string;\n className?: string;\n style?: React.CSSProperties;\n inputStyle?: React.CSSProperties;\n}\n\ninterface HoldState {\n dir: number;\n next: number;\n timerId: ReturnType | null;\n onUp: (() => void) | null;\n}\n\nexport function NumberInput(props: NumberInputProps) {\n const value = props.value != null ? props.value : \"\";\n const onChange = props.onChange;\n const min = props.min != null ? Number(props.min) : null;\n const max = props.max != null ? Number(props.max) : null;\n const rawStep = props.step != null && props.step !== \"any\" ? Number(props.step) : 1;\n const step = isNaN(rawStep) || rawStep <= 0 ? 1 : rawStep;\n const disabled = !!props.disabled;\n const placeholder = props.placeholder;\n const className = \"dv-num\" + (disabled ? \" dv-num-disabled\" : \"\");\n\n // Hold state lives in a ref so setTimeout chains survive React re-renders\n // triggered by each fireChange.\n const holdRef = useRef(null);\n\n const fireChange = (newValueStr: string) => {\n if (!onChange) return;\n onChange({ target: { value: newValueStr } });\n };\n\n const clamp = (n: number): number => {\n if (min != null && n < min) n = min;\n if (max != null && n > max) n = max;\n // Round to the step's decimal precision to avoid 0.1+0.2 float noise.\n const decimals = (String(step).split(\".\")[1] || \"\").length;\n return decimals ? Number(n.toFixed(decimals)) : n;\n };\n\n const numericValue = Number(value);\n const minusDisabled = disabled || (min != null && !isNaN(numericValue) && numericValue <= min);\n const plusDisabled = disabled || (max != null && !isNaN(numericValue) && numericValue >= max);\n\n const stopRepeat = () => {\n const state = holdRef.current;\n if (!state) return;\n if (state.timerId) clearTimeout(state.timerId);\n if (state.onUp && typeof window !== \"undefined\") {\n window.removeEventListener(\"pointerup\", state.onUp);\n window.removeEventListener(\"pointercancel\", state.onUp);\n }\n holdRef.current = null;\n };\n\n const startRepeat = (dir: number) => {\n if ((dir < 0 && minusDisabled) || (dir > 0 && plusDisabled)) return;\n // Seed from the current prop value; every subsequent tick walks our own\n // ref so we stay correct even if React hasn't flushed the re-render yet.\n const seed = isNaN(Number(value)) ? (min != null ? min : 0) : Number(value);\n const state: HoldState = { dir, next: seed, timerId: null, onUp: null };\n holdRef.current = state;\n\n const doStep = () => {\n if (holdRef.current !== state) return;\n const n = clamp(state.next + dir * step);\n state.next = n;\n fireChange(String(n));\n if ((dir < 0 && min != null && n <= min) || (dir > 0 && max != null && n >= max)) {\n stopRepeat();\n }\n };\n\n // First step fires immediately so a quick click still bumps once.\n doStep();\n if (holdRef.current !== state) return;\n\n let ticks = 0;\n const tick = () => {\n if (holdRef.current !== state) return;\n doStep();\n if (holdRef.current !== state) return;\n ticks += 1;\n const delay = ticks > 8 ? 40 : 80;\n state.timerId = setTimeout(tick, delay);\n };\n // 400 ms dwell before the repeat kicks in (standard stepper feel).\n state.timerId = setTimeout(tick, 400);\n\n // Release anywhere on the page ends the hold \u2014 survives the user\n // dragging off the button mid-gesture.\n state.onUp = () => stopRepeat();\n if (typeof window !== \"undefined\") {\n window.addEventListener(\"pointerup\", state.onUp);\n window.addEventListener(\"pointercancel\", state.onUp);\n }\n };\n\n // Clean up if the component unmounts during a hold.\n useEffect(() => {\n return () => stopRepeat();\n }, []);\n\n const onMinusDown = (e: React.PointerEvent) => {\n if (e.button != null && e.button !== 0) return;\n e.preventDefault();\n startRepeat(-1);\n };\n const onPlusDown = (e: React.PointerEvent) => {\n if (e.button != null && e.button !== 0) return;\n e.preventDefault();\n startRepeat(1);\n };\n\n return h(\n \"div\",\n { className, style: props.style },\n h(\n \"button\",\n {\n type: \"button\",\n className: \"dv-num-btn dv-num-btn-minus\",\n onPointerDown: onMinusDown,\n disabled: minusDisabled,\n tabIndex: -1,\n \"aria-label\": \"Decrement\",\n },\n \"\u2212\"\n ),\n h(\"input\", {\n type: \"number\",\n className: \"dv-num-input\",\n value,\n min: props.min,\n max: props.max,\n step: props.step != null ? props.step : 1,\n disabled,\n placeholder,\n onChange: (e: React.ChangeEvent) => fireChange(e.target.value),\n style: props.inputStyle,\n }),\n h(\n \"button\",\n {\n type: \"button\",\n className: \"dv-num-btn dv-num-btn-plus\",\n onPointerDown: onPlusDown,\n disabled: plusDisabled,\n tabIndex: -1,\n \"aria-label\": \"Increment\",\n },\n \"+\"\n )\n );\n}\n", "// _core/icons.tsx \u2014 `TOOL_ICONS` catalogue + the `toolIcon` React helper.\n//\n// Lives as `.tsx` (not `.ts`) because `toolIcon` returns a ReactElement \u2014\n// the call site uses `React.createElement` rather than JSX literals, but\n// the .tsx extension makes the React surface unambiguous to esbuild and\n// future contributors who reach for JSX here.\n\nexport const TOOL_ICONS: Record = {\n aequorin:\n '',\n boxplot:\n '',\n scatter:\n '',\n venn: '',\n molarity:\n '',\n power:\n '',\n lineplot:\n '',\n heatmap:\n '',\n upset:\n '',\n volcano:\n '',\n};\n\n// Per-tool accent colours \u2014 a MIRROR of the landing tile `--tile-accent`\n// values in index.html. `toolIcon` retints each glyph's base hue to its\n// tool's accent so the topbar / upload-panel icon carries the same per-tool\n// colour as the landing tile. Volcano has no base-blue in its glyph, so it\n// keeps its intrinsic multi-tone (matching the landing). Keep this table in\n// sync with the `--tile-accent` literals in index.html if either changes.\nexport const TOOL_ACCENT: Record = {\n boxplot: \"#648fff\",\n scatter: \"#1ba89b\",\n lineplot: \"#fe6100\",\n aequorin: \"#dc267f\",\n venn: \"#d9a400\",\n upset: \"#785ef0\",\n heatmap: \"#0072b2\",\n volcano: \"#d55e00\",\n power: \"#dc267f\",\n molarity: \"#1ba89b\",\n};\n\n// Retint an icon SVG string's base blue (and the secondary violet a couple of\n// glyphs use) to the tool's accent. Glyphs with neither \u2014 e.g. volcano \u2014 pass\n// through unchanged. Used for both the size-injected `toolIcon` glyph and the\n// registry `iconSvg` strings rendered in the topbar quick-jumps. Icons drawn\n// with `currentColor` are unaffected here and instead inherit the accent from\n// the rendering element's `color`.\nexport function tintIcon(svg: string, name: string): string {\n const accent = TOOL_ACCENT[name];\n return accent ? svg.replace(/#648FFF/gi, accent).replace(/#785EF0/gi, accent) : svg;\n}\n\nexport function toolIcon(\n name: string,\n size?: number,\n opts?: { circle?: boolean }\n): React.ReactElement | null {\n const sz = size || 22;\n const o = opts || {};\n if (!TOOL_ICONS[name]) return null;\n const sized = TOOL_ICONS[name].replace(\"\n h(\"div\", {\n key,\n \"aria-hidden\": \"true\",\n style: {\n flex: \"0 0 auto\",\n width: 1,\n alignSelf: \"stretch\",\n background: \"var(--border-strong)\",\n },\n });\n const rowChildren: React.ReactNode[] = [\n h(\n \"h1\",\n {\n key: \"title\",\n style: {\n margin: 0,\n fontSize: 22,\n fontWeight: 700,\n color: \"var(--text)\",\n flex: \"0 0 auto\",\n display: \"flex\",\n alignItems: \"center\",\n },\n },\n // Bumped from the toolIcon default (22) to give the per-tool art\n // a bit more presence next to the H1 \u2014 the topbar is the canonical\n // place to see \"which tool am I in\" and a heftier icon reads more\n // confidently. Title font-size stays at 22 so the icon now slightly\n // exceeds the cap-height (intentional visual hierarchy).\n toolIcon(props.toolName, 28),\n props.title\n ),\n ];\n if (props.middle) {\n rowChildren.push(vbar(\"vbar-middle\"));\n rowChildren.push(\n h(\n \"div\",\n {\n key: \"middle\",\n style: { flex: \"1 1 auto\", minWidth: 0, display: \"flex\", alignItems: \"center\" },\n },\n props.middle\n )\n );\n } else {\n rowChildren.push(h(\"div\", { key: \"spacer\", style: { flex: \"1 1 auto\" } }));\n }\n if (props.right) {\n rowChildren.push(vbar(\"vbar-right\"));\n rowChildren.push(\n h(\n \"div\",\n {\n key: \"right\",\n style: { flex: \"0 0 auto\", display: \"flex\", alignItems: \"center\" },\n },\n props.right\n )\n );\n }\n return h(\n \"div\",\n {\n style: {\n marginBottom: 28,\n borderBottom: \"1px solid var(--border-strong)\",\n paddingBottom: 16,\n },\n },\n h(\n \"div\",\n {\n style: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 16,\n minHeight: 40,\n },\n },\n rowChildren\n )\n );\n}\n", "// Per-tool plot-render-settings persistence \u2014 pure data layer (no JSX).\n// `PrefsPanel.tsx` wraps these in the gear-menu UI; `usePlotToolState.ts`\n// calls `loadAutoPrefs` on init and `saveAutoPrefs` on every vis update so\n// per-slider drags persist across reloads.\n//\n// Storage layout (one localStorage key per tool, prefix `dataviz-prefs-`):\n// { tool, version, savedAt, settings: { ...visStyleKeys } }\n// Label keys (`*Title` / `*Subtitle` / `*AxisLabel`) are NOT auto-persisted\n// \u2014 auto-filling them with stale text across sessions is annoying. They\n// ARE written to and read from the exported JSON file (explicit user opt-in\n// via Save / Load to file in PrefsPanel).\n//\n// `downloadText` is imported directly from `_core/download` for the\n// production path (`PrefsPanel` calls `exportPrefsFile(tool, vis)`). The\n// test loader (`tests/helpers/prefs-loader.js`) passes an explicit capture\n// callback as the third argument to avoid relying on a global stub.\n// Browser globals (`setTimeout` / `clearTimeout` / `localStorage` /\n// `document` / `FileReader`) are still stubbed by the test loader's\n// vm context.\n\nimport { downloadText as defaultDownloadText } from \"../_core/download\";\n\nconst PREFS_LABEL_KEY_RE = /(?:Title|Subtitle|AxisLabel)$/;\nexport function isLabelKey(key: string): boolean {\n return PREFS_LABEL_KEY_RE.test(key);\n}\n\nconst PREFS_STORAGE_PREFIX = \"dataviz-prefs-\";\nexport const PREFS_SCHEMA_VERSION = 1;\n\nfunction prefsStorageKey(toolName: string): string {\n return PREFS_STORAGE_PREFIX + toolName;\n}\n\n// Internal-only working type for the data layer. Public functions accept\n// any `T extends object` (so typed `VolcanoVis` / `BoxplotVis` shapes pass\n// without index-signature gymnastics) and cast to PrefsRecord at the\n// `Object.keys(...)` / `vis[key]` boundary inside.\ntype PrefsRecord = Record;\n\n// Schema migration. Today every persisted blob is v1, so the function is a\n// pass-through. When a future bump renames or restructures keys, add a\n// `case 1: settings = migrate1To2(settings); fromVersion = 2;` step and\n// fall through. Returns null to signal \"unrecognised future version, drop\"\n// \u2014 better than mixing old + new under a whitelist merge.\n//\n// Why: 22-04-2026 audit (M2) flagged that the version field was written at\n// save time but never validated at load time. Without this scaffolding the\n// next schema bump would silently mix old keys that happen to overlap with\n// new ones.\nexport function migratePrefs(settings: PrefsRecord, fromVersion: unknown): PrefsRecord | null {\n let v: number;\n if (typeof fromVersion !== \"number\" || !Number.isFinite(fromVersion)) {\n // No version field at all \u2014 treat as v1 for back-compat with blobs\n // written before the schema was numbered (none should exist today,\n // but cheap defensive default).\n v = 1;\n } else {\n v = fromVersion;\n }\n if (v > PREFS_SCHEMA_VERSION) return null; // future version we can't read\n const s = settings;\n // Migration steps land here. Example for the next bump:\n // if (v === 1) { s = migrate1To2(s); v = 2; }\n while (v < PREFS_SCHEMA_VERSION) {\n // No migration steps yet \u2014 every supported version IS the current one.\n // Reaching this with v < current means a step is missing; bail safely.\n return null;\n }\n return s;\n}\n\n// Type-check a candidate value against the default in visInit. Accepts exact\n// type matches; for null defaults (e.g. lineplot.xMin = null meaning \"auto\"),\n// also accepts null or any finite number.\nexport function isPrefValueCompatible(candidate: unknown, defaultVal: unknown): boolean {\n if (defaultVal === null) {\n if (candidate === null) return true;\n return typeof candidate === \"number\" && Number.isFinite(candidate);\n }\n if (candidate === null || candidate === undefined) return false;\n const tDefault = typeof defaultVal;\n const tCand = typeof candidate;\n if (tDefault !== tCand) return false;\n if (tDefault === \"number\" && !Number.isFinite(candidate as number)) return false;\n return true;\n}\n\n// Merge a candidate settings object into a clone of visInit, keeping only\n// whitelisted keys with compatible types. `opts.onlyStyle` drops label keys.\nexport function mergePrefsSettings(\n visInit: T,\n candidate: unknown,\n opts?: { onlyStyle?: boolean }\n): T {\n const onlyStyle = !!(opts && opts.onlyStyle);\n const visRec = visInit as unknown as PrefsRecord;\n const out: PrefsRecord = { ...visRec };\n if (!candidate || typeof candidate !== \"object\") return out as unknown as T;\n const cand = candidate as PrefsRecord;\n for (const key of Object.keys(visRec)) {\n if (!(key in cand)) continue;\n if (onlyStyle && isLabelKey(key)) continue;\n const val = cand[key];\n if (isPrefValueCompatible(val, visRec[key])) {\n out[key] = val;\n }\n }\n return out as unknown as T;\n}\n\n// Build the style-only subset of a full `vis` state (strips label keys).\nexport function extractStylePrefs(vis: T): PrefsRecord {\n const out: PrefsRecord = {};\n const visRec = vis as unknown as PrefsRecord;\n for (const key of Object.keys(visRec)) {\n if (isLabelKey(key)) continue;\n out[key] = visRec[key];\n }\n return out;\n}\n\nexport function loadAutoPrefs(toolName: string, visInit: T): T {\n try {\n const raw = localStorage.getItem(prefsStorageKey(toolName));\n if (!raw) return { ...visInit };\n const parsed = JSON.parse(raw);\n if (!parsed || typeof parsed !== \"object\") return { ...visInit };\n if (parsed.tool && parsed.tool !== toolName) return { ...visInit };\n const rawSettings: PrefsRecord = parsed.settings || parsed;\n // Run through the migration scaffold so the next schema bump has a\n // single seam to thread through. Today this is a pass-through for\n // version === 1; a stranger or unsupported future version returns\n // null, falling back to defaults.\n const settings = migratePrefs(rawSettings, parsed.version);\n if (!settings) return { ...visInit };\n return mergePrefsSettings(visInit, settings, { onlyStyle: true });\n } catch (_e) {\n return { ...visInit };\n }\n}\n\n// Per-tool debounce so rapid slider drags don't thrash localStorage.\nconst prefsSaveTimers: Record | null> = {};\nexport function saveAutoPrefs(toolName: string, vis: T): void {\n const existing = prefsSaveTimers[toolName];\n if (existing) {\n clearTimeout(existing);\n }\n prefsSaveTimers[toolName] = setTimeout(() => {\n try {\n const payload = {\n tool: toolName,\n version: PREFS_SCHEMA_VERSION,\n savedAt: new Date().toISOString(),\n settings: extractStylePrefs(vis),\n };\n localStorage.setItem(prefsStorageKey(toolName), JSON.stringify(payload));\n } catch (_e) {\n /* quota or private-mode \u2014 silent fallback */\n }\n prefsSaveTimers[toolName] = null;\n }, 300);\n}\n\n// Synchronous flush (used by tests and by clearAutoPrefs so storage state is\n// deterministic). Not called from production code paths directly.\nexport function flushAutoPrefs(toolName: string, vis: T): void {\n const existing = prefsSaveTimers[toolName];\n if (existing) {\n clearTimeout(existing);\n prefsSaveTimers[toolName] = null;\n }\n try {\n const payload = {\n tool: toolName,\n version: PREFS_SCHEMA_VERSION,\n savedAt: new Date().toISOString(),\n settings: extractStylePrefs(vis),\n };\n localStorage.setItem(prefsStorageKey(toolName), JSON.stringify(payload));\n } catch (_e) {\n /* silent */\n }\n}\n\nexport function clearAutoPrefs(toolName: string): void {\n const existing = prefsSaveTimers[toolName];\n if (existing) {\n clearTimeout(existing);\n prefsSaveTimers[toolName] = null;\n }\n try {\n localStorage.removeItem(prefsStorageKey(toolName));\n } catch (_e) {\n /* silent */\n }\n}\n\n// Build the JSON payload and trigger a browser download. Includes label keys\n// because this is an explicit user action and the resulting file is portable.\n//\n// The optional `downloader` parameter exists so the prefs test loader can\n// inject a capture function without relying on a global stub. Production\n// callers (`PrefsPanel.tsx`) omit it and get the default `_core/download`\n// `downloadText`.\nexport function exportPrefsFile(\n toolName: string,\n vis: T,\n downloader: (text: string, filename: string) => void = defaultDownloadText\n): void {\n const payload = {\n tool: toolName,\n version: PREFS_SCHEMA_VERSION,\n savedAt: new Date().toISOString(),\n settings: { ...vis },\n };\n const text = JSON.stringify(payload, null, 2);\n downloader(text, toolName + \"-settings.json\");\n}\n\nexport type ImportPrefsCallback = (\n merged: T | null,\n errorMessage: string | null\n) => void;\n\n// Open a file picker, read the selected file, validate, call cb with the\n// merged settings or an error message.\nexport function importPrefsFile(\n toolName: string,\n visInit: T,\n cb: ImportPrefsCallback\n): void {\n const input = document.createElement(\"input\");\n input.type = \"file\";\n input.accept = \".json,application/json\";\n input.style.display = \"none\";\n const cleanup = () => {\n if (input.parentNode) input.parentNode.removeChild(input);\n };\n input.addEventListener(\"change\", () => {\n const file = input.files && input.files[0];\n if (!file) {\n cleanup();\n return;\n }\n const reader = new FileReader();\n reader.onload = (ev) => {\n cleanup();\n let parsed: unknown;\n try {\n parsed = JSON.parse(String(ev.target?.result ?? \"\"));\n } catch (_e) {\n cb(null, \"Not a valid JSON file.\");\n return;\n }\n if (!parsed || typeof parsed !== \"object\") {\n cb(null, \"Not a valid settings file.\");\n return;\n }\n const obj = parsed as { tool?: string; settings?: PrefsRecord };\n if (obj.tool && obj.tool !== toolName) {\n cb(null, `This file is for the ${obj.tool} tool, not ${toolName}.`);\n return;\n }\n const settings: PrefsRecord = obj.settings || (obj as PrefsRecord);\n const merged = mergePrefsSettings(visInit, settings, { onlyStyle: false });\n cb(merged, null);\n };\n reader.onerror = () => {\n cleanup();\n cb(null, \"Could not read the selected file.\");\n };\n reader.readAsText(file);\n });\n // Some browsers need the input in the DOM for programmatic click to work.\n document.body.appendChild(input);\n input.click();\n}\n", "// Save / Load / Reset controls for plot render settings \u2014 rendered as a\n// single gear icon in PageHeader that toggles a small popover with the three\n// actions + any error message. The click-to-reveal design keeps power-user\n// chrome out of the header until asked for. Style tweaks still auto-persist\n// to localStorage in the background (see `usePlotToolState.ts` \u2192\n// `loadAutoPrefs` / `saveAutoPrefs`); these buttons cover the explicit\n// save-to-file / load-from-file / reset flow.\n\nimport { clearAutoPrefs, exportPrefsFile, importPrefsFile } from \"./prefs-store\";\n\nconst { useState, useRef, useEffect } = React;\n\ninterface PrefsPanelProps {\n // Tool name, matches the localStorage key written by `loadAutoPrefs`.\n tool: string;\n // Current vis state (passed verbatim into the exported JSON file).\n vis: T;\n // Defaults \u2014 used as the whitelist when validating an imported file.\n visInit: T;\n // Reducer dispatch; supports a `_reset: true` sentinel handled by\n // `usePlotToolState`'s reducer.\n updVis: (patch: Partial | { _reset: true }) => void;\n}\n\nexport function PrefsPanel({ tool, vis, visInit, updVis }: PrefsPanelProps) {\n const [error, setError] = useState(\"\");\n const [open, setOpen] = useState(false);\n const wrapRef = useRef(null);\n\n // Close on outside-click or Escape while the popover is open.\n useEffect(() => {\n if (!open) return undefined;\n const onDown = (ev: MouseEvent) => {\n if (wrapRef.current && !wrapRef.current.contains(ev.target as Node)) setOpen(false);\n };\n const onKey = (ev: KeyboardEvent) => {\n if (ev.key === \"Escape\") setOpen(false);\n };\n document.addEventListener(\"mousedown\", onDown);\n document.addEventListener(\"keydown\", onKey);\n return () => {\n document.removeEventListener(\"mousedown\", onDown);\n document.removeEventListener(\"keydown\", onKey);\n };\n }, [open]);\n\n const handleSave = () => {\n exportPrefsFile(tool, vis);\n setError(\"\");\n setOpen(false);\n };\n const handleLoad = () => {\n importPrefsFile(tool, visInit, (merged, errMsg) => {\n if (errMsg || !merged) {\n // Keep popover open to show the error inline.\n setError(errMsg ?? \"Could not load settings file.\");\n setOpen(true);\n return;\n }\n updVis(merged);\n setError(\"\");\n setOpen(false);\n });\n };\n const handleReset = () => {\n updVis({ _reset: true });\n clearAutoPrefs(tool);\n setError(\"\");\n setOpen(false);\n };\n\n const menuBtnStyle = {\n padding: \"6px 10px\",\n fontSize: 12,\n lineHeight: 1.2,\n textAlign: \"left\" as const,\n };\n\n return (\n
\n setOpen((x) => !x)}\n aria-label=\"Visual plot settings\"\n aria-expanded={open}\n aria-haspopup=\"menu\"\n title=\"Visual plot settings\"\n style={{\n width: 40,\n height: 40,\n padding: 0,\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: open ? \"var(--surface-sunken)\" : \"transparent\",\n border: \"1px solid var(--border)\",\n borderRadius: 6,\n cursor: \"pointer\",\n color: \"var(--text-muted)\",\n }}\n >\n {/* Gear glyph \u2014 feather-style, painted through currentColor so it\n inherits the button's themed text color. */}\n \n \n \n \n \n {open && (\n \n \n Visual plot settings\n \n \n Save to file\n \n \n Load from file\n \n \n Reset to defaults\n \n {error && (\n \n {error}\n

\n )}\n
\n )}\n \n );\n}\n", "// `StepNavBar` \u2014 horizontal stepper with circles + labels + connector\n// line. Past steps render a \u2713 on a filled --step-ready circle; the\n// current step renders its number on --step-active-bg chrome;\n// reachable-unvisited steps render a --step-ready outline; locked\n// steps render a neutral outline. Connector line between circles\n// fills --step-ready up to the last completed step.\n\nconst h = React.createElement;\n\ninterface StepNavBarProps {\n steps: string[];\n currentStep: string;\n onStepChange: (s: string) => void;\n canNavigate?: (s: string) => boolean;\n // Optional override for the visible label of a step key. Keys remain\n // the stable identifier used by navigation state; labels can be\n // dynamic (e.g. venn showing \"Import check\" vs \"Configure\").\n stepLabels?: Record;\n}\n\nexport function StepNavBar(props: StepNavBarProps) {\n const { steps, currentStep, onStepChange } = props;\n const canNavigate = props.canNavigate;\n const stepLabels = props.stepLabels || {};\n const currentIdx = steps.indexOf(currentStep);\n const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);\n const labelFor = (s: string) => stepLabels[s] || capitalize(s);\n const cells = steps.map((s, i) => {\n const enabled = canNavigate ? canNavigate(s) : true;\n const isCurrent = i === currentIdx;\n const isPast = i < currentIdx;\n const isReachableUnvisited = enabled && !isCurrent && !isPast;\n\n let circleBg: string;\n let circleBorder: string;\n let circleColor: string;\n let circleContent: React.ReactNode;\n if (isPast) {\n circleBg = \"var(--step-ready)\";\n circleBorder = \"none\";\n circleColor = \"var(--on-accent)\";\n circleContent = h(\n \"svg\",\n {\n key: \"check\",\n width: 18,\n height: 18,\n viewBox: \"0 0 24 24\",\n \"aria-hidden\": \"true\",\n style: { display: \"block\" },\n },\n h(\"path\", {\n d: \"M5 12.5l4.2 4.2L19 7\",\n fill: \"none\",\n stroke: \"currentColor\",\n strokeWidth: 3,\n strokeLinecap: \"round\",\n strokeLinejoin: \"round\",\n })\n );\n } else if (isCurrent) {\n circleBg = \"var(--step-active-bg)\";\n circleBorder = \"1px solid var(--step-active-border)\";\n circleColor = \"var(--on-accent)\";\n circleContent = String(i + 1);\n } else if (isReachableUnvisited) {\n circleBg = \"var(--surface)\";\n circleBorder = \"2px solid var(--step-ready)\";\n circleColor = \"var(--step-ready)\";\n circleContent = String(i + 1);\n } else {\n circleBg = \"var(--surface)\";\n circleBorder = \"1px solid var(--border)\";\n circleColor = \"var(--text-faint)\";\n circleContent = String(i + 1);\n }\n\n const labelColor = isCurrent\n ? \"var(--text)\"\n : isPast\n ? \"var(--text-muted)\"\n : isReachableUnvisited\n ? \"var(--text-faint)\"\n : \"var(--border)\";\n const labelWeight = isCurrent ? 600 : 500;\n\n const connector =\n i < steps.length - 1\n ? h(\"div\", {\n key: \"conn\",\n \"aria-hidden\": \"true\",\n style: {\n position: \"absolute\",\n top: 18,\n left: \"50%\",\n right: \"-50%\",\n height: 2,\n background: isPast ? \"var(--step-ready)\" : \"var(--border-strong)\",\n zIndex: 0,\n transition: \"background 160ms ease-out\",\n },\n })\n : null;\n\n // Visual weight scales with progress: past + current circles are large\n // (36 px) and reachable / locked future circles are smaller (28 px) so\n // the user's place in the flow is obvious at a glance. The current step\n // gets an additional outer ring (rendered as a CSS outline so it never\n // affects layout) in the same accent colour as the filled body \u2014\n // distinct from the 1 px border by virtue of the 2 px offset gap.\n const isLarge = isCurrent || isPast;\n const size = isLarge ? 36 : 28;\n const numberFontSize = isLarge ? 16 : 13;\n\n const circle = h(\n \"span\",\n {\n key: \"circle\",\n style: {\n position: \"relative\",\n zIndex: 1,\n width: size,\n height: size,\n borderRadius: \"50%\",\n display: \"inline-flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n background: circleBg,\n border: circleBorder,\n color: circleColor,\n fontSize: numberFontSize,\n fontWeight: 600,\n lineHeight: 1,\n boxSizing: \"border-box\",\n // Current-step halo: an outline sits OUTSIDE the box without\n // contributing to layout (unlike `border`), so the surrounding\n // circles stay anchored to the same baseline. `outlineOffset`\n // creates the visible gap between the filled body and the ring.\n ...(isCurrent\n ? {\n outline: \"2px solid var(--step-active-border)\",\n outlineOffset: \"2px\",\n }\n : null),\n transition:\n \"background 160ms ease-out, border-color 160ms ease-out, color 160ms ease-out, width 160ms ease-out, height 160ms ease-out, outline-color 160ms ease-out\",\n },\n },\n circleContent\n );\n\n // Fixed 36 px row containing the circle so smaller (future) circles stay\n // vertically centred on the connector line and labels keep a consistent\n // baseline across the row.\n const circleRow = h(\n \"div\",\n {\n key: \"circle-row\",\n style: {\n height: 36,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n position: \"relative\",\n zIndex: 1,\n },\n },\n circle\n );\n\n const label = h(\n \"span\",\n {\n key: \"label\",\n style: {\n marginTop: 6,\n fontSize: 11,\n fontWeight: labelWeight,\n color: labelColor,\n textTransform: \"capitalize\",\n letterSpacing: 0.2,\n whiteSpace: \"nowrap\",\n },\n },\n labelFor(s)\n );\n\n const button = h(\n \"button\",\n {\n key: \"btn\",\n type: \"button\",\n onClick: enabled ? () => onStepChange(s) : undefined,\n disabled: !enabled,\n \"aria-current\": isCurrent ? \"step\" : undefined,\n \"aria-label\": \"Step \" + (i + 1) + \" of \" + steps.length + \": \" + labelFor(s),\n // Stable test handle. The previous e2e selector\n // `getByRole(\"button\", { name: /Plot$/ })` matched both this pill\n // and the SPA topbar's tool-icon buttons (which also end in\n // \"... Plot\"), so .first() picked the wrong one. Tests now use\n // `getByTestId(\"step-plot\")` etc.\n \"data-testid\": \"step-\" + s,\n style: {\n all: \"unset\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n cursor: enabled ? \"pointer\" : \"default\",\n position: \"relative\",\n zIndex: 1,\n },\n },\n circleRow,\n label\n );\n\n return h(\n \"div\",\n {\n key: \"step-\" + s,\n style: {\n flex: \"1 1 0\",\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n minWidth: 0,\n },\n },\n connector,\n button\n );\n });\n\n return h(\n \"div\",\n {\n style: {\n display: \"flex\",\n alignItems: \"flex-start\",\n padding: \"8px 0 4px\",\n width: \"100%\",\n },\n },\n cells\n );\n}\n", "// `CommaFixBanner` \u2014 yellow status banner shown when the parser auto-\n// converted decimal commas to dots during ingest. `commaFixed` is the\n// boolean toggle, `commaFixCount` is the number of values fixed.\n\nconst h = React.createElement;\n\ninterface CommaFixBannerProps {\n commaFixed: boolean;\n commaFixCount: number;\n}\n\nexport function CommaFixBanner(props: CommaFixBannerProps) {\n if (!props.commaFixed) return null;\n return h(\n \"div\",\n {\n className: \"dv-panel\",\n role: \"status\",\n style: {\n background: \"var(--warning-bg)\",\n borderColor: \"var(--warning-border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 12,\n padding: \"10px 16px\",\n },\n },\n h(\"span\", { style: { fontSize: 18 }, \"aria-hidden\": \"true\" }, \"\uD83D\uDD04\"),\n h(\n \"div\",\n { style: { flex: 1 } },\n h(\n \"p\",\n { style: { margin: 0, fontSize: 12, color: \"var(--warning-text)\", fontWeight: 600 } },\n \"Decimal commas automatically converted to dots\"\n ),\n h(\n \"p\",\n {\n style: { margin: \"2px 0 0\", fontSize: 11, color: \"var(--warning-text)\", opacity: 0.85 },\n },\n props.commaFixCount +\n \" value\" +\n (props.commaFixCount > 1 ? \"s\" : \"\") +\n ' had commas as decimal separators (e.g. \"0,5\" \u2192 \"0.5\").'\n )\n )\n );\n}\n", "import type { FormulaInjectionWarning } from \"../_core/csv\";\n\n// `FormulaInjectionBanner` \u2014 yellow alert banner that surfaces the\n// result of `scanForFormulaInjection` at ingest time. Shows up when the\n// uploaded file contains cells / headers that would trigger formula\n// evaluation in Excel / LibreOffice / Sheets. Pl\u00F6ttr exports prefix\n// these with a leading apostrophe to neutralise them, but the original\n// file is unchanged so flagging the input is the safer signal.\n\nconst h = React.createElement;\n\ninterface FormulaInjectionBannerProps {\n warning: FormulaInjectionWarning | null;\n}\n\nexport function FormulaInjectionBanner(props: FormulaInjectionBannerProps) {\n const w = props.warning;\n if (!w || !w.count) return null;\n const trim = (v: unknown) => {\n const s = String(v);\n return s.length > 80 ? s.slice(0, 80) + \"\u2026\" : s;\n };\n const fmtCell = (c: { header?: string | null; row: number; col: number; value: unknown }) => {\n const where = c.header\n ? \"\u201C\" + c.header + \"\u201D row \" + (c.row + 1)\n : \"row \" + (c.row + 1) + \" col \" + (c.col + 1);\n return where + \": \" + trim(c.value);\n };\n const fmtHeader = (hdr: { idx: number; value: unknown }) => {\n return \"column \" + (hdr.idx + 1) + \": \" + trim(hdr.value);\n };\n const examples: string[] = [];\n for (let i = 0; i < w.headers.length; i++) examples.push(\"Header \u2014 \" + fmtHeader(w.headers[i]));\n for (let i = 0; i < w.cells.length; i++) examples.push(fmtCell(w.cells[i]));\n const shown = examples.length;\n const overflow = w.count - shown;\n return h(\n \"div\",\n {\n role: \"alert\",\n style: {\n marginBottom: 16,\n padding: \"10px 14px\",\n borderRadius: 8,\n background: \"var(--warning-bg)\",\n border: \"1px solid var(--warning-border)\",\n display: \"flex\",\n alignItems: \"flex-start\",\n gap: 10,\n },\n },\n h(\"span\", { style: { fontSize: 18, lineHeight: \"20px\" }, \"aria-hidden\": \"true\" }, \"\u26A0\uFE0F\"),\n h(\n \"div\",\n { style: { flex: 1, minWidth: 0 } },\n h(\n \"p\",\n { style: { margin: 0, fontSize: 12, color: \"var(--warning-text)\", fontWeight: 700 } },\n \"Suspicious cells in uploaded data (\" + w.count + (w.count === 1 ? \" cell\" : \" cells\") + \")\"\n ),\n h(\n \"p\",\n {\n style: {\n margin: \"2px 0 6px\",\n fontSize: 11,\n color: \"var(--warning-text)\",\n opacity: 0.9,\n },\n },\n \"Cells starting with \" +\n \"= + - @ tab CR\" +\n \" are treated as formulas by Excel / LibreOffice / Sheets and could exfiltrate or run code if you re-open this data there. Pl\u00F6ttr exports prefix them with a leading apostrophe to neutralise them \u2014 but the original file is unchanged, so handle with care.\"\n ),\n h(\n \"ul\",\n {\n style: {\n margin: 0,\n paddingLeft: 18,\n fontSize: 11,\n color: \"var(--warning-text)\",\n fontFamily:\n 'ui-monospace, SFMono-Regular, Menlo, Monaco, \"Cascadia Mono\", \"Liberation Mono\", monospace',\n wordBreak: \"break-all\",\n },\n },\n examples.map((e, i) => h(\"li\", { key: i }, e))\n ),\n overflow > 0\n ? h(\n \"p\",\n {\n style: {\n margin: \"4px 0 0\",\n fontSize: 11,\n color: \"var(--warning-text)\",\n opacity: 0.85,\n },\n },\n \"\u2026and \" + overflow + \" more.\"\n )\n : null\n )\n );\n}\n", "// `ParseErrorBanner` \u2014 red alert banner shown when the parser raises\n// an error string. Renders nothing when `error` is null/undefined.\n\nconst h = React.createElement;\n\ninterface ParseErrorBannerProps {\n error: string | null | undefined;\n}\n\nexport function ParseErrorBanner(props: ParseErrorBannerProps) {\n if (!props.error) return null;\n return h(\n \"div\",\n {\n role: \"alert\",\n style: {\n marginBottom: 16,\n padding: \"10px 14px\",\n borderRadius: 8,\n background: \"var(--danger-bg)\",\n border: \"1px solid var(--danger-border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n },\n },\n h(\"span\", { style: { fontSize: 16 }, \"aria-hidden\": \"true\" }, \"\uD83D\uDEAB\"),\n h(\n \"span\",\n { style: { fontSize: 12, color: \"var(--danger-text)\", fontWeight: 600 } },\n props.error\n )\n );\n}\n", "// Outer page frame shared by every plot tool. Renders PageHeader (with the\n// PrefsPanel in the top-right slot), the StepNavBar, the comma-fix banner,\n// the parse-error banner, and delegates everything else to children \u2014 each\n// tool still owns the per-step content (UploadStep, ConfigureStep, plot\n// layout, etc.) since those differ per tool.\n//\nimport type { PlotToolState } from \"./usePlotToolState\";\nimport { PrefsPanel } from \"./PrefsPanel\";\nimport { PageHeader } from \"./PageHeader\";\nimport { StepNavBar } from \"./StepNavBar\";\nimport { CommaFixBanner } from \"./CommaFixBanner\";\nimport { FormulaInjectionBanner } from \"./FormulaInjectionBanner\";\nimport { ParseErrorBanner } from \"./ParseErrorBanner\";\n\nconst { useEffect, useRef, useState } = React;\n\ninterface PlotToolShellProps {\n state: PlotToolState;\n toolName: string;\n title: string;\n visInit: TVis;\n steps: string[];\n canNavigate: (target: string) => boolean;\n // Optional override for the step label shown in the nav bar. Useful when a\n // step's *purpose* varies with dataset shape (e.g. venn's step 2 is just an\n // import check with \u2264 3 sets but becomes a real configure flow with \u2265 4).\n // Keys = step identifiers (unchanged); values = human-readable labels.\n stepLabels?: Record;\n children: React.ReactNode;\n}\n\n// 120 ms opacity fade on step change. Tracks the current step in a ref so the\n// first mount doesn't fade in, and respects prefers-reduced-motion (no fade).\nfunction StepFade({ step, children }: { step: string; children: React.ReactNode }) {\n const [opacity, setOpacity] = useState(1);\n const prevStep = useRef(step);\n const reduceMotion =\n typeof window !== \"undefined\" &&\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches;\n useEffect(() => {\n if (prevStep.current === step || reduceMotion) {\n prevStep.current = step;\n return undefined;\n }\n prevStep.current = step;\n setOpacity(0);\n const id = window.setTimeout(() => setOpacity(1), 10);\n return () => window.clearTimeout(id);\n }, [step, reduceMotion]);\n return (\n
\n {children}\n
\n );\n}\n\nexport function PlotToolShell({\n state,\n toolName,\n title,\n visInit,\n steps,\n canNavigate,\n stepLabels,\n children,\n}: PlotToolShellProps) {\n return (\n
\n \n }\n right={\n state.step === \"plot\" ? (\n \n ) : null\n }\n />\n \n \n \n {children}\n
\n );\n}\n", "// Plot card that adds horizontal-scroll affordances when its SVG content is\n// wider than the viewport: a left fade when scrolled away from the start,\n// a right fade when more content remains, and a transient \"Scroll for more\"\n// pill that hides once the user has scrolled. Every affordance is driven by\n// a ResizeObserver on the scroll container + its first child so the\n// overlays stay accurate across window resizes and SVG re-renders.\n//\n// Used only by upset.tsx today. Lives in `_shell/` because the lifted\n// behaviour was extracted from the original inline implementation in\n// upset.tsx; venn and heatmap don't need the affordance (their charts\n// auto-fit). Keep here as the canonical home for any future tool that grows\n// horizontally-scrolling content.\n\nconst { useState, useRef, useEffect, useCallback } = React;\n\ninterface ScrollablePlotCardProps {\n children: React.ReactNode;\n}\n\nexport function ScrollablePlotCard({ children }: ScrollablePlotCardProps) {\n const scrollRef = useRef(null);\n const [hasOverflow, setHasOverflow] = useState(false);\n const [atStart, setAtStart] = useState(true);\n const [atEnd, setAtEnd] = useState(true);\n const [userScrolled, setUserScrolled] = useState(false);\n\n const measure = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n const overflows = el.scrollWidth > el.clientWidth + 1;\n setHasOverflow(overflows);\n setAtStart(el.scrollLeft <= 1);\n setAtEnd(el.scrollLeft + el.clientWidth >= el.scrollWidth - 1);\n }, []);\n\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return undefined;\n measure();\n const ro = new ResizeObserver(measure);\n ro.observe(el);\n const content = el.firstElementChild;\n if (content) ro.observe(content);\n return () => ro.disconnect();\n }, [measure]);\n\n const onScroll = () => {\n measure();\n if (!userScrolled) setUserScrolled(true);\n };\n\n return (\n
\n \n {children}\n
\n {hasOverflow && !atStart && (\n \n )}\n {hasOverflow && !atEnd && (\n \n )}\n {hasOverflow && !userScrolled && (\n \n Scroll for more \u2192\n \n )}\n \n );\n}\n", "// Single source of truth for the inferential tests `selectTest()` can\n// recommend, plus the post-hoc family that pairs with each k\u22653 test.\n//\n// Pre-registry, the same test-id \u2192 (function, label, post-hoc, k=2\n// membership) mapping lived in 4+ places (`_shell/stats-dispatch.ts`,\n// `shared-stats-tile.js`, `aequorin/reports.ts`, the R-export switch in\n// `shared-r-export.js`); every site string-matched on identifiers like\n// `\"welchT\"` independently. The registry collapses dispatch + label\n// lookup + post-hoc routing + arity classification into one declarative\n// table.\n//\n// Naming asymmetry note: `selectTest()` returns `\"welchT\"` for k=2 even\n// though no `welchT` function exists \u2014 it dispatches to\n// `tTest(x, y, { equalVar: false })`. `\"welchANOVA\"` does match a\n// function. The registry is the only place that reconciles the\n// asymmetry; consumers don't need to know about it.\n\nimport { dunnTest, gamesHowell, tukeyHSD } from \"../_core/stats/posthoc\";\nimport { kruskalWallis, mannWhitneyU, oneWayANOVA, tTest, welchANOVA } from \"../_core/stats/tests\";\n\n// `RecommendedTest`, `RecommendedPostHoc`, `TestArity`, `StatsTestEntry`,\n// `StatsPostHocEntry` types stay declared in `types/globals.d.ts`\n// (ambient). selectTest() in stats-posthoc.js (still plain-JS) uses\n// them as ambient types; migrating the types out of globals.d.ts would\n// break that file.\n\nexport const STATS_TEST_REGISTRY: Record = {\n studentT: {\n label: \"Student's t-test\",\n shortLabel: \"Student's t\",\n arity: 2,\n postHoc: null,\n run: (values) => tTest(values[0], values[1], { equalVar: true }),\n },\n welchT: {\n label: \"Welch's t-test\",\n shortLabel: \"Welch's t\",\n arity: 2,\n postHoc: null,\n run: (values) => tTest(values[0], values[1], { equalVar: false }),\n },\n mannWhitney: {\n label: \"Mann-Whitney U\",\n arity: 2,\n postHoc: null,\n run: (values) => mannWhitneyU(values[0], values[1]),\n },\n oneWayANOVA: {\n label: \"One-way ANOVA\",\n arity: \"k\",\n postHoc: \"tukeyHSD\",\n run: (values) => oneWayANOVA(values),\n },\n welchANOVA: {\n label: \"Welch's ANOVA\",\n arity: \"k\",\n postHoc: \"gamesHowell\",\n run: (values) => welchANOVA(values),\n },\n kruskalWallis: {\n label: \"Kruskal-Wallis\",\n arity: \"k\",\n postHoc: \"dunn\",\n run: (values) => kruskalWallis(values),\n },\n};\n\nexport const STATS_POSTHOC_REGISTRY: Record<\n Exclude,\n StatsPostHocEntry\n> = {\n tukeyHSD: {\n label: \"Tukey HSD\",\n run: (values) => tukeyHSD(values),\n },\n gamesHowell: {\n label: \"Games-Howell\",\n run: (values) => gamesHowell(values),\n },\n dunn: {\n label: \"Dunn (BH-adjusted)\",\n run: (values) => dunnTest(values),\n },\n};\n\n// Used by UI dropdowns that surface \"what tests apply to two groups?\"\n// vs \"what tests apply to k\u22653 groups?\". Computed from the registry so\n// a new test entry with the right `arity` lights up the UI without a\n// second edit site.\nexport const STATS_TESTS_FOR_K2: RecommendedTest[] = (\n Object.keys(STATS_TEST_REGISTRY) as RecommendedTest[]\n).filter((id) => STATS_TEST_REGISTRY[id].arity === 2);\nexport const STATS_TESTS_FOR_K: RecommendedTest[] = (\n Object.keys(STATS_TEST_REGISTRY) as RecommendedTest[]\n).filter((id) => STATS_TEST_REGISTRY[id].arity === \"k\");\n", "// Generates a runnable R script from the StatsTile decision trace so the\n// user can paste it into RStudio and reproduce the exact same tests in a\n// \"real\" statistics environment. Closes the \"no reproducibility trail\"\n// gap called out in the README's Scope & limitations section.\n//\n// Public exports:\n// buildRScript(ctx) \u2014 for data-driven stats (boxplot, aequorin)\n// buildRScriptForPower(state) \u2014 for the power-analysis tool\n// sanitizeRString(s) \u2014 escape \" and \\ for safe R string inlining\n// sanitizeRComment(s) \u2014 strip line terminators for `#` comments\n// formatRNumber(n) \u2014 Number \u2192 R literal (period decimals,\n// NA for non-finite)\n// formatRVector(arr, perLine?) \u2014 number[] \u2192 wrapped \"c(...)\" literal\n// wrapRItems(items, perLine?) \u2014 pre-formatted items \u2192 wrapped \"c(...)\";\n// the single line-wrapping helper, capped\n// at R_MAX_LINE characters per line\n// R_MAX_LINE \u2014 hard per-line char cap for emitted R\n//\n// The ctx shape matches the StatsTile's internal report-builder shape so\n// the R-script chip can reuse the exact same context object without\n// threading new props.\n\nimport { STATS_TEST_REGISTRY, STATS_POSTHOC_REGISTRY } from \"./stats-registry\";\n\n// Test / post-hoc display labels are sourced from the shared registry \u2014\n// same labels the StatsTile uses on screen, so the R script's\n// `# Welch's t-test` comment matches the test name shown to the user.\n// The R-code generation below still has per-test branches because each\n// test produces a different R function call (`t.test`, `wilcox.test`,\n// `oneway.test`, \u2026) \u2014 the dispatch lives in this file because the\n// emitted strings are R-specific, not part of the JS dispatcher\n// surface that the registry collapses.\nconst _R_TEST_LABELS: Record = Object.fromEntries(\n Object.entries(STATS_TEST_REGISTRY).map((entry) => [entry[0], entry[1].label])\n);\n\nconst _R_POSTHOC_LABELS: Record = Object.fromEntries(\n Object.entries(STATS_POSTHOC_REGISTRY).map((entry) => [entry[0], entry[1].label])\n);\n\nexport function sanitizeRString(s: unknown): string {\n // Escape backslashes first, then double-quotes. All line terminators\n // (LF, CR, NEL, LS, PS) are flattened to a single space \u2014 a multi-line\n // factor level is almost certainly a paste accident and would break\n // the one-line data.frame layout. The CR strip is also a\n // security-relevant defence: R's lexer treats `\\r` as a statement\n // terminator inside source files, so a column name like\n // `\"foo\\rsystem('cmd')\"` would otherwise escape the surrounding R\n // string in some contexts.\n return String(s)\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\"/g, '\\\\\"')\n .replace(/[\\r\\n\\u0085\\u2028\\u2029]/g, \" \");\n}\n\n// For any user-supplied string that lands in a `# ...` R comment line.\n// `sanitizeRString` is for *quoted* string literals; comments need a\n// stricter scrub \u2014 a CR or LF inside a comment ends the comment, so any\n// embedded line terminator must be flattened. Backslashes / quotes are\n// left alone (they're harmless inside a comment).\nexport function sanitizeRComment(s: unknown): string {\n return String(s).replace(/[\\r\\n\\u0085\\u2028\\u2029]/g, \" \");\n}\n\nexport function formatRNumber(n: number | null | undefined): string {\n if (n == null || !Number.isFinite(n)) return \"NA\";\n return String(n);\n}\n\n// Hard cap on the length of any single line of generated R source. R's\n// parser rejects an input line longer than 4094 characters; 120 keeps\n// the output well clear of that and roughly in line with the repo's own\n// formatting width. Every line of a `c(...)` literal returned by\n// `wrapRItems` is guaranteed to be within this cap.\nexport const R_MAX_LINE = 120;\n\n// Margin subtracted from R_MAX_LINE when wrapRItems decides whether a\n// vector is short enough to keep on a single line. Callers prepend an\n// assignment prefix (`x <- `, `row_labels <- `, ` group = `, \u2026) that\n// the helper cannot see; reserving 20 chars keeps the compact form's\n// physical line within R_MAX_LINE for every prefix the codebase emits.\nconst _R_PREFIX_RESERVE = 20;\n\n// The single line-wrapping helper for inlined R vectors. Packs the\n// pre-formatted `items` into a `c(...)` literal, breaking to a new\n// indented line whenever either `perLine` items have been placed or the\n// next item would push the line past R_MAX_LINE. `formatRVector`, the\n// long-format data.frame builder, the heatmap label / matrix emit and\n// volcano's data columns all route through here \u2014 there is no other\n// wrap path.\n//\n// `perLine` (default 8) is a soft hint; the R_MAX_LINE cap always wins,\n// so a caller may pass a large `perLine` (e.g. one heatmap matrix row\n// per line) and still get a re-wrap when that row is too wide. A single\n// item longer than the cap unavoidably gets an over-long line of its\n// own \u2014 an R token cannot be split.\nexport function wrapRItems(items: string[], perLine?: number): string {\n if (items.length === 0) return \"c()\";\n const P = perLine && perLine > 0 ? perLine : 8;\n const indent = \" \";\n // A wrapped content line carries a trailing \",\" (all but the last),\n // so budget one char for it when measuring against the cap.\n const lineBudget = R_MAX_LINE - indent.length - 1;\n const rows: string[] = [];\n let cur: string[] = [];\n let curLen = 0; // length of cur.join(\", \")\n for (const item of items) {\n if (cur.length > 0 && (cur.length >= P || curLen + 2 + item.length > lineBudget)) {\n rows.push(indent + cur.join(\", \"));\n cur = [];\n curLen = 0;\n }\n curLen += cur.length === 0 ? item.length : 2 + item.length;\n cur.push(item);\n }\n if (cur.length > 0) rows.push(indent + cur.join(\", \"));\n if (rows.length === 1) {\n const oneLine = \"c(\" + items.join(\", \") + \")\";\n if (oneLine.length <= R_MAX_LINE - _R_PREFIX_RESERVE) return oneLine;\n }\n return \"c(\\n\" + rows.join(\",\\n\") + \"\\n )\";\n}\n\n// number[] \u2192 a `c(...)` literal, line-wrapped via wrapRItems.\nexport function formatRVector(arr: Array, perLine?: number): string {\n return wrapRItems(arr.map(formatRNumber), perLine);\n}\n\n// Build a long-format data.frame literal: one row per observation, columns\n// `group` (character, re-factored with the tile's display order) and `value`.\nfunction _longFormatDataFrame(names: string[], values: number[][], varName?: string): string {\n const vn = varName || \"df\";\n const groupEntries: string[] = [];\n const valueEntries: string[] = [];\n for (let i = 0; i < names.length; i++) {\n const quoted = '\"' + sanitizeRString(names[i]) + '\"';\n const vs = values[i] || [];\n for (let j = 0; j < vs.length; j++) {\n groupEntries.push(quoted);\n valueEntries.push(formatRNumber(vs[j]));\n }\n }\n // The factor-level vector is emitted as its own assignment rather\n // than inlined into factor(): a long `levels = c(...)` argument would\n // carry a ~40-char `factor(...)` prefix that wrapRItems cannot see, so\n // a standalone assignment keeps every line within R_MAX_LINE.\n const levelItems = names.map((n) => '\"' + sanitizeRString(n) + '\"');\n return [\n vn + \" <- data.frame(\",\n \" group = \" + wrapRItems(groupEntries) + \",\",\n \" value = \" + wrapRItems(valueEntries) + \",\",\n \" stringsAsFactors = FALSE\",\n \")\",\n vn + \"_levels <- \" + wrapRItems(levelItems),\n vn + \"$group <- factor(\" + vn + \"$group, levels = \" + vn + \"_levels)\",\n ].join(\"\\n\");\n}\n\nfunction _rPackagesFor(postHocName: string | null | undefined): string[] {\n const pkgs = [\"car\"];\n if (postHocName === \"gamesHowell\" || postHocName === \"dunn\") pkgs.push(\"rstatix\");\n return pkgs;\n}\n\nfunction _headerComment(generated: string, dataNote: string | null | undefined): string {\n const lines = [\n \"# -----------------------------------------------------------------------------\",\n \"# Pl\u00F6ttr \u2014 R script export\",\n \"# Generated: \" + generated,\n \"#\",\n \"# This script reproduces the statistical tests run in the browser tool.\",\n \"# Plots are intentionally omitted \u2014 regenerate them in ggplot2 from `df` below.\",\n ];\n if (dataNote) {\n lines.push(\"#\");\n // Split on every line-terminator R recognises so a hostile multi-line\n // `dataNote` becomes multiple comment lines \u2014 each one then run through\n // sanitizeRComment so a stray terminator the split missed still can't\n // escape the comment.\n const noteLines = String(dataNote).split(/\\r\\n|[\\r\\n\\u0085\\u2028\\u2029]/);\n for (let i = 0; i < noteLines.length; i++) {\n lines.push(\"# \" + sanitizeRComment(noteLines[i]));\n }\n }\n lines.push(\"# -----------------------------------------------------------------------------\");\n return lines.join(\"\\n\");\n}\n\nfunction _mainTestBlock(chosenTest: string | null | undefined): string {\n const header = \"# --- Main test ---------------------------------------------------------------\";\n const label = (chosenTest && _R_TEST_LABELS[chosenTest]) || chosenTest || \"\u2014\";\n const pickComment = \"# Toolbox picked: \" + label;\n let call: string;\n if (chosenTest === \"studentT\") {\n call = \"t.test(value ~ group, data = df, var.equal = TRUE)\";\n } else if (chosenTest === \"welchT\") {\n call = \"t.test(value ~ group, data = df, var.equal = FALSE)\";\n } else if (chosenTest === \"mannWhitney\") {\n call = \"wilcox.test(value ~ group, data = df, exact = FALSE)\";\n } else if (chosenTest === \"oneWayANOVA\") {\n call = \"fit <- aov(value ~ group, data = df)\\nsummary(fit)\";\n } else if (chosenTest === \"welchANOVA\") {\n call = \"oneway.test(value ~ group, data = df, var.equal = FALSE)\";\n } else if (chosenTest === \"kruskalWallis\") {\n call = \"kruskal.test(value ~ group, data = df)\";\n } else {\n call = \"# (no inferential test was run)\";\n }\n return [header, pickComment, call].join(\"\\n\");\n}\n\nfunction _postHocBlock(postHocName: string | null | undefined, k: number): string {\n if (!postHocName || k < 3) return \"\";\n const header = \"# --- Post-hoc ----------------------------------------------------------------\";\n const label = _R_POSTHOC_LABELS[postHocName] || postHocName;\n const pickComment = \"# Toolbox picked: \" + label;\n let call: string;\n if (postHocName === \"tukeyHSD\") {\n call = \"TukeyHSD(aov(value ~ group, data = df))\";\n } else if (postHocName === \"gamesHowell\") {\n call = \"rstatix::games_howell_test(df, value ~ group)\";\n } else if (postHocName === \"dunn\") {\n call = 'rstatix::dunn_test(df, value ~ group, p.adjust.method = \"BH\")';\n } else {\n call = \"# (unknown post-hoc)\";\n }\n return [header, pickComment, call].join(\"\\n\");\n}\n\nexport interface BuildRScriptCtx {\n names?: string[];\n values?: number[][];\n recommendation?: { recommendation?: { reason?: string } } | null;\n chosenTest?: string | null;\n postHocName?: string | null;\n dataNote?: string | null;\n generatedAt?: string;\n}\n\nexport function buildRScript(ctx: BuildRScriptCtx | null | undefined): string {\n const names = (ctx && ctx.names) || [];\n const values = (ctx && ctx.values) || [];\n const recommendation = ctx && ctx.recommendation;\n const chosenTest = ctx && ctx.chosenTest;\n const postHocName = ctx && ctx.postHocName;\n const dataNote = ctx && ctx.dataNote;\n const generated = (ctx && ctx.generatedAt) || new Date().toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n\n const k = names.length;\n const pkgs = _rPackagesFor(postHocName);\n\n const parts: string[] = [];\n parts.push(_headerComment(generated, dataNote));\n parts.push(\"\");\n parts.push(\"# install.packages(c(\" + pkgs.map((p) => '\"' + p + '\"').join(\", \") + \"))\");\n for (let i = 0; i < pkgs.length; i++) parts.push(\"library(\" + pkgs[i] + \")\");\n parts.push(\"\");\n\n parts.push(_longFormatDataFrame(names, values, \"df\"));\n parts.push(\"\");\n\n parts.push(\"# --- Descriptive statistics --------------------------------------------------\");\n parts.push(\"desc <- do.call(rbind, by(df$value, df$group, function(v) {\");\n parts.push(\" n <- length(v)\");\n parts.push(\" m <- mean(v)\");\n parts.push(\" s <- if (n > 1) sd(v) else 0\");\n parts.push(\" sem <- if (n > 1) s / sqrt(n) else 0\");\n parts.push(\" ci95 <- if (n > 1) qt(0.975, n - 1) * sem else 0\");\n parts.push(\" data.frame(n = n, mean = m, sd = s, sem = sem, ci95 = ci95)\");\n parts.push(\"}))\");\n parts.push(\"print(desc)\");\n parts.push(\"\");\n\n parts.push(\"# --- Assumptions -------------------------------------------------------------\");\n parts.push(\"by(df$value, df$group, shapiro.test)\");\n parts.push('car::leveneTest(value ~ group, data = df, center = \"median\")');\n parts.push(\"\");\n\n parts.push(_mainTestBlock(chosenTest));\n\n const ph = _postHocBlock(postHocName, k);\n if (ph) {\n parts.push(\"\");\n parts.push(ph);\n }\n\n const reason =\n recommendation && recommendation.recommendation && recommendation.recommendation.reason;\n if (reason) {\n parts.push(\"\");\n parts.push(\"# Decision-tree rationale (from the toolbox):\");\n const rLines = String(reason).split(/\\r\\n|[\\r\\n\\u0085\\u2028\\u2029]/);\n for (let i = 0; i < rLines.length; i++) parts.push(\"# \" + sanitizeRComment(rLines[i]));\n }\n\n return parts.join(\"\\n\") + \"\\n\";\n}\n\n// \u2500\u2500 Power analysis \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst _R_POWER_TEST_LABELS: Record = {\n \"t-ind\": \"Two-sample t-test (independent)\",\n \"t-paired\": \"Paired t-test\",\n \"t-one\": \"One-sample t-test\",\n anova: \"One-way ANOVA\",\n chi2: \"Chi-square test\",\n correlation: \"Correlation test\",\n};\n\nfunction _tTypeForTestKey(testKey: string): string | null {\n if (testKey === \"t-ind\") return \"two.sample\";\n if (testKey === \"t-paired\") return \"paired\";\n if (testKey === \"t-one\") return \"one.sample\";\n return null;\n}\n\nfunction _alternativeForTails(tails: number | undefined): string {\n return tails === 1 ? \"one.sided\" : \"two.sided\";\n}\n\nfunction _pwrArg(\n arg: string,\n value: number | null | undefined,\n solveFor: string | undefined,\n annotation?: string\n): string {\n const isSolveTarget =\n (arg === \"n\" && solveFor === \"n\") || (arg === \"power\" && solveFor === \"power\");\n const rhs = isSolveTarget ? \"NULL\" : formatRNumber(value);\n const suffix = isSolveTarget ? \" # <- solve for this\" : annotation ? \" # \" + annotation : \"\";\n return \" \" + arg + \" = \" + rhs + \",\" + suffix;\n}\n\nfunction _pwrArgString(arg: string, value: string | null): string {\n return \" \" + arg + ' = \"' + value + '\",';\n}\n\nexport interface PowerScriptState {\n testKey?: string;\n solveFor?: string;\n es?: number;\n n?: number;\n alpha?: number;\n power?: number;\n tails?: number;\n k?: number;\n df?: number;\n result?: number | null;\n generatedAt?: string;\n}\n\nfunction _pwrCallBody(testKey: string, state: PowerScriptState): string[] {\n const solveFor = state.solveFor;\n const tTypeArg = _tTypeForTestKey(testKey);\n const lines: string[] = [];\n if (testKey === \"t-ind\" || testKey === \"t-paired\" || testKey === \"t-one\") {\n lines.push(_pwrArg(\"n\", state.n, solveFor));\n lines.push(_pwrArg(\"d\", state.es, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n lines.push(_pwrArgString(\"type\", tTypeArg));\n lines.push(_pwrArgString(\"alternative\", _alternativeForTails(state.tails)));\n } else if (testKey === \"anova\") {\n lines.push(_pwrArg(\"k\", state.k, solveFor));\n lines.push(_pwrArg(\"n\", state.n, solveFor));\n lines.push(_pwrArg(\"f\", state.es, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n } else if (testKey === \"chi2\") {\n lines.push(_pwrArg(\"w\", state.es, solveFor));\n // pwr::pwr.chisq.test uses N (not n) for the total sample size.\n const Ntag = solveFor === \"n\" ? \"NULL\" : formatRNumber(state.n);\n const Nsuffix = solveFor === \"n\" ? \" # <- solve for this\" : \"\";\n lines.push(\" N = \" + Ntag + \",\" + Nsuffix);\n lines.push(_pwrArg(\"df\", state.df, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n } else if (testKey === \"correlation\") {\n lines.push(_pwrArg(\"n\", state.n, solveFor));\n lines.push(_pwrArg(\"r\", state.es, solveFor));\n lines.push(_pwrArg(\"sig.level\", state.alpha, solveFor));\n lines.push(_pwrArg(\"power\", state.power, solveFor));\n lines.push(_pwrArgString(\"alternative\", _alternativeForTails(state.tails)));\n }\n // Drop the trailing comma from whatever the last non-comment line is.\n for (let i = lines.length - 1; i >= 0; i--) {\n const ln = lines[i];\n const commentIdx = ln.indexOf(\"#\");\n const beforeComment = commentIdx >= 0 ? ln.slice(0, commentIdx) : ln;\n const trimmed = beforeComment.replace(/,\\s*$/, \" \");\n lines[i] = commentIdx >= 0 ? trimmed + ln.slice(commentIdx) : trimmed.replace(/\\s+$/, \"\");\n break;\n }\n return lines;\n}\n\nfunction _pwrCallName(testKey: string | undefined): string | null {\n if (testKey === \"t-ind\" || testKey === \"t-paired\" || testKey === \"t-one\") {\n return \"pwr::pwr.t.test\";\n }\n if (testKey === \"anova\") return \"pwr::pwr.anova.test\";\n if (testKey === \"chi2\") return \"pwr::pwr.chisq.test\";\n if (testKey === \"correlation\") return \"pwr::pwr.r.test\";\n return null;\n}\n\nexport function buildRScriptForPower(state: PowerScriptState | null | undefined): string {\n const s = state || {};\n const testKey = s.testKey;\n const call = testKey ? _pwrCallName(testKey) : null;\n const generated = s.generatedAt || new Date().toISOString().replace(/\\.\\d{3}Z$/, \"Z\");\n\n const lines: string[] = [];\n lines.push(\"# -----------------------------------------------------------------------------\");\n lines.push(\"# Pl\u00F6ttr \u2014 Power analysis R export\");\n lines.push(\"# Generated: \" + generated);\n lines.push(\"#\");\n lines.push(\n \"# Test: \" + ((testKey && _R_POWER_TEST_LABELS[testKey]) || testKey || \"(unknown)\")\n );\n lines.push(\n \"# Solving for: \" +\n (s.solveFor === \"n\"\n ? \"sample size (n)\"\n : s.solveFor === \"power\"\n ? \"power\"\n : s.solveFor || \"(unknown)\")\n );\n lines.push(\"#\");\n lines.push(\"# Required package:\");\n lines.push('# install.packages(\"pwr\")');\n lines.push(\"# -----------------------------------------------------------------------------\");\n lines.push(\"\");\n lines.push(\"library(pwr)\");\n lines.push(\"\");\n\n if (!call || !testKey) {\n lines.push(\"# (unknown test id \u2014 no pwr call emitted)\");\n return lines.join(\"\\n\") + \"\\n\";\n }\n\n lines.push(call + \"(\");\n const body = _pwrCallBody(testKey, s);\n for (let i = 0; i < body.length; i++) lines.push(body[i]);\n lines.push(\")\");\n\n if (s.result != null && Number.isFinite(s.result)) {\n lines.push(\"\");\n if (s.solveFor === \"n\") {\n lines.push(\"# Toolbox reported: n = \" + s.result);\n } else if (s.solveFor === \"power\") {\n lines.push(\"# Toolbox reported: power = \" + (s.result * 100).toFixed(1) + \"%\");\n }\n }\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n", "// `DatasheetIcon` \u2014 single shared hand-drawn notepad icon for the\n// sample-dataset banner on every plot tool's upload step. Replaced\n// the per-tool `TOOL_ICONS` art that briefly lived in that slot \u2014\n// repeating the tool icon there made the upload page top-heavy with\n// the same glyph (topbar + sample banner + how-to chevron) and over-\n// claimed visual centre; a plain datasheet sketch signals \"load the\n// example data\" without competing with the topbar's branding role.\n//\n// Drawing notes:\n// - Dog-eared page outline (top-right corner folded under) so it\n// reads as a paper / notebook rather than a generic rectangle.\n// - Four horizontal \"rows\" inside, the last one short on purpose to\n// suggest a trailing partial row in a real spreadsheet.\n// - Subtle hand-drawn wobble on the rows via quadratic curves (~0.5\n// px control-point offset). Outline is straight \u2014 over-sketched\n// edges read as cartoony at small sizes, the wobble belongs in the\n// interior strokes only.\n// - `stroke=\"currentColor\"` so the icon inherits the surrounding\n// text colour (e.g. `var(--success-text)` inside the sample\n// banner) and themes automatically in dark mode.\n\ninterface DatasheetIconProps {\n size?: number;\n}\n\nexport function DatasheetIcon({ size = 36 }: DatasheetIconProps) {\n return (\n \n {/* Page outline with dog-eared corner */}\n \n {/* The fold itself \u2014 short L joining the page edge to the dog-ear */}\n \n {/* Four rows with subtle hand-drawn wobble. Last row deliberately\n short so the spreadsheet \"feels\" alive rather than printed. */}\n \n \n \n \n \n );\n}\n", "// `UploadPanel` \u2014 separator selector + FileDropZone combo for the\n// upload step. Disables the drop zone until a separator is picked\n// (legacy mode). When `autoDetect` is on, the picker is hidden by\n// default \u2014 the tool's `doParse` calls `autoDetectSep` instead \u2014\n// and an \"Override separator \u25BE\" disclosure exposes the picker for\n// the edge cases the auto-detector can't resolve. With `onTextPaste`\n// set, Drop and Paste render side-by-side as equally-prominent cards\n// so the paste path is discoverable without a toggle.\n//\n// When `exampleSummary` is supplied alongside `autoDetect`, the\n// sample-dataset CTA promotes from the cramped \"Try sample data:\" line\n// beneath the cards into a prominent banner at the very top of the\n// upload step \u2014 icon, title, dataset description, primary button \u2014\n// because the legacy treatment is invisible under the two large cards.\n//\n// All three of these behaviours are opt-in props so the 7 other plot\n// tools keep their existing UX untouched while we evaluate the\n// auto-detect + paste + prominent-sample flow on boxplot.\n\nimport { DatasheetIcon } from \"./DatasheetIcon\";\nimport { FILE_LIMIT_BYTES, FILE_WARN_BYTES, FileDropZone } from \"./FileDropZone\";\n\nconst h = React.createElement;\nconst { useState } = React;\n\n// Structured payload for the prominent sample-dataset banner in\n// auto-detect mode. `icon` accepts any React node so callers can pass\n// the polished per-tool SVG via `toolIcon(, 32, { circle: true })`\n// from `shared.js` \u2014 that's the canonical pro-looking choice and what\n// every plot tool currently uses. Legacy emoji strings still render\n// fine (React treats them as text children). When this prop is\n// missing, the banner falls back to a generic \"Try a sample dataset\"\n// pitch with `exampleLabel` as the subtitle (legacy compat).\nexport interface ExampleSummary {\n title: string;\n subtitle?: string;\n icon?: React.ReactNode;\n buttonLabel?: string;\n}\n\ninterface UploadPanelProps {\n sepOverride: string;\n onSepChange: (s: string) => void;\n onFileLoad: (text: string, fileName: string) => void;\n onLoadExample?: () => void;\n exampleLabel?: React.ReactNode;\n hint?: string;\n // Opt-in: hide the separator picker behind an \"Override\" disclosure\n // and enable the drop zone immediately. The tool's `doParse` is\n // responsible for running `autoDetectSep(text, sepOverride)` itself\n // (sepOverride stays \"\" until the user expands and picks a value).\n autoDetect?: boolean;\n // Opt-in: surface a \"Paste data\" card alongside \"Drop file\" that\n // routes the textarea contents through the same parse pipeline as\n // a dropped file. Gates on FILE_LIMIT_BYTES / FILE_WARN_BYTES per\n // the ingest size policy in tools/CLAUDE.md.\n onTextPaste?: (text: string, fileName: string) => void;\n // Opt-in (autoDetect only): structured description for the prominent\n // sample-dataset banner at the top of the upload step. Replaces the\n // tiny bottom-of-card \"Try sample data: [button]\" affordance whose\n // visibility collapsed once Drop + Paste landed side-by-side.\n exampleSummary?: ExampleSummary;\n}\n\nexport function UploadPanel(props: UploadPanelProps) {\n if (props.autoDetect) {\n return ;\n }\n const { sepOverride, onSepChange, onFileLoad, onLoadExample, exampleLabel, hint } = props;\n return h(\n \"div\",\n { className: \"dv-panel\" },\n h(\n \"div\",\n {\n style: {\n marginBottom: 12,\n padding: \"12px 16px\",\n background: \"var(--info-bg)\",\n borderRadius: 8,\n border: \"1.5px solid var(--info-border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 10,\n },\n },\n h(\n \"label\",\n {\n htmlFor: \"dv-separator-select\",\n style: { fontSize: 13, fontWeight: 600, color: \"var(--accent-primary)\" },\n },\n \"Column separator\"\n ),\n h(\n \"select\",\n {\n id: \"dv-separator-select\",\n value: sepOverride,\n onChange: (e: React.ChangeEvent) => onSepChange(e.target.value),\n className: \"dv-select-sep\",\n },\n h(\"option\", { value: \"\" }, \"\u2014 Select \u2014\"),\n h(\"option\", { value: \",\" }, \"Comma (,)\"),\n h(\"option\", { value: \";\" }, \"Semicolon (;)\"),\n h(\"option\", { value: \"\\t\" }, \"Tab (\\\\t)\"),\n h(\"option\", { value: \" \" }, \"Space\")\n )\n ),\n !sepOverride\n ? h(\n \"div\",\n {\n style: {\n border: \"2px dashed var(--border-strong)\",\n borderRadius: 12,\n padding: \"48px 24px\",\n textAlign: \"center\",\n background: \"var(--surface-sunken)\",\n opacity: 0.5,\n },\n },\n h(\"div\", { style: { fontSize: 40, marginBottom: 8 }, \"aria-hidden\": \"true\" }, \"\uD83D\uDEAB\"),\n h(\n \"p\",\n { style: { margin: 0, fontSize: 15, color: \"var(--text-faint)\" } },\n \"Pick a column separator above to enable file loading\"\n )\n )\n : h(FileDropZone, {\n onFileLoad,\n accept: \".csv,.tsv,.txt,.dat,.tab\",\n hint: hint || \"CSV \u00B7 TSV \u00B7 TXT \u00B7 DAT \u2014 2 MB max\",\n }),\n onLoadExample\n ? h(\n \"div\",\n {\n style: {\n marginTop: 12,\n display: \"flex\",\n alignItems: \"center\",\n justifyContent: \"center\",\n gap: 8,\n fontSize: 12,\n color: \"var(--text-muted)\",\n },\n },\n h(\"span\", null, \"Try sample data:\"),\n h(\n \"button\",\n {\n type: \"button\",\n className: \"dv-btn dv-btn-secondary\",\n onClick: onLoadExample,\n \"data-testid\": \"load-example\",\n },\n exampleLabel || \"Load example \u2192\"\n )\n )\n : null\n );\n}\n\n// New auto-detect + side-by-side variant. Behaviour:\n// - The separator picker is collapsed behind an \"Override \u25BE\" disclosure\n// that starts closed. The drop zone is enabled immediately; the tool's\n// parse pipeline calls autoDetectSep(text, sepOverride) so empty\n// sepOverride means \"auto\".\n// - When `onTextPaste` is provided, the panel renders Drop / Paste as\n// two equally-prominent bordered cards in a 2-column grid \u2014 both\n// always visible, no tab toggle to discover. The Paste card holds a\n// textarea + \"Parse pasted data\" button; size is measured with\n// `new Blob([text]).size` to match FileDropZone's FILE_LIMIT_BYTES\n// gate exactly.\n// - \"Try sample data\" stays in the same position as the legacy panel so\n// muscle memory and the load-example test selector still work.\nfunction AutoDetectUploadPanel(props: UploadPanelProps) {\n const {\n sepOverride,\n onSepChange,\n onFileLoad,\n onLoadExample,\n exampleLabel,\n exampleSummary,\n hint,\n onTextPaste,\n } = props;\n const [overrideOpen, setOverrideOpen] = useState(sepOverride !== \"\");\n const [pasteText, setPasteText] = useState(\"\");\n const [pasteError, setPasteError] = useState(null);\n const [pasteWarn, setPasteWarn] = useState(null);\n\n // Prominent sample-dataset banner. Renders only when the tool provides\n // `onLoadExample`; pulls structured title / subtitle / icon from\n // `exampleSummary`, with `exampleLabel` as a plain-string fallback.\n const renderSamplePromo = () => {\n if (!onLoadExample) return null;\n const summary: ExampleSummary = exampleSummary ?? {\n title: \"Try a sample dataset\",\n subtitle: typeof exampleLabel === \"string\" ? exampleLabel : undefined,\n };\n const buttonLabel = summary.buttonLabel ?? \"Plot this example \u2192\";\n return (\n \n \n {summary.icon ?? }\n \n
\n \n New here? Quick start\n
\n
\n {summary.title}\n
\n {summary.subtitle && (\n \n {summary.subtitle}\n \n )}\n \n \n {buttonLabel}\n \n \n );\n };\n\n const submitPaste = () => {\n setPasteError(null);\n setPasteWarn(null);\n const text = pasteText;\n if (!text || text.trim() === \"\") {\n setPasteError(\"Paste some data first \u2014 copy a selection from Excel, Sheets, or any CSV.\");\n return;\n }\n const bytes = new Blob([text]).size;\n if (bytes > FILE_LIMIT_BYTES) {\n setPasteError(\n `Pasted data too large (${(bytes / 1024 / 1024).toFixed(1)} MB). Maximum is 2 MB \u2014 split the data or sample rows and try again.`\n );\n return;\n }\n if (bytes > FILE_WARN_BYTES) {\n setPasteWarn(\n `Large paste (${(bytes / 1024 / 1024).toFixed(1)} MB) \u2014 parsing may take a moment.`\n );\n }\n if (onTextPaste) onTextPaste(text, \"pasted_data.csv\");\n };\n\n const cardWrap: React.CSSProperties = {\n borderRadius: 12,\n border: \"1.5px solid var(--border)\",\n background: \"var(--surface)\",\n overflow: \"hidden\",\n display: \"flex\",\n flexDirection: \"column\",\n };\n const cardHeader: React.CSSProperties = {\n background: \"var(--surface-subtle)\",\n padding: \"10px 14px\",\n borderBottom: \"1px solid var(--border)\",\n display: \"flex\",\n alignItems: \"center\",\n gap: 8,\n };\n const cardHeaderLabel: React.CSSProperties = {\n fontSize: 12,\n fontWeight: 700,\n color: \"var(--text)\",\n textTransform: \"uppercase\",\n letterSpacing: \"0.8px\",\n };\n const cardBody: React.CSSProperties = { padding: 14, flex: 1 };\n\n return (\n
\n {renderSamplePromo()}\n \n
\n {/* Inline SVG info mark (currentColor \u2192 inherits --info-text and\n themes in both modes). Replaces a bare U+2139 \"\u2139\", which has a\n text-default presentation and rendered as a plain italic \"i\" in\n the mono chrome font. */}\n \n \n \n \n \n \n Pl\u00F6ttr auto-detects the column separator (comma, tab, semicolon, \u2026) from the data.\n \n setOverrideOpen((v) => !v)}\n className=\"dv-btn dv-btn-secondary\"\n style={{\n marginLeft: \"auto\",\n padding: \"2px 8px\",\n fontSize: 11,\n fontWeight: 600,\n }}\n aria-expanded={overrideOpen}\n >\n {overrideOpen ? \"Hide override \u25B4\" : \"Override \u25BE\"}\n \n
\n {overrideOpen && (\n \n \n Force separator\n \n onSepChange(e.target.value)}\n className=\"dv-select-sep\"\n >\n \n \n \n \n \n \n \n Only needed when the detector picks the wrong delimiter.\n \n
\n )}\n \n\n \n
\n
\n \uD83D\uDCC2\n Drop a file\n
\n
\n \n
\n
\n\n {onTextPaste && (\n
\n
\n \uD83D\uDCCB\n Paste data\n
\n
\n setPasteText(e.target.value)}\n placeholder={\n \"Paste comma-, tab-, or semicolon-separated rows here.\\n\" +\n \"Tip: a selection copied from Excel or Google Sheets becomes tab-separated automatically.\"\n }\n className=\"dv-input\"\n spellCheck={false}\n style={{\n width: \"100%\",\n minHeight: 160,\n boxSizing: \"border-box\",\n fontFamily: \"ui-monospace, SFMono-Regular, Menlo, Consolas, monospace\",\n fontSize: 12,\n lineHeight: 1.4,\n padding: \"10px 12px\",\n resize: \"vertical\",\n background: \"var(--surface)\",\n color: \"var(--text)\",\n }}\n aria-label=\"Paste tabular data\"\n />\n \n \n Parse pasted data\n \n {\n setPasteText(\"\");\n setPasteError(null);\n setPasteWarn(null);\n }}\n className=\"dv-btn dv-btn-secondary\"\n disabled={pasteText === \"\"}\n >\n Clear\n \n 2 MB max\n
\n {pasteError && (\n \n \uD83D\uDEAB\n {pasteError}\n
\n )}\n {pasteWarn && (\n \n \u26A0\uFE0F\n {pasteWarn}\n \n )}\n \n \n )}\n \n\n {/* Sample-dataset CTA moved to the prominent banner at the top\n (renderSamplePromo). The old bottom-of-card affordance is gone\n in auto-detect mode \u2014 it was unreadable beneath the side-by-side\n Drop + Paste cards, which was the whole point of the move. */}\n \n );\n}\n", "// One-shot inter-tool data hand-off. A source tool (e.g. RLU timecourse's\n// \u03A3 barplot tile) writes a small payload into localStorage under a single\n// fixed key, then top-level-navigates the browser to the destination\n// tool. The destination tool's App() reads-and-clears the payload on\n// mount; if it matches the expected target tool, the payload is consumed\n// instead of running through the regular upload step.\n//\n// Why localStorage and not postMessage / URL fragments:\n// \u2022 postMessage requires both pages to be alive simultaneously plus a\n// coordinator iframe; tool navigation hops the top-level frame, so\n// the source page is gone by the time the destination mounts.\n// \u2022 URL fragments hit the ~2 KB browser limit on payloads and leak\n// data into history / referers.\n// \u2022 localStorage is per-origin, synchronous, and the payload lives only\n// for the milliseconds between A's setHandoff and B's consumeHandoff\n// because consume always clears first (even if parsing later fails).\n//\n// Payload shape is intentionally loose \u2014 each consumer decides how to\n// parse the `csv` string and react to `source`. Conventional fields\n// listed in the HandoffPayload interface; tool-specific extensions\n// (e.g. `tools/upset/helpers.ts` \u2192 `UpsetHandoffPayload`) extend it.\n\nexport interface HandoffPayload {\n // Routing key \u2014 required. Destination tool's `consumeHandoff(targetTool)`\n // call only returns a payload whose `tool` matches.\n tool: string;\n // Long- or wide-format CSV/TSV the destination should parse.\n csv?: string;\n // Hint for the destination's parse path; optional.\n mode?: \"long\" | \"wide\";\n // Human-readable origin string; surfaced in banners / breadcrumbs.\n source?: string;\n // Suggested filename for download chrome on the destination.\n fileName?: string;\n // Suggested y-axis label for the destination tool's plot view, used\n // when the auto-derived label (typically the value column's name)\n // is an implementation detail rather than the standard scientific\n // term. Aequorin sends \"A.U.C.\" when handing off \u03A3-of-luminescence\n // data so the boxplot reads as area-under-curve, not \"Raw Sum\".\n // Consumers should treat a present value as authoritative \u2014 apply\n // it after their own auto-sync logic so it isn't immediately\n // clobbered. Optional; the destination's own auto-derivation runs\n // when this is absent.\n yLabel?: string;\n // Pre-assigned column-role hints; optional.\n colRoles?: string[];\n // Tool-specific consumers extend with their own fields (e.g. upset's\n // intersection-payload variant) via a structural-typing widening.\n [key: string]: unknown;\n}\n\ndeclare global {\n interface Window {\n __plottrSpaNavigate?: (toolKey: string) => void;\n }\n}\n\nconst KEY = \"dataviz-handoff\";\n\nexport function setHandoff(payload: HandoffPayload): boolean {\n try {\n localStorage.setItem(KEY, JSON.stringify(payload));\n } catch (_e) {\n return false;\n }\n // Notify same-tab consumers. The browser `storage` event only fires\n // across tabs / iframes, so under the SPA's keep-alive routing (target\n // tool already mounted) it never reaches the consumer. Dispatching a\n // synchronous CustomEvent gives the already-mounted destination a\n // chance to re-run consumeHandoff.\n try {\n window.dispatchEvent(new CustomEvent(\"plottr-handoff\", { detail: { key: KEY } }));\n } catch (_e) {\n /* swallow */\n }\n return true;\n}\n\nexport function consumeHandoff(targetTool: string): HandoffPayload | null {\n let raw: string | null = null;\n try {\n raw = localStorage.getItem(KEY);\n } catch (_e) {\n return null;\n }\n if (!raw) return null;\n // Always remove first \u2014 even if parsing or routing later fails, we\n // don't want this payload to resurrect on the next mount.\n try {\n localStorage.removeItem(KEY);\n } catch (_e) {\n /* swallow */\n }\n try {\n const payload = JSON.parse(raw) as HandoffPayload | null;\n if (!payload || payload.tool !== targetTool) return null;\n return payload;\n } catch (_e) {\n return null;\n }\n}\n\n// SPA-aware tool navigation. Source tools (e.g. RLU timecourse \u2192\n// Group Plot) call this after `setHandoff(...)` to switch the visible\n// view. Two paths:\n// 1. SPA mode (the production deploy): `window.__plottrSpaNavigate` is\n// registered by `tools/_app/index.tsx` on boot. Calling it changes\n// `location.hash` and the SPA router re-renders the new tool\n// in-place. The destination tool's mount-time `consumeHandoff()`\n// finds the localStorage payload we just wrote and applies it.\n// 2. Standalone-page fallback: `__plottrSpaNavigate` is undefined,\n// so we fall back to a top-level navigation to `.html`. The\n// destination tool boots fresh and its mount-time `consumeHandoff()`\n// does the same.\n// Call sites stay shell-agnostic \u2014 they just call\n// `setHandoff(payload); navigateToTool(payload.tool);`.\nexport function navigateToTool(toolKey: string): void {\n if (typeof toolKey !== \"string\" || toolKey === \"\") return;\n const spaNav = window.__plottrSpaNavigate;\n if (typeof spaNav === \"function\") {\n try {\n spaNav(toolKey);\n return;\n } catch (_e) {\n // Fall through to standalone-page navigation if the SPA path errors.\n }\n }\n try {\n window.location.assign(toolKey + \".html\");\n } catch (_e) {\n /* swallow */\n }\n}\n", "// Long-format detection for set-membership inputs. Given a 2-column\n// table, decide whether it's a wide 2-set file (col 1 = items in set A,\n// col 2 = items in set B) or a long-format (item, set_label) stack.\n//\n// Originally introduced for Venn (audit medium M2 replaced an earlier\n// heuristic that false-positived on duplicated col-2 items and\n// false-negatived on small long-format files); lifted to `_shell/` so\n// UpSet can share the same logic and the wide/long upfront picker can\n// go away there too. Both tools now route their `doParse` through this\n// helper for any 2-column input.\n//\n// Signal requirements (must all hold for `isLong = true`):\n// 1. At least 3 non-empty col-2 cells (anything smaller is too\n// ambiguous to auto-detect; leave it as wide and let the user\n// re-label if wrong).\n// 2. col 2 has \u2265 2 and \u2264 20 distinct values \u2014 \"set labels\" are\n// bounded (a user with > 20 sets is on UpSet, not Venn; either\n// way \u2264 20 is plenty).\n// 3. Heavy repetition: cells whose value appears \u2265 2 times in col 2\n// make up at least half of col 2. A single-duplicate wide file\n// gives 2/20 = 0.1 which fails; a 3-row 2-set long-format gives\n// 2/3 = 0.67.\n// 4. col 1 is mostly distinct (\u2265 70 % unique). A wide file with\n// heavy column-2 duplication would usually have column-1\n// duplication too; long-format has one row per (item, set) pair\n// so col-1 dupes are rare.\n//\n// Pure helper, no React / DOM dependency \u2014 pinned by\n// `tests/venn.test.js` (existing) and `tests/upset.test.js`.\n\nexport interface LongFormatDetection {\n isLong: boolean;\n col1Distinct: number;\n col2Distinct: number;\n col2Repeats: number;\n}\n\nexport function detectLongFormat(headers: string[], rows: string[][]): LongFormatDetection {\n const fail = {\n isLong: false,\n col1Distinct: 0,\n col2Distinct: 0,\n col2Repeats: 0,\n };\n if (!Array.isArray(headers) || headers.length !== 2) return fail;\n if (!Array.isArray(rows) || rows.length === 0) return fail;\n\n const col1: string[] = [];\n const col2: string[] = [];\n for (const r of rows) {\n const v1 = (r[0] || \"\").trim();\n const v2 = (r[1] || \"\").trim();\n if (v1) col1.push(v1);\n if (v2) col2.push(v2);\n }\n\n const col1Distinct = new Set(col1).size;\n const col2Distinct = new Set(col2).size;\n\n let col2Repeats = 0;\n if (col2.length > 0) {\n const counts: Record = {};\n for (const v of col2) counts[v] = (counts[v] || 0) + 1;\n for (const v of col2) if (counts[v] >= 2) col2Repeats++;\n }\n\n const enoughRows = col2.length >= 3;\n const boundedSetCount = col2Distinct >= 2 && col2Distinct <= 20;\n const heavyRepetition = col2.length > 0 && col2Repeats * 2 >= col2.length;\n const col1MostlyUnique = col1Distinct >= Math.max(3, col1.length * 0.7);\n\n const isLong = enoughRows && boundedSetCount && heavyRepetition && col1MostlyUnique;\n\n return { isLong, col1Distinct, col2Distinct, col2Repeats };\n}\n", "// Tiny numeric rounding helpers shared between plot tools. Used to keep\n// auto-axis bounds and per-x stat readouts at a stable decimal precision\n// across re-renders (raw FP would surface 1.4000000000000001 in the\n// padded axis bounds the user pastes back into the override field).\n\nexport const round2 = (v: number): number => Math.round(v * 100) / 100;\nexport const round4 = (v: number): number => Math.round(v * 10000) / 10000;\n", "// Narrow-viewport detection for the calculators. Plot tools are desktop-\n// first and skip this hook entirely; the calculators (`molarity-app.tsx`,\n// `power-app.tsx`) collapse layout below their respective breakpoints.\n// Default of 600 px matches molarity; power passes 900 explicitly.\n\nconst { useState, useEffect } = React;\n\nexport function useIsMobile(breakpoint: number = 600): boolean {\n const [mobile, setMobile] = useState(window.innerWidth < breakpoint);\n useEffect(() => {\n const handler = () => setMobile(window.innerWidth < breakpoint);\n window.addEventListener(\"resize\", handler);\n return () => window.removeEventListener(\"resize\", handler);\n }, [breakpoint]);\n return mobile;\n}\n", "// Shared state hook for every plot tool. Owns the upload-step state\n// (step, rawText/fileName, separator override, decimal-comma fix flags,\n// parse error) plus the `vis` reducer with auto-prefs persistence \u2014\n// everything every plot tool's `App()` needs.\n//\n// Tool-specific state (selectedMask, parsed rows, pendingSelection, etc.)\n// stays outside this hook \u2014 the goal is a common scaffold, not a kitchen\n// sink.\n\nimport { loadAutoPrefs, saveAutoPrefs } from \"./prefs-store\";\n\nimport type { FormulaInjectionWarning } from \"../_core/csv\";\nconst { useState, useReducer, useEffect } = React;\n\nexport interface PlotToolState {\n step: string;\n setStep: (s: string) => void;\n fileName: string;\n setFileName: (n: string) => void;\n parseError: string | null;\n setParseError: (e: string | null) => void;\n sepOverride: string;\n setSepOverride: (s: string) => void;\n commaFixed: boolean;\n setCommaFixed: (b: boolean) => void;\n commaFixCount: number;\n setCommaFixCount: (n: number) => void;\n // Result of scanForFormulaInjection on the most recent ingest. Set by each\n // tool's parse handler; null when the dataset is clean. Surfaced via\n // FormulaInjectionBanner inside PlotToolShell.\n injectionWarning: FormulaInjectionWarning | null;\n setInjectionWarning: (w: FormulaInjectionWarning | null) => void;\n vis: TVis;\n updVis: (patch: Partial | { _reset: true }) => void;\n}\n\nexport function usePlotToolState(\n toolKey: string,\n initialVis: TVis\n): PlotToolState {\n const [fileName, setFileName] = useState(\"\");\n const [step, setStep] = useState(\"upload\");\n const [parseError, setParseError] = useState(null);\n const [sepOverride, setSepOverride] = useState(\"\");\n const [commaFixed, setCommaFixed] = useState(false);\n const [commaFixCount, setCommaFixCount] = useState(0);\n const [injectionWarning, setInjectionWarning] = useState(null);\n\n // The reducer captures `initialVis` by reference; React only invokes the\n // third argument (the initializer) once, so a new inline initialVis object\n // per render doesn't re-seed state. `_reset` resets to the initial shape\n // without touching persisted auto-prefs.\n const [vis, updVis] = useReducer(\n (s: TVis, a: Partial | { _reset: true }) =>\n \"_reset\" in a && a._reset ? { ...initialVis } : { ...s, ...(a as Partial) },\n initialVis,\n (init) => loadAutoPrefs(toolKey, init)\n );\n\n useEffect(() => {\n saveAutoPrefs(toolKey, vis);\n }, [toolKey, vis]);\n\n return {\n step,\n setStep,\n fileName,\n setFileName,\n parseError,\n setParseError,\n sepOverride,\n setSepOverride,\n commaFixed,\n setCommaFixed,\n commaFixCount,\n setCommaFixCount,\n injectionWarning,\n setInjectionWarning,\n vis,\n updVis,\n };\n}\n", "// `ActionsPanel` \u2014 actions tile for the plot step. Renders a wrapping\n// row of unified download chips (SVG / PNG / + any `extraDownloads`\n// like CSV/TXT/R) followed by a full-width Start-over button. Each\n// chip flex-grows so 1/2/3 fit evenly; a 4th wraps. Every button gets\n// a native `title` tooltip; SVG/PNG/Start-over carry fixed built-in\n// strings, each `extraDownloads` entry passes its own `title`.\n\nimport { flashSaved } from \"../_core/download\";\n\nconst h = React.createElement;\n\nexport interface ActionsPanelDownload {\n label: string;\n title?: string;\n onClick: (e: React.MouseEvent) => void;\n}\n\ninterface ActionsPanelProps {\n onDownloadSvg?: (e: React.MouseEvent) => void;\n onDownloadPng?: (e: React.MouseEvent) => void;\n extraDownloads?: ActionsPanelDownload[];\n onReset: () => void;\n}\n\nexport function ActionsPanel(props: ActionsPanelProps) {\n const downloads: ActionsPanelDownload[] = [];\n if (props.onDownloadSvg) {\n downloads.push({\n label: \"SVG\",\n title: \"Download the plot as SVG \u2014 vector graphics, editable in Inkscape or Illustrator\",\n onClick: props.onDownloadSvg,\n });\n }\n if (props.onDownloadPng) {\n downloads.push({\n label: \"PNG\",\n title: \"Download the plot as PNG \u2014 2\u00D7 raster at the plot's native resolution\",\n onClick: props.onDownloadPng,\n });\n }\n if (props.extraDownloads) {\n props.extraDownloads.forEach((d) => downloads.push(d));\n }\n const dlButtons = downloads.map((d, i) =>\n h(\n \"button\",\n {\n key: \"dl\" + i,\n title: d.title || undefined,\n onClick: (e: React.MouseEvent) => {\n d.onClick(e);\n flashSaved(e.currentTarget);\n },\n className: \"dv-btn dv-btn-dl\",\n style: { flex: \"1 1 0\" },\n },\n \"\u2B07 \" + d.label\n )\n );\n return h(\n \"div\",\n { className: \"dv-panel\" },\n h(\n \"p\",\n {\n className: \"dv-tile-title\",\n style: { margin: \"0 0 8px\" },\n },\n \"Actions\"\n ),\n dlButtons.length > 0\n ? h(\n \"div\",\n {\n style: {\n display: \"flex\",\n flexWrap: \"wrap\",\n gap: 6,\n marginBottom: 8,\n },\n },\n dlButtons\n )\n : null,\n h(\n \"button\",\n {\n onClick: props.onReset,\n title: \"Clear all data, controls, and current session \u2014 returns to the upload step\",\n className: \"dv-btn dv-btn-danger\",\n },\n \"\u21BA Start over\"\n )\n );\n}\n", "// `ColorInput` \u2014 paired native `` swatch + hex text\n// field, with mid-keystroke guarding against value/text round-trip\n// clobber. Used in every per-group / per-category colour picker across\n// the plot tools.\n//\n// `normalizeHexColor` accepts `#rgb` or `#rrggbb` (case-insensitive) and\n// returns the lowercased 6-char form, or null if the string is not a\n// valid hex colour. Exported separately so other call sites can validate\n// hex strings without instantiating the component.\n\nconst { useState, useEffect } = React;\n\nexport function normalizeHexColor(v: unknown): string | null {\n if (typeof v !== \"string\") return null;\n if (/^#[0-9a-fA-F]{6}$/.test(v)) return v.toLowerCase();\n if (/^#[0-9a-fA-F]{3}$/.test(v)) {\n const s = v.toLowerCase();\n return \"#\" + s[1] + s[1] + s[2] + s[2] + s[3] + s[3];\n }\n return null;\n}\n\ninterface ColorInputProps {\n value: string;\n onChange: (next: string) => void;\n // Square edge length in pixels for the colour-picker swatch. Default 22.\n size?: number;\n}\n\nexport function ColorInput({ value, onChange, size = 22 }: ColorInputProps) {\n const [text, setText] = useState(value);\n // Only sync local text from parent `value` when the current text doesn't\n // already normalise to that value. Without this guard, typing `#abc` would\n // commit `#aabbcc` upstream, which round-trips back through this effect and\n // rewrites the text field to the 6-char form mid-keystroke \u2014 the cursor\n // jumps and the user's `#abc` shorthand gets auto-expanded against their\n // will. With the guard, external value changes (preset swap, palette reset)\n // still sync, but self-commits don't clobber what the user typed.\n useEffect(() => {\n if (normalizeHexColor(text) !== value) setText(value);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [value]);\n const commit = (v: string) => {\n const n = normalizeHexColor(v);\n if (n) onChange(n);\n };\n return (\n
\n onChange(e.target.value)}\n style={{\n width: size,\n height: size,\n border: \"1px solid var(--border-strong)\",\n borderRadius: 4,\n cursor: \"pointer\",\n padding: 0,\n flexShrink: 0,\n }}\n />\n {\n setText(e.target.value);\n commit(e.target.value);\n }}\n onBlur={(e) => {\n const n = normalizeHexColor(e.target.value);\n if (n) onChange(n);\n else setText(value);\n }}\n maxLength={7}\n spellCheck={false}\n style={{\n width: 64,\n fontFamily: \"monospace\",\n fontSize: 11,\n border: \"1px solid var(--border-strong)\",\n borderRadius: 4,\n padding: \"2px 5px\",\n color: \"var(--text)\",\n background: \"var(--surface)\",\n }}\n />\n
\n );\n}\n", "// Accessible segmented toggle \u2014 the canonical replacement for the inline\n// `flex + overflow:hidden + two/three styled \n );\n })}\n \n );\n}\n\nexport interface OnOffToggleProps {\n value: boolean;\n onChange: (value: boolean) => void;\n offLabel?: string;\n onLabel?: string;\n ariaLabel?: string;\n disabled?: boolean;\n style?: React.CSSProperties;\n}\n\nexport function OnOffToggle({\n value,\n onChange,\n offLabel = \"Off\",\n onLabel = \"On\",\n ariaLabel,\n disabled,\n style,\n}: OnOffToggleProps): React.ReactElement {\n return (\n \n value={value ? \"on\" : \"off\"}\n onChange={(v) => onChange(v === \"on\")}\n options={[\n { value: \"off\", label: offLabel },\n { value: \"on\", label: onLabel },\n ]}\n ariaLabel={ariaLabel}\n disabled={disabled}\n style={style}\n />\n );\n}\n", "// `BaseStyleControls` \u2014 background colour + grid on/off + grid\n// colour controls. Used in the plot-style tile of every group-aware\n// plot tool's sidebar.\n\nimport { ColorInput } from \"./ColorInput\";\nimport { OnOffToggle } from \"./SegToggle\";\n\nconst h = React.createElement;\n\ninterface BaseStyleControlsProps {\n plotBg: string;\n onPlotBgChange: (hex: string) => void;\n showGrid: boolean;\n onShowGridChange: (v: boolean) => void;\n gridColor: string;\n onGridColorChange: (hex: string) => void;\n}\n\nexport function BaseStyleControls(props: BaseStyleControlsProps) {\n const plotBg = props.plotBg;\n const onPlotBgChange = props.onPlotBgChange;\n const showGrid = props.showGrid;\n const onShowGridChange = props.onShowGridChange;\n const gridColor = props.gridColor;\n const onGridColorChange = props.onGridColorChange;\n const children: React.ReactNode[] = [\n h(\n \"div\",\n {\n key: \"bg\",\n style: { display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\" },\n },\n h(\"span\", { className: \"dv-label\" }, \"Background\"),\n h(ColorInput, { value: plotBg, onChange: onPlotBgChange, size: 24 })\n ),\n h(\n \"div\",\n { key: \"grid\" },\n h(\"span\", { className: \"dv-label\" }, \"Grid\"),\n h(OnOffToggle, { value: showGrid, onChange: onShowGridChange, ariaLabel: \"Grid\" })\n ),\n ];\n if (showGrid) {\n children.push(\n h(\n \"div\",\n {\n key: \"gc\",\n style: { display: \"flex\", alignItems: \"center\", justifyContent: \"space-between\" },\n },\n h(\"span\", { className: \"dv-label\" }, \"Grid color\"),\n h(ColorInput, { value: gridColor, onChange: onGridColorChange, size: 24 })\n )\n );\n }\n return h(\"div\", { style: { display: \"flex\", flexDirection: \"column\", gap: 8 } }, children);\n}\n", "// `CldLabels` \u2014 Compact-Letter-Display labels for k\u22653 group annotations.\n// Emits one `` per group with the letter assignment from\n// `compactLetterDisplay` upstream. Pairs with `SignificanceBrackets` as\n// the two annotation primitives every chart uses.\n\nconst CLD_DEFAULTS = {\n fontSize: 13,\n fontWeight: 700,\n fontFamily: \"sans-serif\",\n textColor: \"#222\",\n};\n\ninterface CldLabelsProps {\n labels: (string | null)[];\n axisCoord: (i: number) => number;\n // Perpendicular coord \u2014 y for vertical-top, x for horizontal-right.\n crossCoord: number;\n orientation: \"vertical-top\" | \"horizontal-right\";\n fontSize?: number;\n fontWeight?: number | string;\n fontFamily?: string;\n textColor?: string;\n}\n\nexport function CldLabels({\n labels,\n axisCoord,\n crossCoord,\n orientation,\n fontSize = CLD_DEFAULTS.fontSize,\n fontWeight = CLD_DEFAULTS.fontWeight,\n fontFamily = CLD_DEFAULTS.fontFamily,\n textColor = CLD_DEFAULTS.textColor,\n}: CldLabelsProps) {\n if (!labels || labels.length === 0) return null;\n const horizontal = orientation === \"horizontal-right\";\n return (\n \n {labels.map((lbl, i) =>\n lbl != null ? (\n \n {lbl}\n \n ) : null\n )}\n
\n );\n}\n", "// `scrollDisclosureIntoView` \u2014 scroll-affordance helper used by\n// ControlSection / collapsible disclosures across plot tools. Picks one\n// scrollable ancestor (typically a sticky sidebar with its own\n// overflow-y) and only moves that one \u2014 does NOT use\n// Element.scrollIntoView() because that bubbles up and scrolls every\n// scrollable ancestor including the page. The lower-level\n// `scrollIntoViewWithinAncestor` is kept module-internal; the\n// disclosure-specific wrapper is the only public consumer.\n\nfunction scrollIntoViewWithinAncestor(\n el: Element | null,\n pad?: number,\n extraBottom?: number\n): void {\n if (!el) return;\n const padding = pad == null ? 8 : pad;\n const extra = extraBottom || 0;\n let parent: Element | null = el.parentElement;\n while (parent) {\n const style = getComputedStyle(parent);\n const ov = style.overflowY;\n if ((ov === \"auto\" || ov === \"scroll\") && parent.scrollHeight > parent.clientHeight) {\n const parentRect = parent.getBoundingClientRect();\n const elRect = el.getBoundingClientRect();\n const revealBottom = elRect.bottom + extra;\n if (revealBottom > parentRect.bottom - padding) {\n const delta = revealBottom - parentRect.bottom + padding;\n const maxDelta = elRect.top - parentRect.top - padding;\n parent.scrollBy({ top: Math.min(delta, Math.max(0, maxDelta)), behavior: \"smooth\" });\n } else if (elRect.top < parentRect.top + padding) {\n parent.scrollBy({ top: elRect.top - parentRect.top - padding, behavior: \"smooth\" });\n }\n return;\n }\n parent = parent.parentElement;\n }\n // No scrollable ancestor \u2014 the page itself is what scrolls (heatmap case).\n const elRect = el.getBoundingClientRect();\n const viewportBottom = window.innerHeight;\n const revealBottom = elRect.bottom + extra;\n if (revealBottom > viewportBottom - padding) {\n const delta = revealBottom - viewportBottom + padding;\n const maxDelta = Math.max(0, elRect.top - padding);\n window.scrollBy({ top: Math.min(delta, maxDelta), behavior: \"smooth\" });\n } else if (elRect.top < padding) {\n window.scrollBy({ top: elRect.top - padding, behavior: \"smooth\" });\n }\n}\n\n// `scrollDisclosureIntoView` \u2014 disclosure-specific wrapper. Measures\n// where the next section's header sits relative to the expanded section,\n// and reveals its bottom edge plus ~14 px of clearance below so the\n// next header lands comfortably inside the viewport instead of flush at\n// the bottom edge.\nconst DISCLOSURE_TRAILING_CLEARANCE = 40;\nexport function scrollDisclosureIntoView(el: Element | null, pad?: number): void {\n if (!el) return;\n const next = el.nextElementSibling;\n const nextHeader = next && next.firstElementChild;\n let extra = 0;\n if (nextHeader) {\n const elRect = el.getBoundingClientRect();\n const nhRect = nextHeader.getBoundingClientRect();\n extra = Math.max(0, nhRect.bottom + DISCLOSURE_TRAILING_CLEARANCE - elRect.bottom);\n }\n scrollIntoViewWithinAncestor(el, pad == null ? 8 : pad, extra);\n}\n", "// Collapsible sidebar tile \u2014 the canonical disclosure section every plot\n// tool's control panel is built from. Previously each tool kept its own\n// byte-identical copy; this is the single shared implementation.\n//\n// The header row carries the `dv-disclosure` chevron + title and an optional\n// `headerRight` slot (e.g. a section-level on/off pill) that stays visible\n// when the body is folded. With no `headerRight` the row renders exactly like\n// the plain \"button is the whole header\" variant the simpler tools used, so\n// this is a drop-in replacement for both shapes.\n\nimport { scrollDisclosureIntoView } from \"./scroll-helpers\";\n\nconst { useState, useRef, useEffect } = React;\n\nexport interface ControlSectionProps {\n title: React.ReactNode;\n defaultOpen?: boolean;\n /** Optional inline control rendered to the right of the title; survives folds. */\n headerRight?: React.ReactNode;\n children?: React.ReactNode;\n}\n\nexport function ControlSection({\n title,\n defaultOpen = false,\n headerRight,\n children,\n}: ControlSectionProps) {\n const [open, setOpen] = useState(defaultOpen);\n const rootRef = useRef(null);\n useEffect(() => {\n if (!open) return;\n requestAnimationFrame(() => scrollDisclosureIntoView(rootRef.current));\n }, [open]);\n return (\n
\n \n setOpen(!open)}\n className=\"dv-tile-title\"\n style={{\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n flex: 1,\n padding: 0,\n background: \"none\",\n border: \"none\",\n cursor: \"pointer\",\n textAlign: \"left\",\n }}\n >\n \n {title}\n \n {headerRight}\n
\n {open && (\n
\n {children}\n
\n )}\n \n );\n}\n", "// Dropdown of palette keys with an inline preview strip \u2014 used in every\n// plot tool's controls sidebar to pick a discrete palette for groups /\n// categories / sets. Stateless: parent owns `value`, listens to onChange,\n// optionally seeds per-group colours via `applyColors(hexArray)` (a 3-line\n// lambda that maps `hexArray[i]` into whatever shape the tool stores).\n//\n// Splitting the dropdown into Select + SwatchStrip keeps the visual preview\n// updating live with the picked palette without forcing the parent to\n// re-render.\n\nimport {\n DISCRETE_PALETTES,\n COLORBLIND_SAFE_PALETTES,\n resolveDiscretePalette,\n} from \"./discrete-palette\";\n\nconst { useState } = React;\n\n// Sensible swatch count for the runtime-generated palettes (\"ggplot2-hue\",\n// \"viridis-d\") whose `DISCRETE_PALETTES` entry is a single \"*\" sentinel.\n// 10 stops is enough to read as a continuous-ish gradient and still leaves\n// each swatch clickable at sidebar widths.\nconst RUNTIME_PALETTE_PREVIEW_N = 10;\n\n// \"Natural length\" of a palette \u2014 what the swatch strip renders. For static\n// palettes this is the catalogue length (10 for okabe-ito, 12 for set3,\n// etc.); for the runtime \"*\" palettes we sample `RUNTIME_PALETTE_PREVIEW_N`\n// evenly-spaced stops.\nfunction naturalPaletteLength(name: string): number {\n const def = DISCRETE_PALETTES[name];\n if (!def || def.length === 0) return RUNTIME_PALETTE_PREVIEW_N;\n if (def.length === 1 && def[0] === \"*\") return RUNTIME_PALETTE_PREVIEW_N;\n return def.length;\n}\n\ninterface DiscreteSwatchStripProps {\n palette: string;\n // Optional override; defaults to the palette's natural length so the\n // strip always shows the full catalogue and users can copy any hex,\n // not just the ones currently bound to a group / category.\n n?: number;\n height?: number;\n width?: number | string;\n}\n\n// Full-palette swatch strip with click-to-copy. Each swatch is a real\n// `\n \n \n \n );\n }\n}\n", "// `FilterCheckboxPanel` \u2014 per-column filter grid for the filter step.\n// Renders one panel per categorical / filter column with checkboxes\n// for each unique value (and All / None bulk-toggles). Numeric\n// columns get a placeholder note instead.\n//\n// `isNumericValue` is read off the ambient browser globals.\n\nimport { isNumericValue } from \"../_core/numeric\";\n\nimport type { ColumnRole } from \"../_core/csv\";\nconst h = React.createElement;\n\ninterface FilterCheckboxPanelProps {\n headers: string[];\n colNames: string[];\n colRoles: ColumnRole[];\n filters: Record;\n filteredCount: number;\n totalCount: number;\n onToggle: (i: number, value: string) => void;\n onToggleAll: (i: number, allOn: boolean) => void;\n}\n\nexport function FilterCheckboxPanel(props: FilterCheckboxPanelProps) {\n const headers = props.headers;\n const colNames = props.colNames;\n const colRoles = props.colRoles;\n const filters = props.filters;\n const filteredCount = props.filteredCount;\n const totalCount = props.totalCount;\n const onToggle = props.onToggle;\n const onToggleAll = props.onToggleAll;\n return h(\n \"div\",\n {\n style: {\n flex: 1,\n borderRadius: 10,\n padding: 16,\n border: \"1px solid var(--info-border)\",\n background: \"var(--info-bg)\",\n display: \"flex\",\n flexDirection: \"column\",\n },\n },\n h(\n \"p\",\n { style: { margin: \"0 0 10px\", fontSize: 13, fontWeight: 600, color: \"var(--info-text)\" } },\n \"Filter rows (\" + filteredCount + \"/\" + totalCount + \")\"\n ),\n h(\n \"div\",\n { style: { display: \"flex\", gap: 16, flexWrap: \"wrap\", alignItems: \"stretch\", flex: 1 } },\n headers.map((_hdr, i) => {\n // Hide the value column's tile here too: it was assigned in Configure\n // and is guaranteed to stay (rows are kept if their value is numeric);\n // a filter tile adds no affordance and the old \"numeric \u2014 use axis\n // range in plot\" placeholder was pure noise.\n if (colRoles[i] === \"ignore\" || colRoles[i] === \"value\") return null;\n const u: string[] = filters[i] ? Array.from(filters[i].unique) : [];\n const isNumCol = u.length > 0 && u.filter((v) => isNumericValue(v)).length / u.length > 0.5;\n if (isNumCol && colRoles[i] !== \"filter\") {\n return h(\n \"div\",\n {\n key: \"col-\" + i,\n style: {\n minWidth: 140,\n flex: 1,\n background: \"var(--surface)\",\n borderRadius: 6,\n border: \"1px solid var(--border)\",\n padding: 10,\n },\n },\n h(\n \"div\",\n { style: { display: \"flex\", alignItems: \"center\", gap: 6, flexWrap: \"wrap\" } },\n h(\n \"p\",\n { style: { fontSize: 11, fontWeight: 600, color: \"var(--text)\", margin: 0 } },\n colNames[i]\n ),\n h(\n \"button\",\n {\n onClick: () => onToggleAll(i, true),\n style: {\n fontSize: 9,\n padding: \"2px 6px\",\n background: \"var(--surface-sunken)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 3,\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n color: \"var(--text-muted)\",\n },\n },\n \"All\"\n )\n ),\n h(\n \"p\",\n {\n style: {\n fontSize: 10,\n color: \"var(--text-faint)\",\n margin: \"4px 0 0\",\n fontStyle: \"italic\",\n },\n },\n \"numeric \u2014 use axis range in plot\"\n )\n );\n }\n return h(\n \"div\",\n {\n key: \"col-\" + i,\n style: {\n minWidth: 140,\n flex: 1,\n background: \"var(--surface)\",\n borderRadius: 6,\n border: \"1px solid var(--border)\",\n padding: 10,\n },\n },\n h(\n \"p\",\n { style: { fontSize: 11, fontWeight: 600, color: \"var(--text)\", marginBottom: 4 } },\n colNames[i]\n ),\n h(\n \"div\",\n { style: { display: \"flex\", gap: 6, marginBottom: 4 } },\n h(\n \"button\",\n {\n onClick: () => onToggleAll(i, true),\n style: {\n fontSize: 9,\n padding: \"2px 6px\",\n background: \"var(--surface-sunken)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 3,\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n color: \"var(--text-muted)\",\n },\n },\n \"All\"\n ),\n h(\n \"button\",\n {\n onClick: () => onToggleAll(i, false),\n style: {\n fontSize: 9,\n padding: \"2px 6px\",\n background: \"var(--surface-sunken)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 3,\n cursor: \"pointer\",\n fontFamily: \"inherit\",\n color: \"var(--text-muted)\",\n },\n },\n \"None\"\n )\n ),\n u.map((v) => {\n const checked = !!(filters[i] && filters[i].included && filters[i].included.has(v));\n return h(\n \"label\",\n {\n key: v,\n style: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 4,\n fontSize: 11,\n color: \"var(--text-muted)\",\n cursor: \"pointer\",\n marginBottom: 2,\n },\n },\n h(\"input\", {\n type: \"checkbox\",\n checked,\n onChange: () => onToggle(i, v),\n style: { accentColor: \"var(--cta-primary-bg)\" },\n }),\n v || h(\"em\", { style: { color: \"var(--text-faint)\" } }, \"(empty)\")\n );\n })\n );\n })\n )\n );\n}\n", "// `GroupColorEditor` \u2014 list of {colour swatch, name input, optional\n// toggle, `n=\u2026`} rows for editing per-group colours and display\n// names. Used in every plot tool's plot-step sidebar that exposes\n// per-group colour overrides.\n\nimport { ColorInput } from \"./ColorInput\";\n\nconst h = React.createElement;\n\ninterface GroupColorEditorGroup {\n name: string;\n color: string;\n displayName?: string;\n enabled?: boolean;\n stats?: { n: number } | null;\n}\n\ninterface GroupColorEditorProps {\n groups: GroupColorEditorGroup[];\n onColorChange: (i: number, color: string) => void;\n onNameChange?: (i: number, name: string) => void;\n onToggle?: (i: number) => void;\n}\n\nexport function GroupColorEditor(props: GroupColorEditorProps) {\n const groups = props.groups;\n const onColorChange = props.onColorChange;\n const onNameChange = props.onNameChange;\n const onToggle = props.onToggle;\n return h(\n \"div\",\n { style: { display: \"flex\", flexDirection: \"column\", gap: 4 } },\n groups.map((g, i) => {\n const enabled = g.enabled !== false;\n const children: React.ReactNode[] = [];\n if (onToggle) {\n children.push(\n h(\"input\", {\n key: \"cb\",\n type: \"checkbox\",\n checked: enabled,\n onChange: () => onToggle(i),\n style: { accentColor: g.color, flexShrink: 0, cursor: \"pointer\" },\n })\n );\n }\n children.push(\n h(ColorInput, {\n key: \"clr\",\n value: g.color,\n onChange: (c: string) => onColorChange(i, c),\n size: 18,\n })\n );\n children.push(\n h(\"input\", {\n key: \"nm\",\n value: g.displayName || g.name,\n onChange: (e: React.ChangeEvent) => {\n if (onNameChange) onNameChange(i, e.target.value);\n },\n style: {\n flex: 1,\n minWidth: 0,\n background: \"var(--surface)\",\n border: \"1px solid var(--border-strong)\",\n borderRadius: 4,\n color: \"var(--text)\",\n padding: \"2px 4px\",\n fontSize: 11,\n fontFamily: \"inherit\",\n },\n })\n );\n children.push(\n h(\n \"span\",\n { key: \"n\", style: { color: \"var(--text-faint)\", fontSize: 10, flexShrink: 0 } },\n \"n=\" + (g.stats ? g.stats.n : 0)\n )\n );\n return h(\n \"div\",\n {\n key: g.name,\n style: {\n display: \"flex\",\n alignItems: \"center\",\n gap: 6,\n padding: \"3px 8px\",\n borderRadius: 6,\n fontSize: 12,\n background: enabled ? \"var(--surface-sunken)\" : \"var(--surface-subtle)\",\n opacity: enabled ? 1 : 0.4,\n border: \"1px solid var(--border-strong)\",\n },\n },\n children\n );\n })\n );\n}\n", "// Shared sidebar frame for every plot tool: a fixed-width column that hosts\n// the ActionsPanel and all collapsible control tiles. Sticky by default so\n// the controls follow the viewport while the plot scrolls; opt out with\n// `sticky={false}` for tools whose sidebar is short enough that sticky\n// scrolling would be distracting.\n\ninterface PlotSidebarProps {\n children: React.ReactNode;\n sticky?: boolean;\n width?: number;\n}\n\nexport function PlotSidebar({ children, sticky = true, width = 279 }: PlotSidebarProps) {\n return (\n ` tiles inherit the CSS-level\n // 16px margin-bottom and stack on top of the flex `gap: 10` \u2014\n // giving 26px between bare panels but 10px between ControlSections\n // (which override the margin inline). The class makes the\n // between-panel spacing uniformly 10px regardless of tile type.\n className=\"dv-sidebar\"\n style={{\n width,\n flexShrink: 0,\n ...(sticky\n ? {\n position: \"sticky\",\n top: 24,\n maxHeight: \"calc(100vh - 90px)\",\n overflowY: \"auto\",\n }\n : {}),\n display: \"flex\",\n flexDirection: \"column\",\n gap: 10,\n }}\n >\n {children}\n \n );\n}\n", "import type { ColumnRole } from \"../_core/csv\";\n\n// `RenameReorderPanel` \u2014 per-column rename + drag-to-reorder list. Used\n// in the configure step of long-format tools to (a) rename observed\n// values for plot display, (b) reorder groups along a categorical axis.\n// Drag state is scoped per column so dragging on one column doesn't\n// highlight rows in a sibling column with the same positional index.\n\nconst h = React.createElement;\n\ninterface RenameReorderPanelProps {\n headers: string[];\n colNames: string[];\n colRoles: ColumnRole[];\n filters: Record;\n valueRenames: Record>;\n // Map { [colIdx]: { order, onReorder } } \u2014 every column whose index appears\n // here gets a drag handle and per-column independent reorder state. Columns\n // absent from the map are rendered as rename-only.\n orderableCols?: Record void }>;\n applyRename: (i: number, value: string) => string;\n onRenameVal: (i: number, origValue: string, newValue: string) => void;\n // Drag state is scoped per column so dragging on one column doesn't\n // highlight a row in a neighbouring column with the same positional index.\n dragState: { col: number; idx: number } | null;\n onDragStart: (state: { col: number; idx: number }) => void;\n onDragEnd: () => void;\n}\n\nexport function RenameReorderPanel(props: RenameReorderPanelProps) {\n const headers = props.headers;\n const colNames = props.colNames;\n const colRoles = props.colRoles;\n const filters = props.filters;\n const valueRenames = props.valueRenames;\n const orderableCols = props.orderableCols || {};\n const applyRename = props.applyRename;\n const onRenameVal = props.onRenameVal;\n const dragState = props.dragState;\n const onDragStart = props.onDragStart;\n const onDragEnd = props.onDragEnd;\n return h(\n \"div\",\n {\n style: {\n flex: 1,\n borderRadius: 10,\n padding: 16,\n border: \"1px solid var(--border)\",\n background: \"var(--surface-subtle)\",\n },\n },\n h(\n \"p\",\n { style: { margin: \"0 0 10px\", fontSize: 13, fontWeight: 600, color: \"var(--text-muted)\" } },\n \"Rename values & reorder groups \",\n h(\n \"span\",\n { style: { fontSize: 10, color: \"var(--text-faint)\", fontWeight: 400 } },\n \"(drag \u2630 to reorder groups on plot)\"\n )\n ),\n h(\n \"div\",\n { style: { display: \"flex\", gap: 16, flexWrap: \"wrap\" } },\n headers.map((_hdr, i) => {\n if (colRoles[i] !== \"group\" && colRoles[i] !== \"filter\") return null;\n const u: string[] = (filters[i] ? Array.from(filters[i].unique) : []).filter(\n (v) => filters[i] && filters[i].included && filters[i].included.has(v)\n );\n const colOrder = orderableCols[i];\n const isOrderable = !!colOrder;\n const renamedU = u.map((v) => ({ orig: v, renamed: applyRename(i, v) }));\n const orderedU =\n isOrderable && colOrder.order\n ? colOrder.order\n .map((g) => renamedU.find((x) => x.renamed === g))\n .filter((x): x is { orig: string; renamed: string } => Boolean(x))\n : renamedU;\n const displayList = orderedU.length > 0 ? orderedU : renamedU;\n const dragIdxForCol = dragState && dragState.col === i ? dragState.idx : null;\n return h(\n \"div\",\n {\n key: \"col-\" + i,\n style: {\n minWidth: 200,\n background: \"var(--surface)\",\n borderRadius: 6,\n border: \"1px solid var(--border)\",\n padding: 10,\n },\n },\n h(\n \"p\",\n { style: { fontSize: 11, fontWeight: 600, color: \"var(--text)\", marginBottom: 6 } },\n colNames[i]\n ),\n displayList.map((item, vi) => {\n const v = item.orig;\n return h(\n \"div\",\n {\n key: v,\n draggable: isOrderable,\n onDragStart: () => onDragStart({ col: i, idx: vi }),\n onDragOver: (e: { preventDefault(): void }) => {\n e.preventDefault();\n },\n onDrop: () => {\n if (!isOrderable || dragIdxForCol === null || dragIdxForCol === vi) {\n onDragEnd();\n return;\n }\n const cur = displayList.map((x) => x.renamed);\n const moved = cur[dragIdxForCol];\n cur.splice(dragIdxForCol, 1);\n cur.splice(vi, 0, moved);\n colOrder!.onReorder(cur);\n onDragEnd();\n },\n onDragEnd: () => onDragEnd(),\n style: {\n display: \"flex\",\n gap: 4,\n alignItems: \"center\",\n marginBottom: 3,\n padding: \"3px 4px\",\n borderRadius: 4,\n background: dragIdxForCol === vi ? \"var(--info-bg)\" : \"transparent\",\n cursor: isOrderable ? \"grab\" : \"default\",\n borderLeft: isOrderable\n ? \"3px solid var(--accent-primary)\"\n : \"3px solid transparent\",\n },\n },\n isOrderable\n ? h(\n \"span\",\n { style: { fontSize: 11, color: \"var(--text-faint)\", cursor: \"grab\" } },\n \"\u2630\"\n )\n : null,\n h(\n \"span\",\n {\n style: {\n fontSize: 10,\n color: \"var(--text-muted)\",\n minWidth: 55,\n overflow: \"hidden\",\n textOverflow: \"ellipsis\",\n whiteSpace: \"nowrap\",\n },\n },\n v || \"(empty)\"\n ),\n h(\"span\", { style: { fontSize: 10, color: \"var(--text-faint)\" } }, \"\u2192\"),\n h(\"input\", {\n value: valueRenames[i] && valueRenames[i][v] != null ? valueRenames[i][v] : v,\n onChange: (e: React.ChangeEvent) =>\n onRenameVal(i, v, e.target.value),\n className: \"dv-input\",\n style: { width: 100, fontSize: 11 },\n })\n );\n })\n );\n })\n )\n );\n}\n", "// `SignificanceBrackets` \u2014 pair-wise significance bracket renderer used by\n// boxplot's main chart (vertical + horizontal) and aequorin's inset bar\n// chart (vertical only). The caller pre-computes per-pair levels (via\n// `assignBracketLevels`) and per-pair labels (e.g. `pStars(p)` upstream\n// in the annotation-spec builder); this component only translates\n// (i, j, _level) \u2192 SVG geometry.\n//\n// Coordinate system parameterised through `axisCoord(i)` so the same\n// component works for box-axis, line-x, and any future categorical axis.\n// Orientation drives the layout fork:\n// - \"vertical-top\": brackets arc above the data, levels stack upward\n// - \"horizontal-right\": brackets arc to the right, levels stack rightward\n\nconst BRACKET_DEFAULTS = {\n stroke: \"#333\",\n strokeWidth: 1,\n fontSize: 12,\n fontWeight: 700,\n fontFamily: \"sans-serif\",\n textColor: \"#222\",\n levelStep: 20,\n tickHeight: 4,\n};\n\n// Same shape as the ambient BracketPair declared in types/globals.d.ts \u2014\n// label may be omitted (the renderer falls back to a default string), so\n// the local prop interface accepts `string | undefined` to be assignable\n// from the global type that StatsTile produces.\ninterface ChartAnnotationBracketPair {\n i: number;\n j: number;\n label?: string;\n _level?: number;\n}\n\ninterface SignificanceBracketsProps {\n pairs: ChartAnnotationBracketPair[];\n // Group-center coord along the categorical axis.\n axisCoord: (i: number) => number;\n // Anchor for level=0 along the perpendicular axis. Vertical-top: top edge\n // of bracket band (e.g. `M.top + annotTopPad - 6`). Horizontal-right: left\n // edge of bracket band (e.g. `M.left + w - annotTopPad + 6`).\n baseline: number;\n orientation: \"vertical-top\" | \"horizontal-right\";\n levelStep?: number;\n tickHeight?: number;\n stroke?: string;\n strokeWidth?: number;\n fontSize?: number;\n fontWeight?: number | string;\n fontFamily?: string;\n textColor?: string;\n}\n\nexport function SignificanceBrackets({\n pairs,\n axisCoord,\n baseline,\n orientation,\n levelStep = BRACKET_DEFAULTS.levelStep,\n tickHeight = BRACKET_DEFAULTS.tickHeight,\n stroke = BRACKET_DEFAULTS.stroke,\n strokeWidth = BRACKET_DEFAULTS.strokeWidth,\n fontSize = BRACKET_DEFAULTS.fontSize,\n fontWeight = BRACKET_DEFAULTS.fontWeight,\n fontFamily = BRACKET_DEFAULTS.fontFamily,\n textColor = BRACKET_DEFAULTS.textColor,\n}: SignificanceBracketsProps) {\n if (!pairs || pairs.length === 0) return null;\n return (\n \n {pairs.map((pr, idx) => {\n const lvl = pr._level || 0;\n const a = axisCoord(pr.i);\n const b = axisCoord(pr.j);\n if (orientation === \"horizontal-right\") {\n const xLine = baseline + lvl * levelStep;\n return (\n \n \n \n {pr.label}\n \n \n );\n }\n const yLine = baseline - lvl * levelStep;\n return (\n \n \n \n {pr.label}\n \n \n );\n })}\n \n );\n}\n", "// `SliderControl` \u2014 labelled range slider with the value displayed on top.\n//\n// Wrapped in `React.memo` with a custom comparator that intentionally\n// ignores the `onChange` prop. Without this, dragging one slider\n// re-renders every other slider in the same sidebar (sliders all live\n// under the same parent reducer), because each call site passes an\n// inline `onChange={(v) => updVis({...})}` that gets a fresh function\n// reference every render. Ignoring onChange is safe HERE because every\n// call site closes the inline arrow over a `useReducer` dispatch (or\n// other stable setter) plus a literal patch object \u2014 no captured state\n// that could go stale between renders.\n\nconst h = React.createElement;\n\nconst { memo } = React;\n\ninterface SliderControlProps {\n label: React.ReactNode;\n value: number;\n displayValue?: React.ReactNode;\n min: number;\n max: number;\n step?: number;\n onChange: (v: number) => void;\n}\n\nfunction _SliderControlImpl(props: SliderControlProps) {\n const { label, value, displayValue, min, max, step, onChange } = props;\n const dv = displayValue != null ? displayValue : value;\n const pct = ((value - min) / (max - min)) * 100;\n const grad =\n \"linear-gradient(to right, var(--accent-primary) \" +\n pct +\n \"%, var(--slider-track) \" +\n pct +\n \"%)\";\n return h(\n \"div\",\n null,\n h(\n \"div\",\n { style: { display: \"flex\", justifyContent: \"space-between\", marginBottom: 2 } },\n h(\"span\", { className: \"dv-label\" }, label),\n h(\"span\", { style: { fontSize: 10, color: \"var(--text-faint)\" } }, dv)\n ),\n h(\"input\", {\n type: \"range\",\n min,\n max,\n step,\n value,\n onChange: (e: React.ChangeEvent) => onChange(Number(e.target.value)),\n style: { width: \"100%\", background: grad },\n })\n );\n}\n\nexport const SliderControl = memo(_SliderControlImpl, (prev, next) => {\n return (\n prev.value === next.value &&\n prev.min === next.min &&\n prev.max === next.max &&\n prev.step === next.step &&\n prev.label === next.label &&\n prev.displayValue === next.displayValue\n );\n});\n", "// `StatsTable` \u2014 summary stats table (n / mean / median / SD / SEM /\n// min / max per group) used in the output step of group-plot tools.\n//\n// `PALETTE` is read off the ambient browser globals.\n\nimport { PALETTE } from \"../_core/color\";\n\nimport type { GroupStats } from \"../_core/descriptive\";\nconst h = React.createElement;\n\ninterface StatsTableProps {\n stats: GroupStats[] | null | undefined;\n groupLabel: string;\n}\n\nexport function StatsTable({ stats, groupLabel }: StatsTableProps) {\n if (!stats || stats.length === 0) return null;\n const headers = [\"Group\", \"n\", \"Mean\", \"Median\", \"SD\", \"SEM\", \"Min\", \"Max\"];\n return h(\n \"div\",\n { className: \"dv-panel\" },\n h(\n \"p\",\n { style: { margin: \"0 0 10px\", fontSize: 13, fontWeight: 600, color: \"var(--text-muted)\" } },\n `Summary \u2014 grouped by \"${groupLabel}\"`\n ),\n h(\n \"div\",\n { style: { overflowX: \"auto\" } },\n h(\n \"table\",\n { style: { borderCollapse: \"collapse\", fontSize: 12, width: \"100%\" } },\n h(\n \"thead\",\n null,\n h(\n \"tr\",\n { style: { borderBottom: \"2px solid var(--border-strong)\" } },\n headers.map((hdr) =>\n h(\n \"th\",\n {\n key: hdr,\n style: {\n padding: \"4px 10px\",\n textAlign: \"left\",\n color: \"var(--text-muted)\",\n fontWeight: 600,\n },\n },\n hdr\n )\n )\n )\n ),\n h(\n \"tbody\",\n null,\n stats.map((s, i) =>\n h(\n \"tr\",\n { key: s.name, style: { borderBottom: \"1px solid var(--border)\" } },\n h(\n \"td\",\n {\n style: {\n padding: \"4px 10px\",\n fontWeight: 600,\n color: PALETTE[i % PALETTE.length],\n },\n },\n s.name\n ),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.n),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.mean != null ? s.mean.toFixed(4) : \"\u2014\"),\n h(\n \"td\",\n { style: { padding: \"4px 10px\" } },\n s.median != null ? s.median.toFixed(4) : \"\u2014\"\n ),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.sd != null ? s.sd.toFixed(4) : \"\u2014\"),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.sem != null ? s.sem.toFixed(4) : \"\u2014\"),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.min != null ? s.min.toFixed(4) : \"\u2014\"),\n h(\"td\", { style: { padding: \"4px 10px\" } }, s.max != null ? s.max.toFixed(4) : \"\u2014\")\n )\n )\n )\n )\n )\n );\n}\n", "// `computePowerFromData` \u2014 n-needed-for-80%-power from observed data,\n// dispatched per test family (t-tests / ANOVA / KW). Forward-looking\n// replication planning: given the effect size we just saw, how many\n// samples per group would a future study need to reach 80% power at\n// each \u03B1? For rank-based tests (Mann-Whitney / Kruskal-Wallis) we\n// report the parametric analog as an approximation \u2014 flagged in the\n// returned `approximate` flag.\n//\n// Deliberately does NOT return \"achieved\" / observed power. Post-hoc\n// power is a deterministic transformation of the p-value (Hoenig &\n// Heisey 2001) \u2014 it adds zero information beyond what p already tells\n// the reader, and a coloured \"% achieved\" cell nudges users toward\n// interpreting non-significant tests as \"underpowered\" when the honest\n// framing is \"we observed effect size X; for that effect, a replication\n// would need n=Y.\"\n\nimport { powerAnova, powerTwoSample } from \"../_core/stats/dist\";\nimport { cohenDCI, sampleMean, sampleSD } from \"../_core/stats/tests\";\n\nexport interface PowerFromDataRow {\n alpha: number;\n nForTarget: number | null;\n}\n\n// 95 % CI on the effect size (Cohen's d / d_av). Present for two-group\n// parametric tests (Student / Welch), null for ANOVA k\u22653 and rank-based\n// tests where a single closed-form CI on the effect isn't as clean \u2014\n// callers can either bootstrap or rely on the existing point estimate\n// for those cases.\nexport interface PowerFromDataResult {\n effectLabel: string;\n effect: number;\n effectCI: { lo: number; hi: number } | null;\n rows: PowerFromDataRow[];\n targetPower: number;\n nLabel: string;\n approximate: boolean;\n}\n\nexport function computePowerFromData(\n chosenTest: string | null | undefined,\n values: number[][] | null | undefined\n): PowerFromDataResult | null {\n if (!chosenTest || !values || values.length < 2) return null;\n const alphas = [0.05, 0.01, 0.001];\n const target = 0.8;\n\n if (chosenTest === \"studentT\" || chosenTest === \"welchT\" || chosenTest === \"mannWhitney\") {\n const x = values[0];\n const y = values[1];\n const n1 = x.length;\n const n2 = y.length;\n if (n1 < 2 || n2 < 2) return null;\n const m1 = sampleMean(x);\n const m2 = sampleMean(y);\n const s1 = sampleSD(x);\n const s2 = sampleSD(y);\n // Effect-size denominator picks based on the chosen test's\n // variance assumption (Lakens 2013):\n // - Student's t / Mann-Whitney: pooled SD (d_s). Both assume\n // a single common variance; the pooled estimator is the\n // maximum-likelihood denominator consistent with that.\n // - Welch's t: mean of unpooled SDs (d_av). Welch denies the\n // equal-variance assumption; the pooled denominator embeds\n // it back and is methodologically inconsistent. d_av takes\n // each group's SD on its own merits, symmetric in both.\n // Label changes too so users can tell which denominator was used.\n let denom: number;\n let label: string;\n if (chosenTest === \"welchT\") {\n denom = (s1 + s2) / 2;\n label = \"Cohen's d_av\";\n } else {\n denom = Math.sqrt(((n1 - 1) * s1 * s1 + (n2 - 1) * s2 * s2) / (n1 + n2 - 2));\n label = \"Cohen's d\";\n }\n // d / d_av return the absolute magnitude here for the power-curve\n // input. The CI is computed on the *signed* d (so direction is\n // preserved when the panel displays \"d = -2.05, 95% CI [-2.50, -1.58]\").\n const dAbs = denom > 0 ? Math.abs(m1 - m2) / denom : 0;\n const dSigned = denom > 0 ? (m1 - m2) / denom : 0;\n const effectCI =\n chosenTest === \"mannWhitney\"\n ? null // MWU is rank-based; the d analog has no clean closed-form CI\n : denom > 0\n ? cohenDCI(dSigned, n1, n2)\n : null;\n const rows = alphas.map((alpha) => {\n let needed: number | null = null;\n if (dAbs > 0) {\n for (let n = 2; n <= 5000; n++) {\n if (powerTwoSample(dAbs, n, alpha, 2) >= target) {\n needed = n;\n break;\n }\n }\n }\n return { alpha, nForTarget: needed };\n });\n return {\n effectLabel: label,\n effect: dSigned,\n effectCI,\n rows,\n targetPower: target,\n nLabel: \"per group\",\n approximate: chosenTest === \"mannWhitney\",\n };\n }\n\n if (\n chosenTest === \"oneWayANOVA\" ||\n chosenTest === \"welchANOVA\" ||\n chosenTest === \"kruskalWallis\"\n ) {\n const kk = values.length;\n if (kk < 2) return null;\n const means = values.map(sampleMean);\n if (values.some((v) => v.length < 2)) return null;\n let ssW = 0;\n let totalN = 0;\n let weightedSum = 0;\n for (let i = 0; i < kk; i++) {\n const m = means[i];\n const ni = values[i].length;\n for (let j = 0; j < ni; j++) ssW += (values[i][j] - m) * (values[i][j] - m);\n totalN += ni;\n weightedSum += ni * m;\n }\n // Cohen's f via \u03B7\u00B2-based formula (the canonical noncentrality\n // input for powerAnova, equivalent to `effectsize::cohens_f` in R):\n // f = sqrt(\u03B7\u00B2 / (1 - \u03B7\u00B2)) = sqrt(ssB / ssW)\n // where ssB is weighted by group sizes around the *weighted* grand\n // mean. Pl\u00F6ttr's `fFromGroupMeans(means, sd)` global uses an\n // unweighted SD_means / SD_pooled form that's equivalent at equal n\n // but diverges by ~10 % at unequal n (e.g. ChickWeight Diet 1\u20134\n // post-hoc). Doing the computation inline here keeps the global\n // backward-compatible for `power-app.tsx`'s a-priori calculator\n // (which assumes equal n by design) while ensuring post-hoc\n // replication-planning matches R `effectsize::cohens_f` to FP\n // precision on real observed data.\n const grandMean = totalN > 0 ? weightedSum / totalN : 0;\n let ssB = 0;\n for (let i = 0; i < kk; i++) {\n ssB += values[i].length * (means[i] - grandMean) * (means[i] - grandMean);\n }\n const f = ssW > 0 ? Math.sqrt(ssB / ssW) : 0;\n const rows = alphas.map((alpha) => {\n let needed: number | null = null;\n if (f > 0) {\n for (let n = 2; n <= 5000; n++) {\n if (powerAnova(f, n, alpha, kk) >= target) {\n needed = n;\n break;\n }\n }\n }\n return { alpha, nForTarget: needed };\n });\n return {\n effectLabel: \"Cohen's f\",\n effect: f,\n effectCI: null,\n rows,\n targetPower: target,\n nLabel: \"per group\",\n approximate: chosenTest === \"kruskalWallis\",\n };\n }\n\n return null;\n}\n", "// `assignBracketLevels` \u2014 pure layout helper that assigns a vertical\n// stack level (0 = lowest) to each {i, j} pair so significance brackets\n// at overlapping spans don't collide. Greedy by ascending span width.\n// Used by chart renderers (boxplot/layout, aequorin/chart) and by\n// `StatsTile` when it builds its own annotation specs.\n//\n// Generic over T to preserve any additional fields the caller threads\n// through (label, p, etc.). Lives in its own module so consumers that\n// only need the layout math don't pull in the full StatsTile bundle.\n\nexport function assignBracketLevels(\n pairs: T[]\n): Array {\n const enriched = pairs.map((pr, idx) => ({\n ...pr,\n _span: Math.abs(pr.j - pr.i),\n _orig: idx,\n _level: 0,\n }));\n enriched.sort((a, b) => a._span - b._span);\n const placed: typeof enriched = [];\n for (const pr of enriched) {\n let lvl = 0;\n while (\n placed.some(\n (q) =>\n q._level === lvl &&\n Math.max(Math.min(q.i, q.j), Math.min(pr.i, pr.j)) <=\n Math.min(Math.max(q.i, q.j), Math.max(pr.i, pr.j))\n )\n ) {\n lvl++;\n }\n pr._level = lvl;\n placed.push(pr);\n }\n // Restore original input order so the parent can match up labels.\n placed.sort((a, b) => a._orig - b._orig);\n return placed.map(({ _orig: _o, _span: _s, ...rest }) => rest as T & { _level: number });\n}\n", "// Shared typed chart-layout helpers used by more than one tool \u2014 currently\n// `lineplot/helpers.ts` and `aequorin/helpers.ts`, both of which re-export\n// from here. Only pure functions and data shapes belong here; anything\n// with per-tool behaviour stays in `tools//helpers.ts`.\n\n// Default chart-margin for line-trace charts (lineplot + aequorin). Matches\n// the axis-label / tick-label footprints both tools use.\nexport const CHART_MARGIN = { top: 20, right: 20, bottom: 48, left: 62 };\n\n// Build an SVG polyline `d` attribute from an array of `{x, y}` points.\n// Skips any point whose `y` is null (missing/interpolated gaps). Returns an\n// empty string when fewer than 2 valid points remain \u2014 the caller should\n// suppress rendering rather than emit an empty path.\nexport function buildLineD(pts: { x: number; y: number | null }[]): string {\n const valid = pts.filter((p): p is { x: number; y: number } => p.y != null);\n if (valid.length < 2) return \"\";\n return \"M\" + valid.map((p) => `${p.x.toFixed(2)},${p.y.toFixed(2)}`).join(\"L\");\n}\n", "// Shared test / post-hoc dispatchers for every plot tool that runs\n// inferential stats (boxplot, lineplot, aequorin). Thin wrappers over\n// `STATS_TEST_REGISTRY` and `STATS_POSTHOC_REGISTRY` (defined in\n// `./stats-registry`) \u2014 the registry is the single source of truth for\n// the test-id \u2192 (label, runner, post-hoc, arity) mapping.\n//\n// Pre-registry, this file carried its own switch chain on test names,\n// duplicated across `shared-stats-tile.js` and `aequorin/reports.ts` \u2014\n// adding a new test meant editing 4+ string-matching sites with no\n// compile-time guarantee they stayed in sync. The registry collapses\n// the dispatch surface to one table.\n//\n// All three functions return `{ error }`-shaped values on failure so\n// callers don't have to branch on the test name.\n\nimport { STATS_TEST_REGISTRY, STATS_POSTHOC_REGISTRY } from \"./stats-registry\";\n\ntype GroupValues = number[][];\n\n// Union shape of every `STATS_TEST_REGISTRY[*].run(values)` return. Each\n// underlying test (tTest / mannWhitneyU / oneWayANOVA / welchANOVA /\n// kruskalWallis \u2014 see `tools/stats-tests.js`) populates only its own\n// statistic field; `p`, `df*`, and `error` are common. Kept as a single\n// shape with optional fields rather than a discriminated union because\n// the call sites probe by field presence (`if (res.t != null)`), which\n// works directly without per-test narrowing.\nexport interface TestResult {\n // Test-specific statistic \u2014 exactly one of these is present on success.\n t?: number; // Student / Welch t\n U?: number; // Mann-Whitney U\n U1?: number;\n U2?: number;\n z?: number;\n F?: number; // one-way ANOVA / Welch's ANOVA\n ssBetween?: number;\n ssWithin?: number;\n grandMean?: number;\n H?: number; // Kruskal-Wallis\n\n // Common fields (populated by whichever test ran).\n df?: number;\n df1?: number;\n df2?: number;\n n1?: number;\n n2?: number;\n mean1?: number;\n mean2?: number;\n var1?: number;\n var2?: number;\n p?: number;\n\n // Set when the underlying test threw or refused (n<2, k<2, \u2026).\n error?: string;\n}\n\n// Pull whichever test statistic the result carries \u2014 for stats CSV\n// export and similar display contexts that don't care which test ran.\n// Returns null when the result is missing, errored, or carries no\n// recognisable statistic.\nexport function testStatistic(res: TestResult | null | undefined): number | null {\n if (!res || res.error) return null;\n return res.t ?? res.U ?? res.F ?? res.H ?? null;\n}\n\n// Union shape of every post-hoc pair across tukeyHSD / gamesHowell /\n// dunnTest. Same rationale as `TestResult` \u2014 callers probe by field\n// presence (`pr.diff != null`, `pr.z != null`) rather than discriminating\n// on a method tag.\nexport interface PostHocPair {\n i: number;\n j: number;\n\n // Tukey / Games-Howell carry diff/se/q (and Tukey adds lwr/upr).\n diff?: number;\n se?: number;\n q?: number;\n df?: number;\n lwr?: number;\n upr?: number;\n\n // Dunn carries z.\n z?: number;\n\n // Common.\n p: number;\n pAdj?: number | null;\n}\n\nexport interface PostHocResult {\n pairs: PostHocPair[];\n k?: number;\n df?: number;\n mse?: number;\n method?: string;\n error?: string;\n // Soft warning \u2014 the result is still usable, but one or more derived\n // quantities (typically Tukey HSD CI bounds at small df with large k\n // and 1\u2212\u03B1 \u2265 0.95) were silently NaN. See `qtukey`'s design-envelope\n // comment in `tools/stats-posthoc.js`.\n warning?: string;\n}\n\nexport function runTest(name: RecommendedTest | string, values: GroupValues): TestResult {\n const entry = STATS_TEST_REGISTRY[name as RecommendedTest];\n if (!entry) return { error: \"unknown test\" };\n try {\n return entry.run(values) as TestResult;\n } catch (e) {\n return { error: String((e && (e as Error).message) || e) };\n }\n}\n\nexport function runPostHoc(\n name: Exclude | string,\n values: GroupValues\n): PostHocResult | null {\n const entry = STATS_POSTHOC_REGISTRY[name as Exclude];\n if (!entry) return null;\n try {\n // Cast via `unknown`: the registry signature is the open\n // `Record` shape so the runtime can also carry\n // tool-specific metadata; the static narrow to `PostHocResult` is\n // safe because `tukeyHSD` / `gamesHowell` / `dunnTest` always\n // return `{ pairs, ... }`.\n return entry.run(values) as unknown as PostHocResult;\n } catch (e) {\n return { pairs: [], error: String((e && (e as Error).message) || e) };\n }\n}\n\nexport function postHocForTest(\n testName: RecommendedTest | string | null | undefined\n): Exclude | null {\n if (testName == null) return null;\n const entry = STATS_TEST_REGISTRY[testName as RecommendedTest];\n return entry ? entry.postHoc : null;\n}\n", "// SVG legend renderer shared across chart components. Builds ``\n// blocks with the right layout for three block shapes:\n// - categorical `items` (circle / line / triangle / square / cross + label)\n// - continuous `gradient` (linearGradient stops + min/max labels)\n// - sized circles `sizeItems` (scatter aesthetic \u2014 label-aware row wrapping)\n// `computeLegendHeight` mirrors the layout math so callers can size the\n// reserved bottom band before rendering.\n//\n// Stays in `React.createElement` form (no JSX) because the SVG output is\n// nested deeply enough that JSX would be lower-density without saving any\n// real complexity. Hand-typed as `ReactNode | null` rather than a precise\n// element-tree type.\n//\n// Hex literals inside the SVG output are intentional (chart internals must\n// stay as literal hex per the chrome / SVG split \u2014 see\n// `tools/CLAUDE.md` \u00A7 Theming and the no-css-var-in-svg lint rule).\n//\nimport { svgSafeId } from \"../_core/svg-export\";\n\nconst h = React.createElement;\n\nexport interface LegendBlock {\n // Optional title rendered above the items / gradient / sizeItems.\n // Accepts null in addition to undefined because the runtime guard is\n // `if (block.title)` (see renderSvgLegend), and existing call sites\n // pass `title: null` for the no-header case.\n title?: string | null;\n // Optional explicit DOM id for the rendered `` (otherwise derived from\n // title via svgSafeId). Useful when a tool needs a stable hook into the\n // exported SVG.\n id?: string;\n // Categorical legend items (circle / line / triangle / square / cross).\n items?: Array<{ label: string; color: string; shape?: string }>;\n // Continuous colour-bar block.\n gradient?: { stops: string[]; min: string | number; max: string | number };\n // Scatter aesthetic \u2014 variable-radius circles with labels.\n sizeItems?: Array<{ r: number; label?: string }>;\n // Tools occasionally extend with their own per-block keys.\n [k: string]: unknown;\n}\n\nexport type LegendItemWidth = number | ((block: LegendBlock) => number);\n\nexport function computeLegendHeight(\n blocks: LegendBlock[] | null | undefined,\n usableW: number,\n itemWidth: LegendItemWidth\n): number {\n if (!blocks || !blocks.length) return 0;\n const IH = 18,\n TH = 15;\n const iw: LegendItemWidth = itemWidth || 88;\n let t = 10;\n blocks.forEach((b, bi) => {\n if (b.title) t += TH;\n if (b.items) {\n const bIW = typeof iw === \"function\" ? iw(b) : iw;\n t += Math.ceil(b.items.length / Math.max(1, Math.floor(usableW / bIW))) * IH;\n }\n if (b.gradient) t += 30;\n if (b.sizeItems && b.sizeItems.length) {\n const mr = Math.max(...b.sizeItems.map((i) => i.r), 3);\n const rowH = mr * 2 + 4;\n // Compute per-item widths and wrap into rows.\n let cx = 0,\n rows = 1;\n b.sizeItems.forEach((item, ii) => {\n const labelW = (item.label || \"\").length * 5.6 + 6;\n const itemW = mr * 2 + 4 + labelW + 12;\n if (ii > 0 && cx + itemW > usableW) {\n rows++;\n cx = 0;\n }\n cx += itemW;\n });\n t += rows * rowH;\n }\n if (bi < blocks.length - 1) t += 8;\n });\n return t + 6;\n}\n\nexport function renderSvgLegend(\n blocks: LegendBlock[] | null | undefined,\n startY: number,\n leftX: number,\n usableW: number,\n itemWidth: LegendItemWidth,\n truncateLabel?: number\n): React.ReactNode {\n if (!blocks || !blocks.length) return null;\n const IH = 18,\n TH = 15;\n const iw: LegendItemWidth = itemWidth || 88;\n\n // Pre-compute block Y offsets in a single pass (avoids O(n\u00B2) slice+reduce).\n const blockOffsets: number[] = [0];\n for (let bi = 0; bi < blocks.length - 1; bi++) {\n const b = blocks[bi];\n let off = blockOffsets[bi];\n if (b.title) off += TH;\n if (b.items) {\n const w = typeof iw === \"function\" ? iw(b) : iw;\n off += Math.ceil(b.items.length / Math.max(1, Math.floor(usableW / w))) * IH;\n }\n if (b.gradient) off += 30;\n if (b.sizeItems && b.sizeItems.length) {\n const mr = Math.max(...b.sizeItems.map((i) => i.r).concat([3]));\n off += mr * 2 + 4;\n }\n off += 8;\n blockOffsets.push(off);\n }\n\n const blockGroups = blocks.map((block, bi) => {\n const bIW = typeof iw === \"function\" ? iw(block) : iw;\n const blockY = startY + blockOffsets[bi];\n const itemsPerRow = Math.max(1, Math.floor(usableW / bIW));\n const children: React.ReactNode[] = [];\n\n if (block.title) {\n children.push(\n h(\n \"text\",\n { key: \"title\", fontSize: \"10\", fill: \"#666\", fontFamily: \"sans-serif\", y: 10 },\n block.title\n )\n );\n }\n\n if (block.items) {\n block.items.forEach((item, ii) => {\n const row = Math.floor(ii / itemsPerRow);\n const col = ii % itemsPerRow;\n let label = item.label || \"\";\n if (truncateLabel && label.length > truncateLabel) {\n label = label.slice(0, truncateLabel - 2) + \"\u2026\";\n }\n let shape: React.ReactNode;\n if (item.shape === \"line\") {\n shape = h(\"line\", {\n key: \"s\",\n x1: 0,\n x2: 14,\n y1: 7,\n y2: 7,\n stroke: item.color,\n strokeWidth: \"2.5\",\n });\n } else if (item.shape === \"triangle\") {\n shape = h(\"polygon\", { key: \"s\", points: \"6,1 1,12 11,12\", fill: item.color });\n } else if (item.shape === \"square\") {\n shape = h(\"rect\", { key: \"s\", x: 1, y: 2, width: 10, height: 10, fill: item.color });\n } else if (item.shape === \"cross\") {\n shape = h(\"path\", {\n key: \"s\",\n d: \"M4,0 H8 V4 H12 V8 H8 V12 H4 V8 H0 V4 H4 Z\",\n fill: item.color,\n });\n } else {\n shape = h(\"circle\", { key: \"s\", cx: 6, cy: 7, r: 5, fill: item.color });\n }\n const text = h(\n \"text\",\n {\n key: \"t\",\n x: item.shape === \"line\" ? 18 : 14,\n y: 11,\n fontSize: \"10\",\n fill: \"#444\",\n fontFamily: \"sans-serif\",\n },\n label\n );\n children.push(\n h(\n \"g\",\n {\n key: \"i\" + ii,\n transform:\n \"translate(\" + col * bIW + \", \" + ((block.title ? TH : 0) + row * IH) + \")\",\n },\n shape,\n text\n )\n );\n });\n }\n\n if (block.gradient) {\n const gw = Math.min(usableW * 0.6, 200),\n gh = 12;\n const th = block.title ? TH : 0;\n const gradId = \"svggrad-\" + bi;\n const stops = block.gradient.stops.map((c, si) =>\n h(\"stop\", {\n key: si,\n offset: (si / (block.gradient!.stops.length - 1)) * 100 + \"%\",\n stopColor: c,\n })\n );\n children.push(\n h(\n \"g\",\n { key: \"grad\", transform: \"translate(0, \" + th + \")\" },\n h(\n \"defs\",\n null,\n h(\"linearGradient\", { id: gradId, x1: \"0%\", y1: \"0%\", x2: \"100%\", y2: \"0%\" }, stops)\n ),\n h(\"rect\", { x: 0, y: 0, width: gw, height: gh, fill: \"url(#\" + gradId + \")\", rx: \"2\" }),\n h(\n \"text\",\n {\n x: 0,\n y: gh + 13,\n fontSize: \"9\",\n fill: \"#555\",\n fontFamily: \"sans-serif\",\n textAnchor: \"start\",\n },\n block.gradient.min\n ),\n h(\n \"text\",\n {\n x: gw,\n y: gh + 13,\n fontSize: \"9\",\n fill: \"#555\",\n fontFamily: \"sans-serif\",\n textAnchor: \"end\",\n },\n block.gradient.max\n )\n )\n );\n }\n\n // Size items (scatter) \u2014 label-aware spacing with row wrapping.\n if (block.sizeItems && block.sizeItems.length) {\n const sth = block.title ? TH : 0;\n const maxR = Math.max(...block.sizeItems.map((i) => i.r), 3);\n const rowH = maxR * 2 + 4;\n let cx = 0,\n row = 0;\n const sizeChildren = block.sizeItems.map((item, ii) => {\n const labelW = (item.label || \"\").length * 5.6 + 6;\n const itemW = maxR * 2 + 4 + labelW + 12;\n if (ii > 0 && cx + itemW > usableW) {\n row++;\n cx = 0;\n }\n const tx = cx;\n cx += itemW;\n return h(\n \"g\",\n { key: ii, transform: \"translate(\" + tx + \", \" + row * rowH + \")\" },\n h(\"circle\", {\n cx: maxR,\n cy: 0,\n r: item.r,\n fill: \"#888\",\n fillOpacity: \"0.35\",\n stroke: \"#888\",\n strokeWidth: \"0.8\",\n }),\n h(\n \"text\",\n { x: maxR * 2 + 4, y: 4, fontSize: \"9\", fill: \"#444\", fontFamily: \"sans-serif\" },\n item.label\n )\n );\n });\n children.push(\n h(\"g\", { key: \"size\", transform: \"translate(0, \" + (sth + maxR) + \")\" }, sizeChildren)\n );\n }\n\n const blockId =\n block.id ||\n (block.title && typeof svgSafeId === \"function\"\n ? \"legend-\" + svgSafeId(block.title)\n : \"legend-block-\" + bi);\n return h(\n \"g\",\n {\n key: bi,\n id: blockId,\n transform: \"translate(\" + leftX + \", \" + blockY + \")\",\n },\n children\n );\n });\n return h(\"g\", { id: \"legend\" }, blockGroups);\n}\n"], + "mappings": "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", "names": ["cf", "h", "pm1", "p", "dp", "i", "a", "h", "h", "u", "h", "h", "h", "h", "useState", "h", "useRef", "h", "useState", "useRef", "useEffect", "h", "h", "h", "h", "useEffect", "useRef", "useState", "useState", "useRef", "useEffect", "h", "useState", "useState", "useEffect", "useState", "useEffect", "h", "useState", "useEffect", "h", "elRect", "revealBottom", "useState", "useRef", "useEffect", "useState", "h", "h", "h", "h", "h", "h"] } diff --git a/tools/_app/chunks/chunk-II356IQT.js b/tools/_app/chunks/chunk-MQIRRYKA.js similarity index 94% rename from tools/_app/chunks/chunk-II356IQT.js rename to tools/_app/chunks/chunk-MQIRRYKA.js index 010bb7ec..3909abda 100644 --- a/tools/_app/chunks/chunk-II356IQT.js +++ b/tools/_app/chunks/chunk-MQIRRYKA.js @@ -1,2 +1,2 @@ -import{isNumericValue,tinv,toNumericValue}from"./chunk-5F6S3FYW.js";function computeStats(arr){let n=arr.length;if(n===0)return null;let mean=arr.reduce((a,b)=>a+b,0)/n,variance=arr.reduce((s,v)=>s+(v-mean)**2,0)/(n>1?n-1:1),sd=Math.sqrt(variance),sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0,sorted=[...arr].sort((a,b)=>a-b),min=sorted[0],max=sorted[n-1],median=n%2===0?(sorted[n/2-1]+sorted[n/2])/2:sorted[Math.floor(n/2)];return{mean,sd,sem,ci95,n,min,max,median}}function quartiles(arr){let s=[...arr].sort((a,b)=>a-b),n=s.length;if(n===0)return null;let q=p=>{let i=p*(n-1),lo=Math.floor(i),hi=Math.min(Math.ceil(i),n-1);return lo===hi?s[lo]:s[lo]*(hi-i)+s[hi]*(i-lo)},q1=q(.25),med=q(.5),q3=q(.75),iqr=q3-q1;return{min:s[0],max:s[n-1],q1,med,q3,iqr,wLo:Math.min(s.find(v=>v>=q1-1.5*iqr)??s[0],q1),wHi:Math.max([...s].reverse().find(v=>v<=q3+1.5*iqr)??s[n-1],q3),n}}function kde(values,nPoints=50){let n=values.length;if(n===0)return[];let sorted=[...values].sort((a,b)=>a-b),min=sorted[0],max=sorted[n-1],iqr=n>=4?sorted[Math.floor(n*.75)]-sorted[Math.floor(n*.25)]:max-min,mean=values.reduce((a,b)=>a+b,0)/n,variance=values.reduce((s,v)=>s+(v-mean)**2,0)/n,std=Math.sqrt(variance),bw=1.06*Math.min(std,(iqr||1)/1.34)*n**-.2||1,pad=bw*2,lo=min-pad,step=(max+pad-lo)/(nPoints-1),pts=[];for(let i=0;i{let nums=vals.filter(v=>v!==""&&isNumericValue(v)).map(toNumericValue),stats=computeStats(nums);return stats?{name,...stats}:{name,n:0,mean:null,sd:null,sem:null,min:null,max:null,median:null}})}export{computeStats,quartiles,kde,computeGroupStats}; -//# sourceMappingURL=chunk-II356IQT.js.map +import{isNumericValue,tinv,toNumericValue}from"./chunk-HFTDMS7M.js";function computeStats(arr){let n=arr.length;if(n===0)return null;let mean=arr.reduce((a,b)=>a+b,0)/n,variance=arr.reduce((s,v)=>s+(v-mean)**2,0)/(n>1?n-1:1),sd=Math.sqrt(variance),sem=n>1?sd/Math.sqrt(n):0,ci95=n>1?tinv(.975,n-1)*sem:0,sorted=[...arr].sort((a,b)=>a-b),min=sorted[0],max=sorted[n-1],median=n%2===0?(sorted[n/2-1]+sorted[n/2])/2:sorted[Math.floor(n/2)];return{mean,sd,sem,ci95,n,min,max,median}}function quartiles(arr){let s=[...arr].sort((a,b)=>a-b),n=s.length;if(n===0)return null;let q=p=>{let i=p*(n-1),lo=Math.floor(i),hi=Math.min(Math.ceil(i),n-1);return lo===hi?s[lo]:s[lo]*(hi-i)+s[hi]*(i-lo)},q1=q(.25),med=q(.5),q3=q(.75),iqr=q3-q1;return{min:s[0],max:s[n-1],q1,med,q3,iqr,wLo:Math.min(s.find(v=>v>=q1-1.5*iqr)??s[0],q1),wHi:Math.max([...s].reverse().find(v=>v<=q3+1.5*iqr)??s[n-1],q3),n}}function kde(values,nPoints=50){let n=values.length;if(n===0)return[];let sorted=[...values].sort((a,b)=>a-b),min=sorted[0],max=sorted[n-1],iqr=n>=4?sorted[Math.floor(n*.75)]-sorted[Math.floor(n*.25)]:max-min,mean=values.reduce((a,b)=>a+b,0)/n,variance=values.reduce((s,v)=>s+(v-mean)**2,0)/n,std=Math.sqrt(variance),bw=1.06*Math.min(std,(iqr||1)/1.34)*n**-.2||1,pad=bw*2,lo=min-pad,step=(max+pad-lo)/(nPoints-1),pts=[];for(let i=0;i{let nums=vals.filter(v=>v!==""&&isNumericValue(v)).map(toNumericValue),stats=computeStats(nums);return stats?{name,...stats}:{name,n:0,mean:null,sd:null,sem:null,min:null,max:null,median:null}})}export{computeStats,quartiles,kde,computeGroupStats}; +//# sourceMappingURL=chunk-MQIRRYKA.js.map diff --git a/tools/_app/chunks/chunk-II356IQT.js.map b/tools/_app/chunks/chunk-MQIRRYKA.js.map similarity index 100% rename from tools/_app/chunks/chunk-II356IQT.js.map rename to tools/_app/chunks/chunk-MQIRRYKA.js.map diff --git a/tools/_app/chunks/molarity-app-CV5FPHQ6.js b/tools/_app/chunks/molarity-app-MWMOFUHJ.js similarity index 99% rename from tools/_app/chunks/molarity-app-CV5FPHQ6.js rename to tools/_app/chunks/molarity-app-MWMOFUHJ.js index c66d269b..e8e17585 100644 --- a/tools/_app/chunks/molarity-app-CV5FPHQ6.js +++ b/tools/_app/chunks/molarity-app-MWMOFUHJ.js @@ -1,5 +1,5 @@ -import{FILE_LIMIT_BYTES,PageHeader,downloadCsv,parseRaw,useIsMobile}from"./chunk-5F6S3FYW.js";var{useState,useMemo,useCallback}=React,CONC_UNITS=[{label:"M",factor:1},{label:"mM",factor:.001},{label:"\xB5M",factor:1e-6},{label:"nM",factor:1e-9}],VOL_UNITS=[{label:"L",factor:1},{label:"mL",factor:.001},{label:"\xB5L",factor:1e-6}],MASS_UNITS=[{label:"g",factor:1},{label:"mg",factor:.001},{label:"\xB5g",factor:1e-6}];function toBase(value,unit,units){let u=units.find(u2=>u2.label===unit);return value*(u?u.factor:1)}function fromBase(value,unit,units){let u=units.find(u2=>u2.label===unit);return value/(u?u.factor:1)}function formatResult(val){if(val==null||isNaN(val)||!isFinite(val))return"\u2014";if(val===0)return"0";let abs=Math.abs(val);return abs>=1e6||abs<.001?val.toExponential(4):abs>=100?val.toFixed(2):abs>=1?val.toFixed(4):val.toPrecision(4)}function UnitInput({label,value,onValueChange,unit,onUnitChange,units,disabled,placeholder,compact}){return React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:10,flexWrap:compact?"wrap":"nowrap"}},React.createElement("label",{className:"dv-label",style:{width:compact?"100%":150,flexShrink:0,marginBottom:compact?2:0,fontWeight:600,fontSize:compact?11:void 0}},label),React.createElement("input",{type:"text",inputMode:"decimal",value,onChange:e=>onValueChange(e.target.value),disabled,placeholder:placeholder||"",className:"dv-input",style:{width:compact?0:130,flex:compact?1:void 0,minWidth:compact?80:void 0,fontSize:13,textAlign:"left",background:disabled?"var(--success-bg)":void 0,fontWeight:400}}),React.createElement("select",{value:unit,onChange:e=>onUnitChange(e.target.value),className:"dv-select",style:{minWidth:60}},units.map(u=>React.createElement("option",{key:u.label,value:u.label},u.label))))}function MolarityMode({compact}){let[mw,setMw]=useState(""),[mass,setMass]=useState(""),[massUnit,setMassUnit]=useState("g"),[vol,setVol]=useState(""),[volUnit,setVolUnit]=useState("mL"),[conc,setConc]=useState(""),[concUnit,setConcUnit]=useState("mM"),[solveFor,setSolveFor]=useState("conc"),result=useMemo(()=>{let mwVal=parseFloat(mw),massVal=parseFloat(mass),volVal=parseFloat(vol),concVal=parseFloat(conc);if(solveFor==="conc"){if(!isFinite(mwVal)||!isFinite(massVal)||!isFinite(volVal)||mwVal<=0||volVal<=0)return null;let massG=toBase(massVal,massUnit,MASS_UNITS),volL=toBase(volVal,volUnit,VOL_UNITS),concM=massG/mwVal/volL;return{value:fromBase(concM,concUnit,CONC_UNITS),label:concUnit}}if(solveFor==="mass"){if(!isFinite(mwVal)||!isFinite(concVal)||!isFinite(volVal)||mwVal<=0||volVal<=0)return null;let concM=toBase(concVal,concUnit,CONC_UNITS),volL=toBase(volVal,volUnit,VOL_UNITS),massG=concM*volL*mwVal;return{value:fromBase(massG,massUnit,MASS_UNITS),label:massUnit}}if(solveFor==="volume"){if(!isFinite(mwVal)||!isFinite(concVal)||!isFinite(massVal)||mwVal<=0||concVal<=0)return null;let concM=toBase(concVal,concUnit,CONC_UNITS),volL=toBase(massVal,massUnit,MASS_UNITS)/mwVal/concM;return{value:fromBase(volL,volUnit,VOL_UNITS),label:volUnit}}if(solveFor==="mw"){if(!isFinite(massVal)||!isFinite(concVal)||!isFinite(volVal)||concVal<=0||volVal<=0)return null;let concM=toBase(concVal,concUnit,CONC_UNITS),volL=toBase(volVal,volUnit,VOL_UNITS),massG=toBase(massVal,massUnit,MASS_UNITS),moles=concM*volL;return{value:massG/moles,label:"g/mol"}}return null},[mw,mass,massUnit,vol,volUnit,conc,concUnit,solveFor]),fields=[{key:"mw",label:"Mol. weight (g/mol)"},{key:"mass",label:"Mass"},{key:"volume",label:"Volume"},{key:"conc",label:"Concentration"}];return React.createElement("div",null,React.createElement("div",{style:{display:"flex",flexDirection:compact?"column":"row",gap:10,marginBottom:16,alignItems:compact?void 0:"stretch"}},React.createElement("div",{className:"dv-panel",style:{flex:compact?void 0:"0 0 calc((100% - 20px) / 3)",marginBottom:0,display:"flex",flexDirection:compact?"row":"column",...compact?{flexWrap:"wrap",gap:6,alignItems:"center"}:{}}},React.createElement("p",{style:{margin:compact?"0 8px 0 0":"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)",...compact?{width:"100%",marginBottom:4}:{}}},"Solve for:"),React.createElement("div",{style:{display:"flex",flexDirection:compact?"row":"column",gap:6,flexWrap:compact?"wrap":void 0}},fields.map(f=>React.createElement("button",{key:f.key,onClick:()=>setSolveFor(f.key),style:{padding:compact?"5px 10px":"6px 16px",borderRadius:6,fontSize:compact?11:12,fontWeight:600,background:solveFor===f.key?"var(--step-active-bg)":"var(--surface)",color:solveFor===f.key?"var(--on-accent)":"var(--text-faint)",border:"1px solid "+(solveFor===f.key?"var(--step-active-border)":"var(--border-strong)"),cursor:"pointer",fontFamily:"inherit",textAlign:"left"}},f.label)))),React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Inputs:"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:10,flexWrap:compact?"wrap":"nowrap"}},React.createElement("label",{className:"dv-label",style:{width:compact?"100%":150,flexShrink:0,marginBottom:compact?2:0,fontWeight:600,fontSize:compact?11:void 0}},"MW (g/mol)"),React.createElement("input",{type:"text",inputMode:"decimal",value:solveFor==="mw"?"":mw,onChange:e=>setMw(e.target.value),disabled:solveFor==="mw",placeholder:solveFor==="mw"?"calculated":"",className:"dv-input",style:{width:compact?0:130,flex:compact?1:void 0,minWidth:compact?80:void 0,fontSize:13,textAlign:"left",background:solveFor==="mw"?"var(--success-bg)":void 0,fontWeight:400}}),React.createElement("span",{style:{fontSize:12,color:"var(--text-faint)"}},"g/mol")),React.createElement(UnitInput,{label:"Mass",compact,value:solveFor==="mass"?"":mass,onValueChange:setMass,unit:massUnit,onUnitChange:setMassUnit,units:MASS_UNITS,disabled:solveFor==="mass",placeholder:solveFor==="mass"?"calculated":""}),React.createElement(UnitInput,{label:"Volume",value:solveFor==="volume"?"":vol,onValueChange:setVol,unit:volUnit,onUnitChange:setVolUnit,units:VOL_UNITS,disabled:solveFor==="volume",placeholder:solveFor==="volume"?"calculated":"",compact}),React.createElement(UnitInput,{label:"Concentration",value:solveFor==="conc"?"":conc,onValueChange:setConc,unit:concUnit,onUnitChange:setConcUnit,units:CONC_UNITS,disabled:solveFor==="conc",placeholder:solveFor==="conc"?"calculated":"",compact}),result&&React.createElement("div",{style:{background:"var(--success-bg)",borderRadius:8,border:"1px solid var(--success-border)",display:"flex",alignItems:"center",gap:12,padding:"12px 16px",marginTop:10}},React.createElement("span",{style:{fontSize:22,fontWeight:700,color:"var(--success-text)"}},formatResult(result.value)),React.createElement("span",{style:{fontSize:14,color:"var(--success-text)",fontWeight:600}},result.label)))))}function DilutionMode({compact}){let[c1,setC1]=useState(""),[c1Unit,setC1Unit]=useState("mM"),[v1,setV1]=useState(""),[v1Unit,setV1Unit]=useState("\xB5L"),[c2,setC2]=useState(""),[c2Unit,setC2Unit]=useState("\xB5M"),[v2,setV2]=useState(""),[v2Unit,setV2Unit]=useState("mL"),[solveFor,setSolveFor]=useState("v1"),result=useMemo(()=>{let c1Val=parseFloat(c1),v1Val=parseFloat(v1),c2Val=parseFloat(c2),v2Val=parseFloat(v2);if(solveFor==="c1"){if(!isFinite(v1Val)||!isFinite(c2Val)||!isFinite(v2Val)||v1Val<=0)return null;let base=toBase(c2Val,c2Unit,CONC_UNITS)*toBase(v2Val,v2Unit,VOL_UNITS)/toBase(v1Val,v1Unit,VOL_UNITS);return{value:fromBase(base,c1Unit,CONC_UNITS),label:c1Unit}}if(solveFor==="v1"){if(!isFinite(c1Val)||!isFinite(c2Val)||!isFinite(v2Val)||c1Val<=0)return null;let base=toBase(c2Val,c2Unit,CONC_UNITS)*toBase(v2Val,v2Unit,VOL_UNITS)/toBase(c1Val,c1Unit,CONC_UNITS);return{value:fromBase(base,v1Unit,VOL_UNITS),label:v1Unit}}if(solveFor==="c2"){if(!isFinite(c1Val)||!isFinite(v1Val)||!isFinite(v2Val)||v2Val<=0)return null;let base=toBase(c1Val,c1Unit,CONC_UNITS)*toBase(v1Val,v1Unit,VOL_UNITS)/toBase(v2Val,v2Unit,VOL_UNITS);return{value:fromBase(base,c2Unit,CONC_UNITS),label:c2Unit}}if(solveFor==="v2"){if(!isFinite(c1Val)||!isFinite(v1Val)||!isFinite(c2Val)||c2Val<=0)return null;let base=toBase(c1Val,c1Unit,CONC_UNITS)*toBase(v1Val,v1Unit,VOL_UNITS)/toBase(c2Val,c2Unit,CONC_UNITS);return{value:fromBase(base,v2Unit,VOL_UNITS),label:v2Unit}}return null},[c1,c1Unit,v1,v1Unit,c2,c2Unit,v2,v2Unit,solveFor]),fields=[{key:"c1",label:"C1 (stock conc.)"},{key:"v1",label:"V1 (stock vol.)"},{key:"c2",label:"C2 (final conc.)"},{key:"v2",label:"V2 (final vol.)"}];return React.createElement("div",null,React.createElement("div",{style:{display:"flex",flexDirection:compact?"column":"row",gap:10,marginBottom:16,alignItems:compact?void 0:"stretch"}},React.createElement("div",{className:"dv-panel",style:{flex:compact?void 0:"0 0 calc((100% - 20px) / 3)",marginBottom:0,display:"flex",flexDirection:compact?"row":"column",...compact?{flexWrap:"wrap",gap:6,alignItems:"center"}:{}}},React.createElement("p",{style:{margin:compact?"0 0 4px":"0 0 6px",fontSize:13,fontWeight:600,color:"var(--text-muted)",...compact?{width:"100%"}:{}}},"C1 \xD7 V1 = C2 \xD7 V2 \u2014 Solve for:"),!compact&&React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:"var(--text-faint)"}},"Solve for:"),React.createElement("div",{style:{display:"flex",flexDirection:compact?"row":"column",gap:6,flexWrap:compact?"wrap":void 0}},fields.map(f=>React.createElement("button",{key:f.key,onClick:()=>setSolveFor(f.key),style:{padding:compact?"5px 10px":"6px 16px",borderRadius:6,fontSize:compact?11:12,fontWeight:600,background:solveFor===f.key?"var(--step-active-bg)":"var(--surface)",color:solveFor===f.key?"var(--on-accent)":"var(--text-faint)",border:"1px solid "+(solveFor===f.key?"var(--step-active-border)":"var(--border-strong)"),cursor:"pointer",fontFamily:"inherit",textAlign:"left"}},f.label)))),React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Inputs:"),React.createElement("p",{style:{margin:"0 0 12px",fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Stock solution"),React.createElement(UnitInput,{label:"C1 (concentration)",value:solveFor==="c1"?"":c1,onValueChange:setC1,unit:c1Unit,onUnitChange:setC1Unit,units:CONC_UNITS,disabled:solveFor==="c1",placeholder:solveFor==="c1"?"calculated":"",compact}),React.createElement(UnitInput,{label:"V1 (volume)",value:solveFor==="v1"?"":v1,onValueChange:setV1,unit:v1Unit,onUnitChange:setV1Unit,units:VOL_UNITS,disabled:solveFor==="v1",placeholder:solveFor==="v1"?"calculated":"",compact}),React.createElement("p",{style:{margin:"12px 0 12px",fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Final solution"),React.createElement(UnitInput,{label:"C2 (concentration)",value:solveFor==="c2"?"":c2,onValueChange:setC2,unit:c2Unit,onUnitChange:setC2Unit,units:CONC_UNITS,disabled:solveFor==="c2",placeholder:solveFor==="c2"?"calculated":"",compact}),React.createElement(UnitInput,{label:"V2 (volume)",value:solveFor==="v2"?"":v2,onValueChange:setV2,unit:v2Unit,onUnitChange:setV2Unit,units:VOL_UNITS,disabled:solveFor==="v2",placeholder:solveFor==="v2"?"calculated":"",compact}),result&&React.createElement("div",{style:{background:"var(--success-bg)",borderRadius:8,border:"1px solid var(--success-border)",display:"flex",alignItems:"center",gap:12,padding:"12px 16px",marginTop:10}},React.createElement("span",{style:{fontSize:22,fontWeight:700,color:"var(--success-text)"}},formatResult(result.value)),React.createElement("span",{style:{fontSize:14,color:"var(--success-text)",fontWeight:600}},result.label)))))}var BATCH_EXAMPLE=`Name MW Concentration Volume +import{FILE_LIMIT_BYTES,PageHeader,downloadCsv,parseRaw,useIsMobile}from"./chunk-HFTDMS7M.js";var{useState,useMemo,useCallback}=React,CONC_UNITS=[{label:"M",factor:1},{label:"mM",factor:.001},{label:"\xB5M",factor:1e-6},{label:"nM",factor:1e-9}],VOL_UNITS=[{label:"L",factor:1},{label:"mL",factor:.001},{label:"\xB5L",factor:1e-6}],MASS_UNITS=[{label:"g",factor:1},{label:"mg",factor:.001},{label:"\xB5g",factor:1e-6}];function toBase(value,unit,units){let u=units.find(u2=>u2.label===unit);return value*(u?u.factor:1)}function fromBase(value,unit,units){let u=units.find(u2=>u2.label===unit);return value/(u?u.factor:1)}function formatResult(val){if(val==null||isNaN(val)||!isFinite(val))return"\u2014";if(val===0)return"0";let abs=Math.abs(val);return abs>=1e6||abs<.001?val.toExponential(4):abs>=100?val.toFixed(2):abs>=1?val.toFixed(4):val.toPrecision(4)}function UnitInput({label,value,onValueChange,unit,onUnitChange,units,disabled,placeholder,compact}){return React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:10,flexWrap:compact?"wrap":"nowrap"}},React.createElement("label",{className:"dv-label",style:{width:compact?"100%":150,flexShrink:0,marginBottom:compact?2:0,fontWeight:600,fontSize:compact?11:void 0}},label),React.createElement("input",{type:"text",inputMode:"decimal",value,onChange:e=>onValueChange(e.target.value),disabled,placeholder:placeholder||"",className:"dv-input",style:{width:compact?0:130,flex:compact?1:void 0,minWidth:compact?80:void 0,fontSize:13,textAlign:"left",background:disabled?"var(--success-bg)":void 0,fontWeight:400}}),React.createElement("select",{value:unit,onChange:e=>onUnitChange(e.target.value),className:"dv-select",style:{minWidth:60}},units.map(u=>React.createElement("option",{key:u.label,value:u.label},u.label))))}function MolarityMode({compact}){let[mw,setMw]=useState(""),[mass,setMass]=useState(""),[massUnit,setMassUnit]=useState("g"),[vol,setVol]=useState(""),[volUnit,setVolUnit]=useState("mL"),[conc,setConc]=useState(""),[concUnit,setConcUnit]=useState("mM"),[solveFor,setSolveFor]=useState("conc"),result=useMemo(()=>{let mwVal=parseFloat(mw),massVal=parseFloat(mass),volVal=parseFloat(vol),concVal=parseFloat(conc);if(solveFor==="conc"){if(!isFinite(mwVal)||!isFinite(massVal)||!isFinite(volVal)||mwVal<=0||volVal<=0)return null;let massG=toBase(massVal,massUnit,MASS_UNITS),volL=toBase(volVal,volUnit,VOL_UNITS),concM=massG/mwVal/volL;return{value:fromBase(concM,concUnit,CONC_UNITS),label:concUnit}}if(solveFor==="mass"){if(!isFinite(mwVal)||!isFinite(concVal)||!isFinite(volVal)||mwVal<=0||volVal<=0)return null;let concM=toBase(concVal,concUnit,CONC_UNITS),volL=toBase(volVal,volUnit,VOL_UNITS),massG=concM*volL*mwVal;return{value:fromBase(massG,massUnit,MASS_UNITS),label:massUnit}}if(solveFor==="volume"){if(!isFinite(mwVal)||!isFinite(concVal)||!isFinite(massVal)||mwVal<=0||concVal<=0)return null;let concM=toBase(concVal,concUnit,CONC_UNITS),volL=toBase(massVal,massUnit,MASS_UNITS)/mwVal/concM;return{value:fromBase(volL,volUnit,VOL_UNITS),label:volUnit}}if(solveFor==="mw"){if(!isFinite(massVal)||!isFinite(concVal)||!isFinite(volVal)||concVal<=0||volVal<=0)return null;let concM=toBase(concVal,concUnit,CONC_UNITS),volL=toBase(volVal,volUnit,VOL_UNITS),massG=toBase(massVal,massUnit,MASS_UNITS),moles=concM*volL;return{value:massG/moles,label:"g/mol"}}return null},[mw,mass,massUnit,vol,volUnit,conc,concUnit,solveFor]),fields=[{key:"mw",label:"Mol. weight (g/mol)"},{key:"mass",label:"Mass"},{key:"volume",label:"Volume"},{key:"conc",label:"Concentration"}];return React.createElement("div",null,React.createElement("div",{style:{display:"flex",flexDirection:compact?"column":"row",gap:10,marginBottom:16,alignItems:compact?void 0:"stretch"}},React.createElement("div",{className:"dv-panel",style:{flex:compact?void 0:"0 0 calc((100% - 20px) / 3)",marginBottom:0,display:"flex",flexDirection:compact?"row":"column",...compact?{flexWrap:"wrap",gap:6,alignItems:"center"}:{}}},React.createElement("p",{style:{margin:compact?"0 8px 0 0":"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)",...compact?{width:"100%",marginBottom:4}:{}}},"Solve for:"),React.createElement("div",{style:{display:"flex",flexDirection:compact?"row":"column",gap:6,flexWrap:compact?"wrap":void 0}},fields.map(f=>React.createElement("button",{key:f.key,onClick:()=>setSolveFor(f.key),style:{padding:compact?"5px 10px":"6px 16px",borderRadius:6,fontSize:compact?11:12,fontWeight:600,background:solveFor===f.key?"var(--step-active-bg)":"var(--surface)",color:solveFor===f.key?"var(--on-accent)":"var(--text-faint)",border:"1px solid "+(solveFor===f.key?"var(--step-active-border)":"var(--border-strong)"),cursor:"pointer",fontFamily:"inherit",textAlign:"left"}},f.label)))),React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Inputs:"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,marginBottom:10,flexWrap:compact?"wrap":"nowrap"}},React.createElement("label",{className:"dv-label",style:{width:compact?"100%":150,flexShrink:0,marginBottom:compact?2:0,fontWeight:600,fontSize:compact?11:void 0}},"MW (g/mol)"),React.createElement("input",{type:"text",inputMode:"decimal",value:solveFor==="mw"?"":mw,onChange:e=>setMw(e.target.value),disabled:solveFor==="mw",placeholder:solveFor==="mw"?"calculated":"",className:"dv-input",style:{width:compact?0:130,flex:compact?1:void 0,minWidth:compact?80:void 0,fontSize:13,textAlign:"left",background:solveFor==="mw"?"var(--success-bg)":void 0,fontWeight:400}}),React.createElement("span",{style:{fontSize:12,color:"var(--text-faint)"}},"g/mol")),React.createElement(UnitInput,{label:"Mass",compact,value:solveFor==="mass"?"":mass,onValueChange:setMass,unit:massUnit,onUnitChange:setMassUnit,units:MASS_UNITS,disabled:solveFor==="mass",placeholder:solveFor==="mass"?"calculated":""}),React.createElement(UnitInput,{label:"Volume",value:solveFor==="volume"?"":vol,onValueChange:setVol,unit:volUnit,onUnitChange:setVolUnit,units:VOL_UNITS,disabled:solveFor==="volume",placeholder:solveFor==="volume"?"calculated":"",compact}),React.createElement(UnitInput,{label:"Concentration",value:solveFor==="conc"?"":conc,onValueChange:setConc,unit:concUnit,onUnitChange:setConcUnit,units:CONC_UNITS,disabled:solveFor==="conc",placeholder:solveFor==="conc"?"calculated":"",compact}),result&&React.createElement("div",{style:{background:"var(--success-bg)",borderRadius:8,border:"1px solid var(--success-border)",display:"flex",alignItems:"center",gap:12,padding:"12px 16px",marginTop:10}},React.createElement("span",{style:{fontSize:22,fontWeight:700,color:"var(--success-text)"}},formatResult(result.value)),React.createElement("span",{style:{fontSize:14,color:"var(--success-text)",fontWeight:600}},result.label)))))}function DilutionMode({compact}){let[c1,setC1]=useState(""),[c1Unit,setC1Unit]=useState("mM"),[v1,setV1]=useState(""),[v1Unit,setV1Unit]=useState("\xB5L"),[c2,setC2]=useState(""),[c2Unit,setC2Unit]=useState("\xB5M"),[v2,setV2]=useState(""),[v2Unit,setV2Unit]=useState("mL"),[solveFor,setSolveFor]=useState("v1"),result=useMemo(()=>{let c1Val=parseFloat(c1),v1Val=parseFloat(v1),c2Val=parseFloat(c2),v2Val=parseFloat(v2);if(solveFor==="c1"){if(!isFinite(v1Val)||!isFinite(c2Val)||!isFinite(v2Val)||v1Val<=0)return null;let base=toBase(c2Val,c2Unit,CONC_UNITS)*toBase(v2Val,v2Unit,VOL_UNITS)/toBase(v1Val,v1Unit,VOL_UNITS);return{value:fromBase(base,c1Unit,CONC_UNITS),label:c1Unit}}if(solveFor==="v1"){if(!isFinite(c1Val)||!isFinite(c2Val)||!isFinite(v2Val)||c1Val<=0)return null;let base=toBase(c2Val,c2Unit,CONC_UNITS)*toBase(v2Val,v2Unit,VOL_UNITS)/toBase(c1Val,c1Unit,CONC_UNITS);return{value:fromBase(base,v1Unit,VOL_UNITS),label:v1Unit}}if(solveFor==="c2"){if(!isFinite(c1Val)||!isFinite(v1Val)||!isFinite(v2Val)||v2Val<=0)return null;let base=toBase(c1Val,c1Unit,CONC_UNITS)*toBase(v1Val,v1Unit,VOL_UNITS)/toBase(v2Val,v2Unit,VOL_UNITS);return{value:fromBase(base,c2Unit,CONC_UNITS),label:c2Unit}}if(solveFor==="v2"){if(!isFinite(c1Val)||!isFinite(v1Val)||!isFinite(c2Val)||c2Val<=0)return null;let base=toBase(c1Val,c1Unit,CONC_UNITS)*toBase(v1Val,v1Unit,VOL_UNITS)/toBase(c2Val,c2Unit,CONC_UNITS);return{value:fromBase(base,v2Unit,VOL_UNITS),label:v2Unit}}return null},[c1,c1Unit,v1,v1Unit,c2,c2Unit,v2,v2Unit,solveFor]),fields=[{key:"c1",label:"C1 (stock conc.)"},{key:"v1",label:"V1 (stock vol.)"},{key:"c2",label:"C2 (final conc.)"},{key:"v2",label:"V2 (final vol.)"}];return React.createElement("div",null,React.createElement("div",{style:{display:"flex",flexDirection:compact?"column":"row",gap:10,marginBottom:16,alignItems:compact?void 0:"stretch"}},React.createElement("div",{className:"dv-panel",style:{flex:compact?void 0:"0 0 calc((100% - 20px) / 3)",marginBottom:0,display:"flex",flexDirection:compact?"row":"column",...compact?{flexWrap:"wrap",gap:6,alignItems:"center"}:{}}},React.createElement("p",{style:{margin:compact?"0 0 4px":"0 0 6px",fontSize:13,fontWeight:600,color:"var(--text-muted)",...compact?{width:"100%"}:{}}},"C1 \xD7 V1 = C2 \xD7 V2 \u2014 Solve for:"),!compact&&React.createElement("p",{style:{margin:"0 0 10px",fontSize:11,color:"var(--text-faint)"}},"Solve for:"),React.createElement("div",{style:{display:"flex",flexDirection:compact?"row":"column",gap:6,flexWrap:compact?"wrap":void 0}},fields.map(f=>React.createElement("button",{key:f.key,onClick:()=>setSolveFor(f.key),style:{padding:compact?"5px 10px":"6px 16px",borderRadius:6,fontSize:compact?11:12,fontWeight:600,background:solveFor===f.key?"var(--step-active-bg)":"var(--surface)",color:solveFor===f.key?"var(--on-accent)":"var(--text-faint)",border:"1px solid "+(solveFor===f.key?"var(--step-active-border)":"var(--border-strong)"),cursor:"pointer",fontFamily:"inherit",textAlign:"left"}},f.label)))),React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0}},React.createElement("p",{style:{margin:"0 0 10px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Inputs:"),React.createElement("p",{style:{margin:"0 0 12px",fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Stock solution"),React.createElement(UnitInput,{label:"C1 (concentration)",value:solveFor==="c1"?"":c1,onValueChange:setC1,unit:c1Unit,onUnitChange:setC1Unit,units:CONC_UNITS,disabled:solveFor==="c1",placeholder:solveFor==="c1"?"calculated":"",compact}),React.createElement(UnitInput,{label:"V1 (volume)",value:solveFor==="v1"?"":v1,onValueChange:setV1,unit:v1Unit,onUnitChange:setV1Unit,units:VOL_UNITS,disabled:solveFor==="v1",placeholder:solveFor==="v1"?"calculated":"",compact}),React.createElement("p",{style:{margin:"12px 0 12px",fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Final solution"),React.createElement(UnitInput,{label:"C2 (concentration)",value:solveFor==="c2"?"":c2,onValueChange:setC2,unit:c2Unit,onUnitChange:setC2Unit,units:CONC_UNITS,disabled:solveFor==="c2",placeholder:solveFor==="c2"?"calculated":"",compact}),React.createElement(UnitInput,{label:"V2 (volume)",value:solveFor==="v2"?"":v2,onValueChange:setV2,unit:v2Unit,onUnitChange:setV2Unit,units:VOL_UNITS,disabled:solveFor==="v2",placeholder:solveFor==="v2"?"calculated":"",compact}),result&&React.createElement("div",{style:{background:"var(--success-bg)",borderRadius:8,border:"1px solid var(--success-border)",display:"flex",alignItems:"center",gap:12,padding:"12px 16px",marginTop:10}},React.createElement("span",{style:{fontSize:22,fontWeight:700,color:"var(--success-text)"}},formatResult(result.value)),React.createElement("span",{style:{fontSize:14,color:"var(--success-text)",fontWeight:600}},result.label)))))}var BATCH_EXAMPLE=`Name MW Concentration Volume NaCl 58.44 150 mM 500 mL Sucrose 342.3 0.5 M 1 L Kanamycin 484.5 50 mg/mL 100 mL`;function parseValueUnit(str,defaultUnit,unitList){str=str.trim();let m=str.match(/^([\d.eE+-]+)\s*(.+)?$/);if(!m)return null;let val=parseFloat(m[1]);if(!isFinite(val))return null;let unitStr=(m[2]||defaultUnit).trim();return unitList.find(u=>u.label===unitStr)?{value:val,unit:unitStr}:null}function parseMassVolConc(str){str=str.trim();let m=str.match(/^([\d.eE+-]+)\s*(mg\/mL|µg\/µL|g\/L|µg\/mL|mg\/L|g\/mL)$/i);if(!m)return null;let val=parseFloat(m[1]);if(!isFinite(val))return null;let unit=m[2].toLowerCase();return{gPerL:val*({"g/l":1,"mg/ml":1,"\xB5g/\xB5l":1,"\xB5g/ml":.001,"mg/l":.001,"g/ml":1e3}[unit]||1),originalUnit:m[2],originalValue:val}}function BatchMode(){let[raw,setRaw]=useState(""),[sepOverride,setSepOverride]=useState(""),[results,setResults]=useState(null),[error,setError]=useState(null),compute=useCallback(()=>{if(setError(null),setResults(null),!raw.trim()){setError("Paste your data above.");return}let bytes=new Blob([raw]).size;if(bytes>FILE_LIMIT_BYTES){setError(`Pasted data too large (${(bytes/1024/1024).toFixed(1)} MB). Maximum is 2 MB \u2014 paste fewer rows.`);return}let{headers,rows}=parseRaw(raw,sepOverride);if(rows.length===0){setError("No data rows found.");return}if(headers.length<4){setError("Need at least 4 columns: Name, MW, Concentration, Volume.");return}let output=[];for(let i=0;i{if(!results)return;let hdrs=["Name","MW (g/mol)","Target concentration","Target volume","Mass to weigh"],csvRows=results.map(r=>r.error?[r.name,"","","","ERROR: "+r.error]:[r.name,r.mw??"",r.conc??"",r.vol??"",r.massDisplay??""]);downloadCsv(hdrs,csvRows,"prep-sheet.csv")},[results]);return React.createElement("div",null,React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 8px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Paste a table: Name, MW (g/mol), Concentration (with unit), Volume (with unit)"),React.createElement("p",{style:{margin:"0 0 8px",fontSize:11,color:"var(--text-faint)"}},'Units can be inline (e.g. "150 mM", "500 mL", "50 mg/mL"). Supported: M, mM, \xB5M, nM, g/L, mg/mL, \xB5g/\xB5L, L, mL, \xB5L.'),React.createElement("div",{style:{marginBottom:10,display:"flex",alignItems:"center",gap:10}},React.createElement("span",{style:{fontSize:12,fontWeight:600,color:"var(--accent-primary)"}},"Separator:"),React.createElement("div",{className:"dv-seg"},[["","Auto-detect"],[",","Comma (,)"],[";","Semicolon (;)"],[" ","Tab (\\t)"]].map(([val,label])=>{let active=sepOverride===val;return React.createElement("button",{key:label,type:"button",onClick:()=>setSepOverride(val),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),style:{flex:"0 0 auto",padding:"4px 10px"}},label)}))),React.createElement("textarea",{value:raw,onChange:e=>setRaw(e.target.value),placeholder:BATCH_EXAMPLE,rows:8,style:{width:"100%",fontFamily:"monospace",fontSize:12,padding:10,border:"1px solid var(--border-strong)",borderRadius:6,resize:"vertical",background:"var(--surface)",color:"var(--text)"}}),React.createElement("div",{style:{marginTop:10,display:"flex",gap:10}},React.createElement("button",{onClick:compute,className:"dv-btn dv-btn-primary"},"Calculate"),React.createElement("button",{onClick:()=>setRaw(BATCH_EXAMPLE),className:"dv-btn dv-btn-secondary"},"Load example"))),error&&React.createElement("div",{style:{marginBottom:16,padding:"10px 14px",borderRadius:8,background:"var(--danger-bg)",border:"1px solid var(--danger-border)",display:"flex",alignItems:"center",gap:8}},React.createElement("span",{style:{fontSize:12,color:"var(--danger-text)",fontWeight:600}},error)),results&&results.length>0&&React.createElement("div",{className:"dv-panel"},React.createElement("div",{style:{display:"flex",justifyContent:"space-between",alignItems:"center",marginBottom:12}},React.createElement("p",{style:{margin:0,fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Prep Sheet"),React.createElement("button",{onClick:csvExport,className:"dv-btn dv-btn-dl",style:{marginLeft:"auto",flexShrink:0}},"\u2B07 CSV")),React.createElement("div",{style:{overflowX:"auto"}},React.createElement("table",{style:{borderCollapse:"collapse",fontSize:12,width:"100%"}},React.createElement("thead",null,React.createElement("tr",{style:{borderBottom:"2px solid var(--border-strong)"}},["Name","MW","Concentration","Volume","Mass to weigh"].map(h=>React.createElement("th",{key:h,style:{padding:"6px 10px",textAlign:"left",color:"var(--text-muted)",fontWeight:600}},h)))),React.createElement("tbody",null,results.map((r,i)=>React.createElement("tr",{key:i,style:{borderBottom:"1px solid var(--border)"}},React.createElement("td",{style:{padding:"6px 10px",fontWeight:600,color:"var(--text)"}},r.name),r.error?React.createElement("td",{colSpan:4,style:{padding:"6px 10px",color:"var(--danger-text)",fontStyle:"italic"}},r.error):React.createElement(React.Fragment,null,React.createElement("td",{style:{padding:"6px 10px"}},r.mw," g/mol"),React.createElement("td",{style:{padding:"6px 10px"}},r.conc),React.createElement("td",{style:{padding:"6px 10px"}},r.vol),React.createElement("td",{style:{padding:"6px 10px",fontWeight:700,color:"var(--success-text)"}},r.massDisplay)))))))))}function formatMass(grams){return grams>=1?grams.toFixed(4)+" g":grams>=.001?(grams*1e3).toFixed(4)+" mg":(grams*1e6).toFixed(4)+" \xB5g"}function LigationMode({compact}){let[vectorBp,setVectorBp]=useState(""),[vectorNg,setVectorNg]=useState(""),[insertBp,setInsertBp]=useState(""),[ratioVector,setRatioVector]=useState("1"),[ratioInsert,setRatioInsert]=useState("3"),result=useMemo(()=>{let vBp=parseFloat(vectorBp),vNg=parseFloat(vectorNg),iBp=parseFloat(insertBp),rV=parseFloat(ratioVector),rI=parseFloat(ratioInsert);return!isFinite(vBp)||!isFinite(vNg)||!isFinite(iBp)||!isFinite(rV)||!isFinite(rI)||vBp<=0||vNg<=0||iBp<=0||rV<=0||rI<=0?null:iBp/vBp*vNg*(rI/rV)},[vectorBp,vectorNg,insertBp,ratioVector,ratioInsert]),fieldStyle={width:130,fontSize:13,textAlign:"right"};return React.createElement("div",null,React.createElement("div",{className:"dv-panel"},React.createElement("p",{style:{margin:"0 0 4px",fontSize:13,fontWeight:600,color:"var(--text-muted)"}},"Ligation insert calculator"),React.createElement("p",{style:{margin:"0 0 0",fontSize:11,color:"var(--text-faint)"}},"insert (ng) = (insert bp / vector bp) \xD7 vector ng \xD7 (insert:vector ratio)")),React.createElement("div",{style:{display:"flex",flexDirection:compact?"column":"row",gap:12,marginBottom:16}},React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0,padding:12}},React.createElement("p",{style:{margin:"0 0 8px",fontSize:11,fontWeight:600,color:"var(--accent-dna)"}},"Vector"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:6}},React.createElement("label",{className:"dv-label",style:{marginBottom:0,fontWeight:600,fontSize:11}},"Length"),React.createElement("input",{type:"text",inputMode:"decimal",value:vectorBp,onChange:e=>setVectorBp(e.target.value),className:"dv-input",style:{...fieldStyle,width:90,fontSize:12}}),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"bp")),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:6}},React.createElement("label",{className:"dv-label",style:{marginBottom:0,fontWeight:600,fontSize:11}},"Amount"),React.createElement("input",{type:"text",inputMode:"decimal",value:vectorNg,onChange:e=>setVectorNg(e.target.value),className:"dv-input",style:{...fieldStyle,width:90,fontSize:12}}),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"ng"))),React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0,padding:12}},React.createElement("p",{style:{margin:"0 0 8px",fontSize:11,fontWeight:600,color:"var(--accent-dna)"}},"Insert"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:6}},React.createElement("label",{className:"dv-label",style:{marginBottom:0,fontWeight:600,fontSize:11}},"Length"),React.createElement("input",{type:"text",inputMode:"decimal",value:insertBp,onChange:e=>setInsertBp(e.target.value),className:"dv-input",style:{...fieldStyle,width:90,fontSize:12}}),React.createElement("span",{style:{fontSize:11,color:"var(--text-faint)"}},"bp"))),React.createElement("div",{className:"dv-panel",style:{flex:1,marginBottom:0,padding:12}},React.createElement("p",{style:{margin:"0 0 8px",fontSize:11,fontWeight:600,color:"var(--accent-dna)"}},"Molar ratio"),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:6,marginBottom:6}},React.createElement("label",{className:"dv-label",style:{marginBottom:0,fontWeight:600,fontSize:11,color:"var(--text-muted)"}},"Vector"),React.createElement("input",{type:"text",inputMode:"decimal",value:ratioVector,onChange:e=>setRatioVector(e.target.value),className:"dv-input",style:{...fieldStyle,width:60,fontSize:12}})),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:6}},React.createElement("label",{className:"dv-label",style:{marginBottom:0,fontWeight:600,fontSize:11,color:"var(--text-muted)"}},"Insert"),React.createElement("input",{type:"text",inputMode:"decimal",value:ratioInsert,onChange:e=>setRatioInsert(e.target.value),className:"dv-input",style:{...fieldStyle,width:60,fontSize:12}})))),result!==null&&React.createElement("div",{className:"dv-panel",style:{background:"var(--success-bg)",borderColor:"var(--success-border)",padding:"16px 20px"}},React.createElement("p",{style:{margin:"0 0 4px",fontSize:11,color:"var(--success-text)",fontWeight:600}},"Insert amount needed:"),React.createElement("span",{style:{fontSize:22,fontWeight:700,color:"var(--success-text)"}},formatResult(result)),React.createElement("span",{style:{fontSize:14,color:"var(--success-text)",fontWeight:600,marginLeft:8}},"ng")))}function ModeButton({label,desc,active,accentColor,activeBg,onClick,style:extraStyle}){let[hovered,setHovered]=useState(!1),isActive=active,showAccent=isActive||hovered;return React.createElement("button",{onClick,onMouseEnter:()=>setHovered(!0),onMouseLeave:()=>setHovered(!1),style:{padding:"12px 8px",borderRadius:10,border:showAccent?`2px solid ${accentColor}`:"1px solid var(--border)",background:isActive?activeBg:"var(--surface)",cursor:"pointer",fontFamily:"inherit",textAlign:"center",transition:"all 0.2s ease",transform:hovered&&!isActive?"translateY(-4px)":"none",boxShadow:hovered&&!isActive?`0 6px 16px ${accentColor}26`:"none",...extraStyle}},React.createElement("div",{style:{fontSize:14,fontWeight:700,color:showAccent?accentColor:"var(--text-muted)",transition:"color 0.2s ease"}},label),React.createElement("div",{style:{fontSize:11,color:"var(--text-faint)",marginTop:2}},desc))}function App(){let[mode,setMode]=useState("molarity"),compact=useIsMobile(),chemModes=[{key:"molarity",label:"Molarity",desc:"MW / mass / volume / concentration"},{key:"dilution",label:"Dilution",desc:"C1\xD7V1 = C2\xD7V2"},{key:"batch",label:"Batch",desc:"Paste a table, get a prep sheet"}],dnaModes=[{key:"ligation",label:"Ligation",desc:"Insert mass from vector:insert ratio"}],chemColor="var(--cta-primary-bg)",dnaColor="var(--cta-dna-bg)";return React.createElement("div",{style:{maxWidth:720,margin:"0 auto",padding:compact?"16px 10px 80px":"32px 20px"}},React.createElement(PageHeader,{toolName:"molarity",title:"Calculator"}),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,margin:"0 0 6px"}},React.createElement("span",{style:{fontSize:12,fontWeight:700,color:chemColor,textTransform:"uppercase",letterSpacing:"1px"}},"Solutions"),React.createElement("span",{style:{flex:1,height:1,background:chemColor,opacity:.3}})),React.createElement("div",{style:{display:"flex",gap:compact?6:10,marginBottom:16,flexWrap:"wrap"}},chemModes.map(m=>React.createElement(ModeButton,{key:m.key,label:m.label,desc:m.desc,active:mode===m.key,accentColor:chemColor,activeBg:"var(--info-bg)",onClick:()=>setMode(m.key),style:{flex:compact?"1 1 calc(50% - 6px)":1,minWidth:compact?0:void 0}}))),React.createElement("div",{style:{display:"flex",alignItems:"center",gap:8,margin:"0 0 6px"}},React.createElement("span",{style:{fontSize:12,fontWeight:700,color:dnaColor,textTransform:"uppercase",letterSpacing:"1px"}},"DNA"),React.createElement("span",{style:{flex:1,height:1,background:dnaColor,opacity:.3}})),React.createElement("div",{style:{display:"flex",gap:compact?6:10,marginBottom:24}},dnaModes.map(m=>React.createElement(ModeButton,{key:m.key,label:m.label,desc:m.desc,active:mode===m.key,accentColor:dnaColor,activeBg:"var(--surface-subtle)",onClick:()=>setMode(m.key),style:{flex:compact?1:"0 1 auto",width:compact?void 0:`calc(${100/chemModes.length}% - ${10*(chemModes.length-1)/chemModes.length}px)`}}))),mode==="molarity"&&React.createElement(MolarityMode,{compact}),mode==="dilution"&&React.createElement(DilutionMode,{compact}),mode==="batch"&&React.createElement(BatchMode,null),mode==="ligation"&&React.createElement(LigationMode,{compact}))}export{App}; -//# sourceMappingURL=molarity-app-CV5FPHQ6.js.map +//# sourceMappingURL=molarity-app-MWMOFUHJ.js.map diff --git a/tools/_app/chunks/molarity-app-CV5FPHQ6.js.map b/tools/_app/chunks/molarity-app-MWMOFUHJ.js.map similarity index 100% rename from tools/_app/chunks/molarity-app-CV5FPHQ6.js.map rename to tools/_app/chunks/molarity-app-MWMOFUHJ.js.map diff --git a/tools/_app/chunks/power-app-WK6W4FL3.js b/tools/_app/chunks/power-app-4LQAP5A5.js similarity index 99% rename from tools/_app/chunks/power-app-WK6W4FL3.js rename to tools/_app/chunks/power-app-4LQAP5A5.js index 48fa22dd..40486141 100644 --- a/tools/_app/chunks/power-app-WK6W4FL3.js +++ b/tools/_app/chunks/power-app-4LQAP5A5.js @@ -1,2 +1,2 @@ -import{makeTicks}from"./chunk-T4V5V5TK.js";import{NumberInput,PageHeader,bisect,buildRScriptForPower,downloadText,fFromGroupMeans,flashSaved,powerAnova,powerChi2,powerCorrelation,powerOneSample,powerPaired,powerTwoSample,useIsMobile}from"./chunk-5F6S3FYW.js";var{useState,useMemo,useCallback,useRef,useEffect,forwardRef}=React;function dFromMeans(m1,m2,sd){return sd>0?Math.abs(m1-m2)/sd:0}function wFromProportions(observed,expected){if(!observed.length||observed.length!==expected.length)return 0;let sum=0;for(let i=0;ipowerTwoSample(es,n,alpha,tails),effectMax:3,totalN:n=>n*2,totalLabel:n=>`Total N = ${n*2} (${n} per group \xD7 2)`},"t-paired":{label:"Paired t-test",question:"How many pairs to detect a difference between matched measurements?",nLabel:"n (pairs)",power:(es,n,alpha,tails)=>powerPaired(es,n,alpha,tails),effectMax:3},"t-one":{label:"One-sample t-test",question:"How many observations to detect a deviation from a known reference value?",nLabel:"n",power:(es,n,alpha,tails)=>powerOneSample(es,n,alpha,tails),effectMax:3},anova:{label:"One-way ANOVA",question:"How many subjects per group to detect differences among k group means?",nLabel:"n per group",hasGroups:!0,power:(es,n,alpha,_tails,k)=>powerAnova(es,n,alpha,k??2),effectMax:2,totalN:(n,k)=>n*k,totalLabel:(n,k)=>`Total N = ${n*k} (${n} per group \xD7 ${k} groups)`},correlation:{label:"Correlation",question:"How many observations to detect a non-zero Pearson correlation?",nLabel:"n (total)",power:(es,n,alpha,tails)=>powerCorrelation(es,n,alpha,tails),effectMax:.99,minN:4},chi2:{label:"Chi-square test",question:"How many observations for a goodness-of-fit or independence test?",nLabel:"n (total)",hasDf:!0,power:(es,n,alpha,_tails,_k,df)=>powerChi2(es,n,alpha,df??1),effectMax:1}};function EffectSizePanel({testKey,effectSize,onEffectChange,disabled}){let[mode,setMode]=useState("helper"),[mean1,setMean1]=useState(""),[mean2,setMean2]=useState(""),[sd,setSd]=useState(""),[diffMean,setDiffMean]=useState(""),[diffSd,setDiffSd]=useState(""),[groupMeansStr,setGroupMeansStr]=useState(""),[withinSd,setWithinSd]=useState(""),[expectedStr,setExpectedStr]=useState(""),[observedStr,setObservedStr]=useState(""),inputStyle={width:"100%"},smallLabel={fontSize:11,color:"var(--text-muted)",marginBottom:2},note={fontSize:10,color:"var(--text-faint)",marginTop:2};function parseProportions(str){if(!str.trim())return[];let parts;str.includes(":")?parts=str.split(":").map(s=>parseFloat(s.trim())).filter(v=>!isNaN(v)&&v>=0):parts=str.split(",").map(s=>parseFloat(s.trim())).filter(v=>!isNaN(v)&&v>=0);let sum=parts.reduce((a,b)=>a+b,0);return sum>0?parts.map(p=>p/sum):[]}let computeFromHelper=useCallback(()=>{if(testKey==="t-ind"){let m1=parseFloat(mean1),m2=parseFloat(mean2),s=parseFloat(sd);!isNaN(m1)&&!isNaN(m2)&&!isNaN(s)&&s>0&&onEffectChange(dFromMeans(m1,m2,s).toFixed(4))}else if(testKey==="t-paired"||testKey==="t-one"){let dm=parseFloat(diffMean),ds=parseFloat(diffSd);!isNaN(dm)&&!isNaN(ds)&&ds>0&&onEffectChange((Math.abs(dm)/ds).toFixed(4))}else if(testKey==="anova"){let means=groupMeansStr.split(",").map(s=>parseFloat(s.trim())).filter(v=>!isNaN(v)),wsd=parseFloat(withinSd);means.length>=2&&!isNaN(wsd)&&wsd>0&&onEffectChange(fFromGroupMeans(means,wsd).toFixed(4))}else if(testKey==="chi2"){let exp=parseProportions(expectedStr),obs=parseProportions(observedStr);exp.length>=2&&obs.length===exp.length&&onEffectChange(wFromProportions(obs,exp).toFixed(4))}},[testKey,mean1,mean2,sd,diffMean,diffSd,groupMeansStr,withinSd,expectedStr,observedStr,onEffectChange]),computedD=parseFloat(String(effectSize)),sizeLabel=testKey==="correlation"?computedD<.1?"":computedD<.3?"small":computedD<.5?"medium":"large":testKey==="anova"?computedD<.1?"":computedD<.25?"small":computedD<.4?"medium":"large":testKey==="chi2"?computedD<.1?"":computedD<.3?"small":computedD<.5?"medium":"large":computedD<.2?"":computedD<.5?"small":computedD<.8?"medium":"large",sizeColor=sizeLabel==="small"?"var(--success-text)":sizeLabel==="medium"?"var(--warning-text)":sizeLabel==="large"?"var(--danger-text)":"var(--text-faint)";return testKey==="correlation"?React.createElement("div",{style:{opacity:disabled?.4:1}},React.createElement("div",{style:smallLabel},"Expected correlation |r|"),React.createElement(NumberInput,{min:"0.01",max:"0.99",step:"0.05",value:effectSize,onChange:e=>onEffectChange(e.target.value),disabled,style:inputStyle}),sizeLabel&&React.createElement("div",{style:{...note,color:sizeColor,fontWeight:600}},sizeLabel," effect"),React.createElement("div",{style:note},"How strong a linear relationship do you expect?")):React.createElement("div",{style:{opacity:disabled?.4:1}},React.createElement("div",{className:"dv-seg",style:{marginBottom:6}},["helper","direct"].map(m=>{let active=mode===m;return React.createElement("button",{key:m,type:"button",onClick:()=>setMode(m),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},m==="helper"?"From my data":"Direct value")})),mode==="helper"&&testKey==="t-ind"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},"Expected mean \u2014 group 1"),React.createElement(NumberInput,{step:"any",value:mean1,onChange:e=>setMean1(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 15.2"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Expected mean \u2014 group 2"),React.createElement(NumberInput,{step:"any",value:mean2,onChange:e=>setMean2(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 12.8"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Common standard deviation"),React.createElement(NumberInput,{step:"any",min:"0",value:sd,onChange:e=>setSd(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 4.5"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size"),React.createElement("div",{style:note},"Use pilot data or literature values. The SD should be the pooled within-group SD.")),mode==="helper"&&(testKey==="t-paired"||testKey==="t-one")&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},testKey==="t-paired"?"Expected mean difference":"Expected deviation from reference"),React.createElement(NumberInput,{step:"any",value:diffMean,onChange:e=>setDiffMean(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 2.5"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},testKey==="t-paired"?"SD of paired differences":"Standard deviation"),React.createElement(NumberInput,{step:"any",min:"0",value:diffSd,onChange:e=>setDiffSd(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 5.0"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size")),mode==="helper"&&testKey==="anova"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},"Expected group means (comma-separated)"),React.createElement("input",{type:"text",value:groupMeansStr,onChange:e=>setGroupMeansStr(e.target.value),disabled,className:"dv-input-num",style:inputStyle,placeholder:"e.g. 10, 12, 15"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Within-group standard deviation"),React.createElement(NumberInput,{step:"any",min:"0",value:withinSd,onChange:e=>setWithinSd(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 4.0"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size"),React.createElement("div",{style:note},"Enter the means you expect for each treatment group, and the common within-group SD (from pilot data or literature).")),mode==="helper"&&testKey==="chi2"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},"Baseline proportions (what the theory predicts)"),React.createElement("input",{type:"text",value:expectedStr,onChange:e=>setExpectedStr(e.target.value),disabled,className:"dv-input-num",style:inputStyle,placeholder:"e.g. 3:1 or 0.75, 0.25"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Actual proportions (what you think is really happening)"),React.createElement("input",{type:"text",value:observedStr,onChange:e=>setObservedStr(e.target.value),disabled,className:"dv-input-num",style:inputStyle,placeholder:"e.g. 2:1 or 0.67, 0.33"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size"),React.createElement("div",{style:note},"Use ratios (3:1) or proportions (0.75, 0.25). Common for Mendelian segregation tests.")),mode==="direct"&&React.createElement("div",null,React.createElement("div",{style:smallLabel},testKey==="anova"?"Effect size (f)":testKey==="chi2"?"Effect size (w)":"Effect size (d)"),React.createElement(NumberInput,{min:"0.01",step:"0.1",value:effectSize,onChange:e=>onEffectChange(e.target.value),disabled,style:inputStyle}),React.createElement("div",{style:note},testKey==="anova"?"f = SD of group means / within-group SD":testKey==="chi2"?"w = \u221A(\u03A3 (p_obs \u2212 p_exp)\xB2 / p_exp)":"d = |difference in means| / pooled SD")),effectSize&&parseFloat(String(effectSize))>0&&React.createElement("div",{style:{marginTop:6,padding:"6px 10px",background:"var(--info-bg)",borderRadius:6,border:"1px solid var(--info-border)",display:"flex",justifyContent:"space-between",alignItems:"center"}},React.createElement("span",{style:{fontSize:12,color:"var(--text)"}},"Effect size = ",React.createElement("b",null,parseFloat(String(effectSize)).toFixed(3))),sizeLabel&&React.createElement("span",{style:{fontSize:11,fontWeight:600,color:sizeColor}},sizeLabel)))}var PowerCurve=forwardRef(function({testKey,powerFn,params,solveFor,result},ref){let test=TESTS[testKey];if(!test)return null;let VBW=520,VBH=320,M={top:30,right:20,bottom:50,left:55},w=VBW-M.left-M.right,h=VBH-M.top-M.bottom,{es,n,alpha,tails,k,df}=params,minN=test.minN||2,maxN=Math.max(200,result&&solveFor==="n"?result*2.5:200),xRange=[minN,Math.ceil(maxN)],curvePoints=[];for(let i=0;i<=100;i++){let xn=xRange[0]+(xRange[1]-xRange[0])*i/100,ni=Math.max(minN,Math.round(xn)),pw=powerFn(es,ni,alpha,tails,k,df);curvePoints.push({x:ni,y:Math.min(1,Math.max(0,pw))})}let sx=v=>M.left+(v-xRange[0])/(xRange[1]-xRange[0])*w,sy=v=>M.top+(1-v)*h,pathD=curvePoints.map((p,i)=>`${i===0?"M":"L"}${sx(p.x).toFixed(2)},${sy(p.y).toFixed(2)}`).join(" "),yTicks=[0,.2,.4,.6,.8,1],xTicks=makeTicks(xRange[0],xRange[1],6),marker=null;if(result!=null&&isFinite(result)&&solveFor==="n"){let my=powerFn(es,Math.round(result),alpha,tails,k,df);marker={x:sx(result),y:sy(Math.min(1,Math.max(0,my)))}}else result!=null&&isFinite(result)&&solveFor==="power"&&(marker={x:sx(n),y:sy(Math.min(1,Math.max(0,result)))});return React.createElement("svg",{ref,viewBox:`0 0 ${VBW} ${VBH}`,preserveAspectRatio:"xMidYMid meet",style:{width:"100%",height:"100%",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":"Power curve"},React.createElement("title",null,"Power curve"),React.createElement("desc",null,"Statistical power as a function of sample size"),React.createElement("rect",{id:"background",x:0,y:0,width:VBW,height:VBH,fill:"#ffffff"}),React.createElement("rect",{id:"plot-area-background",x:M.left,y:M.top,width:w,height:h,fill:"#fafafa"}),React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`yg${t}`,x1:M.left,x2:M.left+w,y1:sy(t),y2:sy(t),stroke:"#e8e8e8",strokeWidth:"0.5"}))),React.createElement("g",{id:"reference-line"},React.createElement("line",{x1:M.left,x2:M.left+w,y1:sy(.8),y2:sy(.8),stroke:"#D55E00",strokeWidth:"1",strokeDasharray:"6,3",opacity:"0.6"}),React.createElement("text",{x:M.left+w+3,y:sy(.8)+3,fontSize:"9",fill:"#D55E00",fontFamily:"sans-serif"},"0.80")),React.createElement("g",{id:"power-curve"},React.createElement("path",{d:pathD,fill:"none",stroke:"#0072B2",strokeWidth:"2.5",strokeLinejoin:"round"})),marker&&React.createElement("g",{id:"marker"},React.createElement("line",{x1:marker.x,x2:marker.x,y1:M.top,y2:M.top+h,stroke:"#E69F00",strokeWidth:"1",strokeDasharray:"4,2"}),React.createElement("circle",{cx:marker.x,cy:marker.y,r:"5",fill:"#E69F00",stroke:"#fff",strokeWidth:"1.5"})),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{id:"plot-frame-top",x1:M.left,y1:M.top,x2:M.left+w,y2:M.top}),React.createElement("line",{id:"plot-frame-right",x1:M.left+w,y1:M.top,x2:M.left+w,y2:M.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:M.left,y1:M.top+h,x2:M.left+w,y2:M.top+h}),React.createElement("line",{id:"plot-frame-left",x1:M.left,y1:M.top,x2:M.left,y2:M.top+h})),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("text",{key:`yl${t}`,x:M.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t.toFixed(1)))),React.createElement("g",{id:"y-axis-label"},React.createElement("text",{x:14,y:M.top+h/2,textAnchor:"middle",fontSize:"12",fill:"#333",fontFamily:"sans-serif",transform:`rotate(-90,14,${M.top+h/2})`},"Power (1 \u2212 \u03B2)")),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("text",{key:`xl${t}`,x:sx(t),y:M.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},Math.round(t)))),React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:M.left+w/2,y:VBH-6,textAnchor:"middle",fontSize:"12",fill:"#333",fontFamily:"sans-serif"},test.nLabel)))});function App(){let isMobile=useIsMobile(900),[testKey,setTestKey]=useState("t-ind"),[solveFor,setSolveFor]=useState("n"),[effectSize,setEffectSize]=useState("0.5"),[nInput,setNInput]=useState("30"),[alphaInput,setAlphaInput]=useState("0.05"),[powerInput,setPowerInput]=useState("0.80"),[tails,setTails]=useState(2),[kInput,setKInput]=useState("3"),[dfInput,setDfInput]=useState("1"),prevResultRef=useRef(void 0),[resultFlash,setResultFlash]=useState(!1),test=TESTS[testKey],es=parseFloat(effectSize)||0,n=parseInt(nInput)||2,alpha=parseFloat(alphaInput)||.05,power=parseFloat(powerInput)||.8,k=parseInt(kInput)||3,df=parseInt(dfInput)||1,result=useMemo(()=>{try{let minN=test.minN||2,pw=(e,ni)=>test.power(e,ni,alpha,tails,k,df);if(solveFor==="n")return es<=0||alpha<=0||alpha>=1||power<=0||power>=1?null:Math.ceil(bisect(ni=>pw(es,Math.max(minN,Math.round(ni))),power,minN,1e5,.5));if(solveFor==="power")return es<=0||n=1?null:pw(es,n)}catch{return null}return null},[test,solveFor,es,n,alpha,power,tails,k,df]),resultText=useMemo(()=>result==null?"\u2014":solveFor==="n"?`${result}`:solveFor==="power"?`${(result*100).toFixed(1)}%`:"\u2014",[result,solveFor]),resultLabel={n:`Required ${test.nLabel}`,power:"Statistical power"}[solveFor];useEffect(()=>{if(prevResultRef.current!==void 0&&result!==prevResultRef.current&&result!=null){setResultFlash(!0);let id=setTimeout(()=>setResultFlash(!1),300);return()=>clearTimeout(id)}prevResultRef.current=result},[result]),useEffect(()=>{resultFlash&&(prevResultRef.current=result)},[resultFlash]);let handleTestChange=useCallback(e=>{let key=e.target.value;setTestKey(key),setSolveFor("n"),(key==="anova"||key==="chi2")&&setTails(2)},[]),inputStyle={width:"100%"};return React.createElement("div",{style:{maxWidth:960,margin:"0 auto",padding:"24px 32px"}},React.createElement(PageHeader,{toolName:"power",title:"Power Analysis"}),React.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:10,marginBottom:16}},React.createElement("div",{className:"dv-panel",style:{padding:12,flex:1,minWidth:200}},React.createElement("div",{className:"dv-label"},"Statistical test"),React.createElement("select",{value:testKey,onChange:handleTestChange,className:"dv-select",style:{width:"100%"}},Object.entries(TESTS).map(([key,t])=>React.createElement("option",{key,value:key},t.label)))),React.createElement("div",{className:"dv-panel",style:{padding:12,flex:1,minWidth:200}},React.createElement("div",{className:"dv-label",style:{marginBottom:6}},"What do you need to find?"),React.createElement("div",{className:"dv-seg"},[["n","Sample size"],["power","Power"]].map(([key,label])=>{let active=solveFor===key;return React.createElement("button",{key,type:"button",onClick:()=>setSolveFor(key),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),style:{fontSize:12}},label)}))),React.createElement("div",{className:"dv-panel",style:{padding:"12px 16px",borderLeft:"4px solid var(--accent-blue)",flex:"1 1 100%"}},React.createElement("div",{style:{fontSize:13,color:"var(--text)",lineHeight:1.5}},test.question))),React.createElement("div",{className:"power-main-row",style:{display:"flex",flexDirection:isMobile?"column":"row",gap:10,alignItems:"stretch"}},React.createElement("div",{className:"power-col-left",style:{width:isMobile?"auto":279,flexShrink:0,display:"flex",flexDirection:"column",gap:6}},React.createElement("div",{className:"dv-panel",style:{padding:12}},React.createElement("div",{className:"dv-label",style:{marginBottom:6}},"Expected effect size"),React.createElement(EffectSizePanel,{testKey,effectSize,onEffectChange:setEffectSize,disabled:solveFor==="effect"})),React.createElement("div",{className:"dv-panel",style:{padding:12,display:"flex",flexDirection:"column",gap:10,flex:1}},React.createElement("div",{style:{opacity:solveFor==="n"?.4:1}},React.createElement("div",{className:"dv-label"},test.nLabel),React.createElement(NumberInput,{min:"2",step:"1",value:nInput,onChange:e=>setNInput(e.target.value),disabled:solveFor==="n",style:inputStyle})),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Significance level (\u03B1)"),React.createElement("div",{className:"dv-seg"},["0.10","0.05","0.01","0.001"].map(a=>{let active=alphaInput===a;return React.createElement("button",{key:a,type:"button",onClick:()=>setAlphaInput(a),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},a)}))),React.createElement("div",{style:{opacity:solveFor==="power"?.4:1}},React.createElement("div",{className:"dv-label"},"Desired power (1 \u2212 \u03B2)"),React.createElement("div",{className:"dv-seg"},["0.70","0.80","0.90","0.95"].map(p=>{let active=powerInput===p;return React.createElement("button",{key:p,type:"button",onClick:()=>setPowerInput(p),disabled:solveFor==="power",title:p==="0.80"?"0.80 (standard)":void 0,"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},p)}))),testKey!=="anova"&&testKey!=="chi2"&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Direction of the test"),React.createElement("div",{className:"dv-seg"},[[2,"Two-sided"],[1,"One-sided"]].map(([t,label])=>{let active=tails===t;return React.createElement("button",{key:t,type:"button",onClick:()=>setTails(t),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},label)})),React.createElement("div",{style:{fontSize:10,color:"var(--text-faint)",marginTop:4}},"Two-sided: the difference could go either way. One-sided: you expect a specific direction.")),test.hasGroups&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Number of groups"),React.createElement(NumberInput,{min:"2",max:"20",step:"1",value:kInput,onChange:e=>setKInput(e.target.value),style:inputStyle})),test.hasDf&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Degrees of freedom"),React.createElement(NumberInput,{min:"1",max:"100",step:"1",value:dfInput,onChange:e=>setDfInput(e.target.value),style:inputStyle}),React.createElement("div",{style:{fontSize:10,color:"var(--text-faint)",marginTop:2}},"Goodness-of-fit: categories \u2212 1.",React.createElement("br",null),"Independence: (rows\u22121)(cols\u22121).")))),React.createElement("div",{className:"power-col-right",style:{flex:1,minWidth:isMobile?0:360,display:"flex",flexDirection:"column",gap:6}},!isMobile&&React.createElement("div",{className:"dv-plot-card power-chart-card",style:{background:"var(--plot-card-bg)",border:"1px solid var(--plot-card-border)",borderRadius:10,padding:12,flex:1,display:"flex",alignItems:"center",justifyContent:"center",minHeight:0}},React.createElement(PowerCurve,{testKey,powerFn:test.power,params:{es,n,alpha,tails,k,df},solveFor,result})),React.createElement("div",{className:"dv-panel",style:{padding:16,textAlign:"center",background:resultFlash?"var(--success-bg)":void 0,transition:"background 0.3s ease",position:"relative"}},React.createElement("div",{style:{fontSize:12,color:"var(--text-muted)",marginBottom:4}},resultLabel),React.createElement("div",{style:{fontSize:36,fontWeight:700,color:result==null?"var(--border-strong)":solveFor==="power"&&result<.6?"var(--danger-text)":solveFor==="power"&&result<.8?"var(--accent-warning)":"var(--accent-blue)",fontFamily:"monospace"}},resultText),solveFor==="n"&&result!=null&&test.totalLabel&&React.createElement("div",{style:{fontSize:12,color:"var(--text-faint)",marginTop:4}},test.totalLabel(result,k)),result!=null&&typeof buildRScriptForPower=="function"&&React.createElement("button",{className:"dv-btn dv-btn-dl power-r-dl",onClick:e=>{e.stopPropagation();let script=buildRScriptForPower({testKey,solveFor,es,n,alpha,power,tails,k,df,result});downloadText(script,`power_${testKey}_${solveFor}.R`),flashSaved(e.currentTarget)},title:"Download a runnable R script reproducing this power calculation with the pwr package",style:{position:"absolute",top:10,right:10}},"\u2B07 R")))),React.createElement("div",{className:"dv-panel",style:{padding:12,marginTop:10}},React.createElement("div",{style:{fontSize:11,fontWeight:700,color:"var(--text-muted)",marginBottom:6,textTransform:"uppercase",letterSpacing:"0.5px"}},"What do these numbers mean?"),React.createElement("div",{style:{fontSize:12,color:"var(--text-muted)",lineHeight:1.7}},React.createElement("b",null,"Power")," is the probability that you will correctly reject the null hypothesis (i.e. to claim a result is significant). A power of 0.80 (the dashed line) means an 80% chance of success \u2014 this is the standard minimum. Higher is better but costs more subjects.",React.createElement("br",null),React.createElement("br",null),React.createElement("b",null,"Significance level (\u03B1)")," is the risk of a false positive \u2014 concluding there is an effect when there is none. The standard \u03B1\xA0=\xA00.05 means you accept a 5% chance of a false alarm. Lowering \u03B1 (e.g. to 0.01) makes you more conservative but requires more subjects to keep power high.",React.createElement("br",null),React.createElement("br",null),React.createElement("b",null,"Sample size (",test.nLabel,")")," is the number of observations you need to collect. More subjects give you more power to detect a given effect.",React.createElement("br",null),React.createElement("br",null),React.createElement("b",null,"Effect size"),' measures how large the real difference or relationship is, scaled by variability. Use the "From my data" tab to compute it from values you expect (e.g. group means and standard deviation from pilot data or published studies).',testKey==="t-ind"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"two-sample t-test"),", the effect size (Cohen's d) is the difference between the two group means divided by their common standard deviation. A d of 0.2 is small, 0.5 is medium, and 0.8 is large."),testKey==="t-paired"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"paired t-test"),", the effect size (Cohen's d) is the expected mean of the paired differences divided by the standard deviation of those differences."),testKey==="t-one"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"one-sample t-test"),", the effect size (Cohen's d) is how far the true mean deviates from the reference value, divided by the standard deviation."),testKey==="anova"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For ",React.createElement("b",null,"ANOVA"),", the effect size (Cohen's f) captures how spread out the group means are relative to within-group variability. An f of 0.10 is small, 0.25 is medium, and 0.40 is large."),testKey==="correlation"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For ",React.createElement("b",null,"correlation"),", the effect size is simply the expected Pearson r. An r of 0.1 is small, 0.3 is medium, and 0.5 is large."),testKey==="chi2"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"chi-square test"),", the effect size (Cohen's w) measures how far the observed category proportions deviate from expected. A w of 0.1 is small, 0.3 is medium, and 0.5 is large.",React.createElement("br",null),React.createElement("br",null),"Degrees of freedom:",React.createElement("br",null),"\u2022 Goodness-of-fit: ",React.createElement("b",null,"df = categories \u2212 1"),React.createElement("br",null),"\u2022 Independence: ",React.createElement("b",null,"df = (rows \u2212 1) \xD7 (cols \u2212 1)")))))}export{App}; -//# sourceMappingURL=power-app-WK6W4FL3.js.map +import{makeTicks}from"./chunk-T4V5V5TK.js";import{NumberInput,PageHeader,bisect,buildRScriptForPower,downloadText,fFromGroupMeans,flashSaved,powerAnova,powerChi2,powerCorrelation,powerOneSample,powerPaired,powerTwoSample,useIsMobile}from"./chunk-HFTDMS7M.js";var{useState,useMemo,useCallback,useRef,useEffect,forwardRef}=React;function dFromMeans(m1,m2,sd){return sd>0?Math.abs(m1-m2)/sd:0}function wFromProportions(observed,expected){if(!observed.length||observed.length!==expected.length)return 0;let sum=0;for(let i=0;ipowerTwoSample(es,n,alpha,tails),effectMax:3,totalN:n=>n*2,totalLabel:n=>`Total N = ${n*2} (${n} per group \xD7 2)`},"t-paired":{label:"Paired t-test",question:"How many pairs to detect a difference between matched measurements?",nLabel:"n (pairs)",power:(es,n,alpha,tails)=>powerPaired(es,n,alpha,tails),effectMax:3},"t-one":{label:"One-sample t-test",question:"How many observations to detect a deviation from a known reference value?",nLabel:"n",power:(es,n,alpha,tails)=>powerOneSample(es,n,alpha,tails),effectMax:3},anova:{label:"One-way ANOVA",question:"How many subjects per group to detect differences among k group means?",nLabel:"n per group",hasGroups:!0,power:(es,n,alpha,_tails,k)=>powerAnova(es,n,alpha,k??2),effectMax:2,totalN:(n,k)=>n*k,totalLabel:(n,k)=>`Total N = ${n*k} (${n} per group \xD7 ${k} groups)`},correlation:{label:"Correlation",question:"How many observations to detect a non-zero Pearson correlation?",nLabel:"n (total)",power:(es,n,alpha,tails)=>powerCorrelation(es,n,alpha,tails),effectMax:.99,minN:4},chi2:{label:"Chi-square test",question:"How many observations for a goodness-of-fit or independence test?",nLabel:"n (total)",hasDf:!0,power:(es,n,alpha,_tails,_k,df)=>powerChi2(es,n,alpha,df??1),effectMax:1}};function EffectSizePanel({testKey,effectSize,onEffectChange,disabled}){let[mode,setMode]=useState("helper"),[mean1,setMean1]=useState(""),[mean2,setMean2]=useState(""),[sd,setSd]=useState(""),[diffMean,setDiffMean]=useState(""),[diffSd,setDiffSd]=useState(""),[groupMeansStr,setGroupMeansStr]=useState(""),[withinSd,setWithinSd]=useState(""),[expectedStr,setExpectedStr]=useState(""),[observedStr,setObservedStr]=useState(""),inputStyle={width:"100%"},smallLabel={fontSize:11,color:"var(--text-muted)",marginBottom:2},note={fontSize:10,color:"var(--text-faint)",marginTop:2};function parseProportions(str){if(!str.trim())return[];let parts;str.includes(":")?parts=str.split(":").map(s=>parseFloat(s.trim())).filter(v=>!isNaN(v)&&v>=0):parts=str.split(",").map(s=>parseFloat(s.trim())).filter(v=>!isNaN(v)&&v>=0);let sum=parts.reduce((a,b)=>a+b,0);return sum>0?parts.map(p=>p/sum):[]}let computeFromHelper=useCallback(()=>{if(testKey==="t-ind"){let m1=parseFloat(mean1),m2=parseFloat(mean2),s=parseFloat(sd);!isNaN(m1)&&!isNaN(m2)&&!isNaN(s)&&s>0&&onEffectChange(dFromMeans(m1,m2,s).toFixed(4))}else if(testKey==="t-paired"||testKey==="t-one"){let dm=parseFloat(diffMean),ds=parseFloat(diffSd);!isNaN(dm)&&!isNaN(ds)&&ds>0&&onEffectChange((Math.abs(dm)/ds).toFixed(4))}else if(testKey==="anova"){let means=groupMeansStr.split(",").map(s=>parseFloat(s.trim())).filter(v=>!isNaN(v)),wsd=parseFloat(withinSd);means.length>=2&&!isNaN(wsd)&&wsd>0&&onEffectChange(fFromGroupMeans(means,wsd).toFixed(4))}else if(testKey==="chi2"){let exp=parseProportions(expectedStr),obs=parseProportions(observedStr);exp.length>=2&&obs.length===exp.length&&onEffectChange(wFromProportions(obs,exp).toFixed(4))}},[testKey,mean1,mean2,sd,diffMean,diffSd,groupMeansStr,withinSd,expectedStr,observedStr,onEffectChange]),computedD=parseFloat(String(effectSize)),sizeLabel=testKey==="correlation"?computedD<.1?"":computedD<.3?"small":computedD<.5?"medium":"large":testKey==="anova"?computedD<.1?"":computedD<.25?"small":computedD<.4?"medium":"large":testKey==="chi2"?computedD<.1?"":computedD<.3?"small":computedD<.5?"medium":"large":computedD<.2?"":computedD<.5?"small":computedD<.8?"medium":"large",sizeColor=sizeLabel==="small"?"var(--success-text)":sizeLabel==="medium"?"var(--warning-text)":sizeLabel==="large"?"var(--danger-text)":"var(--text-faint)";return testKey==="correlation"?React.createElement("div",{style:{opacity:disabled?.4:1}},React.createElement("div",{style:smallLabel},"Expected correlation |r|"),React.createElement(NumberInput,{min:"0.01",max:"0.99",step:"0.05",value:effectSize,onChange:e=>onEffectChange(e.target.value),disabled,style:inputStyle}),sizeLabel&&React.createElement("div",{style:{...note,color:sizeColor,fontWeight:600}},sizeLabel," effect"),React.createElement("div",{style:note},"How strong a linear relationship do you expect?")):React.createElement("div",{style:{opacity:disabled?.4:1}},React.createElement("div",{className:"dv-seg",style:{marginBottom:6}},["helper","direct"].map(m=>{let active=mode===m;return React.createElement("button",{key:m,type:"button",onClick:()=>setMode(m),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},m==="helper"?"From my data":"Direct value")})),mode==="helper"&&testKey==="t-ind"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},"Expected mean \u2014 group 1"),React.createElement(NumberInput,{step:"any",value:mean1,onChange:e=>setMean1(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 15.2"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Expected mean \u2014 group 2"),React.createElement(NumberInput,{step:"any",value:mean2,onChange:e=>setMean2(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 12.8"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Common standard deviation"),React.createElement(NumberInput,{step:"any",min:"0",value:sd,onChange:e=>setSd(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 4.5"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size"),React.createElement("div",{style:note},"Use pilot data or literature values. The SD should be the pooled within-group SD.")),mode==="helper"&&(testKey==="t-paired"||testKey==="t-one")&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},testKey==="t-paired"?"Expected mean difference":"Expected deviation from reference"),React.createElement(NumberInput,{step:"any",value:diffMean,onChange:e=>setDiffMean(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 2.5"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},testKey==="t-paired"?"SD of paired differences":"Standard deviation"),React.createElement(NumberInput,{step:"any",min:"0",value:diffSd,onChange:e=>setDiffSd(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 5.0"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size")),mode==="helper"&&testKey==="anova"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},"Expected group means (comma-separated)"),React.createElement("input",{type:"text",value:groupMeansStr,onChange:e=>setGroupMeansStr(e.target.value),disabled,className:"dv-input-num",style:inputStyle,placeholder:"e.g. 10, 12, 15"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Within-group standard deviation"),React.createElement(NumberInput,{step:"any",min:"0",value:withinSd,onChange:e=>setWithinSd(e.target.value),disabled,style:inputStyle,placeholder:"e.g. 4.0"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size"),React.createElement("div",{style:note},"Enter the means you expect for each treatment group, and the common within-group SD (from pilot data or literature).")),mode==="helper"&&testKey==="chi2"&&React.createElement("div",{style:{display:"flex",flexDirection:"column",gap:6}},React.createElement("div",null,React.createElement("div",{style:smallLabel},"Baseline proportions (what the theory predicts)"),React.createElement("input",{type:"text",value:expectedStr,onChange:e=>setExpectedStr(e.target.value),disabled,className:"dv-input-num",style:inputStyle,placeholder:"e.g. 3:1 or 0.75, 0.25"})),React.createElement("div",null,React.createElement("div",{style:smallLabel},"Actual proportions (what you think is really happening)"),React.createElement("input",{type:"text",value:observedStr,onChange:e=>setObservedStr(e.target.value),disabled,className:"dv-input-num",style:inputStyle,placeholder:"e.g. 2:1 or 0.67, 0.33"})),React.createElement("button",{onClick:computeFromHelper,disabled,className:"dv-btn dv-btn-primary",style:{fontSize:12,padding:"5px 10px"}},"Compute effect size"),React.createElement("div",{style:note},"Use ratios (3:1) or proportions (0.75, 0.25). Common for Mendelian segregation tests.")),mode==="direct"&&React.createElement("div",null,React.createElement("div",{style:smallLabel},testKey==="anova"?"Effect size (f)":testKey==="chi2"?"Effect size (w)":"Effect size (d)"),React.createElement(NumberInput,{min:"0.01",step:"0.1",value:effectSize,onChange:e=>onEffectChange(e.target.value),disabled,style:inputStyle}),React.createElement("div",{style:note},testKey==="anova"?"f = SD of group means / within-group SD":testKey==="chi2"?"w = \u221A(\u03A3 (p_obs \u2212 p_exp)\xB2 / p_exp)":"d = |difference in means| / pooled SD")),effectSize&&parseFloat(String(effectSize))>0&&React.createElement("div",{style:{marginTop:6,padding:"6px 10px",background:"var(--info-bg)",borderRadius:6,border:"1px solid var(--info-border)",display:"flex",justifyContent:"space-between",alignItems:"center"}},React.createElement("span",{style:{fontSize:12,color:"var(--text)"}},"Effect size = ",React.createElement("b",null,parseFloat(String(effectSize)).toFixed(3))),sizeLabel&&React.createElement("span",{style:{fontSize:11,fontWeight:600,color:sizeColor}},sizeLabel)))}var PowerCurve=forwardRef(function({testKey,powerFn,params,solveFor,result},ref){let test=TESTS[testKey];if(!test)return null;let VBW=520,VBH=320,M={top:30,right:20,bottom:50,left:55},w=VBW-M.left-M.right,h=VBH-M.top-M.bottom,{es,n,alpha,tails,k,df}=params,minN=test.minN||2,maxN=Math.max(200,result&&solveFor==="n"?result*2.5:200),xRange=[minN,Math.ceil(maxN)],curvePoints=[];for(let i=0;i<=100;i++){let xn=xRange[0]+(xRange[1]-xRange[0])*i/100,ni=Math.max(minN,Math.round(xn)),pw=powerFn(es,ni,alpha,tails,k,df);curvePoints.push({x:ni,y:Math.min(1,Math.max(0,pw))})}let sx=v=>M.left+(v-xRange[0])/(xRange[1]-xRange[0])*w,sy=v=>M.top+(1-v)*h,pathD=curvePoints.map((p,i)=>`${i===0?"M":"L"}${sx(p.x).toFixed(2)},${sy(p.y).toFixed(2)}`).join(" "),yTicks=[0,.2,.4,.6,.8,1],xTicks=makeTicks(xRange[0],xRange[1],6),marker=null;if(result!=null&&isFinite(result)&&solveFor==="n"){let my=powerFn(es,Math.round(result),alpha,tails,k,df);marker={x:sx(result),y:sy(Math.min(1,Math.max(0,my)))}}else result!=null&&isFinite(result)&&solveFor==="power"&&(marker={x:sx(n),y:sy(Math.min(1,Math.max(0,result)))});return React.createElement("svg",{ref,viewBox:`0 0 ${VBW} ${VBH}`,preserveAspectRatio:"xMidYMid meet",style:{width:"100%",height:"100%",display:"block"},xmlns:"http://www.w3.org/2000/svg",role:"img","aria-label":"Power curve"},React.createElement("title",null,"Power curve"),React.createElement("desc",null,"Statistical power as a function of sample size"),React.createElement("rect",{id:"background",x:0,y:0,width:VBW,height:VBH,fill:"#ffffff"}),React.createElement("rect",{id:"plot-area-background",x:M.left,y:M.top,width:w,height:h,fill:"#fafafa"}),React.createElement("g",{id:"grid"},yTicks.map(t=>React.createElement("line",{key:`yg${t}`,x1:M.left,x2:M.left+w,y1:sy(t),y2:sy(t),stroke:"#e8e8e8",strokeWidth:"0.5"}))),React.createElement("g",{id:"reference-line"},React.createElement("line",{x1:M.left,x2:M.left+w,y1:sy(.8),y2:sy(.8),stroke:"#D55E00",strokeWidth:"1",strokeDasharray:"6,3",opacity:"0.6"}),React.createElement("text",{x:M.left+w+3,y:sy(.8)+3,fontSize:"9",fill:"#D55E00",fontFamily:"sans-serif"},"0.80")),React.createElement("g",{id:"power-curve"},React.createElement("path",{d:pathD,fill:"none",stroke:"#0072B2",strokeWidth:"2.5",strokeLinejoin:"round"})),marker&&React.createElement("g",{id:"marker"},React.createElement("line",{x1:marker.x,x2:marker.x,y1:M.top,y2:M.top+h,stroke:"#E69F00",strokeWidth:"1",strokeDasharray:"4,2"}),React.createElement("circle",{cx:marker.x,cy:marker.y,r:"5",fill:"#E69F00",stroke:"#fff",strokeWidth:"1.5"})),React.createElement("g",{id:"plot-frame",fill:"none",stroke:"#333",strokeWidth:"1"},React.createElement("line",{id:"plot-frame-top",x1:M.left,y1:M.top,x2:M.left+w,y2:M.top}),React.createElement("line",{id:"plot-frame-right",x1:M.left+w,y1:M.top,x2:M.left+w,y2:M.top+h}),React.createElement("line",{id:"plot-frame-bottom",x1:M.left,y1:M.top+h,x2:M.left+w,y2:M.top+h}),React.createElement("line",{id:"plot-frame-left",x1:M.left,y1:M.top,x2:M.left,y2:M.top+h})),React.createElement("g",{id:"axis-y"},yTicks.map(t=>React.createElement("text",{key:`yl${t}`,x:M.left-8,y:sy(t)+4,textAnchor:"end",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},t.toFixed(1)))),React.createElement("g",{id:"y-axis-label"},React.createElement("text",{x:14,y:M.top+h/2,textAnchor:"middle",fontSize:"12",fill:"#333",fontFamily:"sans-serif",transform:`rotate(-90,14,${M.top+h/2})`},"Power (1 \u2212 \u03B2)")),React.createElement("g",{id:"axis-x"},xTicks.map(t=>React.createElement("text",{key:`xl${t}`,x:sx(t),y:M.top+h+18,textAnchor:"middle",fontSize:"11",fill:"#555",fontFamily:"sans-serif"},Math.round(t)))),React.createElement("g",{id:"x-axis-label"},React.createElement("text",{x:M.left+w/2,y:VBH-6,textAnchor:"middle",fontSize:"12",fill:"#333",fontFamily:"sans-serif"},test.nLabel)))});function App(){let isMobile=useIsMobile(900),[testKey,setTestKey]=useState("t-ind"),[solveFor,setSolveFor]=useState("n"),[effectSize,setEffectSize]=useState("0.5"),[nInput,setNInput]=useState("30"),[alphaInput,setAlphaInput]=useState("0.05"),[powerInput,setPowerInput]=useState("0.80"),[tails,setTails]=useState(2),[kInput,setKInput]=useState("3"),[dfInput,setDfInput]=useState("1"),prevResultRef=useRef(void 0),[resultFlash,setResultFlash]=useState(!1),test=TESTS[testKey],es=parseFloat(effectSize)||0,n=parseInt(nInput)||2,alpha=parseFloat(alphaInput)||.05,power=parseFloat(powerInput)||.8,k=parseInt(kInput)||3,df=parseInt(dfInput)||1,result=useMemo(()=>{try{let minN=test.minN||2,pw=(e,ni)=>test.power(e,ni,alpha,tails,k,df);if(solveFor==="n")return es<=0||alpha<=0||alpha>=1||power<=0||power>=1?null:Math.ceil(bisect(ni=>pw(es,Math.max(minN,Math.round(ni))),power,minN,1e5,.5));if(solveFor==="power")return es<=0||n=1?null:pw(es,n)}catch{return null}return null},[test,solveFor,es,n,alpha,power,tails,k,df]),resultText=useMemo(()=>result==null?"\u2014":solveFor==="n"?`${result}`:solveFor==="power"?`${(result*100).toFixed(1)}%`:"\u2014",[result,solveFor]),resultLabel={n:`Required ${test.nLabel}`,power:"Statistical power"}[solveFor];useEffect(()=>{if(prevResultRef.current!==void 0&&result!==prevResultRef.current&&result!=null){setResultFlash(!0);let id=setTimeout(()=>setResultFlash(!1),300);return()=>clearTimeout(id)}prevResultRef.current=result},[result]),useEffect(()=>{resultFlash&&(prevResultRef.current=result)},[resultFlash]);let handleTestChange=useCallback(e=>{let key=e.target.value;setTestKey(key),setSolveFor("n"),(key==="anova"||key==="chi2")&&setTails(2)},[]),inputStyle={width:"100%"};return React.createElement("div",{style:{maxWidth:960,margin:"0 auto",padding:"24px 32px"}},React.createElement(PageHeader,{toolName:"power",title:"Power Analysis"}),React.createElement("div",{style:{display:"flex",flexWrap:"wrap",gap:10,marginBottom:16}},React.createElement("div",{className:"dv-panel",style:{padding:12,flex:1,minWidth:200}},React.createElement("div",{className:"dv-label"},"Statistical test"),React.createElement("select",{value:testKey,onChange:handleTestChange,className:"dv-select",style:{width:"100%"}},Object.entries(TESTS).map(([key,t])=>React.createElement("option",{key,value:key},t.label)))),React.createElement("div",{className:"dv-panel",style:{padding:12,flex:1,minWidth:200}},React.createElement("div",{className:"dv-label",style:{marginBottom:6}},"What do you need to find?"),React.createElement("div",{className:"dv-seg"},[["n","Sample size"],["power","Power"]].map(([key,label])=>{let active=solveFor===key;return React.createElement("button",{key,type:"button",onClick:()=>setSolveFor(key),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":""),style:{fontSize:12}},label)}))),React.createElement("div",{className:"dv-panel",style:{padding:"12px 16px",borderLeft:"4px solid var(--accent-blue)",flex:"1 1 100%"}},React.createElement("div",{style:{fontSize:13,color:"var(--text)",lineHeight:1.5}},test.question))),React.createElement("div",{className:"power-main-row",style:{display:"flex",flexDirection:isMobile?"column":"row",gap:10,alignItems:"stretch"}},React.createElement("div",{className:"power-col-left",style:{width:isMobile?"auto":279,flexShrink:0,display:"flex",flexDirection:"column",gap:6}},React.createElement("div",{className:"dv-panel",style:{padding:12}},React.createElement("div",{className:"dv-label",style:{marginBottom:6}},"Expected effect size"),React.createElement(EffectSizePanel,{testKey,effectSize,onEffectChange:setEffectSize,disabled:solveFor==="effect"})),React.createElement("div",{className:"dv-panel",style:{padding:12,display:"flex",flexDirection:"column",gap:10,flex:1}},React.createElement("div",{style:{opacity:solveFor==="n"?.4:1}},React.createElement("div",{className:"dv-label"},test.nLabel),React.createElement(NumberInput,{min:"2",step:"1",value:nInput,onChange:e=>setNInput(e.target.value),disabled:solveFor==="n",style:inputStyle})),React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Significance level (\u03B1)"),React.createElement("div",{className:"dv-seg"},["0.10","0.05","0.01","0.001"].map(a=>{let active=alphaInput===a;return React.createElement("button",{key:a,type:"button",onClick:()=>setAlphaInput(a),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},a)}))),React.createElement("div",{style:{opacity:solveFor==="power"?.4:1}},React.createElement("div",{className:"dv-label"},"Desired power (1 \u2212 \u03B2)"),React.createElement("div",{className:"dv-seg"},["0.70","0.80","0.90","0.95"].map(p=>{let active=powerInput===p;return React.createElement("button",{key:p,type:"button",onClick:()=>setPowerInput(p),disabled:solveFor==="power",title:p==="0.80"?"0.80 (standard)":void 0,"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},p)}))),testKey!=="anova"&&testKey!=="chi2"&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Direction of the test"),React.createElement("div",{className:"dv-seg"},[[2,"Two-sided"],[1,"One-sided"]].map(([t,label])=>{let active=tails===t;return React.createElement("button",{key:t,type:"button",onClick:()=>setTails(t),"aria-pressed":active,className:"dv-seg-btn"+(active?" dv-seg-btn-active":"")},label)})),React.createElement("div",{style:{fontSize:10,color:"var(--text-faint)",marginTop:4}},"Two-sided: the difference could go either way. One-sided: you expect a specific direction.")),test.hasGroups&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Number of groups"),React.createElement(NumberInput,{min:"2",max:"20",step:"1",value:kInput,onChange:e=>setKInput(e.target.value),style:inputStyle})),test.hasDf&&React.createElement("div",null,React.createElement("div",{className:"dv-label"},"Degrees of freedom"),React.createElement(NumberInput,{min:"1",max:"100",step:"1",value:dfInput,onChange:e=>setDfInput(e.target.value),style:inputStyle}),React.createElement("div",{style:{fontSize:10,color:"var(--text-faint)",marginTop:2}},"Goodness-of-fit: categories \u2212 1.",React.createElement("br",null),"Independence: (rows\u22121)(cols\u22121).")))),React.createElement("div",{className:"power-col-right",style:{flex:1,minWidth:isMobile?0:360,display:"flex",flexDirection:"column",gap:6}},!isMobile&&React.createElement("div",{className:"dv-plot-card power-chart-card",style:{background:"var(--plot-card-bg)",border:"1px solid var(--plot-card-border)",borderRadius:10,padding:12,flex:1,display:"flex",alignItems:"center",justifyContent:"center",minHeight:0}},React.createElement(PowerCurve,{testKey,powerFn:test.power,params:{es,n,alpha,tails,k,df},solveFor,result})),React.createElement("div",{className:"dv-panel",style:{padding:16,textAlign:"center",background:resultFlash?"var(--success-bg)":void 0,transition:"background 0.3s ease",position:"relative"}},React.createElement("div",{style:{fontSize:12,color:"var(--text-muted)",marginBottom:4}},resultLabel),React.createElement("div",{style:{fontSize:36,fontWeight:700,color:result==null?"var(--border-strong)":solveFor==="power"&&result<.6?"var(--danger-text)":solveFor==="power"&&result<.8?"var(--accent-warning)":"var(--accent-blue)",fontFamily:"monospace"}},resultText),solveFor==="n"&&result!=null&&test.totalLabel&&React.createElement("div",{style:{fontSize:12,color:"var(--text-faint)",marginTop:4}},test.totalLabel(result,k)),result!=null&&typeof buildRScriptForPower=="function"&&React.createElement("button",{className:"dv-btn dv-btn-dl power-r-dl",onClick:e=>{e.stopPropagation();let script=buildRScriptForPower({testKey,solveFor,es,n,alpha,power,tails,k,df,result});downloadText(script,`power_${testKey}_${solveFor}.R`),flashSaved(e.currentTarget)},title:"Download a runnable R script reproducing this power calculation with the pwr package",style:{position:"absolute",top:10,right:10}},"\u2B07 R")))),React.createElement("div",{className:"dv-panel",style:{padding:12,marginTop:10}},React.createElement("div",{style:{fontSize:11,fontWeight:700,color:"var(--text-muted)",marginBottom:6,textTransform:"uppercase",letterSpacing:"0.5px"}},"What do these numbers mean?"),React.createElement("div",{style:{fontSize:12,color:"var(--text-muted)",lineHeight:1.7}},React.createElement("b",null,"Power")," is the probability that you will correctly reject the null hypothesis (i.e. to claim a result is significant). A power of 0.80 (the dashed line) means an 80% chance of success \u2014 this is the standard minimum. Higher is better but costs more subjects.",React.createElement("br",null),React.createElement("br",null),React.createElement("b",null,"Significance level (\u03B1)")," is the risk of a false positive \u2014 concluding there is an effect when there is none. The standard \u03B1\xA0=\xA00.05 means you accept a 5% chance of a false alarm. Lowering \u03B1 (e.g. to 0.01) makes you more conservative but requires more subjects to keep power high.",React.createElement("br",null),React.createElement("br",null),React.createElement("b",null,"Sample size (",test.nLabel,")")," is the number of observations you need to collect. More subjects give you more power to detect a given effect.",React.createElement("br",null),React.createElement("br",null),React.createElement("b",null,"Effect size"),' measures how large the real difference or relationship is, scaled by variability. Use the "From my data" tab to compute it from values you expect (e.g. group means and standard deviation from pilot data or published studies).',testKey==="t-ind"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"two-sample t-test"),", the effect size (Cohen's d) is the difference between the two group means divided by their common standard deviation. A d of 0.2 is small, 0.5 is medium, and 0.8 is large."),testKey==="t-paired"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"paired t-test"),", the effect size (Cohen's d) is the expected mean of the paired differences divided by the standard deviation of those differences."),testKey==="t-one"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"one-sample t-test"),", the effect size (Cohen's d) is how far the true mean deviates from the reference value, divided by the standard deviation."),testKey==="anova"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For ",React.createElement("b",null,"ANOVA"),", the effect size (Cohen's f) captures how spread out the group means are relative to within-group variability. An f of 0.10 is small, 0.25 is medium, and 0.40 is large."),testKey==="correlation"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For ",React.createElement("b",null,"correlation"),", the effect size is simply the expected Pearson r. An r of 0.1 is small, 0.3 is medium, and 0.5 is large."),testKey==="chi2"&&React.createElement(React.Fragment,null,React.createElement("br",null),React.createElement("br",null),"For a ",React.createElement("b",null,"chi-square test"),", the effect size (Cohen's w) measures how far the observed category proportions deviate from expected. A w of 0.1 is small, 0.3 is medium, and 0.5 is large.",React.createElement("br",null),React.createElement("br",null),"Degrees of freedom:",React.createElement("br",null),"\u2022 Goodness-of-fit: ",React.createElement("b",null,"df = categories \u2212 1"),React.createElement("br",null),"\u2022 Independence: ",React.createElement("b",null,"df = (rows \u2212 1) \xD7 (cols \u2212 1)")))))}export{App}; +//# sourceMappingURL=power-app-4LQAP5A5.js.map diff --git a/tools/_app/chunks/power-app-WK6W4FL3.js.map b/tools/_app/chunks/power-app-4LQAP5A5.js.map similarity index 100% rename from tools/_app/chunks/power-app-WK6W4FL3.js.map rename to tools/_app/chunks/power-app-4LQAP5A5.js.map diff --git a/tools/_app/index.js b/tools/_app/index.js index 5c5c6ed9..616ccf4d 100644 --- a/tools/_app/index.js +++ b/tools/_app/index.js @@ -1,3 +1,3 @@ -import"./chunks/chunk-II356IQT.js";import"./chunks/chunk-J6277AEU.js";import"./chunks/chunk-T4V5V5TK.js";import"./chunks/chunk-GD3FGCBE.js";import{ErrorBoundary,TOOL_ACCENT,tintIcon}from"./chunks/chunk-5F6S3FYW.js";var{useSyncExternalStore}=React;function getInitialRouteFromHash(){let hash=typeof window>"u"?"":window.location.hash;return!hash||hash==="#"||hash==="#/"?null:hash.replace(/^#\/?/,"").split(/[/?]/)[0]||null}function subscribeToHashChange(onChange){return window.addEventListener("hashchange",onChange),window.addEventListener("popstate",onChange),()=>{window.removeEventListener("hashchange",onChange),window.removeEventListener("popstate",onChange)}}function getRouteSnapshot(){return getInitialRouteFromHash()}function getRouteServerSnapshot(){return null}function useRoute(){return useSyncExternalStore(subscribeToHashChange,getRouteSnapshot,getRouteServerSnapshot)}function navigate(routeKey){if(routeKey==null||routeKey===""){window.history.pushState(null,"",window.location.pathname+window.location.search),window.dispatchEvent(new HashChangeEvent("hashchange"));return}window.location.hash=`#/${routeKey}`}var importWithRetry=(loader,{attempts=3,baseDelayMs=400}={})=>{let attempt=0,tryOnce=()=>loader().catch(err=>{if(attempt+=1,attempt>=attempts)throw err;return new Promise(resolve=>setTimeout(resolve,baseDelayMs*attempt)).then(()=>tryOnce())});return tryOnce()},lazyApp=loader=>React.lazy(()=>importWithRetry(loader).then(m=>({default:m.App}))),TOOL_REGISTRY=[{key:"boxplot",label:"Group Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-BIQIRH5G.js"))},{key:"scatter",label:"Scatter Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-NW63HSPZ.js"))},{key:"venn",label:"Venn Diagram",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-3ELPKOLT.js"))},{key:"upset",label:"UpSet Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-G5L2IE5E.js"))},{key:"lineplot",label:"Line Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-BDRRNBIR.js"))},{key:"aequorin",label:"RLU Timecourse",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-ZT4GXXOQ.js"))},{key:"heatmap",label:"Heatmap",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-7CWQ47AS.js"))},{key:"volcano",label:"Volcano Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-5CQNAWMD.js"))},{key:"power",label:"Power Analysis",iconSvg:'',Component:lazyApp(()=>import("./chunks/power-app-WK6W4FL3.js"))},{key:"molarity",label:"Calculator",iconSvg:'',Component:lazyApp(()=>import("./chunks/molarity-app-CV5FPHQ6.js"))}];function findToolEntry(key){return key==null?null:TOOL_REGISTRY.find(t=>t.key===key)||null}var THEME_STORAGE_KEY="dataviz-theme",_themeChannel=null;try{typeof BroadcastChannel=="function"&&(_themeChannel=new BroadcastChannel("dataviz-theme"))}catch{_themeChannel=null}function _applyThemeAttr(mode){if(typeof document>"u")return;let root=document.documentElement;mode==="dark"||mode==="light"?root.setAttribute("data-theme",mode):root.removeAttribute("data-theme")}function _readStoredTheme(){try{let v=localStorage.getItem(THEME_STORAGE_KEY);return v==="dark"||v==="light"?v:null}catch{return null}}function getTheme(){if(typeof document>"u"||typeof window>"u")return"light";let explicit=document.documentElement.getAttribute("data-theme");return explicit==="dark"||explicit==="light"?explicit:window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function setTheme(mode){try{mode==="dark"||mode==="light"?localStorage.setItem(THEME_STORAGE_KEY,mode):localStorage.removeItem(THEME_STORAGE_KEY)}catch{}_applyThemeAttr(mode);try{_themeChannel&&_themeChannel.postMessage({type:"theme",theme:mode})}catch{}try{window.dispatchEvent(new CustomEvent("dataviz-theme-change",{detail:{theme:mode}}))}catch{}}function toggleTheme(){setTheme(getTheme()==="dark"?"light":"dark")}typeof document<"u"&&_applyThemeAttr(_readStoredTheme());_themeChannel&&_themeChannel.addEventListener("message",e=>{let data=e&&e.data;if(!data||data.type!=="theme")return;let v=data.theme==="dark"||data.theme==="light"?data.theme:null;_applyThemeAttr(v);try{window.dispatchEvent(new CustomEvent("dataviz-theme-change",{detail:{theme:v}}))}catch{}});typeof window<"u"&&(window.addEventListener("storage",e=>{if(e.key!==THEME_STORAGE_KEY)return;let v=e.newValue==="dark"||e.newValue==="light"?e.newValue:null;_applyThemeAttr(v);try{window.dispatchEvent(new CustomEvent("dataviz-theme-change",{detail:{theme:v}}))}catch{}}),window.addEventListener("pageshow",()=>{_applyThemeAttr(_readStoredTheme())}));typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&_applyThemeAttr(_readStoredTheme())});function useThemeMode(){let[mode,setMode]=React.useState(()=>getTheme());return React.useEffect(()=>{let onChange=()=>setMode(getTheme());window.addEventListener("dataviz-theme-change",onChange),window.addEventListener("storage",onChange);let mq=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)");return mq&&mq.addEventListener?mq.addEventListener("change",onChange):mq&&mq.addListener&&mq.addListener(onChange),()=>{window.removeEventListener("dataviz-theme-change",onChange),window.removeEventListener("storage",onChange),mq&&mq.removeEventListener?mq.removeEventListener("change",onChange):mq&&mq.removeListener&&mq.removeListener(onChange)}},[]),mode}var HOME_SVG='',SUN_SVG='',MOON_SVG='',FEEDBACK_SVG='',FEEDBACK_EMAIL="plottrproject@gmail.com";function IconButton({title,svg,onClick,extraAttrs,color}){return React.createElement("button",{type:"button",className:"tb-icon-btn",title,"aria-label":title,onClick,...color?{style:{color}}:{},dangerouslySetInnerHTML:{__html:svg},...extraAttrs||{}})}function FeedbackButton({currentKey}){let onClick=()=>{let tool=currentKey||"landing",version=typeof window<"u"&&window.__APP_VERSION__||"v?",ua=typeof navigator<"u"&&navigator.userAgent||"unknown",body=["Thanks for sending feedback! This is a draft in your mail client \u2014","Pl\xF6ttr itself sends nothing. The lines below were filled in","client-side so we can reproduce; edit or strip any of them before","clicking Send if you'd rather not share that detail.","","What happened","-------------","(describe what you saw)","","What you expected","-----------------","(describe what you thought would happen, or what you'd like to see instead)","","---","- Tool: "+tool,"- Pl\xF6ttr: "+version,"- Browser: "+ua,"- Reported: "+new Date().toISOString()].join(` +import"./chunks/chunk-MQIRRYKA.js";import"./chunks/chunk-DT6WX6AK.js";import"./chunks/chunk-T4V5V5TK.js";import"./chunks/chunk-GD3FGCBE.js";import{ErrorBoundary,TOOL_ACCENT,tintIcon}from"./chunks/chunk-HFTDMS7M.js";var{useSyncExternalStore}=React;function getInitialRouteFromHash(){let hash=typeof window>"u"?"":window.location.hash;return!hash||hash==="#"||hash==="#/"?null:hash.replace(/^#\/?/,"").split(/[/?]/)[0]||null}function subscribeToHashChange(onChange){return window.addEventListener("hashchange",onChange),window.addEventListener("popstate",onChange),()=>{window.removeEventListener("hashchange",onChange),window.removeEventListener("popstate",onChange)}}function getRouteSnapshot(){return getInitialRouteFromHash()}function getRouteServerSnapshot(){return null}function useRoute(){return useSyncExternalStore(subscribeToHashChange,getRouteSnapshot,getRouteServerSnapshot)}function navigate(routeKey){if(routeKey==null||routeKey===""){window.history.pushState(null,"",window.location.pathname+window.location.search),window.dispatchEvent(new HashChangeEvent("hashchange"));return}window.location.hash=`#/${routeKey}`}var importWithRetry=(loader,{attempts=3,baseDelayMs=400}={})=>{let attempt=0,tryOnce=()=>loader().catch(err=>{if(attempt+=1,attempt>=attempts)throw err;return new Promise(resolve=>setTimeout(resolve,baseDelayMs*attempt)).then(()=>tryOnce())});return tryOnce()},lazyApp=loader=>React.lazy(()=>importWithRetry(loader).then(m=>({default:m.App}))),TOOL_REGISTRY=[{key:"boxplot",label:"Group Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-3EKSFA3F.js"))},{key:"scatter",label:"Scatter Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-RNTWQ7N2.js"))},{key:"venn",label:"Venn Diagram",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-BLY3XSWB.js"))},{key:"upset",label:"UpSet Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-P3YNIKGG.js"))},{key:"lineplot",label:"Line Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-TQWDLQEC.js"))},{key:"aequorin",label:"RLU Timecourse",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-VRVO3EYQ.js"))},{key:"heatmap",label:"Heatmap",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-IRS66EWH.js"))},{key:"volcano",label:"Volcano Plot",iconSvg:'',Component:lazyApp(()=>import("./chunks/app-PCLQZVY3.js"))},{key:"power",label:"Power Analysis",iconSvg:'',Component:lazyApp(()=>import("./chunks/power-app-4LQAP5A5.js"))},{key:"molarity",label:"Calculator",iconSvg:'',Component:lazyApp(()=>import("./chunks/molarity-app-MWMOFUHJ.js"))}];function findToolEntry(key){return key==null?null:TOOL_REGISTRY.find(t=>t.key===key)||null}var THEME_STORAGE_KEY="dataviz-theme",_themeChannel=null;try{typeof BroadcastChannel=="function"&&(_themeChannel=new BroadcastChannel("dataviz-theme"))}catch{_themeChannel=null}function _applyThemeAttr(mode){if(typeof document>"u")return;let root=document.documentElement;mode==="dark"||mode==="light"?root.setAttribute("data-theme",mode):root.removeAttribute("data-theme")}function _readStoredTheme(){try{let v=localStorage.getItem(THEME_STORAGE_KEY);return v==="dark"||v==="light"?v:null}catch{return null}}function getTheme(){if(typeof document>"u"||typeof window>"u")return"light";let explicit=document.documentElement.getAttribute("data-theme");return explicit==="dark"||explicit==="light"?explicit:window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches?"dark":"light"}function setTheme(mode){try{mode==="dark"||mode==="light"?localStorage.setItem(THEME_STORAGE_KEY,mode):localStorage.removeItem(THEME_STORAGE_KEY)}catch{}_applyThemeAttr(mode);try{_themeChannel&&_themeChannel.postMessage({type:"theme",theme:mode})}catch{}try{window.dispatchEvent(new CustomEvent("dataviz-theme-change",{detail:{theme:mode}}))}catch{}}function toggleTheme(){setTheme(getTheme()==="dark"?"light":"dark")}typeof document<"u"&&_applyThemeAttr(_readStoredTheme());_themeChannel&&_themeChannel.addEventListener("message",e=>{let data=e&&e.data;if(!data||data.type!=="theme")return;let v=data.theme==="dark"||data.theme==="light"?data.theme:null;_applyThemeAttr(v);try{window.dispatchEvent(new CustomEvent("dataviz-theme-change",{detail:{theme:v}}))}catch{}});typeof window<"u"&&(window.addEventListener("storage",e=>{if(e.key!==THEME_STORAGE_KEY)return;let v=e.newValue==="dark"||e.newValue==="light"?e.newValue:null;_applyThemeAttr(v);try{window.dispatchEvent(new CustomEvent("dataviz-theme-change",{detail:{theme:v}}))}catch{}}),window.addEventListener("pageshow",()=>{_applyThemeAttr(_readStoredTheme())}));typeof document<"u"&&document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&_applyThemeAttr(_readStoredTheme())});function useThemeMode(){let[mode,setMode]=React.useState(()=>getTheme());return React.useEffect(()=>{let onChange=()=>setMode(getTheme());window.addEventListener("dataviz-theme-change",onChange),window.addEventListener("storage",onChange);let mq=window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)");return mq&&mq.addEventListener?mq.addEventListener("change",onChange):mq&&mq.addListener&&mq.addListener(onChange),()=>{window.removeEventListener("dataviz-theme-change",onChange),window.removeEventListener("storage",onChange),mq&&mq.removeEventListener?mq.removeEventListener("change",onChange):mq&&mq.removeListener&&mq.removeListener(onChange)}},[]),mode}var HOME_SVG='',SUN_SVG='',MOON_SVG='',FEEDBACK_SVG='',FEEDBACK_EMAIL="plottrproject@gmail.com";function IconButton({title,svg,onClick,extraAttrs,color}){return React.createElement("button",{type:"button",className:"tb-icon-btn",title,"aria-label":title,onClick,...color?{style:{color}}:{},dangerouslySetInnerHTML:{__html:svg},...extraAttrs||{}})}function FeedbackButton({currentKey}){let onClick=()=>{let tool=currentKey||"landing",version=typeof window<"u"&&window.__APP_VERSION__||"v?",ua=typeof navigator<"u"&&navigator.userAgent||"unknown",body=["Thanks for sending feedback! This is a draft in your mail client \u2014","Pl\xF6ttr itself sends nothing. The lines below were filled in","client-side so we can reproduce; edit or strip any of them before","clicking Send if you'd rather not share that detail.","","What happened","-------------","(describe what you saw)","","What you expected","-----------------","(describe what you thought would happen, or what you'd like to see instead)","","---","- Tool: "+tool,"- Pl\xF6ttr: "+version,"- Browser: "+ua,"- Reported: "+new Date().toISOString()].join(` `),subject="[Pl\xF6ttr feedback] "+tool,url="mailto:"+encodeURIComponent(FEEDBACK_EMAIL)+"?subject="+encodeURIComponent(subject)+"&body="+encodeURIComponent(body);window.location.href=url};return React.createElement("button",{type:"button",className:"tb-icon-btn",title:"Send feedback (opens an email draft \u2014 nothing is sent automatically)","aria-label":"Send feedback",onClick,dangerouslySetInnerHTML:{__html:FEEDBACK_SVG},"data-feedback":"true"})}function ThemeButton(){let isDark=useThemeMode()==="dark",title=isDark?"Switch to light mode":"Switch to dark mode";return React.createElement("button",{type:"button",className:"tb-icon-btn",title,"aria-label":title,onClick:()=>toggleTheme(),dangerouslySetInnerHTML:{__html:isDark?SUN_SVG:MOON_SVG}})}function ToolTopbar({currentKey}){let others=TOOL_REGISTRY.filter(t=>t.key!==currentKey);return React.createElement("div",{className:"tool-topbar",style:{display:"flex",alignItems:"center",gap:6,padding:"8px 12px",borderBottom:"1px solid var(--border)",background:"var(--surface)"}},React.createElement(ThemeButton),React.createElement("div",{className:"tb-sep"}),IconButton({title:"Home",svg:HOME_SVG,onClick:()=>navigate(null),extraAttrs:{"data-back":"true"}}),React.createElement("div",{className:"tb-sep"}),...others.map(t=>React.createElement(IconButton,{key:t.key,title:t.label,svg:tintIcon(t.iconSvg,t.key),color:TOOL_ACCENT[t.key],onClick:()=>navigate(t.key),extraAttrs:{"data-tool":t.key}})),React.createElement("div",{key:"feedback-sep",className:"tb-sep",style:{marginLeft:"auto"}}),React.createElement(FeedbackButton,{key:"feedback",currentKey}))}var CHUNK_LOAD_STUCK_MS=6e3;function ChunkLoadingFallback({label}){let[stuck,setStuck]=React.useState(!1);return React.useEffect(()=>{let timer=window.setTimeout(()=>setStuck(!0),CHUNK_LOAD_STUCK_MS);return()=>window.clearTimeout(timer)},[]),stuck?React.createElement("div",{role:"alert",style:{minHeight:"60vh",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:16,color:"var(--text)",fontFamily:"monospace",fontSize:13,padding:24,textAlign:"center"}},React.createElement("div",{style:{color:"var(--text-muted)"}},"Loading ",label," is taking longer than expected."),React.createElement("button",{type:"button",className:"dv-btn dv-btn-primary",onClick:()=>window.location.reload()},"Reload page"),React.createElement("div",{style:{color:"var(--text-faint)",fontSize:11}},"Your data, settings, and theme stay in browser storage.")):React.createElement("div",{role:"status","aria-live":"polite",style:{minHeight:"60vh",display:"flex",flexDirection:"column",alignItems:"center",justifyContent:"center",gap:12,color:"var(--text-muted)",fontFamily:"monospace",fontSize:13}},React.createElement("div",{className:"dv-chunk-spinner","aria-hidden":"true"}),React.createElement("div",null,"Loading ",label,"\u2026"))}function LandingPlaceholder(){return React.createElement("div",{style:{padding:32,margin:"32px auto",maxWidth:720,background:"var(--surface)",border:"1px solid var(--border)",borderRadius:12,textAlign:"center"}},React.createElement("h2",{style:{margin:"0 0 12px",color:"var(--text)"}},"Pl\xF6ttr"),React.createElement("p",{style:{color:"var(--text-muted)",margin:"0 0 16px"}},"Pick a tool from the registry."),React.createElement("ul",{style:{listStyle:"none",padding:0,display:"flex",flexWrap:"wrap",gap:8,justifyContent:"center"}},...TOOL_REGISTRY.map(t=>React.createElement("li",{key:t.key},React.createElement("button",{type:"button",className:"dv-btn dv-btn-secondary",onClick:()=>navigate(t.key)},t.label)))))}function App(){let route=useRoute(),entry=findToolEntry(route),[visitedKeys,setVisitedKeys]=React.useState(()=>entry?new Set([entry.key]):new Set);React.useEffect(()=>{entry&&setVisitedKeys(prev=>{if(prev.has(entry.key))return prev;let next=new Set(prev);return next.add(entry.key),next})},[entry?.key]);let mountedTools=TOOL_REGISTRY.filter(t=>visitedKeys.has(t.key)).map(t=>React.createElement("div",{key:t.key,style:{display:entry&&entry.key===t.key?"block":"none"}},React.createElement(ErrorBoundary,{toolName:t.label},React.createElement(React.Suspense,{fallback:React.createElement(ChunkLoadingFallback,{label:t.label})},React.createElement(t.Component)))));return entry?React.createElement("div",null,React.createElement(ToolTopbar,{currentKey:entry.key}),...mountedTools):React.createElement("div",null,React.createElement(LandingPlaceholder),...mountedTools)}window.__plottrSpaNavigate=navigate;var rootEl=document.getElementById("root");rootEl&&ReactDOM.createRoot(rootEl).render(React.createElement(App,null)); //# sourceMappingURL=index.js.map diff --git a/tools/_shell/UploadPanel.tsx b/tools/_shell/UploadPanel.tsx index d6817e3d..67fe1ea7 100644 --- a/tools/_shell/UploadPanel.tsx +++ b/tools/_shell/UploadPanel.tsx @@ -336,7 +336,21 @@ function AutoDetectUploadPanel(props: UploadPanelProps) { }} >
- + {/* Inline SVG info mark (currentColor → inherits --info-text and + themes in both modes). Replaces a bare U+2139 "ℹ", which has a + text-default presentation and rendered as a plain italic "i" in + the mono chrome font. */} + Plöttr auto-detects the column separator (comma, tab, semicolon, …) from the data.