-
-
Notifications
You must be signed in to change notification settings - Fork 28
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
How to generate graphs with common-lisp-jupyter? #16
Comments
No need to apologize. Unless you want the graphs to have interactive sliders or such, you probably don't need widgets. Media can be displayed with the functions defined in If I understand (defparameter *dataframe*
'((1 1)
(2 2)
(3 1.5)
(4 3)
(5 2.5)
(6 4)))
(defparameter *fig* (make-instance 'figure))
(xlabel *fig* "x-label")
(ylabel *fig* "y-label")
(title *fig* "Basic example")
(scatter *fig* *dataframe*)
(save *fig* "fig.png")
(jupyter:file "fig.png") If you are using JupyterLab there is also an extension for Vega-Lite that allows one to pass the JSON based Vega-Lite description right to the front end. Eventually I may make some wrapper code for this.
|
Thank you! I'll try that! |
What is the name of the recommended extension for Vega-Lite? |
It's called
|
Is the extension still required? I was able to generate some Vega-Lite graphs with JupyterLab 3.0.9 by creating a spec file and double clicking on it. See https://jupyterlab.readthedocs.io/en/stable/user/file_formats.html#vega-vega-lite. I admit though, it's a rather awkward way to create a plot. You mentioned a wrapper. I tried wrapping a Vega Lite spec composed of alists, which seems a natural fit to me, and it worked well until the yason developer(s) reverted the recursive behaviour because it broke some existing functionality slightly, and were unwilling to address it properly because it would mean a breaking change to the API. This is unfortunate because developing a plot spec from lisp is more natural, and you can splice in data with the backquote mechanism. I suppose an alternative JSON library could be tried; I used yason because it's the library used by json-mop and it would be nice to go backwards and forwards between the two. Did you give any thought to how you might wrap a Vega-Lite JSON spec? |
The extension is for rendering the graph in a notebook output cell. I think it is still required if you want to do that. I haven't thought about wrapping the spec much, but I if you are just trying to create inline graphs I would probably just write convenience functions that output the JSON in the above format for each kind of graph you want to do. Right now common-lisp-jupyter is using jsown internally. That doesn't prevent you from using whatever JSON library you want though. Eventually I want to switch to my own library, shasht, which was designed to pass JSONTextSuite which contains over 300 different parsing tests. You can see differences between the various Common Lisp libraries here. If you are just generating JSON that this probably doesn't matter much, but common-lisp-jupyter is doing a lot of JSON parsing internally. |
Interesting. I never heard of shasht before. I'll have a look. I might just try to work around any issues in yason with a macro, or maybe work with the yason guys to get those changes moved back in somehow. |
Can shasht handle encoding of nested alists? For example in the above (though not an alist, imagine it is):
? |
It can. It is in quicklisp, but probably not used by anyone yet. I haven't put much effort in to documentation yet, but you can see some of the configuration stuff in config.lisp
|
Do you have an example of passing Vega-Lite JSON to directly to the front end? If, say, we were to encode a spec with an alist like this: (let ((shasht:*write-alist-as-object* t))
(shasht:write-json
'(("$schema" . "https://vega.github.io/schema/vega-lite/v5.json")
("description" . "A simple bar chart with embedded data.")
("data"
("values"
. #((("a" . "A") ("b" . 28)) (("a" . "B") ("b" . 55))
(("a" . "C") ("b" . 43)) (("a" . "D") ("b" . 91))
(("a" . "E") ("b" . 81)) (("a" . "F") ("b" . 53))
(("a" . "G") ("b" . 19)) (("a" . "H") ("b" . 87))
(("a" . "I") ("b" . 52)))))
("mark" . "bar")
("encoding"
("x" ("field" . "a") ("type" . "nominal") ("axis" ("labelAngle" . 0)))
("y" ("field" . "b") ("type" . "quantitative"))))
)) Is there a way to have Jupyter Lab render the plot? |
You don't need to serialize the JSON. You need to pass the JSOWN style json to (jupyter:inline-result
(jsown:new-js
("$schema" "https://vega.github.io/schema/vega-lite/v3.json")
("description" "A simple bar chart with embedded data.")
("data" (jsown:new-js
("values" (list
(jsown:new-js ("a" "A") ("b" 28)) (jsown:new-js("a" "B") ("b" 55)) (jsown:new-js("a" "C") ("b" 43))
(jsown:new-js("a" "D") ("b" 91)) (jsown:new-js("a" "E") ("b" 81)) (jsown:new-js("a" "F") ("b" 53))
(jsown:new-js("a" "G") ("b" 19)) (jsown:new-js("a" "H") ("b" 87)) (jsown:new-js("a" "I") ("b" 52))))))
("mark" "bar")
("encoding" (jsown:new-js
("x" (jsown:new-js ("field" "a") ("type" "ordinal")))
("y" (jsown:new-js ("field" "b") ("type" "quantitative"))))))
"application/vnd.vegalite.v3+json") Also, it looks like you no longer need to install the separate extension as it now included by default with JupyterLab. |
I was thinking of a way to save a JSON spec in a variable, or as part of a plot class. The jsown is a bit wordy to work with as a lisp-side spec. Are there any ways an alist can be coerced into the JSOWN (or other format) without inserting a bunch of |
The JSOWN objects are just alists with a '(:obj ("a" . "A") ("b" . 28)) |
I see. In fact if you look at the new-js output:
I can see the raw JSOWN. I was thinking that the alist->JSON encoding happened in one of the cells, and then was passed to Jupyter, but it seems instead that Jupyter receives the JSOWN spec to interpret. Are we able to influence this path and somehow send the final JSON spec to the Vega renderer? |
Its JSOWN style because common-lisp-jupyter is using JSOWN and It might be possible to add some options to |
Because of the switch to the shasht JSON library this has changed a bit.
Please note that I am working on adding a simple VegaLite function that takes care of the mime type in #72 and possibly allows for sending proper alists. |
I've switched to shasht so the jsown style encoding won't work anymore. In shasht JSON objects are represented by hashtables, There is also a convenience function ( |
The plot specifications for Vega-Lite are all manipulated as alists. How can we get shasht to encode them? The example above:
Looks like it requires |
shasht does have a way to do this ( {
"execution_count": 3,
"data": {
"application/vnd.vegalite.v4+json": "foo"
},
"metadata": {}
} When you use Probably the long term answer is to provide some keys to control the conversion on |
Is there a short-term work-around? |
Well you could write a recursive function that looks for alists and does |
There are wrappers around the 'grammar of graphics' for Vega-Lite. Examples and documentation explain it better than I could here. |
Perhaps the path of least resistance is to revert to an older version and hold there until shasht alist processing is worked out? I upgraded to get the markdown streams, which happened in commit cda458b on 26 April. Jsown was removed in 2511acb on 3 March. Do you think it would be possible to revert to a version prior to 2511acb and then cherry pick the markdown stream changes? |
alists are a fundamentally ambiguous, but convenient data structure in my opinion. My reading of your documentation and code seems to indicate thatt that you are supporting two different JSON encoders (yason and shasht) and two different environments (Jupyter and static web pages). This is probably making things even more complex. I am currently investigating my own wrapper for VegaLite and I am starting to come to the conclusion that it would be best to delay the JSON encoding until the very last minute. In other words, encode the graph as combination of classes and then override I know that doesn't necessarily answer your question. If you want to continue to use alists you could do the following. (defun alistp (value)
(and (listp value)
(every #'consp value)))
(defun convert (value)
(if (alistp value)
(cons :object-alist
(mapcar (lambda (pair)
(cons (car pair)
(convert (cdr pair))))
value))
value)) Then you could do the this |
I do use yason, but only because an earlier system I was using had it. I agree that alist encoding can be ambiguous, so some convention is probably required. Both jsown and yason produce the same alist output (depending on settings), and there is something to be said for consistency in implementations. It might not be clear from the documentation (and if it isn't, I should change it), but the data frame is a data manipulation structure and the specification for a plot an alist. The alist turns out to be quite a convenient representation of Vega-Lite plots, and has the advantage of manipulation with standard functions and libraries. I had initially went down the object route (that's where the yason came from), but there was little return for the additional complexity. If you're still considering options, you might want to look at using Lisp-Stat's vega-lite wrappers. It would be unfortunate to have duplicates of the same functionality in the Lisp community, and a lot of benefit to having a single vega-lite project to gain critical mass. |
is
but the call to vega-lite gives me:
|
Looks like the alist markers are missing in the vector. Try this. (defun alistp (value)
(and (listp value)
(every #'consp value)))
(defun convert (value)
(cond
((alistp value)
(cons :object-alist
(mapcar (lambda (pair)
(cons (car pair)
(convert (cdr pair))))
value)))
((vectorp value)
(map 'vector #'convert value))
(t
value))) |
That seems to vectorise everything:
|
Probably just have to put a tighter restriction on. (defun alistp (value)
(and (listp value)
(every #'consp value)))
(defun convert (value)
(cond
((alistp value)
(cons :object-alist
(mapcar (lambda (pair)
(cons (car pair)
(convert (cdr pair))))
value)))
((typep value '(vector t *))
(map 'vector #'convert value))
(t
value))) |
Hi! Sorry for bothering you again. I'm just very happy to see this development. I see you add recently a collection of useful widgets. There is some way to to embedded graphs in this kernel?
It would be so awesome!
https://github.com/martinkersner/cl-plot Something like this or better.
The text was updated successfully, but these errors were encountered: