@sjcrh/proteinpaint-server 2.97.0 → 2.98.1-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/package.json +3 -3
- package/routes/termdb.boxplot.js +50 -35
- package/routes/termdb.config.js +8 -7
- package/routes/termdb.violin.js +198 -3
- package/src/app.js +1190 -642
- package/src/serverconfig.js +9 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sjcrh/proteinpaint-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.98.1-0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "a genomics visualization tool for exploring a cohort's genotype and phenotype data",
|
|
6
6
|
"main": "src/app.js",
|
|
@@ -42,7 +42,6 @@
|
|
|
42
42
|
"@babel/preset-env": "^7.9.6",
|
|
43
43
|
"@babel/preset-typescript": "^7.21.4",
|
|
44
44
|
"@babel/register": "^7.14.5",
|
|
45
|
-
"@sjcrh/proteinpaint-types": "2.96.2-0",
|
|
46
45
|
"@types/node": "^20.11.24",
|
|
47
46
|
"@types/tough-cookie": "^4.0.5",
|
|
48
47
|
"@typescript-eslint/eslint-plugin": "^8.13.0",
|
|
@@ -61,7 +60,8 @@
|
|
|
61
60
|
"dependencies": {
|
|
62
61
|
"@sjcrh/augen": "2.87.0",
|
|
63
62
|
"@sjcrh/proteinpaint-rust": "2.84.0",
|
|
64
|
-
"@sjcrh/proteinpaint-shared": "2.
|
|
63
|
+
"@sjcrh/proteinpaint-shared": "2.98.0",
|
|
64
|
+
"@sjcrh/proteinpaint-types": "2.98.1-0",
|
|
65
65
|
"better-sqlite3": "^9.4.1",
|
|
66
66
|
"body-parser": "^1.15.2",
|
|
67
67
|
"canvas": "~2.11.2",
|
package/routes/termdb.boxplot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { boxplotPayload } from "#types/checkers";
|
|
2
2
|
import { getData } from "../src/termdb.matrix.js";
|
|
3
3
|
import { boxplot_getvalue } from "../src/utils.js";
|
|
4
|
-
import { sortKey2values } from "
|
|
4
|
+
import { sortKey2values } from "./termdb.violin.ts";
|
|
5
5
|
import { roundValueAuto } from "#shared/roundValue.js";
|
|
6
6
|
const api = {
|
|
7
7
|
endpoint: "termdb/boxplot",
|
|
@@ -41,43 +41,18 @@ function init({ genomes }) {
|
|
|
41
41
|
if (data.error)
|
|
42
42
|
throw data.error;
|
|
43
43
|
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
44
|
-
const key2values = /* @__PURE__ */ new Map();
|
|
45
44
|
const overlayTerm = q.overlayTw;
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (overlayTerm) {
|
|
57
|
-
if (!val[overlayTerm?.$id])
|
|
58
|
-
continue;
|
|
59
|
-
const value2 = val[overlayTerm.$id];
|
|
60
|
-
if (overlayTerm.term?.values?.[value2.key]?.uncomputable) {
|
|
61
|
-
const label = overlayTerm.term.values[value2?.key]?.label;
|
|
62
|
-
uncomputableValues[label] = (uncomputableValues[label] || 0) + 1;
|
|
63
|
-
}
|
|
64
|
-
if (!key2values.has(value2.key))
|
|
65
|
-
key2values.set(value2.key, []);
|
|
66
|
-
key2values.get(value2.key).push(value.value);
|
|
67
|
-
} else {
|
|
68
|
-
if (!key2values.has(sampleType))
|
|
69
|
-
key2values.set(sampleType, []);
|
|
70
|
-
key2values.get(sampleType).push(value.value);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
45
|
+
const isLog = false;
|
|
46
|
+
const { absMin, absMax, key2values, uncomputableValues } = parseValues(
|
|
47
|
+
q,
|
|
48
|
+
data,
|
|
49
|
+
sampleType,
|
|
50
|
+
isLog,
|
|
51
|
+
overlayTerm
|
|
52
|
+
);
|
|
73
53
|
const plots = [];
|
|
74
|
-
let absMin = null, absMax = null;
|
|
75
54
|
for (const [key, values] of sortKey2values(data, key2values, overlayTerm)) {
|
|
76
55
|
const sortedValues = values.sort((a, b) => a - b);
|
|
77
|
-
if (absMin === null || sortedValues[0] < absMin)
|
|
78
|
-
absMin = sortedValues[0];
|
|
79
|
-
if (absMax === null || sortedValues[sortedValues.length - 1] > absMax)
|
|
80
|
-
absMax = sortedValues[sortedValues.length - 1];
|
|
81
56
|
const vs = sortedValues.map((v) => {
|
|
82
57
|
const value = { value: v };
|
|
83
58
|
return value;
|
|
@@ -179,10 +154,50 @@ function setUncomputableValues(values) {
|
|
|
179
154
|
} else
|
|
180
155
|
return null;
|
|
181
156
|
}
|
|
157
|
+
function parseValues(q, data, sampleType, isLog, overlayTerm) {
|
|
158
|
+
const key2values = /* @__PURE__ */ new Map();
|
|
159
|
+
const uncomputableValues = {};
|
|
160
|
+
let absMin = null, absMax = null;
|
|
161
|
+
for (const val of Object.values(data.samples)) {
|
|
162
|
+
const value = val[q.tw.$id];
|
|
163
|
+
if (!Number.isFinite(value?.value))
|
|
164
|
+
continue;
|
|
165
|
+
if (q.tw.term.values?.[value.value]?.uncomputable) {
|
|
166
|
+
const label = q.tw.term.values[value.value].label;
|
|
167
|
+
uncomputableValues[label] = (uncomputableValues[label] || 0) + 1;
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
if (isLog && value.value <= 0)
|
|
171
|
+
continue;
|
|
172
|
+
if (overlayTerm) {
|
|
173
|
+
if (!val[overlayTerm?.$id])
|
|
174
|
+
continue;
|
|
175
|
+
const value2 = val[overlayTerm.$id];
|
|
176
|
+
if (overlayTerm.term?.values?.[value2.key]?.uncomputable) {
|
|
177
|
+
const label = overlayTerm.term.values[value2?.key]?.label;
|
|
178
|
+
uncomputableValues[label] = (uncomputableValues[label] || 0) + 1;
|
|
179
|
+
}
|
|
180
|
+
if (!key2values.has(value2.key))
|
|
181
|
+
key2values.set(value2.key, []);
|
|
182
|
+
key2values.get(value2.key).push(value.value);
|
|
183
|
+
} else {
|
|
184
|
+
if (!key2values.has(sampleType))
|
|
185
|
+
key2values.set(sampleType, []);
|
|
186
|
+
key2values.get(sampleType).push(value.value);
|
|
187
|
+
}
|
|
188
|
+
if (absMin === null || value.value < absMin)
|
|
189
|
+
absMin = value.value;
|
|
190
|
+
if (absMax === null || value.value > absMax)
|
|
191
|
+
absMax = value.value;
|
|
192
|
+
}
|
|
193
|
+
return { absMax, absMin, key2values, uncomputableValues };
|
|
194
|
+
}
|
|
182
195
|
function numericBins(overlayTerm, data) {
|
|
183
196
|
const overlayBins = data.refs.byTermId[overlayTerm?.$id]?.bins ?? [];
|
|
184
197
|
return new Map(overlayBins.map((bin) => [bin.label, bin]));
|
|
185
198
|
}
|
|
186
199
|
export {
|
|
187
|
-
api
|
|
200
|
+
api,
|
|
201
|
+
numericBins,
|
|
202
|
+
parseValues
|
|
188
203
|
};
|
package/routes/termdb.config.js
CHANGED
|
@@ -27,7 +27,7 @@ function init({ genomes }) {
|
|
|
27
27
|
if (!genome)
|
|
28
28
|
throw "invalid genome";
|
|
29
29
|
const [ds] = get_ds_tdb(genome, q);
|
|
30
|
-
return make(q, res, ds, genome);
|
|
30
|
+
return make(q, req, res, ds, genome);
|
|
31
31
|
} catch (e) {
|
|
32
32
|
res.send({ error: e.message || e });
|
|
33
33
|
if (e.stack)
|
|
@@ -37,13 +37,12 @@ function init({ genomes }) {
|
|
|
37
37
|
}
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
|
-
function make(q, res, ds, genome) {
|
|
40
|
+
function make(q, req, res, ds, genome) {
|
|
41
41
|
const tdb = ds.cohort.termdb;
|
|
42
|
-
const auth = { embedder: q.embedder };
|
|
43
42
|
const c = {
|
|
44
43
|
selectCohort: tdb.selectCohort,
|
|
45
44
|
// optional
|
|
46
|
-
supportedChartTypes: tdb.q?.getSupportedChartTypes(
|
|
45
|
+
supportedChartTypes: tdb.q?.getSupportedChartTypes(req),
|
|
47
46
|
renamedChartTypes: ds.cohort.renamedChartTypes,
|
|
48
47
|
allowedTermTypes: getAllowedTermTypes(ds),
|
|
49
48
|
termMatch2geneSet: tdb.termMatch2geneSet,
|
|
@@ -68,8 +67,6 @@ function make(q, res, ds, genome) {
|
|
|
68
67
|
c.plotConfigByCohort = tdb.plotConfigByCohort;
|
|
69
68
|
if (tdb.multipleTestingCorrection)
|
|
70
69
|
c.multipleTestingCorrection = tdb.multipleTestingCorrection;
|
|
71
|
-
if (tdb.neuroOncRegression)
|
|
72
|
-
c.neuroOncRegression = tdb.neuroOncRegression;
|
|
73
70
|
if (tdb.helpPages)
|
|
74
71
|
c.helpPages = tdb.helpPages;
|
|
75
72
|
if (tdb.minTimeSinceDx)
|
|
@@ -90,15 +87,19 @@ function make(q, res, ds, genome) {
|
|
|
90
87
|
c.excludedTermtypeByTarget = tdb.excludedTermtypeByTarget;
|
|
91
88
|
if (tdb.survival)
|
|
92
89
|
c.survival = tdb.survival;
|
|
90
|
+
if (tdb.regression)
|
|
91
|
+
c.regression = tdb.regression;
|
|
93
92
|
if (ds.assayAvailability)
|
|
94
93
|
c.assayAvailability = ds.assayAvailability;
|
|
95
94
|
if (ds.customTwQByType)
|
|
96
95
|
c.customTwQByType = ds.customTwQByType;
|
|
97
|
-
c.requiredAuth = authApi.getRequiredCredForDsEmbedder(q.dslabel, q.embedder);
|
|
98
96
|
addRestrictAncestries(c, tdb);
|
|
99
97
|
addScatterplots(c, ds);
|
|
100
98
|
addMatrixplots(c, ds);
|
|
101
99
|
addNonDictionaryQueries(c, ds, genome);
|
|
100
|
+
c.requiredAuth = authApi.getRequiredCredForDsEmbedder(q.dslabel, q.embedder);
|
|
101
|
+
const info = authApi.getNonsensitiveInfo(req);
|
|
102
|
+
c.clientAuthResult = info.clientAuthResult || {};
|
|
102
103
|
res.send({ termdbConfig: c });
|
|
103
104
|
}
|
|
104
105
|
function addRestrictAncestries(c, tdb) {
|
package/routes/termdb.violin.js
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { violinPayload } from "#types/checkers";
|
|
2
|
-
import {
|
|
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 summaryStats from "#shared/descriptive.stats.js";
|
|
8
|
+
import { isNumericTerm } from "#shared/terms.js";
|
|
9
|
+
import { getBinsDensity } from "#shared/violin.bins.js";
|
|
10
|
+
import { numericBins, parseValues } from "./termdb.boxplot.ts";
|
|
3
11
|
const api = {
|
|
4
12
|
endpoint: "termdb/violin",
|
|
5
13
|
methods: {
|
|
@@ -24,7 +32,7 @@ function init({ genomes }) {
|
|
|
24
32
|
const ds = g.datasets?.[q.dslabel];
|
|
25
33
|
if (!ds)
|
|
26
34
|
throw "invalid ds";
|
|
27
|
-
data = await trigger_getViolinPlotData(q,
|
|
35
|
+
data = await trigger_getViolinPlotData(q, ds, g);
|
|
28
36
|
} catch (e) {
|
|
29
37
|
data = { error: e?.message || e };
|
|
30
38
|
if (e instanceof Error && e.stack)
|
|
@@ -33,6 +41,193 @@ function init({ genomes }) {
|
|
|
33
41
|
res.send(data);
|
|
34
42
|
};
|
|
35
43
|
}
|
|
44
|
+
async function trigger_getViolinPlotData(q, ds, genome) {
|
|
45
|
+
if (typeof q.tw?.term != "object" || typeof q.tw?.q != "object")
|
|
46
|
+
throw "q.tw not of {term,q}";
|
|
47
|
+
const term = q.tw.term;
|
|
48
|
+
if (!q.tw.q.mode)
|
|
49
|
+
q.tw.q.mode = "continuous";
|
|
50
|
+
if (!isNumericTerm(term) && term.type !== "survival")
|
|
51
|
+
throw "term type is not numeric or survival";
|
|
52
|
+
const terms = [q.tw];
|
|
53
|
+
if (q.divideTw)
|
|
54
|
+
terms.push(q.divideTw);
|
|
55
|
+
const data = await getData(
|
|
56
|
+
{ terms, filter: q.filter, filter0: q.filter0, currentGeneNames: q.currentGeneNames },
|
|
57
|
+
ds,
|
|
58
|
+
genome
|
|
59
|
+
);
|
|
60
|
+
const sampleType = `All ${data.sampleType?.plural_name || "samples"}`;
|
|
61
|
+
if (data.error)
|
|
62
|
+
throw data.error;
|
|
63
|
+
if (q.divideTw && data.refs.byTermId[q.divideTw.$id]) {
|
|
64
|
+
data.refs.byTermId[q.divideTw.$id].orderedLabels = getOrderedLabels(
|
|
65
|
+
q.divideTw,
|
|
66
|
+
data.refs.byTermId[q.divideTw.$id]?.bins,
|
|
67
|
+
void 0,
|
|
68
|
+
q.divideTw.q
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (q.scale)
|
|
72
|
+
setScaleData(q, data, q.tw);
|
|
73
|
+
const valuesObject = divideValues(q, data, sampleType);
|
|
74
|
+
const result = setResponse(valuesObject, data, q, sampleType);
|
|
75
|
+
await getWilcoxonData(q.divideTw, result);
|
|
76
|
+
createCanvasImg(q, result, ds);
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
async function getWilcoxonData(divideTw, result) {
|
|
80
|
+
if (!divideTw)
|
|
81
|
+
return;
|
|
82
|
+
const numPlots = result.plots.length;
|
|
83
|
+
if (numPlots < 2)
|
|
84
|
+
return;
|
|
85
|
+
const wilcoxInput = [];
|
|
86
|
+
for (let i = 0; i < numPlots; i++) {
|
|
87
|
+
const group1_id = result.plots[i].label;
|
|
88
|
+
const group1_values = result.plots[i].values;
|
|
89
|
+
for (let j = i + 1; j < numPlots; j++) {
|
|
90
|
+
const group2_id = result.plots[j].label;
|
|
91
|
+
const group2_values = result.plots[j].values;
|
|
92
|
+
wilcoxInput.push({ group1_id, group1_values, group2_id, group2_values });
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const wilcoxOutput = JSON.parse(await run_rust("wilcoxon", JSON.stringify(wilcoxInput)));
|
|
96
|
+
for (const test of wilcoxOutput) {
|
|
97
|
+
if (test.pvalue == null || test.pvalue == "null") {
|
|
98
|
+
result.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: "NA" }]);
|
|
99
|
+
} else {
|
|
100
|
+
result.pvalues.push([{ value: test.group1_id }, { value: test.group2_id }, { html: test.pvalue.toPrecision(4) }]);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function setScaleData(q, data, tw) {
|
|
105
|
+
if (!q.scale)
|
|
106
|
+
return;
|
|
107
|
+
const scale = Number(q.scale);
|
|
108
|
+
for (const val of Object.values(data.samples)) {
|
|
109
|
+
if (!tw.$id || !val[tw.$id])
|
|
110
|
+
continue;
|
|
111
|
+
if (tw.term.values?.[val[tw.$id]?.value]?.uncomputable)
|
|
112
|
+
continue;
|
|
113
|
+
val[tw.$id].key = val[tw.$id].key / scale;
|
|
114
|
+
val[tw.$id].value = val[tw.$id].value / scale;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function divideValues(q, data, sampleType) {
|
|
118
|
+
const overlayTerm = q.divideTw;
|
|
119
|
+
const useLog = q.unit == "log";
|
|
120
|
+
const { absMax, absMin, key2values, uncomputableValues } = parseValues(q, data, sampleType, useLog, overlayTerm);
|
|
121
|
+
return {
|
|
122
|
+
key2values,
|
|
123
|
+
min: absMin,
|
|
124
|
+
max: absMax,
|
|
125
|
+
uncomputableValues: sortObj(uncomputableValues)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function sortObj(object) {
|
|
129
|
+
return Object.fromEntries(Object.entries(object).sort(([, a], [, b]) => a - b));
|
|
130
|
+
}
|
|
131
|
+
function sortKey2values(data, key2values, overlayTerm) {
|
|
132
|
+
const orderedLabels = overlayTerm?.$id ? data.refs.byTermId[overlayTerm.$id]?.keyOrder : void 0;
|
|
133
|
+
key2values = new Map(
|
|
134
|
+
[...key2values].sort(
|
|
135
|
+
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 })
|
|
136
|
+
)
|
|
137
|
+
);
|
|
138
|
+
return key2values;
|
|
139
|
+
}
|
|
140
|
+
function setResponse(valuesObject, data, q, sampleType) {
|
|
141
|
+
const overlayTerm = q.divideTw;
|
|
142
|
+
const plots = [];
|
|
143
|
+
for (const [key, values] of sortKey2values(data, valuesObject.key2values, overlayTerm)) {
|
|
144
|
+
if (overlayTerm) {
|
|
145
|
+
plots.push({
|
|
146
|
+
label: overlayTerm?.term?.values?.[key]?.label || key,
|
|
147
|
+
values,
|
|
148
|
+
seriesId: key,
|
|
149
|
+
plotValueCount: values?.length,
|
|
150
|
+
color: overlayTerm?.term?.values?.[key]?.color || null,
|
|
151
|
+
divideTwBins: isNumericTerm(overlayTerm.term) ? numericBins(overlayTerm, data) : null
|
|
152
|
+
});
|
|
153
|
+
} else {
|
|
154
|
+
plots.push({
|
|
155
|
+
label: sampleType,
|
|
156
|
+
values,
|
|
157
|
+
plotValueCount: values.length
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
const result = {
|
|
162
|
+
min: valuesObject.min,
|
|
163
|
+
max: valuesObject.max,
|
|
164
|
+
plots,
|
|
165
|
+
pvalues: [],
|
|
166
|
+
uncomputableValues: Object.keys(valuesObject.uncomputableValues).length > 0 ? valuesObject.uncomputableValues : null
|
|
167
|
+
};
|
|
168
|
+
return result;
|
|
169
|
+
}
|
|
170
|
+
function createCanvasImg(q, result, ds) {
|
|
171
|
+
if (!q.radius)
|
|
172
|
+
q.radius = 5;
|
|
173
|
+
if (q.radius <= 0)
|
|
174
|
+
throw "q.radius is not a number";
|
|
175
|
+
else
|
|
176
|
+
q.radius = +q.radius;
|
|
177
|
+
if (!q.strokeWidth)
|
|
178
|
+
q.strokeWidth = 0.2;
|
|
179
|
+
const refSize = q.radius * 4;
|
|
180
|
+
let axisScale;
|
|
181
|
+
const useLog = q.unit == "log";
|
|
182
|
+
if (useLog) {
|
|
183
|
+
axisScale = scaleLog().base(ds.cohort.termdb.logscaleBase2 ? 2 : 10).domain([result.min, result.max]).range(q.orientation === "horizontal" ? [0, q.svgw] : [q.svgw, 0]);
|
|
184
|
+
} else {
|
|
185
|
+
axisScale = scaleLinear().domain([result.min, result.max]).range(q.orientation === "horizontal" ? [0, q.svgw] : [q.svgw, 0]);
|
|
186
|
+
}
|
|
187
|
+
const [width, height] = q.orientation == "horizontal" ? [q.svgw * q.devicePixelRatio, refSize * q.devicePixelRatio] : [refSize * q.devicePixelRatio, q.svgw * q.devicePixelRatio];
|
|
188
|
+
const scaledRadius = q.radius / q.devicePixelRatio;
|
|
189
|
+
const arcEndAngle = scaledRadius * Math.PI;
|
|
190
|
+
for (const plot of result.plots) {
|
|
191
|
+
const canvas = createCanvas(width, height);
|
|
192
|
+
const ctx = canvas.getContext("2d");
|
|
193
|
+
ctx.strokeStyle = "rgba(0,0,0,0.8)";
|
|
194
|
+
ctx.lineWidth = q.strokeWidth / q.devicePixelRatio;
|
|
195
|
+
ctx.globalAlpha = 0.5;
|
|
196
|
+
ctx.fillStyle = "#ffe6e6";
|
|
197
|
+
if (q.devicePixelRatio != 1) {
|
|
198
|
+
ctx.scale(q.devicePixelRatio, q.devicePixelRatio);
|
|
199
|
+
}
|
|
200
|
+
if (q.datasymbol === "rug")
|
|
201
|
+
plot.values.forEach((i) => {
|
|
202
|
+
ctx.beginPath();
|
|
203
|
+
if (q.orientation == "horizontal") {
|
|
204
|
+
ctx.moveTo(+axisScale(i), 0);
|
|
205
|
+
ctx.lineTo(+axisScale(i), scaledRadius * 2);
|
|
206
|
+
} else {
|
|
207
|
+
ctx.moveTo(0, +axisScale(i));
|
|
208
|
+
ctx.lineTo(scaledRadius * 2, +axisScale(i));
|
|
209
|
+
}
|
|
210
|
+
ctx.stroke();
|
|
211
|
+
});
|
|
212
|
+
else if (q.datasymbol === "bean")
|
|
213
|
+
plot.values.forEach((i) => {
|
|
214
|
+
ctx.beginPath();
|
|
215
|
+
if (q.orientation === "horizontal")
|
|
216
|
+
ctx.arc(+axisScale(i), q.radius, scaledRadius, 0, arcEndAngle);
|
|
217
|
+
else
|
|
218
|
+
ctx.arc(q.radius, +axisScale(i), scaledRadius, 0, arcEndAngle);
|
|
219
|
+
ctx.fill();
|
|
220
|
+
ctx.stroke();
|
|
221
|
+
});
|
|
222
|
+
plot.src = canvas.toDataURL();
|
|
223
|
+
plot.density = getBinsDensity(axisScale, plot, q.isKDE, q.ticks);
|
|
224
|
+
plot.summaryStats = summaryStats(plot.values).values;
|
|
225
|
+
delete plot.values;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
36
228
|
export {
|
|
37
|
-
api
|
|
229
|
+
api,
|
|
230
|
+
getWilcoxonData,
|
|
231
|
+
sortKey2values,
|
|
232
|
+
trigger_getViolinPlotData
|
|
38
233
|
};
|