diagcalc 3.2.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -6
- package/bin/diagcalc.js +147 -41
- package/docs/PROVENANCE.md +17 -0
- package/index.html +408 -92
- package/lib/README.md +98 -0
- package/lib/diagcalc-case.js +148 -0
- package/lib/diagcalc-core.js +557 -89
- package/lib/diagcalc-datasets.js +140 -1
- package/lib/diagcalc-geometry.js +27 -0
- package/lib/diagcalc-i18n.js +255 -0
- package/lib/diagcalc-meta.js +9 -0
- package/lib/diagcalc-presentation.js +278 -0
- package/lib/diagcalc-storage.js +45 -0
- package/lib/diagcalc-types.d.ts +59 -0
- package/package.json +18 -4
- package/script.js +1239 -275
- package/styles.css +2186 -325
- package/tui/index.js +83 -273
- package/tui/input.js +24 -0
- package/tui/presentation.js +168 -0
- package/tui/report.js +130 -0
- package/web/charts.js +339 -0
- package/web/results.js +234 -0
package/index.html
CHANGED
|
@@ -6,88 +6,78 @@
|
|
|
6
6
|
<title>DIAG-CALC</title>
|
|
7
7
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
8
8
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
9
|
-
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" />
|
|
9
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Atkinson+Hyperlegible+Next:ital,wght@0,200..800;1,200..800&family=JetBrains+Mono:wght@400;500;600;700;800&display=swap" />
|
|
10
10
|
<link rel="stylesheet" href="styles.css" />
|
|
11
|
+
<script defer src="https://analytics.tiagojct.eu/script.js" data-website-id="2c7df365-61e6-42e6-8298-a20ada3e296a"></script>
|
|
11
12
|
</head>
|
|
12
13
|
<body>
|
|
13
|
-
<header class="
|
|
14
|
-
<div class="
|
|
15
|
-
<div class="
|
|
16
|
-
<
|
|
14
|
+
<header class="app-bar" role="banner">
|
|
15
|
+
<div class="app-bar-inner">
|
|
16
|
+
<div class="app-bar-brand">
|
|
17
|
+
<span class="brand-mark">DIAGCALC</span>
|
|
18
|
+
<span class="app-bar-version" aria-label="version">v5.0.0</span>
|
|
19
|
+
<span class="app-bar-divider" aria-hidden="true">·</span>
|
|
20
|
+
<span class="app-bar-case" id="appCaseStrip" data-i18n="app.case.empty">No case yet</span>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="app-bar-controls">
|
|
23
|
+
<label class="lang-picker" for="languageSelect">
|
|
24
|
+
<span class="visually-hidden" data-i18n="lang.label">Language</span>
|
|
25
|
+
<select id="languageSelect" aria-label="Language">
|
|
26
|
+
<option value="en">English</option>
|
|
27
|
+
<option value="pt-PT">Português</option>
|
|
28
|
+
</select>
|
|
29
|
+
</label>
|
|
30
|
+
<button id="themeToggle" class="theme-toggle" type="button" data-i18n-attr="aria-label=theme.toggle.label">
|
|
17
31
|
<span class="theme-icon">◐</span>
|
|
18
32
|
</button>
|
|
19
33
|
</div>
|
|
20
|
-
<p class="tag">Educational tool</p>
|
|
21
|
-
<h1>DIAGCALC</h1>
|
|
22
|
-
<p class="lead">
|
|
23
|
-
Interactive calculator for exploring diagnostic test performance measures
|
|
24
|
-
and applying Bayesian reasoning in clinical practice.
|
|
25
|
-
</p>
|
|
26
|
-
<div class="intro-highlights" role="list">
|
|
27
|
-
<article class="highlight" role="listitem">
|
|
28
|
-
<h2>Key measures</h2>
|
|
29
|
-
<p>Sensitivity, specificity, PPV, NPV, and likelihood ratios with 95% CI.</p>
|
|
30
|
-
</article>
|
|
31
|
-
<article class="highlight" role="listitem">
|
|
32
|
-
<h2>Post-test probability</h2>
|
|
33
|
-
<p>Convert pre-test probability into positive and negative post-test probability.</p>
|
|
34
|
-
</article>
|
|
35
|
-
<article class="highlight" role="listitem">
|
|
36
|
-
<h2>Guided interpretation</h2>
|
|
37
|
-
<p>Results explained to support self-directed learning.</p>
|
|
38
|
-
</article>
|
|
39
|
-
</div>
|
|
40
34
|
</div>
|
|
41
35
|
</header>
|
|
42
36
|
<main class="container" id="main" role="main">
|
|
43
37
|
<div class="workspace">
|
|
44
|
-
<section aria-labelledby="
|
|
38
|
+
<section aria-labelledby="form-title" class="panel panel-form">
|
|
45
39
|
<div class="panel-heading">
|
|
46
|
-
<p class="panel-kicker">
|
|
47
|
-
<h2 id="
|
|
48
|
-
</div>
|
|
49
|
-
<div class="form-row">
|
|
50
|
-
<label class="full" for="datasetSelect">Scenario</label>
|
|
51
|
-
<select id="datasetSelect" class="full" aria-describedby="dataset-help">
|
|
52
|
-
<option value="">— Select —</option>
|
|
53
|
-
<optgroup label="Generic scenarios">
|
|
54
|
-
<option value="screening">Screening programme (low prevalence)</option>
|
|
55
|
-
<option value="caseControl">Case-control study</option>
|
|
56
|
-
<option value="clinic">Specialist clinic</option>
|
|
57
|
-
</optgroup>
|
|
58
|
-
<optgroup label="Medical literature cases">
|
|
59
|
-
<option value="ddimer">D-dimer for pulmonary embolism</option>
|
|
60
|
-
<option value="troponin">High-sensitivity troponin for AMI</option>
|
|
61
|
-
<option value="mammography">Screening mammography</option>
|
|
62
|
-
<option value="covid_antigen">Rapid COVID-19 antigen test</option>
|
|
63
|
-
<option value="hiv_elisa">HIV ELISA</option>
|
|
64
|
-
<option value="strep_throat">Rapid Streptococcus test</option>
|
|
65
|
-
<option value="xray_pneumonia">Chest X-ray for pneumonia</option>
|
|
66
|
-
</optgroup>
|
|
67
|
-
</select>
|
|
68
|
-
<p id="dataset-help" class="help-text">
|
|
69
|
-
Values are filled in automatically and can be adjusted afterwards.
|
|
70
|
-
</p>
|
|
71
|
-
<div id="dataset-reference" class="dataset-reference" style="display: none;"></div>
|
|
72
|
-
</div>
|
|
73
|
-
</section>
|
|
74
|
-
|
|
75
|
-
<section aria-labelledby="form-title" class="panel panel-inputs">
|
|
76
|
-
<div class="panel-heading">
|
|
77
|
-
<p class="panel-kicker">Editor</p>
|
|
78
|
-
<h2 id="form-title">Build the confusion matrix</h2>
|
|
79
|
-
<p>
|
|
80
|
-
Use non-negative integers and a pre-test probability under 100%.
|
|
81
|
-
</p>
|
|
40
|
+
<p class="panel-kicker" data-i18n="editor.kicker">Editor</p>
|
|
41
|
+
<h2 id="form-title" data-i18n="editor.h">Build the case</h2>
|
|
82
42
|
</div>
|
|
83
43
|
<form id="inputForm" novalidate>
|
|
44
|
+
<div class="form-row">
|
|
45
|
+
<label class="full" for="datasetSelect" data-i18n="form.scenario">Scenario</label>
|
|
46
|
+
<select id="datasetSelect" class="full" aria-describedby="dataset-help">
|
|
47
|
+
<option value="" data-i18n="form.scenario.empty">— Select —</option>
|
|
48
|
+
<optgroup label="Generic scenarios" data-i18n-attr="label=form.scenario.group.generic">
|
|
49
|
+
<option value="screening">Screening programme (low prevalence)</option>
|
|
50
|
+
<option value="caseControl">Case-control study</option>
|
|
51
|
+
<option value="clinic">Specialist clinic</option>
|
|
52
|
+
</optgroup>
|
|
53
|
+
<optgroup label="Illustrative medical scenarios" data-i18n-attr="label=form.scenario.group.literature">
|
|
54
|
+
<option value="ddimer">D-dimer for pulmonary embolism</option>
|
|
55
|
+
<option value="troponin">High-sensitivity troponin for AMI</option>
|
|
56
|
+
<option value="mammography">Screening mammography</option>
|
|
57
|
+
<option value="covid_antigen">Rapid COVID-19 antigen test</option>
|
|
58
|
+
<option value="hiv_elisa">HIV ELISA</option>
|
|
59
|
+
<option value="strep_throat">Rapid Streptococcus test</option>
|
|
60
|
+
<option value="xray_pneumonia">Chest X-ray for pneumonia</option>
|
|
61
|
+
<option value="wells_dvt">Wells score (≥2) for DVT</option>
|
|
62
|
+
<option value="heart_acs">HEART score (≥4) for MACE in chest pain</option>
|
|
63
|
+
<option value="curb65">CURB-65 (≥2) for severe community-acquired pneumonia</option>
|
|
64
|
+
<option value="qsofa">qSOFA (≥2) for sepsis screening</option>
|
|
65
|
+
<option value="apgar_5min">APGAR < 7 at 5 min for severe neonatal outcome</option>
|
|
66
|
+
<option value="fast_trauma">FAST ultrasound for blunt abdominal trauma</option>
|
|
67
|
+
</optgroup>
|
|
68
|
+
</select>
|
|
69
|
+
<p id="dataset-help" class="help-text" data-i18n="library.help">
|
|
70
|
+
Pick a preset to load values, or fill in the matrix manually below.
|
|
71
|
+
</p>
|
|
72
|
+
<div id="dataset-reference" class="dataset-reference" style="display: none;"></div>
|
|
73
|
+
</div>
|
|
84
74
|
<fieldset class="confusion-matrix">
|
|
85
|
-
<legend>Confusion matrix</legend>
|
|
75
|
+
<legend data-i18n="form.matrix.legend">Confusion matrix</legend>
|
|
86
76
|
<div class="grid-container" role="group" aria-label="Diagnostic test results">
|
|
87
77
|
<span class="empty" aria-hidden="true"></span>
|
|
88
|
-
<span class="grid-header grid-header-pos">Disease +</span>
|
|
89
|
-
<span class="grid-header grid-header-neg">Disease -</span>
|
|
90
|
-
<label class="grid-label grid-label-pos" for="tp">Test +</label>
|
|
78
|
+
<span class="grid-header grid-header-pos" data-i18n="form.matrix.dpos">Disease +</span>
|
|
79
|
+
<span class="grid-header grid-header-neg" data-i18n="form.matrix.dneg">Disease -</span>
|
|
80
|
+
<label class="grid-label grid-label-pos" for="tp" data-i18n="form.matrix.tpos">Test +</label>
|
|
91
81
|
<div class="grid-item">
|
|
92
82
|
<input type="number" id="tp" inputmode="numeric" min="0" step="1"
|
|
93
83
|
placeholder="True positives"
|
|
@@ -100,7 +90,7 @@
|
|
|
100
90
|
aria-label="False positives (FP): disease absent, test positive"
|
|
101
91
|
required />
|
|
102
92
|
</div>
|
|
103
|
-
<label class="grid-label grid-label-neg" for="fn">Test -</label>
|
|
93
|
+
<label class="grid-label grid-label-neg" for="fn" data-i18n="form.matrix.tneg">Test -</label>
|
|
104
94
|
<div class="grid-item">
|
|
105
95
|
<input type="number" id="fn" inputmode="numeric" min="0" step="1"
|
|
106
96
|
placeholder="False negatives"
|
|
@@ -116,25 +106,36 @@
|
|
|
116
106
|
</div>
|
|
117
107
|
</fieldset>
|
|
118
108
|
<div class="form-row">
|
|
119
|
-
<label for="preTestProb">Pre-test probability (%)</label>
|
|
109
|
+
<label for="preTestProb" data-i18n="form.pre.label">Pre-test probability (%)</label>
|
|
120
110
|
<input
|
|
121
111
|
class="preTestProb"
|
|
122
|
-
type="
|
|
112
|
+
type="text"
|
|
123
113
|
id="preTestProb"
|
|
124
114
|
inputmode="decimal"
|
|
125
|
-
min="0"
|
|
126
|
-
max="100"
|
|
127
|
-
step="0.1"
|
|
128
115
|
placeholder="e.g. 12.5"
|
|
129
116
|
required
|
|
130
117
|
/>
|
|
131
|
-
<p class="help-text">
|
|
118
|
+
<p class="help-text" data-i18n="form.pre.help">
|
|
132
119
|
Suggestion: estimate the disease prevalence in the study population or the clinical probability before the test.
|
|
133
120
|
</p>
|
|
134
121
|
</div>
|
|
122
|
+
<details class="advanced-settings">
|
|
123
|
+
<summary data-i18n="form.advanced.summary">Advanced settings</summary>
|
|
124
|
+
<div class="form-row">
|
|
125
|
+
<label for="continuityModeSelect" data-i18n="form.advanced.continuity">Continuity correction for LR / DOR CIs</label>
|
|
126
|
+
<select id="continuityModeSelect" class="full">
|
|
127
|
+
<option value="auto" selected data-i18n="form.advanced.continuity.auto">Auto — apply +0.5 only when a 2×2 cell is zero</option>
|
|
128
|
+
<option value="always" data-i18n="form.advanced.continuity.always">Always — apply +0.5 to every cell</option>
|
|
129
|
+
<option value="never" data-i18n="form.advanced.continuity.never">Never — leave undefined intervals unavailable</option>
|
|
130
|
+
</select>
|
|
131
|
+
<p class="help-text" data-i18n="form.advanced.continuity.help">
|
|
132
|
+
When any cell of the confusion matrix is zero the log-normal CI for LR+ / LR− / DOR is undefined.
|
|
133
|
+
</p>
|
|
134
|
+
</div>
|
|
135
|
+
</details>
|
|
135
136
|
<div class="form-actions" role="group">
|
|
136
|
-
<button type="submit">Calculate results</button>
|
|
137
|
-
<button type="reset" id="resetButton" class="secondary">Clear fields</button>
|
|
137
|
+
<button type="submit" data-i18n="form.calculate">Calculate results</button>
|
|
138
|
+
<button type="reset" id="resetButton" class="secondary" data-i18n="form.clear">Clear fields</button>
|
|
138
139
|
</div>
|
|
139
140
|
<p id="feedback" class="feedback" role="alert" aria-live="polite"></p>
|
|
140
141
|
</form>
|
|
@@ -143,59 +144,340 @@
|
|
|
143
144
|
<section aria-labelledby="results-title" class="panel panel-results results-section">
|
|
144
145
|
<div class="panel-heading panel-heading-inline">
|
|
145
146
|
<div>
|
|
146
|
-
<p class="panel-kicker">Output</p>
|
|
147
|
-
<h2 id="results-title">Results</h2>
|
|
148
|
-
<p class="help-text">
|
|
149
|
-
Values are shown with 95% confidence intervals
|
|
147
|
+
<p class="panel-kicker" data-i18n="results.kicker">Output</p>
|
|
148
|
+
<h2 id="results-title" data-i18n="results.h">Results</h2>
|
|
149
|
+
<p class="help-text" data-i18n="results.help">
|
|
150
|
+
Values are shown with 95% confidence intervals where applicable.
|
|
150
151
|
</p>
|
|
151
152
|
</div>
|
|
152
153
|
<div class="results-actions" role="group">
|
|
153
|
-
<button type="button" id="printButton" class="secondary">Print report</button>
|
|
154
|
+
<button type="button" id="printButton" class="secondary" data-i18n="results.print">Print report</button>
|
|
154
155
|
</div>
|
|
155
156
|
</div>
|
|
156
|
-
<div id="
|
|
157
|
+
<div id="biasWarnings" class="bias-warnings" role="status" aria-live="polite"></div>
|
|
158
|
+
<div id="resultsHeadline" class="results-headline" aria-live="polite" tabindex="-1"></div>
|
|
159
|
+
<div id="bayesianUpdate" class="bayesian-update" aria-live="polite"></div>
|
|
160
|
+
<div id="resultsSecondary" class="results-secondary" aria-live="polite"></div>
|
|
157
161
|
<div id="faganNomogram" class="fagan-nomogram" style="display: none;">
|
|
158
|
-
<h3>Fagan Nomogram</h3>
|
|
159
|
-
<p class="help-text">
|
|
160
|
-
<canvas id="faganCanvas" width="600" height="400" aria-label="Fagan nomogram"></canvas>
|
|
162
|
+
<h3 data-i18n="fagan.h">Fagan Nomogram</h3>
|
|
163
|
+
<p class="help-text" data-i18n="fagan.help">Calibrated logarithmic axes: pre-test odds × LR = post-test odds. Off-scale or undefined values are shown in the summary.</p>
|
|
164
|
+
<canvas id="faganCanvas" width="600" height="400" role="img" aria-label="Fagan nomogram" aria-describedby="faganSummary"></canvas>
|
|
165
|
+
<dl id="faganSummary" class="fagan-summary">
|
|
166
|
+
<div class="fagan-summary-item">
|
|
167
|
+
<dt data-i18n="fagan.pre">Pre-test</dt>
|
|
168
|
+
<dd id="faganPreValue">—</dd>
|
|
169
|
+
</div>
|
|
170
|
+
<div class="fagan-summary-item">
|
|
171
|
+
<dt data-i18n="fagan.lrpos">LR+</dt>
|
|
172
|
+
<dd id="faganLrPosValue">—</dd>
|
|
173
|
+
</div>
|
|
174
|
+
<div class="fagan-summary-item">
|
|
175
|
+
<dt data-i18n="fagan.lrneg">LR−</dt>
|
|
176
|
+
<dd id="faganLrNegValue">—</dd>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="fagan-summary-item">
|
|
179
|
+
<dt data-i18n="fagan.postpos">Post-test (+)</dt>
|
|
180
|
+
<dd id="faganPostPosValue">—</dd>
|
|
181
|
+
</div>
|
|
182
|
+
<div class="fagan-summary-item">
|
|
183
|
+
<dt data-i18n="fagan.postneg">Post-test (−)</dt>
|
|
184
|
+
<dd id="faganPostNegValue">—</dd>
|
|
185
|
+
</div>
|
|
186
|
+
</dl>
|
|
161
187
|
</div>
|
|
162
|
-
<
|
|
188
|
+
<details id="sequentialTest" class="sequential-test" style="display: none;">
|
|
189
|
+
<summary><span class="summary-tag summary-tag--tool" data-i18n="panel.tag.tool">Tool</span><strong>Chain a second test</strong> — feed the post-test probability of this test into a follow-up</summary>
|
|
190
|
+
<p class="help-text">
|
|
191
|
+
Real diagnostic workflows often chain tests (e.g. D-dimer → CT-PA, rapid antigen → PCR, screen → confirm). Choose which result of the first test you are following up on, then enter the second test's confusion matrix. The second test's pre-test probability is set automatically.
|
|
192
|
+
</p>
|
|
193
|
+
<div class="sequential-controls">
|
|
194
|
+
<label for="sequentialFrom">
|
|
195
|
+
<span>Follow up on test 1 result</span>
|
|
196
|
+
<select id="sequentialFrom">
|
|
197
|
+
<option value="positive">Positive (use post-test (+) as pre-test for test 2)</option>
|
|
198
|
+
<option value="negative">Negative (use post-test (−) as pre-test for test 2)</option>
|
|
199
|
+
</select>
|
|
200
|
+
</label>
|
|
201
|
+
<p class="sequential-pretest" id="sequentialPretest"></p>
|
|
202
|
+
</div>
|
|
203
|
+
<fieldset class="confusion-matrix sequential-matrix">
|
|
204
|
+
<legend>Test 2 — confusion matrix</legend>
|
|
205
|
+
<div class="grid-container" role="group" aria-label="Diagnostic test 2 results">
|
|
206
|
+
<span class="empty" aria-hidden="true"></span>
|
|
207
|
+
<span class="grid-header">Disease +</span>
|
|
208
|
+
<span class="grid-header">Disease −</span>
|
|
209
|
+
<label class="grid-label" for="tp2">Test +</label>
|
|
210
|
+
<div class="grid-item">
|
|
211
|
+
<input type="number" id="tp2" inputmode="numeric" min="0" step="1" placeholder="True positives" aria-label="Test 2 true positives" />
|
|
212
|
+
</div>
|
|
213
|
+
<div class="grid-item">
|
|
214
|
+
<input type="number" id="fp2" inputmode="numeric" min="0" step="1" placeholder="False positives" aria-label="Test 2 false positives" />
|
|
215
|
+
</div>
|
|
216
|
+
<label class="grid-label" for="fn2">Test −</label>
|
|
217
|
+
<div class="grid-item">
|
|
218
|
+
<input type="number" id="fn2" inputmode="numeric" min="0" step="1" placeholder="False negatives" aria-label="Test 2 false negatives" />
|
|
219
|
+
</div>
|
|
220
|
+
<div class="grid-item">
|
|
221
|
+
<input type="number" id="tn2" inputmode="numeric" min="0" step="1" placeholder="True negatives" aria-label="Test 2 true negatives" />
|
|
222
|
+
</div>
|
|
223
|
+
</div>
|
|
224
|
+
</fieldset>
|
|
225
|
+
<div class="form-actions">
|
|
226
|
+
<button type="button" id="sequentialCalc">Calculate test 2 result</button>
|
|
227
|
+
<button type="button" id="sequentialClear" class="secondary">Clear test 2</button>
|
|
228
|
+
</div>
|
|
229
|
+
<p id="sequentialFeedback" class="feedback" role="alert" aria-live="polite"></p>
|
|
230
|
+
<div id="sequentialResults" class="results-grid sequential-results" aria-live="polite"></div>
|
|
231
|
+
</details>
|
|
232
|
+
<details id="thresholdPanel" class="threshold-panel" style="display: none;">
|
|
233
|
+
<summary><span class="summary-tag summary-tag--tool" data-i18n="panel.tag.tool">Tool</span><strong>Decision thresholds</strong> — when to test, when to treat without testing</summary>
|
|
234
|
+
<p class="help-text">
|
|
235
|
+
Pauker–Kassirer (1980) framework. Set the treatment threshold (the post-test probability at which you would act). Below the <em>testing</em> threshold even a positive result keeps you below the action level — testing changes nothing. Above the <em>test-treatment</em> threshold even a negative result keeps you above the action level — just treat. The "test is useful" zone is the interval between them.
|
|
236
|
+
</p>
|
|
237
|
+
<div class="threshold-input">
|
|
238
|
+
<label for="treatmentThresholdInput">
|
|
239
|
+
<span>Treatment threshold</span>
|
|
240
|
+
<output id="treatmentThresholdValue" for="treatmentThresholdInput">30%</output>
|
|
241
|
+
</label>
|
|
242
|
+
<input type="range" id="treatmentThresholdInput" min="1" max="99" step="1" value="30" />
|
|
243
|
+
</div>
|
|
244
|
+
<div id="thresholdGauge" class="threshold-gauge" aria-hidden="true">
|
|
245
|
+
<div class="threshold-zone threshold-zone-no"></div>
|
|
246
|
+
<div class="threshold-zone threshold-zone-test"></div>
|
|
247
|
+
<div class="threshold-zone threshold-zone-treat"></div>
|
|
248
|
+
<div class="threshold-marker threshold-marker-pt" id="thresholdMarkerPt"></div>
|
|
249
|
+
<div class="threshold-marker threshold-marker-pre" id="thresholdMarkerPre"></div>
|
|
250
|
+
</div>
|
|
251
|
+
<div class="threshold-legend">
|
|
252
|
+
<span><span class="threshold-swatch threshold-zone-no"></span>Don't test</span>
|
|
253
|
+
<span><span class="threshold-swatch threshold-zone-test"></span>Test useful</span>
|
|
254
|
+
<span><span class="threshold-swatch threshold-zone-treat"></span>Treat without testing</span>
|
|
255
|
+
<span><span class="threshold-swatch threshold-swatch-pt"></span>Pt (treatment threshold)</span>
|
|
256
|
+
<span><span class="threshold-swatch threshold-swatch-pre"></span>Current pre-test</span>
|
|
257
|
+
</div>
|
|
258
|
+
<div id="thresholdReadout" class="threshold-readout"></div>
|
|
259
|
+
</details>
|
|
260
|
+
<details id="rocPanel" class="roc-panel" style="display: none;">
|
|
261
|
+
<summary><span class="summary-tag summary-tag--tool" data-i18n="panel.tag.tool">Tool</span><strong>Build an ROC curve</strong> — combine multiple cutoffs of a continuous test</summary>
|
|
262
|
+
<p class="help-text">
|
|
263
|
+
For a continuous test (e.g. troponin, D-dimer, score), each candidate cutoff defines its own 2×2 table. Add one row per cutoff and the plot draws sensitivity vs (1 − specificity), the trapezoidal AUC, and highlights the cutoff with the largest Youden's J (sens + spec − 1).
|
|
264
|
+
</p>
|
|
265
|
+
<div class="roc-table-wrapper">
|
|
266
|
+
<label for="rocDirection">Positive result direction</label>
|
|
267
|
+
<select id="rocDirection"><option value="higher">Higher scores are positive</option><option value="lower">Lower scores are positive</option></select>
|
|
268
|
+
<table class="roc-table">
|
|
269
|
+
<thead>
|
|
270
|
+
<tr>
|
|
271
|
+
<th scope="col">Cutoff</th>
|
|
272
|
+
<th scope="col">TP</th>
|
|
273
|
+
<th scope="col">FP</th>
|
|
274
|
+
<th scope="col">FN</th>
|
|
275
|
+
<th scope="col">TN</th>
|
|
276
|
+
<th scope="col" aria-label="Remove row"></th>
|
|
277
|
+
</tr>
|
|
278
|
+
</thead>
|
|
279
|
+
<tbody id="rocRows"></tbody>
|
|
280
|
+
</table>
|
|
281
|
+
</div>
|
|
282
|
+
<div class="form-actions">
|
|
283
|
+
<button type="button" id="rocAddRow">+ Add cutoff row</button>
|
|
284
|
+
<button type="button" id="rocFillSynthetic" class="secondary" title="Fit an illustrative equal-variance binormal model and generate seven simulated cutoffs.">+ Fill synthetic cutoffs</button>
|
|
285
|
+
<button type="button" id="rocReset" class="secondary">Reset rows</button>
|
|
286
|
+
</div>
|
|
287
|
+
<canvas id="rocCanvas" width="480" height="480" role="img" aria-label="Receiver operating characteristic curve" aria-describedby="rocReadout"></canvas>
|
|
288
|
+
<div id="rocReadout" class="roc-readout"></div>
|
|
289
|
+
</details>
|
|
290
|
+
<details id="historyPanel" class="history-panel" style="display: none;">
|
|
291
|
+
<summary><span class="summary-tag summary-tag--tool" data-i18n="panel.tag.tool">Tool</span><strong>History</strong> — last 10 calculations on this device</summary>
|
|
292
|
+
<p class="help-text">
|
|
293
|
+
Recent Calculate runs are saved in your browser. Click <em>Reload</em> to drop a case back into the form, tick two or more boxes to compare them side by side, or <em>Clear all</em> to wipe local storage. Nothing leaves this device.
|
|
294
|
+
</p>
|
|
295
|
+
<div id="historyCompare" class="history-compare" aria-live="polite"></div>
|
|
296
|
+
<ul id="historyList" class="history-list" aria-label="Saved calculations"></ul>
|
|
297
|
+
<div class="form-actions">
|
|
298
|
+
<button type="button" id="historyClearAll" class="secondary">Clear all history</button>
|
|
299
|
+
</div>
|
|
300
|
+
</details>
|
|
301
|
+
<details id="prevalenceExplorer" class="prevalence-explorer" style="display: none;">
|
|
302
|
+
<summary><span class="summary-tag summary-tag--tool" data-i18n="panel.tag.tool">Tool</span><strong>Explore prevalence sensitivity</strong> — see how PPV, NPV and post-test probabilities shift when prevalence changes</summary>
|
|
303
|
+
<p class="help-text">
|
|
304
|
+
Sensitivity and specificity are intrinsic to the test and stay fixed. Drag the slider to see how the predictive values and post-test probabilities respond if the disease were more or less common in the population you are testing.
|
|
305
|
+
</p>
|
|
306
|
+
<div class="prevalence-controls">
|
|
307
|
+
<label for="prevalenceSlider">
|
|
308
|
+
<span>Prevalence</span>
|
|
309
|
+
<output id="prevalenceValue" for="prevalenceSlider">10.0%</output>
|
|
310
|
+
</label>
|
|
311
|
+
<input type="range" id="prevalenceSlider" min="0.1" max="100" step="0.1" value="10" aria-describedby="prevalenceValue" />
|
|
312
|
+
</div>
|
|
313
|
+
<div id="prevalenceReadout" class="prevalence-readout"></div>
|
|
314
|
+
<canvas id="prevalenceCanvas" width="600" height="320" aria-label="PPV and NPV as a function of prevalence"></canvas>
|
|
315
|
+
</details>
|
|
316
|
+
<details id="thresholdDemo" class="threshold-demo" style="display: none;">
|
|
317
|
+
<summary><span class="summary-tag summary-tag--tool" data-i18n="panel.tag.tool">Tool</span><strong data-i18n="thresholdDemo.summary">Threshold explorer</strong> — <span data-i18n="thresholdDemo.summary.sub">drag the slicer to see how TP, FP, FN, TN and all metrics shift in real time</span></summary>
|
|
318
|
+
<p class="help-text" data-i18n="thresholdDemo.help">
|
|
319
|
+
A continuous test produces scores for 200 individuals: 80 diseased (higher scores) and 120 healthy (lower scores), with overlap. Move the threshold slider to see how the confusion matrix and all performance metrics change in real time. There is no perfect threshold — only trade-offs.
|
|
320
|
+
</p>
|
|
321
|
+
<canvas id="thresholdDemoCanvas" width="600" height="200" role="img" aria-label="Score distribution with threshold line"></canvas>
|
|
322
|
+
<div class="threshold-demo-controls">
|
|
323
|
+
<label for="thresholdDemoSlider">
|
|
324
|
+
<span data-i18n="thresholdDemo.threshold">Threshold</span>
|
|
325
|
+
<output id="thresholdDemoValue" for="thresholdDemoSlider">0.50</output>
|
|
326
|
+
</label>
|
|
327
|
+
<input type="range" id="thresholdDemoSlider" min="0" max="100" step="1" value="50" aria-describedby="thresholdDemoValue" />
|
|
328
|
+
</div>
|
|
329
|
+
<div id="thresholdDemoStats" class="threshold-demo-stats" aria-live="polite">
|
|
330
|
+
<div class="threshold-demo-stat">
|
|
331
|
+
<div class="threshold-demo-stat-label" data-i18n="thresholdDemo.sens">Sensitivity</div>
|
|
332
|
+
<div class="threshold-demo-stat-value" id="tdSens">—</div>
|
|
333
|
+
<div class="threshold-demo-stat-sub">TP / (TP + FN)</div>
|
|
334
|
+
</div>
|
|
335
|
+
<div class="threshold-demo-stat">
|
|
336
|
+
<div class="threshold-demo-stat-label" data-i18n="thresholdDemo.spec">Specificity</div>
|
|
337
|
+
<div class="threshold-demo-stat-value" id="tdSpec">—</div>
|
|
338
|
+
<div class="threshold-demo-stat-sub">TN / (TN + FP)</div>
|
|
339
|
+
</div>
|
|
340
|
+
<div class="threshold-demo-stat">
|
|
341
|
+
<div class="threshold-demo-stat-label" data-i18n="thresholdDemo.ppv">PPV</div>
|
|
342
|
+
<div class="threshold-demo-stat-value" id="tdPpv">—</div>
|
|
343
|
+
<div class="threshold-demo-stat-sub">TP / (TP + FP)</div>
|
|
344
|
+
</div>
|
|
345
|
+
<div class="threshold-demo-stat">
|
|
346
|
+
<div class="threshold-demo-stat-label" data-i18n="thresholdDemo.npv">NPV</div>
|
|
347
|
+
<div class="threshold-demo-stat-value" id="tdNpv">—</div>
|
|
348
|
+
<div class="threshold-demo-stat-sub">TN / (TN + FN)</div>
|
|
349
|
+
</div>
|
|
350
|
+
</div>
|
|
351
|
+
<div class="threshold-demo-matrix" aria-label="Confusion matrix at current threshold">
|
|
352
|
+
<div class="threshold-demo-cell head"></div>
|
|
353
|
+
<div class="threshold-demo-cell head" data-i18n="thresholdDemo.disease">Disease +</div>
|
|
354
|
+
<div class="threshold-demo-cell head" data-i18n="thresholdDemo.healthy">Disease −</div>
|
|
355
|
+
<div class="threshold-demo-cell head" data-i18n="thresholdDemo.testpos">Test +</div>
|
|
356
|
+
<div class="threshold-demo-cell tp">
|
|
357
|
+
<div class="threshold-demo-cell-n" id="tdTp">0</div>
|
|
358
|
+
<div class="threshold-demo-cell-meta" data-i18n="thresholdDemo.tp">True positives</div>
|
|
359
|
+
</div>
|
|
360
|
+
<div class="threshold-demo-cell fp">
|
|
361
|
+
<div class="threshold-demo-cell-n" id="tdFp">0</div>
|
|
362
|
+
<div class="threshold-demo-cell-meta" data-i18n="thresholdDemo.fp">False positives</div>
|
|
363
|
+
</div>
|
|
364
|
+
<div class="threshold-demo-cell head" data-i18n="thresholdDemo.testneg">Test −</div>
|
|
365
|
+
<div class="threshold-demo-cell fn">
|
|
366
|
+
<div class="threshold-demo-cell-n" id="tdFn">0</div>
|
|
367
|
+
<div class="threshold-demo-cell-meta" data-i18n="thresholdDemo.fn">False negatives</div>
|
|
368
|
+
</div>
|
|
369
|
+
<div class="threshold-demo-cell tn">
|
|
370
|
+
<div class="threshold-demo-cell-n" id="tdTn">0</div>
|
|
371
|
+
<div class="threshold-demo-cell-meta" data-i18n="thresholdDemo.tn">True negatives</div>
|
|
372
|
+
</div>
|
|
373
|
+
</div>
|
|
374
|
+
<p id="thresholdDemoNote" class="threshold-demo-note" aria-live="polite"></p>
|
|
375
|
+
</details>
|
|
163
376
|
</section>
|
|
164
377
|
</div>
|
|
165
378
|
|
|
166
379
|
<div class="resource-grid">
|
|
380
|
+
<section aria-labelledby="kappa-title" class="panel panel-docs kappa-panel">
|
|
381
|
+
<div class="panel-heading">
|
|
382
|
+
<p class="panel-kicker">Companion tool</p>
|
|
383
|
+
<h2 id="kappa-title">Inter-rater agreement (Cohen's κ)</h2>
|
|
384
|
+
<p class="help-text">
|
|
385
|
+
Two raters classify the same items as positive / negative. Enter the four counts to get Cohen's κ with 95% asymptotic CI adjusted for estimated rater marginals and a Landis–Koch qualitative label. Useful for imaging or pathology cases where the "test" is a human judgement.
|
|
386
|
+
</p>
|
|
387
|
+
</div>
|
|
388
|
+
<fieldset class="confusion-matrix kappa-matrix">
|
|
389
|
+
<legend>Rater 1 × Rater 2</legend>
|
|
390
|
+
<div class="grid-container" role="group" aria-label="Inter-rater agreement matrix">
|
|
391
|
+
<span class="empty" aria-hidden="true"></span>
|
|
392
|
+
<span class="grid-header">Rater 2 +</span>
|
|
393
|
+
<span class="grid-header">Rater 2 −</span>
|
|
394
|
+
<label class="grid-label" for="kappaBothPos">Rater 1 +</label>
|
|
395
|
+
<div class="grid-item">
|
|
396
|
+
<input type="number" id="kappaBothPos" inputmode="numeric" min="0" step="1" placeholder="Both +" aria-label="Both raters positive" />
|
|
397
|
+
</div>
|
|
398
|
+
<div class="grid-item">
|
|
399
|
+
<input type="number" id="kappaOnly1Pos" inputmode="numeric" min="0" step="1" placeholder="Only rater 1 +" aria-label="Only rater 1 positive" />
|
|
400
|
+
</div>
|
|
401
|
+
<label class="grid-label" for="kappaOnly2Pos">Rater 1 −</label>
|
|
402
|
+
<div class="grid-item">
|
|
403
|
+
<input type="number" id="kappaOnly2Pos" inputmode="numeric" min="0" step="1" placeholder="Only rater 2 +" aria-label="Only rater 2 positive" />
|
|
404
|
+
</div>
|
|
405
|
+
<div class="grid-item">
|
|
406
|
+
<input type="number" id="kappaBothNeg" inputmode="numeric" min="0" step="1" placeholder="Both −" aria-label="Both raters negative" />
|
|
407
|
+
</div>
|
|
408
|
+
</div>
|
|
409
|
+
</fieldset>
|
|
410
|
+
<div class="form-actions">
|
|
411
|
+
<button type="button" id="kappaCalc">Compute κ</button>
|
|
412
|
+
<button type="button" id="kappaClear" class="secondary">Clear</button>
|
|
413
|
+
</div>
|
|
414
|
+
<div id="kappaResult" class="kappa-result" aria-live="polite"></div>
|
|
415
|
+
</section>
|
|
167
416
|
<section aria-labelledby="interpretation-title" class="interpretation panel panel-docs">
|
|
168
417
|
<h2 id="interpretation-title">Interpretation guide</h2>
|
|
169
418
|
<details>
|
|
170
|
-
<summary>Sensitivity and specificity</summary>
|
|
419
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Sensitivity and specificity</summary>
|
|
171
420
|
<p>
|
|
172
421
|
Quantify the test's ability to correctly identify people with and without the condition. Useful for understanding the intrinsic performance of the test. Learn more at <a href="https://en.wikipedia.org/wiki/Sensitivity_and_specificity" target="_blank" rel="noopener noreferrer">Wikipedia</a>.
|
|
173
422
|
</p>
|
|
174
423
|
</details>
|
|
175
424
|
<details>
|
|
176
|
-
<summary>Predictive values</summary>
|
|
425
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Predictive values</summary>
|
|
177
426
|
<p>
|
|
178
427
|
Depend on prevalence (pre-test probability). Use them to estimate the probability that a person has or does not have the disease after a positive or negative result. See <a href="https://en.wikipedia.org/wiki/Positive_and_negative_predictive_values" target="_blank" rel="noopener noreferrer">Wikipedia</a> for further reading.
|
|
179
428
|
</p>
|
|
180
429
|
</details>
|
|
181
430
|
<details>
|
|
182
|
-
<summary>Likelihood ratios</summary>
|
|
431
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Likelihood ratios</summary>
|
|
183
432
|
<p>
|
|
184
433
|
Relate the probability of a result in diseased vs. non-diseased individuals and can be applied directly in Bayes' formula to update the probability of disease. Further reference at <a href="https://en.wikipedia.org/wiki/Likelihood_ratio" target="_blank" rel="noopener noreferrer">Wikipedia</a>.
|
|
185
434
|
</p>
|
|
186
435
|
</details>
|
|
187
436
|
<details>
|
|
188
|
-
<summary>Post-test probabilities</summary>
|
|
437
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Post-test probabilities</summary>
|
|
189
438
|
<p>
|
|
190
439
|
Result from applying the LR to pre-test odds. They are crucial for clinical decisions such as initiating treatment or ordering additional tests. See also <a href="https://en.wikipedia.org/wiki/Posterior_probability" target="_blank" rel="noopener noreferrer">Wikipedia</a>.
|
|
191
440
|
</p>
|
|
192
441
|
</details>
|
|
193
442
|
</section>
|
|
194
443
|
|
|
444
|
+
<section aria-labelledby="stard-title" class="panel panel-docs stard-panel">
|
|
445
|
+
<div class="panel-heading">
|
|
446
|
+
<p class="panel-kicker">Critical appraisal</p>
|
|
447
|
+
<h2 id="stard-title">STARD essentials</h2>
|
|
448
|
+
<p class="help-text">
|
|
449
|
+
Numbers from a 2×2 only mean what the underlying study allows them to mean. The <a href="https://www.equator-network.org/reporting-guidelines/stard/" target="_blank" rel="noopener noreferrer">STARD 2015 checklist</a> has 30 items; six of them dominate the interpretation of sens / spec / PPV / NPV.
|
|
450
|
+
</p>
|
|
451
|
+
</div>
|
|
452
|
+
<details>
|
|
453
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>1. Reference standard — what counted as "truth"?</summary>
|
|
454
|
+
<p>If the gold standard is imperfect (e.g. expert consensus instead of biopsy), sensitivity and specificity are biased. Worse: if the reference standard incorporates the index test result, agreement is inflated (<em>incorporation bias</em>). Ask which standard was used and whether the index test fed into it.</p>
|
|
455
|
+
</details>
|
|
456
|
+
<details>
|
|
457
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>2. Spectrum of patients — who got tested?</summary>
|
|
458
|
+
<p>Performance estimated in obvious cases ("sick" vs healthy volunteers) almost always overestimates real-world accuracy. Check whether enrolment was <em>consecutive</em> or <em>random</em>, and whether it spanned the clinical spectrum you'll apply the test to (mild, atypical, comorbid).</p>
|
|
459
|
+
</details>
|
|
460
|
+
<details>
|
|
461
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>3. Verification — was the reference standard applied to everyone?</summary>
|
|
462
|
+
<p><em>Partial verification bias</em> arises when positive-index-test patients are more likely to receive the gold standard than negative ones — inflates sensitivity, deflates specificity. Look for differential or post-hoc verification protocols.</p>
|
|
463
|
+
</details>
|
|
464
|
+
<details>
|
|
465
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>4. Blinding — were readers independent?</summary>
|
|
466
|
+
<p>If the person interpreting the index test also knew the reference result (or vice versa), expect <em>review bias</em>: estimates drift toward perfect agreement. Imaging and pathology studies should explicitly report blinding between readers and from clinical context.</p>
|
|
467
|
+
</details>
|
|
468
|
+
<details>
|
|
469
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>5. Indeterminate results — where did the "?"s go?</summary>
|
|
470
|
+
<p>If indeterminate, missing, or excluded results were silently dropped, the 2×2 misrepresents real practice. Ask how many results fell outside the binary scheme and how they were handled (re-tested, excluded, counted as negative…).</p>
|
|
471
|
+
</details>
|
|
472
|
+
<details>
|
|
473
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>6. Subgroups — does one number describe everyone?</summary>
|
|
474
|
+
<p>Sensitivity / specificity often vary by age, severity, prior testing, or setting. A single 2×2 hides those differences. Check whether the authors pre-specified clinically meaningful subgroups and reported them separately.</p>
|
|
475
|
+
</details>
|
|
476
|
+
</section>
|
|
195
477
|
<section aria-labelledby="references-title" class="references panel panel-docs">
|
|
196
478
|
<h2 id="references-title">Resources and references</h2>
|
|
197
479
|
<details>
|
|
198
|
-
<summary>Supporting literature</summary>
|
|
480
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Supporting literature</summary>
|
|
199
481
|
<ul class="reference-list">
|
|
200
482
|
<li>
|
|
201
483
|
<strong>Akobeng AK.</strong> Understanding diagnostic tests 1-3: sensitivity, specificity, and predictive values.
|
|
@@ -215,7 +497,7 @@
|
|
|
215
497
|
</ul>
|
|
216
498
|
</details>
|
|
217
499
|
<details>
|
|
218
|
-
<summary>Online resources</summary>
|
|
500
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Online resources</summary>
|
|
219
501
|
<ul class="reference-list">
|
|
220
502
|
<li>
|
|
221
503
|
<strong>The Centre for Evidence-Based Medicine (CEBM):</strong>
|
|
@@ -233,7 +515,7 @@
|
|
|
233
515
|
</ul>
|
|
234
516
|
</details>
|
|
235
517
|
<details>
|
|
236
|
-
<summary>Complementary calculators</summary>
|
|
518
|
+
<summary><span class="summary-tag summary-tag--ref" data-i18n="panel.tag.ref">Reference</span>Complementary calculators</summary>
|
|
237
519
|
<ul class="reference-list">
|
|
238
520
|
<li>
|
|
239
521
|
<strong>MDCalc:</strong>
|
|
@@ -246,6 +528,29 @@
|
|
|
246
528
|
</ul>
|
|
247
529
|
</details>
|
|
248
530
|
</section>
|
|
531
|
+
<section aria-labelledby="about-title" class="panel panel-docs about-panel">
|
|
532
|
+
<div class="panel-heading">
|
|
533
|
+
<p class="panel-kicker" data-i18n="intro.tag">Educational tool</p>
|
|
534
|
+
<h2 id="about-title" data-i18n="about.h">About DIAGCALC</h2>
|
|
535
|
+
<p data-i18n="intro.lead">
|
|
536
|
+
Interactive calculator for exploring diagnostic test performance measures and applying Bayesian reasoning in clinical practice.
|
|
537
|
+
</p>
|
|
538
|
+
</div>
|
|
539
|
+
<div class="about-grid">
|
|
540
|
+
<article class="about-card">
|
|
541
|
+
<h3 data-i18n="intro.highlight1.h">Key measures</h3>
|
|
542
|
+
<p data-i18n="intro.highlight1.p">Sensitivity, specificity, PPV, NPV, and likelihood ratios with 95% CI.</p>
|
|
543
|
+
</article>
|
|
544
|
+
<article class="about-card">
|
|
545
|
+
<h3 data-i18n="intro.highlight2.h">Post-test probability</h3>
|
|
546
|
+
<p data-i18n="intro.highlight2.p">Convert pre-test probability into positive and negative post-test probability.</p>
|
|
547
|
+
</article>
|
|
548
|
+
<article class="about-card">
|
|
549
|
+
<h3 data-i18n="intro.highlight3.h">Guided interpretation</h3>
|
|
550
|
+
<p data-i18n="intro.highlight3.p">Results explained to support self-directed learning.</p>
|
|
551
|
+
</article>
|
|
552
|
+
</div>
|
|
553
|
+
</section>
|
|
249
554
|
</div>
|
|
250
555
|
</main>
|
|
251
556
|
<footer>
|
|
@@ -255,12 +560,23 @@
|
|
|
255
560
|
<p>
|
|
256
561
|
Any analyses, results and conclusions may not be used, interpreted or generalised beyond this specific context.
|
|
257
562
|
</p>
|
|
563
|
+
<p class="privacy-note">
|
|
564
|
+
🔒 All calculations run locally in your browser. No inputs, datasets, or results leave this device.
|
|
565
|
+
</p>
|
|
258
566
|
<p>
|
|
259
567
|
© 2025 — MIT Licence — Developed by <a href="https://tiagojct.eu">Tiago Jacinto</a>. <a href="mailto:tiagojacinto@med.up.pt">Contact</a>
|
|
260
568
|
</p>
|
|
261
569
|
</footer>
|
|
570
|
+
<script src="lib/diagcalc-i18n.js"></script>
|
|
262
571
|
<script src="lib/diagcalc-datasets.js"></script>
|
|
572
|
+
<script src="lib/diagcalc-presentation.js"></script>
|
|
263
573
|
<script src="lib/diagcalc-core.js"></script>
|
|
574
|
+
<script src="lib/diagcalc-meta.js"></script>
|
|
575
|
+
<script src="lib/diagcalc-case.js"></script>
|
|
576
|
+
<script src="lib/diagcalc-storage.js"></script>
|
|
577
|
+
<script src="lib/diagcalc-geometry.js"></script>
|
|
578
|
+
<script src="web/results.js"></script>
|
|
579
|
+
<script src="web/charts.js"></script>
|
|
264
580
|
<script src="script.js"></script>
|
|
265
581
|
</body>
|
|
266
582
|
</html>
|