Skip to content

Commit

Permalink
[nested-v-grid] prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
kimo-k committed Dec 8, 2024
1 parent cba6e65 commit 1c21ca9
Show file tree
Hide file tree
Showing 6 changed files with 294 additions and 186 deletions.
138 changes: 138 additions & 0 deletions src/re_com/nested_grid.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
[clojure.string :as str]
[re-com.util :as u :refer [px deref-or-value]]
[re-com.nested-grid.util :as ngu]
[re-com.nested-grid.parts :as ngp]
[reagent.core :as r]
[re-com.debug :as debug]
[re-com.config :as config :refer [include-args-desc?]]
Expand Down Expand Up @@ -1184,3 +1185,140 @@
row-headers
cells)
overlays]))))

(defn nested-v-grid [{:keys [row-tree column-tree
row-tree-depth column-tree-depth
column-header-heights
row-header-widths
theme
row-header-width column-header-height]
:or {row-header-width 40 column-header-height 40}}]
(let [[wx wy wh ww !wrapper-ref scroll-listener resize-observer]
(repeatedly #(r/atom nil))
wrapper-ref! (partial reset! !wrapper-ref)
on-scroll! #(do (reset! wx (.-scrollLeft (.-target %)))
(reset! wy (.-scrollTop (.-target %))))
on-resize! #(do (reset! wh (.-height (.-contentRect (aget % 0))))
(reset! ww (.-width (.-contentRect (aget % 0)))))
internal-row-tree (r/atom (u/deref-or-value row-tree))
internal-column-tree (r/atom (u/deref-or-value column-tree))
size-cache (volatile! {})
row-traversal (r/reaction (ngu/walk-size {:tree @internal-row-tree
:window-start (or @wy 0)
:window-end (+ @wy @wh)
:size-cache size-cache
:dimension :row
:tree-depth (u/deref-or-value
row-tree-depth)}))
column-traversal (r/reaction (ngu/walk-size {:tree @internal-column-tree
:window-start (or @wx 0)
:window-end (+ @wx @ww)
:size-cache size-cache
:dimension :column
:tree-depth (u/deref-or-value
column-tree-depth)}))
column-depth (r/reaction (:depth @column-traversal))
column-header-heights (r/reaction (or (u/deref-or-value column-header-heights)
(repeat @column-depth column-header-height)))
column-header-height-total (r/reaction (apply + @column-header-heights))
column-width-total (r/reaction (:sum-size @column-traversal))
column-windowed-paths (r/reaction (:windowed-paths @column-traversal))
column-windowed-leaf-paths (r/reaction (:windowed-leaf-paths @column-traversal))
column-tokens (r/reaction (ngu/lazy-grid-tokens @column-traversal))
column-template (r/reaction (ngu/lazy-grid-template @column-tokens))
column-header-template (r/reaction (ngu/grid-template @column-header-heights))
column-spans (r/reaction (ngu/grid-spans @column-tokens))
row-depth (r/reaction (:depth @row-traversal))
row-header-widths (r/reaction (or (u/deref-or-value row-header-widths)
(repeat @row-depth row-header-width)))
row-header-width-total (r/reaction (apply + @row-header-widths))
row-height-total (r/reaction (:sum-size @row-traversal))
row-windowed-paths (r/reaction (:windowed-paths @row-traversal))
row-windowed-leaf-paths (r/reaction (:windowed-leaf-paths @row-traversal))
row-tokens (r/reaction (ngu/lazy-grid-tokens @row-traversal))
row-template (r/reaction (ngu/lazy-grid-template @row-tokens))
row-header-template (r/reaction (ngu/grid-template @row-header-widths))
row-spans (r/reaction (ngu/grid-spans @row-tokens))]
(r/create-class
{:component-did-mount
#(do (reset! scroll-listener
(.addEventListener @!wrapper-ref "scroll" on-scroll!))
(reset! resize-observer
(.observe (js/ResizeObserver. on-resize!) @!wrapper-ref)))
:component-did-update
#(let [[_ {:keys [row-tree column-tree]}] (r/argv %)]
(reset! internal-row-tree (u/deref-or-value row-tree))
(reset! internal-column-tree (u/deref-or-value column-tree)))
:reagent-render
(fn [{:keys [row-tree column-tree
cell
row-header-cell column-header-cell]
:as props}]
(u/deref-or-value row-tree)
(u/deref-or-value row-header-widths)
(u/deref-or-value column-header-heights)
(u/deref-or-value column-tree)
(let [theme (theme/defaults
props
{:user [(theme/<-props props {:part ::wrapper
:include [:style :class]})]})
themed (fn [part props] (theme/apply props {:part part} theme))
spacer-container [:div {:style {:position :sticky
:grid-row-start 1
:grid-column-start 1
:left 0
:top 0
:background-color :white}}]
row-header-container [:div {:style {:display :grid
:grid-template-rows @row-template
:grid-template-columns @row-header-template
:position :sticky
:left 0}}]
row-header-cells (for [row-path @row-windowed-paths
:let [props (themed ::row-header-wrapper
{:style {:grid-row-start (ngu/path->grid-line-name row-path)
:grid-row-end (str "span " (get @row-spans row-path))
:grid-column-start (count row-path)
:grid-column-end (str "span " (inc (- @row-depth (count row-path))))
:font-size 8}
:edge #{:start}})]]
(u/part row-header-cell props {:key row-path
:default ngp/row-header-wrapper}))
column-header-container [:div {:style {:display :grid
:grid-template-rows @column-header-template
:grid-template-columns @column-template
:position :sticky
:top 0}}]
column-header-cells (for [column-path @column-windowed-paths
:let [props (themed ::column-header-wrapper
{:style {:grid-column-start (ngu/path->grid-line-name column-path)
:grid-column-end (str "span " (get @column-spans column-path))
:grid-row-start (count column-path)
:grid-row-end (str "span " (inc (- @column-depth (count column-path))))
:overflow :hidden
:font-size 8}
:children [(pr-str column-path)]})]]
(u/part column-header-cell props {:key column-path}))
main-container [:div
(themed ::wrapper
{:style {:grid-template-rows (ngu/grid-template [@column-header-height-total @row-height-total])
:grid-template-columns (ngu/grid-template [@row-header-width-total @column-width-total])}
:ref wrapper-ref!})]
cell-grid-container [:div (themed ::cell-grid
{:style {:display :grid
:grid-column-start 2
:grid-row-start 2
:grid-template-rows @row-template
:grid-template-columns @column-template}})]
cells (for [row-path @row-windowed-leaf-paths
column-path @column-windowed-leaf-paths
:let [props {:row-path row-path
:column-path column-path}]]
(u/part cell props {:key [row-path column-path]
:default ngp/cell}))]
(conj main-container
(into cell-grid-container cells)
(into column-header-container column-header-cells)
(into row-header-container row-header-cells)
spacer-container)))})))

14 changes: 14 additions & 0 deletions src/re_com/nested_grid/parts.cljs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
(ns re-com.nested-grid.parts
(:require [re-com.nested-grid.util :as ngu]
[re-com.nested-grid :as-alias ng]))

(defn cell [{:keys [row-path column-path]}]
[:div {:style {:border "thin solid grey"
:grid-row-start (ngu/path->grid-line-name row-path)
:grid-column-start (ngu/path->grid-line-name column-path)
:font-size 6}}
(str (gensym))])

(defn row-header-wrapper [{:keys [style]}]
[:div {:style (merge style {:font-size 6})}
(gensym)])
110 changes: 68 additions & 42 deletions src/re_com/nested_grid/util.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -89,9 +89,15 @@
(map? node) (:size node)
(keyword? node) 20)))

(defn walk-size [{:keys [window-start window-end tree size-cache]}]
(defn walk-size [{:keys [window-start window-end tree size-cache dimension tree-depth]}]
(let [sum-size (volatile! 0)
depth (volatile! 0)
tree-hash (hash tree)
cached-depth (and size-cache
(get-in @size-cache [dimension tree-hash ::depth]))
cached-sum-size (and size-cache
(get-in @size-cache [dimension tree-hash ::sum-size]))
tail-cached? (and cached-depth cached-sum-size)
windowed-paths (volatile! [])
windowed-leaf-paths (volatile! [])
windowed-sizes (volatile! [])
Expand All @@ -117,48 +123,53 @@
(>= x2 window-start))))
walk
(fn walk [path node & {:keys [collect-me?] :or {collect-me? true}}]
(cond
(leaf? node) (let [sum @sum-size
leaf-size (leaf-size node)
leaf-path (conj path node)
level (count leaf-path)
bounds [sum (+ sum leaf-size)]]
(when (intersection? bounds)
(collect-leaf-path! leaf-path)
(when collect-me?
(collect-path! leaf-path)
(collect-sum! sum)
(collect-size! leaf-size)))
(vreset! sum-size (+ sum leaf-size))
leaf-size)
(branch? node) (let [sum @sum-size
csize (lookup node)
cbounds (when csize [sum (+ sum csize)])
skippable? (and csize (not (intersection? cbounds)))]
(if skippable?
(let [new-sum (+ sum csize)]
(vreset! sum-size new-sum)
csize)
(let [own-path (conj path (own-leaf node))
level (count own-path)
own-size (walk path (own-leaf node) {:collect-me? false})
_ (collect-path! own-path)
_ (collect-size! own-size)
_ (collect-sum! sum)
_ (collect-depth! (count own-path))
descend-tx (map (partial walk own-path))
total-size (+ own-size
(transduce descend-tx + (children node)))
total-bounds [sum (+ sum total-size)]]
(when-not (intersection? total-bounds)
(forget-path!)
(forget-sum!)
(forget-size!))
(when-not csize (cache! node total-size))
total-size)))))]
(let [sum @sum-size
passed-tail? (and tail-cached? (> sum window-end))]
(cond
passed-tail? :exit
(leaf? node) (let [leaf-size (leaf-size node)
leaf-path (conj path node)
bounds [sum (+ sum leaf-size)]]
(when (intersection? bounds)
(collect-leaf-path! leaf-path)
(when collect-me?
(collect-path! leaf-path)
(collect-sum! sum)
(collect-size! leaf-size)))
(vreset! sum-size (+ sum leaf-size))
leaf-size)
(branch? node) (let [csize (lookup node)
cbounds (when csize [sum (+ sum csize)])
skippable? (and csize (not (intersection? cbounds)))]
(if skippable?
(let [new-sum (+ sum csize)]
(vreset! sum-size new-sum)
csize)
(let [own-path (conj path (own-leaf node))
own-size (walk path (own-leaf node) {:collect-me? false})
_ (collect-path! own-path)
_ (collect-size! own-size)
_ (collect-sum! sum)
_ (when-not (or tree-depth
cached-depth)
(collect-depth! (count own-path)))
descend-tx (map (partial walk own-path))
total-size (+ own-size
(transduce descend-tx + (children node)))
total-bounds [sum (+ sum total-size)]]
(when-not (intersection? total-bounds)
(forget-path!)
(forget-sum!)
(forget-size!))
(when-not csize (cache! node total-size))
total-size))))))]
(walk [] tree)
{:sum-size @sum-size
:depth (inc @depth)
(when-not cached-depth
(vswap! size-cache assoc-in [dimension tree-hash ::depth] @depth))
(when-not cached-sum-size
(vswap! size-cache assoc-in [dimension tree-hash ::sum-size] @sum-size))
{:sum-size (or cached-sum-size @sum-size)
:depth (or tree-depth (inc cached-depth) (inc @depth))
:windowed-sums @windowed-sums
:windowed-paths @windowed-paths
:windowed-sizes @windowed-sizes
Expand All @@ -184,6 +195,21 @@
[:y 40]
[:z 20]]])

(def big-test-tree
(into test-tree
(repeatedly 1000 #(do [(keyword (gensym))
[:x 20]
[:y 40]
[:z 20]]))))

(def huge-test-tree
(into test-tree
[(into [:hhh]
(repeatedly 10000 #(do [(keyword (gensym))
[:x 20]
[:y 40]
[:z 20]])))]))

#_(walk-size {:window-start 372
:window-end 472
:size-cache (volatile! {})
Expand Down
Loading

0 comments on commit 1c21ca9

Please sign in to comment.