-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use Tables.jl interface and ag-grid #6
Changes from all commits
06ef085
779a0ac
14e6c37
7e807a2
f7f1dab
6e6706a
7234810
e396d1c
b97defd
c40a49b
be1a96d
d0f5e96
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
julia 0.6 | ||
julia 0.7 | ||
WebIO | ||
JSExpr | ||
JuliaDB | ||
DataValues | ||
Tables | ||
JSON | ||
Observables |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
ag-grid | ||
build.log |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
isdir(joinpath(@__DIR__, "ag-grid")) || mkdir(joinpath(@__DIR__, "ag-grid")) | ||
|
||
ag_grid_base = joinpath(@__DIR__, "ag-grid", "ag-grid.js") | ||
isfile(ag_grid_base) || download("https://unpkg.com/ag-grid-community/dist/ag-grid-community.min.noStyle.js", ag_grid_base) | ||
|
||
ag_grid_base_style = joinpath(@__DIR__, "ag-grid", "ag-grid.css") | ||
isfile(ag_grid_base_style) || download("https://unpkg.com/ag-grid-community/dist/styles/ag-grid.css", ag_grid_base_style) | ||
|
||
ag_grid_light = joinpath(@__DIR__, "ag-grid", "ag-grid-light.css") | ||
isfile(ag_grid_light) || download("https://unpkg.com/ag-grid-community/dist/styles/ag-theme-balham.css", ag_grid_light) | ||
|
||
ag_grid_dark = joinpath(@__DIR__, "ag-grid", "ag-grid-dark.css") | ||
isfile(ag_grid_dark) || download("https://unpkg.com/ag-grid-community/dist/styles/ag-theme-balham-dark.css", ag_grid_dark) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,119 +1,151 @@ | ||
module TableView | ||
|
||
using WebIO | ||
using JSExpr | ||
using JuliaDB | ||
using DataValues | ||
using Tables | ||
using WebIO, JSExpr, JSON, Dates, UUIDs | ||
using Observables: @map | ||
|
||
import JuliaDB: DNDSparse, DNextTable, NextTable | ||
export showtable | ||
|
||
function JuliaDB.subtable(t::DNextTable, r) | ||
table(collect(rows(t)[r]), pkey=t.pkey) | ||
end | ||
const ag_grid_imports = [] | ||
|
||
showna(xs) = xs | ||
function showna(xs::AbstractArray{T}) where {T<:DataValue} | ||
map(xs) do x | ||
isnull(x) ? "NA" : get(x) | ||
function __init__() | ||
empty!(ag_grid_imports) | ||
for f in ["ag-grid.js", "ag-grid.css", "ag-grid-light.css", "ag-grid-dark.css"] | ||
push!(ag_grid_imports, normpath(joinpath(@__DIR__, "..", "deps", "ag-grid", f))) | ||
end | ||
end | ||
|
||
function showna(xs::Columns) | ||
rows(map(showna, columns(xs))) | ||
end | ||
|
||
function showtable(t::Union{DNextTable, NextTable}; rows=1:100, colopts=Dict(), kwargs...) | ||
w = Scope(imports=["https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.js", | ||
"https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.css"]) | ||
function showtable(table; dark = false, height = 500) | ||
if !Tables.istable(typeof(table)) | ||
throw(ArgumentError("Argument is not a table.")) | ||
end | ||
|
||
trunc_rows = max(1, first(rows)):min(length(t), last(rows)) | ||
subt = JuliaDB.subtable(t, trunc_rows) | ||
tablelength = Base.IteratorSize(table) == Base.HasLength() ? length(Tables.rows(table)) : nothing | ||
|
||
rows = Tables.rows(table) | ||
schema = Tables.schema(table) | ||
if schema === nothing | ||
types = [] | ||
for (i, c) in enumerate(Tables.eachcolumn(first(rows))) | ||
push!(types, typeof(c)) | ||
end | ||
names = collect(propertynames(first(rows))) | ||
else | ||
names = schema.names | ||
types = schema.types | ||
end | ||
w = Scope(imports = ag_grid_imports) | ||
|
||
coldefs = [( | ||
headerName = n, | ||
headerTooltip = types[i], | ||
field = n, | ||
type = types[i] <: Union{Missing, T where T <: Number} ? "numericColumn" : nothing, | ||
filter = types[i] <: Union{Missing, T where T <: Dates.Date} ? "agDateColumnFilter" : | ||
types[i] <: Union{Missing, T where T <: Number} ? "agNumberColumnFilter" : nothing | ||
) for (i, n) in enumerate(names)] | ||
|
||
id = string("grid-", string(uuid1())[1:8]) | ||
w.dom = dom"div"(className = "ag-theme-balham$(dark ? "-dark" : "")", | ||
style = Dict("width" => "100%", | ||
"height" => "$(height)px"), | ||
id = id) | ||
|
||
tablelength === nothing || tablelength > 10_000 ? _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id) : | ||
_showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id) | ||
|
||
headers = colnames(subt) | ||
cols = [merge(Dict(:data=>n), get(colopts, n, Dict())) for n in headers] | ||
w | ||
end | ||
|
||
function _showtable_sync!(w, names, types, rows, coldefs, tablelength, dark, id) | ||
options = Dict( | ||
:data => showna(collect(JuliaDB.rows(subt))), | ||
:colHeaders => headers, | ||
:modifyColWidth => @js(w -> w > 300 ? 300 : w), | ||
:modifyRowHeight => @js(h -> h > 60 ? 50 : h), | ||
:manualColumnResize => true, | ||
:manualRowResize => true, | ||
:columns => cols, | ||
:width => 800, | ||
:height => 400, | ||
:rowData => JSONText(table2json(rows, names, types)), | ||
:columnDefs => coldefs, | ||
:enableSorting => true, | ||
:enableFilter => true, | ||
:enableColResize => true, | ||
:multiSortKey => "ctrl", | ||
) | ||
if (length(t.pkey) > 0 && t.pkey == [1:length(t.pkey);]) | ||
options[:fixedColumnsLeft] = length(t.pkey) | ||
end | ||
|
||
merge!(options, Dict(kwargs)) | ||
|
||
handler = @js function (Handsontable) | ||
@var sizefix = document.createElement("style"); | ||
sizefix.textContent = """ | ||
.htCore td { | ||
white-space:nowrap | ||
} | ||
""" | ||
this.dom.appendChild(sizefix) | ||
this.hot = @new Handsontable(this.dom, $options); | ||
handler = @js function (agGrid) | ||
@var gridOptions = $options | ||
@var el = document.getElementById($id) | ||
this.table = @new agGrid.Grid(el, gridOptions) | ||
gridOptions.columnApi.autoSizeColumns($names) | ||
end | ||
onimport(w, handler) | ||
w.dom = dom"div"() | ||
w | ||
end | ||
|
||
function showtable(t::Union{DNDSparse, NDSparse}; rows=1:100, colopts=Dict(), kwargs...) | ||
w = Scope(imports=["https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.js", | ||
"https://cdnjs.cloudflare.com/ajax/libs/handsontable/0.34.0/handsontable.full.css"]) | ||
data = Observable{Any}(w, "data", []) | ||
|
||
trunc_rows = max(1, first(rows)):min(length(t), last(rows)) | ||
|
||
ks = keys(t)[trunc_rows] | ||
vs = values(t)[trunc_rows] | ||
|
||
if !isa(keys(t), Columns) | ||
ks = collect(ks) | ||
vs = collect(vs) | ||
function _showtable_async!(w, names, types, rows, coldefs, tablelength, dark, id) | ||
rowparams = Observable(w, "rowparams", Dict("startRow" => 1, | ||
"endRow" => 100, | ||
"successCallback" => @js v -> nothing)) | ||
requestedrows = Observable(w, "requestedrows", JSONText("{}")) | ||
on(rowparams) do x | ||
requestedrows[] = JSONText(table2json(rows, names, types, requested = [x["startRow"], x["endRow"]])) | ||
end | ||
|
||
subt = NDSparse(showna(ks), showna(vs)) | ||
|
||
headers = colnames(subt) | ||
cols = [merge(Dict(:data=>n), get(colopts, n, Dict())) for n in headers] | ||
onjs(requestedrows, @js function (val) | ||
($rowparams[]).successCallback(val, $(tablelength)) | ||
end) | ||
|
||
options = Dict( | ||
:data => JuliaDB.rows(subt), | ||
:colHeaders => headers, | ||
:fixedColumnsLeft => ndims(t), | ||
:modifyColWidth => @js(w -> w > 300 ? 300 : w), | ||
:modifyRowHeight => @js(h -> h > 60 ? 50 : h), | ||
:manualColumnResize => true, | ||
:manualRowResize => true, | ||
:columns => cols, | ||
:width => 800, | ||
:height => 400, | ||
:columnDefs => coldefs, | ||
:enableSorting => true, | ||
:enableFilter => true, | ||
:maxConcurrentDatasourceRequests => 1, | ||
:cacheBlockSize => 1000, | ||
:maxBlocksInCache => 100, | ||
:enableColResize => true, | ||
:multiSortKey => "ctrl", | ||
:rowModelType => "infinite", | ||
:datasource => Dict( | ||
"getRows" => | ||
@js function (rowParams) | ||
$rowparams[] = rowParams | ||
end | ||
, | ||
"rowCount" => tablelength | ||
) | ||
) | ||
|
||
merge!(options, Dict(kwargs)) | ||
|
||
handler = @js function (Handsontable) | ||
@var sizefix = document.createElement("style"); | ||
sizefix.textContent = """ | ||
.htCore td { | ||
white-space:nowrap | ||
} | ||
""" | ||
this.dom.appendChild(sizefix) | ||
this.hot = @new Handsontable(this.dom, $options); | ||
handler = @js function (agGrid) | ||
@var gridOptions = $options | ||
@var el = document.getElementById($id) | ||
this.table = @new agGrid.Grid(el, gridOptions) | ||
gridOptions.columnApi.autoSizeColumns($names) | ||
end | ||
onimport(w, handler) | ||
w.dom = dom"div"() | ||
w | ||
end | ||
|
||
showtable(t; kwargs...) = showtable(table(t); kwargs...) | ||
# directly write JSON instead of allocating temporary dicts etc | ||
function table2json(rows, names, types; requested = nothing) | ||
io = IOBuffer() | ||
print(io, '[') | ||
for (i, row) in enumerate(rows) | ||
if requested == nothing || first(requested) <= i <= last(requested) | ||
print(io, '{') | ||
i = 1 | ||
for col in Tables.eachcolumn(row) | ||
JSON.print(io, names[i]) | ||
i += 1 | ||
print(io, ':') | ||
if col isa Number | ||
JSON.print(io, col) | ||
else | ||
JSON.print(io, sprint(print, col)) | ||
end | ||
print(io, ',') | ||
end | ||
skip(io, -1) | ||
print(io, '}') | ||
print(io, ',') | ||
end | ||
end | ||
skip(io, -1) | ||
print(io, ']') | ||
|
||
end # module | ||
String(take!(io)) | ||
end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I love this! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The unfortunate thing is that I can't figure out how to transfer the data with WebIO without an additional There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think there is a type in JSON.jl called JSONString which was introduced for this purpose |
||
end |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,10 @@ | ||
using TableView | ||
using Base.Test | ||
using Test, WebIO | ||
|
||
# write your own tests here | ||
@test 1 == 2 | ||
@test isfile(joinpath(@__DIR__, "..", "deps", "ag-grid", "ag-grid.js")) | ||
|
||
nttable = [ | ||
(a = 2.0, b = 3), | ||
(a = 3.0, b = 12) | ||
] | ||
@test showtable(nttable) isa WebIO.Scope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@shashi Is there a way to directly style the
div
introduced by theScope
? This results in something likeright now, but I'd also like to give the
Scope
(and the mountpoint, I guess) a style to allow piping through the parent's width and height.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't have that right now, but the way to do it would be allow
s(style=Dict(:foo => bar))
to work wheres
is a Scope (just like it works for Node)...