Skip to content

Commit

Permalink
Merge pull request #1158 from nextstrain/feat/frequencyNormalizationT…
Browse files Browse the repository at this point in the history
…oggle

add toggle to normalize frequencies
  • Loading branch information
jameshadfield authored Jun 8, 2020
2 parents 55208f6 + 3d3a1c2 commit 0a998d8
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 54 deletions.
3 changes: 2 additions & 1 deletion src/actions/frequencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ const updateFrequencyData = (dispatch, getState) => {
tree.nodes,
tree.visibility,
controls.colorScale,
controls.colorBy
controls.colorBy,
controls.normalizeFrequencies
);
timerEnd("updateFrequencyData");
dispatch({type: types.FREQUENCY_MATRIX, matrix});
Expand Down
7 changes: 4 additions & 3 deletions src/actions/recomputeReduxState.js
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,8 @@ const modifyStateViaMetadata = (state, metadata) => {
console.warn("JSON did not include any filters");
}
if (metadata.displayDefaults) {
const keysToCheckFor = ["geoResolution", "colorBy", "distanceMeasure", "layout", "mapTriplicate", "selectedBranchLabel", 'sidebar', "showTransmissionLines"];
const expectedTypes = ["string", "string", "string", "string", "boolean", "string", 'string', "boolean" ]; // eslint-disable-line
const keysToCheckFor = ["geoResolution", "colorBy", "distanceMeasure", "layout", "mapTriplicate", "selectedBranchLabel", 'sidebar', "showTransmissionLines", "normalizeFrequencies"];
const expectedTypes = ["string", "string", "string", "string", "boolean", "string", 'string', "boolean" , "boolean"]; // eslint-disable-line

for (let i = 0; i < keysToCheckFor.length; i += 1) {
if (Object.hasOwnProperty.call(metadata.displayDefaults, keysToCheckFor[i])) {
Expand Down Expand Up @@ -787,7 +787,8 @@ export const createStateFromQueryOrJSONs = ({
tree.nodes,
tree.visibility,
controls.colorScale,
controls.colorBy
controls.colorBy,
controls.normalizeFrequencies
);
}

Expand Down
50 changes: 28 additions & 22 deletions src/components/controls/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import ChooseMetric from "./choose-metric";
import PanelLayout from "./panel-layout";
import GeoResolution from "./geo-resolution";
import TransmissionLines from './transmission-lines';
import NormalizeFrequencies from "./frequency-normalization";
import MapAnimationControls from "./map-animation";
import PanelToggles from "./panel-toggles";
import SearchStrains from "./search";
Expand All @@ -19,43 +20,48 @@ import Language from "./language";
import { SidebarHeader, ControlsContainer } from "./styles";


function Controls({mapOn}) {
function Controls({mapOn, frequenciesOn}) {
const { t } = useTranslation();

return (
<ControlsContainer>
<ChooseDataset/>
<ChooseDataset />

<SidebarHeader>{t("sidebar:Date Range")}</SidebarHeader>
<DateRangeInputs/>

<DateRangeInputs />

<SidebarHeader>{t("sidebar:Color By")}</SidebarHeader>
<ColorBy/>

<ColorBy />

<SidebarHeader>{t("sidebar:Tree Options")}</SidebarHeader>
<ChooseLayout/>
<ChooseMetric/>
<ChooseBranchLabelling/>
<SearchStrains/>
<ChooseSecondTree/>
<ToggleTangle/>

{ mapOn ? (
<span style={{marginTop: "15px"}}>
<ChooseLayout />
<ChooseMetric />
<ChooseBranchLabelling />
<SearchStrains />
<ChooseSecondTree />
<ToggleTangle />

{mapOn ? (
<span style={{ marginTop: "15px" }}>
<SidebarHeader>{t("sidebar:Map Options")}</SidebarHeader>
<GeoResolution/>
<TransmissionLines/>
<MapAnimationControls/>
<GeoResolution />
<TransmissionLines />
<MapAnimationControls />
</span>
) : null}

{frequenciesOn ? (
<span style={{ marginTop: "15px" }}>
<SidebarHeader>{t("sidebar:Frequency Options")}</SidebarHeader>
<NormalizeFrequencies />
</span>
) : null}

<span style={{paddingTop: "10px"}}/>
<span style={{ paddingTop: "10px" }} />
<SidebarHeader>{t("sidebar:Panel Options")}</SidebarHeader>
<PanelLayout/>
<PanelToggles/>
<Language/>
<PanelLayout />
<PanelToggles />
<Language />
</ControlsContainer>
);
}
Expand Down
45 changes: 45 additions & 0 deletions src/components/controls/frequency-normalization.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from "react";
import { connect } from "react-redux";
import { withTranslation } from "react-i18next";

import Toggle from "./toggle";
import { controlsWidth } from "../../util/globals";
import { FREQUENCY_MATRIX } from "../../actions/types";
import { computeMatrixFromRawData } from "../../util/processFrequencies";

@connect((state) => {
return {
controls: state.controls,
frequencies: state.frequencies,
tree: state.tree
};
})
class NormalizeFrequencies extends React.Component {
render() {
const { t } = this.props;
return (
<div style={{marginBottom: 10, width: controlsWidth, fontSize: 14}}>
<Toggle
display
on={this.props.controls.normalizeFrequencies}
callback={() => {
const normalizeFrequencies = !this.props.controls.normalizeFrequencies;
const matrix = computeMatrixFromRawData(
this.props.frequencies.data,
this.props.frequencies.pivots,
this.props.tree.nodes,
this.props.tree.visibility,
this.props.controls.colorScale,
this.props.controls.colorBy,
normalizeFrequencies
);
this.props.dispatch({ type: FREQUENCY_MATRIX, matrix, normalizeFrequencies });
}}
label={t("sidebar:Normalize frequencies")}
/>
</div>
);
}
}

export default withTranslation()(NormalizeFrequencies);
10 changes: 10 additions & 0 deletions src/components/frequencies/functions.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,16 @@ export const parseColorBy = (colorBy, colorOptions) => {
return colorBy;
};

export const normString = (normalized, tipCount, fullTipCount) => {
if (tipCount<fullTipCount) {
if (normalized) {
return `and normalized to 100% at each time point for ${tipCount} out of a total of ${fullTipCount} tips`;
}
return `as a fraction of all sequences at each time point showing ${tipCount} out of a total of ${fullTipCount} tips`;
}
return "";
};

const getOrderedCategories = (matrixCategories, colorScale) => {
/* get the colorBy's in the same order as in the tree legend */
const orderedCategories = colorScale.legendValues
Expand Down
10 changes: 7 additions & 3 deletions src/components/frequencies/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@ import 'd3-transition';
import { connect } from "react-redux";
import Card from "../framework/card";
import { calcXScale, calcYScale, drawXAxis, drawYAxis, drawProjectionInfo,
areListsEqual, drawStream, processMatrix, parseColorBy } from "./functions";
areListsEqual, drawStream, processMatrix, parseColorBy, normString } from "./functions";
import "../../css/entropy.css";

@connect((state) => {
return {
data: state.frequencies.data,
pivots: state.frequencies.pivots,
matrix: state.frequencies.matrix,
nodes: state.tree.nodes,
projection_pivot: state.frequencies.projection_pivot,
version: state.frequencies.version,
browserDimensions: state.browserDimensions.browserDimensions,
colorBy: state.controls.colorBy,
colorScale: state.controls.colorScale,
colorOptions: state.metadata.colorings
colorOptions: state.metadata.colorings,
normalizeFrequencies: state.controls.normalizeFrequencies
};
})

class Frequencies extends React.Component {
constructor(props) {
super(props);
Expand Down Expand Up @@ -99,8 +102,9 @@ class Frequencies extends React.Component {
}
render() {
const { t } = this.props;
const {tipCount, fullTipCount} = this.props.nodes[0];
return (
<Card title={`${t("Frequencies")} (${t("colored by")} ${parseColorBy(this.props.colorBy, this.props.colorOptions)})`}>
<Card title={`${t("Frequencies")} (${t("colored by")} ${parseColorBy(this.props.colorBy, this.props.colorOptions)} ${t(normString(this.props.normalizeFrequencies, tipCount, fullTipCount))})`}>
<div
id="freqinfo"
style={{
Expand Down
11 changes: 5 additions & 6 deletions src/components/main/sidebar.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,14 @@ export const Sidebar = (
narrativeTitle={displayNarrative ? narrativeTitle : false}
width={width}
/>
{displayNarrative ?
<Narrative
height={height - narrativeNavBarHeight}
width={width}
/> :
{displayNarrative ? (
<Narrative height={height - narrativeNavBarHeight} width={width} />
) : (
<Controls
mapOn={panelsToDisplay.includes("map")}
frequenciesOn={panelsToDisplay.includes("frequencies")}
/>
}
)}
</SidebarContainer>
</ThemeProvider>
);
Expand Down
56 changes: 39 additions & 17 deletions src/reducers/controls.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const getDefaultControlsState = () => {
strain: null,
geneLength: {},
mutType: defaultMutType,
temporalConfidence: {exists: false, display: false, on: false},
temporalConfidence: { exists: false, display: false, on: false },
layout: defaults.layout,
distanceMeasure: defaults.distanceMeasure,
dateMin,
Expand All @@ -57,7 +57,7 @@ export const getDefaultControlsState = () => {
absoluteDateMax: dateMax,
absoluteDateMaxNumeric: dateMaxNumeric,
colorBy: defaults.colorBy,
colorByConfidence: {display: false, on: false},
colorByConfidence: { display: false, on: false },
colorScale: undefined,
selectedBranchLabel: "none",
analysisSlider: false,
Expand All @@ -82,7 +82,8 @@ export const getDefaultControlsState = () => {
treeLegendOpen: undefined,
mapLegendOpen: undefined,
showOnlyPanels: false,
showTransmissionLines: true
showTransmissionLines: true,
normalizeFrequencies: true
};
};

Expand All @@ -95,7 +96,7 @@ const Controls = (state = getDefaultControlsState(), action) => {
case types.CLEAN_START:
return action.controls;
case types.SET_AVAILABLE:
return Object.assign({}, state, {available: action.data});
return Object.assign({}, state, { available: action.data });
case types.BRANCH_MOUSEENTER:
return Object.assign({}, state, {
selectedBranch: action.data
Expand All @@ -113,28 +114,40 @@ const Controls = (state = getDefaultControlsState(), action) => {
selectedNode: null
});
case types.CHANGE_BRANCH_LABEL:
return Object.assign({}, state, {selectedBranchLabel: action.value});
return Object.assign({}, state, { selectedBranchLabel: action.value });
case types.CHANGE_LAYOUT:
return Object.assign({}, state, {
layout: action.data,
/* temporal confidence can only be displayed for rectangular trees */
temporalConfidence: Object.assign({}, state.temporalConfidence, {
display: shouldDisplayTemporalConfidence(state.temporalConfidence.exists, state.distanceMeasure, action.data),
on: false})
display: shouldDisplayTemporalConfidence(
state.temporalConfidence.exists,
state.distanceMeasure,
action.data
),
on: false
})
});
case types.CHANGE_DISTANCE_MEASURE:
const updatesToState = {
distanceMeasure: action.data,
branchLengthsToDisplay: state.branchLengthsToDisplay
};
if (shouldDisplayTemporalConfidence(state.temporalConfidence.exists, action.data, state.layout)) {
updatesToState.temporalConfidence = Object.assign({}, state.temporalConfidence, {display: true});
if (
shouldDisplayTemporalConfidence(state.temporalConfidence.exists, action.data, state.layout)
) {
updatesToState.temporalConfidence = Object.assign({}, state.temporalConfidence, {
display: true
});
} else {
updatesToState.temporalConfidence = Object.assign({}, state.temporalConfidence, {display: false, on: false});
updatesToState.temporalConfidence = Object.assign({}, state.temporalConfidence, {
display: false,
on: false
});
}
return Object.assign({}, state, updatesToState);
case types.CHANGE_DATES_VISIBILITY_THICKNESS: {
const newDates = {quickdraw: action.quickdraw};
const newDates = { quickdraw: action.quickdraw };
if (action.dateMin) {
newDates.dateMin = action.dateMin;
newDates.dateMinNumeric = action.dateMinNumeric;
Expand Down Expand Up @@ -186,7 +199,9 @@ const Controls = (state = getDefaultControlsState(), action) => {
return Object.assign({}, state, {
panelsToDisplay: action.panelsToDisplay,
panelLayout: action.panelLayout,
canTogglePanelLayout: action.panelsToDisplay.indexOf("tree") !== -1 && action.panelsToDisplay.indexOf("map") !== -1
canTogglePanelLayout:
action.panelsToDisplay.indexOf("tree") !== -1 &&
action.panelsToDisplay.indexOf("map") !== -1
});
case types.NEW_COLORS: {
const newState = Object.assign({}, state, {
Expand Down Expand Up @@ -235,20 +250,27 @@ const Controls = (state = getDefaultControlsState(), action) => {
});
case types.TOGGLE_TANGLE:
if (state.showTreeToo) {
return Object.assign({}, state, {showTangle: !state.showTangle});
return Object.assign({}, state, { showTangle: !state.showTangle });
}
return state;
case types.TOGGLE_SIDEBAR:
return Object.assign({}, state, {sidebarOpen: action.value});
return Object.assign({}, state, { sidebarOpen: action.value });
case types.TOGGLE_LEGEND:
return Object.assign({}, state, {legendOpen: action.value});
return Object.assign({}, state, { legendOpen: action.value });
case types.ADD_COLOR_BYS:
for (const colorBy of Object.keys(action.newColorings)) {
state.coloringsPresentOnTree.add(colorBy);
}
return Object.assign({}, state, {coloringsPresentOnTree: state.coloringsPresentOnTree});
return Object.assign({}, state, { coloringsPresentOnTree: state.coloringsPresentOnTree });
case types.TOGGLE_TRANSMISSION_LINES:
return Object.assign({}, state, {showTransmissionLines: action.data});
return Object.assign({}, state, { showTransmissionLines: action.data });

case types.FREQUENCY_MATRIX: {
if (Object.hasOwnProperty.call(action, "normalizeFrequencies")) {
return Object.assign({}, state, { normalizeFrequencies: action.normalizeFrequencies });
}
return state;
}
default:
return state;
}
Expand Down
15 changes: 13 additions & 2 deletions src/util/processFrequencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const assignCategory = (colorScale, categories, node, colorBy, isGenotype) => {
return unassigned_label;
};

export const computeMatrixFromRawData = (data, pivots, nodes, visibility, colorScale, colorBy) => {
export const computeMatrixFromRawData = (data, pivots, nodes, visibility, colorScale, colorBy, normalizeFrequencies) => {
/* color scale domain forms the categories in the stream graph */
const categories = colorScale.legendValues.filter((d) => d !== undefined);
categories.push(unassigned_label); /* for tips without a colorBy */
Expand All @@ -59,6 +59,16 @@ export const computeMatrixFromRawData = (data, pivots, nodes, visibility, colorS
}
});

if (normalizeFrequencies) {
const nCategories = Object.keys(matrix).length;
const minVal = 1e-10;
Object.keys(matrix).forEach((cat) => {
debugPivotTotals.forEach((norm, i) => {
matrix[cat][i] = (matrix[cat][i] + minVal) / (nCategories * minVal + norm);
});
});
}

if (matrix[unassigned_label].reduce((a, b) => a + b, 0) === 0) {
delete matrix[unassigned_label];
}
Expand Down Expand Up @@ -94,7 +104,8 @@ export const processFrequenciesJSON = (rawJSON, tree, controls) => {
tree.nodes,
tree.visibility,
controls.colorScale,
controls.colorBy
controls.colorBy,
controls.normalizeFrequencies
);
return {
data,
Expand Down

0 comments on commit 0a998d8

Please sign in to comment.