Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query by Sets #480

Open
wants to merge 21 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9448069
Query by sets, needs rendering complete
JakeWags Aug 1, 2024
785cc71
Add Query button
JakeWags Aug 6, 2024
e6aaef0
Merge remote-tracking branch 'origin/fix-build-errors' into 375-query…
JakeWags Aug 8, 2024
7d40642
Merge branch '24-backselection' into 375-query-by-set
JakeWags Aug 8, 2024
1f585fc
Complete merge with backselection
JakeWags Aug 8, 2024
19b7ca5
Merge branch '24-backselection' into 375-query-by-set
JakeWags Aug 8, 2024
177ce16
Merge branch 'main' into 375-query-by-set
JakeWags Feb 11, 2025
9fbd2be
Add setQuery support to UpsetConfig and related state management
JakeWags Feb 11, 2025
c584a58
Implement SetQuery functionality and related components for enhanced …
JakeWags Feb 12, 2025
c44b231
Enhance SetQueryRow component with membership status retrieval and re…
JakeWags Feb 12, 2025
15e3796
Add total query size calculation and SizeBar component to SetQueryRow
JakeWags Feb 12, 2025
9405df9
Add semicolon to remove provenance and revert default state
JakeWags Feb 12, 2025
207a001
Merge branch 'main' into 375-query-by-set
JakeWags Feb 12, 2025
1c8f21e
Refactor rendering logic for clarity and consistency; update variable…
JakeWags Feb 13, 2025
22b15e2
Export utility functions and enhance documentation for clarity in ren…
JakeWags Feb 13, 2025
0055250
Update recoil state set function for query interface
JakeWags Feb 13, 2025
b1fcfcc
Memoizing queryBySetInterface
JakeWags Feb 13, 2025
84289c7
Merge branch 'main' into 375-query-by-set
JakeWags Feb 19, 2025
601b8a7
Fix eslint fallthrough rule violation in convertConfig function
JakeWags Feb 19, 2025
e75c2b5
Refactor many queryBySets files for code consistency and performance …
JakeWags Feb 20, 2025
cfbea7b
Add queryBySets test
JakeWags Feb 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { configAtom } from './atoms/configAtoms';
import { queryParamAtom } from './atoms/queryParamAtom';
import { getMultinetSession } from './api/session';

/** @jsxImportSource @emotion/react */
// eslint-disable-next-line @typescript-eslint/no-unused-vars

const defaultVisibleSets = 6;
Expand Down
105 changes: 95 additions & 10 deletions packages/core/src/convertConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
SortByOrder, SortVisibleBy, UpsetConfig,
AttributePlots,
ElementSelection,
AltText,
AltText, SetQuery,
} from './types';
import { isUpsetConfig } from './typecheck';
import { DefaultConfig } from './defaultConfig';
Expand Down Expand Up @@ -60,6 +60,79 @@ type Version0_1_0 = {
userAltText: AltText | null;
}

type Version0_1_1 = {
plotInformation: PlotInformation;
horizontal: boolean;
firstAggregateBy: AggregateBy;
firstOverlapDegree: number;
secondAggregateBy: AggregateBy;
secondOverlapDegree: number;
sortVisibleBy: SortVisibleBy;
sortBy: string;
sortByOrder: SortByOrder;
filters: {
maxVisible: number;
minVisible: number;
hideEmpty: boolean;
hideNoSet: boolean;
};
visibleSets: ColumnName[];
visibleAttributes: ColumnName[];
attributePlots: AttributePlots;
bookmarks: Bookmark[];
collapsed: string[];
plots: {
scatterplots: Scatterplot[];
histograms: Histogram[];
};
allSets: Column[];
selected: Row | null;
elementSelection: ElementSelection | null;
version: '0.1.1';
useUserAlt: boolean;
userAltText: AltText | null;
intersectionSizeLabels: boolean;
setSizeLabels: boolean;
showHiddenSets: boolean;
}

type Version0_1_2 = {
plotInformation: PlotInformation;
horizontal: boolean;
firstAggregateBy: AggregateBy;
firstOverlapDegree: number;
secondAggregateBy: AggregateBy;
secondOverlapDegree: number;
sortVisibleBy: SortVisibleBy;
sortBy: string;
sortByOrder: SortByOrder;
filters: {
maxVisible: number;
minVisible: number;
hideEmpty: boolean;
hideNoSet: boolean;
};
visibleSets: ColumnName[];
visibleAttributes: ColumnName[];
attributePlots: AttributePlots;
bookmarks: Bookmark[];
collapsed: string[];
plots: {
scatterplots: Scatterplot[];
histograms: Histogram[];
};
allSets: Column[];
selected: Row | null;
elementSelection: ElementSelection | null;
version: '0.1.2';
useUserAlt: boolean;
userAltText: AltText | null;
intersectionSizeLabels: boolean;
setSizeLabels: boolean;
showHiddenSets: boolean;
setQuery: SetQuery | null;
}

/**
* Config type before versioning was implemented.
*/
Expand Down Expand Up @@ -98,14 +171,27 @@ type PreVersionConfig = {
* @returns The converted config.
*/
// eslint-disable-next-line camelcase
function convert0_1_0(config: Version0_1_0): UpsetConfig {
(config as unknown as UpsetConfig).version = '0.1.1';
(config as unknown as UpsetConfig).intersectionSizeLabels = DefaultConfig.intersectionSizeLabels;
(config as unknown as UpsetConfig).setSizeLabels = DefaultConfig.setSizeLabels;
(config as unknown as UpsetConfig).showHiddenSets = DefaultConfig.showHiddenSets;
return (config as unknown as UpsetConfig);
function convert0_1_0(config: Version0_1_0): Version0_1_1 {
(config as unknown as Version0_1_1).version = '0.1.1';
(config as unknown as Version0_1_1).intersectionSizeLabels = DefaultConfig.intersectionSizeLabels;
(config as unknown as Version0_1_1).setSizeLabels = DefaultConfig.setSizeLabels;
(config as unknown as Version0_1_1).showHiddenSets = DefaultConfig.showHiddenSets;
return (config as unknown as Version0_1_1);
}

/**
* Converts a configuration object from version 0.1.1 to version 0.1.2.
*
* @param config - The configuration object of version 0.1.1 to be converted.
* @returns The updated configuration object with version 0.1.2.
*/
function convert0_1_1(config: Version0_1_1): UpsetConfig {
(config as unknown as Version0_1_2).version = '0.1.2';
(config as unknown as Version0_1_2).setQuery = DefaultConfig.setQuery;
return config as unknown as Version0_1_2;
}


/**
* Converts a pre-versioned config to the current version.
* @param config The config to convert.
Expand Down Expand Up @@ -150,12 +236,11 @@ export function convertConfig(config: unknown): UpsetConfig {
switch ((config as {version: string}).version) {
case '0.1.0':
convert0_1_0(config as Version0_1_0);
break;
case '0.1.1':
convert0_1_1(config as Version0_1_1);
default:
void 0;
}
/* eslint-enable no-fallthrough */
/* eslint-enable no-void */

if (!isUpsetConfig(config)) {
// eslint-disable-next-line no-console
Expand Down
8 changes: 6 additions & 2 deletions packages/core/src/defaultConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const DefaultConfig: UpsetConfig = {
title: null,
},
horizontal: false,
firstAggregateBy: 'None',
firstAggregateBy: 'Degree',
firstOverlapDegree: 2,
secondAggregateBy: 'None',
secondOverlapDegree: 2,
Expand All @@ -37,8 +37,12 @@ export const DefaultConfig: UpsetConfig = {
useUserAlt: false,
userAltText: null,
elementSelection: null,
version: '0.1.1',
version: '0.1.2',
intersectionSizeLabels: true,
setSizeLabels: true,
showHiddenSets: true,
setQuery: {
name: '',
query: {},
},
};
120 changes: 80 additions & 40 deletions packages/core/src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,56 @@ import { firstAggregation, secondAggregation } from './aggregate';
import { filterRows } from './filter';
import { getSubsets } from './process';
import { sortRows } from './sort';
import { areRowsAggregates, isRowAggregate } from './typeutils';
import { areRowsAggregates, getBelongingSetsFromSetMembership, isPopulatedSetQuery, isRowAggregate } from './typeutils';
import {
Row, Rows, Sets, UpsetConfig,
Row, Rows, SetQueryMembership, Sets, UpsetConfig,
} from './types';

/**
* Maps Row IDs to Row objects
*/
export type RowMap = Record<string, Row>;

/**
* Represents a row to be rendered.
*
* @typedef {Object} RenderRow
* @property {string} id - The unique identifier for the row.
* @property {Row} row - The row data to be rendered.
*/
export type RenderRow = {
id: string;
row: Row;
};

/**
* Flattens a hierarchical structure of rows into a flat array of RenderRow objects.
*
* @param rows - The hierarchical structure of rows to flatten.
* @param flattenedRows - The array to store the flattened rows (optional, defaults to an empty array).
* @param idPrefix - The prefix to add to the IDs of the flattened rows (optional, defaults to an empty string).
* @returns The flattened array of RenderRow objects.
*/
const flattenRows = (
rows: Rows,
flattenedRows: RenderRow[] = [],
idPrefix: string = '',
): RenderRow[] => {
rows.order.forEach((rowId) => {
const row = rows.values[rowId];
const prefix = idPrefix + row.id;
flattenedRows.push({
id: prefix,
row,
});
if (isRowAggregate(row)) {
flattenRows(row.items, flattenedRows, prefix);
}
});

return flattenedRows;
};

/**
* Calculates the first aggregation for the given data and state.
* @param data - The data object containing items, sets, and attribute columns.
Expand Down Expand Up @@ -58,18 +98,49 @@ const secondAggRR = (data: any, state: UpsetConfig) => {
return rr;
};

function getQueryResult(r: Rows, membership: SetQueryMembership): Rows {
const queryResults: Rows = { order: [], values: {} };
flattenRows(r).forEach((rr) => {
let match = true;
Object.entries(membership).forEach(([set, status]) => {
if (status === 'Yes' && !getBelongingSetsFromSetMembership(rr.row.setMembership).includes(set)) {
match = false;
}
if (status === 'No' && getBelongingSetsFromSetMembership(rr.row.setMembership).includes(set)) {
match = false;
}
});

if (match) {
queryResults.order.push(rr.id);
queryResults.values[rr.id] = rr.row;
}
});

return queryResults;
}

/**
* Sorts the data by RR (Relative Risk) based on the provided state configuration.
*
* @param data - The data to be sorted.
* @param state - The state configuration containing the visible sets and sorting options.
* @param ignoreQuery - Whether to ignore the query when sorting the data. Set this to true to get the sorted rows as if there was no query.
* @returns The sorted rows based on the RR and the provided sorting options.
*/
const sortByRR = (data: any, state: UpsetConfig) => {
const sortByRR = (data: any, state: UpsetConfig, ignoreQuery = false) => {
if (!data || typeof data !== 'object' || !Object.hasOwn(data, 'sets')) return { order: [], values: {} };

const vSets: Sets = Object.fromEntries(Object.entries(data.sets as Sets).filter(([name, _set]) => state.visibleSets.includes(name)));
const rr = secondAggRR(data, state);

let rr: Rows;

if (!ignoreQuery && state.setQuery !== null && isPopulatedSetQuery(state.setQuery)) {
const subsets: Rows = getSubsets(data.items, data.sets, state.visibleSets, data.attributeColumns);
rr = getQueryResult(subsets, state.setQuery.query);
} else {
rr = secondAggRR(data, state);
}

return sortRows(rr, state.sortBy, state.sortVisibleBy, vSets, state.sortByOrder);
};
Expand All @@ -79,10 +150,11 @@ const sortByRR = (data: any, state: UpsetConfig) => {
*
* @param data - The data to be filtered.
* @param state - The state object containing the Upset configuration.
* @param ignoreQuery - Whether to ignore the query when filtering the data. Set this to true to get the filtered rows as if there was no query.
* @returns The filtered rows based on the RR algorithm and the provided filters.
*/
const filterRR = (data: any, state: UpsetConfig) => {
const rr = sortByRR(data, state);
const filterRR = (data: any, state: UpsetConfig, ignoreQuery = false) => {
const rr = sortByRR(data, state, ignoreQuery);

return filterRows(rr, state.filters);
};
Expand All @@ -91,42 +163,10 @@ const filterRR = (data: any, state: UpsetConfig) => {
* Retrieves the rows of data based on the provided data and state.
* @param data - The data to filter.
* @param state - The state of the UpsetConfig.
* @param ignoreQuery - Whether to ignore the query when filtering the data. Set this to true to get the filtered rows as if there was no query.
* @returns The filtered rows of data.
*/
export const getRows = (data: any, state: UpsetConfig) => filterRR(data, state);

export type RenderRow = {
id: string;
row: Row;
};

/**
* Flattens a hierarchical structure of rows into a flat array of RenderRow objects.
*
* @param rows - The hierarchical structure of rows to flatten.
* @param flattenedRows - The array to store the flattened rows (optional, defaults to an empty array).
* @param idPrefix - The prefix to add to the IDs of the flattened rows (optional, defaults to an empty string).
* @returns The flattened array of RenderRow objects.
*/
const flattenRows = (
rows: Rows,
flattenedRows: RenderRow[] = [],
idPrefix: string = '',
): RenderRow[] => {
rows.order.forEach((rowId) => {
const row = rows.values[rowId];
const prefix = idPrefix + row.id;
flattenedRows.push({
id: prefix,
row,
});
if (isRowAggregate(row)) {
flattenRows(row.items, flattenedRows, prefix);
}
});

return flattenedRows;
};
export const getRows = (data: any, state: UpsetConfig, ignoreQuery = false) => filterRR(data, state, ignoreQuery);

/**
* Flattens the rows of data based on the provided state configuration.
Expand Down
Loading
Loading