From 7de16478fdb732bfdaa6882e1ff2ffede150d068 Mon Sep 17 00:00:00 2001 From: Kari Lavikka Date: Thu, 23 Jan 2025 13:26:06 +0200 Subject: [PATCH] chore: reorganize layout properties --- extract-default-properties.mjs | 6 +--- src/defaultProperties.ts | 13 ++++---- src/gui/gui.ts | 43 ++++++++++----------------- src/jellyfish.ts | 8 ++--- src/layout.ts | 54 +++++++++++++--------------------- 5 files changed, 44 insertions(+), 80 deletions(-) diff --git a/extract-default-properties.mjs b/extract-default-properties.mjs index f02f27f..cb4e9d2 100644 --- a/extract-default-properties.mjs +++ b/extract-default-properties.mjs @@ -4,7 +4,7 @@ import { writeFileSync } from "fs"; /** @type {import('ts-json-schema-generator/dist/src/Config').Config} */ const config = { tsconfig: "./tsconfig.json", - type: "InSchema", + type: "LayoutProperties", sortProps: false, }; @@ -34,10 +34,6 @@ import { export const DEFAULT_PROPERTIES = { ${generateProps(schema.definitions["LayoutProperties"].properties).join(",\n")} } as LayoutProperties; - -export const DEFAULT_COST_WEIGHTS = { -${generateProps(schema.definitions["CostWeights"].properties).join(",\n")} -} as CostWeights; `; return jsCode; } diff --git a/src/defaultProperties.ts b/src/defaultProperties.ts index 34b8b77..c940b05 100644 --- a/src/defaultProperties.ts +++ b/src/defaultProperties.ts @@ -7,6 +7,11 @@ import { } from "./layout.js"; export const DEFAULT_PROPERTIES = { + crossingWeight: 10, + pathLengthWeight: 2, + orderMismatchWeight: 2, + bundleMismatchWeight: 3, + divergenceWeight: 4, bellTipShape: 0.1, bellTipSpread: 0.5, bellStrokeWidth: 1, @@ -30,11 +35,3 @@ export const DEFAULT_PROPERTIES = { showRankTitles: true, normalsAtPhylogenyRoot: false } as LayoutProperties; - -export const DEFAULT_COST_WEIGHTS = { - crossing: 10, - pathLength: 2, - orderMismatch: 2, - bundleMismatch: 3, - divergence: 4 -} as CostWeights; diff --git a/src/gui/gui.ts b/src/gui/gui.ts index 3aa38c2..29684f1 100644 --- a/src/gui/gui.ts +++ b/src/gui/gui.ts @@ -1,14 +1,11 @@ import GUI, { Controller } from "lil-gui"; import { DataTables, filterDataTablesByPatient } from "../data.js"; import { tablesToJellyfish } from "../jellyfish.js"; -import { CostWeights, LayoutProperties, optimizeColumns } from "../layout.js"; +import { LayoutProperties } from "../layout.js"; import { addInteractions } from "../interactions.js"; import { downloadSvg, downloadPng, downloadPdf } from "./download.js"; import { escapeHtml } from "../utils.js"; -import { - DEFAULT_COST_WEIGHTS, - DEFAULT_PROPERTIES, -} from "../defaultProperties.js"; +import { DEFAULT_PROPERTIES } from "../defaultProperties.js"; type ControllerStatus = "open" | "closed" | "hidden"; @@ -28,7 +25,6 @@ export function setupGui( container: HTMLElement, tables: DataTables, customLayoutProps: Partial = {}, - customCostWeights: Partial = {}, controllerStatus: ControllerStatus = "open" ) { container.innerHTML = HTML_TEMPLATE; @@ -44,14 +40,12 @@ export function setupGui( const querySvg = () => jellyfishGui.querySelector(".jellyfish-plot svg") as SVGElement; - const { generalProps, layoutProps, costWeights } = - getSavedOrDefaultSettings(); + const { generalProps, layoutProps } = getSavedOrDefaultSettings(); Object.assign(layoutProps, customLayoutProps); - Object.assign(costWeights, customCostWeights); const saveSettings = () => - saveSettingsToSessionStorage(generalProps, layoutProps, costWeights); + saveSettingsToSessionStorage(generalProps, layoutProps); let translateX = 0; let translateY = 0; @@ -81,8 +75,7 @@ export function setupGui( patients.length > 1 ? filterDataTablesByPatient(tables, generalProps.patient) : tables, - layoutProps, - costWeights + layoutProps ); generalProps.zoom = 1; @@ -167,12 +160,12 @@ export function setupGui( miscFolder.onChange(onPatientChange); miscFolder.close(); - const weightsFolder = gui.addFolder("Cost weights"); - weightsFolder.add(costWeights, "crossing", 0, 10); - weightsFolder.add(costWeights, "pathLength", 0, 10); - weightsFolder.add(costWeights, "orderMismatch", 0, 10); - weightsFolder.add(costWeights, "divergence", 0, 10); - weightsFolder.add(costWeights, "bundleMismatch", 0, 10); + const weightsFolder = optionFolder.addFolder("Cost weights"); + weightsFolder.add(layoutProps, "crossingWeight", 0, 10); + weightsFolder.add(layoutProps, "pathLengthWeight", 0, 10); + weightsFolder.add(layoutProps, "orderMismatchWeight", 0, 10); + weightsFolder.add(layoutProps, "divergenceWeight", 0, 10); + weightsFolder.add(layoutProps, "bundleMismatchWeight", 0, 10); weightsFolder.onChange(onPatientChange); weightsFolder.close(); @@ -283,13 +276,12 @@ export function setupGui( function updatePlot( jellyfishGui: HTMLElement, tables: DataTables, - layoutProps: LayoutProperties, - costWeights: CostWeights + layoutProps: LayoutProperties ) { const plot = jellyfishGui.querySelector(".jellyfish-plot") as HTMLElement; try { - const svg = tablesToJellyfish(tables, layoutProps, costWeights); + const svg = tablesToJellyfish(tables, layoutProps); plot.innerHTML = ""; // Purge the old plot svg.addTo(plot); @@ -312,12 +304,11 @@ const STORAGE_KEY = "jellyfish-plotter-settings"; function saveSettingsToSessionStorage( generalProps: GeneralProperties, - layoutProps: LayoutProperties, - costWeights: CostWeights + layoutProps: LayoutProperties ) { sessionStorage.setItem( STORAGE_KEY, - JSON.stringify({ generalProps, layoutProps, costWeights }) + JSON.stringify({ generalProps, layoutProps }) ); } @@ -334,10 +325,6 @@ function getSavedOrDefaultSettings() { ...DEFAULT_PROPERTIES, ...(settings.layoutProps ?? {}), } as LayoutProperties, - costWeights: { - ...DEFAULT_COST_WEIGHTS, - ...(settings.costWeights ?? {}), - } as CostWeights, }; } diff --git a/src/jellyfish.ts b/src/jellyfish.ts index 69b1000..bf85d61 100644 --- a/src/jellyfish.ts +++ b/src/jellyfish.ts @@ -18,7 +18,6 @@ import { DataTables, SampleId, Subclone, validateTables } from "./data.js"; import { treeIterator, treeToNodeArray } from "./tree.js"; import * as d3 from "d3"; import { - CostWeights, findLegendPlacement, getNodePlacement, LayoutProperties, @@ -41,15 +40,13 @@ import { SubcloneMetricsMap, } from "./composition.js"; import { createDistanceMatrix, jsDivergence } from "./statistics.js"; -import { DEFAULT_COST_WEIGHTS } from "./defaultProperties.js"; /** * This is the main function that glues everything together. */ export function tablesToJellyfish( tables: DataTables, - layoutProps: LayoutProperties, - constWeights: CostWeights = DEFAULT_COST_WEIGHTS + layoutProps: LayoutProperties ) { validateTables(tables); @@ -181,8 +178,7 @@ export function tablesToJellyfish( nodesInColumns, layoutProps, preferredOrders, - sampleDistanceMatrix, - constWeights + sampleDistanceMatrix ); /** diff --git a/src/layout.ts b/src/layout.ts index d7a2676..ec32b53 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -1,25 +1,16 @@ import { BellPlotProperties } from "./bellplot.js"; -import { DEFAULT_COST_WEIGHTS } from "./defaultProperties.js"; import { getBoundingBox, isIntersecting, Rect } from "./geometry.js"; import { NODE_TYPES, SampleTreeNode } from "./sampleTree.js"; import { treeToNodeArray } from "./tree.js"; import { fisherYatesShuffle, SeededRNG } from "./utils.js"; -/** - * This is just an entry point for ts-json-schema-generator. - */ -export interface InSchema { - layoutProps: LayoutProperties; - costWeights: CostWeights; -} - export interface NodePosition { node: SampleTreeNode; top: number; height: number; } -export interface LayoutProperties extends BellPlotProperties { +export interface LayoutProperties extends BellPlotProperties, CostWeights { /** * Height of real sample nodes * @@ -167,20 +158,20 @@ export interface LayoutProperties extends BellPlotProperties { export interface CostWeights { /** - * Weight for tentacle bundles between two pairs of samples crossing each other + * Weight for tentacle bundles between two pairs of samples crossing each other. * * @minimum 0 * @default 10 */ - crossing: number; + crossingWeight: number; /** - * Weight for the total length of the paths (tentacle bundles) connecting samples + * Weight for the total length of the paths (tentacle bundles) connecting samples. * * @minimum 0 * @default 2 */ - pathLength: number; + pathLengthWeight: number; /** * Weight for the mismatch in the order of samples. The order is based on the @@ -189,7 +180,7 @@ export interface CostWeights { * @minimum 0 * @default 2 */ - orderMismatch: number; + orderMismatchWeight: number; /** * Weight for the mismatch in the placement of bundles. The "optimal" placement is @@ -199,15 +190,15 @@ export interface CostWeights { * @minimum 0 * @default 3 */ - bundleMismatch: number; + bundleMismatchWeight: number; /** - * Weight for the sum of divergences between adjacent samples + * Weight for the sum of divergences between adjacent samples. * * @minimum 0 * @default 4 */ - divergence: number; + divergenceWeight: number; } export function sampleTreeToColumns(sampleTree: SampleTreeNode) { @@ -266,8 +257,7 @@ function calculateCost( stackedColumns: NodePosition[][], layoutProps: LayoutProperties, preferredOrders: number[], - sampleDistanceMatrix: number[][] | null, - costWeights: CostWeights + sampleDistanceMatrix: number[][] | null ) { const columnPaths: number[][][] = []; @@ -380,18 +370,18 @@ function calculateCost( } const totalCrossings = - costWeights.crossing > 0 + layoutProps.crossingWeight > 0 ? columnPaths.reduce((acc, paths) => acc + getNumberOfCrossings(paths), 0) : 0; const totalPathLength = - costWeights.pathLength > 0 + layoutProps.pathLengthWeight > 0 ? columnPaths.reduce((acc, paths) => acc + getTotalPathLength(paths), 0) / (layoutProps.sampleHeight + layoutProps.sampleSpacing) : 0; const totalOrderMismatch = - costWeights.orderMismatch > 0 + layoutProps.orderMismatchWeight > 0 ? stackedColumns.reduce( (acc, column) => acc + getOrderMismatch(column, false), 0 @@ -399,7 +389,7 @@ function calculateCost( : 0; const totalDivergenceMismatch = - costWeights.divergence > 0 + layoutProps.divergenceWeight > 0 ? stackedColumns.reduce( (acc, column) => acc + getDivergenceMismatch(column), 0 @@ -407,7 +397,7 @@ function calculateCost( : 0; const totalBundleMismatch = - costWeights.bundleMismatch > 0 + layoutProps.bundleMismatchWeight > 0 ? stackedColumns.reduce( (acc, column) => acc + getOrderMismatch(column, true), 0 @@ -415,11 +405,11 @@ function calculateCost( : 0; return ( - totalCrossings * costWeights.crossing + - totalPathLength * costWeights.pathLength + - totalOrderMismatch * costWeights.orderMismatch + - totalDivergenceMismatch * costWeights.divergence + - totalBundleMismatch * costWeights.bundleMismatch + totalCrossings * layoutProps.crossingWeight + + totalPathLength * layoutProps.pathLengthWeight + + totalOrderMismatch * layoutProps.orderMismatchWeight + + totalDivergenceMismatch * layoutProps.divergenceWeight + + totalBundleMismatch * layoutProps.bundleMismatchWeight ); } @@ -475,7 +465,6 @@ export function optimizeColumns( layoutProps: LayoutProperties, preferredOrders: number[], sampleDistanceMatrix: number[][] | null, - costWeights: CostWeights = DEFAULT_COST_WEIGHTS, random: () => number = SeededRNG(0), randomizationRounds: number = 10000 ) { @@ -494,8 +483,7 @@ export function optimizeColumns( stackedColumns, layoutProps, preferredOrders, - sampleDistanceMatrix, - costWeights + sampleDistanceMatrix ); if (cost < bestCost) { bestResult = stackedColumns;