Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5bb5d6b
Add stacked bar chart option for generic assay tracks (#10747)
inodb Apr 21, 2026
fcdca16
Fix WebGL frustum-clipping bars for stacked tracks with >5 shapes
inodb Apr 21, 2026
65e40e9
Sort stacked-bar tracks by a picked category, suppress redundant grou…
inodb Apr 22, 2026
0803d76
Option to render split generic-assay rows as bar charts instead of gr…
inodb Apr 22, 2026
08f1d25
Use white-to-dark gradient for non-negative generic-assay tracks
inodb Apr 22, 2026
d387040
Nested "Sort by" submenu for stacked-bar tracks
inodb Apr 22, 2026
d86f6fe
"Chart type" submenu + hide absolute mode for fraction-like profiles
inodb Apr 22, 2026
e81043c
Hide default sort items on stacked-bar tracks; relabel default sort
inodb Apr 22, 2026
dc5915e
Fix sort-by on mobile + stacked-bar track menu touch support
inodb Apr 22, 2026
77e6d64
Anchor the sort-by category at the bottom of each stacked bar
inodb Apr 22, 2026
bca3520
Add "Select all" in track picker and "Total" sort for absolute stacks
inodb Apr 23, 2026
091e3d6
Let Move up/Move down cross groups when the track is alone in its group
inodb Apr 23, 2026
29f2e6e
Restore Sort a-Z / Z-a / Don't sort items on stacked-bar tracks
inodb Apr 23, 2026
dc590ea
Fix stacked-bar a-Z/Z-a direction and move Chart type to menu bottom
inodb Apr 23, 2026
c454a2e
Revert "Let Move up/Move down cross groups when the track is alone in…
inodb Apr 23, 2026
67ec68f
Don't gray out Move up/down for single-track groups
inodb Apr 23, 2026
de107e7
Move up/down on stacked-bar tracks reorders within the generic-assay …
inodb Apr 23, 2026
ca8d4b5
Place bulk-toggle before Add Track and make it "Clear all" when selected
inodb Apr 23, 2026
1b8938b
Raise Select-all threshold from 25 to 50 entities
inodb Apr 23, 2026
e6f39c8
Guard against undefined submit payload and rejected-with-undefined
inodb Apr 23, 2026
4e3b8be
Batch generic-assay data fetch + guard null response + strip shared l…
inodb Apr 23, 2026
abebd1e
Chart-type selector in the Add Tracks dialog for generic-assay profiles
inodb Apr 23, 2026
9ce812c
Unit tests for sort comparators + prefix-strip; chart type in group h…
inodb Apr 23, 2026
1d28a80
Rework GenericAssaySelection layout: radios below, wide Add Track
inodb Apr 24, 2026
b4b9efa
Discriminated union for ICategoricalTrackSpec + chart-type loading sp…
inodb Apr 24, 2026
1d34af2
Speed up oncoprint rendering: skip core-js parseInt/parseFloat polyfill
inodb Apr 24, 2026
f464045
Use unary + instead of parseFloat on generic-assay datum values
inodb Apr 24, 2026
62ad277
Trim redundant/verbose comments in oncoprint stacked-bar changes
miccheck12 Jun 30, 2026
eef3c57
Drop stray common-dist/ gitignore entry
miccheck12 Jun 30, 2026
4f14c39
Address Copilot review issues on stacked-bar tracks
miccheck12 Jun 30, 2026
ecf5ed3
Use primary (blue) styling for Add Genes to Heatmap button
miccheck12 Jun 30, 2026
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
63 changes: 54 additions & 9 deletions packages/oncoprintjs/src/js/oncoprintmodel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ export type CustomTrackOption = {
weight?: string;
disabled?: boolean;
gapLabelsFn?: (model: OncoprintModel) => OncoprintGapConfig[];
// When set, this option is a parent item: hovering shows a submenu of the
// nested options. Mutually exclusive with onClick.
children?: CustomTrackOption[];
};
export type CustomTrackGroupOption = {
label?: string;
Expand Down Expand Up @@ -124,6 +127,12 @@ export type UserTrackSpec<D> = {
$track_info_tooltip_elt?: JQuery;
track_can_show_gaps?: boolean;
show_gaps_on_init?: boolean;
// Optional overrides for Move up / Move down: a callback fully replaces the
// default within-group move; the disabled flags gray out the item.
on_move_up?: () => void;
on_move_down?: () => void;
move_up_disabled?: boolean;
move_down_disabled?: boolean;
};
export type LibraryTrackSpec<D> = UserTrackSpec<D> & {
rule_set: RuleSet;
Expand Down Expand Up @@ -305,6 +314,10 @@ export default class OncoprintModel {
private track_remove_option_callback: TrackProp<
(track_id: TrackId) => void
>;
private track_on_move_up: TrackProp<(() => void) | undefined>;
private track_on_move_down: TrackProp<(() => void) | undefined>;
private track_move_up_disabled: TrackProp<boolean>;
private track_move_down_disabled: TrackProp<boolean>;
private track_sort_cmp_fn: TrackProp<TrackSortSpecification<Datum>>;
private track_sort_direction_changeable: TrackProp<boolean>;
private track_sort_direction: TrackProp<TrackSortDirection>;
Expand Down Expand Up @@ -416,6 +429,10 @@ export default class OncoprintModel {
this.track_removable = {};
this.track_remove_callback = {};
this.track_remove_option_callback = {};
this.track_on_move_up = {};
this.track_on_move_down = {};
this.track_move_up_disabled = {};
this.track_move_down_disabled = {};
this.track_sort_cmp_fn = {};
this.track_sort_direction_changeable = {};
this.track_sort_direction = {}; // 1: ascending, -1: descending, 0: not
Expand Down Expand Up @@ -1142,11 +1159,11 @@ export default class OncoprintModel {
) {
return self.track_rule_set_id[track_id];
});
const unique_rule_set_ids = arrayUnique(
rule_set_ids.map(x => x.toString())
);
// Dedupe numeric IDs directly to avoid the core-js parseInt polyfill's
// per-call trim(), which dominated chart-type-switch cost on large studies.
const unique_rule_set_ids = Array.from(new Set(rule_set_ids));
return unique_rule_set_ids.map(function(rule_set_id) {
return self.rule_sets[parseInt(rule_set_id, 10)];
return self.rule_sets[rule_set_id];
});
}

Expand Down Expand Up @@ -1375,6 +1392,10 @@ export default class OncoprintModel {
const params = params_list[i];
this.addTrack(params);
}
// Update track_tops synchronously before the (un-awaited) sort below, so
// views reading getZoomedTrackTops() for the new track don't get
// `undefined` and compute NaN heights — i.e. a zero-height canvas.
this.track_tops.update();
if (this.rendering_suppressed_depth === 0) {
if (this.keep_sorted) {
await this.sort();
Expand Down Expand Up @@ -1424,6 +1445,16 @@ export default class OncoprintModel {
this.track_remove_option_callback[
track_id
] = ifndef(params.onClickRemoveInTrackMenu, function() {});
this.track_on_move_up[track_id] = params.on_move_up;
this.track_on_move_down[track_id] = params.on_move_down;
this.track_move_up_disabled[track_id] = ifndef(
params.move_up_disabled,
false
);
this.track_move_down_disabled[track_id] = ifndef(
params.move_down_disabled,
false
);

if (typeof params.expandCallback !== 'undefined') {
this.track_expand_callback[track_id] = params.expandCallback;
Expand Down Expand Up @@ -1493,7 +1524,8 @@ export default class OncoprintModel {
const group_arrays = [this.track_groups[params.target_group].tracks];
if (
this.sort_config.type === 'cluster' &&
this.sort_config.track_group_index === params.target_group
this.sort_config.track_group_index === params.target_group &&
this.unclustered_track_group_order
) {
// if target group is clustered, also add track to unclustered order
group_arrays.push(this.unclustered_track_group_order);
Expand Down Expand Up @@ -1633,6 +1665,10 @@ export default class OncoprintModel {
delete this.track_movable[track_id];
delete this.track_removable[track_id];
delete this.track_remove_callback[track_id];
delete this.track_on_move_up[track_id];
delete this.track_on_move_down[track_id];
delete this.track_move_up_disabled[track_id];
delete this.track_move_down_disabled[track_id];
delete this.track_sort_cmp_fn[track_id];
delete this.track_sort_direction_changeable[track_id];
delete this.track_sort_direction[track_id];
Expand Down Expand Up @@ -1737,10 +1773,7 @@ export default class OncoprintModel {
// Binary search failed (tracks out of order) - linear fallback
for (let t = 0; t < tracks.length; t++) {
const top = cell_tops[tracks[t]];
if (
y >= top &&
y < top + this.getCellHeight(tracks[t])
) {
if (y >= top && y < top + this.getCellHeight(tracks[t])) {
nearest_track = tracks[t];
break;
}
Expand Down Expand Up @@ -2082,6 +2115,18 @@ export default class OncoprintModel {
public getTrackRemoveOptionCallback(track_id: TrackId) {
return this.track_remove_option_callback[track_id];
}
public getTrackOnMoveUp(track_id: TrackId) {
return this.track_on_move_up[track_id];
}
public getTrackOnMoveDown(track_id: TrackId) {
return this.track_on_move_down[track_id];
}
public isTrackMoveUpDisabled(track_id: TrackId) {
return this.track_move_up_disabled[track_id] === true;
}
public isTrackMoveDownDisabled(track_id: TrackId) {
return this.track_move_down_disabled[track_id] === true;
}

public isTrackSortDirectionChangeable(track_id: TrackId) {
return this.track_sort_direction_changeable[track_id];
Expand Down
101 changes: 80 additions & 21 deletions packages/oncoprintjs/src/js/oncoprintruleset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,10 @@ export interface IStackedBarRuleSetParams extends IGeneralRuleSetParams {
value_key: string;
categories: string[];
fills?: RGBAColor[];
// When set, bar heights scale against this constant instead of each datum's
// own total, so height reflects absolute magnitude rather than per-sample
// composition. Typically max(sum(datum values)) across all data.
max_total?: number;
}

export interface IGeneticAlterationRuleSetParams extends IGeneralRuleSetParams {
Expand Down Expand Up @@ -1169,6 +1173,7 @@ class StackedBarRuleSet extends ConditionRuleSet {
const value_key = params.value_key;
const fills = params.fills || [];
const categories = params.categories || [];
const max_total = params.max_total;
const getUnusedColor = makeUniqueColorGetter(fills.map(rgbaToHex));

// Initialize with default values
Expand Down Expand Up @@ -1196,41 +1201,95 @@ class StackedBarRuleSet extends ConditionRuleSet {
fill: fills[I],
width: 100,
height: function(d) {
var total = 0;
var denom;
if (max_total) {
denom = max_total;
return (
(+d[value_key][categories[I]] *
100) /
denom
);
}
// Composition mode: rows fill the full cell
// height. Snap the last rect to
// "100 - prev_sum_pct" so float drift in the
// fractions never leaves a gap at the bottom.
denom = 0;
for (
var j = 0;
j < categories.length;
j++
) {
total += parseFloat(
d[value_key][categories[j]]
);
denom += +d[value_key][categories[j]];
}
if (denom === 0) {
return 0;
}
if (I === categories.length - 1) {
var prev_pct = 0;
for (var k = 0; k < I; k++) {
prev_pct +=
(+d[value_key][categories[k]] *
100) /
denom;
}
return 100 - prev_pct;
}
return (
(parseFloat(
d[value_key][categories[I]]
) *
100) /
total
(+d[value_key][categories[I]] * 100) /
denom
);
},
y: function(d) {
var total = 0;
var denom;
var prev_vals_sum = 0;
for (
var j = 0;
j < categories.length;
j++
) {
var new_val = parseFloat(
d[value_key][categories[j]]
if (max_total) {
// Absolute mode: anchor bars to the
// bottom baseline. empty_pct is the
// whitespace above a short bar.
denom = max_total;
var total = 0;
for (
var j = 0;
j < categories.length;
j++
) {
total += +d[value_key][
categories[j]
];
}
var empty_pct =
((max_total - total) * 100) /
max_total;
for (var j = 0; j < I; j++) {
prev_vals_sum += +d[value_key][
categories[j]
];
}
return (
empty_pct +
(prev_vals_sum * 100) / denom
);
if (j < I) {
prev_vals_sum += new_val;
} else {
denom = 0;
for (
var j = 0;
j < categories.length;
j++
) {
var new_val = +d[value_key][
categories[j]
];
if (j < I) {
prev_vals_sum += new_val;
}
denom += new_val;
}
if (denom === 0) {
return 0;
}
total += new_val;
return (prev_vals_sum * 100) / denom;
}
return (prev_vals_sum * 100) / total;
},
},
],
Expand Down
Loading
Loading