@sjcrh/proteinpaint-server 2.180.1 → 2.181.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dataset/termdb.test.js +7 -1
- package/package.json +7 -7
- package/routes/profile.polar2.js +101 -0
- package/routes/termdb.cluster.js +111 -3
- package/routes/termdb.config.js +13 -5
- package/routes/termdb.diffMeth.js +23 -12
- package/routes/termdb.dmr.js +52 -18
- package/routes/termdb.isoformAvailability.js +35 -0
- package/routes/termdb.profileFormScores.js +15 -4
- package/routes/termdb.profileScores.js +9 -1
- package/routes/termdb.sampleScatter.js +14 -2
- package/routes/termdb.violinBox.js +509 -0
- package/src/app.js +1512 -1158
- package/routes/termdb.boxplot.js +0 -260
- package/routes/termdb.violin.js +0 -285
package/routes/termdb.boxplot.js
DELETED
|
@@ -1,260 +0,0 @@
|
|
|
1
|
-
import { boxplotPayload } from "#types/checkers";
|
|
2
|
-
import { getData } from "../src/termdb.matrix.js";
|
|
3
|
-
import { boxplot_getvalue } from "../src/utils.js";
|
|
4
|
-
import { sortPlot2Values } from "./termdb.violin.ts";
|
|
5
|
-
import { getDescrStats, getStdDev, getMean } from "./termdb.descrstats.ts";
|
|
6
|
-
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
7
|
-
import { roundValueAuto } from "#shared/roundValue.js";
|
|
8
|
-
import { isNumericTerm } from "@sjcrh/proteinpaint-shared";
|
|
9
|
-
const api = {
|
|
10
|
-
endpoint: "termdb/boxplot",
|
|
11
|
-
methods: {
|
|
12
|
-
get: {
|
|
13
|
-
...boxplotPayload,
|
|
14
|
-
init
|
|
15
|
-
},
|
|
16
|
-
post: {
|
|
17
|
-
...boxplotPayload,
|
|
18
|
-
init
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
};
|
|
22
|
-
function init({ genomes }) {
|
|
23
|
-
return async (req, res) => {
|
|
24
|
-
const q = req.query;
|
|
25
|
-
try {
|
|
26
|
-
const genome = genomes[q.genome];
|
|
27
|
-
if (!genome) throw new Error("invalid genome name");
|
|
28
|
-
const ds = genome.datasets?.[q.dslabel];
|
|
29
|
-
if (!ds) throw new Error("invalid dslabel");
|
|
30
|
-
const terms = [q.tw];
|
|
31
|
-
if (q.overlayTw) terms.push(q.overlayTw);
|
|
32
|
-
if (q.divideTw) terms.push(q.divideTw);
|
|
33
|
-
const data = await getData(
|
|
34
|
-
{ filter: q.filter, filter0: q.filter0, terms, __protected__: q.__protected__, __abortSignal: q.__abortSignal },
|
|
35
|
-
ds
|
|
36
|
-
);
|
|
37
|
-
if (data.error) throw new Error(data.error);
|
|
38
|
-
const { absMin, absMax, bins, charts, uncomputableValues, descrStats, outlierMin, outlierMax } = await processData(data, q);
|
|
39
|
-
const returnData = {
|
|
40
|
-
absMin: q.removeOutliers ? outlierMin : absMin,
|
|
41
|
-
absMax: q.removeOutliers ? outlierMax : absMax,
|
|
42
|
-
bins,
|
|
43
|
-
charts,
|
|
44
|
-
uncomputableValues: setUncomputableValues(uncomputableValues),
|
|
45
|
-
descrStats
|
|
46
|
-
};
|
|
47
|
-
res.send(returnData);
|
|
48
|
-
} catch (e) {
|
|
49
|
-
res.send({ error: e?.message || e });
|
|
50
|
-
if (e instanceof Error && e.stack) console.error(e);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
async function processData(data, q) {
|
|
55
|
-
const samples = Object.values(data.samples);
|
|
56
|
-
const values = samples.map((s) => s?.[q.tw.$id]?.value).filter((v) => typeof v === "number" && !q.tw.term.values?.[v]?.uncomputable);
|
|
57
|
-
const descrStats = getDescrStats(values, q.removeOutliers);
|
|
58
|
-
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
59
|
-
const overlayTw = q.overlayTw;
|
|
60
|
-
const divideTw = q.divideTw;
|
|
61
|
-
const { absMin, absMax, chart2plot2values, uncomputableValues } = parseValues(
|
|
62
|
-
q,
|
|
63
|
-
data,
|
|
64
|
-
sampleType,
|
|
65
|
-
q.isLogScale,
|
|
66
|
-
overlayTw,
|
|
67
|
-
divideTw
|
|
68
|
-
);
|
|
69
|
-
if (!absMin && absMin !== 0) throw new Error("absMin is undefined");
|
|
70
|
-
if (!absMax && absMax !== 0) throw new Error("absMax is undefined");
|
|
71
|
-
const charts = {};
|
|
72
|
-
let outlierMin = Number.POSITIVE_INFINITY, outlierMax = Number.NEGATIVE_INFINITY;
|
|
73
|
-
for (const [chart, plot2values] of chart2plot2values) {
|
|
74
|
-
const plots = [];
|
|
75
|
-
for (const [key, values2] of sortPlot2Values(data, plot2values, overlayTw)) {
|
|
76
|
-
;
|
|
77
|
-
[outlierMax, outlierMin] = setPlotData(
|
|
78
|
-
plots,
|
|
79
|
-
values2,
|
|
80
|
-
key,
|
|
81
|
-
sampleType,
|
|
82
|
-
descrStats,
|
|
83
|
-
q,
|
|
84
|
-
outlierMin,
|
|
85
|
-
outlierMax,
|
|
86
|
-
overlayTw
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
if (q.tw.term?.values) setHiddenPlots(q.tw, plots);
|
|
90
|
-
if (overlayTw && overlayTw.term?.values) setHiddenPlots(overlayTw, plots);
|
|
91
|
-
if (divideTw && divideTw.term?.values) setHiddenPlots(divideTw, plots);
|
|
92
|
-
if (q.orderByMedian == true) {
|
|
93
|
-
plots.sort((a, b) => a.boxplot.p50 - b.boxplot.p50);
|
|
94
|
-
}
|
|
95
|
-
const sampleCount = plots.reduce((total, p) => {
|
|
96
|
-
if (p.hidden) return total;
|
|
97
|
-
return total + p.descrStats.total.value;
|
|
98
|
-
}, 0);
|
|
99
|
-
charts[chart] = { chartId: chart, plots, sampleCount };
|
|
100
|
-
}
|
|
101
|
-
const bins = {
|
|
102
|
-
term1: numericBins(q.tw, data)
|
|
103
|
-
};
|
|
104
|
-
if (overlayTw) bins.term2 = numericBins(overlayTw, data);
|
|
105
|
-
if (divideTw) bins.term0 = numericBins(divideTw, data);
|
|
106
|
-
if (q.showAssocTests && overlayTw) await getWilcoxonData(charts);
|
|
107
|
-
Object.keys(charts).forEach((c) => charts[c].plots.forEach((p) => delete p.tempValues));
|
|
108
|
-
return { absMin, absMax, bins, charts, uncomputableValues, descrStats, outlierMin, outlierMax };
|
|
109
|
-
}
|
|
110
|
-
function setPlotData(plots, values, key, sampleType, descrStats, q, outlierMin, outlierMax, overlayTw) {
|
|
111
|
-
const sortedValues = values.sort((a, b) => a - b);
|
|
112
|
-
const vs = sortedValues.map((v) => {
|
|
113
|
-
const value = { value: v };
|
|
114
|
-
return value;
|
|
115
|
-
});
|
|
116
|
-
if (q.removeOutliers) {
|
|
117
|
-
outlierMin = Math.min(outlierMin, descrStats.outlierMin.value);
|
|
118
|
-
outlierMax = Math.max(outlierMax, descrStats.outlierMax.value);
|
|
119
|
-
}
|
|
120
|
-
const boxplot = boxplot_getvalue(vs, q.removeOutliers);
|
|
121
|
-
if (!boxplot) throw new Error("boxplot_getvalue failed [termdb.boxplot init()]");
|
|
122
|
-
const plot = {
|
|
123
|
-
boxplot,
|
|
124
|
-
descrStats: setIndividualBoxPlotStats(boxplot, sortedValues),
|
|
125
|
-
//quick fix
|
|
126
|
-
//to delete later
|
|
127
|
-
tempValues: sortedValues
|
|
128
|
-
};
|
|
129
|
-
if (overlayTw) {
|
|
130
|
-
const _key = overlayTw?.term?.values?.[key]?.label || key;
|
|
131
|
-
plot.color = overlayTw?.term?.values?.[key]?.color || null;
|
|
132
|
-
plot.key = _key;
|
|
133
|
-
plot.seriesId = key;
|
|
134
|
-
plot.boxplot.label = `${_key}, n=${values.length}`;
|
|
135
|
-
} else {
|
|
136
|
-
plot.key = sampleType;
|
|
137
|
-
plot.boxplot.label = `${sampleType}, n=${values.length}`;
|
|
138
|
-
}
|
|
139
|
-
plots.push(plot);
|
|
140
|
-
return [outlierMax, outlierMin];
|
|
141
|
-
}
|
|
142
|
-
function setIndividualBoxPlotStats(boxplot, values) {
|
|
143
|
-
const stats = {
|
|
144
|
-
total: { key: "total", label: "Total", value: values.length },
|
|
145
|
-
min: { key: "min", label: "Minimum", value: values[0] },
|
|
146
|
-
p25: { key: "p25", label: "1st quartile", value: boxplot.p25 },
|
|
147
|
-
median: { key: "median", label: "Median", value: boxplot.p50 },
|
|
148
|
-
p75: { key: "p75", label: "3rd quartile", value: boxplot.p75 },
|
|
149
|
-
mean: { key: "mean", label: "Mean", value: getMean(values) },
|
|
150
|
-
max: { key: "max", label: "Maximum", value: values[values.length - 1] },
|
|
151
|
-
stdDev: { key: "stdDev", label: "Standard deviation", value: getStdDev(values) }
|
|
152
|
-
};
|
|
153
|
-
for (const key of Object.keys(stats)) {
|
|
154
|
-
stats[key].value = roundValueAuto(stats[key].value);
|
|
155
|
-
}
|
|
156
|
-
return stats;
|
|
157
|
-
}
|
|
158
|
-
function setHiddenPlots(term, plots) {
|
|
159
|
-
for (const v of Object.values(term.term?.values)) {
|
|
160
|
-
const plot = plots.find((p) => p.key === v.label);
|
|
161
|
-
if (plot) plot.isHidden = v?.uncomputable;
|
|
162
|
-
}
|
|
163
|
-
if (term.q?.hiddenValues) {
|
|
164
|
-
for (const key of Object.keys(term.q.hiddenValues)) {
|
|
165
|
-
const plot = plots.find((p) => p.key === key);
|
|
166
|
-
if (plot) plot.isHidden = true;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return plots;
|
|
170
|
-
}
|
|
171
|
-
function setUncomputableValues(values) {
|
|
172
|
-
if (Object.entries(values)?.length) {
|
|
173
|
-
return Object.entries(values).map(([label, v]) => ({ label, value: v }));
|
|
174
|
-
} else return null;
|
|
175
|
-
}
|
|
176
|
-
async function getWilcoxonData(charts) {
|
|
177
|
-
for (const chart of Object.values(charts)) {
|
|
178
|
-
const numPlots = chart.plots?.length;
|
|
179
|
-
if (numPlots < 2) continue;
|
|
180
|
-
const wilcoxonInput = [];
|
|
181
|
-
for (let i = 0; i < numPlots; i++) {
|
|
182
|
-
const group1_id = chart.plots[i].boxplot.label.replace(/, n=\d+$/, "");
|
|
183
|
-
const group1_values = chart.plots[i].tempValues;
|
|
184
|
-
for (let j = i + 1; j < numPlots; j++) {
|
|
185
|
-
const group2_id = chart.plots[j].boxplot.label.replace(/, n=\d+$/, "");
|
|
186
|
-
const group2_values = chart.plots[j].tempValues;
|
|
187
|
-
wilcoxonInput.push({ group1_id, group1_values, group2_id, group2_values });
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
const wilcoxonOutput = JSON.parse(await run_rust("wilcoxon", JSON.stringify(wilcoxonInput)));
|
|
191
|
-
chart.wilcoxon = [];
|
|
192
|
-
for (const test of wilcoxonOutput) {
|
|
193
|
-
if (test.pvalue == null || test.pvalue == "null") {
|
|
194
|
-
chart.wilcoxon.push([{ value: test.group1_id }, { value: test.group2_id }, { html: "NA" }]);
|
|
195
|
-
} else {
|
|
196
|
-
chart.wilcoxon.push([
|
|
197
|
-
{ value: test.group1_id },
|
|
198
|
-
{ value: test.group2_id },
|
|
199
|
-
{ html: test.pvalue.toPrecision(4) }
|
|
200
|
-
]);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
function parseValues(q, data, sampleType, isLog, overlayTw, divideTw) {
|
|
206
|
-
const chart2plot2values = /* @__PURE__ */ new Map();
|
|
207
|
-
const uncomputableValues = {};
|
|
208
|
-
let absMin = Infinity, absMax = -Infinity;
|
|
209
|
-
for (const val of Object.values(data.samples)) {
|
|
210
|
-
const value = val[q.tw.$id];
|
|
211
|
-
if (!Number.isFinite(value?.value)) continue;
|
|
212
|
-
if (q.tw.term.values?.[value.value]?.uncomputable) {
|
|
213
|
-
const label = q.tw.term.values[value.value].label;
|
|
214
|
-
uncomputableValues[label] = (uncomputableValues[label] || 0) + 1;
|
|
215
|
-
continue;
|
|
216
|
-
}
|
|
217
|
-
if (isLog && value.value <= 0) continue;
|
|
218
|
-
let chart = "";
|
|
219
|
-
let plot = sampleType;
|
|
220
|
-
if (divideTw) {
|
|
221
|
-
if (!val[divideTw?.$id]) continue;
|
|
222
|
-
const value0 = val[divideTw.$id];
|
|
223
|
-
if (divideTw.term?.values?.[value0.key]?.uncomputable) {
|
|
224
|
-
const label = divideTw.term.values[value0?.key]?.label;
|
|
225
|
-
uncomputableValues[label] = (uncomputableValues[label] || 0) + 1;
|
|
226
|
-
}
|
|
227
|
-
chart = value0.key;
|
|
228
|
-
}
|
|
229
|
-
if (overlayTw) {
|
|
230
|
-
if (!val[overlayTw?.$id]) continue;
|
|
231
|
-
const value2 = val[overlayTw.$id];
|
|
232
|
-
if (overlayTw.term?.values?.[value2.key]?.uncomputable) {
|
|
233
|
-
const label = overlayTw.term.values[value2?.key]?.label;
|
|
234
|
-
uncomputableValues[label] = (uncomputableValues[label] || 0) + 1;
|
|
235
|
-
}
|
|
236
|
-
plot = value2.key;
|
|
237
|
-
}
|
|
238
|
-
if (!chart2plot2values.has(chart)) chart2plot2values.set(chart, /* @__PURE__ */ new Map());
|
|
239
|
-
const plot2values = chart2plot2values.get(chart);
|
|
240
|
-
if (!plot2values.has(plot)) plot2values.set(plot, []);
|
|
241
|
-
const values = plot2values.get(plot);
|
|
242
|
-
values.push(value.value);
|
|
243
|
-
if (value.value < absMin) absMin = value.value;
|
|
244
|
-
if (value.value > absMax) absMax = value.value;
|
|
245
|
-
}
|
|
246
|
-
return { absMax, absMin, chart2plot2values, uncomputableValues };
|
|
247
|
-
}
|
|
248
|
-
function numericBins(tw, data) {
|
|
249
|
-
const bins = {};
|
|
250
|
-
if (!isNumericTerm(tw?.term)) return bins;
|
|
251
|
-
for (const bin of data.refs.byTermId[tw?.$id]?.bins || []) {
|
|
252
|
-
bins[bin.label] = bin;
|
|
253
|
-
}
|
|
254
|
-
return bins;
|
|
255
|
-
}
|
|
256
|
-
export {
|
|
257
|
-
api,
|
|
258
|
-
numericBins,
|
|
259
|
-
parseValues
|
|
260
|
-
};
|
package/routes/termdb.violin.js
DELETED
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import { violinPayload } from "#types/checkers";
|
|
2
|
-
import { scaleLinear, scaleLog } from "d3";
|
|
3
|
-
import { run_rust } from "@sjcrh/proteinpaint-rust";
|
|
4
|
-
import { getData } from "../src/termdb.matrix.js";
|
|
5
|
-
import { createCanvas } from "canvas";
|
|
6
|
-
import { getOrderedLabels } from "../src/termdb.barchart.js";
|
|
7
|
-
import { getDescrStats } from "./termdb.descrstats.ts";
|
|
8
|
-
import { isNumericTerm } from "#shared/terms.js";
|
|
9
|
-
import { numericBins, parseValues } from "./termdb.boxplot.ts";
|
|
10
|
-
import { run_R } from "@sjcrh/proteinpaint-r";
|
|
11
|
-
const api = {
|
|
12
|
-
endpoint: "termdb/violin",
|
|
13
|
-
methods: {
|
|
14
|
-
get: {
|
|
15
|
-
...violinPayload,
|
|
16
|
-
init
|
|
17
|
-
},
|
|
18
|
-
post: {
|
|
19
|
-
...violinPayload,
|
|
20
|
-
init
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
function init({ genomes }) {
|
|
25
|
-
return async (req, res) => {
|
|
26
|
-
const q = req.query;
|
|
27
|
-
let data;
|
|
28
|
-
try {
|
|
29
|
-
const g = genomes[q.genome];
|
|
30
|
-
if (!g) throw new Error("invalid genome name");
|
|
31
|
-
const ds = g.datasets?.[q.dslabel];
|
|
32
|
-
if (!ds) throw new Error("invalid ds");
|
|
33
|
-
data = await getViolin(q, ds);
|
|
34
|
-
} catch (e) {
|
|
35
|
-
data = { error: e?.message || e };
|
|
36
|
-
if (e instanceof Error && e.stack) console.log(e);
|
|
37
|
-
}
|
|
38
|
-
res.send(data);
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
async function getViolin(q, ds) {
|
|
42
|
-
if (typeof q.tw?.term != "object" || typeof q.tw?.q != "object") throw new Error("q.tw not of {term,q}");
|
|
43
|
-
const term = q.tw.term;
|
|
44
|
-
if (!q.tw.q.mode) q.tw.q.mode = "continuous";
|
|
45
|
-
if (!isNumericTerm(term) && term.type !== "survival") throw new Error("term type is not numeric or survival");
|
|
46
|
-
const terms = [q.tw];
|
|
47
|
-
if (q.overlayTw) terms.push(q.overlayTw);
|
|
48
|
-
if (q.divideTw) terms.push(q.divideTw);
|
|
49
|
-
const data = await getData(
|
|
50
|
-
{
|
|
51
|
-
terms,
|
|
52
|
-
filter: q.filter,
|
|
53
|
-
filter0: q.filter0,
|
|
54
|
-
currentGeneNames: q.currentGeneNames,
|
|
55
|
-
__protected__: q.__protected__,
|
|
56
|
-
__abortSignal: q.__abortSignal
|
|
57
|
-
},
|
|
58
|
-
ds
|
|
59
|
-
);
|
|
60
|
-
if (!data) throw new Error("getData() returns nothing");
|
|
61
|
-
if (data.error) throw new Error(data.error);
|
|
62
|
-
const samples = Object.values(data.samples);
|
|
63
|
-
let values = samples.map((s) => s?.[q.tw.$id]?.value).filter((v) => typeof v === "number" && !q.tw.term.values?.[v]?.uncomputable);
|
|
64
|
-
if (q.unit == "log") values = values.filter((v) => v > 0);
|
|
65
|
-
const descrStats = getDescrStats(values);
|
|
66
|
-
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
67
|
-
if (data.error) throw new Error(data.error);
|
|
68
|
-
if (q.overlayTw && data.refs.byTermId[q.overlayTw.$id]) {
|
|
69
|
-
data.refs.byTermId[q.overlayTw.$id].orderedLabels = getOrderedLabels(
|
|
70
|
-
q.overlayTw,
|
|
71
|
-
data.refs.byTermId[q.overlayTw.$id]?.bins,
|
|
72
|
-
void 0,
|
|
73
|
-
q.overlayTw.q
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
if (q.scale) setScaleData(q, data, q.tw);
|
|
77
|
-
const valuesObject = divideValues(q, data, sampleType);
|
|
78
|
-
const result = setResponse(valuesObject, data, q);
|
|
79
|
-
if (q.overlayTw) await getWilcoxonData(result);
|
|
80
|
-
await createCanvasImg(q, result, ds);
|
|
81
|
-
result["descrStats"] = descrStats;
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
async function getWilcoxonData(result) {
|
|
85
|
-
for (const k of Object.keys(result.charts)) {
|
|
86
|
-
const chart = result.charts[k];
|
|
87
|
-
const numPlots = chart.plots.length;
|
|
88
|
-
if (numPlots < 2) continue;
|
|
89
|
-
const wilcoxInput = [];
|
|
90
|
-
for (let i = 0; i < numPlots; i++) {
|
|
91
|
-
const group1_id = chart.plots[i].label;
|
|
92
|
-
const group1_values = chart.plots[i].values;
|
|
93
|
-
for (let j = i + 1; j < numPlots; j++) {
|
|
94
|
-
const group2_id = chart.plots[j].label;
|
|
95
|
-
const group2_values = chart.plots[j].values;
|
|
96
|
-
wilcoxInput.push({ group1_id, group1_values, group2_id, group2_values });
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
const wilcoxOutput = JSON.parse(await run_rust("wilcoxon", JSON.stringify(wilcoxInput)));
|
|
100
|
-
chart.pvalues = [];
|
|
101
|
-
for (const test of wilcoxOutput) {
|
|
102
|
-
if (test.pvalue == null || test.pvalue == "null") {
|
|
103
|
-
chart.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: "NA" }]);
|
|
104
|
-
} else {
|
|
105
|
-
chart.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: test.pvalue.toPrecision(4) }]);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function setScaleData(q, data, tw) {
|
|
111
|
-
if (!q.scale) return;
|
|
112
|
-
const scale = Number(q.scale);
|
|
113
|
-
for (const val of Object.values(data.samples)) {
|
|
114
|
-
if (!tw.$id || !val[tw.$id]) continue;
|
|
115
|
-
if (tw.term.values?.[val[tw.$id]?.value]?.uncomputable) continue;
|
|
116
|
-
val[tw.$id].key = val[tw.$id].key / scale;
|
|
117
|
-
val[tw.$id].value = val[tw.$id].value / scale;
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
function divideValues(q, data, sampleType) {
|
|
121
|
-
const overlayTerm = q.overlayTw;
|
|
122
|
-
const divideTerm = q.divideTw;
|
|
123
|
-
const useLog = q.unit == "log";
|
|
124
|
-
const { absMax, absMin, chart2plot2values, uncomputableValues } = parseValues(
|
|
125
|
-
q,
|
|
126
|
-
data,
|
|
127
|
-
sampleType,
|
|
128
|
-
useLog,
|
|
129
|
-
overlayTerm,
|
|
130
|
-
divideTerm
|
|
131
|
-
);
|
|
132
|
-
return {
|
|
133
|
-
chart2plot2values,
|
|
134
|
-
min: absMin,
|
|
135
|
-
max: absMax,
|
|
136
|
-
uncomputableValues: sortObj(uncomputableValues)
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function sortObj(object) {
|
|
140
|
-
return Object.fromEntries(Object.entries(object).sort(([, a], [, b]) => a - b));
|
|
141
|
-
}
|
|
142
|
-
function sortPlot2Values(data, plot2values, overlayTerm) {
|
|
143
|
-
const orderedLabels = overlayTerm?.$id ? data.refs.byTermId[overlayTerm.$id]?.keyOrder : void 0;
|
|
144
|
-
plot2values = new Map(
|
|
145
|
-
[...plot2values].sort(
|
|
146
|
-
orderedLabels ? (a, b) => orderedLabels.indexOf(a[0]) - orderedLabels.indexOf(b[0]) : overlayTerm?.term?.type === "categorical" ? (a, b) => b[1].length - a[1].length : overlayTerm?.term?.type === "condition" ? (a, b) => Number(a[0]) - Number(b[0]) : (a, b) => a.toString().replace(/[^a-zA-Z0-9<]/g, "").localeCompare(b.toString().replace(/[^a-zA-Z0-9<]/g, ""), void 0, { numeric: true })
|
|
147
|
-
)
|
|
148
|
-
);
|
|
149
|
-
return plot2values;
|
|
150
|
-
}
|
|
151
|
-
function setResponse(valuesObject, data, q) {
|
|
152
|
-
const charts = {};
|
|
153
|
-
const overlayTerm = q.overlayTw;
|
|
154
|
-
const divideTw = q.divideTw;
|
|
155
|
-
for (const [chart, plot2values] of valuesObject.chart2plot2values) {
|
|
156
|
-
const plots = [];
|
|
157
|
-
for (const [plot, values] of sortPlot2Values(data, plot2values, overlayTerm)) {
|
|
158
|
-
plots.push({
|
|
159
|
-
label: overlayTerm?.term?.values?.[plot]?.label || plot,
|
|
160
|
-
values,
|
|
161
|
-
seriesId: plot,
|
|
162
|
-
chartId: chart,
|
|
163
|
-
//quick fix to get list samples working
|
|
164
|
-
plotValueCount: values?.length,
|
|
165
|
-
color: overlayTerm?.term?.values?.[plot]?.color || null
|
|
166
|
-
});
|
|
167
|
-
}
|
|
168
|
-
charts[chart] = { chartId: chart, plots };
|
|
169
|
-
}
|
|
170
|
-
const bins = {
|
|
171
|
-
term1: numericBins(q.tw, data)
|
|
172
|
-
};
|
|
173
|
-
if (overlayTerm) bins.term2 = numericBins(overlayTerm, data);
|
|
174
|
-
if (divideTw) bins.term0 = numericBins(divideTw, data);
|
|
175
|
-
const result = {
|
|
176
|
-
min: valuesObject.min,
|
|
177
|
-
max: valuesObject.max,
|
|
178
|
-
bins,
|
|
179
|
-
charts,
|
|
180
|
-
uncomputableValues: Object.keys(valuesObject.uncomputableValues).length > 0 ? valuesObject.uncomputableValues : null
|
|
181
|
-
};
|
|
182
|
-
return result;
|
|
183
|
-
}
|
|
184
|
-
async function createCanvasImg(q, result, ds) {
|
|
185
|
-
if (!q.radius) q.radius = 5;
|
|
186
|
-
if (q.radius <= 0) throw new Error("q.radius is not a number");
|
|
187
|
-
else q.radius = +q.radius;
|
|
188
|
-
const isH = q.orientation == "horizontal";
|
|
189
|
-
for (const k of Object.keys(result.charts)) {
|
|
190
|
-
const chart = result.charts[k];
|
|
191
|
-
const plot2Values = {};
|
|
192
|
-
for (const plot of chart.plots) plot2Values[plot.label] = plot.values;
|
|
193
|
-
const useLog = q.unit == "log";
|
|
194
|
-
const logBase = ds.cohort.termdb.logscaleBase2 ? 2 : 10;
|
|
195
|
-
const densities = await getDensities(plot2Values, useLog, logBase);
|
|
196
|
-
let axisScale;
|
|
197
|
-
if (useLog) {
|
|
198
|
-
axisScale = scaleLog().base(ds.cohort.termdb.logscaleBase2 ? 2 : 10).domain([result.min, result.max]).range(isH ? [0, q.svgw] : [q.svgw, 0]);
|
|
199
|
-
} else {
|
|
200
|
-
axisScale = scaleLinear().domain([result.min, result.max]).range(isH ? [0, q.svgw] : [q.svgw, 0]);
|
|
201
|
-
}
|
|
202
|
-
const [width, height] = isH ? [q.svgw * q.devicePixelRatio, q.radius * q.devicePixelRatio] : [q.radius * q.devicePixelRatio, q.svgw * q.devicePixelRatio];
|
|
203
|
-
for (const plot of chart.plots) {
|
|
204
|
-
plot.density = densities[plot.label];
|
|
205
|
-
const canvas = createCanvas(width, height);
|
|
206
|
-
const ctx = canvas.getContext("2d");
|
|
207
|
-
if (q.devicePixelRatio != 1) ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
|
|
208
|
-
ctx.strokeStyle = "black";
|
|
209
|
-
ctx.lineWidth = 1;
|
|
210
|
-
if (q.datasymbol === "rug") {
|
|
211
|
-
ctx.globalAlpha = 0.8;
|
|
212
|
-
plot.values.forEach((i) => {
|
|
213
|
-
const s = axisScale(i);
|
|
214
|
-
ctx.beginPath();
|
|
215
|
-
if (isH) {
|
|
216
|
-
ctx.moveTo(s, 0);
|
|
217
|
-
ctx.lineTo(s, q.radius);
|
|
218
|
-
} else {
|
|
219
|
-
ctx.moveTo(0, s);
|
|
220
|
-
ctx.lineTo(q.radius, s);
|
|
221
|
-
}
|
|
222
|
-
ctx.stroke();
|
|
223
|
-
});
|
|
224
|
-
} else if (q.datasymbol === "bean") {
|
|
225
|
-
ctx.globalAlpha = 0.6;
|
|
226
|
-
ctx.fillStyle = "#ffe6e6";
|
|
227
|
-
plot.values.forEach((i) => {
|
|
228
|
-
const s = axisScale(i);
|
|
229
|
-
ctx.beginPath();
|
|
230
|
-
if (isH) ctx.arc(s, q.radius / 2, q.radius / 2, 0, 2 * Math.PI);
|
|
231
|
-
else ctx.arc(q.radius / 2, s, q.radius / 2, 0, 2 * Math.PI);
|
|
232
|
-
ctx.fill();
|
|
233
|
-
ctx.stroke();
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
plot.src = canvas.toDataURL();
|
|
237
|
-
plot.summaryStats = getDescrStats(plot.values);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
async function getDensity(values) {
|
|
242
|
-
const result = await getDensities({ plot: values });
|
|
243
|
-
return result.plot;
|
|
244
|
-
}
|
|
245
|
-
async function getDensities(plot2Values, useLog = false, logBase = 10) {
|
|
246
|
-
let transformedPlot2Values = {};
|
|
247
|
-
if (useLog) {
|
|
248
|
-
for (const plot in plot2Values) {
|
|
249
|
-
transformedPlot2Values[plot] = plot2Values[plot].filter((v) => v > 0).map((v) => Math.log(v) / Math.log(logBase));
|
|
250
|
-
}
|
|
251
|
-
} else {
|
|
252
|
-
transformedPlot2Values = plot2Values;
|
|
253
|
-
}
|
|
254
|
-
const plot2Density = JSON.parse(await run_R("density.R", JSON.stringify({ plot2Values: transformedPlot2Values })));
|
|
255
|
-
const densities = {};
|
|
256
|
-
for (const plot in plot2Density) {
|
|
257
|
-
const result = plot2Density[plot];
|
|
258
|
-
const bins = [];
|
|
259
|
-
let densityMin = Infinity;
|
|
260
|
-
let densityMax = -Infinity;
|
|
261
|
-
let xMin = Infinity;
|
|
262
|
-
let xMax = -Infinity;
|
|
263
|
-
for (const [i, x] of Object.entries(result.x)) {
|
|
264
|
-
const density2 = result.y[i];
|
|
265
|
-
const x0 = useLog ? Math.pow(logBase, x) : x;
|
|
266
|
-
xMin = Math.min(xMin, x0);
|
|
267
|
-
xMax = Math.max(xMax, x0);
|
|
268
|
-
densityMin = Math.min(densityMin, density2);
|
|
269
|
-
densityMax = Math.max(densityMax, density2);
|
|
270
|
-
bins.push({ x0, density: density2 });
|
|
271
|
-
}
|
|
272
|
-
bins.unshift({ x0: xMin, density: densityMin });
|
|
273
|
-
bins.push({ x0: xMax, density: densityMin });
|
|
274
|
-
const density = { bins, densityMin, densityMax, minvalue: xMin, maxvalue: xMax };
|
|
275
|
-
densities[plot] = density;
|
|
276
|
-
}
|
|
277
|
-
return densities;
|
|
278
|
-
}
|
|
279
|
-
export {
|
|
280
|
-
api,
|
|
281
|
-
getDensities,
|
|
282
|
-
getDensity,
|
|
283
|
-
getWilcoxonData,
|
|
284
|
-
sortPlot2Values
|
|
285
|
-
};
|