Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: prototype node states graph #1186

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
21 changes: 18 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@
"@babel/core": "^7.3.4",
"@babel/plugin-proposal-class-properties": "^7.3.4",
"@babel/plugin-proposal-decorators": "^7.3.0",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-react": "^7.0.0",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-transform-runtime": "^7.8.3",
"@babel/preset-env": "^7.9.6",
"@babel/preset-react": "^7.0.0",
"@hot-loader/react-dom": "^16.13.0",
"argparse": "^1.0.10",
"awesomplete": "^1.1.2",
Expand All @@ -65,7 +65,9 @@
"d3-brush": "^1.0.4",
"d3-collection": "^1.0.4",
"d3-color": "^1.0.3",
"d3-drag": "^1.2.5",
"d3-ease": "^1.0.3",
"d3-force": "^2.0.1",
"d3-format": "^1.3.0",
"d3-interpolate": "^1.1.5",
"d3-scale": "^1.0.5",
Expand Down Expand Up @@ -118,9 +120,9 @@
"styled-components": "^4.0.3",
"typeface-lato": "^0.0.75",
"webpack": "^4.30.0",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-chunk-hash": "^0.6.0",
"webpack-cli": "^3.1.2",
"webpack-bundle-analyzer": "^3.3.2",
"webpack-dev-middleware": "^3.1.3",
"webpack-hot-middleware": "^2.24.3",
"whatwg-fetch": "^0.10.1",
Expand Down
8 changes: 8 additions & 0 deletions scripts/get-data.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,12 @@ do
curl http://data.nextstrain.org/"${i}" --compressed -o data/"${i}"
done

staging_files=(
"testing_states.json" \
)
for i in "${staging_files[@]}"
do
curl http://staging.nextstrain.org/"${i}" --compressed -o data/"${i}"
done

echo "The local data directory ./data now contains up-to-date datasets from http://data.nextstrain.org"
20 changes: 19 additions & 1 deletion src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { computeMatrixFromRawData } from "../util/processFrequencies";
import { applyInViewNodesToTree } from "../actions/tree";
import { isColorByGenotype, decodeColorByGenotype } from "../util/getGenotype";
import { getTraitFromNode, getDivFromNode } from "../util/treeMiscHelpers";

import { getMapTypesAvailable } from "../util/spatialResolutionHelpers";

export const doesColorByHaveConfidence = (controlsState, colorBy) =>
controlsState.coloringsPresentOnTreeWithConfidence.has(colorBy);
Expand Down Expand Up @@ -67,6 +67,9 @@ const modifyStateViaURLQuery = (state, query) => {
if (query.r) {
state["geoResolution"] = query.r;
}
if (Object.hasOwnProperty.call(query, "showNetwork")) {
state.mapDisplayType = "network";
}
if (query.p && state.canTogglePanelLayout && (query.p === "full" || query.p === "grid")) {
state["panelLayout"] = query.p;
}
Expand Down Expand Up @@ -446,6 +449,9 @@ const checkAndCorrectErrorsInState = (state, metadata, query, tree, viewingNarra

/* geoResolutions */
if (metadata.geoResolutions) {
if (isColorByGenotype(state.colorBy)) { // already error corrected above
metadata.geoResolutions.push({key: state.colorBy, title: state.colorBy, isGenotype: true});
}
const availableGeoResultions = metadata.geoResolutions.map((i) => i.key);
if (availableGeoResultions.indexOf(state["geoResolution"]) === -1) {
/* fallbacks: JSON defined default, then hardocded default, then any available */
Expand All @@ -459,6 +465,18 @@ const checkAndCorrectErrorsInState = (state, metadata, query, tree, viewingNarra
console.error("Error detected. Setting geoResolution to ", state.geoResolution);
delete query.r; // no-op if query.r doesn't exist
}

/* only once the geo-res has been set can we decide on the mapDisplayType and mapDisplayTypesAvailable */
const { mapDisplayType, mapDisplayTypesAvailable } = getMapTypesAvailable({
currentMapDisplayType: state.mapDisplayType,
currentMapDisplayTypesAvailable: state.mapDisplayTypesAvailable,
newGeoResolution: state.geoResolution,
geoResolutions: metadata.geoResolutions
});
state.mapDisplayType = mapDisplayType;
state.mapDisplayTypesAvailable = mapDisplayTypesAvailable;
if (state.mapDisplayTypesAvailable.length === 1 || state.mapDisplayType === "geo") delete query.showNetwork;

} else {
console.warn("JSONs did not include `geoResolutions`");
}
Expand Down
1 change: 1 addition & 0 deletions src/actions/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const CHANGE_DATES_VISIBILITY_THICKNESS = "CHANGE_DATES_VISIBILITY_THICKN
export const CHANGE_ABSOLUTE_DATE_MIN = "CHANGE_ABSOLUTE_DATE_MIN";
export const CHANGE_ABSOLUTE_DATE_MAX = "CHANGE_ABSOLUTE_DATE_MAX";
export const CHANGE_GEO_RESOLUTION = "CHANGE_GEO_RESOLUTION";
export const CHANGE_MAP_DISPLAY_TYPE = "CHANGE_MAP_DISPLAY_TYPE";
export const CHANGE_LANGUAGE = "CHANGE_LANGUAGE";
export const CLEAN_START = "CLEAN_START";
export const APPLY_FILTER = "APPLY_FILTER";
Expand Down
2 changes: 2 additions & 0 deletions src/components/controls/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import MapAnimationControls from "./map-animation";
import PanelToggles from "./panel-toggles";
import SearchStrains from "./search";
import ToggleTangle from "./toggle-tangle";
import ToggleMapDisplayType from "./map-display-type-toggle";
import Language from "./language";
import { SidebarHeader, ControlsContainer } from "./styles";

Expand Down Expand Up @@ -45,6 +46,7 @@ function Controls({mapOn, frequenciesOn}) {
<span style={{ marginTop: "15px" }}>
<SidebarHeader>{t("sidebar:Map Options")}</SidebarHeader>
<GeoResolution />
<ToggleMapDisplayType />
<TransmissionLines />
<MapAnimationControls />
</span>
Expand Down
21 changes: 16 additions & 5 deletions src/components/controls/geo-resolution.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import { connect } from "react-redux";
import Select from "react-select/lib/Select";
import { withTranslation } from "react-i18next";

import { getMapTypesAvailable } from "../../util/spatialResolutionHelpers";
import { controlsWidth } from "../../util/globals";
import { CHANGE_GEO_RESOLUTION } from "../../actions/types";
import { analyticsControlsEvent } from "../../util/googleAnalytics";
Expand All @@ -11,7 +11,9 @@ import { SidebarSubtitle } from "./styles";
@connect((state) => {
return {
metadata: state.metadata,
geoResolution: state.controls.geoResolution
geoResolution: state.controls.geoResolution,
mapDisplayType: state.controls.mapDisplayType,
mapDisplayTypesAvailable: state.controls.mapDisplayTypesAvailable
};
})
class GeoResolution extends React.Component {
Expand All @@ -21,9 +23,18 @@ class GeoResolution extends React.Component {
[];
}

changeGeoResolution(resolution) {
changeGeoResolution(geoResolution) {
analyticsControlsEvent("change-geo-resolution");
this.props.dispatch({ type: CHANGE_GEO_RESOLUTION, data: resolution });
this.props.dispatch({
type: CHANGE_GEO_RESOLUTION,
geoResolution,
...getMapTypesAvailable({
currentMapDisplayType: this.props.mapDisplayType,
currentMapDisplayTypesAvailable: this.props.mapDisplayTypesAvailable,
newGeoResolution: geoResolution,
geoResolutions: this.props.metadata.geoResolutions
})
});
}

render() {
Expand All @@ -32,7 +43,7 @@ class GeoResolution extends React.Component {
return (
<>
<SidebarSubtitle spaceAbove>
{t("sidebar:Geographic resolution")}
{t("sidebar:Spatial resolution")}
</SidebarSubtitle>
<div style={{marginBottom: 10, width: controlsWidth, fontSize: 14}}>
<Select
Expand Down
39 changes: 39 additions & 0 deletions src/components/controls/map-display-type-toggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import React from "react";
import { connect } from "react-redux";
import { withTranslation } from "react-i18next";

import Toggle from "./toggle";
import { controlsWidth } from "../../util/globals";
import { CHANGE_MAP_DISPLAY_TYPE } from "../../actions/types";

@connect((state) => {
return {
mapDisplayType: state.controls.mapDisplayType,
mapDisplayTypesAvailable: state.controls.mapDisplayTypesAvailable
};
})
class ToggleMapDisplayType extends React.Component {
render() {
const { t } = this.props;

if (this.props.mapDisplayTypesAvailable.length !== 2) return null;
return (
<div style={{marginBottom: 10, width: controlsWidth, fontSize: 14}}>
<Toggle
display
on={this.props.mapDisplayType === "network"}
callback={() => {
this.props.dispatch({
type: CHANGE_MAP_DISPLAY_TYPE,
mapDisplayTypesAvailable: this.props.mapDisplayTypesAvailable,
mapDisplayType: this.props.mapDisplayType === "geo" ? "network" : "geo"
});
}}
label={t("sidebar:Show network layout view")}
/>
</div>
);
}
}

export default withTranslation()(ToggleMapDisplayType);
15 changes: 13 additions & 2 deletions src/components/main/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import SidebarToggle from "../framework/sidebar-toggle";
import Info from "../info/info";
import Tree from "../tree";
import Map from "../map/map";
import States from "../network/network";
import { controlsHiddenWidth } from "../../util/globals";
import Footer from "../framework/footer";
import DownloadModal from "../download/downloadModal";
Expand Down Expand Up @@ -38,7 +39,8 @@ const Frequencies = lazy(() => import("../frequencies"));
metadataLoaded: state.metadata.loaded,
treeLoaded: state.tree.loaded,
sidebarOpen: state.controls.sidebarOpen,
showOnlyPanels: state.controls.showOnlyPanels
showOnlyPanels: state.controls.showOnlyPanels,
mapDisplayType: state.controls.mapDisplayType
}))
class Main extends React.Component {
constructor(props) {
Expand Down Expand Up @@ -141,7 +143,16 @@ class Main extends React.Component {
}
{this.props.displayNarrative || this.props.showOnlyPanels ? null : <Info width={calcUsableWidth(availableWidth, 1)} />}
{this.props.panelsToDisplay.includes("tree") ? <Tree width={big.width} height={big.height} /> : null}
{this.props.panelsToDisplay.includes("map") ? <Map width={big.width} height={big.height} justGotNewDatasetRenderNewMap={false} legend={this.shouldShowMapLegend()} /> : null}
{this.props.panelsToDisplay.includes("map") ?
<ErrorBoundary showNothing>
{
this.props.mapDisplayType === "geo" ?
<Map width={big.width} height={big.height} justGotNewDatasetRenderNewMap={false} legend={this.shouldShowMapLegend()} /> :
<States legend={this.shouldShowMapLegend()} width={big.width} height={big.height} />
}
</ErrorBoundary>
: null
}
{this.props.panelsToDisplay.includes("entropy") ?
(<Suspense fallback={null}>
<Entropy width={chart.width} height={chart.height} />
Expand Down
1 change: 1 addition & 0 deletions src/components/map/map.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,7 @@ class Map extends React.Component {
*/
maybeUpdateDemesAndTransmissions(nextProps) {
if (!this.state.map || !this.props.treeLoaded || !this.state.d3elems) { return; }

const visibilityChange = nextProps.visibilityVersion !== this.props.visibilityVersion;
const haveData = nextProps.nodes && nextProps.visibility && nextProps.geoResolution && nextProps.nodeColors;

Expand Down
4 changes: 2 additions & 2 deletions src/components/map/mapHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export const pathStringGenerator = line()
.y((d) => { return d.y; })
.curve(curveBasis);

const extractLineSegmentForAnimationEffect = (
export const extractLineSegmentForAnimationEffect = (
numDateMin,
numDateMax,
originCoords,
Expand Down Expand Up @@ -117,7 +117,7 @@ const extractLineSegmentForAnimationEffect = (
return curve;
};

const createArcsFromDemes = (demeData) => {
export const createArcsFromDemes = (demeData) => {
const individualArcs = [];
demeData.forEach((demeInfo) => {
demeInfo.arcs.forEach((slice) => {
Expand Down
7 changes: 4 additions & 3 deletions src/components/map/mapHelpersLatLong.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,12 @@ const maybeGetTransmissionPair = (latOrig, longOrig, latDest, longDest, map) =>
* Traverses the tips of the tree to create a dict of
* location(deme) -> list of visible tips at that location
*/
const getVisibleNodesPerLocation = (nodes, visibility, geoResolution) => {
export const getVisibleNodesPerLocation = (nodes, visibility, geoResolution) => {
const locationToVisibleNodes = {};
const genotype = isColorByGenotype(geoResolution);
nodes.forEach((n, i) => {
if (n.children) return; /* only consider terminal nodes */
const location = getTraitFromNode(n, geoResolution);
const location = getTraitFromNode(n, geoResolution, {genotype});
if (!location) return; /* ignore undefined locations */
if (!locationToVisibleNodes[location]) locationToVisibleNodes[location]=[];
if (visibility[i] !== NODE_NOT_VISIBLE) {
Expand All @@ -73,7 +74,7 @@ const getVisibleNodesPerLocation = (nodes, visibility, geoResolution) => {
* @param {array} currentArcs only used if updating. Array of current arcs.
* @returns {array} arcs for display
*/
const createOrUpdateArcs = (visibleNodes, legendValues, colorBy, nodeColors, currentArcs=undefined) => {
export const createOrUpdateArcs = (visibleNodes, legendValues, colorBy, nodeColors, currentArcs=undefined) => {
const colorByIsGenotype = isColorByGenotype(colorBy);
const legendValueToArcIdx = {};
const undefinedArcIdx = legendValues.length; /* the arc which is grey to represent undefined values on tips */
Expand Down
Loading