mobx#toJS JavaScript Examples
The following examples show how to use
mobx#toJS.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: SyncStore.js From lens-extension-cc with MIT License | 6 votes |
toJSON() {
// throw-away: just to get keys we care about on this
const defaults = SyncStore.getDefaults();
const observableThis = Object.keys(defaults).reduce((obj, key) => {
obj[key] = this[key];
return obj;
}, {});
// return a deep-clone that is no longer observable
// NOTE: it's not "pure JSON" void of Mobx proxy objects, however; the sole purpose of
// this is to let Lens serialize the store to the related JSON file on disk, however
// it does that
return toJS(observableThis);
}
Example #2
Source File: CloudStore.js From lens-extension-cc with MIT License | 6 votes |
toJSON() {
// throw-away: just to get keys we care about on this
const defaults = CloudStore.getDefaults();
const observableThis = Object.keys(defaults).reduce((obj, key) => {
if (key === 'clouds') {
// store from map of cloudUrl to Cloud instance -> into a map of cloudUrl
// to JSON object
obj[key] = Object.keys(this[key]).reduce((jsonMap, cloudUrl) => {
jsonMap[cloudUrl] = this[key][cloudUrl].toJSON();
return jsonMap;
}, {});
} else {
obj[key] = this[key];
}
return obj;
}, {});
// return a deep-clone that is no longer observable
// NOTE: it's not "pure JSON" void of Mobx proxy objects, however; the sole purpose of
// this is to let Lens serialize the store to the related JSON file on disk, however
// it does that
return toJS(observableThis);
}
Example #3
Source File: capture-central-live-events.js From content-components with Apache License 2.0 | 6 votes |
observeQueryParams() {
observe(
rootStore.routingStore,
'queryParams',
async change => {
if (this.loading) {
return;
}
const { searchQuery = '' } = toJS(change.newValue);
if (rootStore.routingStore.getPage() === pageNames.manageLiveEvents) {
await this.reload({ searchQuery });
}
}
);
}
Example #4
Source File: content-filter-dropdown.js From content-components with Apache License 2.0 | 6 votes |
observeQueryParams() {
observe(
rootStore.routingStore,
'queryParams',
change => {
if (this.loading) {
return;
}
const { dateModified = '', dateCreated = '' } =
toJS(change.newValue);
if (dateModified === this.selectedFilterParams.dateModified &&
dateCreated === this.selectedFilterParams.dateCreated
) {
return;
}
this.selectedFilterParams = { dateModified, dateCreated };
}
);
}
Example #5
Source File: content-list.js From content-components with Apache License 2.0 | 6 votes |
observeQueryParams() {
observe(
rootStore.routingStore,
'queryParams',
change => {
if (this.loading) {
return;
}
const {
searchQuery: updatedSearchQuery = '',
sortQuery: updatedSortQuery = 'updatedAt:desc',
dateCreated: updatedDateCreated = '',
dateModified: updatedDateModified = ''
} = toJS(change.newValue);
const { searchQuery, sortQuery, dateCreated, dateModified } =
this.queryParams;
if (updatedSearchQuery === searchQuery && updatedSortQuery === sortQuery &&
updatedDateCreated === dateCreated && updatedDateModified === dateModified
) {
return;
}
this.queryParams = {
searchQuery: updatedSearchQuery,
sortQuery: updatedSortQuery,
dateCreated: updatedDateCreated,
dateModified: updatedDateModified
};
this.reloadPage();
}
);
}
Example #6
Source File: content-list.js From content-components with Apache License 2.0 | 6 votes |
observeSuccessfulUpload() {
observe(
this.uploader,
'successfulUpload',
async change => {
if (change.newValue &&
change.newValue.content &&
!this.areAnyFiltersActive()) {
return this.addNewItemIntoContentItems(toJS(change.newValue));
}
}
);
}
Example #7
Source File: dataStore.js From covidcg with MIT License | 6 votes |
downloadAggLocationGroupDate() {
let locationData;
if (rootStoreInstance.configStore.groupKey === GROUP_MUTATION) {
locationData = toJS(this.aggLocationSingleMutationDate);
// Get mutation data
locationData.forEach((record) => {
let mutation = rootStoreInstance.mutationDataStore.intToMutation(
rootStoreInstance.configStore.dnaOrAa,
rootStoreInstance.configStore.coordinateMode,
record.group_id
);
record.group_name = mutation.name;
record.group = mutation.mutation_str;
});
} else {
locationData = toJS(this.aggLocationGroupDate);
locationData.forEach((record) => {
record.group_name = record.group_id;
record.group = record.group_id;
});
}
let csvString = `location,collection_date,${rootStoreInstance.configStore.getGroupLabel()},${rootStoreInstance.configStore.getGroupLabel()} Name,count\n`;
locationData.forEach((row) => {
csvString += `${row.location},${intToISO(row.collection_date)},${
row.group
},${row.group_name},${row.counts}\n`;
});
const blob = new Blob([csvString]);
const url = URL.createObjectURL(blob);
downloadBlobURL(url, 'data_agg_location_group_date.csv');
}
Example #8
Source File: configStore.js From covidcg with MIT License | 6 votes |
getSelectedMetadataFields() {
const selectedMetadataFields = toJS(this.selectedMetadataFields);
Object.keys(selectedMetadataFields).forEach((metadataField) => {
selectedMetadataFields[metadataField] = selectedMetadataFields[
metadataField
].map((item) => {
return parseInt(item.value);
});
});
return selectedMetadataFields;
}
Example #9
Source File: content-filter-dropdown.js From content-components with Apache License 2.0 | 6 votes |
observeQueryParams() {
observe(
rootStore.routingStore,
'queryParams',
change => {
if (this.loading) {
return;
}
const { contentType = '', dateModified = '', dateCreated = '' } =
toJS(change.newValue);
if (contentType === this.selectedFilterParams.contentType &&
dateModified === this.selectedFilterParams.dateModified &&
dateCreated === this.selectedFilterParams.dateCreated
) {
return;
}
this.selectedFilterParams = { contentType, dateModified, dateCreated };
}
);
}
Example #10
Source File: content-list.js From content-components with Apache License 2.0 | 6 votes |
observeQueryParams() {
observe(
rootStore.routingStore,
'queryParams',
change => {
if (this.loading) {
return;
}
const {
searchQuery: updatedSearchQuery = '',
sortQuery: updatedSortQuery = 'updatedAt:desc',
contentType: updatedContentType = '',
dateCreated: updatedDateCreated = '',
dateModified: updatedDateModified = ''
} = toJS(change.newValue);
const { searchQuery, sortQuery, contentType, dateCreated, dateModified } =
this.queryParams;
if (updatedSearchQuery === searchQuery && updatedSortQuery === sortQuery &&
updatedContentType === contentType && updatedDateCreated === dateCreated &&
updatedDateModified === dateModified
) {
return;
}
this.queryParams = {
searchQuery: updatedSearchQuery,
sortQuery: updatedSortQuery,
contentType: updatedContentType,
dateCreated: updatedDateCreated,
dateModified: updatedDateModified
};
this.reloadPage();
}
);
}
Example #11
Source File: content-list.js From content-components with Apache License 2.0 | 6 votes |
observeSuccessfulUpload() {
observe(
this.uploader,
'successfulUpload',
async change => {
if (change.newValue &&
change.newValue.content &&
!this.areAnyFiltersActive()) {
return this.addNewItemIntoContentItems(toJS(change.newValue));
}
}
);
}
Example #12
Source File: metadataStore.js From covidcg with MIT License | 5 votes |
@action
async fetchMetadataFields() {
rootStoreInstance.UIStore.onMetadataFieldStarted();
fetch(hostname + '/metadata', {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
start_date: toJS(rootStoreInstance.configStore.startDate),
end_date: toJS(rootStoreInstance.configStore.endDate),
subm_start_date: toJS(rootStoreInstance.configStore.submStartDate),
subm_end_date: toJS(rootStoreInstance.configStore.submEndDate),
...rootStoreInstance.configStore.getSelectedLocations(),
selected_metadata_fields:
rootStoreInstance.configStore.getSelectedMetadataFields(),
}),
})
.then((res) => {
if (!res.ok) {
throw res;
}
return res.json();
})
.then((metadataRecords) => {
// Each row in this array of records is structured as:
// { "field", "val_id", "count", "val_str" }
metadataRecords.forEach((record) => {
this.metadataMap.get(record.field).set(record.val_id, record.val_str);
this.metadataCounts
.get(record.field)
.set(record.val_id, record.count);
});
rootStoreInstance.UIStore.onMetadataFieldFinished();
})
.catch((err) => {
if (!(typeof err.text === 'function')) {
console.error(err);
} else {
err.text().then((errMsg) => {
console.error(errMsg);
});
}
rootStoreInstance.UIStore.onMetadataFieldErr();
});
}
Example #13
Source File: dataStore.js From covidcg with MIT License | 5 votes |
downloadVariantTable({ selectedFields, mutationFormat, selectedReference }) {
rootStoreInstance.UIStore.onDownloadStarted();
fetch(hostname + '/variant_table', {
method: 'POST',
headers: {
Accept: 'application/octet-stream',
'Content-Type': 'application/json',
},
body: JSON.stringify({
group_key: toJS(rootStoreInstance.configStore.groupKey),
dna_or_aa: toJS(rootStoreInstance.configStore.dnaOrAa),
coordinate_mode: toJS(rootStoreInstance.configStore.coordinateMode),
coordinate_ranges: rootStoreInstance.configStore.getCoordinateRanges(),
selected_gene: toJS(rootStoreInstance.configStore.selectedGene).name,
selected_protein: toJS(rootStoreInstance.configStore.selectedProtein)
.name,
...rootStoreInstance.configStore.getSelectedLocations(),
selected_reference: selectedReference,
selected_group_fields: toJS(
rootStoreInstance.configStore.selectedGroupFields
),
selected_metadata_fields:
rootStoreInstance.configStore.getSelectedMetadataFields(),
start_date: toJS(rootStoreInstance.configStore.startDate),
end_date: toJS(rootStoreInstance.configStore.endDate),
subm_start_date: toJS(rootStoreInstance.configStore.submStartDate),
subm_end_date: toJS(rootStoreInstance.configStore.submEndDate),
// Pass an array of only the fields that were selected
selected_fields: Object.keys(selectedFields).filter(
(field) => selectedFields[field]
),
mutation_format: mutationFormat,
}),
})
.then((res) => {
if (!res.ok) {
throw res;
}
return res.blob();
})
.then((blob) => {
const url = URL.createObjectURL(blob);
downloadBlobURL(url, 'variant_table.xlsx');
rootStoreInstance.UIStore.onDownloadFinished();
})
.catch((err) => {
let prefix = 'Error downloading variant table';
if (!(typeof err.text === 'function')) {
console.error(prefix, err);
} else {
err.text().then((errMsg) => {
console.error(prefix, errMsg);
});
}
rootStoreInstance.UIStore.onDownloadErr();
});
}
Example #14
Source File: MainQna.js From front-app with MIT License | 5 votes |
MainQna = observer(() => {
const { chatStore, userStore } = useStores()
const { chatList: storeChatList, qnaList, setQnaList } = chatStore
const { socket } = userStore
usePageTracking('qna')
const removeMsg = async (id) => {
const { isConfirmed } = await Swal.fire({
title: '정말 삭제 하시겠습니까?',
showCancelButton: true,
})
if (isConfirmed) socket.emit('delete message', { msgId: id })
}
// 웨비나 중간에 채팅 초기화 버튼을 누르면 qna 리스트는 초기화 안됨.
useEffect(() => {
getAllChat().then(({ chatList }) => {
if (toJS(qnaList).length !== chatList.filter((chatData) => chatData.question === true).length) {
// console.log('update qna', toJS(qnaList), chatList.filter((chatData) => chatData.question === true))
setQnaList(chatList)
}
})
}, [])
return (
<div className={'qnaContainer'}>
{qnaList &&
qnaList.map((qna, idx) => (
<div key={`qna_${idx}`} className={'qnaBox'}>
<div className={'qnaBox__header'}>
{getTokenVerification().length > 0 && (
<img
src={xIcon}
alt={'xIcon'}
onClick={() => {
removeMsg(qna.msg_id)
}}
/>
)}
</div>
<div className={'qnaBox__contents'}>Q. {qna.text}</div>
</div>
))}
</div>
)
})
Example #15
Source File: routing-store.test.js From content-components with Apache License 2.0 | 5 votes |
describe('Routing Store', () => {
let store;
beforeEach(() => {
store = new RoutingStore();
});
it('routing ctx is saved', () => {
const testCtx = {
pathname: '/d2l/contentstore/main',
querystring: ''
};
store.setRouteCtx(testCtx);
assert.deepEqual(toJS(store.routeCtx), testCtx);
});
it('with only page', () => {
store.setRouteCtx({
pathname: '/d2l/contentstore/404',
querystring: ''
});
assert.equal(store.page, '404');
assert.equal(store.subView, '');
assert.deepEqual(store.queryParams, {});
});
it('with query params', () => {
store.setRouteCtx({
pathname: '/d2l/contentstore/123/files',
querystring: 'foo=bar&number=5'
});
assert.equal(store.page, '123');
assert.equal(store.subView, 'files');
assert.deepEqual(toJS(store.queryParams), { foo: 'bar', number: '5' });
});
it('without query params', () => {
store.setRouteCtx({
pathname: '/d2l/contentstore/manage/files',
querystring: ''
});
assert.equal(store.page, 'manage');
assert.equal(store.subView, 'files');
assert.deepEqual(store.queryParams, {});
});
});
Example #16
Source File: routing-store.js From content-components with Apache License 2.0 | 5 votes |
getQueryParams() {
return toJS(this.queryParams);
}
Example #17
Source File: uploader.js From content-components with Apache License 2.0 | 5 votes |
getUploads() {
return toJS(this.uploads);
}
Example #18
Source File: uploader.js From content-components with Apache License 2.0 | 5 votes |
getSuccessfulUpload() {
return toJS(this.successfulUpload);
}
Example #19
Source File: routing-store.js From content-components with Apache License 2.0 | 5 votes |
getQueryParams() {
return toJS(this.queryParams);
}
Example #20
Source File: permission-store.js From content-components with Apache License 2.0 | 5 votes |
getPermissions() {
return toJS(this.permissions);
}
Example #21
Source File: LegendContainer.js From covidcg with MIT License | 4 votes |
LegendContainer = observer(() => {
const { dataStore, UIStore, configStore, groupDataStore, mutationDataStore } =
useStores();
const [state, setState] = useState({
legendItems: [],
sortColumn: COLUMN_NAMES.COUNTS,
sortDir: SORT_DIRECTIONS.SORT_DESC,
});
const updateHoverGroup = debounce((group) => {
configStore.updateHoverGroup(group);
}, 10);
const onClickColumnHeader = ({ columnName }) => {
if (state.sortColumn === columnName) {
if (state.sortDir === SORT_DIRECTIONS.SORT_ASC) {
setState({ ...state, sortDir: SORT_DIRECTIONS.SORT_DESC });
} else {
setState({ ...state, sortDir: SORT_DIRECTIONS.SORT_ASC });
}
} else {
setState({
...state,
sortColumn: columnName,
sortDir: SORT_DIRECTIONS.SORT_DESC,
});
}
};
const onItemSelect = (e) => {
const selectedGroup = e.target.getAttribute('data-group');
let newGroups;
// If the click was not on an item, then unset the selection
if (selectedGroup === null) {
newGroups = [];
}
// If the item is already selected, then deselect it
else if (
configStore.selectedGroups.find(
(group) => group.group === selectedGroup
) !== undefined
) {
newGroups = configStore.selectedGroups.filter(
(group) => !(group.group === selectedGroup)
);
} else {
// Otherwise, add it
newGroups = [{ group: selectedGroup }];
// If shift is pressed, then add it to the existing selected groups
if (UIStore.isKeyPressed(16)) {
newGroups = newGroups.concat(configStore.selectedGroups);
}
}
configStore.updateSelectedGroups(newGroups);
};
const getLegendKeys = () => {
// Make own copy of the elements, and sort by group
let legendItems = toJS(dataStore.groupCounts);
// groupCounts is structured as:
// [{ group_id, counts }]
// Where group_id is either a mutation ID (in mutation mode)
// or a string representing e.g. a lineage
// Get some additional data:
// 1) Group Name (get mutation name from mutation ID if in mutation mode)
// 2) Color
// 3) Calculate percent based off counts and total sequences
// 4) mutation gene/protein (for sorting mutations - AA mutation mode only)
// 5) mutation position (for sorting mutations - mutation mode only)
if (configStore.groupKey === GROUP_MUTATION) {
legendItems.forEach((record) => {
let mut = mutationDataStore.intToMutation(
configStore.dnaOrAa,
configStore.coordinateMode,
record.group_id
);
record.group = mut.mutation_str;
record.group_name = mut.name;
record.color = mut.color;
record.percent =
record.counts / dataStore.numSequencesAfterAllFiltering;
record.pos = mut.pos;
// If we're in DNA mode, then leave this null
// Otherwise, get the gene or protein, depending on our AA mode
record.gene_or_protein =
configStore.dnaOrAa === DNA_OR_AA.DNA
? null
: configStore.coordinateMode === COORDINATE_MODES.COORD_GENE
? mut.gene
: mut.protein;
});
} else {
legendItems.forEach((record) => {
// For non-mutation groups, the name is the same as the ID
record.group = record.group_id;
record.group_name = record.group_id;
record.color = groupDataStore.getGroupColor(
configStore.groupKey,
record.group_id
);
record.percent =
record.counts / dataStore.numSequencesAfterAllFiltering;
});
}
// Set aside the reference, and remove it from the rows list
// Also set aside the "Other" group, if it exists
// Sort the list, then add the reference back to the beginning
// and add the other group back to the end
const refItem = legendItems.find(
(item) => item.group === GROUPS.REFERENCE_GROUP
);
const otherItem = legendItems.find(
(item) => item.group === GROUPS.OTHER_GROUP
);
legendItems = legendItems.filter(
(item) =>
!(
item.group === GROUPS.REFERENCE_GROUP ||
item.group === GROUPS.OTHER_GROUP
)
);
legendItems = legendItems.sort(
comparer({
sortColumn: state.sortColumn,
sortDirection: state.sortDir,
groupKey: configStore.groupKey,
dnaOrAa: configStore.dnaOrAa,
coordinateMode: configStore.coordinateMode,
})
);
if (refItem !== undefined) {
legendItems.unshift(refItem);
}
if (otherItem !== undefined) {
legendItems.push(otherItem);
}
return legendItems;
};
useEffect(() => {
let _arr = [...state.legendItems];
_arr = _arr.sort(
comparer({
sortColumn: state.sortColumn,
sortDirection: state.sortDir,
groupKey: configStore.groupKey,
dnaOrAa: configStore.dnaOrAa,
coordinateMode: configStore.coordinateMode,
})
);
setState({ ...state, legendItems: _arr });
}, [state.sortColumn, state.sortDir]);
useEffect(() => {
if (UIStore.caseDataState !== ASYNC_STATES.SUCCEEDED) {
return;
}
setState({
...state,
legendItems: getLegendKeys().sort(
comparer({
sortColumn: state.sortColumn,
sortDirection: state.sortDir,
groupKey: configStore.groupKey,
dnaOrAa: configStore.dnaOrAa,
coordinateMode: configStore.coordinateMode,
})
),
});
}, [UIStore.caseDataState]);
if (UIStore.caseDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
height: '100%',
}}
>
{Array.from({ length: 50 }, (_, i) => (
<SkeletonElement
key={`legend-loading-bar-${i}`}
delay={5 + i + (i % 2) * 12.5}
height={25}
/>
))}
</div>
);
}
return (
<TableLegendContainer>
<TableLegend
legendItems={state.legendItems}
updateHoverGroup={updateHoverGroup}
updateSelectGroup={onItemSelect}
sortColumn={state.sortColumn}
sortDir={state.sortDir}
onClickColumnHeader={onClickColumnHeader}
/>
</TableLegendContainer>
);
})
Example #22
Source File: CooccurrencePlot.js From covidcg with MIT License | 4 votes |
CooccurrencePlot = observer(({ width }) => {
const vegaRef = useRef();
const { configStore, dataStore, plotSettingsStore, UIStore } = useStores();
const handleDownloadSelect = (option) => {
// console.log(option);
// TODO: use the plot options and configStore options to build a more descriptive filename
// something like new_lineages_by_day_S_2020-05-03-2020-05-15_NYC.png...
if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA) {
dataStore.downloadMutationCooccurrence();
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG) {
vegaRef.current.downloadImage('png', 'vega-export.png', 1);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 2);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 4);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG) {
vegaRef.current.downloadImage('svg', 'vega-export.svg');
}
};
const onChangeNormMode = (event) =>
plotSettingsStore.setCooccurrenceNormMode(event.target.value);
const handleHoverGroup = (...args) => {
configStore.updateHoverGroup(args[1] === null ? null : args[1]['group']);
};
const handleSelectedGroups = (...args) => {
const curSelectedGroups = toJS(configStore.selectedGroups).map(
(item) => item.group
);
// console.log(curSelectedGroups);
// Some of the groups might be multiple mutations, where the
// group string is the two mutations separated by ' + '
// So break those up and then combine into one big list
// Also make sure we don't process "Reference" or "Other"
// Array -> Set -> Array to remove duplicates
const newSelectedGroups = Array.from(
new Set(
args[1]
.map((item) => item.group.split(' + '))
.reduce((memo, arr) => memo.concat(arr), [])
.filter((item) => {
return (
item !== GROUPS.REFERENCE_GROUP && item !== GROUPS.OTHER_GROUP
);
})
)
);
// console.log(newSelectedGroups);
const groupsInCommon = newSelectedGroups
.slice()
.filter((group) => curSelectedGroups.includes(group));
// The new selection to push to the store is
// (cur u new) - (cur n new)
// i.e., the union minus the intersection (XOR)
// Array -> Set -> Array to remove duplicates
// Then wrap in an object of { group: group }
const pushSelectedGroups = Array.from(
new Set(
curSelectedGroups
.concat(newSelectedGroups)
.filter((group) => !groupsInCommon.includes(group))
)
).map((group) => {
return { group };
});
// console.log(pushSelectedGroups);
configStore.updateSelectedGroups(pushSelectedGroups);
};
const getCooccurrenceData = () => {
// console.log('GET COOCCURRENCE DATA');
let newCooccurrenceData = toJS(dataStore.mutationCooccurrence);
newCooccurrenceData = aggregate({
data: newCooccurrenceData,
groupby: ['combi', 'mutation', 'mutationName'],
fields: ['combiName', 'mutationName', 'color', 'count'],
ops: ['max', 'max', 'max', 'sum'],
as: ['combiName', 'mutationName', 'color', 'count'],
});
return newCooccurrenceData;
};
const [state, setState] = useState({
data: {},
hoverGroup: null,
signalListeners: {
hoverGroup: throttle(handleHoverGroup, 50),
},
dataListeners: {
selectedGroups: handleSelectedGroups,
},
});
useEffect(() => {
setState({
...state,
hoverGroup: { group: configStore.hoverGroup },
});
}, [configStore.hoverGroup]);
const refreshData = () => {
// Only update once the mutation data finished processing
if (UIStore.cooccurrenceDataState !== ASYNC_STATES.SUCCEEDED) {
return;
}
setState({
...state,
data: {
//selectedGroups: toJS(configStore.selectedGroups),
selectedGroups: [],
cooccurrence_data: getCooccurrenceData(),
},
});
};
// Refresh data on mount (i.e., tab change) or when data state changes
useEffect(refreshData, [UIStore.cooccurrenceDataState]);
useEffect(refreshData, []);
if (UIStore.cooccurrenceDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
paddingTop: '12px',
paddingRight: '24px',
paddingLeft: '12px',
paddingBottom: '24px',
}}
>
<SkeletonElement delay={2} height={70} />
</div>
);
}
if (configStore.selectedGroups.length === 0) {
return (
<EmptyPlot height={70}>
<p>
No mutations selected. Please select one or more mutations from the
legend, frequency plot, or table.
</p>
</EmptyPlot>
);
} else if (dataStore.mutationCooccurrence.length === 0) {
return (
<EmptyPlot height={70}>
<p>
No mutations that co-occur with selected mutations{' '}
{configStore.selectedGroups
.map((item) => formatMutation(item.group, configStore.dnaOrAa))
.join(', ')}
</p>
</EmptyPlot>
);
}
// Signals
let stackOffset, xLabel, xFormat;
if (plotSettingsStore.cooccurrenceNormMode === NORM_MODES.NORM_COUNTS) {
stackOffset = 'zero';
xLabel = 'Mutation Frequency';
xFormat = 's';
} else if (
plotSettingsStore.cooccurrenceNormMode === NORM_MODES.NORM_PERCENTAGES
) {
stackOffset = 'normalize';
xLabel = 'Mutation Frequency (Normalized)';
xFormat = 's';
}
// Subtitle text
const maxShownMutations = 4;
let subtitle = configStore.selectedGroups
.slice(0, maxShownMutations)
.map((item) => formatMutation(item.group, configStore.dnaOrAa))
.join(', ');
if (configStore.selectedGroups.length > maxShownMutations) {
subtitle += ', ...';
} else {
subtitle += '';
}
return (
<PlotContainer>
<PlotOptions>
<PlotTitle>
<span className="title">Co-occurring mutations of {subtitle}</span>
</PlotTitle>
Show mutations as{' '}
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.cooccurrenceNormMode}
onChange={onChangeNormMode}
>
<option value={NORM_MODES.NORM_COUNTS}>Counts</option>
<option value={NORM_MODES.NORM_PERCENTAGES}>
Normalized Counts
</option>
</select>
</label>
</OptionSelectContainer>
<div className="spacer"></div>
<DropdownButton
text={'Download'}
options={[
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG,
]}
onSelect={handleDownloadSelect}
/>
</PlotOptions>
<VegaEmbed
ref={vegaRef}
width={width}
spec={initialSpec}
data={state.data}
signalListeners={state.signalListeners}
dataListeners={state.dataListeners}
signals={{
dna: configStore.dnaOrAa === DNA_OR_AA.DNA,
hoverGroup: state.hoverGroup,
stackOffset,
xLabel,
xFormat,
}}
actions={false}
/>
</PlotContainer>
);
})
Example #23
Source File: EntropyPlot.js From covidcg with MIT License | 4 votes |
EntropyPlot = observer(({ width }) => {
const vegaRef = useRef();
const {
configStore,
dataStore,
UIStore,
mutationDataStore,
plotSettingsStore,
} = useStores();
const onDismissWarning = () => {
setState({
...state,
showWarning: false,
});
};
const handleDownloadSelect = (option) => {
// console.log(option);
// TODO: use the plot options and configStore options to build a more descriptive filename
// something like new_lineages_by_day_S_2020-05-03-2020-05-15_NYC.png...
if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA) {
dataStore.downloadMutationFrequencies();
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG) {
vegaRef.current.downloadImage('png', 'vega-export.png', 1);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 2);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 4);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG) {
vegaRef.current.downloadImage('svg', 'vega-export.svg');
}
};
const processData = () => {
let mutationCounts = toJS(dataStore.groupCounts);
return mutationCounts.filter((record) => {
return (
record.group !== GROUPS.OTHER_GROUP &&
record.group !== GROUPS.REFERENCE_GROUP
);
});
};
const handleHoverGroup = (...args) => {
// Don't fire the action if there's no change
let hoverGroup = args[1] === null ? null : args[1]['group'];
if (hoverGroup === configStore.hoverGroup) {
return;
}
configStore.updateHoverGroup(hoverGroup);
};
const handleSelected = (...args) => {
// console.log(args[1], toJS(configStore.selectedGroups));
const curSelectedGroups = args[1].map((item) => {
return { group: item.group };
});
configStore.updateSelectedGroups(curSelectedGroups);
};
const onChangeEntropyYMode = (event) => {
plotSettingsStore.setEntropyYMode(event.target.value);
};
const onChangeEntropyYPow = (event) => {
plotSettingsStore.setEntropyYPow(event.target.value);
};
// Domain Plot height is calculated as the number of rows times a constant
const domainPlotRowHeight = 15;
const getDomainPlotHeight = () => {
// There will always be at least 1 row (nullDomain displays when no rows)
let numRows = 1;
// Logic for Primer track
if (configStore.coordinateMode === COORDINATE_MODES.COORD_PRIMER) {
if (configStore.selectedPrimers.length > 0) {
const primerObj = configStore.selectedPrimers;
primerObj.forEach((primer) => {
if (primer.row + 1 > numRows) {
numRows = primer.row + 1;
}
});
return numRows * domainPlotRowHeight;
} else {
return domainPlotRowHeight;
}
} else if (
configStore.coordinateMode === COORDINATE_MODES.COORD_GENE ||
configStore.coordinateMode === COORDINATE_MODES.COORD_PROTEIN
) {
// Logic for Gene/Protein track
let geneProteinObj = null;
if (configStore.coordinateMode === COORDINATE_MODES.COORD_GENE) {
geneProteinObj = configStore.selectedGene;
} else if (
configStore.coordinateMode === COORDINATE_MODES.COORD_PROTEIN
) {
geneProteinObj = configStore.selectedProtein;
}
// Greedily get the number of rows
if (geneProteinObj && geneProteinObj.domains.length > 0) {
geneProteinObj.domains.forEach((domain) => {
// geneProtein[row] is zero-indexed so add 1 to get total number of rows
if (domain['row'] + 1 > numRows) {
numRows = domain['row'] + 1;
}
});
}
}
return numRows * domainPlotRowHeight;
};
const getXRange = () => {
// Apply xRange
let xRange;
if (configStore.residueCoordinates.length === 0) {
// If the residue coordinates are empty, then either "All Genes" or
// "All Proteins" is selected -- so show everything
xRange = [1, 30000];
} else if (configStore.dnaOrAa === DNA_OR_AA.DNA) {
const coordRanges = toJS(configStore.getCoordinateRanges());
xRange = [
coordRanges.reduce((memo, rng) => Math.min(...rng, memo), 30000),
coordRanges.reduce((memo, rng) => Math.max(...rng, memo), 0),
];
} else if (configStore.dnaOrAa === DNA_OR_AA.AA) {
// Get the extent of the selected gene/protein
let geneOrProteinObj;
let residueCoordinates = toJS(configStore.residueCoordinates);
if (configStore.coordinateMode === COORDINATE_MODES.COORD_GENE) {
geneOrProteinObj = configStore.selectedGene;
} else if (
configStore.coordinateMode === COORDINATE_MODES.COORD_PROTEIN
) {
geneOrProteinObj = configStore.selectedProtein;
}
// Find the smallest and largest residue index
// from the selected residue coordinates
const minResidueIndex = residueCoordinates.reduce(
(minIndex, rng) => Math.min(...rng, minIndex),
geneOrProteinObj.len_aa
);
const maxResidueIndex = residueCoordinates.reduce(
(minIndex, rng) => Math.max(...rng, minIndex),
1
);
if (configStore.dnaOrAa === DNA_OR_AA.DNA) {
// Find the AA range that the minimum and maximum AA index fall in,
// and then get the NT coordinates (from the start of the codon)
let startNTInd, endNTInd;
geneOrProteinObj.aa_ranges.forEach((aaRange, ind) => {
if (minResidueIndex >= aaRange[0] && minResidueIndex <= aaRange[1]) {
// Get the matching NT range, add residues * 3
startNTInd =
geneOrProteinObj.ranges[ind][0] +
(minResidueIndex - aaRange[0]) * 3;
}
if (maxResidueIndex >= aaRange[0] && maxResidueIndex <= aaRange[1]) {
// Get the matching NT range, add residues * 3 (to end of codon)
endNTInd =
geneOrProteinObj.ranges[ind][0] +
2 +
(maxResidueIndex - aaRange[0]) * 3;
}
});
xRange = [startNTInd, endNTInd];
} else if (configStore.dnaOrAa === DNA_OR_AA.AA) {
xRange = [minResidueIndex, maxResidueIndex];
}
}
return xRange;
};
const getDomains = () => {
// Apply domains
const xRange = getXRange();
const nullDomain = [
{
name: 'No Domains Available',
abbr: 'No Domains Available',
ranges: [xRange],
row: 0,
},
];
if (configStore.coordinateMode === COORDINATE_MODES.COORD_GENE) {
return configStore.selectedGene.domains.length > 0
? configStore.selectedGene.domains
: nullDomain;
} else if (configStore.coordinateMode === COORDINATE_MODES.COORD_PROTEIN) {
return configStore.selectedProtein.domains.length > 0
? configStore.selectedProtein.domains
: nullDomain;
} else {
return nullDomain;
}
};
const checkIfPrimersOverlap = (primer1, primer2) => {
return (
(primer1.Start < primer2.End && primer1.End > primer2.End) ||
(primer1.Start < primer2.Start && primer1.End > primer2.Start)
);
};
const getPrimers = () => {
const selectedPrimers = configStore.selectedPrimers;
if (selectedPrimers.length) {
selectedPrimers[0].row = 0;
for (let i = 0; i < selectedPrimers.length; i++) {
let overlaps = true;
let curRow = 0;
const primerToPlace = selectedPrimers[i];
while (overlaps) {
const primersInRow = selectedPrimers.filter(
(primer) => primer.row && primer.row === curRow
);
if (primersInRow.length) {
for (const primer of primersInRow) {
overlaps = checkIfPrimersOverlap(primer, primerToPlace);
if (!overlaps) break;
}
} else {
overlaps = false;
}
if (overlaps) curRow += 1;
}
primerToPlace.row = curRow;
primerToPlace.ranges = [[primerToPlace.Start, primerToPlace.End]];
primerToPlace.name = primerToPlace.Name;
selectedPrimers[i] = primerToPlace;
}
return selectedPrimers;
} else {
const nullDomain = [
{
Institution: 'None',
Name: 'No Primers Selected',
ranges: [[0, 30000]],
row: 0,
Start: 0,
End: 30000,
},
];
configStore.selectedPrimers = nullDomain;
return nullDomain;
}
};
const domainsToShow = () => {
return configStore.coordinateMode === COORDINATE_MODES.COORD_PRIMER
? getPrimers()
: getDomains();
};
const [state, setState] = useState({
showWarning: true,
xRange: getXRange(),
hoverGroup: null,
data: { domains: domainsToShow() },
domainPlotHeight: getDomainPlotHeight(),
signalListeners: {
hoverGroup: throttle(handleHoverGroup, 100),
},
dataListeners: {
selected: handleSelected,
},
});
useEffect(() => {
setState({
...state,
hoverGroup: { group: configStore.hoverGroup },
});
}, [configStore.hoverGroup]);
// Update internal selected groups copy
useEffect(() => {
setState({
...state,
data: {
...state.data,
selected: toJS(configStore.selectedGroups),
},
});
}, [configStore.selectedGroups]);
const refreshData = () => {
if (UIStore.caseDataState !== ASYNC_STATES.SUCCEEDED) {
return;
}
setState({
...state,
xRange: getXRange(),
domainPlotHeight: getDomainPlotHeight(),
data: {
...state.data,
domains: domainsToShow(),
table: processData(),
coverage: mutationDataStore.coverage,
},
});
};
// Refresh data on mount (i.e., tab change) or when data state changes
useEffect(refreshData, [UIStore.caseDataState]);
useEffect(refreshData, []);
// Generate x-axis title
let xLabel = '';
if (configStore.dnaOrAa === DNA_OR_AA.DNA) {
xLabel += 'NT';
} else if (configStore.dnaOrAa === DNA_OR_AA.AA) {
xLabel += 'AA residue';
}
xLabel += ' (' + configStore.selectedReference;
if (configStore.coordinateMode === COORDINATE_MODES.COORD_GENE) {
xLabel += ', ' + configStore.selectedGene.name + ' Gene';
} else if (configStore.coordinateMode === COORDINATE_MODES.COORD_PROTEIN) {
xLabel += ', ' + configStore.selectedProtein.name + ' Protein';
}
xLabel += ')';
if (UIStore.caseDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
paddingTop: '12px',
paddingRight: '24px',
paddingLeft: '12px',
paddingBottom: '24px',
}}
>
<SkeletonElement delay={2} height={150} />
</div>
);
}
// If we have no rows, then return an empty element
// We'll always have the "reference" row, so no rows = 1 row
if (dataStore.numSequencesAfterAllFiltering === 0) {
return (
<EmptyPlot height={150}>
<p>No sequences selected</p>
</EmptyPlot>
);
}
const entropyPlotHeight = 120;
const coveragePlotHeight = 40;
const padding = 40;
return (
<PlotContainer>
{config['virus'] === 'sars2' && (
<WarningBox show={state.showWarning} onDismiss={onDismissWarning}>
{config.site_title} plots reflect data contributed to GISAID and are
therefore impacted by the sequence coverage in each country. For
example, systematic errors are sometimes observed specific to
particular labs or methods (
<ExternalLink href="https://virological.org/t/issues-with-sars-cov-2-sequencing-data/473/14">
https://virological.org/t/issues-with-sars-cov-2-sequencing-data/473/14
</ExternalLink>
,{' '}
<ExternalLink href="https://doi.org/10.1371/journal.pgen.1009175">
https://doi.org/10.1371/journal.pgen.1009175
</ExternalLink>
). Users are advised to consider these errors in their high resolution
analyses.
</WarningBox>
)}
<PlotOptions>
<OptionSelectContainer>
<label>
Show:
<select
value={plotSettingsStore.entropyYMode}
onChange={onChangeEntropyYMode}
>
<option value={NORM_MODES.NORM_COUNTS}>Counts</option>
<option value={NORM_MODES.NORM_PERCENTAGES}>Percents</option>
<option value={NORM_MODES.NORM_COVERAGE_ADJUSTED}>
Percents (coverage adjusted)
</option>
</select>
</label>
<QuestionButton
data-tip={`
<ul>
<li>
Counts: show raw mutation counts
</li>
<li>
Percents: show mutation counts as a percentage of
the total number of sequences selected
</li>
<li>
Percents (coverage adjusted): show mutation counts as a
percentage of sequences with coverage at the mutation position
</li>
</ul>
`}
data-html="true"
data-for="main-tooltip"
/>
</OptionSelectContainer>
<OptionInputContainer style={{ marginLeft: '10px' }}>
<label>
Y-scale:
<input
value={plotSettingsStore.entropyYPow}
type="number"
step={0.1}
min={0}
onChange={onChangeEntropyYPow}
></input>
</label>
<QuestionButton
data-tip={`
<ul>
<li>
Y-scale: adjust the power of the y-axis.
</li>
<li>
For Y-scale = 1, the y-axis scale is linear.
</li>
<li>
For Y-scale < 1, lower values are more visible.
</li>
<li>
For Y-scale > 1, lower values are less visible.
</li>
</ul>
`}
data-html="true"
data-for="main-tooltip"
/>
</OptionInputContainer>
<div className="spacer"></div>
<DropdownButton
text={'Download'}
options={[
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG,
]}
onSelect={handleDownloadSelect}
/>
</PlotOptions>
<VegaEmbed
ref={vegaRef}
spec={initialSpec}
data={state.data}
width={width}
height={
entropyPlotHeight +
padding +
state.domainPlotHeight +
padding +
coveragePlotHeight
}
signals={{
yMode: plotSettingsStore.entropyYMode,
yScaleExponent: plotSettingsStore.entropyYPow,
totalSequences: dataStore.numSequencesAfterAllFiltering,
xLabel,
xRange: state.xRange,
hoverGroup: state.hoverGroup,
numDomainRows: state.domainPlotHeight / domainPlotRowHeight,
domainPlotHeight: state.domainPlotHeight,
posField:
configStore.dnaOrAa === DNA_OR_AA.DNA &&
configStore.residueCoordinates.length !== 0 &&
configStore.coordinateMode !== COORDINATE_MODES.COORD_PRIMER
? 0
: 1,
}}
signalListeners={state.signalListeners}
dataListeners={state.dataListeners}
actions={false}
/>
</PlotContainer>
);
})
Example #24
Source File: GroupStackPlot.js From covidcg with MIT License | 4 votes |
GroupStackPlot = observer(({ width }) => {
const vegaRef = useRef();
const { dataStore, UIStore, configStore, plotSettingsStore, groupDataStore } =
useStores();
const handleHoverGroup = (...args) => {
// Don't fire the action if there's no change
configStore.updateHoverGroup(args[1] === null ? null : args[1]['group']);
};
const handleSelected = (...args) => {
// Ignore selections in mutation mode
if (configStore.groupKey === GROUP_MUTATION) {
return;
}
configStore.updateSelectedGroups(args[1]);
};
const handleDownloadSelect = (option) => {
// console.log(option);
// TODO: use the plot options and configStore options to build a more descriptive filename
// something like new_lineages_by_day_S_2020-05-03-2020-05-15_NYC.png...
if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA) {
dataStore.downloadAggGroupDate();
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG) {
vegaRef.current.downloadImage('png', 'vega-export.png', 1);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 2);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 4);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG) {
vegaRef.current.downloadImage('svg', 'vega-export.svg');
}
};
const processData = () => {
// console.log('GROUP STACK PROCESS DATA');
if (configStore.groupKey === GROUP_MUTATION) {
let data = toJS(dataStore.aggLocationSelectedMutationsDate);
// Filter focused locations
const focusedLocations = state.focusLocationTree
.filter((node) => node.checked)
.map((node) => node.value);
data = data.filter((record) =>
focusedLocations.includes(record.location)
);
// Re-aggregate
data = aggregate({
data,
groupby: ['group', 'collection_date'],
fields: ['counts', 'color', 'group_name'],
ops: ['sum', 'first', 'first'],
as: ['counts', 'color', 'group_name'],
});
// console.log(JSON.stringify(data));
return data;
}
// For non-mutation mode, we'll need some additional fields:
// 1) color of group
// 2) name of group (same as group id)
// Also collapse low-frequency groups based on settings
const validGroups = getValidGroups({
records: dataStore.groupCounts,
lowFreqFilterType: plotSettingsStore.groupStackLowFreqFilter,
lowFreqFilterValue: plotSettingsStore.groupStackLowFreqValue,
});
let data = toJS(dataStore.aggLocationGroupDate);
// Filter focused locations
const focusedLocations = state.focusLocationTree
.filter((node) => node.checked)
.map((node) => node.value);
data = data.filter((record) => focusedLocations.includes(record.location));
data = data.map((record) => {
if (!validGroups.includes(record.group_id)) {
record.group = GROUPS.OTHER_GROUP;
} else {
record.group = record.group_id;
}
record.group_name = record.group;
record.color = groupDataStore.getGroupColor(
configStore.groupKey,
record.group
);
return record;
});
data = aggregate({
data,
groupby: ['group', 'collection_date'],
fields: ['counts', 'color', 'group_name'],
ops: ['sum', 'first', 'first'],
as: ['counts', 'color', 'group_name'],
});
// console.log(JSON.stringify(data));
return data;
};
const [state, setState] = useState({
showWarning: true,
// data: {},
hoverGroup: null,
focusLocationTree: [],
signalListeners: {
// detailDomain: debounce(handleBrush, 500),
hoverBar: throttle(handleHoverGroup, 100),
},
dataListeners: {
selected: handleSelected,
},
});
// Update state based on the focused location dropdown select
const focusLocationSelectOnChange = (event) => {
const focusLocationTree = state.focusLocationTree.slice();
focusLocationTree.forEach((node) => {
if (node.value === event.value) {
node.checked = event.checked;
}
});
setState({
...state,
focusLocationTree,
});
};
// Deselect all focused locations
const deselectAllFocusedLocations = () => {
const focusLocationTree = state.focusLocationTree.slice().map((node) => {
node.checked = false;
return node;
});
setState({
...state,
focusLocationTree,
});
};
const onDismissWarning = () => {
setState({
...state,
showWarning: false,
});
};
const onChangeNormMode = (event) =>
plotSettingsStore.setGroupStackNormMode(event.target.value);
const onChangeCountMode = (event) =>
plotSettingsStore.setGroupStackCountMode(event.target.value);
const onChangeDateBin = (event) =>
plotSettingsStore.setGroupStackDateBin(event.target.value);
useEffect(() => {
setState({
...state,
hoverGroup: { group: configStore.hoverGroup },
});
}, [configStore.hoverGroup]);
// Update internal selected groups copy
useEffect(() => {
// console.log('SELECTED GROUPS');
// Skip this update if we're in mutation mode
if (configStore.groupKey === GROUP_MUTATION) {
return;
}
setState({
...state,
data: {
...state.data,
selected: toJS(configStore.selectedGroups),
},
});
}, [configStore.selectedGroups]);
const refreshData = () => {
// Skip unless the mutation data finished processing
if (
configStore.groupKey === GROUP_MUTATION &&
UIStore.mutationDataState !== ASYNC_STATES.SUCCEEDED
) {
return;
}
if (UIStore.caseDataState !== ASYNC_STATES.SUCCEEDED) {
return;
}
// console.log('REFRESH DATA FROM STORE');
// Update focus location tree with new locations
const focusLocationTree = [];
Object.keys(dataStore.countsPerLocationMap).forEach((loc) => {
focusLocationTree.push({
label: loc + ' (' + dataStore.countsPerLocationMap[loc] + ')',
value: loc,
checked: true,
});
});
const newState = {
...state,
data: {
cases_by_date_and_group: processData(),
selected: toJS(configStore.selectedGroups),
},
};
// Only update location tree if the location list changed
// This is to prevent firing the processData event twice when we change selected groups
const prevLocs = state.focusLocationTree.map((node) => node.value);
if (
focusLocationTree.length !== state.focusLocationTree.length ||
!focusLocationTree.every((node) => prevLocs.includes(node.value))
) {
newState['focusLocationTree'] = focusLocationTree;
}
setState(newState);
};
useEffect(() => {
// console.log('UPDATE DATA FROM FOCUSED LOCATIONS');
setState({
...state,
data: {
...state.data,
cases_by_date_and_group: processData(),
},
});
}, [state.focusLocationTree]);
const focusLocationDropdownContainer = (
<StyledDropdownTreeSelect
mode={'multiSelect'}
data={state.focusLocationTree}
className="geo-dropdown-tree-select"
clearSearchOnChange={false}
keepTreeOnSearch={true}
keepChildrenOnSearch={true}
showPartiallySelected={false}
inlineSearchInput={false}
texts={{
placeholder: 'Search...',
noMatches: 'No matches found',
}}
onChange={focusLocationSelectOnChange}
// onAction={treeSelectOnAction}
// onNodeToggle={treeSelectOnNodeToggleCurrentNode}
/>
);
// Refresh data on mount (i.e., tab change) or when data state changes
useEffect(refreshData, [
UIStore.caseDataState,
UIStore.mutationDataState,
plotSettingsStore.groupStackLowFreqFilter,
plotSettingsStore.groupStackLowFreqValue,
]);
useEffect(refreshData, []);
// For development in Vega Editor
// console.log(JSON.stringify(caseData));
if (UIStore.caseDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
paddingTop: '12px',
paddingRight: '24px',
paddingLeft: '12px',
paddingBottom: '24px',
}}
>
<SkeletonElement delay={2} height={400} />
</div>
);
}
if (configStore.selectedLocationNodes.length === 0) {
return (
<EmptyPlot height={250}>
<p>
No locations selected. Please select one or more locations from the
sidebar, under "Selected Locations", to compare counts of{' '}
<b>{configStore.getGroupLabel()}</b> between them.
</p>
</EmptyPlot>
);
}
let plotTitle = '';
if (plotSettingsStore.groupStackCountMode === COUNT_MODES.COUNT_CUMULATIVE) {
plotTitle += 'Cumulative ';
} else if (plotSettingsStore.groupStackCountMode === COUNT_MODES.COUNT_NEW) {
plotTitle += 'New ';
}
plotTitle += configStore.getGroupLabel();
plotTitle +=
plotSettingsStore.groupStackNormMode === NORM_MODES.NORM_PERCENTAGES
? ' Percentages'
: ' Counts';
if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_DAY) {
plotTitle += ' by Day';
} else if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_WEEK) {
plotTitle += ' by Week';
} else if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_MONTH) {
plotTitle += ' by Month';
} else if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_YEAR) {
plotTitle += ' by Year';
}
const maxShownLocations = 4;
let selectedLocationsText = '';
if (!state.focusLocationTree.every((node) => node.checked === false)) {
selectedLocationsText +=
'(' +
state.focusLocationTree
.filter((node) => node.checked)
.slice(0, maxShownLocations)
.map((node) => node.value)
.join(', ');
if (state.focusLocationTree.length > maxShownLocations) {
selectedLocationsText += ', ...)';
} else {
selectedLocationsText += ')';
}
}
// Set the stack offset mode
const stackOffset =
plotSettingsStore.groupStackNormMode === NORM_MODES.NORM_PERCENTAGES
? 'normalize'
: 'zero';
// Set the date bin
let dateBin;
if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_DAY) {
dateBin = 1000 * 60 * 60 * 24;
} else if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_WEEK) {
dateBin = 1000 * 60 * 60 * 24 * 7;
} else if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_MONTH) {
dateBin = 1000 * 60 * 60 * 24 * 30;
} else if (plotSettingsStore.groupStackDateBin === DATE_BINS.DATE_BIN_YEAR) {
dateBin = 1000 * 60 * 60 * 24 * 365;
}
// If running in cumulative mode, add the vega transformation
// By default the cumulative transformation is dumped into a column
// "counts_cumulative", so if active, just overwrite the "counts"
// column with this cumulative count
const cumulativeWindow =
plotSettingsStore.groupStackCountMode === COUNT_MODES.COUNT_CUMULATIVE
? [null, 0]
: [0, 0];
// Adapt labels to groupings
let detailYLabel = '';
if (plotSettingsStore.groupStackCountMode === COUNT_MODES.COUNT_CUMULATIVE) {
detailYLabel += 'Cumulative ';
}
if (plotSettingsStore.groupStackNormMode === NORM_MODES.NORM_PERCENTAGES) {
detailYLabel += '% ';
}
detailYLabel += 'Sequences by ' + configStore.getGroupLabel();
// Hide the detail view in mutation mode when there's no selections
// Also disable the plot options when the detail panel is hidden
const hideDetail =
configStore.groupKey === GROUP_MUTATION &&
configStore.selectedGroups.length === 0;
const detailHeight = hideDetail ? 0 : 280;
const height = hideDetail ? 60 : 380;
return (
<div>
<WarningBox show={state.showWarning} onDismiss={onDismissWarning}>
{config.site_title} plots reflect data contributed to GISAID and are
therefore impacted by the sequence coverage in each country.
</WarningBox>
{hideDetail && (
<EmptyPlot height={100}>
<p>
No {configStore.getGroupLabel()}s selected. Please select one or
more {configStore.getGroupLabel()}s from the legend, frequency plot,
or table.
</p>
</EmptyPlot>
)}
{!hideDetail && (
<PlotHeader>
<PlotTitle style={{ gridRow: '1/-1' }}>
<span className="title">{plotTitle}</span>
<span className="subtitle">{selectedLocationsText}</span>
</PlotTitle>
<PlotOptionsRow>
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.groupStackCountMode}
onChange={onChangeCountMode}
>
<option value={COUNT_MODES.COUNT_NEW}>New</option>
<option value={COUNT_MODES.COUNT_CUMULATIVE}>
Cumulative
</option>
</select>
</label>
</OptionSelectContainer>
sequences, shown as{' '}
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.groupStackNormMode}
onChange={onChangeNormMode}
>
<option value={NORM_MODES.NORM_COUNTS}>Counts</option>
<option value={NORM_MODES.NORM_PERCENTAGES}>
Percentages
</option>
</select>
</label>
</OptionSelectContainer>
grouped by{' '}
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.groupStackDateBin}
onChange={onChangeDateBin}
>
<option value={DATE_BINS.DATE_BIN_DAY}>Day</option>
<option value={DATE_BINS.DATE_BIN_WEEK}>Week</option>
<option value={DATE_BINS.DATE_BIN_MONTH}>Month</option>
<option value={DATE_BINS.DATE_BIN_YEAR}>Year</option>
</select>
</label>
</OptionSelectContainer>
</PlotOptionsRow>
{configStore.groupKey !== GROUP_MUTATION &&
config.group_cols[configStore.groupKey].show_collapse_options && (
<PlotOptionsRow>
<LowFreqFilter
lowFreqFilterType={plotSettingsStore.groupStackLowFreqFilter}
lowFreqFilterValue={plotSettingsStore.groupStackLowFreqValue}
updateLowFreqFilterType={
plotSettingsStore.setGroupStackLowFreqFilter
}
updateLowFreqFilterValue={
plotSettingsStore.setGroupStackLowFreqValue
}
></LowFreqFilter>
</PlotOptionsRow>
)}
<PlotOptionsRow>
Only show locations: {focusLocationDropdownContainer}
{' '}
<button
disabled={state.focusLocationTree.every(
(node) => node.checked === false
)}
onClick={deselectAllFocusedLocations}
style={{ marginLeft: 10 }}
>
Deselect all
</button>
</PlotOptionsRow>
<PlotOptionsRow style={{ justifyContent: 'flex-end' }}>
<DropdownButton
text={'Download'}
options={[
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG,
]}
style={{ minWidth: '90px' }}
onSelect={handleDownloadSelect}
/>
</PlotOptionsRow>
</PlotHeader>
)}
<div style={{ width: `${width}px` }}>
<VegaEmbed
ref={vegaRef}
data={state.data}
spec={initialSpec}
signalListeners={state.signalListeners}
dataListeners={state.dataListeners}
signals={{
dateRangeStart: new Date(config.min_date).getTime(),
disableSelectionColoring: configStore.groupKey === GROUP_MUTATION,
detailHeight,
hoverBar: state.hoverGroup,
stackOffset,
dateBin,
cumulativeWindow,
detailYLabel,
yFormat:
plotSettingsStore.groupStackNormMode === NORM_MODES.NORM_COUNTS
? 's'
: '%',
}}
cheapSignals={['hoverBar']}
width={width}
height={height}
actions={false}
/>
</div>
</div>
);
})
Example #25
Source File: LocationDatePlot.js From covidcg with MIT License | 4 votes |
LocationDatePlot = observer(({ width }) => {
const vegaRef = useRef();
const { dataStore, configStore, UIStore, plotSettingsStore, groupDataStore } =
useStores();
const handleHoverLocation = (...args) => {
// Don't fire the action if there's no change
let hoverLocation = args[1] === null ? null : args[1]['location'];
if (hoverLocation === configStore.hoverLocation) {
return;
}
configStore.updateHoverLocation(hoverLocation);
};
const handleSelected = (...args) => {
configStore.updateFocusedLocations(args[1]);
};
const processLocationData = () => {
//console.log('PROCESS LOCATION DATE DATA');
let locationData;
if (configStore.groupKey === GROUP_MUTATION) {
if (dataStore.aggLocationSelectedMutationsDate === undefined) {
return [];
}
locationData = toJS(dataStore.aggLocationSelectedMutationsDate);
// Filter out 'All Other Sequences' group
locationData = locationData.filter((row) => {
return row.group !== GROUPS.ALL_OTHER_GROUP;
});
} else {
if (dataStore.aggLocationGroupDate === undefined) {
return [];
}
locationData = toJS(dataStore.aggLocationGroupDate).map((record) => {
record.color = groupDataStore.getGroupColor(
configStore.groupKey,
record.group_id
);
record.group = record.group_id;
record.group_name = record.group_id;
return record;
});
}
// TODO: add coverage-adjusted percentages
// Because in this plot we allow selecting multiple mutations
// the coverage-adjusted percentage should have, as the denominator,
// the total number of mutations with *both* mutations covered
//
// This isn't really possible with the current range-based setup because
// the coverage data only tracks counts on a rolling basis
// for example, let's say we selected mutations at positions 100 and 200,
// and we had 10 sequences covering [90, 110] and 10 sequences covering [190, 210]
//
// From the coverage data we would get 10 as the denominator for both of the
// sequences separately, but jointly the real denominator would be 0.
//
// It's not that big of a deal anyways since this plot is less about comparing mutations
// to each other and more about comparing frequencies over locations/time
locationData = aggregate({
data: locationData,
groupby: ['location', 'collection_date', 'group'],
fields: ['counts', 'group_name'],
ops: ['sum', 'first'],
as: ['counts', 'group_name'],
}).map((record) => {
// Add location counts
record.location_counts = dataStore.countsPerLocationMap[record.location];
record.location_date_count = dataStore.countsPerLocationDateMap
.get(record.location)
.get(record.collection_date);
record.cumulative_location_date_count =
dataStore.cumulativeCountsPerLocationDateMap
.get(record.location)
.get(record.collection_date);
return record;
});
// console.log(locationData);
// console.log(JSON.stringify(locationData));
return locationData;
};
const processSelectedGroups = () => {
return JSON.parse(JSON.stringify(configStore.selectedGroups));
};
const processFocusedLocations = () => {
return JSON.parse(JSON.stringify(configStore.focusedLocations));
};
// There's some weird data persistence things going on with the dateBin
// in this plot... so instead of passing it as a signal, just modify the
// spec and force a hard re-render
const injectDateBinIntoSpec = () => {
// setState({ ...state, dateBin: event.target.value })
const spec = JSON.parse(JSON.stringify(initialSpec));
// Set the date bin
let dateBin;
if (plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_DAY) {
dateBin = 1000 * 60 * 60 * 24;
} else if (
plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_WEEK
) {
dateBin = 1000 * 60 * 60 * 24 * 7;
} else if (
plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_MONTH
) {
dateBin = 1000 * 60 * 60 * 24 * 30;
} else if (
plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_YEAR
) {
dateBin = 1000 * 60 * 60 * 24 * 365;
}
const dateBinSignal = spec.signals.find(
(signal) => signal.name === 'dateBin'
);
dateBinSignal['value'] = dateBin;
return spec;
};
const [state, setState] = useState({
showWarning: true,
data: {
location_data: [],
selectedGroups: [],
selected: [],
},
hoverLocation: null,
spec: injectDateBinIntoSpec(),
signalListeners: {
hoverLocation: throttle(handleHoverLocation, 100),
},
dataListeners: {
selected: handleSelected,
},
});
const onDismissWarning = () => {
setState({
...state,
showWarning: false,
});
};
const onChangeNormMode = (event) =>
plotSettingsStore.setLocationDateNormMode(event.target.value);
const onChangeCountMode = (event) =>
plotSettingsStore.setLocationDateCountMode(event.target.value);
const onChangeDateBin = (event) => {
plotSettingsStore.setLocationDateDateBin(event.target.value);
};
const handleDownloadSelect = (option) => {
// console.log(option);
// TODO: use the plot options and configStore options to build a more descriptive filename
// something like new_lineages_by_day_S_2020-05-03-2020-05-15_NYC.png...
if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA) {
dataStore.downloadAggLocationGroupDate();
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG) {
vegaRef.current.downloadImage('png', 'vega-export.png', 1);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 2);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X) {
vegaRef.current.downloadImage('png', 'vega-export.png', 4);
} else if (option === PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG) {
vegaRef.current.downloadImage('svg', 'vega-export.svg');
}
};
// Trigger date bin spec injection when the date bin in the store changes
useEffect(() => {
const spec = injectDateBinIntoSpec();
setState({
...state,
spec,
});
}, [plotSettingsStore.locationDateDateBin]);
useEffect(() => {
setState({
...state,
hoverLocation: { location: configStore.hoverLocation },
});
}, [configStore.hoverLocation]);
useEffect(() => {
setState({
...state,
data: {
...state.data,
selected: processFocusedLocations(),
},
});
}, [configStore.focusedLocations]);
const refreshData = () => {
if (
configStore.groupKey === GROUP_MUTATION &&
UIStore.mutationDataState !== ASYNC_STATES.SUCCEEDED
) {
return;
}
setState({
...state,
data: {
...state.data,
location_data: processLocationData(),
selectedGroups: processSelectedGroups(),
},
});
};
// Refresh data on mount (i.e., tab change) or when data state changes
useEffect(refreshData, [
UIStore.caseDataState,
UIStore.mutationDataState,
configStore.selectedGroups,
]);
useEffect(refreshData, []);
if (UIStore.caseDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
paddingTop: '12px',
paddingRight: '24px',
paddingLeft: '12px',
paddingBottom: '24px',
}}
>
<SkeletonElement delay={2} height={400}>
<LoadingSpinner />
</SkeletonElement>
</div>
);
}
if (configStore.selectedLocationNodes.length == 0) {
return (
<EmptyPlot height={200}>
<p>
No locations selected. Please select one or more locations from the
sidebar, under "Selected Locations", to compare counts of{' '}
<b>{configStore.getGroupLabel()}</b> between them.
</p>
</EmptyPlot>
);
}
if (configStore.selectedGroups.length == 0) {
return (
<EmptyPlot height={200}>
<p>
Please select a <b>{configStore.getGroupLabel()}</b> from the legend
(above) or the group plot (below), to compare its counts between the
selected locations.
</p>
</EmptyPlot>
);
}
if (
configStore.groupKey === GROUP_MUTATION &&
state.data.location_data.length === 0
) {
return (
<EmptyPlot height={200}>
<p>
No sequences with {configStore.getGroupLabel()}s:{' '}
{`${configStore.selectedGroups
.map((group) => group.group)
.join(' & ')}`}
</p>
</EmptyPlot>
);
}
let yLabel = '';
if (
plotSettingsStore.locationDateCountMode === COUNT_MODES.COUNT_CUMULATIVE
) {
yLabel += 'Cumulative ';
}
if (plotSettingsStore.locationDateNormMode === NORM_MODES.NORM_PERCENTAGES) {
yLabel += '% ';
}
if (configStore.groupKey === GROUP_MUTATION) {
yLabel += 'Sequences with this ' + configStore.getGroupLabel();
} else {
yLabel += 'Sequences by ' + configStore.getGroupLabel();
}
let plotTitle = '';
if (
plotSettingsStore.locationDateCountMode === COUNT_MODES.COUNT_CUMULATIVE
) {
plotTitle += 'Cumulative ';
} else if (
plotSettingsStore.locationDateCountMode === COUNT_MODES.COUNT_NEW
) {
plotTitle += 'New ';
}
plotTitle += configStore.getGroupLabel();
plotTitle +=
plotSettingsStore.locationDateNormMode === NORM_MODES.NORM_PERCENTAGES
? ' Percentages'
: ' Counts';
if (plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_DAY) {
plotTitle += ' by Day';
} else if (
plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_WEEK
) {
plotTitle += ' by Week';
} else if (
plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_MONTH
) {
plotTitle += ' by Month';
} else if (
plotSettingsStore.locationDateDateBin === DATE_BINS.DATE_BIN_MONTH
) {
plotTitle += ' by Year';
}
if (configStore.selectedGroups.length > 0) {
if (configStore.groupKey === GROUP_MUTATION) {
plotTitle += ` (${configStore.selectedGroups
.map((group) => formatMutation(group.group, configStore.dnaOrAa))
.join(' & ')})`;
} else {
plotTitle += ` (${configStore.selectedGroups
.map((group) => group.group)
.join(', ')})`;
}
}
return (
<PlotContainer>
<WarningBox show={state.showWarning} onDismiss={onDismissWarning}>
{config.site_title} plots reflect data contributed to GISAID and are
therefore impacted by the sequence coverage in each country.
</WarningBox>
<PlotOptions>
<span className="plot-title">{plotTitle}</span>
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.locationDateCountMode}
onChange={onChangeCountMode}
>
<option value={COUNT_MODES.COUNT_NEW}>New</option>
<option value={COUNT_MODES.COUNT_CUMULATIVE}>Cumulative</option>
</select>
</label>
</OptionSelectContainer>
sequences, shown as{' '}
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.locationDateNormMode}
onChange={onChangeNormMode}
>
<option value={NORM_MODES.NORM_COUNTS}>Counts</option>
<option value={NORM_MODES.NORM_PERCENTAGES}>Percentages</option>
</select>
</label>
</OptionSelectContainer>
grouped by{' '}
<OptionSelectContainer>
<label>
<select
value={plotSettingsStore.locationDateDateBin}
onChange={onChangeDateBin}
>
<option value={DATE_BINS.DATE_BIN_DAY}>Day</option>
<option value={DATE_BINS.DATE_BIN_WEEK}>Week</option>
<option value={DATE_BINS.DATE_BIN_MONTH}>Month</option>
<option value={DATE_BINS.DATE_BIN_YEAR}>Year</option>
</select>
</label>
</OptionSelectContainer>
<div className="spacer"></div>
<DropdownButton
text={'Download'}
options={[
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_DATA,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_2X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_PNG_4X,
PLOT_DOWNLOAD_OPTIONS.DOWNLOAD_SVG,
]}
onSelect={handleDownloadSelect}
/>
</PlotOptions>
<div style={{ width: `${width}` }}>
<VegaEmbed
ref={vegaRef}
data={state.data}
recreateOnDatasets={['selectedGroups']}
spec={state.spec}
signalListeners={state.signalListeners}
dataListeners={state.dataListeners}
width={width}
signals={{
dateRangeStart: new Date(config.min_date).getTime() / 1000,
percentages:
plotSettingsStore.locationDateNormMode ===
NORM_MODES.NORM_PERCENTAGES,
cumulative:
plotSettingsStore.locationDateCountMode ===
COUNT_MODES.COUNT_CUMULATIVE,
skipFiltering: configStore.groupKey === GROUP_MUTATION,
hoverLocation: state.hoverLocation,
yLabel,
}}
actions={false}
/>
</div>
</PlotContainer>
);
})
Example #26
Source File: LocationGroupPlot.js From covidcg with MIT License | 4 votes |
LocationGroupPlot = observer(({ width }) => {
const vegaRef = useRef();
const {
dataStore,
configStore,
UIStore,
plotSettingsStore,
groupDataStore,
mutationDataStore,
} = useStores();
const handleHoverLocation = (...args) => {
// Don't fire the action if there's no change
let hoverLocation = args[1] === null ? null : args[1]['location'];
if (hoverLocation === configStore.hoverLocation) {
return;
}
configStore.updateHoverLocation(hoverLocation);
};
const handleHoverGroup = (...args) => {
configStore.updateHoverGroup(args[1] === null ? null : args[1]['group']);
};
const handleSelectedLocations = (...args) => {
configStore.updateFocusedLocations(args[1]);
};
const handleSelectedGroups = (...args) => {
configStore.updateSelectedGroups(
args[1] === null
? []
: args[1].map((item) => {
return { group: item.group };
})
);
};
const onChangeHideReference = (e) => {
plotSettingsStore.setLocationGroupHideReference(e.target.checked);
};
const processLocationByGroup = () => {
//console.log('LOCATION GROUP PLOT PROCESS DATA');
let locationData;
if (configStore.groupKey === GROUP_MUTATION) {
locationData = aggregate({
data: toJS(dataStore.aggLocationSingleMutationDate),
groupby: ['location', 'group_id'],
fields: ['counts'],
ops: ['sum'],
as: ['counts'],
});
locationData.forEach((record) => {
let mutation = mutationDataStore.intToMutation(
configStore.dnaOrAa,
configStore.coordinateMode,
record.group_id
);
record.color = mutation.color;
record.group = mutation.mutation_str;
record.group_name = mutation.name;
});
if (plotSettingsStore.locationGroupHideReference) {
// Filter out 'Reference' group, when in mutation mode
locationData = locationData.filter((row) => {
return row.group !== GROUPS.REFERENCE_GROUP;
});
}
} else {
locationData = aggregate({
data: toJS(dataStore.aggLocationGroupDate),
groupby: ['location', 'group_id'],
fields: ['counts'],
ops: ['sum'],
as: ['counts'],
});
locationData.forEach((record) => {
record.color = groupDataStore.getGroupColor(
configStore.groupKey,
record.group_id
);
record.group = record.group_id;
record.group_name = record.group_id;
});
}
locationData.forEach((record) => {
record.location_counts = dataStore.countsPerLocationMap[record.location];
});
//console.log(JSON.stringify(locationData));
return locationData;
};
const [state, setState] = useState({
data: {
location_by_group: [],
selectedGroups: [],
selectedLocations: [],
},
hoverGroup: null,
hoverLocation: null,
spec: JSON.parse(JSON.stringify(initialSpec)),
signalListeners: {
hoverLocation: throttle(handleHoverLocation, 100),
hoverGroup: debounce(handleHoverGroup, 100),
},
dataListeners: {
selectedLocations: handleSelectedLocations,
selectedGroups: handleSelectedGroups,
},
});
useEffect(() => {
setState({
...state,
hoverGroup: { group: configStore.hoverGroup },
});
}, [configStore.hoverGroup]);
useEffect(() => {
setState({
...state,
hoverLocation: { location: configStore.hoverLocation },
});
}, [configStore.hoverLocation]);
useEffect(() => {
setState({
...state,
data: {
...state.data,
selectedLocations: toJS(configStore.focusedLocations),
},
});
}, [configStore.focusedLocations]);
useEffect(() => {
setState({
...state,
data: {
...state.data,
selectedGroups: toJS(configStore.selectedGroups),
},
});
}, [configStore.selectedGroups]);
const refreshData = () => {
if (UIStore.caseDataState !== ASYNC_STATES.SUCCEEDED) {
return;
}
setState({
...state,
data: {
...state.data,
location_by_group: processLocationByGroup(),
selectedGroups: toJS(configStore.selectedGroups),
},
});
};
// Refresh data on mount (i.e., tab change) or when data state changes
useEffect(refreshData, [
UIStore.caseDataState,
plotSettingsStore.locationGroupHideReference,
]);
useEffect(refreshData, []);
if (UIStore.caseDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
paddingTop: '12px',
paddingRight: '24px',
paddingLeft: '12px',
paddingBottom: '24px',
}}
>
<SkeletonElement delay={2} height={100} />
</div>
);
}
if (configStore.selectedLocationNodes.length == 0) {
return (
<EmptyPlot height={100}>
<p>
No locations selected. Please select one or more locations from the
sidebar, under "Selected Locations", to compare counts of{' '}
<b>{configStore.getGroupLabel()}</b> between them.
</p>
</EmptyPlot>
);
}
let xLabel, xLabelFormat, stackOffset;
if (Object.keys(config.group_cols).includes(configStore.groupKey)) {
xLabel += `${config.group_cols[configStore.groupKey].title} `;
} else if (configStore.groupKey === GROUP_MUTATION) {
if (configStore.dnaOrAa === DNA_OR_AA.DNA) {
xLabel += 'NT';
} else {
xLabel += 'AA';
}
xLabel += ' mutation ';
}
xLabel += ' (Cumulative, All Sequences)';
if (configStore.groupKey === GROUP_MUTATION) {
xLabelFormat = 's';
stackOffset = 'zero';
xLabel = `# Sequences with ${configStore.getGroupLabel()} (Cumulative, All Sequences)`;
} else {
xLabelFormat = '%';
stackOffset = 'normalize';
xLabel = `% Sequences by ${configStore.getGroupLabel()} (Cumulative, All Sequences)`;
}
return (
<PlotContainer>
<PlotOptions>
{configStore.groupKey === GROUP_MUTATION && (
<OptionCheckboxContainer>
<label>
<input
type="checkbox"
checked={plotSettingsStore.locationGroupHideReference}
onChange={onChangeHideReference}
/>
Hide Reference Group
</label>
</OptionCheckboxContainer>
)}
<div className="spacer" />
</PlotOptions>
<div style={{ width: `${width}` }}>
<VegaEmbed
ref={vegaRef}
data={state.data}
spec={state.spec}
signalListeners={state.signalListeners}
dataListeners={state.dataListeners}
signals={{
hoverLocation: state.hoverLocation,
hoverGroup: state.hoverGroup,
xLabel,
xLabelFormat,
stackOffset,
}}
width={width}
actions={false}
/>
</div>
</PlotContainer>
);
})
Example #27
Source File: NumSeqPerLocationLine.js From covidcg with MIT License | 4 votes |
NumSeqPerLocationLine = observer(({ width }) => {
const vegaRef = useRef();
const { dataStore, configStore, UIStore } = useStores();
const handleHoverLocation = (...args) => {
// Don't fire the action if there's no change
let hoverLocation = args[1] === null ? null : args[1]['location'];
if (hoverLocation === configStore.hoverLocation) {
return;
}
configStore.updateHoverLocation(hoverLocation);
};
const processLocation = () => {
let dat = toJS(dataStore.countsPerLocationDateMap);
let dateTransformedData = [];
// Get list of date for each key and iterate through all lists.
// Potentially optimizable.
dat.forEach((value, key) => {
value.forEach((amount, date) => {
// Convert date from unix time stamp to human readable, vega useable.
dateTransformedData.push({ c: key, x: date, y: amount });
});
});
dateTransformedData.sort(function (a, b) {
let textA = a.c.toUpperCase();
let textB = b.c.toUpperCase();
return textA > textB ? -1 : textA < textB ? 1 : 0;
});
return dateTransformedData;
};
const [state, setState] = useState({
data: {
line_data: [],
},
hoverLocation: null,
spec: JSON.parse(JSON.stringify(initialSpec)),
signalListeners: {
hoverLocation: throttle(handleHoverLocation, 100),
},
});
useEffect(() => {
setState({
...state,
hoverLocation: { location: configStore.hoverLocation },
});
}, [configStore.hoverLocation]);
useEffect(() => {
setState({
...state,
data: {
...state.data,
},
});
}, [configStore.focusedLocations]);
const refreshData = () => {
if (UIStore.caseDataState !== ASYNC_STATES.SUCCEEDED) {
return;
}
setState({
...state,
data: {
...state.data,
line_data: processLocation(),
},
});
};
// Refresh data on mount (i.e., tab change) or when data state changes
useEffect(refreshData, [UIStore.caseDataState]);
useEffect(refreshData, []);
if (UIStore.caseDataState === ASYNC_STATES.STARTED) {
return (
<div
style={{
paddingTop: '12px',
paddingRight: '24px',
paddingLeft: '12px',
paddingBottom: '24px',
}}
>
<SkeletonElement delay={2} height={100} />
</div>
);
}
if (configStore.selectedLocationNodes.length == 0) {
return (
<EmptyPlot height={100}>
<p>
No locations selected. Please select one or more locations from the
sidebar, under "Selected Locations", to compare counts of{' '}
<b>{configStore.getGroupLabel()}</b> between them.
</p>
</EmptyPlot>
);
}
return (
<PlotContainer>
<PlotOptions>
<div className="spacer" />
</PlotOptions>
<div style={{ width: `${width}` }}>
<VegaEmbed
ref={vegaRef}
data={state.data}
spec={state.spec}
signals={{
hoverLocation: state.hoverLocation,
}}
signalListeners={state.signalListeners}
dataListeners={state.dataListeners}
//width={width}
actions={false}
/>
</div>
</PlotContainer>
);
})
Example #28
Source File: configStore.js From covidcg with MIT License | 4 votes |
getCoordinateRanges() {
// Set the coordinate range based off the coordinate mode
if (this.coordinateMode === COORDINATE_MODES.COORD_GENE) {
// Return ranges if All Genes
if (this.selectedGene.name === 'All Genes') {
return this.selectedGene.ranges;
}
// Disable residue indices for non-protein-coding genes
if (!this.selectedGene.protein_coding) {
return this.selectedGene.segments;
}
const coordinateRanges = [];
this.residueCoordinates.forEach((range) => {
// Make a deep copy of the current range
const curRange = range.slice();
if (this.dnaOrAa === DNA_OR_AA.DNA) {
for (let i = 0; i < this.selectedGene.aa_ranges.length; i++) {
const curAARange = this.selectedGene.aa_ranges[i];
const curNTRange = this.selectedGene.segments[i];
if (
(curRange[0] >= curAARange[0] && curRange[0] <= curAARange[1]) ||
(curRange[0] <= curAARange[0] && curRange[1] >= curAARange[0])
) {
coordinateRanges.push([
curNTRange[0] + (curRange[0] - curAARange[0]) * 3,
curNTRange[0] -
1 +
Math.min(curRange[1] - curAARange[0] + 1, curAARange[1]) * 3,
]);
// Push the beginning of the current range to the end of
// the current AA range of the gene
if (curAARange[1] < curRange[1]) {
curRange[0] = curAARange[1] + 1;
}
}
}
} else {
coordinateRanges.push([curRange[0], curRange[1]]);
}
});
return coordinateRanges;
} else if (this.coordinateMode === COORDINATE_MODES.COORD_PROTEIN) {
const coordinateRanges = [];
this.residueCoordinates.forEach((range) => {
// Make a deep copy of the current range
const curRange = range.slice();
if (this.dnaOrAa === DNA_OR_AA.DNA) {
for (let i = 0; i < this.selectedProtein.aa_ranges.length; i++) {
const curAARange = this.selectedProtein.aa_ranges[i];
const curNTRange = this.selectedProtein.segments[i];
if (
(curRange[0] >= curAARange[0] && curRange[0] <= curAARange[1]) ||
(curRange[0] <= curAARange[0] && curRange[1] >= curAARange[0])
) {
coordinateRanges.push([
curNTRange[0] + (curRange[0] - curAARange[0]) * 3,
curNTRange[0] -
1 +
Math.min(curRange[1] - curAARange[0] + 1, curAARange[1]) * 3,
]);
// Push the beginning of the current range to the end of
// the current AA range of the gene
if (curAARange[1] < curRange[1]) {
curRange[0] = curAARange[1] + 1;
}
}
}
} else {
coordinateRanges.push([curRange[0], curRange[1]]);
}
});
return coordinateRanges;
} else if (this.coordinateMode === COORDINATE_MODES.COORD_PRIMER) {
return this.selectedPrimers.map((primer) => {
return [primer.Start, primer.End];
});
} else if (this.coordinateMode === COORDINATE_MODES.COORD_CUSTOM) {
return toJS(this.customCoordinates);
} else if (this.coordinateMode === COORDINATE_MODES.COORD_SEQUENCE) {
return this.customSequences.map((seq) => {
return queryReferenceSequence(seq, this.selectedReference);
});
}
}
Example #29
Source File: runtime.js From apps-ng with Apache License 2.0 | 4 votes |
createAppRuntimeStore = (defaultValue = {}) => {
const AppRuntimeStore = types
.model('RuntimeStore', {
ecdhChannel: types.maybeNull(anyType),
ecdhShouldJoin: types.optional(types.boolean, false),
latency: types.optional(types.number, 0),
info: types.maybeNull(anyType),
error: types.maybeNull(anyType),
pApi: types.maybeNull(anyType),
})
.views(self => ({
get runtimeEndpointUrl () {
return self.appSettings.phalaTeeApiUrl
},
get appSettings () {
return defaultValue.appSettings
},
get appAccount () {
return defaultValue.appAccount
},
get accountId () {
return self.appAccount.address
},
get keypair () {
return self.appAccount.keypair
},
get accountIdHex () {
if (!self.accountId) { return null }
return ss58ToHex(self.accountId)
},
get channelReady () {
if (!self.ecdhChannel || !self.ecdhChannel.core.agreedSecret || !self.ecdhChannel.core.remotePubkey) {
console.warn('ECDH not ready')
return false
}
if (!self.keypair || self.keypair.isLocked) {
console.warn('Account not ready')
return false
}
if (!self.pApi) {
console.warn('pRuntime not ready')
return false
}
return true
}
}))
.actions(self => ({
checkChannelReady () {
if (!self.ecdhChannel || !self.ecdhChannel.core.agreedSecret || !self.ecdhChannel.core.remotePubkey) {
throw new Error('ECDH not ready')
}
if (!self.keypair || self.keypair.isLocked) {
throw new Error('Account not ready')
}
if (!self.pApi) {
throw new Error('pRuntime not ready')
}
},
async query (contractId, name, getPayload) {
self.checkChannelReady()
const data = getPayload ? { [name]: getPayload() } : name
return self.pApi.query(contractId, data)
},
initEcdhChannel: flow(function* () {
const ch = yield Crypto.newChannel()
self.ecdhChannel = ch
self.ecdhShouldJoin = true
}),
joinEcdhChannel: flow(function* () {
const ch = yield Crypto.joinChannel(self.ecdhChannel, self.info.ecdhPublicKey)
self.ecdhChannel = ch
self.ecdhShouldJoin = false
console.log('Joined channel:', toJS(ch))
}),
initPApi (endpoint) {
self.pApi = new PRuntime({
endpoint,
channel: self.ecdhChannel,
keypair: self.keypair
})
},
resetNetwork () {
self.error = null
self.latency = 0
self.info = null
},
setInfo (i) {
self.info = i
},
setError (e) {
self.error = e
},
setLatency (dt) {
self.latency = parseInt((l => l ? l * 0.8 + dt * 0.2 : dt)(self.latency))
}
}))
return AppRuntimeStore.create(defaultValue)
}