Skip to content

Commit

Permalink
feat: move continuous fields to the dataset drawer if there is a sing…
Browse files Browse the repository at this point in the history
…le value for all cells (#918)

Co-authored-by: atarashansky <[email protected]>
  • Loading branch information
seve and atarashansky authored May 10, 2024
1 parent 1de020b commit 055b24d
Show file tree
Hide file tree
Showing 6 changed files with 108 additions and 26 deletions.
2 changes: 1 addition & 1 deletion client/src/annoMatrix/annoMatrix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -350,8 +350,8 @@ export default abstract class AnnoMatrix {
_resolveCachedQueries(field: Field, queries: Query[]): LabelType[] {
return queries
.map((query: Query) =>
// @ts-expect-error ts-migrate --- suppressing TS defect (https://github.com/microsoft/TypeScript/issues/44373).
// Compiler is complaining that expression is not callable on array union types. Remove suppression once fixed.
// @ts-expect-error ts-migrate --- suppressing TS defect (https://github.com/microsoft/TypeScript/issues/44373).
_whereCacheGet(this._whereCache, this.schema, field, query).filter(
(cacheKey: LabelType | undefined): cacheKey is LabelType =>
cacheKey !== undefined && this._cache[field].hasCol(cacheKey)
Expand Down
44 changes: 42 additions & 2 deletions client/src/components/brushableHistogram/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type BrushableHistogramProps = Partial<RootState> & BrushableHistogramOwnProps;
isColorAccessor:
state.colors.colorAccessor === field &&
state.colors.colorMode !== "color by categorical metadata",
singleContinuousValues: state.singleContinuousValue.singleContinuousValues,
};
})
class HistogramBrush extends React.PureComponent<BrushableHistogramProps> {
Expand Down Expand Up @@ -235,8 +236,25 @@ class HistogramBrush extends React.PureComponent<BrushableHistogramProps> {
};

fetchAsyncProps = async () => {
const { annoMatrix, width, onGeneExpressionComplete } = this.props;
const {
annoMatrix,
width,
onGeneExpressionComplete,
field,
dispatch,
singleContinuousValues,
} = this.props;
const { isClipped } = annoMatrix;
if (singleContinuousValues.has(field)) {
return {
histogram: undefined,
range: undefined,
unclippedRange: undefined,
unclippedRangeColor: globals.blue,
isSingleValue: true,
OK2Render: false,
};
}

const query = this.createQuery();
if (!query) {
Expand All @@ -258,6 +276,29 @@ class HistogramBrush extends React.PureComponent<BrushableHistogramProps> {
const summary = column.summarizeContinuous();
const range = [summary.min, summary.max];

// seve: if the anno matrix is not a view and it is a single value, remove it from histograms and send it to the dataset drawer
// NOTE: this also includes embedding views, so if the default embedding subsets to a view and there is a single continuous value for a field, it will not be added to the dataset drawer
if (summary.min === summary.max && !annoMatrix.isView) {
dispatch({
type: "add single continuous value",
field,
value: summary.min,
});
return {
histogram: undefined,
range,
unclippedRange: range,
unclippedRangeColor: globals.blue,
isSingleValue: true,
OK2Render: false,
};
}

const isSingleValue = summary.min === summary.max;

// if we are clipped, fetch both our value and our unclipped value,
// as we need the absolute min/max range, not just the clipped min/max.

let unclippedRange = [...range];
if (isClipped) {
const parent: Dataframe = await annoMatrix.viewOf.fetch(
Expand Down Expand Up @@ -290,7 +331,6 @@ class HistogramBrush extends React.PureComponent<BrushableHistogramProps> {
HEIGHT_MINI
);

const isSingleValue = summary.min === summary.max;
const nonFiniteExtent =
summary.min === undefined ||
summary.max === undefined ||
Expand Down
22 changes: 17 additions & 5 deletions client/src/components/infoDrawer/infoDrawer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { connect } from "react-redux";
import { Drawer, Position } from "@blueprintjs/core";

/* App dependencies */
import InfoFormat, { SingleValueCategories } from "./infoFormat";
import InfoFormat, { SingleValues } from "./infoFormat";
import { AppDispatch, RootState } from "../../reducers";
import { selectableCategoryNames } from "../../util/stateManager/controlsHelpers";
import { DatasetMetadata } from "../../common/types/entities";
import { Schema } from "../../common/types/schema";
import { SingleContinuousValueState } from "../../reducers/singleContinuousValue";

/**
* Actions dispatched by info drawer.
Expand All @@ -31,6 +32,7 @@ interface StateProps {
datasetMetadata: DatasetMetadata;
isOpen: boolean;
schema: Schema;
singleContinuousValues: SingleContinuousValueState["singleContinuousValues"];
}

type Props = DispatchProps & OwnProps & StateProps;
Expand All @@ -42,6 +44,7 @@ const mapStateToProps = (state: RootState): StateProps => ({
datasetMetadata: state.datasetMetadata?.datasetMetadata,
isOpen: state.controls.datasetDrawer,
schema: state.annoMatrix.schema,
singleContinuousValues: state.singleContinuousValue.singleContinuousValues,
});

/**
Expand All @@ -58,24 +61,33 @@ class InfoDrawer extends PureComponent<Props> {
};

render(): JSX.Element {
const { datasetMetadata, position, schema, isOpen } = this.props;
const {
datasetMetadata,
position,
schema,
isOpen,
singleContinuousValues,
} = this.props;

const allCategoryNames = selectableCategoryNames(schema).sort();
const singleValueCategories: SingleValueCategories = new Map();
const allSingleValues: SingleValues = new Map();

allCategoryNames.forEach((catName) => {
const isUserAnno = schema?.annotations?.obsByName[catName]?.writable;
const colSchema = schema.annotations.obsByName[catName];
if (!isUserAnno && colSchema.categories?.length === 1) {
singleValueCategories.set(catName, colSchema.categories[0]);
allSingleValues.set(catName, colSchema.categories[0]);
}
});
singleContinuousValues.forEach((value, catName) => {
allSingleValues.set(catName, value);
});
return (
<Drawer size={480} onClose={this.handleClose} {...{ isOpen, position }}>
<InfoFormat
{...{
datasetMetadata,
singleValueCategories,
allSingleValues,
}}
/>
</Drawer>
Expand Down
34 changes: 16 additions & 18 deletions client/src/components/infoDrawer/infoFormat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ interface MetadataView {

interface Props {
datasetMetadata: DatasetMetadata;
singleValueCategories: SingleValueCategories;
allSingleValues: SingleValues;
}

export type SingleValueCategories = Map<string, Category>;
export type SingleValues = Map<string, Category>;

/**
* Sort collection links by custom sort order, create view-friendly model of link types.
Expand Down Expand Up @@ -244,16 +244,16 @@ const renderCollectionLinks = (

/**
* Render dataset metadata. That is, attributes found in categorical fields.
* @param singleValueCategories - Attributes from categorical fields
* @param renderSingleValues - Attributes from categorical fields
* @returns Markup for displaying meta in table format.
*/
const renderDatasetMetadata = (
singleValueCategories: SingleValueCategories
renderSingleValues: SingleValues
): JSX.Element | null => {
if (singleValueCategories.size === 0) {
if (renderSingleValues.size === 0) {
return null;
}
const metadataViews = buildDatasetMetadataViews(singleValueCategories);
const metadataViews = buildDatasetMetadataViews(renderSingleValues);
metadataViews.sort(sortDatasetMetadata);
return (
<>
Expand Down Expand Up @@ -328,7 +328,7 @@ const transformLinkTypeToDisplay = (type: string): string => {
* @returns Array of metadata key/value pairs.
*/
const buildDatasetMetadataViews = (
singleValueCategories: SingleValueCategories
singleValueCategories: SingleValues
): MetadataView[] =>
Array.from(singleValueCategories.entries())
.filter(([key, value]) => {
Expand All @@ -341,17 +341,15 @@ const buildDatasetMetadataViews = (
})
.map(([key, value]) => ({ key, value: String(value) }));

const InfoFormat = React.memo<Props>(
({ datasetMetadata, singleValueCategories }) => (
<div className={Classes.DRAWER_BODY}>
<div className={Classes.DIALOG_BODY}>
<H3>{datasetMetadata.collection_name}</H3>
<p>{datasetMetadata.collection_description}</p>
{renderCollectionLinks(datasetMetadata)}
{renderDatasetMetadata(singleValueCategories)}
</div>
const InfoFormat = React.memo<Props>(({ datasetMetadata, allSingleValues }) => (
<div className={Classes.DRAWER_BODY}>
<div className={Classes.DIALOG_BODY}>
<H3>{datasetMetadata.collection_name}</H3>
<p>{datasetMetadata.collection_description}</p>
{renderCollectionLinks(datasetMetadata)}
{renderDatasetMetadata(allSingleValues)}
</div>
)
);
</div>
));

export default InfoFormat;
2 changes: 2 additions & 0 deletions client/src/reducers/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import genesetsUI from "./genesetsUI";
import centroidLabels from "./centroidLabels";
import pointDialation from "./pointDilation";
import quickGenes from "./quickGenes";
import singleContinuousValue from "./singleContinuousValue";

import { gcMiddleware as annoMatrixGC } from "../annoMatrix";

Expand All @@ -40,6 +41,7 @@ const AppReducer = undoable(
["genesets", genesets],
["genesetsUI", genesetsUI],
["layoutChoice", layoutChoice],
["singleContinuousValue", singleContinuousValue],
["categoricalSelection", categoricalSelection],
["continuousSelection", continuousSelection],
["graphSelection", graphSelection],
Expand Down
30 changes: 30 additions & 0 deletions client/src/reducers/singleContinuousValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Action, AnyAction } from "redux";

export interface SingleContinuousValueState {
singleContinuousValues: Map<string, string>;
}

const initialState = {
singleContinuousValues: new Map(),
};

export interface SingleContinuousValueAction extends Action<string> {
field: string;
value: string;
}

const singleContinuousValue = (
state = initialState,
action: AnyAction
): SingleContinuousValueState => {
switch (action.type) {
case "add single continuous value":

state.singleContinuousValues.set(action.field, action.value);
return state;
default:
return state;
}
};

export default singleContinuousValue;

0 comments on commit 055b24d

Please sign in to comment.