Skip to content
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 JavaScript output and save static PNG as well in the notebook #32

Merged
merged 14 commits into from
May 19, 2016

Conversation

ellisonbg
Copy link
Contributor

This is a work in progress...

This PR changes how output is rendered in Jupyter notebooks. The goal here is get ipyvega to work in the following three contexts:

  • Live notebook as real JavaScript
  • Nbviewer as PNG (and eventally JavaScript)
  • GitHub as PNG

This is accomplished by having a JavaScript output that works on the live notebook and having that JS also save a static PNG in the notebook output that will be used on GitHub and nbviewer.

@ellisonbg
Copy link
Contributor Author

Here is example of the output that this PR will produce.

First, it sends an HTML output to the notebook with an empty div that will be used for the rendering and some inline CSS:

<div class="vega-embed" id="cea3f6f3-620b-4c66-8372-60aa3a9a296e"></div>

<style>
.vega-embed svg, .vega-embed canvas {
  border: 1px dotted gray;
}

.vega-embed .vega-actions a {
  margin-right: 6px;
}
</style>

Then it sends a JavaScript output that requires the nbextension and then renders the visualization into the div:

var spec = {"data": {"values": [{"b": 28, "a": "A"}, {"b": 55, "a": "B"}, {"b": 43, "a": "C"}, {"b": 91, "a": "D"}, {"b": 81, "a": "E"}, {"b": 53, "a": "F"}, {"b": 19, "a": "G"}, {"b": 87, "a": "H"}, {"b": 52, "a": "I"}]}, "encoding": {"x": {"type": "ordinal", "field": "a"}, "y": {"type": "quantitative", "field": "b"}}, "mark": "bar", "config": {"cell": {"width": 500, "height": 350}}};
var selector = "#cea3f6f3-620b-4c66-8372-60aa3a9a296e";
var type = "vega-lite";

var output_area = this;

require(['nbextensions/vega/index'], function(vega) {
  vega.render(selector, spec, type, output_area);
});

if type(df.index) == pd.core.index.MultiIndex:
raise ValueError('Hierarchical indices not supported')
if type(df.columns) == pd.core.index.MultiIndex:
raise ValueError('Hierarchical indices not supported')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use an isinstance check here to catch derived classes as well.

@jakevdp
Copy link
Contributor

jakevdp commented May 17, 2016

Looks really good so far! I like the approach to defaults here. I've not yet had a chance to download and see the vega display in action, but I'll do that when you think it's ready.

<div class="vega-embed" data-type="vega-lite">{spec}</div>
<div class="vega-embed" id="{id}"></div>

<style>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you do this so that we don't need the js file for styling?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seemed a tad simpler, but I don't mind going back...

On Tue, May 17, 2016 at 3:41 PM, Dominik Moritz notifications@github.com
wrote:

In vega/static/vega-lite.html
#32 (comment):

@@ -1 +1,11 @@
-

{spec}

+

+
+<style>

Did you do this so that we don't need the js file for styling?


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
https://github.com/vega/ipyvega/pull/32/files/8fe2e0feea95138022e230298c9c4f4efbf0252e#r63617094

Brian E. Granger
Associate Professor of Physics and Data Science
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it totally makes sense for github where we can't use js.

@ellisonbg
Copy link
Contributor Author

Jake, can you successfully run "npm install && npm run build"? I still
can't do that and it is preventing me from actually seeing the
visualizations...

On Tue, May 17, 2016 at 3:32 PM, Jake Vanderplas notifications@github.com
wrote:

Looks really good so far! I like the approach to defaults here. I've not
yet had a chance to download and see the vega display in action, but I'll
do that when you think it's ready.


You are receiving this because you authored the thread.
Reply to this email directly or view it on GitHub
#32 (comment)

Brian E. Granger
Associate Professor of Physics and Data Science
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com


import pandas as pd


def update(d, u):
"""Update dictionary."""
def update(d, u, overwrite=True):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of adding this option, why don't we just reorder the arguments in the call?

@jakevdp
Copy link
Contributor

jakevdp commented May 17, 2016

Jake, can you successfully run "npm install && npm run build"? I still can't do that and it is preventing me from actually seeing the visualizations...

I'm getting the same error as Brian: Cannot resolve module 'd3'...

@ellisonbg
Copy link
Contributor Author

OK, I think the code should work now, but I am still stuck at building the webpack bundle.

console.log(spec);
console.log(selector);
console.log(type);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean for these to stay in here? Some sort of permanent console message might actually be useful to keep.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are really helpful for now. I agree that some message in the console
would be helpful, but I probably wouldn't log the entire spec with the data
though. Maybe just the non-data part of the spec?

On Tue, May 17, 2016 at 4:20 PM, Jake Vanderplas notifications@github.com
wrote:

In vega/static/vega-lite.js
#32 (comment):

@@ -2,6 +2,10 @@ var spec = {spec};
var selector = "{selector}";
var type = "{type}";

+console.log(spec);
+console.log(selector);
+console.log(type);
+

Do you mean for these to stay in here? Some sort of permanent console
message might actually be useful to keep.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
https://github.com/vega/ipyvega/pull/32/files/d4541c081fe73ace7618ab34c6921f85cc63d86b..15e0a6819c108f72b5ef3dc647846de13bea216d#r63621113

Brian E. Granger
Associate Professor of Physics and Data Science
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typically all console.log statements should be removed. I think they are only relevant for debugging.

@ellisonbg
Copy link
Contributor Author

@domoritz hmmm, I am looking at the build problems. The vega-embed package.json only lists d3 and friends as dev dependencies, so they are not included when vega-embed is used in a different project like this. How is vega-embed being resolved on your system? Do you have a dev/source build of it?

@ellisonbg
Copy link
Contributor Author

I am making progress by just adding js to the list of dependencies in ipyvega. Almost have it working now...

@domoritz
Copy link
Member

I use vega-embed form npm (nothing special). Yes, vega-embed does not require d3 directly, but vega does. See https://github.com/vega/vega/blob/master/package.json#L39

When you install d3 manually (npm install d3), does it solve your problem?

@domoritz
Copy link
Member

This is the content of my node_modules directory:

Base64
JSONStream
acorn
align-text
alphanum-sort
amdefine
ansi-regex
ansi-styles
anymatch
argparse
arr-diff
arr-flatten
array-filter
array-map
array-reduce
array-unique
arrify
asap
asn1
asn1.js
assert
assert-plus
ast-types
astw
async
async-each
autoprefixer
aws-sign2
aws4
balanced-match
base62
base64-js
big.js
binary-extensions
bl
bn.js
boom
brace-expansion
braces
brfs
brorand
browser-pack
browser-resolve
browserify
browserify-aes
browserify-cipher
browserify-des
browserify-rsa
browserify-sign
browserify-zlib
browserslist
buffer
buffer-equal
buffer-xor
builtin-modules
builtin-status-codes
camelcase
caniuse-db
canvas
caseless
center-align
chalk
chokidar
cipher-base
clap
cliui
clone
coa
code-point-at
color
color-convert
color-name
color-string
colormin
colors
combine-source-map
combined-stream
commander
commoner
concat-map
concat-stream
console-browserify
constants-browserify
convert-source-map
core-util-is
create-ecdh
create-hash
create-hmac
cryptiles
crypto-browserify
css-color-names
css-loader
css-selector-tokenizer
cssesc
cssnano
csso
d3
d3-cloud
d3-dispatch
d3-dsv
d3-format
d3-geo-projection
d3-queue
d3-time
d3-time-format
dashdash
datalib
date-now
decamelize
defined
delayed-stream
deps-sort
des.js
detective
diffie-hellman
domain-browser
duplexer2
ecc-jsbn
elliptic
emojis-list
enhanced-resolve
errno
error-ex
escape-string-regexp
escodegen
esprima
estraverse
esutils
events
evp_bytestokey
exorcist
expand-brackets
expand-range
extend
extglob
extsprintf
falafel
fastparse
filename-regex
fill-range
find-up
flatten
for-in
for-own
foreach
forever-agent
form-data
fsevents
function-bind
generate-function
generate-object-property
getpass
glob
glob-base
glob-parent
graceful-fs
graceful-readlink
har-validator
has
has-ansi
has-flag
has-own
hash.js
hawk
hoek
hosted-git-info
html-comment-regex
htmlescape
http-basic
http-browserify
http-response-object
http-signature
https-browserify
iconv-lite
icss-replace-symbols
ieee754
indexes-of
indexof
inflight
inherits
inline-source-map
insert-module-globals
interpret
invert-kv
is-absolute-url
is-arrayish
is-binary-path
is-buffer
is-builtin-module
is-dotfile
is-equal-shallow
is-extendable
is-extglob
is-fullwidth-code-point
is-glob
is-my-json-valid
is-number
is-plain-obj
is-posix-bracket
is-primitive
is-property
is-svg
is-typedarray
is-utf8
isarray
isobject
isstream
jodid25519
js-base64
js-yaml
jsbn
json-schema
json-stable-stringify
json-stringify-safe
json5
jsonify
jsonparse
jsonpointer
jsprim
jstransform
kind-of
labeled-stream-splicer
lazy-cache
lcid
lexical-scope
load-json-file
loader-utils
lodash._createcompounder
lodash._root
lodash.assign
lodash.camelcase
lodash.deburr
lodash.keys
lodash.memoize
lodash.rest
lodash.words
longest
memory-fs
micromatch
miller-rabin
mime-db
mime-types
minimalistic-assert
minimatch
minimist
mkdirp
module-deps
mold-source-map
nan
nave
node-libs-browser
node-uuid
normalize-package-data
normalize-path
normalize-range
normalize-url
num2fraction
number-is-nan
oauth-sign
object-assign
object-inspect
object-keys
object.omit
once
optimist
os-browserify
os-locale
os-shim
outpipe
pako
parents
parse-asn1
parse-glob
parse-json
path-browserify
path-exists
path-is-absolute
path-platform
path-type
pbkdf2
pbkdf2-compat
pify
pinkie
pinkie-promise
pkg-conf
postcss
postcss-calc
postcss-colormin
postcss-convert-values
postcss-discard-comments
postcss-discard-duplicates
postcss-discard-empty
postcss-discard-unused
postcss-filter-plugins
postcss-merge-idents
postcss-merge-longhand
postcss-merge-rules
postcss-message-helpers
postcss-minify-font-values
postcss-minify-gradients
postcss-minify-params
postcss-minify-selectors
postcss-modules-extract-imports
postcss-modules-local-by-default
postcss-modules-scope
postcss-modules-values
postcss-normalize-charset
postcss-normalize-url
postcss-ordered-values
postcss-reduce-idents
postcss-reduce-transforms
postcss-selector-parser
postcss-svgo
postcss-unique-selectors
postcss-value-parser
postcss-zindex
prepend-http
preserve
private
process
process-nextick-args
promise
prr
public-encrypt
punycode
q
qs
query-string
querystring
querystring-es3
quote-stream
randomatic
randombytes
read-only-stream
read-pkg
read-pkg-up
readable-stream
readdirp
recast
reduce-css-calc
reduce-function-call
regex-cache
repeat-element
repeat-string
request
require-main-filename
resolve
right-align
ripemd160
rw
sax
semver
set-blocking
sha.js
shallow-copy
shapefile
shasum
shell-quote
sntp
sort-keys
source-list-map
source-map
spawn-sync
spdx-correct
spdx-exceptions
spdx-expression-parse
spdx-license-ids
sprintf-js
sshpk
static-eval
static-module
stream-browserify
stream-combiner2
stream-http
stream-splicer
strict-uri-encode
string-width
string_decoder
stringstream
strip-ansi
strip-bom
style-loader
subarg
supports-color
svgo
symbol
sync-request
syntax-error
tapable
then-request
through
through2
timers-browserify
to-arraybuffer
topojson
tough-cookie
tty-browserify
tunnel-agent
tweetnacl
typedarray
uglify-js
uglify-to-browserify
umd
uniq
uniqid
uniqs
url
util
util-deprecate
validate-npm-package-license
vega
vega-dataflow
vega-embed
vega-event-selector
vega-expression
vega-lite
vega-logging
vega-scenegraph
verror
vm-browserify
watchify
watchpack
webpack
webpack-core
whet.extend
window-size
wordwrap
wrap-ansi
wrappy
xtend
y18n
yargs
yargs-parser

@domoritz
Copy link
Member

Do you see the same issues with master?

* List d3 in dependencies
* Debug everything else...
@ellisonbg
Copy link
Contributor Author

Yes, I see the same thing in master. I got it to work (see last commit) by just listing d3 in the package.json of this project.

Here is proof it is working in this branch :-)

vega 3

@ellisonbg
Copy link
Contributor Author

What method do I call on the view or spec that is returned by render to grab the base64 encoded image data?

@domoritz
Copy link
Member

Very odd.

$ npm --version
3.9.1
$ node --version
v6.1.0

@domoritz
Copy link
Member

I'm getting errors with this branch. Let me know when I can try it.

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-6-6dbdcfa21eef> in <module>()
      3   "encoding": {
      4     "y": {"type": "quantitative","field": "Acceleration"},
----> 5     "x": {"type": "quantitative","field": "Horsepower"}
      6   }
      7 })

/Users/domoritz/Developer/UW/ipython-vega/vega/vegalite.py in view(spec, data)
     31         If the spec doesn't encode the data, it can be passed in separately.
     32     """
---> 33     spec = utils.prepare_spec(spec, data)
     34     display(VegaLite(spec))
     35 

/Users/domoritz/Developer/UW/ipython-vega/vega/utils.py in prepare_spec(spec, data)
     81     spec = copy.deepcopy(spec)
     82     data = sanitize_dataframe(data)
---> 83     spec['data'] = {'values': data.to_dict(orient='records')}
     84     return spec
     85 

/Users/domoritz/.virtualenvs/ipython3/lib/python3.5/site-packages/pandas/core/frame.py in __setitem__(self, key, value)
   2355         else:
   2356             # set column
-> 2357             self._set_item(key, value)
   2358 
   2359     def _setitem_slice(self, key, value):

/Users/domoritz/.virtualenvs/ipython3/lib/python3.5/site-packages/pandas/core/frame.py in _set_item(self, key, value)
   2421 
   2422         self._ensure_valid_index(value)
-> 2423         value = self._sanitize_column(key, value)
   2424         NDFrame._set_item(self, key, value)
   2425 

/Users/domoritz/.virtualenvs/ipython3/lib/python3.5/site-packages/pandas/core/frame.py in _sanitize_column(self, key, value)
   2576 
   2577             # turn me into an ndarray
-> 2578             value = _sanitize_index(value, self.index, copy=False)
   2579             if not isinstance(value, (np.ndarray, Index)):
   2580                 if isinstance(value, list) and len(value) > 0:

/Users/domoritz/.virtualenvs/ipython3/lib/python3.5/site-packages/pandas/core/series.py in _sanitize_index(data, index, copy)
   2768 
   2769     if len(data) != len(index):
-> 2770         raise ValueError('Length of values does not match length of ' 'index')
   2771 
   2772     if isinstance(data, PeriodIndex):

ValueError: Length of values does not match length of index

@domoritz
Copy link
Member

@ellisonbg

This gives you the image data:

result.view.toImageURL()

It looks like this even works when the renderer is SVG.

@ellisonbg
Copy link
Contributor Author

Hmm, trying to track down that last traceback. Can you post the code that
caused the error?

On Tue, May 17, 2016 at 6:09 PM, Dominik Moritz notifications@github.com
wrote:

@ellisonbg https://github.com/ellisonbg

This gives you the image data:

result.view.toImageURL()

It looks like this even works when the renderer is SVG.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#32 (comment)

Brian E. Granger
Associate Professor of Physics and Data Science
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com

@domoritz
Copy link
Member

domoritz commented May 18, 2016

I just launched the notebook from this repo and re-ran all cells.

@jakevdp
Copy link
Contributor

jakevdp commented May 18, 2016

Very nice! One minor thing: on nbviewer there is a side-effect of an error being printed in the console when the vega plugin is loaded. Probably not a big deal, but it may cause someone unnecessary worry. Is there an easy way in JS to catch that exception somehow?

@jakevdp
Copy link
Contributor

jakevdp commented May 18, 2016

Also – I noticed that while the vegalite functionality works, the vega functionality does not. How hard would it be to adapt what you've done for that as well? Or perhaps we should plan to merge this and update that separately?

* Common base for vega and vegalite capabilities.
* Simplified display.
* Top-level imports.
* Working notebooks.
@ellisonbg ellisonbg changed the title [WIP] Use JavaScript output and save static PNG as well in the notebook Use JavaScript output and save static PNG as well in the notebook May 18, 2016
@ellisonbg
Copy link
Contributor Author

OK this is ready for review. Everything should be ready, including vega functionality...

This was referenced May 18, 2016
@jakevdp
Copy link
Contributor

jakevdp commented May 18, 2016

Do you think the console errors on nbviewer are worth worrying about?

@ellisonbg ellisonbg mentioned this pull request May 18, 2016
4 tasks
if data is '' and 'data' not in spec:
raise ValueError('No data provided')
# Data provided, prepared it and put it into the spec
if not data is '':
Copy link
Contributor

@jakevdp jakevdp May 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably == rather than is here. I'm not sure whether empty strings are pooled.

@jakevdp
Copy link
Contributor

jakevdp commented May 18, 2016

One thought: what if we handle the pandas encoding using a custom JSON encoder? i.e. something like

import json
class JSONEncoder(json.JSONEncoder):
    def default(self, obj):
        if hasattr(obj,'to_json'):
            return obj.to_json()
        return json.JSONEncoder.default(self, obj)

spec = json.dumps(spec_dict, cls=JSONEncoder)

It would simplify the logic within the classes, and also delegate encoding of dataframes to pandas.

@ellisonbg
Copy link
Contributor Author

We have to avoid comparing a DataFrame to None as it raises a warning in
Pandas. Thus, I have started to use an empty string instead of None.

We could use a custom JSONEncoder, but there are still some transformations
of the DataFrame we would want to do before. Maybe open up an issue to
investigate this later?

On Wed, May 18, 2016 at 4:14 PM, Jake Vanderplas notifications@github.com
wrote:

One thought: what if we handle the pandas encoding using a custom JSON
encoder? i.e. something like

import jsonclass JSONEncoder(json.JSONEncoder):
def default(self, obj):
if hasattr(obj,'to_json'):
return obj.to_json()
return json.JSONEncoder.default(self, obj)

It would simplify the logic within the classes, and also delegate encoding
of dataframes to pandas.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#32 (comment)

Brian E. Granger
Associate Professor of Physics and Data Science
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com

@jakevdp
Copy link
Contributor

jakevdp commented May 19, 2016

Maybe open up an issue to
investigate this later?

Sounds good

Need to add tests for this nastiness!
@ellisonbg
Copy link
Contributor Author

OK I have fixed a few more things and addressed comments. Master of altair is also now working with this and I have updated its (non autogenerated) examples:

https://github.com/ellisonbg/altair/tree/master/notebooks

All render on GitHub! @jakevdp this was just the very basic of cleaning up the examples. If you want to start to work on a nicer set of example and tutorial notebooks for altair that would be great!

@ellisonbg
Copy link
Contributor Author

@domoritz can we merge this now and iterate in further PRs?

@domoritz
Copy link
Member

I started reviewing half an hour ago and rewrite some of the js. Will merge when I'm done testing. Looks awesome so far!

@domoritz
Copy link
Member

See WIP at https://github.com/vega/ipyvega/tree/newrender. I'm just making sure that things sill work on nbviewer + github.

@domoritz
Copy link
Member

@ellisonbg Is there a way to tell nbviewer to pull a new Version from github. I'm trying to simplify the js code but can't get new versions into nbviewer.

@domoritz domoritz merged commit d5d6e80 into vega:master May 19, 2016
@domoritz
Copy link
Member

🎉

@ellisonbg
Copy link
Contributor Author

Awesome, thanks for the review and merge!

On Wed, May 18, 2016 at 6:51 PM, Dominik Moritz notifications@github.com
wrote:

🎉


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#32 (comment)

Brian E. Granger
Associate Professor of Physics and Data Science
Cal Poly State University, San Luis Obispo
@ellisonbg on Twitter and GitHub
bgranger@calpoly.edu and ellisonbg@gmail.com

@jakevdp
Copy link
Contributor

jakevdp commented May 19, 2016

Add ?flush_cache=true at the end of the nbviewer URL to force a re-render

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants