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
node.js integration and static image generation #3047
Changes from 16 commits
15401cc
de7cc11
a2c053f
b67b80c
96fcc68
af8c682
c16d186
75657c1
dc5aa82
019fcd5
e756bcc
a839346
eeb3871
babd570
a6b60c0
3ffc50a
4a1af80
5fad6dd
244b979
ae9cb9f
851cc93
6e51490
468c775
7f69c3c
3eaf4a0
1b9e5b5
a60b37c
df12d4e
6e3dfc5
1f4e178
cc6077b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
var fs = require("fs"); | ||
var path = require("path"); | ||
var uuid = require("uuid"); | ||
var argv = require("yargs").argv; | ||
var jsdom = require("jsdom"); | ||
var htmlparser2 = require("htmlparser2"); | ||
|
||
var all_models; | ||
var file = argv._[0]; | ||
var ext = path.extname(file); | ||
var basename = path.basename(file, ext); | ||
var dirname = path.dirname(file); | ||
|
||
switch (ext) { | ||
case ".html": | ||
var all_texts = []; | ||
var all_text = null; | ||
var parser = new htmlparser2.Parser({ | ||
onopentag: function(name, attrs) { | ||
if (name == "script" && attrs.type == "text/x-bokeh") { | ||
all_text = ""; | ||
} | ||
}, | ||
ontext: function(text) { | ||
if (all_text !== null) { | ||
all_text += text; | ||
} | ||
}, | ||
onclosetag: function(name) { | ||
if (name == "script" && all_text !== null) { | ||
all_texts.push(all_text); | ||
all_text = null; | ||
} | ||
} | ||
}); | ||
parser.write(fs.readFileSync(file)); | ||
parser.end(); | ||
switch (all_texts.length) { | ||
case 0: | ||
throw new Error("no 'text/x-bokeh' sections found"); | ||
break; | ||
case 1: | ||
all_models = JSON.parse(all_texts[0]); | ||
break; | ||
default: | ||
throw new Error("too many 'text/x-bokeh' sections"); | ||
} | ||
break; | ||
case ".json": | ||
all_models = require(file); | ||
break; | ||
default: | ||
throw new Error("expected an HTML or JSON file"); | ||
} | ||
|
||
global.document = jsdom.jsdom(); | ||
global.window = document.defaultView; | ||
global.location = require("location"); | ||
global.navigator = require("navigator"); | ||
global.window.Canvas = require("canvas"); | ||
global.window.Image = global.window.Canvas.Image; | ||
|
||
global.bokehRequire = require("./build/js/bokeh.js").bokehRequire; | ||
require("./build/js/bokeh-widgets.js"); | ||
|
||
var Bokeh = global.window.Bokeh; | ||
|
||
var head = document.getElementsByTagName('head')[0]; | ||
var link = document.createElement('link'); | ||
link.rel = 'stylesheet'; | ||
link.href = './build/css/bokeh.css'; | ||
head.appendChild(link); | ||
|
||
Bokeh.set_log_level("debug"); | ||
Bokeh.load_models(all_models); | ||
|
||
Bokeh.Collections("PlotContext").each(function(model) { | ||
var el = document.createElement("div"); | ||
el.setAttribute("class", "plotdiv"); | ||
document.body.appendChild(el); | ||
|
||
Bokeh.Events.on("render:done", function(plot_view) { | ||
var nodeCanvas = plot_view.canvas_view.canvas[0]._nodeCanvas; | ||
var name = basename + "-" + plot_view.model.id + ".png"; | ||
var outfile = path.join(dirname, name) | ||
Bokeh.logger.info("writing " + outfile); | ||
var out = fs.createWriteStream(outfile); | ||
nodeCanvas.pngStream().on('data', function(chunk) { out.write(chunk); }); | ||
}); | ||
|
||
var view = new model.default_view({model: model, el: el}); | ||
Bokeh.index[model.id] = view; | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -117,7 +117,19 @@ fill_render_item_from_script_tag = (script, item) -> | |
|
||
logger.info("Will inject Bokeh script tag with params #{JSON.stringify(item)}") | ||
|
||
embed_items = (docs_json, render_items, websocket_url) -> | ||
embed_items = (docs_json_or_id, render_items, websocket_url) -> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would be nicer perhaps if we either always do it the "embed the doc as a script tag" way or the "embed the doc as JSON" way, rather than sometimes doing one and sometimes doing the other. It looks like you added the "embed doc as a script" way in order to scrape a document out of an HTML file? If so I would say we should always go from Command line implications:
I know you aren't done with the PR so maybe you've already gone in another direction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is a secondary goal (thought useful for testing), but really I wanted to externalize the JSON for quite some time already. This is not the final form, because of
Obviously, but as I said, the current approach is good for testing. I'm currently working on the proper API/CLI. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if we end up with both externalized docs_json and in-script docs_json, let's make two separate functions here in |
||
if _.isString(docs_json_or_id) | ||
element = document.getElementById(docs_json_or_id) | ||
if not element | ||
throw new Error("element ##{docs_json_or_id} not found") | ||
if element.nodeName.toLowerCase() != "script" or element.getAttribute("type") != "text/x-bokeh" | ||
throw new Error("element ##{docs_json_or_id} must be a script with type='text/x-bokeh'") | ||
docs_json = JSON.parse(element.innerHTML) | ||
else if _.isObject(docs_json_or_id) | ||
docs_json = docs_json_or_id | ||
else | ||
throw new Error("expected a string identifier or an object") | ||
|
||
if websocket_url? | ||
set_websocket_url(websocket_url) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
Backbone = require "backbone" | ||
|
||
class Events extends Backbone.Events | ||
|
||
module.exports = { | ||
Events: Events | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,7 @@ test: | |
- ipython-notebook | ||
- matplotlib | ||
- sympy | ||
- scikit-learn | ||
- ggplot | ||
- seaborn | ||
- icalendar | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this refactor is incomplete and we end up with one function that really does two separate things. As it is, the function is named and expected elsewhere to return one hunk of script, which is optionally wrapped in one
<script>
tag. If we want to change it so that to embed render items, you need N script tags, we should rename the function_scripts_for_render_items
, return a list, and have all callers handle the list result.wrap_script
should control whether each element is wrapped in a<script>
tag, not how many elements there are. Though with docs_json in its own tag I think we have to always wrap_script so that parameter would maybe have to go.The simplest thing for this PR is probably to drop the "externalize docs json into its own
<script>
tag" and do that as its own PR, but if you think externalizing the docs json is important to this PR, I think we should finish the refactor - do it always/consistently. There's no value I see to having two ways to do this: either externalize the docs_json into tags, or don't.Going from one script to a list of scripts will break the
components()
API I think. If we don't want to breakcomponents()
I'm not sure we should do this at all, is it really worth two codepaths?If we do have two codepaths, I'd rather not overload
wrap_script
- it refers to whether to wrap the scripts in a script tag, which is separate from whether the docs_json is in its own script tag. Maybe a separate function_scripts_for_render_items_with_externalized_json
?