From 242fc9a74beb1a106290d4d1b2b86fc2d52be8fe Mon Sep 17 00:00:00 2001 From: Patrick Golden Date: Tue, 13 Apr 2021 15:22:41 -0400 Subject: [PATCH] Add useWatchedTranscripts hook; simplify cleanup for plot hooks ...also, oops, added a couple other changes: 1) Remove a stray `console.log` 2) Reset the hovered transcript when the displayed transcripts change --- src/components/MAPlot/OldPlot.ts | 4 + src/components/MAPlot/Outer.ts | 1 + src/components/MAPlot/Plot.ts | 169 +++++++++++++--------- src/components/TreatmentSelector/index.ts | 2 - src/view/reducer.ts | 2 + 5 files changed, 107 insertions(+), 71 deletions(-) diff --git a/src/components/MAPlot/OldPlot.ts b/src/components/MAPlot/OldPlot.ts index 3221a1c..a9a3816 100644 --- a/src/components/MAPlot/OldPlot.ts +++ b/src/components/MAPlot/OldPlot.ts @@ -70,6 +70,7 @@ type PlotState = { scaleTransform: d3.ZoomTransform, } +/* function getDimensions(props: PlotProps, state?: PlotState) { const plotHeight = props.height! - padding.t - padding.b , plotWidth = props.width! - padding.l - padding.r @@ -98,6 +99,7 @@ function getDimensions(props: PlotProps, state?: PlotState) { scaleTransform, } } +*/ export default class Plot extends React.Component { brush?: d3.BrushBehavior; @@ -519,6 +521,7 @@ export default class Plot extends React.Component { } */ + /* drawSavedTranscripts() { const { xScale, yScale } = this.state , { savedTranscripts, pairwiseData } = this.props @@ -542,6 +545,7 @@ export default class Plot extends React.Component { .attr('fill', 'red') } + */ /* updateHoveredTranscript() { diff --git a/src/components/MAPlot/Outer.ts b/src/components/MAPlot/Outer.ts index ecae9dc..2fe5bee 100644 --- a/src/components/MAPlot/Outer.ts +++ b/src/components/MAPlot/Outer.ts @@ -113,6 +113,7 @@ export default function Wrapper(props: OuterProps) { 'pairwiseData', 'pValueThreshold', 'hoveredTranscript', + 'displayedTranscripts', ], view) return { diff --git a/src/components/MAPlot/Plot.ts b/src/components/MAPlot/Plot.ts index 8cdb32f..10a607a 100644 --- a/src/components/MAPlot/Plot.ts +++ b/src/components/MAPlot/Plot.ts @@ -2,12 +2,16 @@ import h from 'react-hyperscript' import * as d3 from 'd3' import * as React from 'react' -import { PairwiseComparison } from '../../types' +import { ViewState, DredgeConfig } from '../../types' import { getPlotBins, Bin } from '../../utils' import padding from './padding' import { PlotDimensions, useDimensions } from './hooks' +type InteractionActions = + 'drag' | + 'zoom' + const { useState, useEffect, useRef } = React const TRANSCRIPT_BIN_MULTIPLIERS = [ @@ -24,16 +28,16 @@ type PlotProps = { loading: boolean, width: number; height: number; - abundanceLimits: [[number, number], [number, number]]; - pairwiseData: PairwiseComparison | null; - pValueThreshold: number; - - hoveredTranscript: string | null; - savedTranscripts: Set; - - // FIXME - brushedArea: any; -} +} & Pick & Pick function useAxes( svgEl: SVGSVGElement | null, @@ -99,28 +103,22 @@ function useBins( const ref = useRef>() useEffect(() => { - if (svgEl) { - const { xScale, yScale } = dimensions - - d3.select(svgEl) - .select('.squares > g') - .remove() - - if (loading) return; - - if (pairwiseData === null) { - d3.select(svgEl) - .select('.squares') - .append('g') - .append('text') - .attr('x', xScale(d3.mean(xScale.domain())!)) - .attr('y', yScale(d3.mean(yScale.domain())!)) - .text('No data available for comparison') - .style('text-anchor', 'middle') + if (!svgEl || loading) return; - return; - } + const { xScale, yScale } = dimensions + if (pairwiseData === null) { + d3.select(svgEl) + .select('.squares') + .append('g') + .append('text') + .attr('x', xScale(d3.mean(xScale.domain())!)) + .attr('y', yScale(d3.mean(yScale.domain())!)) + .text('No data available for comparison') + .style('text-anchor', 'middle') + + return; + } else { const bins = getPlotBins( pairwiseData, ({ pValue }) => ( @@ -192,22 +190,53 @@ function useBins( .attr('y', 0) .attr('width', xScale.range()[1]) .attr('height', yScale.range()[0]) - - // drawSavedTranscripts() } return () => { - // resetSelectedBin() + d3.select(svgEl) + .select('.squares > g') + .remove() } }, [ svgEl, dimensions, pairwiseData, pValueThreshold ]) return ref } -function drawSavedTranscripts() { -} +function useWatchedTranscripts( + svgEl: SVGSVGElement | null, + dimensions: PlotDimensions, + { + savedTranscripts, + pairwiseData, + }: PlotProps +) { + useEffect(() => { + if (!svgEl || !pairwiseData) return + + const { xScale, yScale } = dimensions -function resetSelectedBin() { + d3.select(svgEl) + .select('.saved-transcripts') + .selectAll('circle') + .data([...savedTranscripts].filter(x => pairwiseData.has(x))) + .enter() + .append('circle') + // The pairwise data is guaranteed to have the transcript in it + // because of the filter above. I'm pretty sure this means that + // logATA and logFC are guaranteed to exist as well, but I can't + // quite recall right now. so.... TODO + .attr('cx', d => xScale(pairwiseData.get(d)!.logATA!)) + .attr('cy', d => yScale(pairwiseData.get(d)!.logFC!)) + .attr('r', 2) + .attr('fill', 'red') + + return () => { + d3.select(svgEl) + .select('.saved-transcripts') + .selectAll('circle') + .remove() + } + }, [ svgEl, dimensions, pairwiseData, savedTranscripts ]) } function useHoveredTranscriptMarker( @@ -215,49 +244,50 @@ function useHoveredTranscriptMarker( dimensions: PlotDimensions, { hoveredTranscript, - pairwiseData + pairwiseData, }: PlotProps ) { useEffect(() => { - if (svgEl === null) return - - const { xScale, yScale } = dimensions + if (!svgEl) return - const container = d3.select(svgEl).select('.hovered-marker') + const { xScale, yScale } = dimensions + , container = d3.select(svgEl).select('.hovered-marker') - container.selectAll('circle') - .transition() - .duration(360) - .ease(d3.easeCubicOut) - .style('opacity', 0) - .remove() + if (hoveredTranscript === null) return; + if (pairwiseData === null) return; - if (hoveredTranscript === null) return; - if (pairwiseData === null) return; + const data = pairwiseData.get(hoveredTranscript) - const data = pairwiseData.get(hoveredTranscript) + if (!data) return - if (!data) return + const { logATA, logFC } = data - const { logATA, logFC } = data + if (logATA === null || logFC === null) return - if (logATA === null || logFC === null) return + container.append('circle') + .attr('cx', xScale(logATA)) + .attr('cy', yScale(logFC)) + .attr('r', 20) + .attr('opacity', 1) + .attr('fill', 'none') + .attr('stroke', 'coral') + .attr('stroke-width', 2) - container.append('circle') - .attr('cx', xScale(logATA)) - .attr('cy', yScale(logFC)) - .attr('r', 20) - .attr('opacity', 1) - .attr('fill', 'none') - .attr('stroke', 'coral') - .attr('stroke-width', 2) + container.append('circle') + .attr('cx', xScale(logATA)) + .attr('cy', yScale(logFC)) + .attr('opacity', 1) + .attr('r', 3) + .attr('fill', 'coral') - container.append('circle') - .attr('cx', xScale(logATA)) - .attr('cy', yScale(logFC)) - .attr('opacity', 1) - .attr('r', 3) - .attr('fill', 'coral') + return () => { + container.selectAll('circle') + .transition() + .duration(360) + .ease(d3.easeCubicOut) + .style('opacity', 0) + .remove() + } }, [ hoveredTranscript, pairwiseData, dimensions ]) } @@ -281,6 +311,7 @@ export default function Plot(props: PlotProps) { const binSelectionRef = useBins(svgEl, dimensions, props) useHoveredTranscriptMarker(svgEl, dimensions, props) + useWatchedTranscripts(svgEl, dimensions, props) return ( h('div', [ @@ -351,7 +382,7 @@ export default function Plot(props: PlotProps) { h('g.hovered-marker'), ]), ]), - ]) + ]), ]) ) } diff --git a/src/components/TreatmentSelector/index.ts b/src/components/TreatmentSelector/index.ts index 5639967..8e7fc50 100644 --- a/src/components/TreatmentSelector/index.ts +++ b/src/components/TreatmentSelector/index.ts @@ -192,8 +192,6 @@ export default function TreatmentSelector(props: SelectorProps) { const treatmentEls = Array.from( svgEl.querySelectorAll(`[data-treatment="${view.hoveredTreatment}"]`)) as SVGElement[] - console.log(treatmentEls) - treatmentEls.forEach(el => { el.classList.add('active') }) diff --git a/src/view/reducer.ts b/src/view/reducer.ts index 0910c87..c697080 100644 --- a/src/view/reducer.ts +++ b/src/view/reducer.ts @@ -107,6 +107,8 @@ const reducer = createReducer(null as MultiViewState, builder => { source, transcripts: displayedTranscripts, } + + state.default.hoveredTranscript = null }) .addCase(viewActions.setSavedTranscripts.fulfilled, (state, action) => { if (!state) return