Coder Social home page Coder Social logo

metasoarous / oz Goto Github PK

View Code? Open in Web Editor NEW
821.0 25.0 74.0 27.14 MB

Data visualizations in Clojure and ClojureScript using Vega and Vega-lite

License: Eclipse Public License 1.0

Clojure 57.45% CSS 0.73% HTML 19.31% Shell 7.35% Makefile 0.84% Jupyter Notebook 14.32%
vega vega-lite clojure clojurescript dataviz

oz's Introduction

oz

Great and powerful scientific documents & data visualizations

Clojars Project cljdoc badge


Please use 1.6.0-alpha36 for the most recent stable version of Oz.

For the latest notebook and async data & document processing capabilities, please try out the 2.0.0-alpha5, but note that it may have some bugs still.

Overview

Oz is a data visualization and scientific document processing library for Clojure built around Vega-Lite & Vega.

Vega-Lite & Vega are declarative grammars for describing interactive data visualizations. Of note, they are based on the Grammar of Graphics, which served as the guiding light for the popular R ggplot2 viz library. With Vega & Vega-Lite, we define visualizations by declaratively specifying how attributes of our data map to aesthetic properties of a visualization. Vega-Lite in particular focuses on maximal productivity and leverage for day to day usage (and is the place to start), while Vega (to which Vega-Lite compiles) is ideal for more nuanced control.

About oz specifically...

Oz itself provides:

  • view!: Clojure REPL API for for pushing Vega-Lite & Vega (+ hiccup) data to a browser window over a websocket
  • vega, vega-lite: Reagent component API for dynamic client side ClojureScript apps
  • publish!: create a GitHub gist with Vega-Lite & Vega (+ hiccup), and print a link to visualize it with either the IDL's live vega editor or the ozviz.io
  • load: load markdown, hiccup or Vega/Vega-Lite files (+ combinations) from disk as EDN or JSON
  • export!: write out self-contained html files with live/interactive visualizations embedded
  • oz.notebook.<kernel>: embed Vega-Lite & Vega data (+ hiccup) in Jupyter notebooks via the Clojupyter & IClojure kernels
  • live-reload!: live clj code reloading (à la Figwheel), tuned for data-science hackery (only reruns from first changed form, for a pleasant, performant live-coding experience)
  • live-view!: similar Figwheel-inspired live-view! function for watching and view!ing .md, .edn and .json files with Vega-Lite & Vega (+ (or markdown hiccup))
  • build!: generate a static website from directories of markdown, hiccup &/or interactive Vega-Lite & Vega visualizations, while being able to see changes live (as with live-view!)

Learning Vega, Vega-Lite & Oz

To take full advantage of the data visualization capabilities of Oz, it pays to understanding the core Vega & Vega-Lite. If you're new to the scene, it's worth taking a few minutes to orient yourself with this mindblowing talk/demo from the creators at the Interactive Data Lab (IDL) at University of Washington.

Vega & Vega-Lite talk from IDL

Watched the IDL talk and hungry for more content? Here's another which focuses on the philosophical ideas behind Vega & Vega-Lite, how they relate to Clojure, and how you can use the tools from Clojure using Oz.

Seajure Clojure + Vega/Vega-Lite talk

This Readme is the canonical entry point for learning about Oz. You may also want to check out the cljdoc page (if you're not there already) for API & other docs, and look at the examples directory of this project (references occassionally below).

Ecosystem

Some other things in the Vega/Vega-Lite ecosystem you may want to look at for getting started or learning more

  • Vega Editor - Wonderful editing tool (as mentioned above) for editing and sharing Vega/Vega-Lite data visualizations.
  • Ozviz - Sister project to Oz: A Vega Editor like tool for sharing (and soon editing) hiccup with embedded Vega/Vega-Lite visualizations, as used with the view! function.
  • Voyager - Also from the IDL, Voyager is a wonderful Tableau like (drag and drop) tool for exploring data and constructing exportable Vega/Vega-Lite visualizations.
  • Vega Examples & Vega-Lite Examples - A robust showcase of visualizations from which to draw inspiration and code.
  • Vega home - More great stuff from the IDL folks.

REPL Usage

If you clone this repository and open up the dev/user.clj file, you can follow along by executing the commented out code block at the end of the file.

Assuming you're starting from scratch, first add oz to your leiningen project dependencies

Clojars Project

Next, require oz and start the plot server as follows:

(require '[oz.core :as oz])

(oz/start-server!)

This will fire up a browser window with a websocket connection for funneling view data back and forth. If you forget to call this function, it will be called for you when you create your first plot, but be aware that it will delay the first display, and it's possible you'll have to resend the plot on a slower computer.

Next we'll define a function for generating some dummy data

(defn play-data [& names]
  (for [n names
        i (range 20)]
    {:time i :item n :quantity (+ (Math/pow (* i (count n)) 0.8) (rand-int (count n)))}))

oz/view!

The main function for displaying vega or vega-lite is oz/view!.

For example, a simple line plot:

(def line-plot
  {:data {:values (play-data "monkey" "slipper" "broom")}
   :encoding {:x {:field "time" :type "quantitative"}
              :y {:field "quantity" :type "quantitative"}
              :color {:field "item" :type "nominal"}}
   :mark "line"})

;; Render the plot
(oz/view! line-plot)

Should render something like:

lines plot

Another example:

(def stacked-bar
  {:data {:values (play-data "munchkin" "witch" "dog" "lion" "tiger" "bear")}
   :mark "bar"
   :encoding {:x {:field "time"
                  :type "ordinal"}
              :y {:aggregate "sum"
                  :field "quantity"
                  :type "quantitative"}
              :color {:field "item"
                      :type "nominal"}}})

(oz/view! stacked-bar)

This should render something like:

bars plot

vega support

For vega instead of vega-lite, you can also specify :mode :vega to oz/view!:

;; load some example vega (this may only work from within a checkout of oz; haven't checked)

(require '[cheshire.core :as json])

(def contour-plot (oz/load "examples/contour-lines.vega.json"))
(oz/view! contour-plot :mode :vega)

This should render like:

contours plot

Hiccup

We can also embed Vega-Lite & Vega visualizations within hiccup documents:

(def viz
  [:div
    [:h1 "Look ye and behold"]
    [:p "A couple of small charts"]
    [:div {:style {:display "flex" :flex-direction "row"}}
      [:vega-lite line-plot]
      [:vega-lite stacked-bar]]
    [:p "A wider, more expansive chart"]
    [:vega contour-plot]
    [:h2 "If ever, oh ever a viz there was, the vizard of oz is one because, because, because..."]
    [:p "Because of the wonderful things it does"]])

(oz/view! viz)

Note that the Vega-Lite & Vega specs are described in the output vega as using the :vega and :vega-lite keys.

You should now see something like this:

composite view

Note that vega/vega-lite already have very powerful and impressive plot concatenation features which allow for coupling of interactivity between plots in a viz. However, combing things through hiccup like this is nice for expedience, gives one the ability to combine such visualizations in the context of HTML documents.

Also note that while not illustrated above, you can specify multiple maps in these vectors, and they will be merged into one. So for example, you can do [:vega-lite stacked-bar {:width 100}] to override the width.

As client side reagent components

If you like, you may also use the Reagent components found at oz.core to render vega and/or vega-lite you construct client side.

[:div
 [oz.core/vega { ... }]
 [oz.core/vega-lite { ... }]]

At present, these components do not take a second argument. The merging of spec maps described above applies prior to application of this reagent component.

Eventually we'll be adding options for hooking into the signal dataflow graphs within these visualizations so that interactions in a Vega/Vega-Lite visualization can be used to inform other Reagent components in your app.

Please note that when using oz.core client side, the :data entry in your vega spec map should not be nil (for example you're loading data into a reagent atom which has not been populated yet). Instead prefer an empty sequence () to avoid hard to diagnose errors in the browser.

Loading specs

Oz now features a load function which accepts the following formats:

  • edn, json, yaml: directly parse into hiccup &/or Vega/Vega-Lite representations
  • md: loads a markdown file, with a notation for specifying Vega/Vega-Lite in code blocks tagged with the vega, vega-lite or oz class

As example of the markdown syntax:

# An example markdown file

```edn vega-lite
{:data {:url "data/cars.json"}
 :mark "point"
 :encoding {
   :x {:field "Horsepower", :type "quantitative"}
   :y {:field "Miles_per_Gallon", :type "quantitative"}
   :color {:field "Origin", :type "nominal"}}}
```

The real magic here is in the code class specification edn vega-lite. It's possible to replace edn with json or yaml, and vega with vega-lite as appropriate. Additionally, these classes can be hyphenated for compatibility with editors/parsers that have problems with multiple class specifications (e.g. edn-vega-lite)

Note that embedding all of your data into a vega/vega-lite spec directly as :values may be untenable for larger data sets. In these cases, the recommended solution is to post your data to a GitHub gist, or elsewhere online where you can refer to it using the :url syntax (e.g. {:data {:url "https://your.data.url/path"} ...}).

One final note: in lieue of vega or vega-lite you can specify hiccup in order to embed oz-style hiccup forms which may or may not contain [:vega ...] or [:vega-lite ...] blocks. This allows you to embed nontrivial html in your markdown files as hiccup, when basic markdown just doesn't cut it, without having to resort to manually writing html.

Export

We can also export static HTML files which use Vega-Embed to render interactive Vega/Vega-Lite visualizations using the oz/export! function.

(oz/export! spec "test.html")

Notebook support

Oz now also features Jupyter support for both the Clojupyter and IClojure kernels. See the view! method in the namespaces oz.notebook.clojupyter and oz.notebook.iclojure for usage.

example notebook

Requiring in Clojupyter

Take a look at the example clojupyter notebook.

If you have docker installed you can run the following to build and run a jupyter container with clojupyter installed.

docker run --rm -p 8888:8888 kxxoling/jupyter-clojure-docker

Note that if you get a permission related error, you may need to run this command like sudo docker run ....

Once you have a notebook up and running you can either import the example clojupyter notebook or manually add something like:

(require '[clojupyter.misc.helper :as helper])
(helper/add-dependencies '[metasoarous/oz "x.x.x"])
(require '[oz.notebook.clojupyter :as oz])

;; Create spec

;; then...
(oz/view! spec)

Based on my own tinkering and the reports of other users, the functionality of this integration is somewhat sensitive to version/environment details, so running from the docker image is the recommended way of getting things running for the moment.

Requiring in IClojure

If you have docker installed you can get an IClojure environment up and running using:

docker run -p 8888:8888 cgrand/iclojure

As with Clojupyter, note that if you get a permission related error, you may need to run this command like sudo docker run ....

Once you have that running, you can:

/cp {:deps {metasoarous/oz {:mvn/version "x.x.x"}}}
(require '[oz.notebook.iclojure :as oz])

;; Create spec

;; then...
(oz/view! spec)

Live code reloading

Oz now features Figwheel-like hot code reloading for Clojure-based data science workflows. To start this functionality, you specify from the REPL a file you would like to watch for changes, like so:

(oz/live-reload! "live-reload-test.clj")

As soon as you run this, the code in the file will be executed in its entirety. Thereafter, if you save changes to the file, all forms starting from the first form with material changes will be re-evaluated. Additionally, whitespace changes are ignored, and namespace changes only trigger a recompile if there were other code changes in flight, or if there was an error during the last execution. We also try to do a good job of logging notifications as things are running so that you know what is running and how long things are taking for to execute long-running forms.

Collectively all of these features give you the same magic of Figwheel's hot-code reloading experience, but geared towards the specific demands of a data scientist, or really anyone who needs to quickly hack together potentially long running jobs.

Here's a quick video of this in action: https://www.youtube.com/watch?v=yUTxm29fjT4

Of import: Because the code evaluated with live-reload! is evaluated in a separate thread, you can't include any code which might try to set root bindings of a dynamic var. Fortunately, setting root var bindings isn't something I've ever needed to do in my data science workflow (nor should you), but of course, it's possible there are libraries out there that do this. Just be aware that it might come up. This seems to be a pretty fundamental Clojure limitation, but I'd be interested to hear from the oracles whether there's any chance of this being supported in a future version of Clojure.

There's also a related function, oz/live-view! which will similarly watch a file for changes, oz/load! it, then oz/view! it.

Sharing features

Looking to share your cool plots or hiccup documents with someone? We've got you covered via the publish! utility function.

This will post the plot content to a GitHub Gist, and use the gist uuid to create a vega-editor link which prints to the screen. When you visit the vega-editor link, it will load the gist in question and place the content in the editor. It renders the plot, and updates in real time as you tinker with the code, making it a wonderful yet simple tool for sharing and prototyping.

user=> (oz/publish! stacked-bar)
Gist url: https://gist.github.com/87a5621b0dbec648b2b54f68b3354c3a
Raw gist url: https://api.github.com/gists/87a5621b0dbec648b2b54f68b3354c3a
Vega editor url: https://vega.github.io/editor/#/gist/vega-lite/metasoarous/87a5621b0dbec648b2b54f68b3354c3a/e1d471b5a5619a1f6f94e38b2673feff15056146/vega-viz.json

Following the Vega editor url with take you here (click on image to follow):

vega-editor

As mentioned above, we can also share our hiccup documents/dashboards. Since Vega Editor knows nothing about hiccup, we've created ozviz.io as a tool for loading these documents.

user=> (oz/publish! viz)
Gist url: https://gist.github.com/305fb42fa03e3be2a2c78597b240d30e
Raw gist url: https://api.github.com/gists/305fb42fa03e3be2a2c78597b240d30e
Ozviz url: http://ozviz.io/#/gist/305fb42fa03e3be2a2c78597b240d30e

Try it out: http://ozviz.io/#/gist/305fb42fa03e3be2a2c78597b240d30e

Authentication

In order to use the oz/publish! function, you must provide authentication.

The easiest way is to pass :auth "username:password" to the oz/publish! function. However, this can be problematic in that you don't want these credentials accidentally strewn throughout your code or ./.lein-repl-history.

To address this issue, oz/publish! will by default try to read authorization parameters from a file at ~/.oz/github-creds.edn. The contents should be a map of authorization arguments, as passed to the tentacles api. While you can use {:auth "username:password"} in this file, as above, it's far better from a security standpoint to use OAuth tokens.

  • First, generate a new token (Settings > Developer settings > Personal access tokens):
    • Enter a description like "Oz api token"
    • Select the "[ ] gist" scope checkbox, to grant gisting permissions for this token
    • Click "Generate token" to finish
  • Copy the token and paste place in your ~/.oz/github-creds.edn file as {:oauth-token "xxxxxxxxxxxxxx"}

When you're finished, it's a good idea to run chmod 600 ~/.oz/github-creds.edn so that only your user can read the credential file.

And that's it! Your calls to (oz/publish! spec) should now be authenticated.

Sadly, GitHub used to allow the posting of anonymous gists, without the requirement of authentication, which saved us from all this hassle. However, they've since deprecated this. If you like, you can submit a comment asking that GitHub consider enabling auto-expiring anonymous gists, which would avoid this setup.

Static site generation

If you've ever thought "man, I wish there was a static site generation toolkit which had live code reloading of whatever page you're currently editing, and it would be great if it was in Clojure and let me embed data visualizations and math formulas via LaTeX in Markdown & Hiccup documents", boy, are you in for a treat!

Oz now features exectly such features in the form of the oz/build!. A very simple site might be generated with:

(build!
  [{:from "examples/static-site/src/"
    :to "examples/static-site/build/"}])

The input formats currently supported by oz/build! are

  • md: As described above, markdown with embedded Vega-Lite or Vega visualizations, Latex, and hiccup
  • json, edn: You can directly supply hiccup data for more control over layout and content
  • clj: Will live-reload! Clojure files (as described above), and render the last form evaluated as hiccup

Oz should handle image and css files it comes across by simply copying them over. However, if you have any json or edn assets (datasets perhaps) which need to pass through unchanged, you can separate these into their own build specification, like so:

(defn site-template
  [spec]
  [:div {:style {:max-width 900 :margin-left "auto" :margin-right "auto"}}
   spec])

(build!
  [{:from "examples/static-site/src/site/"
    :to "examples/static-site/build/"
    :template-fn site-template}
   ;; If you have static assets, like datasets or imagines which need to be simply copied over
   {:from "examples/static-site/src/assets/"
    :to "examples/static-site/build/"
    :as-assets? true}])

This can be a good way to separate document code from other static assets.

Specifying multiple builds like this can be used to do other things as well. For example, if you wanted to render a particular set of pages using a different template function (for example, so that your blog posts style differently than the main pages), you can do that easily

(defn blog-template
  [spec]
  (site-template
    (let [{:as spec-meta :keys [title published-at tags]} (meta spec)]
      [:div
       [:h1 {:style {:line-height 1.35}} title]
       [:p "Published on: " published-at]
       [:p "Tags: " (string/join ", " tags)]
       spec])))

(build!
  [{:from "examples/static-site/src/site/"
    :to "examples/static-site/build/"
    :template-fn site-template}
   {:from "examples/static-site/src/blog/"
    :to "examples/static-site/build/blog/"
    :template-fn blog-template}
   ;; If you have static assets, like datasets or imagines which need to be simply copied over
   {:from "examples/static-site/src/assets/"
    :to "examples/static-site/build/"
    :as-assets? true}])

Note that the blog-template above is using metadata about the spec to inform how it renders. This metadata can be written into Markdown files using a yaml markdown metadata header (see /examples/static-site/src/)

---
title: Oz static websites rock
tags: oz, dataviz
---

# Oz static websites!

Some markdown content...

The title in particular here will wind it's way into the Title metadata tag of your output HTML document, and thus will be visible at the top of your browser window when you view the file. This is a pattern that Jekyll and some other blogging engines use, and markdown-clj now supports extracting this data.

Again, as you edit and save these files, the outputs just automatically update for you, both as compiled HTML files, and in the live-view window which lets you see your changes as you make em. If you need to change a template, or some other detail of the specs, you can simply rerun build! with the modified arguments, and the most recently edited page will updated before your eyes. This provides for a lovely live-view editing experience from the comfort of your favorite editor.

When you're done, one of the easiest ways to deploy is with the excellent surge.sh toolkit, which makes static site deployment a breeze. You can also use GitHub Pages or S3 or really whatever if you prefer. The great thing about static sites is that they are easy and cheap to deploy and scale, so you have plenty of options at your disposal.

EDN translation caveats in expression strings

In general, it's pretty easy to translate specs between EDN (Clojure data) and JSON. However, there is one place where you can get a little tripped up if you don't know what to do, and that's in expressions (as used in calculate and filter transforms).

The expression you see in the Vega docs typically look like {"calculate": "datum.attr * 2", "as": "attr2"} (as JSON). However, in Clojure, we often use kebab cased keywords for data map keys (e.g. :cool-attr). For these attributes, you obviously can't use datum.cool-attr, since this will be interpretted as data.cool - attr, and either error out or not produce the desired result. Instead you'll need to use datum['cool-attr'] in your expressions when your keys are kebab cased.

This may be easy to miss, since most of the docs assume that you're working with camel or snake cased keys. It is mentioned somewhere in there if you look, but tends to bite us Clojurists more frequently than practitioners of other languages, and so isn't particularly front and center. Once you know the trick though, you should be on your way.

Local CLJS development

Oz is now compiled (on the cljs side) with Shadow-CLJS, together with the Clojure CLI tooling. A typical workflow involves running clj -M:shadow-cljs watch devcards app (note, older versions of clj use -A instead of -M; consider updating). This will watch your cljs files for changes, and immediately compile both the app.js and devcards.js targets (to resources/oz/public/js/).

In general, the best way to develop is to visit http://localhost:7125/devcards.html, which will pull up a live view of a set of example Reagent components defined at src/cljs/oz/core_devcards.cljs. This is the easiest way to tweak functionality and test new features, as editing src/cljs/oz/core.cljs will trigger updates to the devcards views.

If it's necessary or desirable to test the app (live-view, etc) functionality "in-situ", you can also use the normal Clj REPL utilities to feed plots to the app.js target using oz/view!, etc. Note that if you do this, you will need to use whatever port is passed to oz/view! (by default, 10666) and not the one printed out when you start clj -M:shadow-cljs.

See documentation for your specific editing environment if you'd like your editor to be able to connect to the Shadow-CLJS repl. For vim-fireplace, the initial Clj connection should establish itself automatically when you attempt to evaluate your first form. From there simply execute the vim command :CljEval (shadow/repl :app), and you should be able to evaluate code in the *.cljs files from vim. Code in *.clj files should also continue to evaluate as before as well.

IMPORTANT NOTE: If you end up deploying a version of Oz to Clojars or elsewhere, make sure you stop your clj -M:shadow-cljs watch process before running make release. If you don't, shadow will continue watching files and rebuild js compilation targets with dev time configuration (shadow, less minification, etc), that shouldn't be in the final release build. If however you are simply making changes and pushing up for me to release, please just leave any compiled changes to the js targets out of your commits.

Debugging & updating Vega/Vega-Lite versions

I'm frequently shocked (pleasantly) at how if I find I'm unable to do something in Vega or Vega-Lite that I think I should, updating the Vega or Vega-Lite version fixes the problem. As a side note, I think this speaks volumes of the stellar job (pun intended) the IDL has been doing of developing these tools. More to the point though, if you find yourself unable to do something you expect to be able to do, it's not a bad idea to try

  1. Make sure your Oz version is up to date, in case there's a more recent Vega/Vega-Lite versions required there fix the problem.
  2. Check npm to see if there's a more recent version of the Vega/Vega-Lite (or Vega-Embed or Vega-Tooltip, as appropriate).
  3. Clone Oz, update the package.json file, and attempt to rebuild the Oz as described above.
  4. If this still doesn't solve your problem, file an issue on the appropriate Vega GitHub project. I've found the developers super responsive to issues.

License

Copyright © 2018 Christopher Small

Forked from Vizard (with thanks) - Copyright © 2017 Yieldbot, Inc.

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

oz's People

Contributors

behrica avatar bombaywalla avatar cjbarre avatar danielsbastos avatar folcon avatar harold avatar judepayne avatar jumarko avatar keesterbrugge avatar kovasap avatar mainej avatar mawiraike avatar mdurkovic avatar metasoarous avatar mn-dimension avatar mthomure avatar oubiwann avatar randomizedthinking avatar rschmukler avatar sindreij avatar sorenmacbeth avatar teodorlu avatar wongjoel avatar yubrshen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oz's Issues

oz.core/view! function should treat map inputs as singular vega/vega-lite specs

Right now, the docs emphasize that the v! is to be used for single plots, and view! for composite hiccup+vega/lite documents. However, I don't see any reason that view! shouldn't accept a single map argument which is treats more or less the way v! does (default to :vega-lite with :mode :vega as an optional argument (or ideally reflect on a $schema/mode attribute in the spec?).

Example apps

Would be nice to have a couple of example apps for folks to look at to get started, especially for folks new to Clojure (see @awb99's comment on #14). Perhaps one showing off reagent functionality, and another showing off REPL usage.

More robust export and static rendering

Would be nice to be able to save views as svg and png files, and for the composite views, as html or perhaps with https://github.com/clj-pdf/clj-pdf thrown in the mix, to pdf. Could imagine a grammar around the pdfs that could render nicely as html. Maybe useful for static sites / blogs as well? Embedding in md? Latex? Let's get crazy!

'markdown-to-hiccup.core' not found

Keep getting this exception:
CompilerException java.lang.Exception: namespace 'markdown-to-hiccup.core' not found, compiling:(oz/core.clj:1:1)

I have [markdown-to-hiccup "0.6.2"], [metasoarous/oz "1.5.6"] in my dependencies in project.clj.

File watchers

Could be nice to have a figwheel like thing that watches a file for changes so you can edit data there and watch things update as you change the file.

Publish to named gists

It would be nice to be able to publish to a named github gist under a particular users account, as this would prevent rapid back and forth edits to a document being shared with others from creating dozens of gists, and make it easier to go back and find old visualizations.

This may not be easy though, as for convenience it requires some how keeping track of github credentials, and I'm leary of the potential security risks. But I would love to have this feature so if anyone has any bright ideas on that front, please do share.

Visualize data in a table?

Maybe I'm being dumb, but is there a way to render the data in a simple data, column/row with header, that I can sort, filter, search on, etc. ?

Or can this only do charts?

Example apps

Would be nice to have a couple of example apps folks could play around with to use as either starter templates, or to help folks new to Clojure get things going quickly (see @awb99's comment on #14).

Latex support

Should be able to just add mathjax, but may want to find a way to restrict this to [:latex ] blurbs, or via whatever notation generalizes out of #22 for including vega in markdown documents.

exported document gives vega-lite error

When I export! a document, the resulting html file does not display the vega-lite elements and gives the following error in the console:

Uncaught SyntaxError: Unexpected end of input  [email protected]:1 

I can reproduce this with:

(oz/export! [:div [:h1 "foo"]] "foo.html")

Maybe there is an issue with the vega-lite CDN? Looks like the most recent version is rc12.

Add tools for exporting to various vega tools

I've done a bit of tinkering with this and have some code I'm going to be pulling out and extending upon for pushing vega to their example tool. Would also be worth looking into the group's sister projects for tableau like exploration and viz construction capabilities. Could be cool to have similar export functionality there potentially?

clickable links opening in new tabs.

I am looking to make clickable links in a vega visualisation (href) in oz to open in new tabs. One of the issues listed on vega's github offers a solution (adding some bit of syntax to the loader), but I am at a bit of a loss how to use this in oz.

vega/vega#1171

new vega.View(runtimeSpec, {
  loader: vega.loader({target: '_blank'})
});

Can't export to image file

I checked out oz/export! docstring. It's experimental. But it says support jpg image exporting.
I tried, but get error:

(require '[oz.core :as vega])

(defn group-data [& names]
     (apply concat (for [n names]
                     (map-indexed (fn [i x] {:x i :y x :col n})
                                  (take 20 (repeatedly #(rand-int 100)))))))

(def line-plot
  {:data {:values (group-data "monkey" "slipper" "broom")}
   :encoding {:x {:field "x"}
              :y {:field "y"}
              :color {:field "col" :type "nominal"}}
   :mark "line"})

(vega/export! line-plot "chart.jpg")
(vega/export! line-plot "chart.jpg" {:as :jpg})

I really hope Oz can support image exporting.

Add voyager api?

Looks like there's a client api for http://vega.github.io/voyager/:

https://github.com/vega/voyager/

We could add a voyager component, and corresponding to oz/voyage! REPL command which would bring up the Voyager explorer tool for some collection of data.

Have to think about call style; Maybe want to require passing a map with a data key, so that it would more or less operate the same as the other components with loading from url vs values vs...

Rendering differences between localhost and online vega-editor

Hi.

thanks for picking up vizard and moving it forward.

I have a small example of a world map with a few dots on them. On the plot server, all the dots get plotted in the middle of the screen (just east of Sardinia), while in the vega editor they end up in the correct position (Netherlands). Any idea what is causing the difference?

{ "width": 800, "height": 800, "layer": [ { "data": { "url": "https://gist.githubusercontent.com/almccon/b2d9eaea25b73a16a0ffeb3a2485054c/raw/ce2f85c244be6f0a90f2444080f9d030d1046183/world-50m.json", "format": {"type": "topojson", "feature": "countries"} }, "projection": {"type": "orthographic", "rotate": [-10, -40, 0]}, "mark": {"type": "geoshape", "fill": "lightgrey", "stroke": "white"} }, { "data": { "values": [ {"name": "Baarlo", "lng": "5.95", "lat": "52.73833"}, {"name": "Baarlo", "lng": "6.09444", "lat": "51.33083"}, {"name": "Hoog-Baarlo", "lng": "5.87639", "lat": "52.10917"}, {"name": "Baarlo", "lng": "6.10694", "lat": "52.66333"} ] }, "projection": {"type": "orthographic", "rotate": [-10, -40, 0]}, "mark": "circle", "encoding": { "longitude": {"field": "lng", "type": "quantitative"}, "latitude": {"field": "lat", "type": "quantitative"}, "size": {"value": 10}, "color": {"value": "steelblue"} } } ] }

Compile markdown

It would be lovely to have a simple extension of markdown which lets us embed vega/vega-lite specs, and maybe provides latex support.

License

I didn't see a license file in the project. We are considering using it in Apache (incubating) MXNet.
Could you please clarify?

Does not work with figwheel?

Figwheel and Oz are not playing nicely together for me.

I can reproduce using the figwheel template i.e. lein new figwheel and adding [metasoarous/oz "1.5.6"] to the project.clj

lein figwheel
Figwheel: Cutting some fruit, just a sec ...
Exception in thread "main" java.lang.ExceptionInInitializerError
	at clojure.main.<clinit>(main.java:20)
Caused by: java.lang.ExceptionInInitializerError, compiling:(figwheel_sidecar/repl.clj:1:1)
	at clojure.lang.Compiler.load(Compiler.java:7526)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:1789)
	at figwheel_sidecar.system$eval7178$loading__6434__auto____7179.invoke(system.clj:1)
	at figwheel_sidecar.system$eval7178.invokeStatic(system.clj:1)
	at figwheel_sidecar.system$eval7178.invoke(system.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:930)
	at figwheel_sidecar.repl_api$eval19$loading__6434__auto____20.invoke(repl_api.clj:1)
	at figwheel_sidecar.repl_api$eval19.invokeStatic(repl_api.clj:1)
	at figwheel_sidecar.repl_api$eval19.invoke(repl_api.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:370)
	at clojure.lang.RT.load(RT.java:460)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at user$eval13$loading__6434__auto____14.invoke(user.clj:1)
	at user$eval13.invokeStatic(user.clj:1)
	at user$eval13.invoke(user.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	at clojure.lang.RT.loadResourceScript(RT.java:379)
	at clojure.lang.RT.loadResourceScript(RT.java:366)
	at clojure.lang.RT.maybeLoadResourceScript(RT.java:362)
	at clojure.lang.RT.doInit(RT.java:482)
	at clojure.lang.RT.<clinit>(RT.java:336)
	... 1 more
Caused by: java.lang.ExceptionInInitializerError
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at clojure.lang.RT.classForName(RT.java:2204)
	at clojure.lang.RT.classForName(RT.java:2213)
	at clojure.lang.RT.loadClassForName(RT.java:2232)
	at clojure.lang.RT.load(RT.java:450)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:436)
	at clojure.core.async.impl.channels$loading__5569__auto____2502.invoke(channels.clj:9)
	at clojure.core.async.impl.channels__init.load(Unknown Source)
	at clojure.core.async.impl.channels__init.<clinit>(Unknown Source)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at clojure.lang.RT.classForName(RT.java:2204)
	at clojure.lang.RT.classForName(RT.java:2213)
	at clojure.lang.RT.loadClassForName(RT.java:2232)
	at clojure.lang.RT.load(RT.java:450)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:619)
	at clojure.core.async$loading__5569__auto____2295.invoke(async.clj:9)
	at clojure.core.async__init.load(Unknown Source)
	at clojure.core.async__init.<clinit>(Unknown Source)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at clojure.lang.RT.classForName(RT.java:2204)
	at clojure.lang.RT.classForName(RT.java:2213)
	at clojure.lang.RT.loadClassForName(RT.java:2232)
	at clojure.lang.RT.load(RT.java:450)
	at clojure.lang.RT.load(RT.java:426)
	at clojure.core$load$fn__6548.invoke(core.clj:6046)
	at clojure.core$load.invokeStatic(core.clj:6045)
	at clojure.core$load.doInvoke(core.clj:6029)
	at clojure.lang.RestFn.invoke(RestFn.java:408)
	at clojure.core$load_one.invokeStatic(core.clj:5848)
	at clojure.core$load_one.invoke(core.clj:5843)
	at clojure.core$load_lib$fn__6493.invoke(core.clj:5888)
	at clojure.core$load_lib.invokeStatic(core.clj:5887)
	at clojure.core$load_lib.doInvoke(core.clj:5868)
	at clojure.lang.RestFn.applyTo(RestFn.java:142)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$load_libs.invokeStatic(core.clj:5925)
	at clojure.core$load_libs.doInvoke(core.clj:5909)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.core$apply.invokeStatic(core.clj:659)
	at clojure.core$require.invokeStatic(core.clj:5947)
	at clojure.core$require.doInvoke(core.clj:5947)
	at clojure.lang.RestFn.invoke(RestFn.java:1789)
	at figwheel_sidecar.repl$eval7184$loading__6434__auto____7185.invoke(repl.clj:1)
	at figwheel_sidecar.repl$eval7184.invokeStatic(repl.clj:1)
	at figwheel_sidecar.repl$eval7184.invoke(repl.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7062)
	at clojure.lang.Compiler.eval(Compiler.java:7051)
	at clojure.lang.Compiler.load(Compiler.java:7514)
	... 90 more
Caused by: java.lang.ClassNotFoundException: clojure.core.async.Mutex
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at java.lang.Class.forName0(Native Method)
	at java.lang.Class.forName(Class.java:348)
	at clojure.lang.RT.classForName(RT.java:2204)
	at clojure.lang.RT.classForNameNonLoading(RT.java:2217)
	at clojure.core.async.impl.mutex$loading__5569__auto____2536.invoke(mutex.clj:9)
	at clojure.core.async.impl.mutex__init.load(Unknown Source)
	at clojure.core.async.impl.mutex__init.<clinit>(Unknown Source)
	... 177 more

JSON Support

Should be able to take a natural json translation of the hiccup data for interoperability with other languages.

Bad CSRF Token ?

Hi,

I'm trying out this library and after I launch the plot-server, the network inspector has " Bad CSRF token" as a response in the /chsk websocket route. I'm using an app that has reagent-template with ring defaults and such. Could that be what's causing this? Anyway to pass options to the plot server to have it disable CSRF tokens ?

Provide a Dockerfile enabling mybinder hosting

Thanks for this awesome software !
I'd live to be able to easily share notebooks by having them (freely) hosted on mybinder, as I do with BeakerX Notebooks. I think that it should be possible to provide a Dockerfile that would allow that but I just hacked something just from the one you are using based on the binder documentation without knowing anything about Docker.

So if you could find a way to provide a functional Dockerfile (maybe not even based on my broken proof of concept for mybinder hosting, I think it would lower the barrier to adoption tremendously (the live notebook being just one clic away !

Thx !

Clojupyter support

@mikeyford has worked up some code for plotting from a Jupyter notebook using Clojupyter!

(require '[clojupyter.misc.display :as display])
(require '[clojupyter.misc.helper :as helper])
(helper/add-dependencies '[org.clojure/data.json "0.2.6"])
(require '[clojure.data.json :as json])
(helper/add-javascript "https://cdn.jsdelivr.net/npm//vega-embed@3")

(defn plot-vega [vega-json]
  (let [id (str (java.util.UUID/randomUUID))
        code (format "vegaEmbed('#%s', %s, {'mode': 'vega-lite'});" id, (json/write-str vega-json))]
      (display/hiccup-html 
        [:div [:div {:id id}]
                   [:script code]])))

(plot-vega some-vega)

I'd love to see this go into an oz.clojupyter namespace with functions view! and v! which more or less imitate the API of the oz.core namespace as much as possible (also, take note of #32).

Ideally, we'd do this so as to make clojupyter an optional dependency, wrapping all requires in a (try ... (catch ...)), but I'm actually having some trouble figuring out how to do this. So for now, let's just assume clojupyter will be a dependency and see if we can't relax this assumption down the road.

clojure 1.9.0 support

Hello! Thx for your work, can you provide clojure 1.9.0 support?
This is my dependencies for project.clj

:dependencies [[org.clojure/clojure "1.9.0"]
               [metasoarous/oz "1.1.1"]]

When I started repl, this error occurs

#error {
 :cause Call to clojure.core/refer-clojure did not conform to spec:
In: [2 1] val: :as fails at: [:args :exclude :op :quoted-spec :spec] predicate: #{:exclude}
In: [2 1] val: :as fails at: [:args :only :op :quoted-spec :spec] predicate: #{:only}
In: [2 1] val: :as fails at: [:args :rename :op :quoted-spec :spec] predicate: #{:rename}
In: [2] val: (quote :as) fails at: [:args :exclude :op :spec] predicate: #{:exclude}
In: [2] val: (quote :as) fails at: [:args :only :op :spec] predicate: #{:only}
In: [2] val: (quote :as) fails at: [:args :rename :op :spec] predicate: #{:rename}

 :data #:clojure.spec.alpha{:problems ({:path [:args :exclude :op :spec], :pred #{:exclude}, :val (quote :as), :via [], :in [2]} {:path [:args :exclude :op :quoted-spec :spec], :pred #{:exclude}, :val :as, :via [], :in [2 1]} {:path [:args :only :op :spec], :pred #{:only}, :val (quote :as), :via [], :in [2]} {:path [:args :only :op :quoted-spec :spec], :pred #{:only}, :val :as, :via [], :in [2 1]} {:path [:args :rename :op :spec], :pred #{:rename}, :val (quote :as), :via [], :in [2]} {:path [:args :rename :op :quoted-spec :spec], :pred #{:rename}, :val :as, :via [], :in [2 1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x303fbc4 clojure.spec.alpha$regex_spec_impl$reify__2436@303fbc4], :value ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core)), :args ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core))}
 :via
 [{:type clojure.lang.Compiler$CompilerException
   :message clojure.lang.ExceptionInfo: Call to clojure.core/refer-clojure did not conform to spec:
In: [2 1] val: :as fails at: [:args :exclude :op :quoted-spec :spec] predicate: #{:exclude}
In: [2 1] val: :as fails at: [:args :only :op :quoted-spec :spec] predicate: #{:only}
In: [2 1] val: :as fails at: [:args :rename :op :quoted-spec :spec] predicate: #{:rename}
In: [2] val: (quote :as) fails at: [:args :exclude :op :spec] predicate: #{:exclude}
In: [2] val: (quote :as) fails at: [:args :only :op :spec] predicate: #{:only}
In: [2] val: (quote :as) fails at: [:args :rename :op :spec] predicate: #{:rename}
 #:clojure.spec.alpha{:problems ({:path [:args :exclude :op :spec], :pred #{:exclude}, :val (quote :as), :via [], :in [2]} {:path [:args :exclude :op :quoted-spec :spec], :pred #{:exclude}, :val :as, :via [], :in [2 1]} {:path [:args :only :op :spec], :pred #{:only}, :val (quote :as), :via [], :in [2]} {:path [:args :only :op :quoted-spec :spec], :pred #{:only}, :val :as, :via [], :in [2 1]} {:path [:args :rename :op :spec], :pred #{:rename}, :val (quote :as), :via [], :in [2]} {:path [:args :rename :op :quoted-spec :spec], :pred #{:rename}, :val :as, :via [], :in [2 1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x303fbc4 "clojure.spec.alpha$regex_spec_impl$reify__2436@303fbc4"], :value ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core)), :args ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core))}, compiling:(clojure/core/async.clj:9:1)
   :at [clojure.lang.Compiler checkSpecs Compiler.java 6891]}
  {:type clojure.lang.ExceptionInfo
   :message Call to clojure.core/refer-clojure did not conform to spec:
In: [2 1] val: :as fails at: [:args :exclude :op :quoted-spec :spec] predicate: #{:exclude}
In: [2 1] val: :as fails at: [:args :only :op :quoted-spec :spec] predicate: #{:only}
In: [2 1] val: :as fails at: [:args :rename :op :quoted-spec :spec] predicate: #{:rename}
In: [2] val: (quote :as) fails at: [:args :exclude :op :spec] predicate: #{:exclude}
In: [2] val: (quote :as) fails at: [:args :only :op :spec] predicate: #{:only}
In: [2] val: (quote :as) fails at: [:args :rename :op :spec] predicate: #{:rename}

   :data #:clojure.spec.alpha{:problems ({:path [:args :exclude :op :spec], :pred #{:exclude}, :val (quote :as), :via [], :in [2]} {:path [:args :exclude :op :quoted-spec :spec], :pred #{:exclude}, :val :as, :via [], :in [2 1]} {:path [:args :only :op :spec], :pred #{:only}, :val (quote :as), :via [], :in [2]} {:path [:args :only :op :quoted-spec :spec], :pred #{:only}, :val :as, :via [], :in [2 1]} {:path [:args :rename :op :spec], :pred #{:rename}, :val (quote :as), :via [], :in [2]} {:path [:args :rename :op :quoted-spec :spec], :pred #{:rename}, :val :as, :via [], :in [2 1]}), :spec #object[clojure.spec.alpha$regex_spec_impl$reify__2436 0x303fbc4 clojure.spec.alpha$regex_spec_impl$reify__2436@303fbc4], :value ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core)), :args ((quote :exclude) (quote [reduce into merge map take partition partition-by]) (quote :as) (quote core))}
   :at [clojure.core$ex_info invokeStatic core.clj 4739]}]

With clojure 1.8.0 all things works well

Tooltips don't seem to work on vega

I checked the vega implementation, and it works (all in all), but the tooltips don't seem to be working. Will have to figure out how they're supposed to install differently for vega vs vega-lite.

Embed pre-rendered SVG in document for notebooks (and maybe md?)

I've realized looking at some of the output from folks trying to use Oz in Jupyter notebooks (specifically @cnuernber in this kaggle project; manually amended here), that it's unfortunate that visualizations don't show up in statically compiled output. This even extends to when you first open up a Jupyter notebook with Oz visualizations embedded presently (see the clojupyter example notebook.

However, it's interesting to note that what Nextjournal currently does with its plotly visualizations is directly embed into the md, so that they are eminently visible when put on GH or whatever. I think with Jupyter we could maybe do something similar, in that instead of attaching the Vega-View api to a blank div, we could attach it to a div containing a compiled SVG, so that in static exports (or on a fresh boot of a notebook) you still get something to look at. You would have to re-evaluate cells to activate the Vega/Vega-Lite interactive features, but at least you wouldn't be confusedly staring at a blank page on boot. This may ultimately depend on getting #36 in place.

This is somewhat true of Oz markdown files as well (see for instance https://github.com/metasoarous/oz/blob/master/examples/test.md). This is a little less of an issue, as it's assumed here (for the most part) that you're going to be compiling this md to html, and can then view the interactive visualization from there. But it would be nice if there was an option to "insert" compiled SVG into the md document, so that the visualizations could be viewed on a github page (e.g.). Or again, that in html mode, it start with a static version, which then gets replaced with the live/interactive viz once the page finishes booting up.

Error in IClojure notebook

I'm running IClojure on OSX Mojave in the official docker container, with the command: docker run -p 8888:8888 cgrand/iclojure.

I got the following error when trying to require oz.notebook.iclojure namespace:

Exception while doing something unexpected.
#unrepl/browsable
  [#error
     {:via [{:type clojure.lang.Compiler$CompilerException,
             :message
               "Syntax error compiling deftype* at (flatland/ordered/set.clj:12:1).",
             :data {:clojure.error/phase :compile-syntax-check,
                    :clojure.error/line 12, :clojure.error/column 1,
                    :clojure.error/source "flatland/ordered/set.clj",
                    :clojure.error/symbol deftype*},
             :at [clojure.lang.Compiler analyzeSeq "Compiler.java" 7114]}
            {:type java.lang.IllegalArgumentException,
             :message "Must hint overloaded method: toArray",
             :at [clojure.lang.Compiler$NewInstanceMethod
                  parse "Compiler.java" 8496]}],
      :trace [[clojure.lang.Compiler$NewInstanceMethod
               parse "Compiler.java" 8496]
              [clojure.lang.Compiler$NewInstanceExpr build "Compiler.java" 8058]
              [clojure.lang.Compiler$NewInstanceExpr$DeftypeParser
               parse "Compiler.java" 7934]
              [clojure.lang.Compiler analyzeSeq "Compiler.java" 7106]
              [clojure.lang.Compiler analyze "Compiler.java" 6789]
              [clojure.lang.Compiler analyze "Compiler.java" 6745]
              [clojure.lang.Compiler$BodyExpr$Parser parse "Compiler.java" 6118]
              [clojure.lang.Compiler$LetExpr$Parser parse /8] /9],
      /10}
   /11]

image

This is the output of the docker container:

iclojure.log

How much interest in making this a consumer-facing lib?

Thanks for the excellent library!

It looks like a great dev time / internal tool. What are the steps necessary to make this something that can drive visualizations inside webapps (competing with something like recharts), and what is your level of interest in that direction?

allow for selecting the svg renderer in interactive mode?

It looks like the default renderer of oz is "canvas", but if I want to put text on the screen that can be copy-pasted, I need the svg renderer. Is it possible to have the renderer option also be available in v!. Right now it looks like I can only pass the mode option (vega or vega-lite~) to v!

Static export options

Leftover from #5: Right now we have live-embed html output support via export! but would like to add support for svg output, from which we should be able to tackle jpg or png, as well as pdf (or even just html without the need for js).

Conflicting public/index.html found in classpath

I ran into an odd issue when trying out oz with the Clojure CLI. The server seems to start fine:

0% cat deps.edn
{:deps {metasoarous/oz {:mvn/version "1.5.2"}}}
0% clj
Clojure 1.10.0
user=> (require '[oz.core :as oz])
nil
user=> (oz/start-plot-server!)
19-02-01 20:48:25 xxx INFO [oz.server:113] - Web server is running at `http://localhost:10666/`
nil

But when a browser window opens, there's an error in the Javascript console:

js-error

The above works with Leiningen though.

Clojupyter oz/view! not working in jupyter lab

Hey,

Thanks a lot for Oz!

I tried to run this:

(require '[oz.notebook.clojupyter :as oz])
(def chart
    {:data {:values (for [i (range 20)] {:time i :quantity i :item "John"})}
     :encoding {:x {:field "time"}
                :y {:field "quantity"}
                :color {:field "item" :type "nominal"}}
     :mark "line"}
)
(oz/view! chart)

And all I get is an empty line. I don't see a chart at all.

The same example worked with IClojure.

Thanks

Org-mode

Adding org support as an option (in addition to existing markdown) would be useful.

Add {signal,data,event,resize} listener options reagent components

As in Similar to onSignalXXX hooks of react-vega. This means getting into some of vega's stateful bits, so may be a nice opportunity to look at dataflow in and out, and how to optimize against rerendering and vega compilation. Ideally, we'd have a really high level way of plugging into signals at different levels.

fail to lein run

When I lein run (2.8.3, with the cider 0.20.0) in the root directory of the project on OSX 10.14.2, some errors were caught as follows:

Warning: implicit middleware found: cider-nrepl.plugin/middleware 
Please declare all middleware in :middleware as implicit loading is deprecated.
Compiling ClojureScript...
Compiling "resources/public/js/compiled/oz.js" from ["src/cljs"]...
Compiling "resources/public/js/compiled/oz.js" failed.
clojure.lang.ExceptionInfo: failed compiling file:/Users/x/oz/target/cljsbuild-compiler-1/cljs/core.cljs {:file #object[java.io.File 0x466afa50 "/Users/x/oz/target/cljsbuild-compiler-1/cljs/core.cljs"]}
	at cljs.compiler$compile_file$fn__3822.invoke(compiler.cljc:1705)
	at cljs.compiler$compile_file.invokeStatic(compiler.cljc:1665)
	at cljs.compiler$compile_file.invoke(compiler.cljc:1641)
	at cljs.closure$compile_file.invokeStatic(closure.clj:647)
	at cljs.closure$compile_file.invoke(closure.clj:625)
	at cljs.closure$fn__5294.invokeStatic(closure.clj:721)
	at cljs.closure$fn__5294.invoke(closure.clj:715)
	at cljs.closure$fn__5207$G__5200__5214.invoke(closure.clj:543)
	at cljs.closure$compile_from_jar.invokeStatic(closure.clj:694)
	at cljs.closure$compile_from_jar.invoke(closure.clj:684)
	at cljs.closure$fn__5298.invokeStatic(closure.clj:731)
	at cljs.closure$fn__5298.invoke(closure.clj:715)
	at cljs.closure$fn__5207$G__5200__5214.invoke(closure.clj:543)
	at cljs.closure$compile_sources$iter__5420__5424$fn__5425.invoke(closure.clj:1081)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.core$seq__5387.invokeStatic(core.clj:137)
	at clojure.core$dorun.invokeStatic(core.clj:3133)
	at clojure.core$doall.invokeStatic(core.clj:3148)
	at clojure.core$doall.invoke(core.clj:3148)
	at cljs.closure$compile_sources.invokeStatic(closure.clj:1077)
	at cljs.closure$compile_sources.invoke(closure.clj:1066)
	at cljs.closure$build.invokeStatic(closure.clj:2995)
	at cljs.closure$build.invoke(closure.clj:2903)
	at cljs.build.api$build.invokeStatic(api.clj:208)
	at cljs.build.api$build.invoke(api.clj:189)
	at cljs.build.api$build.invokeStatic(api.clj:195)
	at cljs.build.api$build.invoke(api.clj:189)
	at cljsbuild.compiler$compile_cljs$fn__33943.invoke(compiler.clj:66)
	at cljsbuild.compiler$compile_cljs.invokeStatic(compiler.clj:65)
	at cljsbuild.compiler$compile_cljs.invoke(compiler.clj:54)
	at cljsbuild.compiler$run_compiler.invokeStatic(compiler.clj:165)
	at cljsbuild.compiler$run_compiler.invoke(compiler.clj:126)
	at user$eval34061$iter__34109__34113$fn__34114$fn__34140.invoke(form-init4346494109930367496.clj:1)
	at user$eval34061$iter__34109__34113$fn__34114.invoke(form-init4346494109930367496.clj:1)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.RT.seq(RT.java:531)
	at clojure.core$seq__5387.invokeStatic(core.clj:137)
	at clojure.core$dorun.invokeStatic(core.clj:3133)
	at clojure.core$doall.invokeStatic(core.clj:3148)
	at clojure.core$doall.invoke(core.clj:3148)
	at user$eval34061.invokeStatic(form-init4346494109930367496.clj:1)
	at user$eval34061.invoke(form-init4346494109930367496.clj:1)
	at clojure.lang.Compiler.eval(Compiler.java:7176)
	at clojure.lang.Compiler.eval(Compiler.java:7166)
	at clojure.lang.Compiler.load(Compiler.java:7635)
	at clojure.lang.Compiler.loadFile(Compiler.java:7573)
	at clojure.main$load_script.invokeStatic(main.clj:452)
	at clojure.main$init_opt.invokeStatic(main.clj:454)
	at clojure.main$init_opt.invoke(main.clj:454)
	at clojure.main$initialize.invokeStatic(main.clj:485)
	at clojure.main$null_opt.invokeStatic(main.clj:519)
	at clojure.main$null_opt.invoke(main.clj:516)
	at clojure.main$main.invokeStatic(main.clj:598)
	at clojure.main$main.doInvoke(main.clj:561)
	at clojure.lang.RestFn.applyTo(RestFn.java:137)
	at clojure.lang.Var.applyTo(Var.java:705)
	at clojure.main.main(main.java:37)
Caused by: clojure.lang.ExceptionInfo: No reader function for tag Inf {:type :reader-exception, :line 999, :column 14, :file "file:/Users/x/.m2/repository/org/clojure/clojurescript/1.10.439/clojurescript-1.10.439.jar!/cljs/core.cljs"}
	at clojure.tools.reader.reader_types$reader_error.invokeStatic(reader_types.clj:331)
	at clojure.tools.reader.reader_types$reader_error.doInvoke(reader_types.clj:327)
	at clojure.lang.RestFn.invoke(RestFn.java:439)
	at clojure.tools.reader$read_tagged.invokeStatic(reader.clj:824)
	at clojure.tools.reader$read_tagged.invoke(reader.clj:811)
	at clojure.tools.reader$read_dispatch.invokeStatic(reader.clj:71)
	at clojure.tools.reader$read_dispatch.invoke(reader.clj:66)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:889)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:872)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read_tagged.invokeStatic(reader.clj:812)
	at clojure.tools.reader$read_tagged.invoke(reader.clj:811)
	at clojure.tools.reader$read_dispatch.invokeStatic(reader.clj:71)
	at clojure.tools.reader$read_dispatch.invoke(reader.clj:66)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:889)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read_delimited.invokeStatic(reader.clj:195)
	at clojure.tools.reader$read_delimited.invoke(reader.clj:188)
	at clojure.tools.reader$read_list.invokeStatic(reader.clj:208)
	at clojure.tools.reader$read_list.invoke(reader.clj:204)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:889)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read_delimited.invokeStatic(reader.clj:195)
	at clojure.tools.reader$read_delimited.invoke(reader.clj:188)
	at clojure.tools.reader$read_list.invokeStatic(reader.clj:208)
	at clojure.tools.reader$read_list.invoke(reader.clj:204)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:889)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read_delimited.invokeStatic(reader.clj:195)
	at clojure.tools.reader$read_delimited.invoke(reader.clj:188)
	at clojure.tools.reader$read_list.invokeStatic(reader.clj:208)
	at clojure.tools.reader$read_list.invoke(reader.clj:204)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:889)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read_delimited.invokeStatic(reader.clj:195)
	at clojure.tools.reader$read_delimited.invoke(reader.clj:188)
	at clojure.tools.reader$read_list.invokeStatic(reader.clj:208)
	at clojure.tools.reader$read_list.invoke(reader.clj:204)
	at clojure.tools.reader$read_STAR_.invokeStatic(reader.clj:889)
	at clojure.tools.reader$read_STAR_.invoke(reader.clj:870)
	at clojure.tools.reader$read.invokeStatic(reader.clj:938)
	at clojure.tools.reader$read.invoke(reader.clj:915)
	at cljs.analyzer$forms_seq_STAR_$forms_seq___2593$fn__2594$fn__2595.invoke(analyzer.cljc:3938)
	at cljs.analyzer$forms_seq_STAR_$forms_seq___2593$fn__2594.invoke(analyzer.cljc:3931)
	at clojure.lang.LazySeq.sval(LazySeq.java:42)
	at clojure.lang.LazySeq.seq(LazySeq.java:51)
	at clojure.lang.Cons.next(Cons.java:39)
	at clojure.lang.RT.next(RT.java:709)
	at clojure.core$next__5371.invokeStatic(core.clj:64)
	at clojure.core$next__5371.invoke(core.clj:64)
	at cljs.analyzer$analyze_file$fn__2699.invoke(analyzer.cljc:4388)
	at cljs.analyzer$analyze_file.invokeStatic(analyzer.cljc:4374)
	at cljs.analyzer$analyze_file.invoke(analyzer.cljc:4332)
	at cljs.analyzer$analyze_file.invokeStatic(analyzer.cljc:4346)
	at cljs.analyzer$analyze_file.invoke(analyzer.cljc:4332)
	at cljs.compiler$with_core_cljs.invokeStatic(compiler.cljc:1415)
	at cljs.compiler$with_core_cljs.invoke(compiler.cljc:1405)
	at cljs.compiler$compile_file_STAR_.invokeStatic(compiler.cljc:1592)
	at cljs.compiler$compile_file_STAR_.invoke(compiler.cljc:1585)
	at cljs.compiler$compile_file$fn__3822.invoke(compiler.cljc:1690)
	... 59 more

I don't know how to resolve this.

Clarify how to get started

When I create a simple project and run (oz/v! line-plot) in the REPL, it fails with

ERROR [oz.core:34] - error sending plot to server: nil

while I expect it to open a browser window with the plot. What did I do wrong? Perhaps I need to manually access some URL first?

Thank you!

Add ability to layer vega / vega-lite on top of eachother

It seems like it should be possible to pass vega and vega lite descriptions to the client, use the js lib to parse the vega-lite into vega, and then merge everything together, so that you could do nitty gritty tuning with vega, but take care of the trivial bits using vega-lite.

Ideally, we'd just find a way of translating vega-lite to vega on the server, but I'm not sure how best to do that short of using the socket to request translations from the js/browser, or call out to a js/node process. Perhaps we can load vega in nashorn?

Short of that, perhaps we just pass along both descriptions to the browser and have it merge them together. But this would provide much less flexibility that being able to just translate vega-lite to vega and build upon that description.

(oz/v!) of boxplot of 60K elements takes over 10m to render

Hi, @metasoarous — 

As you know, I absolutely love oz. :) But for months, I've had to resort to using incanter to generate boxplots, because it required over 10m between the moment I called (oz/v!) and the graph being rendered in the browser.

I've always assumed that this was a limitation in vega-lite, but on a whim, I finally copied the JSON into the vega-lite editor. And it rendered in milliseconds! So the problem is in oz, not in vega-lite. (I'm kicking myself for not trying this earlier.)

Enclosed is the screenshot of the vega-lite editor, and you can find the EDN file that generates the long render time here:

https://gist.githubusercontent.com/realgenekim/cfd961575d3f171c5b8f5b0fbd5a3ce0/raw/301b30a2094c14bec7118a68ed09a21e11d2f234/ozout.edn

Any thoughts on how to speed up the render? THANK YOU!

vega_editor

PS: I'm starting to look at the oz source to better understand how it works, because I still want to try generating static PNG or SVG files. But I suspect you could more quickly diagnose what is causing the long render times. :)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.