diff --git a/src/actions/frequencies.js b/src/actions/frequencies.js
index 405e46c90..b49b2bf86 100644
--- a/src/actions/frequencies.js
+++ b/src/actions/frequencies.js
@@ -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});
diff --git a/src/actions/recomputeReduxState.js b/src/actions/recomputeReduxState.js
index 274c88152..5fc361774 100644
--- a/src/actions/recomputeReduxState.js
+++ b/src/actions/recomputeReduxState.js
@@ -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])) {
@@ -787,7 +787,8 @@ export const createStateFromQueryOrJSONs = ({
tree.nodes,
tree.visibility,
controls.colorScale,
- controls.colorBy
+ controls.colorBy,
+ controls.normalizeFrequencies
);
}
diff --git a/src/components/controls/controls.js b/src/components/controls/controls.js
index 01af38816..79a43e662 100644
--- a/src/components/controls/controls.js
+++ b/src/components/controls/controls.js
@@ -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";
@@ -19,43 +20,48 @@ import Language from "./language";
import { SidebarHeader, ControlsContainer } from "./styles";
-function Controls({mapOn}) {
+function Controls({mapOn, frequenciesOn}) {
const { t } = useTranslation();
return (
-
+
{t("sidebar:Date Range")}
-
-
+
{t("sidebar:Color By")}
-
-
+
{t("sidebar:Tree Options")}
-
-
-
-
-
-
-
- { mapOn ? (
-
+
+
+
+
+
+
+
+ {mapOn ? (
+
{t("sidebar:Map Options")}
-
-
-
+
+
+
+
+ ) : null}
+
+ {frequenciesOn ? (
+
+ {t("sidebar:Frequency Options")}
+
) : null}
-
+
{t("sidebar:Panel Options")}
-
-
-
+
+
+
);
}
diff --git a/src/components/controls/frequency-normalization.js b/src/components/controls/frequency-normalization.js
new file mode 100644
index 000000000..bdaa22193
--- /dev/null
+++ b/src/components/controls/frequency-normalization.js
@@ -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 (
+
+ {
+ 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")}
+ />
+
+ );
+ }
+}
+
+export default withTranslation()(NormalizeFrequencies);
diff --git a/src/components/frequencies/functions.js b/src/components/frequencies/functions.js
index 62d3061a4..c4f9ca716 100644
--- a/src/components/frequencies/functions.js
+++ b/src/components/frequencies/functions.js
@@ -32,6 +32,16 @@ export const parseColorBy = (colorBy, colorOptions) => {
return colorBy;
};
+export const normString = (normalized, tipCount, fullTipCount) => {
+ if (tipCount {
/* get the colorBy's in the same order as in the tree legend */
const orderedCategories = colorScale.legendValues
diff --git a/src/components/frequencies/index.js b/src/components/frequencies/index.js
index 170e972d2..b6001fc18 100644
--- a/src/components/frequencies/index.js
+++ b/src/components/frequencies/index.js
@@ -5,7 +5,7 @@ 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) => {
@@ -13,14 +13,17 @@ import "../../css/entropy.css";
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);
@@ -99,8 +102,9 @@ class Frequencies extends React.Component {
}
render() {
const { t } = this.props;
+ const {tipCount, fullTipCount} = this.props.nodes[0];
return (
-
+
- {displayNarrative ?
- :
+ {displayNarrative ? (
+
+ ) : (
- }
+ )}
);
diff --git a/src/reducers/controls.js b/src/reducers/controls.js
index 1e2c756c6..b6f30df37 100644
--- a/src/reducers/controls.js
+++ b/src/reducers/controls.js
@@ -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,
@@ -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,
@@ -82,7 +82,8 @@ export const getDefaultControlsState = () => {
treeLegendOpen: undefined,
mapLegendOpen: undefined,
showOnlyPanels: false,
- showTransmissionLines: true
+ showTransmissionLines: true,
+ normalizeFrequencies: true
};
};
@@ -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
@@ -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;
@@ -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, {
@@ -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;
}
diff --git a/src/util/processFrequencies.js b/src/util/processFrequencies.js
index 0cd55057f..3df05e8d1 100644
--- a/src/util/processFrequencies.js
+++ b/src/util/processFrequencies.js
@@ -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 */
@@ -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];
}
@@ -94,7 +104,8 @@ export const processFrequenciesJSON = (rawJSON, tree, controls) => {
tree.nodes,
tree.visibility,
controls.colorScale,
- controls.colorBy
+ controls.colorBy,
+ controls.normalizeFrequencies
);
return {
data,