diff --git a/README.md b/README.md index c1ae91bf..d6d321ff 100644 --- a/README.md +++ b/README.md @@ -7,18 +7,22 @@ _Julia bindings to Vega-Lite_ |[![VegaLite](http://pkg.julialang.org/badges/VegaLite_0.4.svg)](http://pkg.julialang.org/?pkg=VegaLite&ver=0.4) | N/A | [![Build Status](https://travis-ci.org/fredo-dedup/VegaLite.jl.svg?branch=master)](https://travis-ci.org/fredo-dedup/VegaLite.jl) | [![Coverage Status](https://coveralls.io/repos/github/fredo-dedup/VegaLite.jl/badge.svg?branch=master)](https://coveralls.io/github/fredo-dedup/VegaLite.jl?branch=master) | -This package provides access to the Vega-Lite high-level visualization grammar (http://vega.github.io/vega-lite/) from within Julia . Install with -`Pkg.add("VegaLite")` (or `Pkg.clone("https://github.com/fredo-dedup/VegaLite.jl.git")` -until it hasn't reached the official repository). You can use the integrated documentation (e.g. `? config_mark`) to get the full list of properties. +This package provides access to the Vega-Lite high-level visualization grammar (http://vega.github.io/vega-lite/) from Julia. -Vega-Lite is a simpler version of the Vega grammar allowing smaller and more expressive chart specifications. If you don't find this library powerful enough for your needs you can turn to Vega.jl (https://github.com/johnmyleswhite/Vega.jl) on which this project is largely based (thanks !). +Vega-Lite is a simpler version of the Vega grammar allowing smaller and more expressive chart specifications. If you don't find this library powerful enough for your needs you can turn to Vega.jl (https://github.com/johnmyleswhite/Vega.jl) on which this project is partially based (thanks !). -##TODO: -- IJulia/Jupyter integration -- More tests -- Implement missing sub-specs : `transform`, `bin`,.. +Install with `Pkg.add("VegaLite")` (or `Pkg.clone("https://github.com/fredo-dedup/VegaLite.jl.git")` +until it reaches the official repository). You can use the integrated documentation, e.g. `? config_mark` to get the full list of properties of the `config_mark` function. -Any contribution, PR or issue, is welcome ! +The julia functions follow pretty closely the Vega-Lite JSON format: `data_values()` creates the `{"data": {values: { ...} }}` part of the spec file, etc. +Only two functions are added: +- `svg(Bool)` : sets the drawing mode of the plots, SVG if `true`, canvas if `false`. Default = `true` +- `buttons(Bool)` : indicates if the plot should be accompanied with links 'Save as PNG', 'View source' and 'Open in Vega Editor'. + +Currently, VegaLite.jl works with IJulia/Jupyter, Escher and in the standard REPL (a browser window will open). + + +All contributions, PR or issue, are welcome ! ##Examples: @@ -38,7 +42,7 @@ v = data_values(time=ts, res=ys) + # add the data vectors & assign to symbols ![plot1](examples/png/vega (1).png) -- Scatterplot from a DataFrame: +- Scatterplot, using a DataFrame as the source: ```julia using RDatasets @@ -52,7 +56,7 @@ data_values(mpg) + # add values ![plot1](examples/png/vega (2).png) -- A scatterplot, with colors and size settings: +- A scatterplot, with colors and size settings for the plot: ```julia data_values(mpg) + mark_point() + diff --git a/assets/vega-lite.html b/assets/vega-lite.html index 2c169d3b..b170ec0b 100644 --- a/assets/vega-lite.html +++ b/assets/vega-lite.html @@ -27,7 +27,7 @@ var embedSpec = { mode: "vega-lite", renderer: this.svg ? "svg" : "canvas", - actions: false, //this.actions, + actions: this.actions, spec: this.json } vg.embed(this, embedSpec, function(error, result) { diff --git a/src/VegaLite.jl b/src/VegaLite.jl index 50081ab1..92c25754 100644 --- a/src/VegaLite.jl +++ b/src/VegaLite.jl @@ -8,6 +8,8 @@ module VegaLite export VegaLiteVis, scale, axis, legend + export svg, buttons + export data_values export config_grid, config_facet_axis, config_facet_cell, @@ -51,6 +53,6 @@ module VegaLite Pkg.installed("DataFrames") != nothing && include("dataframes_integration.jl") ### Integration with IJulia - Jupyter - # Pkg.installed("IJulia") != nothing && include("ijulia_integration.jl") + Pkg.installed("IJulia") != nothing && include("ijulia_integration.jl") end diff --git a/src/escher_integration.jl b/src/escher_integration.jl index 0e38ea5c..6ecf3036 100644 --- a/src/escher_integration.jl +++ b/src/escher_integration.jl @@ -1,17 +1,6 @@ import Escher: Elem, Tile, render import Base: convert -ESCHER_SVG = true -ESCHER_BUTTONS = true - -export svg, buttons - -svg() = ESCHER_SVG -svg(b::Bool) = (global ESCHER_SVG ; ESCHER_SVG = b) -buttons() = ESCHER_BUTTONS -buttons(b::Bool) = (global ESCHER_BUTTONS ; ESCHER_BUTTONS = b) - - type VegaLiteTile <: Tile json::AbstractString svg::Bool @@ -19,7 +8,7 @@ type VegaLiteTile <: Tile end function VegaLiteTile(vis::AbstractString) - VegaLiteTile(vis, ESCHER_SVG, ESCHER_BUTTONS) + VegaLiteTile(vis, SVG, SAVE_BUTTONS) end convert(::Type{Tile}, v::VegaLiteVis) = VegaLiteTile(JSON.json(v.vis)) diff --git a/src/ijulia_integration.jl b/src/ijulia_integration.jl new file mode 100644 index 00000000..83c279b1 --- /dev/null +++ b/src/ijulia_integration.jl @@ -0,0 +1,79 @@ +###################################################################### +# +# IJulia Integration +# +# - consist in defining a writemime(::IO, m::MIME"text/html", v::VegaLiteVis) +# including scripts that load the required libraries (D3, vega, vega-lite) +# - Will it work with other html backends ? probably not. +# +###################################################################### + +import Base.writemime + +# FIXME : Apparently, loading local js files is not allowed by the browser +# => libraries are loaded externally in the `require.config` + +# function jslibpath(url...) +# libpath = Pkg.dir("VegaLite", "assets", "bower_components", url...) +# replace(libpath, "\\", "/") # for windows... +# end +# // d3: "$(jslibpath("d3","d3.min.js"))", +# // vega: "$(jslibpath("vega", "vega.js"))", +# // vegalite: "$(jslibpath("vega-lite", "vega-lite.js"))", +# // vegaembed: "$(jslibpath("vega-embed", "vega-embed.js"))" + + +function writemime(io::IO, m::MIME"text/html", v::VegaLiteVis) + divid = "vl" * randstring(10) # generated id for this plot + + fh = """ +
+
+ + + + + +
+ """ + # FIXME : understand why vega-embed can't be loaded + # SVG and SAVE_BUTTONS flags are ignored + # vg.embed("#$divid", embedSpec, function(error, result) { + + write(io, fh) +end + + +export writemime diff --git a/src/render.jl b/src/render.jl index 0fabcae0..88e31210 100644 --- a/src/render.jl +++ b/src/render.jl @@ -29,6 +29,8 @@ function writehtml(io::IO, v::VegaLiteVis; title="Vega.jl Visualization") var embedSpec = { mode: "vega-lite", + renderer: "$(SVG ? "svg" : "canvas")", + actions: $SAVE_BUTTONS, spec: $(JSON.json(v.vis)) } @@ -65,7 +67,6 @@ end ################################################### -import Base.writemime # asset(url...) = readall(Pkg.dir("Vega", "assets", "bower_components", url...)) @@ -82,7 +83,7 @@ import Base.writemime # """) # end # -import Patchwork: Elem +# import Patchwork: Elem # function patchwork_repr(v::VegaLiteVis) # divid = "vg" * randstring(3) @@ -151,9 +152,211 @@ import Patchwork: Elem # write(io, fh) # end -jspath(url...) = Pkg.dir("VegaLite", "assets", "bower_components", url...) +# function jslibpath(url...) +# libpath = Pkg.dir("VegaLite", "assets", "bower_components", url...) +# replace(libpath, "\\", "/") # for windows... +# end +# +# +# function writemime(io::IO, m::MIME"text/html", v::VegaLiteVis) +# divid = "vl" * randstring(3) # generated id for this plot + +# script = """ +# require.config({ +# paths: { +# d3: "https://d3js.org/d3.v3.min", +# vega: "https://vega.github.io/vega/vega.min", +# vegalite: "https://vega.github.io/vega-lite/vega-lite", +# vegaembed: "https://vega.github.io/vega-editor/vendor/vega-embed" +# } +# }); +# +# require(["d3"], function(d3){ +# +# window.d3 = d3; +# +# require(["vega", "vegalite", "vegaembed"], +# function(vg, vgl, vge){ +# +# // window.vg = vg; +# +# vg.parse.spec({ +# "description": "A simple bar chart with embedded data.", +# "data": { +# "values": [ +# {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, +# {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, +# {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} +# ] +# }, +# "mark": "bar", +# "encoding": { +# "x": {"field": "a", "type": "ordinal"}, +# "y": {"field": "b", "type": "quantitative"} +# } +# }, function(chart) { chart({el:"#$divid"}).update(); }); +# +# document.getElementById("$divid").insertAdjacentHTML('beforeend', '
Save yourgh ! as PNG') +# +# +# }); +# }); +# """ +# +# spec = """ +# { +# "width": 400, +# "height": 200, +# "padding": {"top": 10, "left": 30, "bottom": 30, "right": 10}, +# "data": [ +# { +# "name": "table", +# "values": [ +# {"x": 1, "y": 28}, {"x": 2, "y": 55}, +# {"x": 3, "y": 43}, {"x": 4, "y": 91}, +# {"x": 5, "y": 81}, {"x": 6, "y": 53}, +# {"x": 7, "y": 19}, {"x": 8, "y": 87}, +# {"x": 9, "y": 52}, {"x": 10, "y": 48}, +# {"x": 11, "y": 24}, {"x": 12, "y": 49}, +# {"x": 13, "y": 87}, {"x": 14, "y": 66}, +# {"x": 15, "y": 17}, {"x": 16, "y": 27}, +# {"x": 17, "y": 68}, {"x": 18, "y": 16}, +# {"x": 19, "y": 49}, {"x": 20, "y": 15} +# ] +# } +# ], +# "scales": [ +# { +# "name": "x", +# "type": "ordinal", +# "range": "width", +# "domain": {"data": "table", "field": "x"} +# }, +# { +# "name": "y", +# "type": "linear", +# "range": "height", +# "domain": {"data": "table", "field": "y"}, +# "nice": true +# } +# ], +# "axes": [ +# {"type": "x", "scale": "x"}, +# {"type": "y", "scale": "y"} +# ], +# "marks": [ +# { +# "type": "rect", +# "from": {"data": "table"}, +# "properties": { +# "enter": { +# "x": {"scale": "x", "field": "x"}, +# "width": {"scale": "x", "band": true, "offset": -1}, +# "y": {"scale": "y", "field": "y"}, +# "y2": {"scale": "y", "value": 0} +# }, +# "update": { +# "fill": {"value": "steelblue"} +# }, +# "hover": { +# "fill": {"value": "red"} +# } +# } +# } +# ] +# } +# """ +# +# spec2 = """ +# { +# "description": "A simple bar chart with embedded data.", +# "data": { +# "values": [ +# {"a": "A","b": 28}, {"a": "B","b": 55}, {"a": "C","b": 43}, +# {"a": "D","b": 91}, {"a": "E","b": 81}, {"a": "F","b": 53}, +# {"a": "G","b": 19}, {"a": "H","b": 87}, {"a": "I","b": 52} +# ] +# }, +# "mark": "bar", +# "encoding": { +# "x": {"field": "a", "type": "ordinal"}, +# "y": {"field": "b", "type": "quantitative"} +# } +# } +# """ +# +# script = """ +# require.config({ +# paths: { +# d3: "https://vega.github.io/vega-editor/vendor/d3.min", +# vega: "https://vega.github.io/vega/vega.min", +# cloud: "https://vega.github.io/vega-editor/vendor/d3.layout.cloud", +# topojson: "https://vega.github.io/vega-editor/vendor/topojson", +# vegalite: "https://vega.github.io/vega-lite/vega-lite", +# embed: "https://vega.github.io/vega-editor/vendor/vega-embed" +# } +# }); +# +# require(["d3", "topojson", "cloud"], function(d3, topojson, cloud){ +# +# window.d3 = d3; +# window.topojson = topojson; +# window.d3.layout.cloud = cloud; +# console.log("d3") +# +# +# require(["vega"], function(vg) { +# +# window.vg = vg +# console.log("vg") +# +# require(["vegalite"], function(vgl) { +# +# console.log("vegalite") +# +# var embedSpec = { +# renderer: "svg", +# actions: false, +# spec: $spec2 +# }; +# +# var vgSpec = vgl.compile(embedSpec.spec).spec; +# vg.parse.spec(vgSpec, function(chart) { chart({el:\"#$divid\"}).update(); }); +# +# }); //vegaembed require end +# }); //vega require end +# +# }); //d3 require end +# """ + # vg.embed("#$divid", embedSpec, function(error, result) { }); + # vg.parse.spec($spec, function(chart) { chart({el:\"#$divid\"}).update(); }); + + + # window.setTimeout(function() { + # var pnglink = document.getElementById(\"$divid\").getElementsByTagName(\"canvas\")[0].toDataURL(\"image/png\") + # document.getElementById(\"$divid\").insertAdjacentHTML('beforeend', '
Save as PNG') + # + # }, 20); + + # var vgSpec = vgl.compile(embedSpec.spec).spec; + # var embedSpec = { + # mode: "vega-lite", + # renderer: "svg", + # actions: false, + # spec: $(JSON.json(v.vis)) + # }; + # + # vg.embed("#$divid", embedSpec, function(error, result) { result.view }); + + # patch = Elem(:div, [ + # Elem(:div, "") & Dict(:id=>divid, :style=>Dict("min-height"=>"300px")), + # # Elem(:div, "") & Dict(:id=>divid), + # Elem(:script, script) & Dict(:type=>"text/javascript") + # ]) + # + # return writemime(io, m, patch) + -function writemime(io::IO, m::MIME"text/html", v::VegaLiteVis) # fh = """ #
# @@ -161,50 +364,66 @@ function writemime(io::IO, m::MIME"text/html", v::VegaLiteVis) # # - fh = """ -
- -
- -
- - - - - -
- """ - - println(fh) - # writemime(io, m, fh) - write(io, fh) -end - - -export writemime + # // d3: "$(jslibpath("d3","d3.min.js"))", + # // vega: "$(jslibpath("vega", "vega.js"))", + # // vegalite: "$(jslibpath("vega-lite", "vega-lite.js"))", + # // vegaembed: "$(jslibpath("vega-embed", "vega-embed.js"))" +# +# fh = """ +#
+#
+# +# +# +# +# +#
+# """ +# # FIXME : understand why vega-embed can't be loaded +# # vg.embed("#$divid", embedSpec, function(error, result) { +# +# println(fh) +# # writemime(io, m, fh) +# write(io, fh) +# end +# +# +# export writemime diff --git a/src/utils.jl b/src/utils.jl index 473b7e96..2a902c8d 100644 --- a/src/utils.jl +++ b/src/utils.jl @@ -21,6 +21,29 @@ softmerge(a::Dict, b::Any) = a softmerge(a::Any, b::Dict) = b softmerge(a, b) = b + + + + +# Switch for plotting in SVGs or canvas +SVG = true +svg() = SVG +svg(b::Bool) = (global SVG ; SVG = b) + + + + +# Switch for showing or not the "save as PNG buttons" +SAVE_BUTTONS = true +buttons() = SAVE_BUTTONS +buttons(b::Bool) = (global SAVE_BUTTONS ; SAVE_BUTTONS = b) + + + + + + + # build Dict from kwargs, checks against signature function _mkdict(signature, properties) d = Dict() diff --git a/test/debug.jl b/test/debug.jl new file mode 100644 index 00000000..ae602968 --- /dev/null +++ b/test/debug.jl @@ -0,0 +1,179 @@ +module A ; end +reload("VegaLite") +reload("Paper") + + +module A +using Paper +using VegaLite +import JSON + + +# config(abcd="trus") +# config(viewport="trus") +# config(viewport=[400,330]) +# JSON.json(v.vis) + +@session vgtest vbox pad(2em) + +@rewire VegaLite.VegaLiteVis + +sleep(3.0) +vv = Paper.currentSession.window +push!(vv.assets, ("VegaLite", "vega-lite")) + +#-------- draw the header ---------------------------------- +@newchunk pg vbox packacross(center) fillcolor("#ddd") pad(1em) + +title(2, "Veg Lite") +title(1, "with plots using VegaLite") |> fontstyle(italic) + +#------- plot ------------------------------------ +@newchunk center + +ts = sort(rand(10)) +ys = Float64[ rand()*0.1 + cos(x) for x in ts] + +v = data_values(time=ts, res=ys) + + mark_line() + + encoding_x_quant(:time) + + encoding_y_quant(:res) + +buttons(false) +svg(false) + +@newchunk center2 hbox +@newchunk center2.nw hbox + +container(10em,10em) |> fillcolor("#daa") +hline() |> fillcolor("#daa") +vskip(1em) + +v2 = v + config_scale(round=true) +JSON.print(v2.vis) + + +v2 = v + config_axis(axisWidth=3.2, labelAngle=45) +v2 = v + config_axis(labelAlign="left") +v2 = v + config_axis(tickPadding=10) +JSON.print(v2.vis) + + + +v = data_values(time=[ts, reverse(ts)], res=[ys, reverse(ys-0.3)]) + + mark_area() + + encoding_x_quant(:time) + + encoding_y_quant(:res) + + # encoding_path_quant() + + config_cell(width=300, height=300) + +v |> fontstyle(italic) + +data_values(posit=[1:2length(ts);], time=[ts, reverse(ts)], res=[ys, reverse(ys-0.3)]) + + mark_line() + + encoding_x_quant(:time, scale=Dict(:domain=>[0,1])) + + encoding_y_quant(:res) + + encoding_path_ord(:posit) + + config(background="#eee") + + config_grid(gridColor="green") + + config_mark(font="Courrier New", strokeDash=[5,5], stroke="red", + filled=true, fill="#8e8", fillOpacity=0.2) + + config_cell(width=200, height=150, strokeWidth=.2, strokeDash=[10,5]) + + +JSON.print(data_values(posit=[1:2length(ts);], time=[ts, reverse(ts)], res=[ys, reverse(ys-0.3)])) + +@newchunk rdataset + +using RDatasets + +mpg = dataset("ggplot2", "mpg") + + +data_values(mpg) + + mark_point() + + encoding_x_quant(:Cty) + + encoding_y_quant(:Hwy) + +data_values(mpg) + + mark_point() + + encoding_x_quant(:Cty, axis=false) + + encoding_y_quant(:Hwy, scale=scale(zero=false)) + + encoding_color_nominal(:Manufacturer) + + config_cell(width=350, height=400) + +data_values(mpg) + + mark_line() + + encoding_x_ord(:Year, + axis = axis(labelAngle=-45, labelAlign="right"), + scale = scale(bandSize=50)) + + encoding_y_quant(:Hwy, aggregate="mean") + + encoding_color_nominal(:Manufacturer) + +data_values(mpg) + + mark_point() + + encoding_column_ord(:Cyl) + + encoding_row_ord(:Year) + + encoding_x_quant(:Displ) + + encoding_y_quant(:Hwy) + + encoding_size_quant(:Cty) + + encoding_color_nominal(:Manufacturer) + +data_values(mpg) + + mark_text() + + encoding_column_ord(:Cyl) + + encoding_row_ord(:Year) + + encoding_text_quant(:Displ, aggregate="mean") + + config_mark(fontStyle="italic", fontSize=12, font="courier") + + +##################### IJulia ####################### + +Pkg.build("IJulia") +Pkg.build("IJulia") +Pkg.build("IJulia") + +using IJulia +@async notebook() + +using SHA +sha256("abcde") +5+6 + +WARNING:root:kernel 6e1e5e58-776c-4d0e-b9e2-0d934ff40742 restarted +WARNING: Union(args...) is deprecated, use Union{args...} instead. + in depwarn at deprecated.jl:73 + in call at deprecated.jl:50 + in include at boot.jl:261 + in include_from_node1 at loading.jl:304 + in include at boot.jl:261 + in include_from_node1 at loading.jl:304 + in include at boot.jl:261 + in include_from_node1 at loading.jl:304 + in process_options at client.jl:280 + in _start at client.jl:378 +while loading D:\frtestar\.julia\v0.4\IJulia\src\handlers.jl, in expression starting on line 49 +ERROR: LoadError: UndefVarError: SHA256 not defined + in init at D:\frtestar\.julia\v0.4\IJulia\src\IJulia.jl:62 + in include at boot.jl:261 + in include_from_node1 at loading.jl:304 + in process_options at client.jl:280 + in _start at client.jl:378 +while loading D:\frtestar\.julia\v0.4\IJulia\src\kernel.jl, in expression starting on line 6 +[I 14:40:00.311 NotebookApp] KernelRestarter: restarting kernel (1/5) + +using Nettle + +signature_scheme = "hmac-sha256" +isempty(signature_scheme) && (signature_scheme = "hmac-sha256") +signature_scheme = split(signature_scheme, "-") +if signature_scheme[1] != "hmac" || length(signature_scheme) != 2 + error("unrecognized signature_scheme") +end +global const hmacstate = HMACState(eval(symbol(uppercase(signature_scheme[2]))), + profile["key"]) + + +HMACState(uppercase(signature_scheme[2]), "abcd") +import IJulia +whos(IJulia)