From b77f7878dd0ddc3fce6c6046436eb546fc6c0cca Mon Sep 17 00:00:00 2001 From: Jover Date: Thu, 2 Feb 2023 17:03:33 -0800 Subject: [PATCH] measurements: Jitter y-value for raw measurements within color-by groups For each subplot, jitter the y-values of the raw measurements within each color-by group. The y-value range for each color-by group is determined by the proportion of measurements within each group, i.e. color-by groups with more measurements get a larger portion of the subplot height. Removes the jitter value added during loading of the measurements JSON and the `measurementJitterSymbol` since they are no longer needed. --- src/actions/measurements.js | 8 +--- src/components/measurements/index.js | 4 +- src/components/measurements/measurementsD3.js | 43 ++++++++++++++++++- src/util/globals.js | 1 - 4 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/actions/measurements.js b/src/actions/measurements.js index c7dd4e191..6909c560a 100644 --- a/src/actions/measurements.js +++ b/src/actions/measurements.js @@ -1,6 +1,5 @@ import { pick } from "lodash"; -import { measurementIdSymbol, measurementJitterSymbol } from "../util/globals"; -import { layout as measurementsLayout } from "../components/measurements/measurementsD3"; +import { measurementIdSymbol } from "../util/globals"; import { APPLY_MEASUREMENTS_FILTER, CHANGE_MEASUREMENTS_COLLECTION, @@ -170,10 +169,7 @@ export const loadMeasurements = (json) => (dispatch, getState) => { } }); - // Add jitter and stable id for each measurement to help visualization - const { yMin, yMax } = measurementsLayout; - // Generates a random number between the y min and max, inclusively - measurement[measurementJitterSymbol] = Math.random() * (yMax - yMin + 1) + yMin; + // Add stable id for each measurement to help visualization measurement[measurementIdSymbol] = index; }); diff --git a/src/components/measurements/index.js b/src/components/measurements/index.js index 0f40e39a1..841638067 100644 --- a/src/components/measurements/index.js +++ b/src/components/measurements/index.js @@ -22,7 +22,8 @@ import { toggleDisplay, addHoverPanelToMeasurementsAndMeans, addColorByAttrToGroupingLabel, - layout + layout, + jitterRawMeansByColorBy } from "./measurementsD3"; /** @@ -223,6 +224,7 @@ const MeasurementsPlot = ({height, width, showLegend, setPanelTitle}) => { useEffect(() => { addColorByAttrToGroupingLabel(d3Ref.current, treeStrainColors); colorMeasurementsSVG(d3Ref.current, treeStrainColors); + jitterRawMeansByColorBy(d3Ref.current, svgData, treeStrainColors, legendValues); drawMeansForColorBy(d3Ref.current, svgData, treeStrainColors, legendValues); addHoverPanelToMeasurementsAndMeans(d3Ref.current, handleHover, treeStrainColors); }, [svgData, treeStrainColors, legendValues, handleHover]); diff --git a/src/components/measurements/measurementsD3.js b/src/components/measurements/measurementsD3.js index cd46e2cd9..d5fd0a460 100644 --- a/src/components/measurements/measurementsD3.js +++ b/src/components/measurements/measurementsD3.js @@ -4,7 +4,7 @@ import { scaleLinear } from "d3-scale"; import { select, event as d3event } from "d3-selection"; import { symbol, symbolDiamond } from "d3-shape"; import { orderBy } from "lodash"; -import { measurementIdSymbol, measurementJitterSymbol } from "../../util/globals"; +import { measurementIdSymbol } from "../../util/globals"; import { getBrighterColor } from "../../util/colorHelpers"; /* C O N S T A N T S */ @@ -264,6 +264,7 @@ export const drawMeasurementsSVG = (ref, xAxisRef, svgData) => { }); // Add circles for each measurement + // Note, "cy" is added later when jittering within color-by groups subplot.append("g") .attr("class", classes.rawMeasurementsGroup) .attr("display", "none") @@ -274,7 +275,6 @@ export const drawMeasurementsSVG = (ref, xAxisRef, svgData) => { .attr("class", classes.rawMeasurements) .attr("id", (d) => getMeasurementDOMId(d)) .attr("cx", (d) => xScale(d.value)) - .attr("cy", (d) => yScale(d[measurementJitterSymbol])) .attr("r", layout.circleRadius) .on("mouseover.radius", (d, i, elements) => { select(elements[i]).transition() @@ -307,6 +307,45 @@ export const colorMeasurementsSVG = (ref, treeStrainColors) => { .style("fill", (d) => getBrighterColor(treeStrainColors[d.strain].color)); }; +export const jitterRawMeansByColorBy = (ref, svgData, treeStrainColors, legendValues) => { + const { groupedMeasurements } = svgData; + const svg = select(ref); + + groupedMeasurements.forEach(([_, measurements]) => { + // For each color-by attribute, create an array of measurement DOM ids + const colorByGroups = {}; + measurements.forEach((measurement) => { + const { attribute } = treeStrainColors[measurement.strain]; + colorByGroups[attribute] = colorByGroups[attribute] || []; + colorByGroups[attribute].push(getMeasurementDOMId(measurement)); + }); + // Calculate total available subplot height + // Accounts for top/bottom padding and padding between color-by groups + const numberOfColorByAttributes = Object.keys(colorByGroups).length; + const totalColorByPadding = (numberOfColorByAttributes - 1) * 2 * layout.circleRadius; + const availableSubplotHeight = layout.subplotHeight - (2*layout.subplotPadding) - totalColorByPadding; + + let currentYMin = layout.subplotPadding; + Object.keys(colorByGroups) + // Sort by legendValues for stable ordering of color-by groups + .sort((a, b) => legendValues.indexOf(a) - legendValues.indexOf(b)) + .forEach((attribute) => { + // Calculate max Y value for each color-by attribute + // This is determined by the proportion of measurements in each attribute group + const domIds = colorByGroups[attribute]; + const proportionOfMeasurements = domIds.length / measurements.length; + const currentYMax = currentYMin + (proportionOfMeasurements * availableSubplotHeight); + // Jitter "cy" value for each raw measurement + domIds.forEach((domId) => { + const jitter = Math.random() * (currentYMax - currentYMin) + currentYMin; + svg.select(`#${domId}`).attr("cy", jitter); + }); + // Set next min Y value for next color-by attribute group + currentYMin = currentYMax + (2 * layout.circleRadius); + }); + }); +}; + export const drawMeansForColorBy = (ref, svgData, treeStrainColors, legendValues) => { const { xScale, groupingOrderedValues, groupedMeasurements } = svgData; const svg = select(ref); diff --git a/src/util/globals.js b/src/util/globals.js index 4b3c15754..80d4251c4 100644 --- a/src/util/globals.js +++ b/src/util/globals.js @@ -204,7 +204,6 @@ export const isValueValid = (value) => { export const strainSymbol = Symbol('strain'); export const genotypeSymbol = Symbol('genotype'); export const measurementIdSymbol = Symbol('measurementId'); -export const measurementJitterSymbol = Symbol('measurementJitter'); /** * Address to fetch tiles from (including access key).