gramene-search 2.0.2 → 2.0.4
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/.claude/settings.local.json +5 -1
- package/.parcel-cache/83e7562660f7cc15-BundleGraph +0 -0
- package/.parcel-cache/d3a1b9507cb44047-AssetGraph +0 -0
- package/.parcel-cache/data.mdb +0 -0
- package/.parcel-cache/dc1da35000e13623-RequestGraph +0 -0
- package/.parcel-cache/lock.mdb +0 -0
- package/.parcel-cache/snapshot-dc1da35000e13623.txt +2 -2
- package/dist/index.css +314 -0
- package/dist/index.css.map +1 -1
- package/dist/index.js +2022 -13
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/bundles/exprViz.js +422 -0
- package/src/bundles/index.js +2 -1
- package/src/bundles/views.js +18 -12
- package/src/components/exprViz/ExprTable.js +194 -0
- package/src/components/exprViz/ExprVizView.js +294 -0
- package/src/components/exprViz/FieldsModal.js +414 -0
- package/src/components/exprViz/ParallelCoordsPlot.js +314 -0
- package/src/components/exprViz/styles.css +316 -0
- package/src/components/geneSearchUI.js +2 -0
package/dist/index.js
CHANGED
|
@@ -31,6 +31,7 @@ var $gXNCa$reacticonsai = require("react-icons/ai");
|
|
|
31
31
|
var $gXNCa$gramenesearchvis = require("gramene-search-vis");
|
|
32
32
|
var $gXNCa$numeral = require("numeral");
|
|
33
33
|
var $gXNCa$firebaseauth = require("firebase/auth");
|
|
34
|
+
var $gXNCa$d3 = require("d3");
|
|
34
35
|
|
|
35
36
|
|
|
36
37
|
function $parcel$defineInteropFlag(a) {
|
|
@@ -1420,12 +1421,6 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1420
1421
|
show: 'off',
|
|
1421
1422
|
shouldScroll: false
|
|
1422
1423
|
},
|
|
1423
|
-
{
|
|
1424
|
-
id: 'userLists',
|
|
1425
|
-
name: 'User Gene Lists',
|
|
1426
|
-
show: 'off',
|
|
1427
|
-
shouldScroll: false
|
|
1428
|
-
},
|
|
1429
1424
|
{
|
|
1430
1425
|
id: 'taxonomy',
|
|
1431
1426
|
name: 'Taxonomic distribution',
|
|
@@ -1439,15 +1434,14 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1439
1434
|
shouldScroll: false
|
|
1440
1435
|
},
|
|
1441
1436
|
{
|
|
1442
|
-
id: '
|
|
1443
|
-
name: '
|
|
1437
|
+
id: 'exprViz',
|
|
1438
|
+
name: 'Expression visualization',
|
|
1444
1439
|
show: 'off',
|
|
1445
|
-
shouldScroll: false
|
|
1446
|
-
desiredSamples: {}
|
|
1440
|
+
shouldScroll: false
|
|
1447
1441
|
},
|
|
1448
1442
|
{
|
|
1449
|
-
id: '
|
|
1450
|
-
name: 'Gene
|
|
1443
|
+
id: 'userLists',
|
|
1444
|
+
name: 'User Gene Lists',
|
|
1451
1445
|
show: 'off',
|
|
1452
1446
|
shouldScroll: false
|
|
1453
1447
|
},
|
|
@@ -1456,6 +1450,19 @@ const $24971af0a229e0e3$var$grameneViews = {
|
|
|
1456
1450
|
name: 'Data exporter',
|
|
1457
1451
|
show: 'off',
|
|
1458
1452
|
shouldScroll: false
|
|
1453
|
+
},
|
|
1454
|
+
{
|
|
1455
|
+
id: 'expression',
|
|
1456
|
+
name: 'Gene expression',
|
|
1457
|
+
show: 'off',
|
|
1458
|
+
shouldScroll: false,
|
|
1459
|
+
desiredSamples: {}
|
|
1460
|
+
},
|
|
1461
|
+
{
|
|
1462
|
+
id: 'attribs',
|
|
1463
|
+
name: 'Gene attributes',
|
|
1464
|
+
show: 'off',
|
|
1465
|
+
shouldScroll: false
|
|
1459
1466
|
}
|
|
1460
1467
|
],
|
|
1461
1468
|
touched: {}
|
|
@@ -3831,6 +3838,574 @@ const $c921a0d2b34aadb6$var$ontologies = {
|
|
|
3831
3838
|
var $c921a0d2b34aadb6$export$2e2bcd8739ae039 = $c921a0d2b34aadb6$var$ontologies;
|
|
3832
3839
|
|
|
3833
3840
|
|
|
3841
|
+
|
|
3842
|
+
// State shape:
|
|
3843
|
+
// {
|
|
3844
|
+
// pivot: { status, signature, data: { [taxon_id]: <gene count> }, error, requestId },
|
|
3845
|
+
// activeTaxon: <taxon_id|null>,
|
|
3846
|
+
// byTaxon: {
|
|
3847
|
+
// [taxon_id]: {
|
|
3848
|
+
// selectedFields: [<solr field name>...],
|
|
3849
|
+
// fieldsModalOpen: <bool>,
|
|
3850
|
+
// fetch: { status, offset, total, signature, requestId, error },
|
|
3851
|
+
// rows: [<doc>...]
|
|
3852
|
+
// }
|
|
3853
|
+
// }
|
|
3854
|
+
// }
|
|
3855
|
+
// Per-taxon study lists are derived in the view layer from expressionStudies.
|
|
3856
|
+
const $4f15cd8a7d970b18$var$PAGE_SIZE = 200;
|
|
3857
|
+
let $4f15cd8a7d970b18$var$pivotPendingId = 0;
|
|
3858
|
+
const $4f15cd8a7d970b18$var$fetchPending = {}; // per-taxon request id
|
|
3859
|
+
const $4f15cd8a7d970b18$var$attrsPending = {}; // per-taxon request id for available attrs
|
|
3860
|
+
const $4f15cd8a7d970b18$var$existencePending = {}; // per-taxon request id for field-existence check
|
|
3861
|
+
function $4f15cd8a7d970b18$var$pivotSignature(store) {
|
|
3862
|
+
const q = store.selectGrameneFiltersQueryString();
|
|
3863
|
+
const g = store.selectGrameneGenomes();
|
|
3864
|
+
const m = store.selectGrameneMaps() || {};
|
|
3865
|
+
const taxa = Object.keys(g.active || {}).filter((tid)=>m[tid] && !m[tid].hidden);
|
|
3866
|
+
return `${q}|${taxa.sort().join(',')}`;
|
|
3867
|
+
}
|
|
3868
|
+
function $4f15cd8a7d970b18$var$fetchSignature(store, taxon) {
|
|
3869
|
+
const q = store.selectGrameneFiltersQueryString();
|
|
3870
|
+
const ev = store.selectExprViz();
|
|
3871
|
+
const sel = ev.byTaxon[taxon] && ev.byTaxon[taxon].selectedFields || [];
|
|
3872
|
+
return `${q}|${taxon}|${sel.slice().sort().join(',')}`;
|
|
3873
|
+
}
|
|
3874
|
+
const $4f15cd8a7d970b18$var$ALWAYS_FL = [
|
|
3875
|
+
'id',
|
|
3876
|
+
'name',
|
|
3877
|
+
'system_name',
|
|
3878
|
+
'taxon_id'
|
|
3879
|
+
];
|
|
3880
|
+
const $4f15cd8a7d970b18$var$exprViz = {
|
|
3881
|
+
name: 'exprViz',
|
|
3882
|
+
getReducer: ()=>{
|
|
3883
|
+
const initialState = {
|
|
3884
|
+
pivot: {
|
|
3885
|
+
status: 'idle',
|
|
3886
|
+
signature: null,
|
|
3887
|
+
data: {},
|
|
3888
|
+
error: null,
|
|
3889
|
+
requestId: 0
|
|
3890
|
+
},
|
|
3891
|
+
activeTaxon: null,
|
|
3892
|
+
byTaxon: {}
|
|
3893
|
+
};
|
|
3894
|
+
function ensureTaxon(state, taxon) {
|
|
3895
|
+
if (state.byTaxon[taxon]) return state.byTaxon[taxon];
|
|
3896
|
+
return {
|
|
3897
|
+
selectedFields: [],
|
|
3898
|
+
fieldsModalOpen: false,
|
|
3899
|
+
fetch: {
|
|
3900
|
+
status: 'idle',
|
|
3901
|
+
offset: 0,
|
|
3902
|
+
total: 0,
|
|
3903
|
+
signature: null,
|
|
3904
|
+
requestId: 0,
|
|
3905
|
+
error: null
|
|
3906
|
+
},
|
|
3907
|
+
rows: [],
|
|
3908
|
+
availableAttrs: null,
|
|
3909
|
+
attrsSignature: null,
|
|
3910
|
+
attrsRequestId: 0,
|
|
3911
|
+
fieldExistence: null,
|
|
3912
|
+
existenceStatus: 'idle',
|
|
3913
|
+
existenceSignature: null,
|
|
3914
|
+
existenceRequestId: 0
|
|
3915
|
+
};
|
|
3916
|
+
}
|
|
3917
|
+
return (state = initialState, { type: type, payload: payload })=>{
|
|
3918
|
+
switch(type){
|
|
3919
|
+
case 'EXPRVIZ_PIVOT_STARTED':
|
|
3920
|
+
return {
|
|
3921
|
+
...state,
|
|
3922
|
+
pivot: {
|
|
3923
|
+
status: 'loading',
|
|
3924
|
+
signature: payload.signature,
|
|
3925
|
+
data: state.pivot.data,
|
|
3926
|
+
error: null,
|
|
3927
|
+
requestId: payload.requestId
|
|
3928
|
+
}
|
|
3929
|
+
};
|
|
3930
|
+
case 'EXPRVIZ_PIVOT_SUCCEEDED':
|
|
3931
|
+
if (payload.requestId !== state.pivot.requestId) return state;
|
|
3932
|
+
return {
|
|
3933
|
+
...state,
|
|
3934
|
+
pivot: {
|
|
3935
|
+
status: 'ready',
|
|
3936
|
+
signature: state.pivot.signature,
|
|
3937
|
+
data: payload.data,
|
|
3938
|
+
error: null,
|
|
3939
|
+
requestId: payload.requestId
|
|
3940
|
+
}
|
|
3941
|
+
};
|
|
3942
|
+
case 'EXPRVIZ_PIVOT_FAILED':
|
|
3943
|
+
if (payload.requestId !== state.pivot.requestId) return state;
|
|
3944
|
+
return {
|
|
3945
|
+
...state,
|
|
3946
|
+
pivot: {
|
|
3947
|
+
...state.pivot,
|
|
3948
|
+
status: 'error',
|
|
3949
|
+
error: payload.error
|
|
3950
|
+
}
|
|
3951
|
+
};
|
|
3952
|
+
case 'EXPRVIZ_ACTIVE_TAXON_SET':
|
|
3953
|
+
return {
|
|
3954
|
+
...state,
|
|
3955
|
+
activeTaxon: payload
|
|
3956
|
+
};
|
|
3957
|
+
case 'EXPRVIZ_FIELDS_MODAL_TOGGLED':
|
|
3958
|
+
{
|
|
3959
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
3960
|
+
return {
|
|
3961
|
+
...state,
|
|
3962
|
+
byTaxon: {
|
|
3963
|
+
...state.byTaxon,
|
|
3964
|
+
[payload.taxon]: {
|
|
3965
|
+
...t,
|
|
3966
|
+
fieldsModalOpen: payload.open
|
|
3967
|
+
}
|
|
3968
|
+
}
|
|
3969
|
+
};
|
|
3970
|
+
}
|
|
3971
|
+
case 'EXPRVIZ_FIELDS_SET':
|
|
3972
|
+
{
|
|
3973
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
3974
|
+
return {
|
|
3975
|
+
...state,
|
|
3976
|
+
byTaxon: {
|
|
3977
|
+
...state.byTaxon,
|
|
3978
|
+
[payload.taxon]: {
|
|
3979
|
+
...t,
|
|
3980
|
+
selectedFields: payload.fields,
|
|
3981
|
+
rows: [],
|
|
3982
|
+
fetch: {
|
|
3983
|
+
status: 'idle',
|
|
3984
|
+
offset: 0,
|
|
3985
|
+
total: 0,
|
|
3986
|
+
signature: null,
|
|
3987
|
+
requestId: 0,
|
|
3988
|
+
error: null
|
|
3989
|
+
}
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
};
|
|
3993
|
+
}
|
|
3994
|
+
case 'EXPRVIZ_FIELDS_REORDERED':
|
|
3995
|
+
{
|
|
3996
|
+
const t = state.byTaxon[payload.taxon];
|
|
3997
|
+
if (!t) return state;
|
|
3998
|
+
// Same set of fields — preserve rows; only the column/axis order changes.
|
|
3999
|
+
const setA = new Set(t.selectedFields);
|
|
4000
|
+
const setB = new Set(payload.fields);
|
|
4001
|
+
if (setA.size !== setB.size || ![
|
|
4002
|
+
...setA
|
|
4003
|
+
].every((f)=>setB.has(f))) return state;
|
|
4004
|
+
return {
|
|
4005
|
+
...state,
|
|
4006
|
+
byTaxon: {
|
|
4007
|
+
...state.byTaxon,
|
|
4008
|
+
[payload.taxon]: {
|
|
4009
|
+
...t,
|
|
4010
|
+
selectedFields: payload.fields
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
case 'EXPRVIZ_FETCH_STARTED':
|
|
4016
|
+
{
|
|
4017
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
4018
|
+
return {
|
|
4019
|
+
...state,
|
|
4020
|
+
byTaxon: {
|
|
4021
|
+
...state.byTaxon,
|
|
4022
|
+
[payload.taxon]: {
|
|
4023
|
+
...t,
|
|
4024
|
+
fetch: {
|
|
4025
|
+
...t.fetch,
|
|
4026
|
+
status: 'loading',
|
|
4027
|
+
signature: payload.signature,
|
|
4028
|
+
requestId: payload.requestId,
|
|
4029
|
+
error: null
|
|
4030
|
+
}
|
|
4031
|
+
}
|
|
4032
|
+
}
|
|
4033
|
+
};
|
|
4034
|
+
}
|
|
4035
|
+
case 'EXPRVIZ_FETCH_BATCH':
|
|
4036
|
+
{
|
|
4037
|
+
const t = state.byTaxon[payload.taxon];
|
|
4038
|
+
if (!t || payload.requestId !== t.fetch.requestId) return state;
|
|
4039
|
+
const rows = t.rows.concat(payload.docs);
|
|
4040
|
+
return {
|
|
4041
|
+
...state,
|
|
4042
|
+
byTaxon: {
|
|
4043
|
+
...state.byTaxon,
|
|
4044
|
+
[payload.taxon]: {
|
|
4045
|
+
...t,
|
|
4046
|
+
rows: rows,
|
|
4047
|
+
fetch: {
|
|
4048
|
+
...t.fetch,
|
|
4049
|
+
offset: rows.length,
|
|
4050
|
+
total: payload.total,
|
|
4051
|
+
status: rows.length >= payload.total ? 'done' : 'loading'
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
}
|
|
4055
|
+
};
|
|
4056
|
+
}
|
|
4057
|
+
case 'EXPRVIZ_FETCH_FAILED':
|
|
4058
|
+
{
|
|
4059
|
+
const t = state.byTaxon[payload.taxon];
|
|
4060
|
+
if (!t || payload.requestId !== t.fetch.requestId) return state;
|
|
4061
|
+
return {
|
|
4062
|
+
...state,
|
|
4063
|
+
byTaxon: {
|
|
4064
|
+
...state.byTaxon,
|
|
4065
|
+
[payload.taxon]: {
|
|
4066
|
+
...t,
|
|
4067
|
+
fetch: {
|
|
4068
|
+
...t.fetch,
|
|
4069
|
+
status: 'error',
|
|
4070
|
+
error: payload.error
|
|
4071
|
+
}
|
|
4072
|
+
}
|
|
4073
|
+
}
|
|
4074
|
+
};
|
|
4075
|
+
}
|
|
4076
|
+
case 'EXPRVIZ_ATTRS_STARTED':
|
|
4077
|
+
{
|
|
4078
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
4079
|
+
return {
|
|
4080
|
+
...state,
|
|
4081
|
+
byTaxon: {
|
|
4082
|
+
...state.byTaxon,
|
|
4083
|
+
[payload.taxon]: {
|
|
4084
|
+
...t,
|
|
4085
|
+
attrsRequestId: payload.requestId,
|
|
4086
|
+
attrsSignature: payload.signature
|
|
4087
|
+
}
|
|
4088
|
+
}
|
|
4089
|
+
};
|
|
4090
|
+
}
|
|
4091
|
+
case 'EXPRVIZ_ATTRS_SUCCEEDED':
|
|
4092
|
+
{
|
|
4093
|
+
const t = state.byTaxon[payload.taxon];
|
|
4094
|
+
if (!t || payload.requestId !== t.attrsRequestId) return state;
|
|
4095
|
+
return {
|
|
4096
|
+
...state,
|
|
4097
|
+
byTaxon: {
|
|
4098
|
+
...state.byTaxon,
|
|
4099
|
+
[payload.taxon]: {
|
|
4100
|
+
...t,
|
|
4101
|
+
availableAttrs: payload.attrs
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
};
|
|
4105
|
+
}
|
|
4106
|
+
case 'EXPRVIZ_EXISTENCE_STARTED':
|
|
4107
|
+
{
|
|
4108
|
+
const t = ensureTaxon(state, payload.taxon);
|
|
4109
|
+
return {
|
|
4110
|
+
...state,
|
|
4111
|
+
byTaxon: {
|
|
4112
|
+
...state.byTaxon,
|
|
4113
|
+
[payload.taxon]: {
|
|
4114
|
+
...t,
|
|
4115
|
+
existenceStatus: 'loading',
|
|
4116
|
+
existenceRequestId: payload.requestId,
|
|
4117
|
+
existenceSignature: payload.signature
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
case 'EXPRVIZ_EXISTENCE_SUCCEEDED':
|
|
4123
|
+
{
|
|
4124
|
+
const t = state.byTaxon[payload.taxon];
|
|
4125
|
+
if (!t || payload.requestId !== t.existenceRequestId) return state;
|
|
4126
|
+
return {
|
|
4127
|
+
...state,
|
|
4128
|
+
byTaxon: {
|
|
4129
|
+
...state.byTaxon,
|
|
4130
|
+
[payload.taxon]: {
|
|
4131
|
+
...t,
|
|
4132
|
+
existenceStatus: 'ready',
|
|
4133
|
+
fieldExistence: payload.counts
|
|
4134
|
+
}
|
|
4135
|
+
}
|
|
4136
|
+
};
|
|
4137
|
+
}
|
|
4138
|
+
case 'GRAMENE_SEARCH_CLEARED':
|
|
4139
|
+
{
|
|
4140
|
+
// Search context changed — invalidate pivot and any loaded rows.
|
|
4141
|
+
// Selected fields are kept so the user can re-run the load.
|
|
4142
|
+
const newByTaxon = {};
|
|
4143
|
+
for (const tid of Object.keys(state.byTaxon))newByTaxon[tid] = {
|
|
4144
|
+
...state.byTaxon[tid],
|
|
4145
|
+
rows: [],
|
|
4146
|
+
fetch: {
|
|
4147
|
+
status: 'idle',
|
|
4148
|
+
offset: 0,
|
|
4149
|
+
total: 0,
|
|
4150
|
+
signature: null,
|
|
4151
|
+
requestId: 0,
|
|
4152
|
+
error: null
|
|
4153
|
+
},
|
|
4154
|
+
availableAttrs: null,
|
|
4155
|
+
attrsSignature: null,
|
|
4156
|
+
fieldExistence: null,
|
|
4157
|
+
existenceStatus: 'idle',
|
|
4158
|
+
existenceSignature: null
|
|
4159
|
+
};
|
|
4160
|
+
return {
|
|
4161
|
+
...state,
|
|
4162
|
+
pivot: {
|
|
4163
|
+
status: 'idle',
|
|
4164
|
+
signature: null,
|
|
4165
|
+
data: {},
|
|
4166
|
+
error: null,
|
|
4167
|
+
requestId: 0
|
|
4168
|
+
},
|
|
4169
|
+
byTaxon: newByTaxon
|
|
4170
|
+
};
|
|
4171
|
+
}
|
|
4172
|
+
default:
|
|
4173
|
+
return state;
|
|
4174
|
+
}
|
|
4175
|
+
};
|
|
4176
|
+
},
|
|
4177
|
+
doFetchExprVizPivot: ()=>({ dispatch: dispatch, store: store })=>{
|
|
4178
|
+
const requestId = ++$4f15cd8a7d970b18$var$pivotPendingId;
|
|
4179
|
+
const signature = $4f15cd8a7d970b18$var$pivotSignature(store);
|
|
4180
|
+
dispatch({
|
|
4181
|
+
type: 'EXPRVIZ_PIVOT_STARTED',
|
|
4182
|
+
payload: {
|
|
4183
|
+
requestId: requestId,
|
|
4184
|
+
signature: signature
|
|
4185
|
+
}
|
|
4186
|
+
});
|
|
4187
|
+
const api = store.selectGrameneAPI();
|
|
4188
|
+
const q = store.selectGrameneFiltersQueryString();
|
|
4189
|
+
const g = store.selectGrameneGenomes();
|
|
4190
|
+
const m = store.selectGrameneMaps() || {};
|
|
4191
|
+
const taxa = Object.keys(g.active || {}).filter((tid)=>m[tid] && !m[tid].hidden);
|
|
4192
|
+
const fq = taxa.length ? `&fq=taxon_id:(${taxa.join(' OR ')})` : '';
|
|
4193
|
+
const facetField = "{!facet.limit='300' facet.mincount='1' key='taxon_id'}taxon_id";
|
|
4194
|
+
const url = `${api}/search?q=${q}${fq}&fq=expressed_in_gxa_attr_ss:*&rows=0&facet=true&facet.field=${facetField}`;
|
|
4195
|
+
fetch(url).then((r)=>r.json()).then((json)=>{
|
|
4196
|
+
const arr = json && json.facet_counts && json.facet_counts.facet_fields && json.facet_counts.facet_fields.taxon_id || [];
|
|
4197
|
+
const data = {};
|
|
4198
|
+
for(let i = 0; i < arr.length; i += 2)data[arr[i]] = arr[i + 1];
|
|
4199
|
+
if (Object.keys(data).length === 0) console.warn('[exprViz] no taxon_ids found with expression', {
|
|
4200
|
+
url: url,
|
|
4201
|
+
json: json
|
|
4202
|
+
});
|
|
4203
|
+
dispatch({
|
|
4204
|
+
type: 'EXPRVIZ_PIVOT_SUCCEEDED',
|
|
4205
|
+
payload: {
|
|
4206
|
+
requestId: requestId,
|
|
4207
|
+
data: data
|
|
4208
|
+
}
|
|
4209
|
+
});
|
|
4210
|
+
}).catch((err)=>{
|
|
4211
|
+
dispatch({
|
|
4212
|
+
type: 'EXPRVIZ_PIVOT_FAILED',
|
|
4213
|
+
payload: {
|
|
4214
|
+
requestId: requestId,
|
|
4215
|
+
error: String(err)
|
|
4216
|
+
}
|
|
4217
|
+
});
|
|
4218
|
+
});
|
|
4219
|
+
},
|
|
4220
|
+
doSetExprVizActiveTaxon: (tid)=>({ dispatch: dispatch })=>dispatch({
|
|
4221
|
+
type: 'EXPRVIZ_ACTIVE_TAXON_SET',
|
|
4222
|
+
payload: tid
|
|
4223
|
+
}),
|
|
4224
|
+
doToggleExprVizFieldsModal: (taxon, open)=>({ dispatch: dispatch, store: store })=>{
|
|
4225
|
+
dispatch({
|
|
4226
|
+
type: 'EXPRVIZ_FIELDS_MODAL_TOGGLED',
|
|
4227
|
+
payload: {
|
|
4228
|
+
taxon: taxon,
|
|
4229
|
+
open: !!open
|
|
4230
|
+
}
|
|
4231
|
+
});
|
|
4232
|
+
if (open) store.doFetchExprVizAvailableAttrs(taxon);
|
|
4233
|
+
},
|
|
4234
|
+
doFetchExprVizAvailableAttrs: (taxon)=>({ dispatch: dispatch, store: store })=>{
|
|
4235
|
+
const q = store.selectGrameneFiltersQueryString();
|
|
4236
|
+
const signature = `${q}|${taxon}`;
|
|
4237
|
+
const ev = store.selectExprViz();
|
|
4238
|
+
const t = ev.byTaxon[taxon];
|
|
4239
|
+
if (t && t.availableAttrs && t.attrsSignature === signature) return;
|
|
4240
|
+
const requestId = $4f15cd8a7d970b18$var$attrsPending[taxon] = ($4f15cd8a7d970b18$var$attrsPending[taxon] || 0) + 1;
|
|
4241
|
+
dispatch({
|
|
4242
|
+
type: 'EXPRVIZ_ATTRS_STARTED',
|
|
4243
|
+
payload: {
|
|
4244
|
+
taxon: taxon,
|
|
4245
|
+
requestId: requestId,
|
|
4246
|
+
signature: signature
|
|
4247
|
+
}
|
|
4248
|
+
});
|
|
4249
|
+
const api = store.selectGrameneAPI();
|
|
4250
|
+
const facetField = "{!facet.limit='2000' facet.mincount='1' key='attrs'}expressed_in_gxa_attr_ss";
|
|
4251
|
+
const url = `${api}/search?q=${q}&fq=taxon_id:${taxon}&fq=expressed_in_gxa_attr_ss:*&rows=0&facet=true&facet.field=${facetField}`;
|
|
4252
|
+
fetch(url).then((r)=>r.json()).then((json)=>{
|
|
4253
|
+
if (requestId !== $4f15cd8a7d970b18$var$attrsPending[taxon]) return;
|
|
4254
|
+
const arr = json && json.facet_counts && json.facet_counts.facet_fields && json.facet_counts.facet_fields.attrs || [];
|
|
4255
|
+
const attrs = [];
|
|
4256
|
+
for(let i = 0; i < arr.length; i += 2)attrs.push(arr[i]);
|
|
4257
|
+
dispatch({
|
|
4258
|
+
type: 'EXPRVIZ_ATTRS_SUCCEEDED',
|
|
4259
|
+
payload: {
|
|
4260
|
+
taxon: taxon,
|
|
4261
|
+
requestId: requestId,
|
|
4262
|
+
attrs: attrs
|
|
4263
|
+
}
|
|
4264
|
+
});
|
|
4265
|
+
}).catch(()=>{});
|
|
4266
|
+
},
|
|
4267
|
+
doSetExprVizFields: (taxon, fields)=>({ dispatch: dispatch })=>dispatch({
|
|
4268
|
+
type: 'EXPRVIZ_FIELDS_SET',
|
|
4269
|
+
payload: {
|
|
4270
|
+
taxon: taxon,
|
|
4271
|
+
fields: fields
|
|
4272
|
+
}
|
|
4273
|
+
}),
|
|
4274
|
+
doReorderExprVizFields: (taxon, fields)=>({ dispatch: dispatch })=>dispatch({
|
|
4275
|
+
type: 'EXPRVIZ_FIELDS_REORDERED',
|
|
4276
|
+
payload: {
|
|
4277
|
+
taxon: taxon,
|
|
4278
|
+
fields: fields
|
|
4279
|
+
}
|
|
4280
|
+
}),
|
|
4281
|
+
doFetchExprVizData: (taxon)=>({ dispatch: dispatch, store: store })=>{
|
|
4282
|
+
const ev = store.selectExprViz();
|
|
4283
|
+
const t = ev.byTaxon[taxon];
|
|
4284
|
+
if (!t || t.selectedFields.length === 0) return;
|
|
4285
|
+
const requestId = $4f15cd8a7d970b18$var$fetchPending[taxon] = ($4f15cd8a7d970b18$var$fetchPending[taxon] || 0) + 1;
|
|
4286
|
+
const signature = $4f15cd8a7d970b18$var$fetchSignature(store, taxon);
|
|
4287
|
+
dispatch({
|
|
4288
|
+
type: 'EXPRVIZ_FETCH_STARTED',
|
|
4289
|
+
payload: {
|
|
4290
|
+
taxon: taxon,
|
|
4291
|
+
requestId: requestId,
|
|
4292
|
+
signature: signature
|
|
4293
|
+
}
|
|
4294
|
+
});
|
|
4295
|
+
const api = store.selectGrameneAPI();
|
|
4296
|
+
const q = store.selectGrameneFiltersQueryString();
|
|
4297
|
+
const fl = [
|
|
4298
|
+
...new Set([
|
|
4299
|
+
...$4f15cd8a7d970b18$var$ALWAYS_FL,
|
|
4300
|
+
...t.selectedFields
|
|
4301
|
+
])
|
|
4302
|
+
].join(',');
|
|
4303
|
+
const fetchPage = (offset)=>{
|
|
4304
|
+
if (requestId !== $4f15cd8a7d970b18$var$fetchPending[taxon]) return; // superseded
|
|
4305
|
+
const url = `${api}/search?q=${q}&fq=taxon_id:${taxon}&fl=${fl}&rows=${$4f15cd8a7d970b18$var$PAGE_SIZE}&start=${offset}`;
|
|
4306
|
+
fetch(url).then((r)=>r.json()).then((json)=>{
|
|
4307
|
+
if (requestId !== $4f15cd8a7d970b18$var$fetchPending[taxon]) return;
|
|
4308
|
+
const docs = json.response && json.response.docs || [];
|
|
4309
|
+
const total = json.response && json.response.numFound || 0;
|
|
4310
|
+
dispatch({
|
|
4311
|
+
type: 'EXPRVIZ_FETCH_BATCH',
|
|
4312
|
+
payload: {
|
|
4313
|
+
taxon: taxon,
|
|
4314
|
+
requestId: requestId,
|
|
4315
|
+
docs: docs,
|
|
4316
|
+
total: total
|
|
4317
|
+
}
|
|
4318
|
+
});
|
|
4319
|
+
const next = offset + docs.length;
|
|
4320
|
+
if (docs.length > 0 && next < total) fetchPage(next);
|
|
4321
|
+
}).catch((err)=>{
|
|
4322
|
+
dispatch({
|
|
4323
|
+
type: 'EXPRVIZ_FETCH_FAILED',
|
|
4324
|
+
payload: {
|
|
4325
|
+
taxon: taxon,
|
|
4326
|
+
requestId: requestId,
|
|
4327
|
+
error: String(err)
|
|
4328
|
+
}
|
|
4329
|
+
});
|
|
4330
|
+
});
|
|
4331
|
+
};
|
|
4332
|
+
fetchPage(0);
|
|
4333
|
+
},
|
|
4334
|
+
// Per-candidate-field non-null counts under the current q + taxon. Used by
|
|
4335
|
+
// FieldsModal to hide fields that have no data in the current result set
|
|
4336
|
+
// before the user picks. Uses stats.field (the swagger whitelist drops
|
|
4337
|
+
// facet.query) batched across multiple GETs to stay under URL limits.
|
|
4338
|
+
doFetchExprVizFieldExistence: (taxon, fields)=>({ dispatch: dispatch, store: store })=>{
|
|
4339
|
+
if (!fields || fields.length === 0) return;
|
|
4340
|
+
const q = store.selectGrameneFiltersQueryString();
|
|
4341
|
+
const fieldsKey = fields.slice().sort().join(',');
|
|
4342
|
+
const signature = `${q}|${taxon}|${fieldsKey}`;
|
|
4343
|
+
const ev = store.selectExprViz();
|
|
4344
|
+
const t = ev.byTaxon[taxon];
|
|
4345
|
+
if (t && t.fieldExistence && t.existenceSignature === signature) return;
|
|
4346
|
+
const requestId = $4f15cd8a7d970b18$var$existencePending[taxon] = ($4f15cd8a7d970b18$var$existencePending[taxon] || 0) + 1;
|
|
4347
|
+
dispatch({
|
|
4348
|
+
type: 'EXPRVIZ_EXISTENCE_STARTED',
|
|
4349
|
+
payload: {
|
|
4350
|
+
taxon: taxon,
|
|
4351
|
+
requestId: requestId,
|
|
4352
|
+
signature: signature
|
|
4353
|
+
}
|
|
4354
|
+
});
|
|
4355
|
+
const api = store.selectGrameneAPI();
|
|
4356
|
+
const BATCH_SIZE = 40;
|
|
4357
|
+
const batches = [];
|
|
4358
|
+
for(let i = 0; i < fields.length; i += BATCH_SIZE)batches.push(fields.slice(i, i + BATCH_SIZE));
|
|
4359
|
+
Promise.all(batches.map((batch)=>{
|
|
4360
|
+
const params = new URLSearchParams();
|
|
4361
|
+
params.append('q', q);
|
|
4362
|
+
params.append('fq', `taxon_id:${taxon}`);
|
|
4363
|
+
params.append('rows', '0');
|
|
4364
|
+
params.append('stats', 'true');
|
|
4365
|
+
batch.forEach((f)=>params.append('stats.field', f));
|
|
4366
|
+
return fetch(`${api}/search?${params.toString()}`).then((r)=>r.json()).then((json)=>{
|
|
4367
|
+
const sf = json && json.stats && json.stats.stats_fields || {};
|
|
4368
|
+
const out = {};
|
|
4369
|
+
for (const f of Object.keys(sf)){
|
|
4370
|
+
const s = sf[f];
|
|
4371
|
+
out[f] = s && typeof s.count === 'number' ? s.count : 0;
|
|
4372
|
+
}
|
|
4373
|
+
return out;
|
|
4374
|
+
});
|
|
4375
|
+
})).then((results)=>{
|
|
4376
|
+
if (requestId !== $4f15cd8a7d970b18$var$existencePending[taxon]) return;
|
|
4377
|
+
const counts = Object.assign({}, ...results);
|
|
4378
|
+
dispatch({
|
|
4379
|
+
type: 'EXPRVIZ_EXISTENCE_SUCCEEDED',
|
|
4380
|
+
payload: {
|
|
4381
|
+
taxon: taxon,
|
|
4382
|
+
requestId: requestId,
|
|
4383
|
+
counts: counts
|
|
4384
|
+
}
|
|
4385
|
+
});
|
|
4386
|
+
}).catch(()=>{});
|
|
4387
|
+
},
|
|
4388
|
+
reactExprVizPivot: (0, $gXNCa$reduxbundler.createSelector)('selectExprViz', 'selectGrameneFiltersStatus', 'selectGrameneViews', 'selectGrameneFiltersQueryString', 'selectGrameneGenomes', 'selectGrameneMaps', (ev, filtersStatus, views, q, g, m)=>{
|
|
4389
|
+
if (!ev || filtersStatus === 'init') return;
|
|
4390
|
+
const onView = views && views.options && views.options.find((v)=>v.id === 'exprViz');
|
|
4391
|
+
if (!onView || onView.show !== 'on') return;
|
|
4392
|
+
const maps = m || {};
|
|
4393
|
+
const taxa = Object.keys(g && g.active || {}).filter((tid)=>maps[tid] && !maps[tid].hidden);
|
|
4394
|
+
const sig = `${q}|${taxa.sort().join(',')}`;
|
|
4395
|
+
if (ev.pivot.status === 'loading') return;
|
|
4396
|
+
if (ev.pivot.signature === sig && ev.pivot.status === 'ready') return;
|
|
4397
|
+
return {
|
|
4398
|
+
actionCreator: 'doFetchExprVizPivot'
|
|
4399
|
+
};
|
|
4400
|
+
}),
|
|
4401
|
+
selectExprViz: (state)=>state.exprViz,
|
|
4402
|
+
selectExprVizPivot: (state)=>state.exprViz.pivot,
|
|
4403
|
+
selectExprVizActiveTaxon: (state)=>state.exprViz.activeTaxon,
|
|
4404
|
+
selectExprVizByTaxon: (state)=>state.exprViz.byTaxon
|
|
4405
|
+
};
|
|
4406
|
+
var $4f15cd8a7d970b18$export$2e2bcd8739ae039 = $4f15cd8a7d970b18$var$exprViz;
|
|
4407
|
+
|
|
4408
|
+
|
|
3834
4409
|
var $5df6c55c1bef3469$export$2e2bcd8739ae039 = [
|
|
3835
4410
|
...(0, $9d9aeaf9299e61a1$export$2e2bcd8739ae039),
|
|
3836
4411
|
(0, $671312b287158a8a$export$2e2bcd8739ae039),
|
|
@@ -3839,7 +4414,8 @@ var $5df6c55c1bef3469$export$2e2bcd8739ae039 = [
|
|
|
3839
4414
|
(0, $0d54502f6cafe273$export$2e2bcd8739ae039),
|
|
3840
4415
|
(0, $0f839422d0d8c772$export$2e2bcd8739ae039),
|
|
3841
4416
|
(0, $1508f5a42be6e7b5$export$2e2bcd8739ae039),
|
|
3842
|
-
(0, $c921a0d2b34aadb6$export$2e2bcd8739ae039)
|
|
4417
|
+
(0, $c921a0d2b34aadb6$export$2e2bcd8739ae039),
|
|
4418
|
+
(0, $4f15cd8a7d970b18$export$2e2bcd8739ae039)
|
|
3843
4419
|
];
|
|
3844
4420
|
|
|
3845
4421
|
|
|
@@ -10203,6 +10779,1438 @@ var $37b3bb0145d266b0$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.conn
|
|
|
10203
10779
|
|
|
10204
10780
|
|
|
10205
10781
|
|
|
10782
|
+
|
|
10783
|
+
function $4d0c2e01f58b53b1$var$speciesTaxonId(tid) {
|
|
10784
|
+
const n = +tid;
|
|
10785
|
+
return n > 1000000 ? Math.floor(n / 1000) : n;
|
|
10786
|
+
}
|
|
10787
|
+
const $4d0c2e01f58b53b1$var$EXPERIMENT_KEYS = [
|
|
10788
|
+
{
|
|
10789
|
+
key: 'exp:study',
|
|
10790
|
+
label: 'Study'
|
|
10791
|
+
},
|
|
10792
|
+
{
|
|
10793
|
+
key: 'exp:type',
|
|
10794
|
+
label: 'Study type'
|
|
10795
|
+
},
|
|
10796
|
+
{
|
|
10797
|
+
key: 'exp:organism',
|
|
10798
|
+
label: 'Organism'
|
|
10799
|
+
}
|
|
10800
|
+
];
|
|
10801
|
+
// One record per expression field (one sample group within one study).
|
|
10802
|
+
// `props` is a flat map of property-key → value used for filtering and display.
|
|
10803
|
+
function $4d0c2e01f58b53b1$var$buildFieldRecords(taxon, studyIds, expressionStudies, expressionSamples, fieldCatalog, grameneMaps) {
|
|
10804
|
+
const records = [];
|
|
10805
|
+
const factorTypes = new Set();
|
|
10806
|
+
const charTypes = new Set();
|
|
10807
|
+
if (!expressionSamples || !expressionStudies) return {
|
|
10808
|
+
records: records,
|
|
10809
|
+
factorTypes: [],
|
|
10810
|
+
charTypes: []
|
|
10811
|
+
};
|
|
10812
|
+
const studyById = {};
|
|
10813
|
+
const list = expressionStudies[taxon] || expressionStudies[$4d0c2e01f58b53b1$var$speciesTaxonId(taxon)] || [];
|
|
10814
|
+
list.forEach((s)=>{
|
|
10815
|
+
studyById[s._id] = s;
|
|
10816
|
+
});
|
|
10817
|
+
for (const studyId of studyIds){
|
|
10818
|
+
const study = studyById[studyId];
|
|
10819
|
+
if (!study) continue;
|
|
10820
|
+
const samples = expressionSamples[studyId];
|
|
10821
|
+
if (!samples) continue;
|
|
10822
|
+
const byGroup = {};
|
|
10823
|
+
for (const s of samples)if (!byGroup[s.group]) byGroup[s.group] = s;
|
|
10824
|
+
for (const group of Object.keys(byGroup)){
|
|
10825
|
+
const sample = byGroup[group];
|
|
10826
|
+
const fieldName = `${studyId.replace(/-/g, '_')}_${group}__expr`;
|
|
10827
|
+
if (fieldCatalog && fieldCatalog.fields && !fieldCatalog.fields[fieldName]) continue;
|
|
10828
|
+
const props = {};
|
|
10829
|
+
props['exp:study'] = study.description || studyId;
|
|
10830
|
+
props['exp:type'] = study.type || '(unknown)';
|
|
10831
|
+
const taxon_id = study.taxon_id;
|
|
10832
|
+
const taxName = grameneMaps && (grameneMaps[taxon_id] || grameneMaps[$4d0c2e01f58b53b1$var$speciesTaxonId(taxon_id)]);
|
|
10833
|
+
props['exp:organism'] = taxName && taxName.display_name || String(taxon_id);
|
|
10834
|
+
const usedFactorTypes = new Set();
|
|
10835
|
+
(sample.factor || []).forEach((f)=>{
|
|
10836
|
+
props[`fac:${f.type}`] = f.label;
|
|
10837
|
+
factorTypes.add(f.type);
|
|
10838
|
+
usedFactorTypes.add(f.type);
|
|
10839
|
+
});
|
|
10840
|
+
(sample.characteristic || []).forEach((c)=>{
|
|
10841
|
+
if (usedFactorTypes.has(c.type)) return; // factor takes precedence
|
|
10842
|
+
props[`char:${c.type}`] = c.label;
|
|
10843
|
+
charTypes.add(c.type);
|
|
10844
|
+
});
|
|
10845
|
+
records.push({
|
|
10846
|
+
fieldName: fieldName,
|
|
10847
|
+
studyId: studyId,
|
|
10848
|
+
group: group,
|
|
10849
|
+
props: props
|
|
10850
|
+
});
|
|
10851
|
+
}
|
|
10852
|
+
}
|
|
10853
|
+
return {
|
|
10854
|
+
records: records,
|
|
10855
|
+
factorTypes: Array.from(factorTypes).sort(),
|
|
10856
|
+
charTypes: Array.from(charTypes).sort()
|
|
10857
|
+
};
|
|
10858
|
+
}
|
|
10859
|
+
function $4d0c2e01f58b53b1$var$fieldMatches(field, selections, excludeKey) {
|
|
10860
|
+
for (const key of Object.keys(selections)){
|
|
10861
|
+
if (key === excludeKey) continue;
|
|
10862
|
+
const values = selections[key];
|
|
10863
|
+
if (!values || values.size === 0) continue;
|
|
10864
|
+
const v = field.props[key];
|
|
10865
|
+
if (v == null || !values.has(v)) return false;
|
|
10866
|
+
}
|
|
10867
|
+
return true;
|
|
10868
|
+
}
|
|
10869
|
+
function $4d0c2e01f58b53b1$var$valueCounts(records, key, selections) {
|
|
10870
|
+
const counts = new Map();
|
|
10871
|
+
for (const f of records){
|
|
10872
|
+
if (!$4d0c2e01f58b53b1$var$fieldMatches(f, selections, key)) continue;
|
|
10873
|
+
const v = f.props[key];
|
|
10874
|
+
if (v == null) continue;
|
|
10875
|
+
counts.set(v, (counts.get(v) || 0) + 1);
|
|
10876
|
+
}
|
|
10877
|
+
return counts;
|
|
10878
|
+
}
|
|
10879
|
+
function $4d0c2e01f58b53b1$var$labelForKey(key, tree) {
|
|
10880
|
+
for (const grp of tree){
|
|
10881
|
+
for (const t of grp.types)if (t.key === key) return t.label;
|
|
10882
|
+
}
|
|
10883
|
+
return key;
|
|
10884
|
+
}
|
|
10885
|
+
const $4d0c2e01f58b53b1$var$FieldsModalCmp = (props)=>{
|
|
10886
|
+
const { fieldCatalog: fieldCatalog, exprViz: exprViz, expressionStudies: expressionStudies, expressionSamples: expressionSamples, grameneMaps: grameneMaps, doToggleExprVizFieldsModal: doToggleExprVizFieldsModal, doSetExprVizFields: doSetExprVizFields, doFetchExprVizFieldExistence: doFetchExprVizFieldExistence } = props;
|
|
10887
|
+
const taxon = Object.keys(exprViz.byTaxon).find((t)=>exprViz.byTaxon[t].fieldsModalOpen);
|
|
10888
|
+
const open = !!taxon;
|
|
10889
|
+
const availableAttrs = taxon && exprViz.byTaxon[taxon] && exprViz.byTaxon[taxon].availableAttrs;
|
|
10890
|
+
const studyIds = (0, $gXNCa$react.useMemo)(()=>{
|
|
10891
|
+
if (!taxon || !expressionStudies) return [];
|
|
10892
|
+
const list = expressionStudies[taxon] || expressionStudies[$4d0c2e01f58b53b1$var$speciesTaxonId(taxon)] || [];
|
|
10893
|
+
let ids = list.map((s)=>s._id);
|
|
10894
|
+
if (availableAttrs) {
|
|
10895
|
+
const allow = new Set(availableAttrs);
|
|
10896
|
+
ids = ids.filter((id)=>allow.has(id));
|
|
10897
|
+
}
|
|
10898
|
+
return ids;
|
|
10899
|
+
}, [
|
|
10900
|
+
taxon,
|
|
10901
|
+
expressionStudies,
|
|
10902
|
+
availableAttrs
|
|
10903
|
+
]);
|
|
10904
|
+
const allRecords = (0, $gXNCa$react.useMemo)(()=>$4d0c2e01f58b53b1$var$buildFieldRecords(taxon, studyIds, expressionStudies, expressionSamples, fieldCatalog, grameneMaps), [
|
|
10905
|
+
taxon,
|
|
10906
|
+
studyIds,
|
|
10907
|
+
expressionStudies,
|
|
10908
|
+
expressionSamples,
|
|
10909
|
+
fieldCatalog,
|
|
10910
|
+
grameneMaps
|
|
10911
|
+
]);
|
|
10912
|
+
const candidateFields = (0, $gXNCa$react.useMemo)(()=>allRecords.records.map((r)=>r.fieldName), [
|
|
10913
|
+
allRecords
|
|
10914
|
+
]);
|
|
10915
|
+
// Trigger the field-existence facet check whenever the modal is open and
|
|
10916
|
+
// the candidate field list is known. The bundle caches per (q, taxon, fields).
|
|
10917
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
10918
|
+
if (open && taxon && candidateFields.length > 0) doFetchExprVizFieldExistence(taxon, candidateFields);
|
|
10919
|
+
}, [
|
|
10920
|
+
open,
|
|
10921
|
+
taxon,
|
|
10922
|
+
candidateFields,
|
|
10923
|
+
doFetchExprVizFieldExistence
|
|
10924
|
+
]);
|
|
10925
|
+
const fieldExistence = taxon && exprViz.byTaxon[taxon] && exprViz.byTaxon[taxon].fieldExistence;
|
|
10926
|
+
const existenceStatus = taxon && exprViz.byTaxon[taxon] && exprViz.byTaxon[taxon].existenceStatus;
|
|
10927
|
+
// Once existence counts are loaded, drop fields with no data in the current
|
|
10928
|
+
// result set. Until then, render with the full candidate set so the modal
|
|
10929
|
+
// doesn't flash empty during the precheck.
|
|
10930
|
+
const { records: records, factorTypes: factorTypes, charTypes: charTypes } = (0, $gXNCa$react.useMemo)(()=>{
|
|
10931
|
+
if (!fieldExistence) return allRecords;
|
|
10932
|
+
const live = new Set(Object.keys(fieldExistence).filter((f)=>fieldExistence[f] > 0));
|
|
10933
|
+
const filtered = allRecords.records.filter((r)=>live.has(r.fieldName));
|
|
10934
|
+
const factorTypes = new Set();
|
|
10935
|
+
const charTypes = new Set();
|
|
10936
|
+
for (const r of filtered)for (const k of Object.keys(r.props)){
|
|
10937
|
+
if (k.startsWith('fac:')) factorTypes.add(k.slice(4));
|
|
10938
|
+
else if (k.startsWith('char:')) charTypes.add(k.slice(5));
|
|
10939
|
+
}
|
|
10940
|
+
return {
|
|
10941
|
+
records: filtered,
|
|
10942
|
+
factorTypes: Array.from(factorTypes).sort(),
|
|
10943
|
+
charTypes: Array.from(charTypes).sort()
|
|
10944
|
+
};
|
|
10945
|
+
}, [
|
|
10946
|
+
allRecords,
|
|
10947
|
+
fieldExistence
|
|
10948
|
+
]);
|
|
10949
|
+
const propTree = (0, $gXNCa$react.useMemo)(()=>[
|
|
10950
|
+
{
|
|
10951
|
+
group: 'experiment',
|
|
10952
|
+
label: 'Experiment',
|
|
10953
|
+
types: $4d0c2e01f58b53b1$var$EXPERIMENT_KEYS
|
|
10954
|
+
},
|
|
10955
|
+
{
|
|
10956
|
+
group: 'factors',
|
|
10957
|
+
label: 'Factors',
|
|
10958
|
+
types: factorTypes.map((t)=>({
|
|
10959
|
+
key: `fac:${t}`,
|
|
10960
|
+
label: t
|
|
10961
|
+
}))
|
|
10962
|
+
},
|
|
10963
|
+
{
|
|
10964
|
+
group: 'characteristics',
|
|
10965
|
+
label: 'Characteristics',
|
|
10966
|
+
types: charTypes.map((t)=>({
|
|
10967
|
+
key: `char:${t}`,
|
|
10968
|
+
label: t
|
|
10969
|
+
}))
|
|
10970
|
+
}
|
|
10971
|
+
], [
|
|
10972
|
+
factorTypes,
|
|
10973
|
+
charTypes
|
|
10974
|
+
]);
|
|
10975
|
+
const [selections, setSelections] = (0, $gXNCa$react.useState)({});
|
|
10976
|
+
const [expandedKey, setExpandedKey] = (0, $gXNCa$react.useState)(null);
|
|
10977
|
+
const [collapsedGroups, setCollapsedGroups] = (0, $gXNCa$react.useState)({});
|
|
10978
|
+
const [valueSort, setValueSort] = (0, $gXNCa$react.useState)('count');
|
|
10979
|
+
const [orderedSelectedKeys, setOrderedSelectedKeys] = (0, $gXNCa$react.useState)([]);
|
|
10980
|
+
const [searchQuery, setSearchQuery] = (0, $gXNCa$react.useState)('');
|
|
10981
|
+
const searchLc = searchQuery.trim().toLowerCase();
|
|
10982
|
+
const isSearching = searchLc.length > 0;
|
|
10983
|
+
// Distinct values per property type, ignoring the type's own selection but
|
|
10984
|
+
// applying every other selected type. Counts shown next to a property type
|
|
10985
|
+
// reflect "how many distinct values exist among the fields currently
|
|
10986
|
+
// matching the rest of the filter."
|
|
10987
|
+
const valueSetByKey = (0, $gXNCa$react.useMemo)(()=>{
|
|
10988
|
+
const allKeys = new Set();
|
|
10989
|
+
for (const r of records)for (const k of Object.keys(r.props))allKeys.add(k);
|
|
10990
|
+
const m = {};
|
|
10991
|
+
for (const key of allKeys){
|
|
10992
|
+
const set = new Set();
|
|
10993
|
+
for (const r of records){
|
|
10994
|
+
if (!$4d0c2e01f58b53b1$var$fieldMatches(r, selections, key)) continue;
|
|
10995
|
+
const v = r.props[key];
|
|
10996
|
+
if (v != null) set.add(v);
|
|
10997
|
+
}
|
|
10998
|
+
m[key] = set;
|
|
10999
|
+
}
|
|
11000
|
+
return m;
|
|
11001
|
+
}, [
|
|
11002
|
+
records,
|
|
11003
|
+
selections
|
|
11004
|
+
]);
|
|
11005
|
+
// Reset state when modal (re)opens for a taxon.
|
|
11006
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11007
|
+
if (open && taxon) {
|
|
11008
|
+
setSelections({});
|
|
11009
|
+
setExpandedKey(null);
|
|
11010
|
+
setCollapsedGroups({});
|
|
11011
|
+
setOrderedSelectedKeys([]);
|
|
11012
|
+
setSearchQuery('');
|
|
11013
|
+
}
|
|
11014
|
+
}, [
|
|
11015
|
+
open,
|
|
11016
|
+
taxon
|
|
11017
|
+
]);
|
|
11018
|
+
// Keep orderedSelectedKeys in sync with selections (preserves the order in
|
|
11019
|
+
// which the user first selected each property type).
|
|
11020
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11021
|
+
setOrderedSelectedKeys((prev)=>{
|
|
11022
|
+
const active = Object.keys(selections);
|
|
11023
|
+
const activeSet = new Set(active);
|
|
11024
|
+
const kept = prev.filter((k)=>activeSet.has(k));
|
|
11025
|
+
const newOnes = active.filter((k)=>!prev.includes(k));
|
|
11026
|
+
return [
|
|
11027
|
+
...kept,
|
|
11028
|
+
...newOnes
|
|
11029
|
+
];
|
|
11030
|
+
});
|
|
11031
|
+
}, [
|
|
11032
|
+
selections
|
|
11033
|
+
]);
|
|
11034
|
+
const matchingFields = (0, $gXNCa$react.useMemo)(()=>records.filter((r)=>$4d0c2e01f58b53b1$var$fieldMatches(r, selections, null)), [
|
|
11035
|
+
records,
|
|
11036
|
+
selections
|
|
11037
|
+
]);
|
|
11038
|
+
if (!open) return null;
|
|
11039
|
+
const toggleValue = (key, value)=>{
|
|
11040
|
+
setSelections((prev)=>{
|
|
11041
|
+
const next = {
|
|
11042
|
+
...prev
|
|
11043
|
+
};
|
|
11044
|
+
const set = new Set(next[key] || []);
|
|
11045
|
+
if (set.has(value)) set.delete(value);
|
|
11046
|
+
else set.add(value);
|
|
11047
|
+
if (set.size === 0) delete next[key];
|
|
11048
|
+
else next[key] = set;
|
|
11049
|
+
return next;
|
|
11050
|
+
});
|
|
11051
|
+
};
|
|
11052
|
+
const clearTypeSelection = (key)=>{
|
|
11053
|
+
setSelections((prev)=>{
|
|
11054
|
+
if (!prev[key]) return prev;
|
|
11055
|
+
const next = {
|
|
11056
|
+
...prev
|
|
11057
|
+
};
|
|
11058
|
+
delete next[key];
|
|
11059
|
+
return next;
|
|
11060
|
+
});
|
|
11061
|
+
};
|
|
11062
|
+
const toggleGroup = (g)=>setCollapsedGroups((prev)=>({
|
|
11063
|
+
...prev,
|
|
11064
|
+
[g]: !prev[g]
|
|
11065
|
+
}));
|
|
11066
|
+
const renderValues = (key, typeLabelMatches)=>{
|
|
11067
|
+
const counts = $4d0c2e01f58b53b1$var$valueCounts(records, key, selections);
|
|
11068
|
+
let entries = Array.from(counts.entries());
|
|
11069
|
+
if (isSearching && !typeLabelMatches) entries = entries.filter(([v])=>String(v).toLowerCase().includes(searchLc));
|
|
11070
|
+
if (valueSort === 'name') entries.sort((a, b)=>String(a[0]).localeCompare(String(b[0])));
|
|
11071
|
+
else entries.sort((a, b)=>b[1] - a[1] || String(a[0]).localeCompare(String(b[0])));
|
|
11072
|
+
const sel = selections[key] || new Set();
|
|
11073
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11074
|
+
className: "exprviz-tree-values",
|
|
11075
|
+
children: [
|
|
11076
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11077
|
+
className: "exprviz-values-toolbar",
|
|
11078
|
+
children: [
|
|
11079
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.ToggleButtonGroup), {
|
|
11080
|
+
type: "radio",
|
|
11081
|
+
name: `exprviz-vsort-${key}`,
|
|
11082
|
+
size: "sm",
|
|
11083
|
+
value: valueSort,
|
|
11084
|
+
onChange: setValueSort,
|
|
11085
|
+
children: [
|
|
11086
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.ToggleButton), {
|
|
11087
|
+
id: `exprviz-vsort-name-${key}`,
|
|
11088
|
+
value: "name",
|
|
11089
|
+
variant: "outline-secondary",
|
|
11090
|
+
children: "Name"
|
|
11091
|
+
}),
|
|
11092
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.ToggleButton), {
|
|
11093
|
+
id: `exprviz-vsort-count-${key}`,
|
|
11094
|
+
value: "count",
|
|
11095
|
+
variant: "outline-secondary",
|
|
11096
|
+
children: "Count"
|
|
11097
|
+
})
|
|
11098
|
+
]
|
|
11099
|
+
}),
|
|
11100
|
+
sel.size > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
11101
|
+
size: "sm",
|
|
11102
|
+
variant: "link",
|
|
11103
|
+
onClick: ()=>clearTypeSelection(key),
|
|
11104
|
+
children: "clear"
|
|
11105
|
+
})
|
|
11106
|
+
]
|
|
11107
|
+
}),
|
|
11108
|
+
entries.map(([v, c])=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("label", {
|
|
11109
|
+
className: "exprviz-tree-value",
|
|
11110
|
+
children: [
|
|
11111
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("input", {
|
|
11112
|
+
type: "checkbox",
|
|
11113
|
+
checked: sel.has(v),
|
|
11114
|
+
onChange: ()=>toggleValue(key, v)
|
|
11115
|
+
}),
|
|
11116
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11117
|
+
className: "exprviz-tree-value-label",
|
|
11118
|
+
title: v,
|
|
11119
|
+
children: v
|
|
11120
|
+
}),
|
|
11121
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11122
|
+
className: "exprviz-tree-value-count",
|
|
11123
|
+
children: c
|
|
11124
|
+
})
|
|
11125
|
+
]
|
|
11126
|
+
}, v)),
|
|
11127
|
+
entries.length === 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11128
|
+
className: "exprviz-tree-empty",
|
|
11129
|
+
children: "No values"
|
|
11130
|
+
})
|
|
11131
|
+
]
|
|
11132
|
+
});
|
|
11133
|
+
};
|
|
11134
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Modal), {
|
|
11135
|
+
show: open,
|
|
11136
|
+
onHide: ()=>doToggleExprVizFieldsModal(taxon, false),
|
|
11137
|
+
size: "xl",
|
|
11138
|
+
scrollable: true,
|
|
11139
|
+
children: [
|
|
11140
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Modal).Header, {
|
|
11141
|
+
closeButton: true,
|
|
11142
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Modal).Title, {
|
|
11143
|
+
children: [
|
|
11144
|
+
"Filter expression fields (",
|
|
11145
|
+
matchingFields.length,
|
|
11146
|
+
" of ",
|
|
11147
|
+
records.length,
|
|
11148
|
+
" match)",
|
|
11149
|
+
existenceStatus === 'loading' && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11150
|
+
className: "exprviz-modal-loading",
|
|
11151
|
+
children: " \xb7 checking field availability\u2026"
|
|
11152
|
+
})
|
|
11153
|
+
]
|
|
11154
|
+
})
|
|
11155
|
+
}),
|
|
11156
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Modal).Body, {
|
|
11157
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11158
|
+
className: "exprviz-fields-layout",
|
|
11159
|
+
children: [
|
|
11160
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11161
|
+
className: "exprviz-tree",
|
|
11162
|
+
children: [
|
|
11163
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("input", {
|
|
11164
|
+
type: "text",
|
|
11165
|
+
className: "exprviz-tree-search",
|
|
11166
|
+
placeholder: "Search property types and values\u2026",
|
|
11167
|
+
value: searchQuery,
|
|
11168
|
+
onChange: (e)=>setSearchQuery(e.target.value)
|
|
11169
|
+
}),
|
|
11170
|
+
propTree.map((grp)=>{
|
|
11171
|
+
const typeRows = grp.types.map((t)=>{
|
|
11172
|
+
const numValues = valueSetByKey[t.key] && valueSetByKey[t.key].size || 0;
|
|
11173
|
+
if (numValues === 0) return null;
|
|
11174
|
+
const typeLabelMatches = isSearching && t.label.toLowerCase().includes(searchLc);
|
|
11175
|
+
let matchingValueCount = numValues;
|
|
11176
|
+
if (isSearching && !typeLabelMatches) {
|
|
11177
|
+
const vset = valueSetByKey[t.key] || new Set();
|
|
11178
|
+
matchingValueCount = 0;
|
|
11179
|
+
for (const v of vset)if (String(v).toLowerCase().includes(searchLc)) matchingValueCount++;
|
|
11180
|
+
if (matchingValueCount === 0) return null;
|
|
11181
|
+
}
|
|
11182
|
+
const sel = selections[t.key];
|
|
11183
|
+
const selCount = sel ? sel.size : 0;
|
|
11184
|
+
const isExpanded = isSearching ? !typeLabelMatches : expandedKey === t.key;
|
|
11185
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11186
|
+
className: `exprviz-tree-type${selCount > 0 ? ' is-active' : ''}`,
|
|
11187
|
+
children: [
|
|
11188
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11189
|
+
className: "exprviz-tree-type-header",
|
|
11190
|
+
onClick: ()=>setExpandedKey(isExpanded ? null : t.key),
|
|
11191
|
+
children: [
|
|
11192
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11193
|
+
className: "exprviz-tree-caret",
|
|
11194
|
+
children: isExpanded ? "\u25BE" : "\u25B8"
|
|
11195
|
+
}),
|
|
11196
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11197
|
+
className: "exprviz-tree-type-label",
|
|
11198
|
+
children: t.label
|
|
11199
|
+
}),
|
|
11200
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11201
|
+
className: "exprviz-tree-type-count",
|
|
11202
|
+
children: selCount > 0 ? `${selCount}/${numValues}` : numValues
|
|
11203
|
+
})
|
|
11204
|
+
]
|
|
11205
|
+
}),
|
|
11206
|
+
isExpanded && renderValues(t.key, typeLabelMatches)
|
|
11207
|
+
]
|
|
11208
|
+
}, t.key);
|
|
11209
|
+
}).filter(Boolean);
|
|
11210
|
+
if (isSearching && typeRows.length === 0) return null;
|
|
11211
|
+
const groupCollapsed = !isSearching && collapsedGroups[grp.group];
|
|
11212
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11213
|
+
className: "exprviz-tree-group",
|
|
11214
|
+
children: [
|
|
11215
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11216
|
+
className: "exprviz-tree-group-header",
|
|
11217
|
+
onClick: ()=>toggleGroup(grp.group),
|
|
11218
|
+
children: [
|
|
11219
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11220
|
+
className: "exprviz-tree-caret",
|
|
11221
|
+
children: groupCollapsed ? "\u25B6" : "\u25BC"
|
|
11222
|
+
}),
|
|
11223
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11224
|
+
children: grp.label
|
|
11225
|
+
})
|
|
11226
|
+
]
|
|
11227
|
+
}),
|
|
11228
|
+
!groupCollapsed && grp.types.length === 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11229
|
+
className: "exprviz-tree-empty",
|
|
11230
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11231
|
+
children: "(none)"
|
|
11232
|
+
})
|
|
11233
|
+
}),
|
|
11234
|
+
!groupCollapsed && typeRows
|
|
11235
|
+
]
|
|
11236
|
+
}, grp.group);
|
|
11237
|
+
})
|
|
11238
|
+
]
|
|
11239
|
+
}),
|
|
11240
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11241
|
+
className: "exprviz-fields-preview",
|
|
11242
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("table", {
|
|
11243
|
+
className: "exprviz-fields-table",
|
|
11244
|
+
children: [
|
|
11245
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("thead", {
|
|
11246
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("tr", {
|
|
11247
|
+
children: [
|
|
11248
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("th", {
|
|
11249
|
+
children: "Field"
|
|
11250
|
+
}),
|
|
11251
|
+
orderedSelectedKeys.map((k)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("th", {
|
|
11252
|
+
children: $4d0c2e01f58b53b1$var$labelForKey(k, propTree)
|
|
11253
|
+
}, k))
|
|
11254
|
+
]
|
|
11255
|
+
})
|
|
11256
|
+
}),
|
|
11257
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("tbody", {
|
|
11258
|
+
children: [
|
|
11259
|
+
matchingFields.map((f)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("tr", {
|
|
11260
|
+
children: [
|
|
11261
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("td", {
|
|
11262
|
+
title: f.fieldName,
|
|
11263
|
+
children: f.fieldName.replace(/__expr$/, '')
|
|
11264
|
+
}),
|
|
11265
|
+
orderedSelectedKeys.map((k)=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("td", {
|
|
11266
|
+
children: f.props[k] || ''
|
|
11267
|
+
}, k))
|
|
11268
|
+
]
|
|
11269
|
+
}, f.fieldName)),
|
|
11270
|
+
matchingFields.length === 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("tr", {
|
|
11271
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("td", {
|
|
11272
|
+
colSpan: 1 + orderedSelectedKeys.length,
|
|
11273
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11274
|
+
children: "No matching fields"
|
|
11275
|
+
})
|
|
11276
|
+
})
|
|
11277
|
+
})
|
|
11278
|
+
]
|
|
11279
|
+
})
|
|
11280
|
+
]
|
|
11281
|
+
})
|
|
11282
|
+
})
|
|
11283
|
+
]
|
|
11284
|
+
})
|
|
11285
|
+
}),
|
|
11286
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Modal).Footer, {
|
|
11287
|
+
children: [
|
|
11288
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
11289
|
+
variant: "secondary",
|
|
11290
|
+
onClick: ()=>doToggleExprVizFieldsModal(taxon, false),
|
|
11291
|
+
children: "Cancel"
|
|
11292
|
+
}),
|
|
11293
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Button), {
|
|
11294
|
+
variant: "primary",
|
|
11295
|
+
disabled: matchingFields.length === 0,
|
|
11296
|
+
onClick: ()=>{
|
|
11297
|
+
doSetExprVizFields(taxon, matchingFields.map((f)=>f.fieldName));
|
|
11298
|
+
doToggleExprVizFieldsModal(taxon, false);
|
|
11299
|
+
},
|
|
11300
|
+
children: [
|
|
11301
|
+
"Apply (",
|
|
11302
|
+
matchingFields.length,
|
|
11303
|
+
" fields)"
|
|
11304
|
+
]
|
|
11305
|
+
})
|
|
11306
|
+
]
|
|
11307
|
+
})
|
|
11308
|
+
]
|
|
11309
|
+
});
|
|
11310
|
+
};
|
|
11311
|
+
var $4d0c2e01f58b53b1$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectFieldCatalog', 'selectExprViz', 'selectExpressionStudies', 'selectExpressionSamples', 'selectGrameneMaps', 'doToggleExprVizFieldsModal', 'doSetExprVizFields', 'doFetchExprVizFieldExistence', $4d0c2e01f58b53b1$var$FieldsModalCmp);
|
|
11312
|
+
|
|
11313
|
+
|
|
11314
|
+
|
|
11315
|
+
|
|
11316
|
+
|
|
11317
|
+
|
|
11318
|
+
|
|
11319
|
+
|
|
11320
|
+
const $4ab64e76c1caef59$var$baseColDefs = [
|
|
11321
|
+
{
|
|
11322
|
+
field: 'id',
|
|
11323
|
+
headerName: 'Gene ID',
|
|
11324
|
+
pinned: 'left',
|
|
11325
|
+
width: 160,
|
|
11326
|
+
suppressMovable: true
|
|
11327
|
+
},
|
|
11328
|
+
{
|
|
11329
|
+
field: 'name',
|
|
11330
|
+
headerName: 'Name',
|
|
11331
|
+
width: 140,
|
|
11332
|
+
suppressMovable: true
|
|
11333
|
+
}
|
|
11334
|
+
];
|
|
11335
|
+
function $4ab64e76c1caef59$var$arraysEqual(a, b) {
|
|
11336
|
+
if (a.length !== b.length) return false;
|
|
11337
|
+
for(let i = 0; i < a.length; i++)if (a[i] !== b[i]) return false;
|
|
11338
|
+
return true;
|
|
11339
|
+
}
|
|
11340
|
+
function $4ab64e76c1caef59$var$buildFieldInfo(fields, studies, expressionSamples) {
|
|
11341
|
+
const info = {};
|
|
11342
|
+
if (!fields || !studies || !expressionSamples) return info;
|
|
11343
|
+
const wanted = new Set(fields);
|
|
11344
|
+
for (const study of studies){
|
|
11345
|
+
const studyId = study._id;
|
|
11346
|
+
const samples = expressionSamples[studyId];
|
|
11347
|
+
if (!samples) continue;
|
|
11348
|
+
const byGroup = {};
|
|
11349
|
+
for (const s of samples)if (!byGroup[s.group]) byGroup[s.group] = s;
|
|
11350
|
+
for (const group of Object.keys(byGroup)){
|
|
11351
|
+
const fieldName = `${studyId.replace(/-/g, '_')}_${group}__expr`;
|
|
11352
|
+
if (!wanted.has(fieldName)) continue;
|
|
11353
|
+
const sample = byGroup[group];
|
|
11354
|
+
const factors = {};
|
|
11355
|
+
(sample.factor || []).forEach((f)=>{
|
|
11356
|
+
factors[f.type] = f.label;
|
|
11357
|
+
});
|
|
11358
|
+
const characteristics = {};
|
|
11359
|
+
(sample.characteristic || []).forEach((c)=>{
|
|
11360
|
+
if (factors[c.type] != null) return;
|
|
11361
|
+
characteristics[c.type] = c.label;
|
|
11362
|
+
});
|
|
11363
|
+
info[fieldName] = {
|
|
11364
|
+
studyId: studyId,
|
|
11365
|
+
studyDescription: study.description || studyId,
|
|
11366
|
+
group: group,
|
|
11367
|
+
replicates: samples.filter((s)=>s.group === group).length,
|
|
11368
|
+
factors: factors,
|
|
11369
|
+
characteristics: characteristics
|
|
11370
|
+
};
|
|
11371
|
+
}
|
|
11372
|
+
}
|
|
11373
|
+
return info;
|
|
11374
|
+
}
|
|
11375
|
+
// Custom header: re-implements sort click + menu button so ag-grid's column
|
|
11376
|
+
// drag/menu/filter still work, with an info icon as the popover trigger.
|
|
11377
|
+
const $4ab64e76c1caef59$var$HeaderWithInfo = (props)=>{
|
|
11378
|
+
const { displayName: displayName, enableSorting: enableSorting, enableMenu: enableMenu, showColumnMenu: showColumnMenu, progressSort: progressSort, column: column, info: info } = props;
|
|
11379
|
+
const [sort, setSort] = (0, $gXNCa$react.useState)(column && column.getSort ? column.getSort() : null);
|
|
11380
|
+
const menuRef = (0, $gXNCa$react.useRef)(null);
|
|
11381
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11382
|
+
if (!column || !column.addEventListener) return;
|
|
11383
|
+
const handler = ()=>setSort(column.getSort());
|
|
11384
|
+
column.addEventListener('sortChanged', handler);
|
|
11385
|
+
return ()=>column.removeEventListener('sortChanged', handler);
|
|
11386
|
+
}, [
|
|
11387
|
+
column
|
|
11388
|
+
]);
|
|
11389
|
+
const onSortClick = (event)=>{
|
|
11390
|
+
if (enableSorting && progressSort) progressSort(event.shiftKey);
|
|
11391
|
+
};
|
|
11392
|
+
const onMenuClick = (event)=>{
|
|
11393
|
+
event.stopPropagation();
|
|
11394
|
+
if (showColumnMenu && menuRef.current) showColumnMenu(menuRef.current);
|
|
11395
|
+
};
|
|
11396
|
+
const factorEntries = info ? Object.entries(info.factors || {}) : [];
|
|
11397
|
+
const charEntries = info ? Object.entries(info.characteristics || {}) : [];
|
|
11398
|
+
const popover = info ? /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Popover), {
|
|
11399
|
+
id: `exprviz-header-${info.studyId}-${info.group}`,
|
|
11400
|
+
className: "exprviz-header-popover",
|
|
11401
|
+
children: [
|
|
11402
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Popover).Header, {
|
|
11403
|
+
as: "h6",
|
|
11404
|
+
children: info.studyDescription
|
|
11405
|
+
}),
|
|
11406
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Popover).Body, {
|
|
11407
|
+
children: [
|
|
11408
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11409
|
+
children: [
|
|
11410
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11411
|
+
children: "Study:"
|
|
11412
|
+
}),
|
|
11413
|
+
" ",
|
|
11414
|
+
info.studyId
|
|
11415
|
+
]
|
|
11416
|
+
}),
|
|
11417
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11418
|
+
children: [
|
|
11419
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11420
|
+
children: "Group:"
|
|
11421
|
+
}),
|
|
11422
|
+
" ",
|
|
11423
|
+
info.group,
|
|
11424
|
+
" (",
|
|
11425
|
+
info.replicates,
|
|
11426
|
+
" ",
|
|
11427
|
+
info.replicates === 1 ? 'rep' : 'reps',
|
|
11428
|
+
")"
|
|
11429
|
+
]
|
|
11430
|
+
}),
|
|
11431
|
+
factorEntries.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11432
|
+
className: "exprviz-header-section",
|
|
11433
|
+
children: [
|
|
11434
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11435
|
+
children: "Factors"
|
|
11436
|
+
}),
|
|
11437
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("ul", {
|
|
11438
|
+
children: factorEntries.map(([t, v])=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("li", {
|
|
11439
|
+
children: [
|
|
11440
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("em", {
|
|
11441
|
+
children: [
|
|
11442
|
+
t,
|
|
11443
|
+
":"
|
|
11444
|
+
]
|
|
11445
|
+
}),
|
|
11446
|
+
" ",
|
|
11447
|
+
v
|
|
11448
|
+
]
|
|
11449
|
+
}, t))
|
|
11450
|
+
})
|
|
11451
|
+
]
|
|
11452
|
+
}),
|
|
11453
|
+
charEntries.length > 0 && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11454
|
+
className: "exprviz-header-section",
|
|
11455
|
+
children: [
|
|
11456
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("strong", {
|
|
11457
|
+
children: "Characteristics"
|
|
11458
|
+
}),
|
|
11459
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("ul", {
|
|
11460
|
+
children: charEntries.map(([t, v])=>/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("li", {
|
|
11461
|
+
children: [
|
|
11462
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("em", {
|
|
11463
|
+
children: [
|
|
11464
|
+
t,
|
|
11465
|
+
":"
|
|
11466
|
+
]
|
|
11467
|
+
}),
|
|
11468
|
+
" ",
|
|
11469
|
+
v
|
|
11470
|
+
]
|
|
11471
|
+
}, t))
|
|
11472
|
+
})
|
|
11473
|
+
]
|
|
11474
|
+
})
|
|
11475
|
+
]
|
|
11476
|
+
})
|
|
11477
|
+
]
|
|
11478
|
+
}) : null;
|
|
11479
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11480
|
+
className: "exprviz-header",
|
|
11481
|
+
children: [
|
|
11482
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
11483
|
+
className: "exprviz-header-text",
|
|
11484
|
+
onClick: onSortClick,
|
|
11485
|
+
style: {
|
|
11486
|
+
cursor: enableSorting ? 'pointer' : 'default'
|
|
11487
|
+
},
|
|
11488
|
+
children: [
|
|
11489
|
+
displayName,
|
|
11490
|
+
sort === 'asc' && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11491
|
+
className: "exprviz-header-sort",
|
|
11492
|
+
children: " \u25B2"
|
|
11493
|
+
}),
|
|
11494
|
+
sort === 'desc' && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11495
|
+
className: "exprviz-header-sort",
|
|
11496
|
+
children: " \u25BC"
|
|
11497
|
+
})
|
|
11498
|
+
]
|
|
11499
|
+
}),
|
|
11500
|
+
info && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.OverlayTrigger), {
|
|
11501
|
+
trigger: [
|
|
11502
|
+
'hover',
|
|
11503
|
+
'focus'
|
|
11504
|
+
],
|
|
11505
|
+
placement: "bottom",
|
|
11506
|
+
overlay: popover,
|
|
11507
|
+
delay: {
|
|
11508
|
+
show: 200,
|
|
11509
|
+
hide: 100
|
|
11510
|
+
},
|
|
11511
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11512
|
+
className: "exprviz-header-info",
|
|
11513
|
+
onClick: (e)=>e.stopPropagation(),
|
|
11514
|
+
onMouseDown: (e)=>e.stopPropagation(),
|
|
11515
|
+
"aria-label": "More info",
|
|
11516
|
+
children: "\u24D8"
|
|
11517
|
+
})
|
|
11518
|
+
}),
|
|
11519
|
+
enableMenu && /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("span", {
|
|
11520
|
+
ref: menuRef,
|
|
11521
|
+
className: "exprviz-header-menu ag-icon ag-icon-menu",
|
|
11522
|
+
onClick: onMenuClick
|
|
11523
|
+
})
|
|
11524
|
+
]
|
|
11525
|
+
});
|
|
11526
|
+
};
|
|
11527
|
+
const $4ab64e76c1caef59$var$ExprTable = ({ rows: rows, fields: fields, onReorder: onReorder, studies: studies, expressionSamples: expressionSamples, onHoverRow: onHoverRow })=>{
|
|
11528
|
+
const fieldInfo = (0, $gXNCa$react.useMemo)(()=>$4ab64e76c1caef59$var$buildFieldInfo(fields, studies, expressionSamples), [
|
|
11529
|
+
fields,
|
|
11530
|
+
studies,
|
|
11531
|
+
expressionSamples
|
|
11532
|
+
]);
|
|
11533
|
+
const columnDefs = (0, $gXNCa$react.useMemo)(()=>{
|
|
11534
|
+
const expressionCols = (fields || []).map((f)=>({
|
|
11535
|
+
field: f,
|
|
11536
|
+
headerName: f.replace(/__expr$/, ''),
|
|
11537
|
+
width: 160,
|
|
11538
|
+
suppressMovable: false,
|
|
11539
|
+
headerComponent: $4ab64e76c1caef59$var$HeaderWithInfo,
|
|
11540
|
+
headerComponentParams: {
|
|
11541
|
+
info: fieldInfo[f]
|
|
11542
|
+
},
|
|
11543
|
+
valueFormatter: (p)=>{
|
|
11544
|
+
const v = p.value;
|
|
11545
|
+
if (v == null) return '';
|
|
11546
|
+
if (Array.isArray(v)) return v.join(', ');
|
|
11547
|
+
if (typeof v === 'object') return JSON.stringify(v);
|
|
11548
|
+
return String(v);
|
|
11549
|
+
}
|
|
11550
|
+
}));
|
|
11551
|
+
return [
|
|
11552
|
+
...$4ab64e76c1caef59$var$baseColDefs,
|
|
11553
|
+
...expressionCols
|
|
11554
|
+
];
|
|
11555
|
+
}, [
|
|
11556
|
+
fields,
|
|
11557
|
+
fieldInfo
|
|
11558
|
+
]);
|
|
11559
|
+
const onColumnMoved = (e)=>{
|
|
11560
|
+
if (!onReorder || !e.finished) return;
|
|
11561
|
+
const allCols = e.api.getAllGridColumns ? e.api.getAllGridColumns() : e.columnApi && e.columnApi.getAllGridColumns && e.columnApi.getAllGridColumns();
|
|
11562
|
+
if (!allCols) return;
|
|
11563
|
+
const next = allCols.map((c)=>c.getColId()).filter((id)=>fields.includes(id));
|
|
11564
|
+
if (!$4ab64e76c1caef59$var$arraysEqual(next, fields)) onReorder(next);
|
|
11565
|
+
};
|
|
11566
|
+
if (!rows || rows.length === 0) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11567
|
+
className: "exprviz-table-empty",
|
|
11568
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11569
|
+
children: "No data loaded."
|
|
11570
|
+
})
|
|
11571
|
+
});
|
|
11572
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11573
|
+
className: "ag-theme-quartz exprviz-aggrid",
|
|
11574
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$aggridreact.AgGridReact), {
|
|
11575
|
+
rowData: rows,
|
|
11576
|
+
columnDefs: columnDefs,
|
|
11577
|
+
defaultColDef: {
|
|
11578
|
+
resizable: true,
|
|
11579
|
+
sortable: true,
|
|
11580
|
+
filter: true
|
|
11581
|
+
},
|
|
11582
|
+
animateRows: false,
|
|
11583
|
+
suppressFieldDotNotation: true,
|
|
11584
|
+
suppressDragLeaveHidesColumns: true,
|
|
11585
|
+
onColumnMoved: onColumnMoved,
|
|
11586
|
+
onCellMouseOver: (e)=>onHoverRow && onHoverRow(e.data && e.data.id),
|
|
11587
|
+
onCellMouseOut: ()=>onHoverRow && onHoverRow(null)
|
|
11588
|
+
})
|
|
11589
|
+
});
|
|
11590
|
+
};
|
|
11591
|
+
var $4ab64e76c1caef59$export$2e2bcd8739ae039 = $4ab64e76c1caef59$var$ExprTable;
|
|
11592
|
+
|
|
11593
|
+
|
|
11594
|
+
|
|
11595
|
+
|
|
11596
|
+
|
|
11597
|
+
// Parallel-coordinates plot with per-axis brushing and drag-to-reorder axes.
|
|
11598
|
+
// - Axis labels are draggable horizontally; on drop, onReorder(newOrder) fires.
|
|
11599
|
+
// - Brushes on axes intersect (AND): a row is "in" only when every active brush contains it.
|
|
11600
|
+
// - Numeric fields use a linear or symlog scale; non-numeric values are skipped.
|
|
11601
|
+
const $caf32827df861c4e$var$MARGIN = {
|
|
11602
|
+
top: 100,
|
|
11603
|
+
right: 24,
|
|
11604
|
+
bottom: 24,
|
|
11605
|
+
left: 32
|
|
11606
|
+
};
|
|
11607
|
+
const $caf32827df861c4e$var$LABEL_ROTATION = -40;
|
|
11608
|
+
const $caf32827df861c4e$var$BRUSH_WIDTH = 16;
|
|
11609
|
+
function $caf32827df861c4e$var$isNumeric(v) {
|
|
11610
|
+
if (v == null) return false;
|
|
11611
|
+
if (Array.isArray(v)) return false;
|
|
11612
|
+
const n = +v;
|
|
11613
|
+
return Number.isFinite(n);
|
|
11614
|
+
}
|
|
11615
|
+
function $caf32827df861c4e$var$arraysEqual(a, b) {
|
|
11616
|
+
if (a.length !== b.length) return false;
|
|
11617
|
+
for(let i = 0; i < a.length; i++)if (a[i] !== b[i]) return false;
|
|
11618
|
+
return true;
|
|
11619
|
+
}
|
|
11620
|
+
// Powers-of-10 tick values spanning [lo, hi]; includes 0 if the range crosses zero.
|
|
11621
|
+
// Clamped to |v| >= 0.1 to keep low-magnitude tick labels from overlapping near 0.
|
|
11622
|
+
const $caf32827df861c4e$var$MIN_LOG_TICK = 0.1;
|
|
11623
|
+
function $caf32827df861c4e$var$logTickValues([lo, hi]) {
|
|
11624
|
+
const ticks = new Set();
|
|
11625
|
+
if (lo <= 0 && hi >= 0) ticks.add(0);
|
|
11626
|
+
if (hi >= $caf32827df861c4e$var$MIN_LOG_TICK) {
|
|
11627
|
+
const start = Math.max(-1, Math.floor(Math.log10(lo > 0 ? lo : $caf32827df861c4e$var$MIN_LOG_TICK)));
|
|
11628
|
+
const end = Math.ceil(Math.log10(hi));
|
|
11629
|
+
for(let p = start; p <= end; p++){
|
|
11630
|
+
const v = Math.pow(10, p);
|
|
11631
|
+
if (v >= $caf32827df861c4e$var$MIN_LOG_TICK && v <= hi) ticks.add(v);
|
|
11632
|
+
}
|
|
11633
|
+
}
|
|
11634
|
+
if (lo <= -$caf32827df861c4e$var$MIN_LOG_TICK) {
|
|
11635
|
+
const start = Math.max(-1, Math.floor(Math.log10(hi < 0 ? -hi : $caf32827df861c4e$var$MIN_LOG_TICK)));
|
|
11636
|
+
const end = Math.ceil(Math.log10(-lo));
|
|
11637
|
+
for(let p = start; p <= end; p++){
|
|
11638
|
+
const v = -Math.pow(10, p);
|
|
11639
|
+
if (-v >= $caf32827df861c4e$var$MIN_LOG_TICK && v >= lo) ticks.add(v);
|
|
11640
|
+
}
|
|
11641
|
+
}
|
|
11642
|
+
return Array.from(ticks).sort((a, b)=>a - b);
|
|
11643
|
+
}
|
|
11644
|
+
function $caf32827df861c4e$var$logTickFormat(v) {
|
|
11645
|
+
if (v === 0) return '0';
|
|
11646
|
+
const a = Math.abs(v);
|
|
11647
|
+
if (a >= 0.01 && a < 10000) return $gXNCa$d3.format('~g')(v);
|
|
11648
|
+
return $gXNCa$d3.format('.0e')(v);
|
|
11649
|
+
}
|
|
11650
|
+
const $caf32827df861c4e$var$ParallelCoordsPlot = ({ rows: rows, fields: fields, scale: scale = 'linear', onBrushChange: onBrushChange, onReorder: onReorder, clearVersion: clearVersion = 0, hoveredId: hoveredId = null })=>{
|
|
11651
|
+
const svgRef = (0, $gXNCa$react.useRef)(null);
|
|
11652
|
+
const containerRef = (0, $gXNCa$react.useRef)(null);
|
|
11653
|
+
// selections in data domain: { [field]: [lo, hi] }
|
|
11654
|
+
const selectionsRef = (0, $gXNCa$react.useRef)({});
|
|
11655
|
+
const lastClearRef = (0, $gXNCa$react.useRef)(0);
|
|
11656
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11657
|
+
if (clearVersion !== lastClearRef.current) {
|
|
11658
|
+
selectionsRef.current = {};
|
|
11659
|
+
lastClearRef.current = clearVersion;
|
|
11660
|
+
if (onBrushChange) onBrushChange({});
|
|
11661
|
+
}
|
|
11662
|
+
Object.keys(selectionsRef.current).forEach((f)=>{
|
|
11663
|
+
if (!fields || !fields.includes(f)) delete selectionsRef.current[f];
|
|
11664
|
+
});
|
|
11665
|
+
const svg = $gXNCa$d3.select(svgRef.current);
|
|
11666
|
+
svg.selectAll('*').remove();
|
|
11667
|
+
if (!fields || fields.length === 0 || !rows || rows.length === 0) return;
|
|
11668
|
+
const el = containerRef.current;
|
|
11669
|
+
const width = el && el.clientWidth || 600;
|
|
11670
|
+
const height = el && el.clientHeight || 300;
|
|
11671
|
+
const innerW = width - $caf32827df861c4e$var$MARGIN.left - $caf32827df861c4e$var$MARGIN.right;
|
|
11672
|
+
const innerH = height - $caf32827df861c4e$var$MARGIN.top - $caf32827df861c4e$var$MARGIN.bottom;
|
|
11673
|
+
svg.attr('viewBox', `0 0 ${width} ${height}`);
|
|
11674
|
+
const g = svg.append('g').attr('transform', `translate(${$caf32827df861c4e$var$MARGIN.left},${$caf32827df861c4e$var$MARGIN.top})`);
|
|
11675
|
+
// Mutable order during drag — starts as a copy of fields.
|
|
11676
|
+
let order = fields.slice();
|
|
11677
|
+
const x = $gXNCa$d3.scalePoint().range([
|
|
11678
|
+
0,
|
|
11679
|
+
innerW
|
|
11680
|
+
]).padding(0.5).domain(order);
|
|
11681
|
+
const yByField = {};
|
|
11682
|
+
let globalExt = null;
|
|
11683
|
+
if (scale === 'log') {
|
|
11684
|
+
const all = [];
|
|
11685
|
+
fields.forEach((f)=>{
|
|
11686
|
+
rows.forEach((r)=>{
|
|
11687
|
+
const v = r[f];
|
|
11688
|
+
if ($caf32827df861c4e$var$isNumeric(v)) all.push(+v);
|
|
11689
|
+
});
|
|
11690
|
+
});
|
|
11691
|
+
globalExt = all.length ? $gXNCa$d3.extent(all) : [
|
|
11692
|
+
0,
|
|
11693
|
+
1
|
|
11694
|
+
];
|
|
11695
|
+
}
|
|
11696
|
+
fields.forEach((f)=>{
|
|
11697
|
+
if (scale === 'log') yByField[f] = $gXNCa$d3.scaleSymlog().domain(globalExt).range([
|
|
11698
|
+
innerH,
|
|
11699
|
+
0
|
|
11700
|
+
]).nice();
|
|
11701
|
+
else {
|
|
11702
|
+
const vals = rows.map((r)=>r[f]).filter($caf32827df861c4e$var$isNumeric).map(Number);
|
|
11703
|
+
const ext = vals.length ? $gXNCa$d3.extent(vals) : [
|
|
11704
|
+
0,
|
|
11705
|
+
1
|
|
11706
|
+
];
|
|
11707
|
+
yByField[f] = $gXNCa$d3.scaleLinear().domain(ext).range([
|
|
11708
|
+
innerH,
|
|
11709
|
+
0
|
|
11710
|
+
]).nice();
|
|
11711
|
+
}
|
|
11712
|
+
});
|
|
11713
|
+
function pathForRow(row, posOf) {
|
|
11714
|
+
const pts = order.map((f)=>{
|
|
11715
|
+
const v = row[f];
|
|
11716
|
+
if (!$caf32827df861c4e$var$isNumeric(v)) return null;
|
|
11717
|
+
return [
|
|
11718
|
+
posOf(f),
|
|
11719
|
+
yByField[f](Number(v))
|
|
11720
|
+
];
|
|
11721
|
+
});
|
|
11722
|
+
return line(pts);
|
|
11723
|
+
}
|
|
11724
|
+
const line = $gXNCa$d3.line().defined((d)=>d != null && Number.isFinite(d[1])).x((d)=>d[0]).y((d)=>d[1]);
|
|
11725
|
+
const linesG = g.append('g').attr('class', 'exprviz-pc-lines');
|
|
11726
|
+
const paths = linesG.selectAll('path').data(rows).enter().append('path').attr('fill', 'none').attr('stroke', 'steelblue').attr('stroke-width', 1).attr('data-id', (d)=>d && d.id != null ? String(d.id) : null).attr('d', (row)=>pathForRow(row, (f)=>x(f)));
|
|
11727
|
+
function isBrushedIn(row) {
|
|
11728
|
+
for (const f of order){
|
|
11729
|
+
const sel = selectionsRef.current[f];
|
|
11730
|
+
if (!sel) continue;
|
|
11731
|
+
const v = row[f];
|
|
11732
|
+
if (!$caf32827df861c4e$var$isNumeric(v)) return false;
|
|
11733
|
+
const n = Number(v);
|
|
11734
|
+
const [lo, hi] = sel;
|
|
11735
|
+
if (n < lo || n > hi) return false;
|
|
11736
|
+
}
|
|
11737
|
+
return true;
|
|
11738
|
+
}
|
|
11739
|
+
function applyBrushStyles() {
|
|
11740
|
+
const anyActive = Object.keys(selectionsRef.current).length > 0;
|
|
11741
|
+
paths.classed('exprviz-pc-line-in', (d)=>!anyActive || isBrushedIn(d)).classed('exprviz-pc-line-out', (d)=>anyActive && !isBrushedIn(d));
|
|
11742
|
+
}
|
|
11743
|
+
applyBrushStyles();
|
|
11744
|
+
// axis groups, keyed by field name so D3 can match them across reorders
|
|
11745
|
+
const axisG = g.selectAll('.exprviz-pc-axis').data(order, (d)=>d).enter().append('g').attr('class', 'exprviz-pc-axis').attr('transform', (d)=>`translate(${x(d)},0)`);
|
|
11746
|
+
axisG.each(function(f) {
|
|
11747
|
+
const ax = $gXNCa$d3.select(this);
|
|
11748
|
+
const axisGen = $gXNCa$d3.axisLeft(yByField[f]);
|
|
11749
|
+
if (scale === 'log') axisGen.tickValues($caf32827df861c4e$var$logTickValues(yByField[f].domain())).tickFormat($caf32827df861c4e$var$logTickFormat);
|
|
11750
|
+
else axisGen.ticks(5);
|
|
11751
|
+
ax.call(axisGen);
|
|
11752
|
+
const label = ax.append('text').attr('class', 'exprviz-pc-axis-label').attr('x', 4).attr('y', -4).attr('text-anchor', 'start').attr('transform', `rotate(${$caf32827df861c4e$var$LABEL_ROTATION}, 0, -4)`).attr('fill', '#333').style('font-size', '10px').style('cursor', 'grab').text(f.replace(/__expr$/, ''));
|
|
11753
|
+
// hit area for grabbing — sits along the rotated label
|
|
11754
|
+
ax.append('rect').attr('class', 'exprviz-pc-axis-handle').attr('x', 0).attr('y', -11).attr('width', 140).attr('height', 14).attr('transform', `rotate(${$caf32827df861c4e$var$LABEL_ROTATION}, 0, -4)`).attr('fill', 'transparent').style('cursor', 'grab');
|
|
11755
|
+
const brush = $gXNCa$d3.brushY().extent([
|
|
11756
|
+
[
|
|
11757
|
+
-$caf32827df861c4e$var$BRUSH_WIDTH / 2,
|
|
11758
|
+
0
|
|
11759
|
+
],
|
|
11760
|
+
[
|
|
11761
|
+
$caf32827df861c4e$var$BRUSH_WIDTH / 2,
|
|
11762
|
+
innerH
|
|
11763
|
+
]
|
|
11764
|
+
]).on('brush end', (event)=>{
|
|
11765
|
+
const s = event.selection;
|
|
11766
|
+
if (!s) delete selectionsRef.current[f];
|
|
11767
|
+
else {
|
|
11768
|
+
const y = yByField[f];
|
|
11769
|
+
const a = y.invert(s[0]);
|
|
11770
|
+
const b = y.invert(s[1]);
|
|
11771
|
+
selectionsRef.current[f] = [
|
|
11772
|
+
Math.min(a, b),
|
|
11773
|
+
Math.max(a, b)
|
|
11774
|
+
];
|
|
11775
|
+
}
|
|
11776
|
+
applyBrushStyles();
|
|
11777
|
+
// event.sourceEvent is null when brush.move is called programmatically
|
|
11778
|
+
// (e.g. when this effect re-runs and we restore prior selections).
|
|
11779
|
+
// Skipping that case avoids a re-render loop with the parent.
|
|
11780
|
+
if (event.type === 'end' && event.sourceEvent && onBrushChange) onBrushChange({
|
|
11781
|
+
...selectionsRef.current
|
|
11782
|
+
});
|
|
11783
|
+
});
|
|
11784
|
+
const brushG = ax.append('g').attr('class', 'exprviz-pc-brush').call(brush);
|
|
11785
|
+
const prior = selectionsRef.current[f];
|
|
11786
|
+
if (prior) {
|
|
11787
|
+
const y = yByField[f];
|
|
11788
|
+
const py0 = y(prior[1]);
|
|
11789
|
+
const py1 = y(prior[0]);
|
|
11790
|
+
if (Number.isFinite(py0) && Number.isFinite(py1)) brushG.call(brush.move, [
|
|
11791
|
+
py0,
|
|
11792
|
+
py1
|
|
11793
|
+
]);
|
|
11794
|
+
}
|
|
11795
|
+
});
|
|
11796
|
+
// Drag-to-reorder: while dragging, only the dragged axis moves and the
|
|
11797
|
+
// line segments connecting to it are recomputed. Other axes stay put.
|
|
11798
|
+
// The new order is computed once at drag end and emitted via onReorder.
|
|
11799
|
+
const drag = $gXNCa$d3.drag().container(function() {
|
|
11800
|
+
return g.node();
|
|
11801
|
+
}).subject(function(event, d) {
|
|
11802
|
+
return {
|
|
11803
|
+
x: x(d),
|
|
11804
|
+
y: 0
|
|
11805
|
+
};
|
|
11806
|
+
}).on('start', function(event, d) {
|
|
11807
|
+
const axNode = this.parentNode;
|
|
11808
|
+
$gXNCa$d3.select(axNode).raise().classed('exprviz-pc-axis-dragging', true);
|
|
11809
|
+
$gXNCa$d3.select(axNode).select('.exprviz-pc-axis-label').style('cursor', 'grabbing');
|
|
11810
|
+
linesG.classed('exprviz-pc-lines-dragging', true);
|
|
11811
|
+
}).on('drag', function(event, d) {
|
|
11812
|
+
const axNode = this.parentNode;
|
|
11813
|
+
const newX = Math.max(0, Math.min(innerW, event.x));
|
|
11814
|
+
$gXNCa$d3.select(axNode).attr('transform', `translate(${newX},0)`);
|
|
11815
|
+
paths.attr('d', (row)=>pathForRow(row, (f)=>f === d ? newX : x(f)));
|
|
11816
|
+
}).on('end', function(event, d) {
|
|
11817
|
+
const axNode = this.parentNode;
|
|
11818
|
+
const newX = Math.max(0, Math.min(innerW, event.x));
|
|
11819
|
+
$gXNCa$d3.select(axNode).classed('exprviz-pc-axis-dragging', false);
|
|
11820
|
+
$gXNCa$d3.select(axNode).select('.exprviz-pc-axis-label').style('cursor', 'grab');
|
|
11821
|
+
linesG.classed('exprviz-pc-lines-dragging', false);
|
|
11822
|
+
const newOrder = order.slice().sort((a, b)=>{
|
|
11823
|
+
const xa = a === d ? newX : x(a);
|
|
11824
|
+
const xb = b === d ? newX : x(b);
|
|
11825
|
+
return xa - xb;
|
|
11826
|
+
});
|
|
11827
|
+
if (onReorder && !$caf32827df861c4e$var$arraysEqual(newOrder, fields)) {
|
|
11828
|
+
// Snap the dragged axis to its target slot for the brief moment
|
|
11829
|
+
// before the parent re-renders with the new order.
|
|
11830
|
+
x.domain(newOrder);
|
|
11831
|
+
$gXNCa$d3.select(axNode).attr('transform', `translate(${x(d)},0)`);
|
|
11832
|
+
paths.attr('d', (row)=>pathForRow(row, (f)=>x(f)));
|
|
11833
|
+
onReorder(newOrder);
|
|
11834
|
+
} else {
|
|
11835
|
+
// No order change — restore the dragged axis to its original slot.
|
|
11836
|
+
$gXNCa$d3.select(axNode).attr('transform', `translate(${x(d)},0)`);
|
|
11837
|
+
paths.attr('d', (row)=>pathForRow(row, (f)=>x(f)));
|
|
11838
|
+
}
|
|
11839
|
+
});
|
|
11840
|
+
axisG.selectAll('.exprviz-pc-axis-label, .exprviz-pc-axis-handle').call(drag);
|
|
11841
|
+
}, [
|
|
11842
|
+
rows,
|
|
11843
|
+
fields,
|
|
11844
|
+
scale,
|
|
11845
|
+
onBrushChange,
|
|
11846
|
+
onReorder,
|
|
11847
|
+
clearVersion
|
|
11848
|
+
]);
|
|
11849
|
+
// Highlight the polyline matching the hovered row id without rebuilding the
|
|
11850
|
+
// SVG. Raises the highlighted path so it draws above its neighbors.
|
|
11851
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11852
|
+
const svg = $gXNCa$d3.select(svgRef.current);
|
|
11853
|
+
if (svg.empty()) return;
|
|
11854
|
+
const paths = svg.selectAll('.exprviz-pc-lines path');
|
|
11855
|
+
paths.classed('exprviz-pc-line-hover', false);
|
|
11856
|
+
if (hoveredId == null) return;
|
|
11857
|
+
const target = paths.filter(function() {
|
|
11858
|
+
return this.getAttribute('data-id') === String(hoveredId);
|
|
11859
|
+
});
|
|
11860
|
+
target.classed('exprviz-pc-line-hover', true).raise();
|
|
11861
|
+
}, [
|
|
11862
|
+
hoveredId,
|
|
11863
|
+
rows,
|
|
11864
|
+
fields,
|
|
11865
|
+
scale
|
|
11866
|
+
]);
|
|
11867
|
+
if (!fields || fields.length === 0) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11868
|
+
className: "exprviz-plot-empty",
|
|
11869
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11870
|
+
children: "Select fields to plot."
|
|
11871
|
+
})
|
|
11872
|
+
});
|
|
11873
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11874
|
+
ref: containerRef,
|
|
11875
|
+
className: "exprviz-pc-container",
|
|
11876
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("svg", {
|
|
11877
|
+
ref: svgRef,
|
|
11878
|
+
width: "100%",
|
|
11879
|
+
height: "100%",
|
|
11880
|
+
preserveAspectRatio: "none"
|
|
11881
|
+
})
|
|
11882
|
+
});
|
|
11883
|
+
};
|
|
11884
|
+
var $caf32827df861c4e$export$2e2bcd8739ae039 = $caf32827df861c4e$var$ParallelCoordsPlot;
|
|
11885
|
+
|
|
11886
|
+
|
|
11887
|
+
|
|
11888
|
+
function $1fd2507769d5bd00$var$speciesTaxonId(tid) {
|
|
11889
|
+
const n = +tid;
|
|
11890
|
+
return n > 1000000 ? Math.floor(n / 1000) : n;
|
|
11891
|
+
}
|
|
11892
|
+
function $1fd2507769d5bd00$var$genomeName(grameneMaps, tid) {
|
|
11893
|
+
if (!grameneMaps) return tid;
|
|
11894
|
+
const direct = grameneMaps[tid];
|
|
11895
|
+
if (direct && direct.display_name) return direct.display_name;
|
|
11896
|
+
const sp = grameneMaps[$1fd2507769d5bd00$var$speciesTaxonId(tid)];
|
|
11897
|
+
if (sp && sp.display_name) return sp.display_name;
|
|
11898
|
+
return tid;
|
|
11899
|
+
}
|
|
11900
|
+
const $1fd2507769d5bd00$var$ExprVizViewCmp = (props)=>{
|
|
11901
|
+
const { exprVizPivot: pivot, exprViz: exprViz, exprVizActiveTaxon: activeTaxon, grameneMaps: grameneMaps, expressionStudies: expressionStudies, expressionSamples: expressionSamples, doSetExprVizActiveTaxon: doSetExprVizActiveTaxon, doToggleExprVizFieldsModal: doToggleExprVizFieldsModal, doFetchExprVizData: doFetchExprVizData } = props;
|
|
11902
|
+
const studiesFor = (tid)=>{
|
|
11903
|
+
if (!expressionStudies) return [];
|
|
11904
|
+
return expressionStudies[tid] || expressionStudies[$1fd2507769d5bd00$var$speciesTaxonId(tid)] || [];
|
|
11905
|
+
};
|
|
11906
|
+
const taxa = (0, $gXNCa$react.useMemo)(()=>{
|
|
11907
|
+
const ids = Object.keys(pivot.data || {});
|
|
11908
|
+
if (!grameneMaps) return ids;
|
|
11909
|
+
return ids.sort((a, b)=>{
|
|
11910
|
+
const ma = grameneMaps[a] || grameneMaps[$1fd2507769d5bd00$var$speciesTaxonId(a)];
|
|
11911
|
+
const mb = grameneMaps[b] || grameneMaps[$1fd2507769d5bd00$var$speciesTaxonId(b)];
|
|
11912
|
+
return (ma && ma.left_index || 0) - (mb && mb.left_index || 0);
|
|
11913
|
+
});
|
|
11914
|
+
}, [
|
|
11915
|
+
pivot.data,
|
|
11916
|
+
grameneMaps
|
|
11917
|
+
]);
|
|
11918
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
11919
|
+
if (taxa.length === 0) return;
|
|
11920
|
+
if (!activeTaxon || !taxa.includes(String(activeTaxon))) doSetExprVizActiveTaxon(taxa[0]);
|
|
11921
|
+
}, [
|
|
11922
|
+
taxa,
|
|
11923
|
+
activeTaxon,
|
|
11924
|
+
doSetExprVizActiveTaxon
|
|
11925
|
+
]);
|
|
11926
|
+
if (pivot.status === 'loading') return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11927
|
+
className: "exprviz-view",
|
|
11928
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11929
|
+
children: "Loading studies\u2026"
|
|
11930
|
+
})
|
|
11931
|
+
});
|
|
11932
|
+
if (pivot.status === 'error') return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11933
|
+
className: "exprviz-view",
|
|
11934
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("em", {
|
|
11935
|
+
children: [
|
|
11936
|
+
"Error: ",
|
|
11937
|
+
pivot.error
|
|
11938
|
+
]
|
|
11939
|
+
})
|
|
11940
|
+
});
|
|
11941
|
+
if (taxa.length === 0) return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
11942
|
+
className: "exprviz-view",
|
|
11943
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("em", {
|
|
11944
|
+
children: "No expression studies for current results."
|
|
11945
|
+
})
|
|
11946
|
+
});
|
|
11947
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
11948
|
+
className: "exprviz-view",
|
|
11949
|
+
children: [
|
|
11950
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Tabs), {
|
|
11951
|
+
activeKey: activeTaxon || taxa[0],
|
|
11952
|
+
onSelect: (k)=>doSetExprVizActiveTaxon(k),
|
|
11953
|
+
className: "exprviz-tabs",
|
|
11954
|
+
children: taxa.map((tid)=>{
|
|
11955
|
+
const studies = studiesFor(tid);
|
|
11956
|
+
const taxName = $1fd2507769d5bd00$var$genomeName(grameneMaps, tid);
|
|
11957
|
+
const geneCount = pivot.data[tid] || 0;
|
|
11958
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Tab), {
|
|
11959
|
+
eventKey: tid,
|
|
11960
|
+
title: `${taxName} (${studies.length} studies \xb7 ${geneCount} genes)`,
|
|
11961
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)($1fd2507769d5bd00$var$TaxonPanel, {
|
|
11962
|
+
taxon: tid,
|
|
11963
|
+
studies: studies,
|
|
11964
|
+
expressionSamples: expressionSamples,
|
|
11965
|
+
tabState: exprViz.byTaxon[tid],
|
|
11966
|
+
onOpenFields: ()=>doToggleExprVizFieldsModal(tid, true),
|
|
11967
|
+
onLoad: ()=>doFetchExprVizData(tid),
|
|
11968
|
+
onReorder: (next)=>props.doReorderExprVizFields(tid, next),
|
|
11969
|
+
onAddRangeQuery: props.doAddGrameneRangeQuery
|
|
11970
|
+
})
|
|
11971
|
+
}, tid);
|
|
11972
|
+
})
|
|
11973
|
+
}),
|
|
11974
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $4d0c2e01f58b53b1$export$2e2bcd8739ae039), {})
|
|
11975
|
+
]
|
|
11976
|
+
});
|
|
11977
|
+
};
|
|
11978
|
+
function $1fd2507769d5bd00$var$rowMatchesSelections(row, selections) {
|
|
11979
|
+
for (const f of Object.keys(selections)){
|
|
11980
|
+
const v = row[f];
|
|
11981
|
+
if (v == null || Array.isArray(v)) return false;
|
|
11982
|
+
const n = +v;
|
|
11983
|
+
if (!Number.isFinite(n)) return false;
|
|
11984
|
+
const [lo, hi] = selections[f];
|
|
11985
|
+
if (n < lo || n > hi) return false;
|
|
11986
|
+
}
|
|
11987
|
+
return true;
|
|
11988
|
+
}
|
|
11989
|
+
function $1fd2507769d5bd00$var$fmt(n) {
|
|
11990
|
+
if (!Number.isFinite(n)) return String(n);
|
|
11991
|
+
const a = Math.abs(n);
|
|
11992
|
+
if (a !== 0 && (a < 0.001 || a >= 1e6)) return n.toExponential(3);
|
|
11993
|
+
return Number(n.toFixed(4)).toString();
|
|
11994
|
+
}
|
|
11995
|
+
function $1fd2507769d5bd00$var$tsvCell(v) {
|
|
11996
|
+
if (v == null) return '';
|
|
11997
|
+
if (Array.isArray(v)) return v.join(',');
|
|
11998
|
+
const s = typeof v === 'object' ? JSON.stringify(v) : String(v);
|
|
11999
|
+
return s.replace(/[\t\r\n]+/g, ' ');
|
|
12000
|
+
}
|
|
12001
|
+
function $1fd2507769d5bd00$var$downloadTsv(filename, rows, fields) {
|
|
12002
|
+
const cols = [
|
|
12003
|
+
'id',
|
|
12004
|
+
'name',
|
|
12005
|
+
...fields
|
|
12006
|
+
];
|
|
12007
|
+
const headerLabels = [
|
|
12008
|
+
'id',
|
|
12009
|
+
'name',
|
|
12010
|
+
...fields.map((f)=>f.replace(/__expr$/, ''))
|
|
12011
|
+
];
|
|
12012
|
+
const lines = [
|
|
12013
|
+
headerLabels.join('\t')
|
|
12014
|
+
];
|
|
12015
|
+
for (const r of rows)lines.push(cols.map((c)=>$1fd2507769d5bd00$var$tsvCell(r[c])).join('\t'));
|
|
12016
|
+
const blob = new Blob([
|
|
12017
|
+
lines.join('\n') + '\n'
|
|
12018
|
+
], {
|
|
12019
|
+
type: 'text/tab-separated-values'
|
|
12020
|
+
});
|
|
12021
|
+
const url = URL.createObjectURL(blob);
|
|
12022
|
+
const a = document.createElement('a');
|
|
12023
|
+
a.href = url;
|
|
12024
|
+
a.download = filename;
|
|
12025
|
+
document.body.appendChild(a);
|
|
12026
|
+
a.click();
|
|
12027
|
+
document.body.removeChild(a);
|
|
12028
|
+
URL.revokeObjectURL(url);
|
|
12029
|
+
}
|
|
12030
|
+
const $1fd2507769d5bd00$var$TaxonPanel = ({ taxon: taxon, studies: studies, expressionSamples: expressionSamples, tabState: tabState, onOpenFields: onOpenFields, onLoad: onLoad, onReorder: onReorder, onAddRangeQuery: onAddRangeQuery })=>{
|
|
12031
|
+
const selected = tabState && tabState.selectedFields || [];
|
|
12032
|
+
const rows = tabState && tabState.rows || [];
|
|
12033
|
+
const fetchInfo = tabState && tabState.fetch || {
|
|
12034
|
+
status: 'idle',
|
|
12035
|
+
total: 0
|
|
12036
|
+
};
|
|
12037
|
+
const [scale, setScale] = (0, $gXNCa$react.useState)('linear');
|
|
12038
|
+
const [selections, setSelections] = (0, $gXNCa$react.useState)({});
|
|
12039
|
+
const [clearVersion, setClearVersion] = (0, $gXNCa$react.useState)(0);
|
|
12040
|
+
const [hoveredId, setHoveredId] = (0, $gXNCa$react.useState)(null);
|
|
12041
|
+
const hasBrush = Object.keys(selections).length > 0;
|
|
12042
|
+
const filteredRows = (0, $gXNCa$react.useMemo)(()=>{
|
|
12043
|
+
if (!hasBrush) return rows;
|
|
12044
|
+
return rows.filter((r)=>$1fd2507769d5bd00$var$rowMatchesSelections(r, selections));
|
|
12045
|
+
}, [
|
|
12046
|
+
rows,
|
|
12047
|
+
selections,
|
|
12048
|
+
hasBrush
|
|
12049
|
+
]);
|
|
12050
|
+
// Drop fields with no numeric data in the loaded rows so empty axes/columns
|
|
12051
|
+
// don't clutter the visualization. Selected-but-empty fields stay in the
|
|
12052
|
+
// underlying selection so a future load can repopulate them.
|
|
12053
|
+
const visibleFields = (0, $gXNCa$react.useMemo)(()=>{
|
|
12054
|
+
if (rows.length === 0 || selected.length === 0) return selected;
|
|
12055
|
+
return selected.filter((f)=>rows.some((r)=>{
|
|
12056
|
+
const v = r[f];
|
|
12057
|
+
return v != null && !Array.isArray(v) && Number.isFinite(+v);
|
|
12058
|
+
}));
|
|
12059
|
+
}, [
|
|
12060
|
+
rows,
|
|
12061
|
+
selected
|
|
12062
|
+
]);
|
|
12063
|
+
const handleReorder = onReorder ? (newVisibleOrder)=>{
|
|
12064
|
+
const visibleSet = new Set(newVisibleOrder);
|
|
12065
|
+
const hidden = selected.filter((f)=>!visibleSet.has(f));
|
|
12066
|
+
onReorder([
|
|
12067
|
+
...newVisibleOrder,
|
|
12068
|
+
...hidden
|
|
12069
|
+
]);
|
|
12070
|
+
} : undefined;
|
|
12071
|
+
(0, $gXNCa$react.useEffect)(()=>{
|
|
12072
|
+
if (rows.length === 0 && hasBrush) {
|
|
12073
|
+
setSelections({});
|
|
12074
|
+
setClearVersion((v)=>v + 1);
|
|
12075
|
+
}
|
|
12076
|
+
}, [
|
|
12077
|
+
rows.length,
|
|
12078
|
+
hasBrush
|
|
12079
|
+
]);
|
|
12080
|
+
return /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12081
|
+
className: "exprviz-tab-panel",
|
|
12082
|
+
children: [
|
|
12083
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12084
|
+
className: "exprviz-toolbar",
|
|
12085
|
+
children: [
|
|
12086
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.Button), {
|
|
12087
|
+
size: "sm",
|
|
12088
|
+
onClick: onOpenFields,
|
|
12089
|
+
children: [
|
|
12090
|
+
"Select fields (",
|
|
12091
|
+
selected.length,
|
|
12092
|
+
" selected, ",
|
|
12093
|
+
studies.length,
|
|
12094
|
+
" studies)"
|
|
12095
|
+
]
|
|
12096
|
+
}),
|
|
12097
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
12098
|
+
size: "sm",
|
|
12099
|
+
variant: "primary",
|
|
12100
|
+
disabled: selected.length === 0 || fetchInfo.status === 'loading',
|
|
12101
|
+
onClick: onLoad,
|
|
12102
|
+
children: fetchInfo.status === 'loading' ? "Loading\u2026" : 'Load data'
|
|
12103
|
+
}),
|
|
12104
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)((0, $gXNCa$reactbootstrap.ToggleButtonGroup), {
|
|
12105
|
+
type: "radio",
|
|
12106
|
+
name: `exprviz-scale-${taxon}`,
|
|
12107
|
+
size: "sm",
|
|
12108
|
+
value: scale,
|
|
12109
|
+
onChange: setScale,
|
|
12110
|
+
children: [
|
|
12111
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.ToggleButton), {
|
|
12112
|
+
id: `exprviz-scale-${taxon}-lin`,
|
|
12113
|
+
value: "linear",
|
|
12114
|
+
variant: "outline-secondary",
|
|
12115
|
+
children: "Linear"
|
|
12116
|
+
}),
|
|
12117
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.ToggleButton), {
|
|
12118
|
+
id: `exprviz-scale-${taxon}-log`,
|
|
12119
|
+
value: "log",
|
|
12120
|
+
variant: "outline-secondary",
|
|
12121
|
+
children: "Log"
|
|
12122
|
+
})
|
|
12123
|
+
]
|
|
12124
|
+
}),
|
|
12125
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
12126
|
+
size: "sm",
|
|
12127
|
+
variant: "outline-secondary",
|
|
12128
|
+
disabled: !hasBrush,
|
|
12129
|
+
onClick: ()=>{
|
|
12130
|
+
setClearVersion((v)=>v + 1);
|
|
12131
|
+
setSelections({});
|
|
12132
|
+
},
|
|
12133
|
+
children: "Clear brushes"
|
|
12134
|
+
}),
|
|
12135
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
12136
|
+
size: "sm",
|
|
12137
|
+
variant: "outline-secondary",
|
|
12138
|
+
disabled: filteredRows.length === 0 || visibleFields.length === 0,
|
|
12139
|
+
onClick: ()=>$1fd2507769d5bd00$var$downloadTsv(`expression_${taxon}.tsv`, filteredRows, visibleFields),
|
|
12140
|
+
title: "Download the visible rows and columns as tab-delimited text",
|
|
12141
|
+
children: "Download TSV"
|
|
12142
|
+
}),
|
|
12143
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $gXNCa$reactbootstrap.Button), {
|
|
12144
|
+
size: "sm",
|
|
12145
|
+
variant: "success",
|
|
12146
|
+
disabled: !hasBrush || !onAddRangeQuery,
|
|
12147
|
+
onClick: ()=>{
|
|
12148
|
+
const terms = Object.keys(selections).map((field)=>{
|
|
12149
|
+
const [lo, hi] = selections[field];
|
|
12150
|
+
return {
|
|
12151
|
+
category: 'Expression',
|
|
12152
|
+
name: `${field}: ${$1fd2507769d5bd00$var$fmt(lo)}\u{2013}${$1fd2507769d5bd00$var$fmt(hi)}`,
|
|
12153
|
+
fq_field: field,
|
|
12154
|
+
fq_value: `[${lo} TO ${hi}]`
|
|
12155
|
+
};
|
|
12156
|
+
});
|
|
12157
|
+
onAddRangeQuery(terms);
|
|
12158
|
+
},
|
|
12159
|
+
title: "Add brush ranges as an AND-conjunction filter on the search",
|
|
12160
|
+
children: "Apply as filter"
|
|
12161
|
+
}),
|
|
12162
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("span", {
|
|
12163
|
+
className: "exprviz-status",
|
|
12164
|
+
children: [
|
|
12165
|
+
hasBrush ? `${filteredRows.length} of ${rows.length}` : rows.length,
|
|
12166
|
+
fetchInfo.total ? ` / ${fetchInfo.total}` : '',
|
|
12167
|
+
" genes",
|
|
12168
|
+
hasBrush ? ' (brushed)' : ' loaded'
|
|
12169
|
+
]
|
|
12170
|
+
})
|
|
12171
|
+
]
|
|
12172
|
+
}),
|
|
12173
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsxs)("div", {
|
|
12174
|
+
className: "exprviz-body",
|
|
12175
|
+
children: [
|
|
12176
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12177
|
+
className: "exprviz-plot",
|
|
12178
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $caf32827df861c4e$export$2e2bcd8739ae039), {
|
|
12179
|
+
rows: rows,
|
|
12180
|
+
fields: visibleFields,
|
|
12181
|
+
scale: scale,
|
|
12182
|
+
onBrushChange: setSelections,
|
|
12183
|
+
onReorder: handleReorder,
|
|
12184
|
+
clearVersion: clearVersion,
|
|
12185
|
+
hoveredId: hoveredId
|
|
12186
|
+
})
|
|
12187
|
+
}),
|
|
12188
|
+
/*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)("div", {
|
|
12189
|
+
className: "exprviz-table",
|
|
12190
|
+
children: /*#__PURE__*/ (0, $gXNCa$reactjsxruntime.jsx)((0, $4ab64e76c1caef59$export$2e2bcd8739ae039), {
|
|
12191
|
+
rows: filteredRows,
|
|
12192
|
+
fields: visibleFields,
|
|
12193
|
+
onReorder: handleReorder,
|
|
12194
|
+
studies: studies,
|
|
12195
|
+
expressionSamples: expressionSamples,
|
|
12196
|
+
onHoverRow: setHoveredId
|
|
12197
|
+
})
|
|
12198
|
+
})
|
|
12199
|
+
]
|
|
12200
|
+
})
|
|
12201
|
+
]
|
|
12202
|
+
});
|
|
12203
|
+
};
|
|
12204
|
+
var $1fd2507769d5bd00$export$2e2bcd8739ae039 = (0, $gXNCa$reduxbundlerreact.connect)('selectExprViz', 'selectExprVizPivot', 'selectExprVizActiveTaxon', 'selectGrameneMaps', 'selectExpressionStudies', 'selectExpressionSamples', 'doSetExprVizActiveTaxon', 'doToggleExprVizFieldsModal', 'doFetchExprVizData', 'doReorderExprVizFields', 'doAddGrameneRangeQuery', $1fd2507769d5bd00$var$ExprVizViewCmp);
|
|
12205
|
+
|
|
12206
|
+
|
|
12207
|
+
|
|
12208
|
+
|
|
12209
|
+
|
|
12210
|
+
|
|
12211
|
+
|
|
12212
|
+
|
|
12213
|
+
|
|
10206
12214
|
const $c5d403787de8b05f$var$provider = new (0, $gXNCa$firebaseauth.GoogleAuthProvider)();
|
|
10207
12215
|
const $c5d403787de8b05f$var$Auth = (props)=>{
|
|
10208
12216
|
const firebaseConfig = props.configuration && props.configuration.firebaseConfig;
|
|
@@ -10313,6 +12321,7 @@ const $693dd8c7a5607c3a$var$inventory = {
|
|
|
10313
12321
|
taxonomy: (0, $a67cad486021eb32$export$2e2bcd8739ae039),
|
|
10314
12322
|
attribs: (0, $67bf5a43401bffdc$export$2e2bcd8739ae039),
|
|
10315
12323
|
expression: (0, $261baeb81c4d4d8a$export$2e2bcd8739ae039),
|
|
12324
|
+
exprViz: (0, $1fd2507769d5bd00$export$2e2bcd8739ae039),
|
|
10316
12325
|
userLists: (0, $0f50f369018a42ef$export$2e2bcd8739ae039),
|
|
10317
12326
|
export: (0, $37b3bb0145d266b0$export$2e2bcd8739ae039)
|
|
10318
12327
|
};
|