Skip to content

Commit 4a167bc

Browse files
creilly8xzhou82
authored andcommitted
Let getter, rather than vocabApi method, handle scge requests
1 parent d296f3c commit 4a167bc

File tree

5 files changed

+44
-84
lines changed

5 files changed

+44
-84
lines changed

client/plots/scatter/model/scatterModel.ts

Lines changed: 19 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import type {
1515
//icons have size 16x16
1616
export const shapes = shapesArray
1717

18-
const numberOfSamplesCutoff = 20000 // if map is greater than cutoff, switch from svg to canvas rendering
18+
const maxSvgSamplesCutoff = 20000 // if map is greater than cutoff, switch from svg to canvas rendering
1919
const noExpColor = '#F5F5F5' //light gray
2020
const expColor = '#ff000d' //default color for gene expression
2121

@@ -85,16 +85,13 @@ export class ScatterModel {
8585
if ('error' in data) throw data.error
8686

8787
this.charts = []
88-
//This is not preferable.
89-
if (reqOpts.colorTW?.term.type == SINGLECELL_GENE_EXPRESSION) {
90-
this.processGEData(data['plots'])
91-
} else {
92-
this.range = data.range
93-
for (const [key, chartData] of Object.entries(data.result)) {
94-
if (!Array.isArray(chartData.samples)) throw 'data.samples[] not array'
95-
this.createChart(key, chartData)
96-
}
88+
89+
this.range = data.range
90+
for (const [key, chartData] of Object.entries(data.result)) {
91+
if (!Array.isArray(chartData.samples)) throw 'data.samples[] not array'
92+
this.createChart(key, chartData)
9793
}
94+
9895
this.is3D = this.scatter.config.term0?.q.mode == 'continuous'
9996
this.initRanges()
10097
} catch (e: any) {
@@ -106,57 +103,19 @@ export class ScatterModel {
106103

107104
createChart(id: string, data: ScatterDataResult) {
108105
const cohortSamples: any[] = data.samples.filter(sample => 'sampleId' in sample)
109-
if (cohortSamples.length > numberOfSamplesCutoff) this.is2DLarge = true
106+
if (cohortSamples.length > maxSvgSamplesCutoff) this.is2DLarge = true
110107
const colorLegend: Map<string, ColorLegendItem> = new Map(data.colorLegend)
111108
const shapeLegend: Map<string, ShapeLegendItem> = new Map(data.shapeLegend)
112109
this.charts.push({ id, data, cohortSamples, colorLegend, shapeLegend })
113110
}
114111

115-
processGEData(plots: any) {
116-
const plotsCopy = structuredClone(plots)
117-
let xMin = Infinity,
118-
xMax = -Infinity,
119-
yMin = Infinity,
120-
yMax = -Infinity
121-
for (const plot of plotsCopy) {
122-
plot.data = plot.data || {}
123-
plot.id = plot.name
124-
let plotMax = -Infinity,
125-
plotMin = Infinity,
126-
plotGEMin = Infinity,
127-
plotGEMax = -Infinity
128-
for (const cell of plot.expCells) {
129-
xMin = Math.min(xMin, cell.x)
130-
xMax = Math.max(xMax, cell.x)
131-
yMin = Math.min(yMin, cell.y)
132-
yMax = Math.max(yMax, cell.y)
133-
plotMin = Math.min(plotMin, cell.x)
134-
plotMax = Math.max(plotMax, cell.y)
135-
plotGEMin = Math.min(plotGEMin, cell.geneExp)
136-
plotGEMax = Math.max(plotGEMax, cell.geneExp)
137-
cell.shape = 'Ref'
138-
}
139-
plot.min = plotMin
140-
plot.max = plotMax
141-
plot.geMin = plotGEMin
142-
plot.geMax = plotGEMax
143-
plot.data.samples = plot.expCells.concat(plot.noExpCells)
144-
plot.cohortSamples = plot.expCells.concat(plot.noExpCells)
145-
plot.colorLegend = new Map()
146-
plot.shapeLegend = new Map()
147-
plot.shapeLegend.set('Ref', { shape: 0, key: 'Ref', sampleCount: plot.expCells.length })
148-
this.charts.push(plot)
149-
}
150-
this.range = { xMin, xMax, yMin, yMax }
151-
}
152-
153112
async initRanges() {
154113
let samples: any[] = []
155114
for (const chart of this.charts) samples = samples.concat(chart.data.samples)
156-
if (samples.length > numberOfSamplesCutoff) this.is2DLarge = true
115+
if (samples.length > maxSvgSamplesCutoff) this.is2DLarge = true
157116
if (samples.length == 0) return
158117
const s0 = samples[0] //First sample to start reduce comparisons
159-
const [xMin, xMax, yMin, yMax, zMin, zMax, scaleMin, scaleMax] = samples.reduce(
118+
const [xMin, xMax, yMin, yMax, zMin, zMax, scaleMin, scaleMax, geMin, geMax] = samples.reduce(
160119
(s, d) => [
161120
d.x < s[0] ? d.x : s[0],
162121
d.x > s[1] ? d.x : s[1],
@@ -165,9 +124,11 @@ export class ScatterModel {
165124
d.z < s[4] ? d.z : s[4],
166125
d.z > s[5] ? d.z : s[5],
167126
'scale' in d ? (d.scale < s[6] ? d.scale : s[6]) : Number.POSITIVE_INFINITY,
168-
'scale' in d ? (d.scale > s[7] ? d.scale : s[7]) : Number.NEGATIVE_INFINITY
127+
'scale' in d ? (d.scale > s[7] ? d.scale : s[7]) : Number.NEGATIVE_INFINITY,
128+
'geneExp' in d ? (d.geneExp < s[8] ? d.geneExp : s[8]) : Number.POSITIVE_INFINITY,
129+
'geneExp' in d ? (d.geneExp > s[9] ? d.geneExp : s[9]) : Number.NEGATIVE_INFINITY
169130
],
170-
[s0.x, s0.x, s0.y, s0.y, s0.z, s0.z, s0.scale, s0.scale]
131+
[s0.x, s0.x, s0.y, s0.y, s0.z, s0.z, s0.scale, s0.scale, s0.geneExp, s0.geneExp]
171132
)
172133
const settings = this.scatter.settings
173134
for (const chart of this.charts) {
@@ -179,7 +140,9 @@ export class ScatterModel {
179140
zMin,
180141
zMax,
181142
scaleMin,
182-
scaleMax
143+
scaleMax,
144+
geMin,
145+
geMax
183146
}
184147
}
185148
}
@@ -258,7 +221,7 @@ export class ScatterModel {
258221
if (this.scatter.config.colorTW?.term.type == SINGLECELL_GENE_EXPRESSION) {
259222
let color
260223
if (!c.geneExp) color = noExpColor
261-
else if (c.geneExp > chart.geMax) color = expColor
224+
else if (c.geneExp > chart.ranges.geMax) color = expColor
262225
else color = chart.colorGenerator(c.geneExp)
263226
return color
264227
}
@@ -404,15 +367,14 @@ export class ScatterModel {
404367
? expColor
405368
: config.colorTW?.term.continuousColorScale?.maxColor || gradientColor.darker().toString()
406369
}
407-
408370
// Handle continuous color scaling when color term wrapper is in continuous mode
409371
if (config.colorTW?.q.mode === 'continuous') {
410372
// Extract and sort all sample values for our calculations
411373
// We filter out any values that are explicitly defined in the term values
412374
// This gives us the raw numerical data we need for scaling
413375
let colorValues
414376
if (config.colorTW.term.type == SINGLECELL_GENE_EXPRESSION) {
415-
colorValues = [chart.geMin, chart.geMax]
377+
colorValues = [chart.ranges.geMin, chart.ranges.geMax]
416378
} else {
417379
colorValues = chart.cohortSamples
418380
.filter(
@@ -452,7 +414,6 @@ export class ScatterModel {
452414
// order just get the first and last values
453415
break
454416
}
455-
456417
// Create the color generator using d3's linear scale
457418
// This maps our numerical range to a color gradient
458419

client/plots/scatter/scatterTypes.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ export type ScatterChart = {
6969
zMax?: number
7070
scaleMin?: number
7171
scaleMax?: number
72+
geMin?: number
73+
geMax?: number
7274
}
7375
id: any
7476
cohortSamples: any

client/plots/scatter/test/scatter.integration.spec.js

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ import {
1212
sleep
1313
} from '../../../test/test.helpers'
1414
import { openSummaryPlot, openPlot, getSamplelstTW } from '../../../mass/groups.js'
15-
import { rgb } from 'd3-color'
1615
import { mclass } from '#shared/common.js'
1716
import {
18-
getSamplelstTw,
19-
getCategoryGroupsetting,
2017
getGenesetMutTw,
2118
getGeneVariantTw,
2219
getSsgseaTw,
23-
getScgeneexpTw
20+
getScgeneexpTw,
21+
getScctTw
2422
} from '../../../test/testdata/data.ts'
2523

2624
/* Tests:
@@ -1293,6 +1291,7 @@ tape('singlecell map', function (test) {
12931291
plots: [
12941292
{
12951293
chartType: 'sampleScatter',
1294+
colorTW: getScctTw(),
12961295
singleCellPlot: { name: 'scRNA', sample: { sID: '1_patient' } }
12971296
}
12981297
]

client/termdb/TermdbVocab.js

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Vocab } from './Vocab'
22
import { getNormalRoot } from '#filter'
33
import { isUsableTerm } from '#shared/termdb.usecase.js'
44
import { throwMsgWithFilePathAndFnName } from '../dom/sayerror'
5-
import { isDictionaryType, SINGLECELL_GENE_EXPRESSION } from '#shared/terms.js'
5+
import { isDictionaryType } from '#shared/terms.js'
66

77
export class TermdbVocab extends Vocab {
88
// migrated from termdb/store
@@ -1041,13 +1041,6 @@ export class TermdbVocab extends Vocab {
10411041
if (opts.divideByTW) body.divideByTW = this.getTwMinCopy(opts.divideByTW)
10421042
if (opts.scaleDotTW) body.scaleDotTW = this.getTwMinCopy(opts.scaleDotTW)
10431043
body.excludeOutliers = opts.excludeOutliers
1044-
if (opts.singleCellPlot && opts.colorTW?.term.type == SINGLECELL_GENE_EXPRESSION) {
1045-
// required by legacy singlecell. delete when that's retired
1046-
body.gene = opts.colorTW.term.gene
1047-
body.plots = [opts.singleCellPlot.name]
1048-
body.sample = opts.singleCellPlot.sample
1049-
return await this.dofetch3('termdb/singlecellData', { headers, body, signal })
1050-
}
10511044
return await this.dofetch3('termdb/sampleScatter', { headers, body, signal })
10521045
}
10531046

server/routes/termdb.sampleScatter.ts

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { authApi } from '../src/auth.js'
2222
import { run_R } from '@sjcrh/proteinpaint-r'
2323
import { read_file } from '../src/utils.js'
2424
import { getDescrStats } from '#routes/termdb.descrstats.ts'
25-
import { SINGLECELL_CELLTYPE, SINGLECELL_GENE_EXPRESSION } from '#shared/terms.js'
25+
import { SINGLECELL_GENE_EXPRESSION, isSingleCellTerm } from '#shared/terms.js'
2626

2727
export const api: RouteApi = {
2828
endpoint: 'termdb/sampleScatter',
@@ -158,12 +158,13 @@ async function getSingleCellScatter(req, res, ds) {
158158
const { name, sample } = q.singleCellPlot
159159
try {
160160
const tw = q.colorTW as any // not using "TermWrapper" due to tsc err
161+
if (tw && !isSingleCellTerm(tw.term))
162+
throw new Error('colorTW must be a single cell term for single cell scatter plot')
161163
const arg: any = { plots: [name], sample }
162-
if (tw) {
163-
if (tw.term.type == SINGLECELL_GENE_EXPRESSION) arg.gene = tw.term.gene
164-
else if (tw.term.type == SINGLECELL_CELLTYPE) arg.colorBy = tw.term.name
165-
else throw new Error('unsupported tw')
166-
}
164+
165+
if (tw.term.type == SINGLECELL_GENE_EXPRESSION) arg.gene = tw.term.gene
166+
else arg.colorBy = tw.term.name //SINGLECELL_CELLTYPE
167+
167168
const data = await ds.queries.singleCell.data.get(arg)
168169

169170
const plot = data.plots[0]
@@ -180,7 +181,8 @@ async function getSingleCellScatter(req, res, ds) {
180181
z: 0,
181182
category: cell.category,
182183
shape: 'Ref',
183-
hidden
184+
hidden,
185+
geneExp: cell.geneExp
184186
}
185187
})
186188
const [xMin, xMax, yMin, yMax] = samples.reduce(
@@ -189,13 +191,16 @@ async function getSingleCellScatter(req, res, ds) {
189191
)
190192
const categories: any = new Set(samples.map(s => s.category))
191193
const colorMap = {}
192-
const k2c = getColors(categories.size)
193-
for (const category of categories) {
194-
const color = k2c(category)
195-
colorMap[category] = {
196-
sampleCount: samples.filter((s: any) => s.category == category).length,
197-
color,
198-
key: category
194+
195+
if (tw.term.type != SINGLECELL_GENE_EXPRESSION) {
196+
const k2c = getColors(categories.size)
197+
for (const category of categories) {
198+
const color = k2c(category)
199+
colorMap[category] = {
200+
sampleCount: samples.filter((s: any) => s.category == category).length,
201+
color,
202+
key: category
203+
}
199204
}
200205
}
201206
const shapeLegend: ShapeLegendEntry[] = [['Ref', { sampleCount: samples.length, shape: 0, key: 'Ref' }]]

0 commit comments

Comments
 (0)