Skip to content

Commit

Permalink
chore(wmg): rewrite x axis using react (#3879)
Browse files Browse the repository at this point in the history
* rewrite x axis as react

* update

* update

* prettier

* remove dup prop

Co-authored-by: atarashansky <atarashansky@CZIMACOS3990.hsd1.ma.comcast.net>
  • Loading branch information
atarashansky and atarashansky authored Jan 6, 2023
1 parent bf07eff commit d37b16d
Show file tree
Hide file tree
Showing 8 changed files with 177 additions and 279 deletions.
7 changes: 5 additions & 2 deletions frontend/src/common/queries/filter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,8 +768,11 @@ function sanitizeDatasetResponse(
): DatasetResponse {
const sanitizedDatasetResponse = { ...datasetResponse };

sanitizedDatasetResponse.self_reported_ethnicity = (datasetResponse.self_reported_ethnicity ?? []).filter(
(self_reported_ethnicity) => !SELF_REPORTED_ETHNICITY_DENY_LIST.includes(self_reported_ethnicity.label)
sanitizedDatasetResponse.self_reported_ethnicity = (
datasetResponse.self_reported_ethnicity ?? []
).filter(
(self_reported_ethnicity) =>
!SELF_REPORTED_ETHNICITY_DENY_LIST.includes(self_reported_ethnicity.label)
);

sanitizedDatasetResponse.assay = datasetResponse.assay ?? [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,75 +1,67 @@
import { init } from "echarts";
import { useEffect, useRef, useState } from "react";
import { EMPTY_OBJECT } from "src/common/constants/utils";
import { useEffect, useState } from "react";
import { useDeleteGenesAndCellTypes } from "../../hooks/useDeleteGenesAndCellTypes";
import { useUpdateXAxisChart } from "../../hooks/useUpdateXAxisChart";
import { CHART_LEFT_PADDING } from "../../style";
import { getHeatmapWidth, Y_AXIS_CHART_WIDTH_PX } from "../../utils";
import { XAxisWrapper, XAxisContainer } from "./style";
import { CHART_LEFT_PADDING, SELECTED_STYLE } from "../../style";
import {
getHeatmapWidth,
formatLabel,
X_AXIS_CHART_HEIGHT_PX,
Y_AXIS_CHART_WIDTH_PX,
} from "../../utils";
import {
XAxisWrapper,
XAxisContainer,
XAxisLabel,
GeneButtonStyle,
} from "./style";

interface Props {
geneNames: string[];
}

export default function XAxisChart({ geneNames }: Props): JSX.Element {
const [isChartInitialized, setIsChartInitialized] = useState(false);
const [xAxisChart, setXAxisChart] = useState<echarts.ECharts | null>(null);
const [heatmapWidth, setHeatmapWidth] = useState(getHeatmapWidth(geneNames));

const { genesToDelete, handleGeneClick } = useDeleteGenesAndCellTypes();

const xAxisRef = useRef(null);

// Update heatmap size
useEffect(() => {
setHeatmapWidth(getHeatmapWidth(geneNames));
}, [geneNames]);

// Initialize charts
useEffect(() => {
const { current: xAxisCurrent } = xAxisRef;

if (!xAxisCurrent || isChartInitialized) {
return;
}

setIsChartInitialized(true);

const xAxisChart = init(xAxisCurrent, EMPTY_OBJECT, {
renderer: "svg",
useDirtyRect: true,
});

setXAxisChart(xAxisChart);
}, [xAxisRef, isChartInitialized]);

// Bind xAxisChart events
useEffect(() => {
xAxisChart?.on("click", function (params) {
/**
* `value` is set by utils.getGeneNames()
*/
const { value } = params;
handleGeneClick(value as string);
});
}, [handleGeneClick, xAxisChart]);

useUpdateXAxisChart({
geneNames,
genesToDelete,
heatmapWidth,
xAxisChart,
});
return (
<XAxisWrapper
width={heatmapWidth}
left={Y_AXIS_CHART_WIDTH_PX + CHART_LEFT_PADDING}
>
<XAxisContainer
data-test-id="gene-labels"
width={heatmapWidth}
ref={xAxisRef}
/>
<XAxisContainer data-test-id="gene-labels" width={heatmapWidth}>
{geneNames.map((geneName) => {
const active = genesToDelete.includes(geneName);
const currentFont = `
normal
${active ? SELECTED_STYLE.fontWeight : "normal"}
${SELECTED_STYLE.fontSize}px ${SELECTED_STYLE.fontFamily}
`;
const formattedLabel = formatLabel(
geneName,
X_AXIS_CHART_HEIGHT_PX,
currentFont,
0
);
return (
<GeneButtonStyle
key={geneName}
onClick={() => handleGeneClick(geneName)}
data-test-id={`gene-label-${geneName}`}
>
<XAxisLabel
active={genesToDelete.includes(geneName)}
font={currentFont}
>
{formattedLabel}
</XAxisLabel>
</GeneButtonStyle>
);
})}
</XAxisContainer>
</XAxisWrapper>
);
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import styled from "@emotion/styled";
import { SELECTED_STYLE } from "../../style";
import { X_AXIS_CHART_HEIGHT_PX, Y_AXIS_CHART_WIDTH_PX } from "../../utils";
import { HEAT_MAP_BASE_CELL_WIDTH_PX } from "../../utils";

export const ECHART_AXIS_LABEL_COLOR_HEX = "#6e7079";
const ECHART_AXIS_LABEL_FONT_SIZE = 12;
Expand All @@ -10,6 +12,9 @@ export const XAxisContainer = styled.div`
height: ${X_AXIS_CHART_HEIGHT_PX}px;
top: 0px;
position: absolute;
display: flex;
flex-direction: row;
justify-content: space-between;
`;

export const XAxisWrapper = styled.div`
Expand All @@ -20,6 +25,28 @@ export const XAxisWrapper = styled.div`
z-index: 2;
`;

export const XAxisLabel = styled.div`
${selectedStyle}
width: ${HEAT_MAP_BASE_CELL_WIDTH_PX}px;
text-orientation: sideways;
writing-mode: vertical-rl;
display: inline-block;
user-select: none;
color: ${ECHART_AXIS_LABEL_COLOR_HEX};
`;

export const GeneButtonStyle = styled.button`
cursor: pointer;
background-color: white;
border: none;
z-index: 2;
display: inline-flex;
justify-content: center;
align-items: end;
white-space: nowrap;
overflow: hidden;
`;

// adjust the left position of CellCountLabel by -20 to center it properly
export const CellCountLabel = styled.div`
font: ${ECHART_AXIS_LABEL_FONT_SIZE}px sans-serif;
Expand All @@ -31,7 +58,7 @@ export const CellCountLabel = styled.div`
writing-mode: vertical-rl;
padding-top: 16px;
position: absolute;
top: -8px;
top: 0px;
text-align: right;
left: ${Y_AXIS_CHART_WIDTH_PX - 20}px;
z-index: 2;
Expand All @@ -49,3 +76,10 @@ function xAxisWidth({ width }: { width: number }) {
width: ${width}px;
`;
}

function selectedStyle({ font, active }: { font: string; active: boolean }) {
return `
font: ${font};
background-color: ${active ? SELECTED_STYLE.backgroundColor : "white"};
`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
deserializeCellTypeMetadata,
getAllSerializedCellTypeMetadata,
getHeatmapHeight,
SELECTED_STYLE,
Y_AXIS_CHART_WIDTH_PX,
formatLabel,
} from "../../utils";
import ReplaySVG from "./icons/replay.svg";
import InfoSVG from "./icons/info-sign-icon.svg";
Expand All @@ -30,6 +30,7 @@ import {
FlexRow,
InfoButtonWrapper,
} from "./style";
import { SELECTED_STYLE } from "../../style";

const MAX_DEPTH = 2;

Expand Down Expand Up @@ -108,7 +109,7 @@ export default memo(function YAxisChart({
const { fontWeight, fontSize, fontFamily } = SELECTED_STYLE;
const selectedFont = `${fontWeight} ${fontSize}px ${fontFamily}`;

const paddedName = formatCellLabel(
const paddedName = formatLabel(
name,
Y_AXIS_CHART_WIDTH_PX - 90, // scale based on y-axis width
selectedFont, // prevents selected style from overlapping count
Expand Down Expand Up @@ -214,82 +215,3 @@ const CellTypeButton = ({
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}

/**
* Used to calculate text pixel widths. Should be only created once.
*/
const CTX =
(typeof document !== "undefined" &&
document.createElement("canvas").getContext("2d")) ||
null;

/**
* Formats and truncates the cell type name to a given width
*
* @param name The text to truncate
* @param maxWidth The max width in pixels the string should be
* @param font The font family and font size as a string. Ex. "bold 12px sans-serif"
* @param displayDepth The depth of the cell type name (indentation/padding)
* @returns The string fixed to a certain pixel width
*/
function formatCellLabel(
name: string,
maxWidth: number,
font: string,
displayDepth = 0
): string {
CTX!.font = font;
const ellipsisWidth = CTX!.measureText("...").width;

const padding = " ".repeat(displayDepth * 8);
const paddingWidth = CTX!.measureText(padding).width;

if (CTX!.measureText(name).width + paddingWidth <= maxWidth) {
return padding + name;
}

const labelHalfWidth = (maxWidth - paddingWidth - ellipsisWidth) / 2;

const firstHalf = getFixedWidth(name, labelHalfWidth, font);
const secondHalf = getFixedWidth(name, labelHalfWidth, font, true);

return padding + firstHalf + "..." + secondHalf;
}

/**
* Truncates the string to a given width
*
* @param text The text to truncate
* @param maxWidth The max width in pixels the string should be
* @param font The font family and font size as a string. Ex. "bold 12px sans-serif"
* @param reverse Whether to truncate the end or beginning of the string
* @returns The string fixed to a certain pixel width
*/
export function getFixedWidth(
text: string,
maxWidth: number,
font: string,
reverse = false
): string {
CTX!.font = font;

if (reverse) {
for (let i = text.length; i >= 0; i--) {
const substring = text.substring(i - 1);
const textWidth = CTX!.measureText(substring).width;
if (textWidth > maxWidth) {
return text.substring(i);
}
}
} else {
for (let i = 0; i < text.length; i++) {
const substring = text.substring(0, i + 1);
const textWidth = CTX!.measureText(substring).width;
if (textWidth > maxWidth) {
return text.substring(0, i);
}
}
}

return text;
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import Image from "next/image";
import styled from "@emotion/styled";
import {
SELECTED_STYLE,
HEAT_MAP_BASE_CELL_PX,
X_AXIS_CHART_HEIGHT_PX,
Y_AXIS_CHART_WIDTH_PX,
} from "../../utils";
import { ECHART_AXIS_LABEL_COLOR_HEX } from "../XAxisChart/style";
import { SELECTED_STYLE } from "../../style";

export const Y_AXIS_TISSUE_WIDTH_PX = 30;

Expand Down

This file was deleted.

Loading

0 comments on commit d37b16d

Please sign in to comment.