{"id":20386984,"url":"https://github.com/cmdcolin/track_layout_benchmark","last_synced_at":"2026-03-01T21:38:17.446Z","repository":{"id":137213424,"uuid":"553045361","full_name":"cmdcolin/track_layout_benchmark","owner":"cmdcolin","description":"Benchmarking different layout algorithms used for genomic feature tracks","archived":false,"fork":false,"pushed_at":"2025-11-12T06:52:35.000Z","size":11027,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-12T08:24:31.266Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cmdcolin.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2022-10-17T16:18:33.000Z","updated_at":"2025-11-12T06:52:39.000Z","dependencies_parsed_at":null,"dependency_job_id":"d298d8d3-e54e-4819-a6ab-292233d128e5","html_url":"https://github.com/cmdcolin/track_layout_benchmark","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cmdcolin/track_layout_benchmark","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdcolin%2Ftrack_layout_benchmark","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdcolin%2Ftrack_layout_benchmark/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdcolin%2Ftrack_layout_benchmark/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdcolin%2Ftrack_layout_benchmark/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cmdcolin","download_url":"https://codeload.github.com/cmdcolin/track_layout_benchmark/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cmdcolin%2Ftrack_layout_benchmark/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29984725,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-03-01T21:06:37.093Z","status":"ssl_error","status_checked_at":"2026-03-01T21:05:45.052Z","response_time":124,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-15T02:41:59.214Z","updated_at":"2026-03-01T21:38:17.440Z","avatar_url":"https://github.com/cmdcolin.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## Background\n\nGenome browsers commonly stack features that occupy genomic ranges on top of\neach other sort of like bricks. There are likely other possible applications.\nThese stackings don't need to be the NP-hard bin-packed optimal layout, just\ngood enough. It is a little tricky to get the right algorithm to do this\nhowever.\n\nIn this repo, I have surveyed a couple of techniques, and put them in a little\nbenchmark\n\n## Techniques\n\nSome of these are implemented for experimental purposes\n\n### End-array layout\n\nAn \"end-array\" is an idea implemented in gw genome browser\nhttps://www.biorxiv.org/content/10.1101/2024.07.26.605272v3\n\nIt uses a single array, with each element of the array representing a row, and\nthe value of each element of the array keeps track of the current maximum 'end\ncoordinate' of a feature on that row.\n\nTo add a new rectangle to the end-array layout, it iterates over the end-array\narray for the first place where the START position of a genomic feature is\ngreater than a position in the end-array. The first place where the start\nposition is greater than an element of the end-array means the feature can be\nsafely 'laid out' there, and the value of that element of the end-array is\nupdated to the END position of that genomic feature\n\nThis layout method works best when incoming features are sorted by their start\nposition, otherwise the layout will not have good density\n\n### Priority queue layout\n\nThis is very similar to the end-array layout, but instead of scanning the\nend-array linearly, it has a priority queue that keeps track of the first\navailable position\n\nThis is flatqueue in benchmark and is used by\n[GenomeSpy](https://genomespy.app/), developed by Kari Lavikka\n\n### Granular rect layout (w/ \"bitmap\" layout)\n\nThe \"Granular rect layout\" is a system used in JBrowse 1.\n\nIt works with each row being an array of binary true/false, where true\nrepresents the occupied space\n\nIt uses a scaling factor when you are \"zoomed out\" so that it doesn't represent\n1 array element per 1bp but rather e.g. 1 array element per 100bp when you are\nzoomed out (in the code, it calls this scaling factor the \"pitchX\")\n\nTo add a new rectangle to the layout, it looks at each row, finds if it is\noccupied anywhere in the region you want to place the rect, and if it is\noccupied there, it checks the next row, and so on\n\nThis layout method does not require any particular sorting, though sorting may\nincrease the density of the resulting layout\n\n### Granular rect layout (w/ interval array)\n\nIn 2025, I had Claude Code make some optimizations to make the Granular rect\nlayout more interval-tree-like, but instead is now an \"interval array\". So\ninstead of a bitmap, it just stores an array of intervals in each row. It uses\nsplice to insert intervals into sorted order. This has a slightly higher upfront\ncost, but it is faster to query than the interval tree\n\nThis is now used in JBrowse 2 (gran_ultra) in benchmark\n\n### Interval tree\n\nInterval tree is an interesting data structure that lets you query intervals,\nand we can imagine each row with its own interval tree to query the occupancy of\na given genomic range. It is O(log(n)) for \"queries\".\n\nThis implementation I used is similar to the granular rect layout but instead of\nan array-per-row, it is a interval-tree-per-row\n\nThis layout method does not require any particular sorting, though sorting may\nincrease the density of the resulting layout\n\n## Rendered images\n\nI created rendered images of the resulting layouts in img folder, example\n\n![](img/endarr.png)\n\n## Benchmark output\n\n#### Wide layout\n\n```\n\n==========================================\nBenchmarking with 100,000 rectangles\n==========================================\nBenchmark 1: node dist/bench/arrsimple.js 100000\n  Time (mean ± σ):     33.752 s ±  3.884 s    [User: 33.727 s, System: 0.190 s]\n  Range (min … max):   30.083 s … 38.990 s    4 runs\n\nBenchmark 2: node dist/bench/endarr.js 100000\n  Time (mean ± σ):      4.379 s ±  0.284 s    [User: 4.230 s, System: 0.249 s]\n  Range (min … max):    4.149 s …  4.767 s    4 runs\n\nBenchmark 3: node dist/bench/flatqueue.js 100000\n  Time (mean ± σ):      3.820 s ±  0.187 s    [User: 3.700 s, System: 0.226 s]\n  Range (min … max):    3.692 s …  4.098 s    4 runs\n\nBenchmark 4: node dist/bench/gran.js 100000\n  Time (mean ± σ):     12.990 s ±  0.833 s    [User: 14.691 s, System: 1.026 s]\n  Range (min … max):   11.894 s … 13.849 s    4 runs\n\nBenchmark 5: node dist/bench/gran_ultra.js 100000\n  Time (mean ± σ):      8.856 s ±  0.835 s    [User: 8.829 s, System: 0.186 s]\n  Range (min … max):    7.954 s …  9.975 s    4 runs\n\nBenchmark 6: node dist/bench/iv1.js 100000\n  Time (mean ± σ):     49.071 s ±  2.475 s    [User: 49.697 s, System: 0.317 s]\n  Range (min … max):   46.568 s … 51.414 s    4 runs\n\nSummary\n  node dist/bench/flatqueue.js 100000 ran\n    1.15 ± 0.09 times faster than node dist/bench/endarr.js 100000\n    2.32 ± 0.25 times faster than node dist/bench/gran_ultra.js 100000\n    3.40 ± 0.27 times faster than node dist/bench/gran.js 100000\n    8.83 ± 1.10 times faster than node dist/bench/arrsimple.js 100000\n   12.84 ± 0.90 times faster than node dist/bench/iv1.js 100000\nDone in 454.34s.\n```\n\n#### Tall layout\n\n```\n\n\n\n\n==========================================\nBenchmarking TALL screen (5000x20000) with 50,000 rectangles\nTesting: arrsimple_tall endarr_tall flatqueue_tall gran_tall gran_ultra_tall iv1_tall\n==========================================\nBenchmark 1: node src/bench/arrsimple_tall.ts\n  Time (mean ± σ):      6.682 s ±  0.813 s    [User: 6.657 s, System: 0.177 s]\n  Range (min … max):    6.113 s …  7.889 s    4 runs\n\nBenchmark 2: node src/bench/endarr_tall.ts\n  Time (mean ± σ):      4.516 s ±  0.474 s    [User: 4.399 s, System: 0.234 s]\n  Range (min … max):    4.014 s …  5.060 s    4 runs\n\nBenchmark 3: node src/bench/flatqueue_tall.ts\n  Time (mean ± σ):      3.755 s ±  0.106 s    [User: 3.613 s, System: 0.269 s]\n  Range (min … max):    3.673 s …  3.898 s    4 runs\n\nBenchmark 4: node src/bench/gran_tall.ts\n  Time (mean ± σ):     10.399 s ±  1.161 s    [User: 11.889 s, System: 0.523 s]\n  Range (min … max):    9.065 s … 11.896 s    4 runs\n\nBenchmark 5: node src/bench/gran_ultra_tall.ts\n  Time (mean ± σ):      7.326 s ±  0.366 s    [User: 7.310 s, System: 0.169 s]\n  Range (min … max):    7.028 s …  7.799 s    4 runs\n\nBenchmark 6: node src/bench/iv1_tall.ts\n  Time (mean ± σ):     17.797 s ±  3.601 s    [User: 17.958 s, System: 0.234 s]\n  Range (min … max):   13.809 s … 21.969 s    4 runs\n\nSummary\n  node src/bench/flatqueue_tall.ts ran\n    1.20 ± 0.13 times faster than node src/bench/endarr_tall.ts\n    1.78 ± 0.22 times faster than node src/bench/arrsimple_tall.ts\n    1.95 ± 0.11 times faster than node src/bench/gran_ultra_tall.ts\n    2.77 ± 0.32 times faster than node src/bench/gran_tall.ts\n    4.74 ± 0.97 times faster than node src/bench/iv1_tall.ts\n```\n\n## Next steps\n\n- Experiment with freeing layout memory when no longer used. How this is done\n  may vary based on the approach\n- See if there are other references to algorithms like this from outside the\n  bioinformatics-sphere (masonry layout in CSS?)\n\n## Note\n\nFeel free to provide any optimizations or PRs to this benchmark!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdcolin%2Ftrack_layout_benchmark","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcmdcolin%2Ftrack_layout_benchmark","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcmdcolin%2Ftrack_layout_benchmark/lists"}