Skip to content

Commit 3179e08

Browse files
authored
Merge pull request #6 from JuliaComputing/sp/tables
Use Tables.jl interface and ag-grid
2 parents f911076 + d0f5e96 commit 3179e08

File tree

5 files changed

+148
-95
lines changed

5 files changed

+148
-95
lines changed

REQUIRE

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
julia 0.6
1+
julia 0.7
22
WebIO
33
JSExpr
4-
JuliaDB
5-
DataValues
4+
Tables
5+
JSON
6+
Observables

deps/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ag-grid
2+
build.log

deps/build.jl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
isdir(joinpath(@__DIR__, "ag-grid")) || mkdir(joinpath(@__DIR__, "ag-grid"))
2+
3+
ag_grid_base = joinpath(@__DIR__, "ag-grid", "ag-grid.js")
4+
isfile(ag_grid_base) || download("https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.noStyle.js", ag_grid_base)
5+
6+
ag_grid_base_style = joinpath(@__DIR__, "ag-grid", "ag-grid.css")
7+
isfile(ag_grid_base_style) || download("https://unpkg.com/ag-grid-community/dist/styles/ag-grid.css", ag_grid_base_style)
8+
9+
ag_grid_light = joinpath(@__DIR__, "ag-grid", "ag-grid-light.css")
10+
isfile(ag_grid_light) || download("https://unpkg.com/ag-grid-community/dist/styles/ag-theme-balham.css", ag_grid_light)
11+
12+
ag_grid_dark = joinpath(@__DIR__, "ag-grid", "ag-grid-dark.css")
13+
isfile(ag_grid_dark) || download("https://unpkg.com/ag-grid-community/dist/styles/ag-theme-balham-dark.css", ag_grid_dark)

src/TableView.jl

Lines changed: 121 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -1,119 +1,151 @@
11
module TableView
22

3-
using WebIO
4-
using JSExpr
5-
using JuliaDB
6-
using DataValues
3+
using Tables
4+
using WebIO, JSExpr, JSON, Dates, UUIDs
5+
using Observables: @map
76

8-
import JuliaDB: DNDSparse, DNextTable, NextTable
7+
export showtable
98

10-
function JuliaDB.subtable(t::DNextTable, r)
11-
table(collect(rows(t)[r]), pkey=t.pkey)
12-
end
9+
const ag_grid_imports = []
1310

14-
showna(xs) = xs
15-
function showna(xs::AbstractArray{T}) where {T<:DataValue}
16-
map(xs) do x
17-
isnull(x) ? "NA" : get(x)
11+
function __init__()
12+
empty!(ag_grid_imports)
13+
for f in ["ag-grid.js", "ag-grid.css", "ag-grid-light.css", "ag-grid-dark.css"]
14+
push!(ag_grid_imports, normpath(joinpath(@__DIR__, "..", "deps", "ag-grid", f)))
1815
end
1916
end
2017

21-
function showna(xs::Columns)
22-
rows(map(showna, columns(xs)))
23-
end
24-
25-
function showtable(t::Union{DNextTable, NextTable}; rows=1:100, colopts=Dict(), kwargs...)
26-
w = Scope(imports=["https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.js",
27-
"https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.css"])
18+
function showtable(table; dark = false, height = 500)
19+
if !Tables.istable(typeof(table))
20+
throw(ArgumentError("Argument is not a table."))
21+
end
2822

29-
trunc_rows = max(1, first(rows)):min(length(t), last(rows))
30-
subt = JuliaDB.subtable(t, trunc_rows)
23+
tablelength = Base.IteratorSize(table) == Base.HasLength() ? length(Tables.rows(table)) : nothing
24+
25+
rows = Tables.rows(table)
26+
schema = Tables.schema(table)
27+
if schema === nothing
28+
types = []
29+
for (i, c) in enumerate(Tables.eachcolumn(first(rows)))
30+
push!(types, typeof(c))
31+
end
32+
names = collect(propertynames(first(rows)))
33+
else
34+
names = schema.names
35+
types = schema.types
36+
end
37+
w = Scope(imports = ag_grid_imports)
38+
39+
coldefs = [(
40+
headerName = n,
41+
headerTooltip = types[i],
42+
field = n,
43+
type = types[i] <: Union{Missing, T where T <: Number} ? "numericColumn" : nothing,
44+
filter = types[i] <: Union{Missing, T where T <: Dates.Date} ? "agDateColumnFilter" :
45+
types[i] <: Union{Missing, T where T <: Number} ? "agNumberColumnFilter" : nothing
46+
) for (i, n) in enumerate(names)]
47+
48+
id = string("grid-", string(uuid1())[1:8])
49+
w.dom = dom"div"(className = "ag-theme-balham$(dark ? "-dark" : "")",
50+
style = Dict("width" => "100%",
51+
"height" => "$(height)px"),
52+
id = id)
53+
54+
tablelength === nothing || tablelength > 10_000 ? _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id) :
55+
_showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id)
3156

32-
headers = colnames(subt)
33-
cols = [merge(Dict(:data=>n), get(colopts, n, Dict())) for n in headers]
57+
w
58+
end
3459

60+
function _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id)
3561
options = Dict(
36-
:data => showna(collect(JuliaDB.rows(subt))),
37-
:colHeaders => headers,
38-
:modifyColWidth => @js(w -> w > 300 ? 300 : w),
39-
:modifyRowHeight => @js(h -> h > 60 ? 50 : h),
40-
:manualColumnResize => true,
41-
:manualRowResize => true,
42-
:columns => cols,
43-
:width => 800,
44-
:height => 400,
62+
:rowData => JSONText(table2json(rows, names, types)),
63+
:columnDefs => coldefs,
64+
:enableSorting => true,
65+
:enableFilter => true,
66+
:enableColResize => true,
67+
:multiSortKey => "ctrl",
4568
)
46-
if (length(t.pkey) > 0 && t.pkey == [1:length(t.pkey);])
47-
options[:fixedColumnsLeft] = length(t.pkey)
48-
end
4969

50-
merge!(options, Dict(kwargs))
51-
52-
handler = @js function (Handsontable)
53-
@var sizefix = document.createElement("style");
54-
sizefix.textContent = """
55-
.htCore td {
56-
white-space:nowrap
57-
}
58-
"""
59-
this.dom.appendChild(sizefix)
60-
this.hot = @new Handsontable(this.dom, $options);
70+
handler = @js function (agGrid)
71+
@var gridOptions = $options
72+
@var el = document.getElementById($id)
73+
this.table = @new agGrid.Grid(el, gridOptions)
74+
gridOptions.columnApi.autoSizeColumns($names)
6175
end
6276
onimport(w, handler)
63-
w.dom = dom"div"()
64-
w
6577
end
6678

67-
function showtable(t::Union{DNDSparse, NDSparse}; rows=1:100, colopts=Dict(), kwargs...)
68-
w = Scope(imports=["https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.js",
69-
"https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.css"])
70-
data = Observable{Any}(w, "data", [])
71-
72-
trunc_rows = max(1, first(rows)):min(length(t), last(rows))
7379

74-
ks = keys(t)[trunc_rows]
75-
vs = values(t)[trunc_rows]
76-
77-
if !isa(keys(t), Columns)
78-
ks = collect(ks)
79-
vs = collect(vs)
80+
function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id)
81+
rowparams = Observable(w, "rowparams", Dict("startRow" => 1,
82+
"endRow" => 100,
83+
"successCallback" => @js v -> nothing))
84+
requestedrows = Observable(w, "requestedrows", JSONText("{}"))
85+
on(rowparams) do x
86+
requestedrows[] = JSONText(table2json(rows, names, types, requested = [x["startRow"], x["endRow"]]))
8087
end
8188

82-
subt = NDSparse(showna(ks), showna(vs))
83-
84-
headers = colnames(subt)
85-
cols = [merge(Dict(:data=>n), get(colopts, n, Dict())) for n in headers]
89+
onjs(requestedrows, @js function (val)
90+
($rowparams[]).successCallback(val, $(tablelength))
91+
end)
8692

8793
options = Dict(
88-
:data => JuliaDB.rows(subt),
89-
:colHeaders => headers,
90-
:fixedColumnsLeft => ndims(t),
91-
:modifyColWidth => @js(w -> w > 300 ? 300 : w),
92-
:modifyRowHeight => @js(h -> h > 60 ? 50 : h),
93-
:manualColumnResize => true,
94-
:manualRowResize => true,
95-
:columns => cols,
96-
:width => 800,
97-
:height => 400,
94+
:columnDefs => coldefs,
95+
:enableSorting => true,
96+
:enableFilter => true,
97+
:maxConcurrentDatasourceRequests => 1,
98+
:cacheBlockSize => 1000,
99+
:maxBlocksInCache => 100,
100+
:enableColResize => true,
101+
:multiSortKey => "ctrl",
102+
:rowModelType => "infinite",
103+
:datasource => Dict(
104+
"getRows" =>
105+
@js function (rowParams)
106+
$rowparams[] = rowParams
107+
end
108+
,
109+
"rowCount" => tablelength
110+
)
98111
)
99112

100-
merge!(options, Dict(kwargs))
101-
102-
handler = @js function (Handsontable)
103-
@var sizefix = document.createElement("style");
104-
sizefix.textContent = """
105-
.htCore td {
106-
white-space:nowrap
107-
}
108-
"""
109-
this.dom.appendChild(sizefix)
110-
this.hot = @new Handsontable(this.dom, $options);
113+
handler = @js function (agGrid)
114+
@var gridOptions = $options
115+
@var el = document.getElementById($id)
116+
this.table = @new agGrid.Grid(el, gridOptions)
117+
gridOptions.columnApi.autoSizeColumns($names)
111118
end
112119
onimport(w, handler)
113-
w.dom = dom"div"()
114-
w
115120
end
116121

117-
showtable(t; kwargs...) = showtable(table(t); kwargs...)
122+
# directly write JSON instead of allocating temporary dicts etc
123+
function table2json(rows, names, types; requested = nothing)
124+
io = IOBuffer()
125+
print(io, '[')
126+
for (i, row) in enumerate(rows)
127+
if requested == nothing || first(requested) <= i <= last(requested)
128+
print(io, '{')
129+
i = 1
130+
for col in Tables.eachcolumn(row)
131+
JSON.print(io, names[i])
132+
i += 1
133+
print(io, ':')
134+
if col isa Number
135+
JSON.print(io, col)
136+
else
137+
JSON.print(io, sprint(print, col))
138+
end
139+
print(io, ',')
140+
end
141+
skip(io, -1)
142+
print(io, '}')
143+
print(io, ',')
144+
end
145+
end
146+
skip(io, -1)
147+
print(io, ']')
118148

119-
end # module
149+
String(take!(io))
150+
end
151+
end

test/runtests.jl

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
using TableView
2-
using Base.Test
2+
using Test, WebIO
33

4-
# write your own tests here
5-
@test 1 == 2
4+
@test isfile(joinpath(@__DIR__, "..", "deps", "ag-grid", "ag-grid.js"))
5+
6+
nttable = [
7+
(a = 2.0, b = 3),
8+
(a = 3.0, b = 12)
9+
]
10+
@test showtable(nttable) isa WebIO.Scope

0 commit comments

Comments
 (0)