Skip to content

Commit

Permalink
Gene expression improvements (#483)
Browse files Browse the repository at this point in the history
* Download Dialog improvements

* Eslint config, plot progress

* Big Improvements

* small tweaks

* Tweak mui versions to fix build warning

* Working tissue selection component

* Progress

* checkpoint

* Refactor MultiSelect to handle biosamples

* Single assembly, refactor to support

* Rework Gene Autocomplete

* Shorten labels of controls

* Bit of cleanup
  • Loading branch information
jpfisher72 authored Nov 4, 2024
1 parent 75a0ffd commit 2096359
Show file tree
Hide file tree
Showing 24 changed files with 3,125 additions and 6,756 deletions.
3 changes: 0 additions & 3 deletions screen2.0/.eslintrc.json

This file was deleted.

23 changes: 23 additions & 0 deletions screen2.0/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
{ ignores: ['dist'] },
{
extends: [js.configs.recommended, ...tseslint.configs.recommended],
files: ['**/*.{ts,tsx}'],
plugins: {
'react-hooks': reactHooks,
'react-refresh': reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
)
15 changes: 8 additions & 7 deletions screen2.0/next.config.js → screen2.0/next.config.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
/** @type {import('next').NextConfig} */

let assetPrefix = ''
let basePath = ''
// @ts-check

/**
* @type {import('next').NextConfig}
*/

const nextConfig = {
// output: "export",
Expand All @@ -12,8 +13,8 @@ const nextConfig = {
},
generateEtags: false,
trailingSlash: false,
assetPrefix: assetPrefix,
basePath: basePath,
assetPrefix: '',
basePath: '',
logging: {
fetches: {
fullUrl: true,
Expand All @@ -32,4 +33,4 @@ const nextConfig = {
}
}

module.exports = nextConfig
export default nextConfig
20 changes: 13 additions & 7 deletions screen2.0/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"name": "screen2.0",
"version": "0.0.5",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
Expand Down Expand Up @@ -43,10 +44,10 @@
"@emotion/cache": "^11.13.1",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^6.1.1",
"@mui/lab": "^6.0.0-beta.10",
"@mui/material": "^6.1.1",
"@mui/material-nextjs": "^6.1.1",
"@mui/icons-material": "^6.1.5",
"@mui/lab": "6.0.0-beta.13",
"@mui/material": "^6.1.5",
"@mui/material-nextjs": "^6.1.5",
"@parcel/watcher": "^2.4.1",
"@types/node": "^22.6.1",
"@types/react": "^18.3.8",
Expand All @@ -58,8 +59,6 @@
"@weng-lab/ts-ztable": "^4.0.1",
"autoprefixer": "10.4.20",
"bpnet-ui": "^0.3.8",
"eslint": "9.11.1",
"eslint-config-next": "14.2.13",
"graphql": "^16.9.0",
"jubilant-carnival": "^0.6.0",
"logots-react": "^0.2.0",
Expand All @@ -79,13 +78,20 @@
"uuid": "^10.0.0"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@graphql-codegen/cli": "^5.0.2",
"@graphql-codegen/client-preset": "^4.3.3",
"@graphql-typed-document-node/core": "^3.2.0",
"eslint": "9.13.0",
"eslint-config-next": "15.0.1",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-react-refresh": "^0.4.14",
"file-loader": "^6.2.0",
"lint-staged": "^15.2.10",
"prettier": "^3.3.3",
"typescript": "5.6.2",
"typescript-eslint": "^8.11.0",
"webpack": "^5.94.0",
"webpack-cli": "^5.1.4"
},
Expand All @@ -104,5 +110,5 @@
"not ie <= 11",
"not op_mini all"
],
"packageManager": "yarn@3.5.0+sha512.2dc70be5fce9f66756d25b00a888f3ca66f86b502b76750e72ba54cec89da767b938c54124595e26f868825688e0fe3552c26c76a330673343057acadd5cfcf2"
"packageManager": "yarn@4.5.1"
}
File renamed without changes.
165 changes: 165 additions & 0 deletions screen2.0/src/app/applets/gene-expression/BarPlot.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import React, { useCallback, useMemo, useRef } from 'react';
import { Bar } from '@visx/shape';
import { scaleBand, scaleLinear } from '@visx/scale';
import { AxisTop } from '@visx/axis';
import { Group } from '@visx/group';
import { Text } from '@visx/text';
import { useParentSize } from '@visx/responsive';
import { defaultStyles as defaultTooltipStyles, useTooltip, TooltipWithBounds, Portal } from '@visx/tooltip';

export interface BarData<T> {
category: string;
label: string;
value: number;
color?: string;
metadata?: T;
}

export interface BarPlotProps<T> {
data: BarData<T>[];
SVGref?: React.MutableRefObject<SVGSVGElement>
topAxisLabel?: string;
onBarClicked?: (bar: BarData<T>) => void;
TooltipContents?: (bar: BarData<T>) => React.ReactNode
}

const VerticalBarPlot = <T,>({
data = [],
SVGref,
topAxisLabel,
onBarClicked,
TooltipContents
}: BarPlotProps<T>) => {
const { parentRef, width: ParentWidth } = useParentSize({ debounceTime: 150 });
const { tooltipOpen, tooltipLeft, tooltipTop, tooltipData, hideTooltip, showTooltip } = useTooltip<BarData<T>>({});
const requestRef = useRef<number | null>(null);
const tooltipDataRef = useRef<{ top: number; left: number; data: BarData<T> } | null>(null);

const handleMouseMove = useCallback((event: React.MouseEvent, barData: BarData<T>) => {
tooltipDataRef.current = {
top: event.pageY,
left: event.pageX,
data: barData,
};
if (!requestRef.current) {
requestRef.current = requestAnimationFrame(() => {
if (tooltipDataRef.current) {
showTooltip({
tooltipTop: tooltipDataRef.current.top,
tooltipLeft: tooltipDataRef.current.left,
tooltipData: tooltipDataRef.current.data,
});
}
requestRef.current = null;
});
}
}, [showTooltip]);

const width = useMemo(() => Math.max(500, ParentWidth), [ParentWidth])

/**
* @todo it'd be nice to somehow extract these from what is passed to the component. This is hardcoded to fit the gene expression data
*/
const spaceForTopAxis = 50
const spaceOnBottom = 20
const spaceForCategory = 120
const spaceForLabel = 280

const height = data.length * 20 + spaceForTopAxis + spaceOnBottom

// Dimensions
const xMax = width - spaceForCategory - spaceForLabel;
const yMax = height - spaceForTopAxis - spaceOnBottom;

// Scales
const yScale = useMemo(() =>
scaleBand<string>({
domain: data.map((d) => d.label),
range: [0, yMax],
padding: 0.2,
}), [data, yMax])

const xScale = useMemo(() =>
scaleLinear<number>({
domain: [0, Math.max(...data.map((d) => d.value))],
range: [0, Math.max(xMax, 0)],
}), [data, xMax])

return (
<div ref={parentRef}>
{data.length === 0 ?
<p>No Data To Display</p>
:
<svg width={width} height={height} ref={SVGref}>
<Group left={spaceForCategory} top={spaceForTopAxis}>
{/* Top Axis with Label */}
<AxisTop scale={xScale} top={0} label={topAxisLabel} labelProps={{dy: -5, fontSize: 16}} numTicks={width < 600 ? 4 : undefined} />
{data.map((d) => {
const barHeight = yScale.bandwidth();
const barWidth = xScale(d.value) ?? 0;
const barY = yScale(d.label);
const barX = 0;
return (
<Group
key={d.label}
onClick={() => onBarClicked && onBarClicked(d)}
style={onBarClicked && { cursor: 'pointer' }}
onMouseMove={(event) => handleMouseMove(event, d)}
onMouseLeave={() => hideTooltip()}
>
{/* Category label to the left of each bar */}
<Text
x={-10} // Positioning slightly to the left of the bar
y={(barY ?? 0) + barHeight / 2}
dy=".35em"
fontSize={12}
textAnchor="end"
fill="black"
>
{d.category}
</Text>
<Group>
<Bar
key={`bar-${d.label}`}
x={barX}
y={barY}
width={barWidth}
height={barHeight}
fill={d.color || "black"}
rx={2}
/>
{/* Value label next to the bar */}
<Text
x={barX + barWidth + 5} // Position label slightly after the end of the bar
y={(barY ?? 0) + barHeight / 2}
dy=".35em" // Vertically align to the middle of the bar
fontSize={12}
fill="black"
>
{d.label}
</Text>
</Group>
</Group>
);
})}
</Group>
</svg>

}
{/* Maybe should provide a default tooltip */}
{TooltipContents && tooltipOpen && (
<Portal>
<TooltipWithBounds
top={tooltipTop}
left={tooltipLeft}
style={{ ...defaultTooltipStyles, backgroundColor: '#283238', color: 'white', zIndex: 1000 }}
>
<TooltipContents {...tooltipData} />
</TooltipWithBounds>
</Portal>
)}
</div>
);
};

export default VerticalBarPlot;
Loading

0 comments on commit 2096359

Please sign in to comment.