reagent

Schmoho 2025-02-26T10:49:37.831689Z

I mostly gave up on using the AlignmentViewer JS package I was intending to use and I am now investigating what it would take to just write a simple one myself. There is a performance problem with this though, which as far as I can tell is due to a bunch of elements being loaded into the DOM, although they are not visible at a given time. Is there a straightforward way to control this? I.e. put the whole thing in a scrollable container and only load into the dom what is visible? For some context, MSAs are things like these: https://en.wikipedia.org/wiki/Multiple_sequence_alignment#/media/File:RPLP0_90_ClustalW_aln.gif Basically, you have a couple hundred to tens of thousand strings of (usually, but not necessarily equal) length up to ~10k and you want to put them all underneath each other, color each character and calculate summary statistics per column. In my case, it will always only be up to a few thousand rows though. The super naive thing I am doing now is this:

(defn msa-viewer
  [alignment]
  (when (seq alignment)
    (let [seq-length (-> alignment
                         first
                         :clustalo/aligned-sequence
                         count)]
      [re-com/scroller
       :size "100px"
       :child
       [re-com/v-box
        :gap "5px"
        :children
        (cons
         [:div
          {:style {:white-space "nowrap"}}
          (into
           [:span {:style {:display    "inline-block"}} ""]
           (for [i (range 1 (inc seq-length))]
             ^{:key i}
             [:span {:style {:display       "inline-block"
                             :width         "20px"
                             :text-align    "center"
                             :font-family   "monospace"
                             :font-weight   "bold"}}
              i]))]
         (for [{:keys [fasta/header clustalo/aligned-sequence]} alignment]
           ^{:key header}
           [:div
            {:style {:white-space "nowrap"}}
            (into
             [:span {:style {:display       "inline-block"
                             :font-weight   "bold"
                             :font-family   "sans-serif"}}
                header]
             (for [[idx c] (map-indexed vector aligned-sequence)]
               ^{:key idx}
               [:span {:style {:display       "inline-block"
                               :width         "20px"
                               :text-align    "center"
                               :font-family   "monospace"}}
                c]))]))]])))

p-himik 2025-02-26T11:02:25.397719Z

Either so-called "virtual lists" (e.g. https://github.com/bvaughn/react-window) or progressive rendering (render 100 or so rows at each frame with js/requestAnimationFrame).

p-himik 2025-02-26T11:04:53.405519Z

Did webpack not work for you?

p-himik 2025-02-26T11:06:20.071559Z

Oh, another alternative, which might or might not be performant enough, is to create a vanilla JS component that renders the whole thing and wrap it in a React/Reagent component. So you don't end up creating thousands of instances.

Schmoho 2025-02-26T11:09:38.639059Z

I started reading into the webpack thing and then got sidetracked a bit: I realized their distro comes with precompiled "standalone" files, so I required these instead to see if that does it, but somebody just pointed out to me that this might not work for other reasons, possibly bundling react already or something.

Schmoho 2025-02-26T11:13:43.256639Z

I am basically trying to assess my options at this point. I.e. should I spend a couple of hours trying it with webpack with the real possibility of this not working out, especially given that their (and frankly most) implementations of an AlignmentViewer does not really address many things that would actually be kinda relevant for the my specific use case. Or could I just implement the damn thing myself in a reasonable time frame.

p-himik 2025-02-26T11:17:27.115789Z

It depends on what kind of functionality you need. It seems that that alignment viewer library that you shared earlier does have some degree of interactivity, and it might perform well even on huge input data. Just make sure you don't underestimate the amount of work some particular thing might require.

👍 1
Schmoho 2025-02-26T11:18:47.110699Z

Really hard to do for me in JS land 😄

p-himik 2025-02-26T11:21:41.260659Z

Oh, have you tried their older library? It seems to be JS only, without any fluff. https://github.com/sanderlab/alignmentviewer

p-himik 2025-02-26T11:23:02.331299Z

Hmm, maybe it's not a library but a full on app. But should be possible to extract and use the relevant code from it.

Schmoho 2025-02-26T11:30:31.435399Z

They claim it is embeddable, but I haven't seen any actual examples where somebody does it yet

Schmoho 2025-02-26T11:30:50.673739Z

Ah sorry, you were talking about the old one

Schmoho 2025-02-26T11:38:31.506589Z

Why is this crazy slow?

(defn msa-viewer-scrollable
  [alignment row-height container-height]
  (let [scroll-offset (r/atom 0)
        total-rows    (count alignment)
        visible-count (.ceil js/Math (/ container-height row-height))
        buffer        5]
    (fn [alignment row-height container-height]
      (let [first-row            (max 0 (- (int (/ @scroll-offset row-height)) buffer))
            first-row            (if (< first-row 0) 0 first-row)
            last-row             (min total-rows (+ first-row visible-count (* 2 buffer)))
            top-spacer-height    (* first-row row-height)
            bottom-spacer-height (* (- total-rows last-row) row-height)]
        [:div {:style     {:height      (str container-height "px")
                           :overflow-y  "auto"
                           :white-space "nowrap"
                           :border      "solid black 1px"}
               :on-scroll (fn [e]
                            (reset! scroll-offset (.-scrollTop (.-target e))))}
         [:div
          [:div {:style {:height (str top-spacer-height "px")}}]
          (for [row (subvec alignment first-row last-row)]
            ^{:key (:name row)}
            [:div
             (into
              [:span {:style {:display     "inline-block"
                              :width       "100px"
                              :font-weight "bold"}}
              (:name row)]
              (for [[idx c] (map-indexed vector (:sequence row))]
                ^{:key idx}
                [:span {:style {:display     "inline-block"
                                :width       "20px"
                                :text-align  "center"
                                :font-family "monospace"}}
                 c]))])
          [:div {:style {:height (str bottom-spacer-height "px")}}]]]))))

(def sample-alignment
  (vec
   (map (fn [i]
          {:name (str "Seq" i) :sequence (apply concat (repeat 100 "ACGT-ACGT"))})
        (range 100))))

p-himik 2025-02-26T11:44:07.350339Z

Impossible to tell without a profiler result, but probably because you create an element per character.

👍 1
p-himik 2025-02-26T11:54:08.234449Z

Also you shouldn't use ^{:key idx}. When you have nothing but the index for a key, it's better to avoid lazy seqs altogether.

👍 1
Schmoho 2025-02-26T12:11:41.345929Z

Mh, so I looked into the Webpack thing some more, and if I understand this correctly: this

:js-options {:js-provider    :external
                              :external-index "frontend/target/index.js"}
and this: npx webpack --entry ./frontend/target/index.js --output-path ./frontend/resources/public/js --output-filename libs.js bundle the external JS and then I load this in the HTML
<script src="/js/libs.js"></script>
    <script src="/js/compiled/app.js"></script>	
But then I get:
Error: Dependency: alignment-viewer-2 not provided by external JS. Do you maybe need a recompile?
    shadow$bridge libs.js:2
    <anonymous> shadow.js.shim.module$alignment_viewer_2.js:3
    globalEval app.js:434
    evalLoad app.js:1405
    <anonymous> app.js:2014
and
TypeError: shadow.js.shim.module$alignment_viewer_2.FastaAlignment is undefined
    unknown_client$core$mount_root core.cljs:86
    unknown_client$core$init core.cljs:93
    <anonymous> shadow.module.app.append.js:4
    globalEval app.js:434
    evalLoad app.js:1405
    <anonymous> app.js:2016
It seems both lib.js and app.js have been loaded in the HTML though

Schmoho 2025-02-26T12:12:57.664509Z

Maybe I'll better take this over to shadow-cljs