Skip to content

Commit 981eb5b

Browse files
authored
Merge pull request #1827 from nextstrain/measurements-x-scale
Limit x-axis by filtered measurements
2 parents dddc6c9 + 6bb14a6 commit 981eb5b

File tree

3 files changed

+38
-11
lines changed

3 files changed

+38
-11
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* Fix bug where app crashed if measurements JSON did not define thresholds ([#1802](https://github.com/nextstrain/auspice/pull/1802))
44
* Fix bug where measurements display did not honor the default `measurements_display` ([#1802](https://github.com/nextstrain/auspice/pull/1802))
55
* Only display download-JSON button if the dataset name can be parsed from pathname ([#1804](https://github.com/nextstrain/auspice/pull/1804))
6+
* Fix bug where measurements panel did not display means for measurements that had an "undefined" coloring ([#1827](https://github.com/nextstrain/auspice/pull/1827))
7+
* Measurement panel's x-axis min/max values are now limited by visible measurements ([#1827](https://github.com/nextstrain/auspice/pull/1827))
68

79
## version 2.56.0 - 2024/07/01
810

src/components/measurements/index.js

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useRef, useEffect, useMemo, useState } from "react";
1+
import React, { useCallback, useRef, useEffect, useState } from "react";
22
import { useSelector } from "react-redux";
33
import { isEqual, orderBy } from "lodash";
44
import { NODE_VISIBLE } from "../../util/globals";
@@ -41,6 +41,17 @@ const useDeepCompareMemo = (value) => {
4141
return ref.current;
4242
};
4343

44+
/**
45+
* A wrapper around React's useCallback hook that does a deep comparison of the
46+
* dependencies.
47+
* @param {function} fn
48+
* @param {Array} dependencies
49+
* @returns
50+
*/
51+
const useDeepCompareCallback = (fn, dependencies) => {
52+
return useCallback(fn, dependencies.map(useDeepCompareMemo))
53+
}
54+
4455
// Checks visibility against global NODE_VISIBLE
4556
const isVisible = (visibility) => visibility === NODE_VISIBLE;
4657

@@ -159,9 +170,9 @@ const MeasurementsPlot = ({height, width, showLegend, setPanelTitle}) => {
159170
}
160171
const groupedMeasurements = groupMeasurements(filteredMeasurements, groupBy, groupByValueOrder);
161172

162-
// Memoize D3 scale functions to allow deep comparison to work below for svgData
163-
const xScale = useMemo(() => createXScale(width, measurements), [width, measurements]);
164-
const yScale = useMemo(() => createYScale(), []);
173+
// Cache D3 scale functions to allow deep comparison to work below for svgData
174+
const xScale = useDeepCompareCallback(createXScale(width, filteredMeasurements), [width, filteredMeasurements]);
175+
const yScale = useCallback(createYScale(), []);
165176
// Memoize all data needed for basic SVG to avoid extra re-drawings
166177
const svgData = useDeepCompareMemo({
167178
containerHeight: height,
@@ -172,8 +183,8 @@ const MeasurementsPlot = ({height, width, showLegend, setPanelTitle}) => {
172183
groupingOrderedValues,
173184
groupedMeasurements
174185
});
175-
// Memoize handleHover function to avoid extra useEffect calls
176-
const handleHover = useMemo(() => (data, dataType, mouseX, mouseY, colorByAttr=null) => {
186+
// Cache handleHover function to avoid extra useEffect calls
187+
const handleHover = useCallback((data, dataType, mouseX, mouseY, colorByAttr=null) => {
177188
let newHoverData = null;
178189
if (data !== null) {
179190
// Set color-by attribute as title if provided

src/components/measurements/measurementsD3.js

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,24 @@ const getSubplotDOMId = (groupingValueIndex) => `measurement_subplot_${groupingV
5858
/**
5959
* Creates the D3 linear scale for the x-axis with the provided measurements'
6060
* values as the domain and the panelWidth with hard-coded padding values as
61-
* the range. Expected to be shared across all subplots.
61+
* the range. The optional paddingProportion can be provided to include additional
62+
* padding for the domain. Expected to be shared across all subplots.
6263
* @param {number} panelWidth
6364
* @param {Array<Object>} measurements
65+
* @param {number} [paddingProportion=0.1]
6466
* @returns {function}
6567
*/
66-
export const createXScale = (panelWidth, measurements) => {
68+
export const createXScale = (panelWidth, measurements, paddingProportion = 0.1) => {
69+
// Padding the xScale based on proportion
70+
// Copied from https://github.com/d3/d3-scale/issues/150#issuecomment-561304239
71+
function padLinear([x0, x1], k) {
72+
const dx = (x1 - x0) * k / 2;
73+
return [x0 - dx, x1 + dx];
74+
}
75+
6776
return (
6877
scaleLinear()
69-
.domain(extent(measurements, (m) => m.value))
78+
.domain(padLinear(extent(measurements, (m) => m.value), paddingProportion))
7079
.range([layout.leftPadding, panelWidth - layout.rightPadding])
7180
.nice()
7281
);
@@ -377,8 +386,13 @@ export const drawMeansForColorBy = (ref, svgData, treeStrainColors, legendValues
377386
const ySpacing = (layout.subplotHeight - 4 * layout.subplotPadding) / (numberOfColorByAttributes - 1);
378387
let yValue = layout.subplotPadding;
379388
// Order the color groups by the legend value order so that we have a stable order for the means
380-
legendValues
381-
.filter((attribute) => String(attribute) in colorByGroups)
389+
const orderedColorGroups = orderBy(
390+
Object.keys(colorByGroups),
391+
(key) => legendValues.indexOf(key),
392+
"asc"
393+
);
394+
395+
orderedColorGroups
382396
.forEach((attribute) => {
383397
const {color, values} = colorByGroups[attribute];
384398
drawMeanAndStandardDeviation(

0 commit comments

Comments
 (0)