quarto-dev / quarto-cli

Open-source scientific and technical publishing system built on Pandoc.
https://quarto.org
Other
3.96k stars 326 forks source link

A single boket plot is wrongly identified as several subfigures #7967

Open cderv opened 11 months ago

cderv commented 11 months ago

It seems this previous issue is not fixed

Same problem. Downloaded repo and installed most current version 1.4.529 with ./configure.sh

Quarto 99.9.9
[✓] Checking versions of quarto binary dependencies...
      Pandoc version 3.1.11: OK
      Dart Sass version 1.69.5: OK
      Deno version 1.37.2: OK
[✓] Checking versions of quarto dependencies......OK
[✓] Checking Quarto installation......OK
      Version: 99.9.9
      Path: /home/cdaniels/junk/quarto/quarto-cli/package/dist/bin

Check file:///home/cdaniels/junk/quarto/quarto-cli/src/resources/vendor/deno-land/x/puppeteer@9-0-2/mod.ts
[✓] Checking tools....................OK
      TinyTeX: v2023.12
      Chromium: (not installed)

[✓] Checking LaTeX....................OK
      Using: TinyTex
      Path: /home/cdaniels/.TinyTeX/bin/x86_64-linux
      Version: 2023

[✓] Checking basic markdown render....OK

[✓] Checking Python 3 installation....OK
      Version: 3.12.0 (Conda)
      Path: /home/cdaniels/mambaforge/envs/quarto/bin/python
      Jupyter: (None)

      Jupyter is not available in this Python installation.
      Install with conda install jupyter

[✓] Checking R installation...........OK
      Version: 4.3.2
      Path: /usr/lib/R
      LibPaths:
        - /home/cdaniels/R/x86_64-pc-linux-gnu-library/4.3
        - /usr/local/lib/R/site-library
        - /usr/lib/R/site-library
        - /usr/lib/R/library
      knitr: 1.45
      rmarkdown: 2.25

[✓] Checking Knitr engine render......OK

Not sure why Jupyter is not installed. (Installed with both mamba and conda). It does shows up in correct env:


Selected Jupyter core packages...
IPython          : 8.18.1
ipykernel        : 6.26.0
ipywidgets       : 8.1.1
jupyter_client   : 8.6.0
jupyter_core     : 5.5.1
jupyter_server   : 2.12.1
jupyterlab       : 4.0.9
nbclient         : 0.8.0
nbconvert        : 7.13.0
nbformat         : 5.9.2
notebook         : 7.0.6
qtconsole        : 5.5.1
traitlets        : 5.14.0
image

Originally posted by @prairie-guy in https://github.com/quarto-dev/quarto-cli/issues/2107#issuecomment-1862166865

cderv commented 11 months ago

Here is a simplified test document

---
title: "test "
format:
  html: default
keep-md: true
---

```{python}
from bokeh.io import output_notebook
output_notebook(hide_banner=True)
#| label: fig-4
#| fig-cap: "Line Plot from Bokeh"

from bokeh.plotting import figure, show

# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

# create a new plot with a title and axis labels
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")

# add a line renderer with legend and line thickness
p.line(x, y, legend_label="Temp.", line_width=2)

# show the results
show(p)

![image](https://github.com/quarto-dev/quarto-cli/assets/6791940/1bae7802-1e03-4dac-a307-7d1657f4eec7)

It happens because this is two cells - if there is only one, no problems

<details>
<summary>Very long intermediate md</summary>

````markdown
---
title: "test webpage"
format:
  html: default
keep-md: true
---

::: {#9a1b83c0 .cell execution_count=1}
``` {.python .cell-code}
from bokeh.io import output_notebook
output_notebook(hide_banner=True)

::: {.cell-output .cell-output-display}

<script type="application/javascript">
(function(root) {
  function now() {
    return new Date();
  }

  const force = true;

  if (typeof root._bokeh_onload_callbacks === "undefined" || force === true) {
    root._bokeh_onload_callbacks = [];
    root._bokeh_is_loading = undefined;
  }

const JS_MIME_TYPE = 'application/javascript';
  const HTML_MIME_TYPE = 'text/html';
  const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';
  const CLASS_NAME = 'output_bokeh rendered_html';

  /**
   * Render data to the DOM node
   */
  function render(props, node) {
    const script = document.createElement("script");
    node.appendChild(script);
  }

  /**
   * Handle when an output is cleared or removed
   */
  function handleClearOutput(event, handle) {
    const cell = handle.cell;

    const id = cell.output_area._bokeh_element_id;
    const server_id = cell.output_area._bokeh_server_id;
    // Clean up Bokeh references
    if (id != null && id in Bokeh.index) {
      Bokeh.index[id].model.document.clear();
      delete Bokeh.index[id];
    }

    if (server_id !== undefined) {
      // Clean up Bokeh references
      const cmd_clean = "from bokeh.io.state import curstate; print(curstate().uuid_to_server['" + server_id + "'].get_sessions()[0].document.roots[0]._id)";
      cell.notebook.kernel.execute(cmd_clean, {
        iopub: {
          output: function(msg) {
            const id = msg.content.text.trim();
            if (id in Bokeh.index) {
              Bokeh.index[id].model.document.clear();
              delete Bokeh.index[id];
            }
          }
        }
      });
      // Destroy server and session
      const cmd_destroy = "import bokeh.io.notebook as ion; ion.destroy_server('" + server_id + "')";
      cell.notebook.kernel.execute(cmd_destroy);
    }
  }

  /**
   * Handle when a new output is added
   */
  function handleAddOutput(event, handle) {
    const output_area = handle.output_area;
    const output = handle.output;

    // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only
    if ((output.output_type != "display_data") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {
      return
    }

    const toinsert = output_area.element.find("." + CLASS_NAME.split(' ')[0]);

    if (output.metadata[EXEC_MIME_TYPE]["id"] !== undefined) {
      toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];
      // store reference to embed id on output_area
      output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE]["id"];
    }
    if (output.metadata[EXEC_MIME_TYPE]["server_id"] !== undefined) {
      const bk_div = document.createElement("div");
      bk_div.innerHTML = output.data[HTML_MIME_TYPE];
      const script_attrs = bk_div.children[0].attributes;
      for (let i = 0; i < script_attrs.length; i++) {
        toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);
        toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent
      }
      // store reference to server id on output_area
      output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE]["server_id"];
    }
  }

  function register_renderer(events, OutputArea) {

    function append_mime(data, metadata, element) {
      // create a DOM node to render to
      const toinsert = this.create_output_subarea(
        metadata,
        CLASS_NAME,
        EXEC_MIME_TYPE
      );
      this.keyboard_manager.register_events(toinsert);
      // Render to node
      const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};
      render(props, toinsert[toinsert.length - 1]);
      element.append(toinsert);
      return toinsert
    }

    /* Handle when an output is cleared or removed */
    events.on('clear_output.CodeCell', handleClearOutput);
    events.on('delete.Cell', handleClearOutput);

    /* Handle when a new output is added */
    events.on('output_added.OutputArea', handleAddOutput);

    /**
     * Register the mime type and append_mime function with output_area
     */
    OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {
      /* Is output safe? */
      safe: true,
      /* Index of renderer in `output_area.display_order` */
      index: 0
    });
  }

  // register the mime type if in Jupyter Notebook environment and previously unregistered
  if (root.Jupyter !== undefined) {
    const events = require('base/js/events');
    const OutputArea = require('notebook/js/outputarea').OutputArea;

    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {
      register_renderer(events, OutputArea);
    }
  }
  if (typeof (root._bokeh_timeout) === "undefined" || force === true) {
    root._bokeh_timeout = Date.now() + 5000;
    root._bokeh_failed_load = false;
  }

  const NB_LOAD_WARNING = {'data': {'text/html':
     "<div style='background-color: #fdd'>\n"+
     "<p>\n"+
     "BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \n"+
     "may be due to a slow or bad network connection. Possible fixes:\n"+
     "</p>\n"+
     "<ul>\n"+
     "<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\n"+
     "<li>use INLINE resources instead, as so:</li>\n"+
     "</ul>\n"+
     "<code>\n"+
     "from bokeh.resources import INLINE\n"+
     "output_notebook(resources=INLINE)\n"+
     "</code>\n"+
     "</div>"}};

  function display_loaded() {
    const el = document.getElementById(null);
    if (el != null) {
      el.textContent = "BokehJS is loading...";
    }
    if (root.Bokeh !== undefined) {
      if (el != null) {
        el.textContent = "BokehJS " + root.Bokeh.version + " successfully loaded.";
      }
    } else if (Date.now() < root._bokeh_timeout) {
      setTimeout(display_loaded, 100)
    }
  }

  function run_callbacks() {
    try {
      root._bokeh_onload_callbacks.forEach(function(callback) {
        if (callback != null)
          callback();
      });
    } finally {
      delete root._bokeh_onload_callbacks
    }
    console.debug("Bokeh: all callbacks have finished");
  }

  function load_libs(css_urls, js_urls, callback) {
    if (css_urls == null) css_urls = [];
    if (js_urls == null) js_urls = [];

    root._bokeh_onload_callbacks.push(callback);
    if (root._bokeh_is_loading > 0) {
      console.debug("Bokeh: BokehJS is being loaded, scheduling callback at", now());
      return null;
    }
    if (js_urls == null || js_urls.length === 0) {
      run_callbacks();
      return null;
    }
    console.debug("Bokeh: BokehJS not loaded, scheduling load and callback at", now());
    root._bokeh_is_loading = css_urls.length + js_urls.length;

    function on_load() {
      root._bokeh_is_loading--;
      if (root._bokeh_is_loading === 0) {
        console.debug("Bokeh: all BokehJS libraries/stylesheets loaded");
        run_callbacks()
      }
    }

    function on_error(url) {
      console.error("failed to load " + url);
    }

    for (let i = 0; i < css_urls.length; i++) {
      const url = css_urls[i];
      const element = document.createElement("link");
      element.onload = on_load;
      element.onerror = on_error.bind(null, url);
      element.rel = "stylesheet";
      element.type = "text/css";
      element.href = url;
      console.debug("Bokeh: injecting link tag for BokehJS stylesheet: ", url);
      document.body.appendChild(element);
    }

    for (let i = 0; i < js_urls.length; i++) {
      const url = js_urls[i];
      const element = document.createElement('script');
      element.onload = on_load;
      element.onerror = on_error.bind(null, url);
      element.async = false;
      element.src = url;
      console.debug("Bokeh: injecting script tag for BokehJS library: ", url);
      document.head.appendChild(element);
    }
  };

  function inject_raw_css(css) {
    const element = document.createElement("style");
    element.appendChild(document.createTextNode(css));
    document.body.appendChild(element);
  }

  const js_urls = ["https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js"];
  const css_urls = [];

  const inline_js = [    function(Bokeh) {
      Bokeh.set_log_level("info");
    },
function(Bokeh) {
    }
  ];

  function run_inline_js() {
    if (root.Bokeh !== undefined || force === true) {
          for (let i = 0; i < inline_js.length; i++) {
      inline_js[i].call(root, root.Bokeh);
    }
} else if (Date.now() < root._bokeh_timeout) {
      setTimeout(run_inline_js, 100);
    } else if (!root._bokeh_failed_load) {
      console.log("Bokeh: BokehJS failed to load within specified timeout.");
      root._bokeh_failed_load = true;
    } else if (force !== true) {
      const cell = $(document.getElementById(null)).parents('.cell').data().cell;
      cell.output_area.append_execute_result(NB_LOAD_WARNING)
    }
  }

  if (root._bokeh_is_loading === 0) {
    console.debug("Bokeh: BokehJS loaded, going straight to plotting");
    run_inline_js();
  } else {
    load_libs(css_urls, js_urls, function() {
      console.debug("Bokeh: BokehJS plotting callback run at", now());
      run_inline_js();
    });
  }
}(window));
</script>

::: :::

::: {#fig-4 .cell execution_count=2}

from bokeh.plotting import figure, show

# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

# create a new plot with a title and axis labels
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")

# add a line renderer with legend and line thickness
p.line(x, y, legend_label="Temp.", line_width=2)

# show the results
show(p)

::: {#fig-4-1 .cell-output .cell-output-display}


  <div class="bk-root" id="bd096a76-3096-475a-9054-ff1b53329601" data-root-id="3320"></div>

Line Plot from Bokeh :::

::: {#fig-4-2 .cell-output .cell-output-display}

<script type="application/javascript">
(function(root) {
  function embed_document(root) {
  const docs_json = {"e88c3968-8e0a-4037-a315-5e0168c10e1b":{"defs":[],"roots":{"references":[{"attributes":{"below":[{"id":"3331"}],"center":[{"id":"3334"},{"id":"3338"},{"id":"3369"}],"left":[{"id":"3335"}],"renderers":[{"id":"3357"}],"title":{"id":"3321"},"toolbar":{"id":"3346"},"x_range":{"id":"3323"},"x_scale":{"id":"3327"},"y_range":{"id":"3325"},"y_scale":{"id":"3329"}},"id":"3320","subtype":"Figure","type":"Plot"},{"attributes":{"line_color":"#1f77b4","line_width":2,"x":{"field":"x"},"y":{"field":"y"}},"id":"3354","type":"Line"},{"attributes":{"line_alpha":0.2,"line_color":"#1f77b4","line_width":2,"x":{"field":"x"},"y":{"field":"y"}},"id":"3356","type":"Line"},{"attributes":{"data":{"x":[1,2,3,4,5],"y":[6,7,2,4,5]},"selected":{"id":"3367"},"selection_policy":{"id":"3366"}},"id":"3353","type":"ColumnDataSource"},{"attributes":{"line_alpha":0.1,"line_color":"#1f77b4","line_width":2,"x":{"field":"x"},"y":{"field":"y"}},"id":"3355","type":"Line"},{"attributes":{},"id":"3362","type":"AllLabels"},{"attributes":{"coordinates":null,"data_source":{"id":"3353"},"glyph":{"id":"3354"},"group":null,"hover_glyph":null,"muted_glyph":{"id":"3356"},"nonselection_glyph":{"id":"3355"},"view":{"id":"3358"}},"id":"3357","type":"GlyphRenderer"},{"attributes":{"tools":[{"id":"3339"},{"id":"3340"},{"id":"3341"},{"id":"3342"},{"id":"3343"},{"id":"3344"}]},"id":"3346","type":"Toolbar"},{"attributes":{},"id":"3339","type":"PanTool"},{"attributes":{},"id":"3340","type":"WheelZoomTool"},{"attributes":{},"id":"3361","type":"BasicTickFormatter"},{"attributes":{"axis":{"id":"3335"},"coordinates":null,"dimension":1,"group":null,"ticker":null},"id":"3338","type":"Grid"},{"attributes":{"coordinates":null,"group":null,"items":[{"id":"3370"}]},"id":"3369","type":"Legend"},{"attributes":{},"id":"3366","type":"UnionRenderers"},{"attributes":{},"id":"3329","type":"LinearScale"},{"attributes":{"label":{"value":"Temp."},"renderers":[{"id":"3357"}]},"id":"3370","type":"LegendItem"},{"attributes":{"axis_label":"y","coordinates":null,"formatter":{"id":"3361"},"group":null,"major_label_policy":{"id":"3362"},"ticker":{"id":"3336"}},"id":"3335","type":"LinearAxis"},{"attributes":{},"id":"3342","type":"SaveTool"},{"attributes":{},"id":"3365","type":"AllLabels"},{"attributes":{"bottom_units":"screen","coordinates":null,"fill_alpha":0.5,"fill_color":"lightgrey","group":null,"left_units":"screen","level":"overlay","line_alpha":1.0,"line_color":"black","line_dash":[4,4],"line_width":2,"right_units":"screen","syncable":false,"top_units":"screen"},"id":"3345","type":"BoxAnnotation"},{"attributes":{"overlay":{"id":"3345"}},"id":"3341","type":"BoxZoomTool"},{"attributes":{},"id":"3327","type":"LinearScale"},{"attributes":{"axis":{"id":"3331"},"coordinates":null,"group":null,"ticker":null},"id":"3334","type":"Grid"},{"attributes":{},"id":"3344","type":"HelpTool"},{"attributes":{},"id":"3323","type":"DataRange1d"},{"attributes":{"coordinates":null,"group":null,"text":"Simple line example"},"id":"3321","type":"Title"},{"attributes":{},"id":"3336","type":"BasicTicker"},{"attributes":{},"id":"3367","type":"Selection"},{"attributes":{"axis_label":"x","coordinates":null,"formatter":{"id":"3364"},"group":null,"major_label_policy":{"id":"3365"},"ticker":{"id":"3332"}},"id":"3331","type":"LinearAxis"},{"attributes":{},"id":"3364","type":"BasicTickFormatter"},{"attributes":{},"id":"3343","type":"ResetTool"},{"attributes":{},"id":"3332","type":"BasicTicker"},{"attributes":{"source":{"id":"3353"}},"id":"3358","type":"CDSView"},{"attributes":{},"id":"3325","type":"DataRange1d"}],"root_ids":["3320"]},"title":"Bokeh Application","version":"2.4.3"}};
  const render_items = [{"docid":"e88c3968-8e0a-4037-a315-5e0168c10e1b","root_ids":["3320"],"roots":{"3320":"bd096a76-3096-475a-9054-ff1b53329601"}}];
  root.Bokeh.embed.embed_items_notebook(docs_json, render_items);
  }
  if (root.Bokeh !== undefined) {
    embed_document(root);
  } else {
    let attempts = 0;
    const timer = setInterval(function(root) {
      if (root.Bokeh !== undefined) {
        clearInterval(timer);
        embed_document(root);
      } else {
        attempts++;
        if (attempts > 100) {
          clearInterval(timer);
          console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
        }
      }
    }, 10, root)
  }
})(window);
</script>

::: :::


</details>

we got the second cell outputing two `display_data`, one is only a HTML `div` that should contains  the graph, and the other is the JS script to create it. Somehow, it got separated and we end up with somthing like 

````markdown
::: {#fig-4 .cell execution_count=2}

< python source code cell here>

::: {#fig-4-1 .cell-output .cell-output-display}

```{=html}

  <div class="bk-root" id="bd096a76-3096-475a-9054-ff1b53329601" data-root-id="3320"></div>

Line Plot from Bokeh :::

::: {#fig-4-2 .cell-output .cell-output-display}

<script type="application/javascript">

(...)
:::
:::

Merging the two cells fix the issue

---
title: "test webpage"
format:
  html: default
keep-md: true
---

```{python}
#| label: fig-4
#| fig-cap: "Line Plot from Bokeh"

from bokeh.plotting import figure, show
from bokeh.io import output_notebook
output_notebook(hide_banner=True)

# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

# create a new plot with a title and axis labels
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")

# add a line renderer with legend and line thickness
p.line(x, y, legend_label="Temp.", line_width=2)

# show the results
show(p)

![image](https://github.com/quarto-dev/quarto-cli/assets/6791940/8dfa10d8-8567-4cb8-b252-96fe9df19480)

<details>
<summary>Intermediate md</summary>

````markdown
---
title: "test webpage"
format:
  html: default
keep-md: true
---

::: {#cell-fig-4 .cell execution_count=1}
``` {.python .cell-code}
from bokeh.plotting import figure, show
from bokeh.io import output_notebook
output_notebook(hide_banner=True)

# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

# create a new plot with a title and axis labels
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")

# add a line renderer with legend and line thickness
p.line(x, y, legend_label="Temp.", line_width=2)

# show the results
show(p)

::: {#fig-4 .cell-output .cell-output-display}

<script>(function(root) {
  function now() {
    return new Date();
  }

  const force = true;

  if (typeof root._bokeh_onload_callbacks === "undefined" || force === true) {
    root._bokeh_onload_callbacks = [];
    root._bokeh_is_loading = undefined;
  }

const JS_MIME_TYPE = 'application/javascript';
  const HTML_MIME_TYPE = 'text/html';
  const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';
  const CLASS_NAME = 'output_bokeh rendered_html';

  /**
   * Render data to the DOM node
   */
  function render(props, node) {
    const script = document.createElement("script");
    node.appendChild(script);
  }

  /**
   * Handle when an output is cleared or removed
   */
  function handleClearOutput(event, handle) {
    const cell = handle.cell;

    const id = cell.output_area._bokeh_element_id;
    const server_id = cell.output_area._bokeh_server_id;
    // Clean up Bokeh references
    if (id != null && id in Bokeh.index) {
      Bokeh.index[id].model.document.clear();
      delete Bokeh.index[id];
    }

    if (server_id !== undefined) {
      // Clean up Bokeh references
      const cmd_clean = "from bokeh.io.state import curstate; print(curstate().uuid_to_server['" + server_id + "'].get_sessions()[0].document.roots[0]._id)";
      cell.notebook.kernel.execute(cmd_clean, {
        iopub: {
          output: function(msg) {
            const id = msg.content.text.trim();
            if (id in Bokeh.index) {
              Bokeh.index[id].model.document.clear();
              delete Bokeh.index[id];
            }
          }
        }
      });
      // Destroy server and session
      const cmd_destroy = "import bokeh.io.notebook as ion; ion.destroy_server('" + server_id + "')";
      cell.notebook.kernel.execute(cmd_destroy);
    }
  }

  /**
   * Handle when a new output is added
   */
  function handleAddOutput(event, handle) {
    const output_area = handle.output_area;
    const output = handle.output;

    // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only
    if ((output.output_type != "display_data") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {
      return
    }

    const toinsert = output_area.element.find("." + CLASS_NAME.split(' ')[0]);

    if (output.metadata[EXEC_MIME_TYPE]["id"] !== undefined) {
      toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];
      // store reference to embed id on output_area
      output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE]["id"];
    }
    if (output.metadata[EXEC_MIME_TYPE]["server_id"] !== undefined) {
      const bk_div = document.createElement("div");
      bk_div.innerHTML = output.data[HTML_MIME_TYPE];
      const script_attrs = bk_div.children[0].attributes;
      for (let i = 0; i < script_attrs.length; i++) {
        toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);
        toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent
      }
      // store reference to server id on output_area
      output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE]["server_id"];
    }
  }

  function register_renderer(events, OutputArea) {

    function append_mime(data, metadata, element) {
      // create a DOM node to render to
      const toinsert = this.create_output_subarea(
        metadata,
        CLASS_NAME,
        EXEC_MIME_TYPE
      );
      this.keyboard_manager.register_events(toinsert);
      // Render to node
      const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};
      render(props, toinsert[toinsert.length - 1]);
      element.append(toinsert);
      return toinsert
    }

    /* Handle when an output is cleared or removed */
    events.on('clear_output.CodeCell', handleClearOutput);
    events.on('delete.Cell', handleClearOutput);

    /* Handle when a new output is added */
    events.on('output_added.OutputArea', handleAddOutput);

    /**
     * Register the mime type and append_mime function with output_area
     */
    OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {
      /* Is output safe? */
      safe: true,
      /* Index of renderer in `output_area.display_order` */
      index: 0
    });
  }

  // register the mime type if in Jupyter Notebook environment and previously unregistered
  if (root.Jupyter !== undefined) {
    const events = require('base/js/events');
    const OutputArea = require('notebook/js/outputarea').OutputArea;

    if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {
      register_renderer(events, OutputArea);
    }
  }
  if (typeof (root._bokeh_timeout) === "undefined" || force === true) {
    root._bokeh_timeout = Date.now() + 5000;
    root._bokeh_failed_load = false;
  }

  const NB_LOAD_WARNING = {'data': {'text/html':
     "<div style='background-color: #fdd'>\n"+
     "<p>\n"+
     "BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \n"+
     "may be due to a slow or bad network connection. Possible fixes:\n"+
     "</p>\n"+
     "<ul>\n"+
     "<li>re-rerun `output_notebook()` to attempt to load from CDN again, or</li>\n"+
     "<li>use INLINE resources instead, as so:</li>\n"+
     "</ul>\n"+
     "<code>\n"+
     "from bokeh.resources import INLINE\n"+
     "output_notebook(resources=INLINE)\n"+
     "</code>\n"+
     "</div>"}};

  function display_loaded() {
    const el = document.getElementById(null);
    if (el != null) {
      el.textContent = "BokehJS is loading...";
    }
    if (root.Bokeh !== undefined) {
      if (el != null) {
        el.textContent = "BokehJS " + root.Bokeh.version + " successfully loaded.";
      }
    } else if (Date.now() < root._bokeh_timeout) {
      setTimeout(display_loaded, 100)
    }
  }

  function run_callbacks() {
    try {
      root._bokeh_onload_callbacks.forEach(function(callback) {
        if (callback != null)
          callback();
      });
    } finally {
      delete root._bokeh_onload_callbacks
    }
    console.debug("Bokeh: all callbacks have finished");
  }

  function load_libs(css_urls, js_urls, callback) {
    if (css_urls == null) css_urls = [];
    if (js_urls == null) js_urls = [];

    root._bokeh_onload_callbacks.push(callback);
    if (root._bokeh_is_loading > 0) {
      console.debug("Bokeh: BokehJS is being loaded, scheduling callback at", now());
      return null;
    }
    if (js_urls == null || js_urls.length === 0) {
      run_callbacks();
      return null;
    }
    console.debug("Bokeh: BokehJS not loaded, scheduling load and callback at", now());
    root._bokeh_is_loading = css_urls.length + js_urls.length;

    function on_load() {
      root._bokeh_is_loading--;
      if (root._bokeh_is_loading === 0) {
        console.debug("Bokeh: all BokehJS libraries/stylesheets loaded");
        run_callbacks()
      }
    }

    function on_error(url) {
      console.error("failed to load " + url);
    }

    for (let i = 0; i < css_urls.length; i++) {
      const url = css_urls[i];
      const element = document.createElement("link");
      element.onload = on_load;
      element.onerror = on_error.bind(null, url);
      element.rel = "stylesheet";
      element.type = "text/css";
      element.href = url;
      console.debug("Bokeh: injecting link tag for BokehJS stylesheet: ", url);
      document.body.appendChild(element);
    }

    for (let i = 0; i < js_urls.length; i++) {
      const url = js_urls[i];
      const element = document.createElement('script');
      element.onload = on_load;
      element.onerror = on_error.bind(null, url);
      element.async = false;
      element.src = url;
      console.debug("Bokeh: injecting script tag for BokehJS library: ", url);
      document.head.appendChild(element);
    }
  };

  function inject_raw_css(css) {
    const element = document.createElement("style");
    element.appendChild(document.createTextNode(css));
    document.body.appendChild(element);
  }

  const js_urls = ["https://cdn.bokeh.org/bokeh/release/bokeh-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-gl-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-widgets-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-tables-2.4.3.min.js", "https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-2.4.3.min.js"];
  const css_urls = [];

  const inline_js = [    function(Bokeh) {
      Bokeh.set_log_level("info");
    },
function(Bokeh) {
    }
  ];

  function run_inline_js() {
    if (root.Bokeh !== undefined || force === true) {
          for (let i = 0; i < inline_js.length; i++) {
      inline_js[i].call(root, root.Bokeh);
    }
} else if (Date.now() < root._bokeh_timeout) {
      setTimeout(run_inline_js, 100);
    } else if (!root._bokeh_failed_load) {
      console.log("Bokeh: BokehJS failed to load within specified timeout.");
      root._bokeh_failed_load = true;
    } else if (force !== true) {
      const cell = $(document.getElementById(null)).parents('.cell').data().cell;
      cell.output_area.append_execute_result(NB_LOAD_WARNING)
    }
  }

  if (root._bokeh_is_loading === 0) {
    console.debug("Bokeh: BokehJS loaded, going straight to plotting");
    run_inline_js();
  } else {
    load_libs(css_urls, js_urls, function() {
      console.debug("Bokeh: BokehJS plotting callback run at", now());
      run_inline_js();
    });
  }
}(window));</script>
  <div class="bk-root" id="567c4106-f6c4-41f7-bfb6-0aca5bdc7c42" data-root-id="3550"></div>
<script>(function(root) {
  function embed_document(root) {
  const docs_json = {"104cdd4c-c6c2-4012-b05c-854ec2a8c186":{"defs":[],"roots":{"references":[{"attributes":{"below":[{"id":"3561"}],"center":[{"id":"3564"},{"id":"3568"},{"id":"3599"}],"left":[{"id":"3565"}],"renderers":[{"id":"3587"}],"title":{"id":"3551"},"toolbar":{"id":"3576"},"x_range":{"id":"3553"},"x_scale":{"id":"3557"},"y_range":{"id":"3555"},"y_scale":{"id":"3559"}},"id":"3550","subtype":"Figure","type":"Plot"},{"attributes":{"coordinates":null,"data_source":{"id":"3583"},"glyph":{"id":"3584"},"group":null,"hover_glyph":null,"muted_glyph":{"id":"3586"},"nonselection_glyph":{"id":"3585"},"view":{"id":"3588"}},"id":"3587","type":"GlyphRenderer"},{"attributes":{},"id":"3569","type":"PanTool"},{"attributes":{},"id":"3557","type":"LinearScale"},{"attributes":{"overlay":{"id":"3575"}},"id":"3571","type":"BoxZoomTool"},{"attributes":{},"id":"3592","type":"AllLabels"},{"attributes":{},"id":"3562","type":"BasicTicker"},{"attributes":{"line_alpha":0.1,"line_color":"#1f77b4","line_width":2,"x":{"field":"x"},"y":{"field":"y"}},"id":"3585","type":"Line"},{"attributes":{"line_alpha":0.2,"line_color":"#1f77b4","line_width":2,"x":{"field":"x"},"y":{"field":"y"}},"id":"3586","type":"Line"},{"attributes":{},"id":"3555","type":"DataRange1d"},{"attributes":{},"id":"3595","type":"AllLabels"},{"attributes":{"source":{"id":"3583"}},"id":"3588","type":"CDSView"},{"attributes":{},"id":"3553","type":"DataRange1d"},{"attributes":{},"id":"3573","type":"ResetTool"},{"attributes":{},"id":"3597","type":"Selection"},{"attributes":{"line_color":"#1f77b4","line_width":2,"x":{"field":"x"},"y":{"field":"y"}},"id":"3584","type":"Line"},{"attributes":{"axis_label":"x","coordinates":null,"formatter":{"id":"3594"},"group":null,"major_label_policy":{"id":"3595"},"ticker":{"id":"3562"}},"id":"3561","type":"LinearAxis"},{"attributes":{"axis":{"id":"3565"},"coordinates":null,"dimension":1,"group":null,"ticker":null},"id":"3568","type":"Grid"},{"attributes":{},"id":"3596","type":"UnionRenderers"},{"attributes":{},"id":"3591","type":"BasicTickFormatter"},{"attributes":{},"id":"3559","type":"LinearScale"},{"attributes":{"coordinates":null,"group":null,"text":"Simple line example"},"id":"3551","type":"Title"},{"attributes":{"axis":{"id":"3561"},"coordinates":null,"group":null,"ticker":null},"id":"3564","type":"Grid"},{"attributes":{"axis_label":"y","coordinates":null,"formatter":{"id":"3591"},"group":null,"major_label_policy":{"id":"3592"},"ticker":{"id":"3566"}},"id":"3565","type":"LinearAxis"},{"attributes":{"label":{"value":"Temp."},"renderers":[{"id":"3587"}]},"id":"3600","type":"LegendItem"},{"attributes":{"tools":[{"id":"3569"},{"id":"3570"},{"id":"3571"},{"id":"3572"},{"id":"3573"},{"id":"3574"}]},"id":"3576","type":"Toolbar"},{"attributes":{"bottom_units":"screen","coordinates":null,"fill_alpha":0.5,"fill_color":"lightgrey","group":null,"left_units":"screen","level":"overlay","line_alpha":1.0,"line_color":"black","line_dash":[4,4],"line_width":2,"right_units":"screen","syncable":false,"top_units":"screen"},"id":"3575","type":"BoxAnnotation"},{"attributes":{"data":{"x":[1,2,3,4,5],"y":[6,7,2,4,5]},"selected":{"id":"3597"},"selection_policy":{"id":"3596"}},"id":"3583","type":"ColumnDataSource"},{"attributes":{},"id":"3594","type":"BasicTickFormatter"},{"attributes":{"coordinates":null,"group":null,"items":[{"id":"3600"}]},"id":"3599","type":"Legend"},{"attributes":{},"id":"3570","type":"WheelZoomTool"},{"attributes":{},"id":"3566","type":"BasicTicker"},{"attributes":{},"id":"3574","type":"HelpTool"},{"attributes":{},"id":"3572","type":"SaveTool"}],"root_ids":["3550"]},"title":"Bokeh Application","version":"2.4.3"}};
  const render_items = [{"docid":"104cdd4c-c6c2-4012-b05c-854ec2a8c186","root_ids":["3550"],"roots":{"3550":"567c4106-f6c4-41f7-bfb6-0aca5bdc7c42"}}];
  root.Bokeh.embed.embed_items_notebook(docs_json, render_items);
  }
  if (root.Bokeh !== undefined) {
    embed_document(root);
  } else {
    let attempts = 0;
    const timer = setInterval(function(root) {
      if (root.Bokeh !== undefined) {
        clearInterval(timer);
        embed_document(root);
      } else {
        attempts++;
        if (attempts > 100) {
          clearInterval(timer);
          console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
        }
      }
    }, 10, root)
  }
})(window);</script>

Line Plot from Bokeh ::: :::



</details>

So probably something in `fixupBokehCells()` does not correctly merge the two output display data produces 
prairie-guy commented 11 months ago

@cderv Thanks for following this bug. I’m new to Quarto and have been very impressed! I’m an emacs guy that lives in a Microsoft Office World, so being able to use a mark up based language to generate docx, ppt, pdf is great. Keep up the great work!

One thing I haven’t been able to figure out is how to use Quarto with Bokeh (or Plotly) to render pdf or docx documents. Is this even possible as these are dynamic JS based packages?

Thanks

cscheid commented 11 months ago

One thing I haven’t been able to figure out is how to use Quarto with Bokeh (or Plotly) to render pdf or docx documents. Is this even possible as these are dynamic JS based packages?

It's not currently possible, exactly for the reason you point out. Some libraries know how to emit static output, but not all do. I believe Bokeh and Plotly don't.

We have plans to use an offscreen HTML renderer and take screenshots for the non-HTML formats, but the situation is more complicated than it might look at a glance.

prairie-guy commented 11 months ago

Thanks. Keep us posted. What you describe would be a great feature as those packages create great looking content. Probably many more use cases.

cderv commented 11 months ago

I believe Bokeh and Plotly don't.

Plotly as a way see https://plotly.com/python/static-image-export/ and example at https://github.com/quarto-dev/quarto-cli/issues/4225#issuecomment-1816064725

For bokeh, it is a bit more complex to export to image (https://docs.bokeh.org/en/2.4.1/docs/user_guide/export.html) as it requires Selenium

cscheid commented 11 months ago

Plotly as a way see https://plotly.com/python/static-image-export/ and example at https://github.com/quarto-dev/quarto-cli/issues/4225#issuecomment-1816064725

My point that they could/should be doing that by default and emitting an image in the notebook itself.

cderv commented 11 months ago

Oh I see. If they can do that this is indeed what we want !! I'll see if there are issues about this in there repo.

cscheid commented 11 months ago

WRT original report: I wish bokeh emitted better output in this case. They're emitting two separate cells, one with text/html and another with application/javascript:

   "outputs": [
    {
     "data": {
      "text/html": [
       "\n",
       "  <div class=\"bk-root\" id=\"ff6b5ddd-9a7b-49ff-850a-2d676060cc5c\" data-root-id=\"1083\"></div>\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "application/javascript": [
       "(function(root) {\n",
       "  function embed_document(root) {\n",
       "  const docs_json = {\"d2a1b461-05 ... },\"view\":{\"id\":\"1121\"}},\"id\":\"1120\",\"type\":\"GlyphRenderer\"}],\"root_ids\":[\"1083\"]},\"title\":\"Bokeh Application\",\"version\":\"2.4.3\"}};\n",
       "  const render_items = [{\"docid\":\"d2a1b461-05ae-48dd-818e-bced8fb6ab08\",\"root_ids\":[\"1083\"],\"roots\":{\"1083\":\"ff6b5ddd-9a7b-49ff-850a-2d676060cc5c\"}}];\n",
       "  root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n",
       "  }\n",
       "  if (root.Bokeh !== undefined) {\n",
       "    embed_document(root);\n",
       "  } else {\n",
       "    let attempts = 0;\n",
       "    const timer = setInterval(function(root) {\n",
       "      if (root.Bokeh !== undefined) {\n",
       "        clearInterval(timer);\n",
       "        embed_document(root);\n",
       "      } else {\n",
       "        attempts++;\n",
       "        if (attempts > 100) {\n",
       "          clearInterval(timer);\n",
       "          console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n",
       "        }\n",
       "      }\n",
       "    }, 10, root)\n",
       "  }\n",
       "})(window);"
      ],
      "application/vnd.bokehjs_exec.v0+json": ""
     },

We can add a workaround that tries to detect a cell with "application/vnd.bokehjs_exec.v0+json": "", application/javascript, and a previous cell that looks like it might have come from bokeh, and merge all of them. It's a dangerous move, though, because we can't know for sure that the previous cell was indeed generated by bokeh (since they don't actually mark it as such).

It's not clearly a bug because I bet they only test the rendered .ipynb result on jupyter lab or jupyter notebook. But it sure feels like an unfortunate way to generate results.

cscheid commented 11 months ago

They could, for example, add metadata to the cells so that they would be parseable as a "single bokeh figure". I'm somewhat surprised that this isn't already a jupyter feature. I wonder how jupyterbook handles this.

cderv commented 11 months ago

Really unfortunate indeed. It still puzzles me why it works when single python cells, but not when there is another cell before ? Isn't the bokeh plot outputing two separate cells in both cases ? 🤔

cscheid commented 11 months ago

Isn't the bokeh plot outputing two separate cells in both cases ? 🤔

You are correct, and my understanding is wrong. Let me revisit and I'll get back to you.

cscheid commented 11 months ago

What's happening is that fixupBokehCells is wrong about how bokeh cells are emitted.

cscheid commented 11 months ago

We need a corpus of bokeh plots to do this well. I kind of didn't want to do this for 1.4, and now I really don't want to do this for 1.4.

prairie-guy commented 11 months ago

Not in 1.4?🥲

cscheid commented 11 months ago

PR's are always welcome. We unfortunately have a lot else on our plate that's higher priority.

prairie-guy commented 11 months ago

@cderv - Your suggestion for using static-images in plotly worked great for me! I was able to render plotly images directly into standalone html, pdf and docx files. Exactly what I wanted to do. Here is my code.


---
title: "Testing Plotly"
format: 
  html: 
    embed-resources: true
  pdf: default
  docx: default
---

## Two Figures

```{python}
#| echo: false
#| output: false

import plotly.graph_objects as go
from IPython.display import Image
import numpy as np
np.random.seed(1)

N = 100
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
sz = np.random.rand(N) * 30

fig = go.Figure()
fig.add_trace(go.Scatter(
    x=x,
    y=y,
    mode="markers",
    marker=go.scatter.Marker(
        size=sz,
        color=colors,
        opacity=0.6,
        colorscale="Viridis"
    )
))
#| echo: false
#| label: "Figure 1" 
#| fig-cap: "Plotly Static Figure"
img_bytes = fig.to_image(format="png")
Image(img_bytes)
#| echo: false
#| output: false

import plotly.graph_objects as go

import pandas as pd
from datetime import datetime

df = pd.read_csv('https://raw.githubusercontent.com/plotly/datasets/master/finance-charts-apple.csv')

fig = go.Figure(data=[go.Candlestick(x=df['Date'],
                open=df['AAPL.Open'],
                high=df['AAPL.High'],
                low=df['AAPL.Low'],
                close=df['AAPL.Close'])])
#| echo: false
#| label: "Figure 2" 
#| fig-cap: "Plotly Second Figure"
img_bytes = fig.to_image(format="png")
Image(img_bytes)

Adding new cells for static output is the key
#| echo: false
#| label: "Figure 2" 
#| fig-cap: "Plotly Second Figure"
img_bytes = fig.to_image(format="png")
Image(img_bytes)

Maybe I’ve missed it, but is there a macro facility within Quarto so that I could abstract out the boiler plate code to create the static-image?
prairie-guy commented 11 months ago

PR's are always welcome. We unfortunately have a lot else on our plate that's higher priority.

Totally get it. I’m happy with plotly for now. Really appreciate all of your and @cderv time responding to me.

cderv commented 11 months ago

(If someone would tell me how to copy qmd into GitHub,

Look at https://quarto.org/bug-reports.html#formatting-make-githubs-markdown-work-for-us

Maybe I’ve missed it, but is there a macro facility within Quarto so that I could abstract out the boiler plate code to create the static-image?

You would need to do that probably using a Python function that produce some Markdown raw output directly (https://quarto.org/docs/computations/execution-options.html#raw-output). We some include feature to reuse content, but this would work only if you object from previous chunk before include is same name always (https://quarto.org/docs/authoring/includes.html)

y9c commented 11 months ago

Thank you @prairie-guy and @cderv.

I have tested both bokeh and plotly just now, but it seems that plotly causes another bug in rendering the figures in html format. Both the title and figure legend are not correct. bokeh works fine, but only when one chunk is rendered.

The output:

image

The code:

---
title: "Testing Plot"
format:
  html:
    toc: true
    embed-resources: true
    standalone: true
execute:
  cache: false
  echo: false
jupyter: python3
---

## Figure by plotly

```{python}
#| label: fig-1
#| fig-cap: "Plotly First Figure"

import plotly.graph_objects as go
from IPython.display import Image
import numpy as np
np.random.seed(1)
N = 100
x = np.random.rand(N)
y = np.random.rand(N)
colors = np.random.rand(N)
sz = np.random.rand(N) * 30
fig = go.Figure()
fig.add_trace(go.Scatter(
    x=x,
    y=y,
    mode="markers",
    marker=go.scatter.Marker(
        size=sz,
        color=colors,
        opacity=0.6,
        colorscale="Viridis"
    )
))
fig.show()

Figure by bokeh

#| label: fig-2
#| fig-cap: "Line Plot from Bokeh"

from bokeh.plotting import figure, show
from bokeh.io import output_notebook
output_notebook(hide_banner=True)
# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]
# create a new plot with a title and axis labels
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")
# add a line renderer with legend and line thickness
p.line(x, y, legend_label="Temp.", line_width=2)
# show the results
show(p)
cscheid commented 11 months ago

I can't seem to reproduce that:

image
prairie-guy commented 11 months ago

@y9c - Using your code with quarto preview I get the same results as @cscheid.

image
y9c commented 11 months ago

@prairie-guy

This bug is trigger when the preview mode is on.

quarto preview test.qmd


Quarto 1.4.526 [✓] Checking versions of quarto binary dependencies... Pandoc version 3.1.9: OK Dart Sass version 1.69.5: OK Deno version 1.37.2: OK

y9c commented 11 months ago

It is wired that when stop the preview, then run the render command, and restart the preview command, the problem won't happen again. It might be relative to the cache of the preview mode.

cscheid commented 11 months ago

This bug is trigger when the preview mode is on.

preview and render use the same rendering path when converting the .ipynb file to markdown, so I suspect there's something else happening.

cderv commented 11 months ago

it would be awesome if we can keep issue thread clean and scoped to original issue. I understand that issues can be related, but linking / cross reference issues by there number is clear enough for us to know they are related.

This would help keep discussion scoped and clear.

@y9c I can't reproduced either by copy pasting your example and either runnning quarto preview or quarto render.

Please to check your configuration and open a new issues withh all the information and examples so that you can reproduce. Sharing a reprex as github repo could insure with all get the same files. Thanks !

cderv commented 5 months ago

Looking again at this issue we have now this warning showing when rendering the example from https://github.com/quarto-dev/quarto-cli/issues/7967#issuecomment-1862491326

WARN: jupyter-fixup: cells without output data. Will not fixup bokeh cell

So definitely something with the Jupyter fixup we do for bokeh

We do generated two figures in the intermediate.md when there is only one expected.

::: {#fig-4 .cell execution_count=2}
``` {.python .cell-code}
from bokeh.plotting import figure, show

# prepare some data
x = [1, 2, 3, 4, 5]
y = [6, 7, 2, 4, 5]

# create a new plot with a title and axis labels
p = figure(title="Simple line example", x_axis_label="x", y_axis_label="y")

# add a line renderer with legend and line thickness
p.line(x, y, legend_label="Temp.", line_width=2)

# show the results
show(p)

::: {#fig-4-1 .cell-output .cell-output-display}


  <div id="fbd0b477-c829-4bc2-9a1e-1ad472aa1e86" data-root-id="p1001" style="display: contents;"></div>

Line Plot from Bokeh :::

::: {#fig-4-2 .cell-output .cell-output-display}

<script type="application/javascript">
(function(root) {
  function embed_document(root) {
  const docs_json = {"4d4cf6a6-54f1-4d4a-a143-e41d5b6564c7":{"version":"3.4.1","title":"Bokeh Application","roots":[{"type":"object","name":"Figure","id":"p1001","attributes":{"x_range":{"type":"object","name":"DataRange1d","id":"p1002"},"y_range":{"type":"object","name":"DataRange1d","id":"p1003"},"x_scale":{"type":"object","name":"LinearScale","id":"p1011"},"y_scale":{"type":"object","name":"LinearScale","id":"p1012"},"title":{"type":"object","name":"Title","id":"p1004","attributes":{"text":"Simple line example"}},"renderers":[{"type":"object","name":"GlyphRenderer","id":"p1040","attributes":{"data_source":{"type":"object","name":"ColumnDataSource","id":"p1034","attributes":{"selected":{"type":"object","name":"Selection","id":"p1035","attributes":{"indices":[],"line_indices":[]}},"selection_policy":{"type":"object","name":"UnionRenderers","id":"p1036"},"data":{"type":"map","entries":[["x",[1,2,3,4,5]],["y",[6,7,2,4,5]]]}}},"view":{"type":"object","name":"CDSView","id":"p1041","attributes":{"filter":{"type":"object","name":"AllIndices","id":"p1042"}}},"glyph":{"type":"object","name":"Line","id":"p1037","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f77b4","line_width":2}},"nonselection_glyph":{"type":"object","name":"Line","id":"p1038","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f77b4","line_alpha":0.1,"line_width":2}},"muted_glyph":{"type":"object","name":"Line","id":"p1039","attributes":{"x":{"type":"field","field":"x"},"y":{"type":"field","field":"y"},"line_color":"#1f77b4","line_alpha":0.2,"line_width":2}}}}],"toolbar":{"type":"object","name":"Toolbar","id":"p1010","attributes":{"tools":[{"type":"object","name":"PanTool","id":"p1023"},{"type":"object","name":"WheelZoomTool","id":"p1024","attributes":{"renderers":"auto"}},{"type":"object","name":"BoxZoomTool","id":"p1025","attributes":{"overlay":{"type":"object","name":"BoxAnnotation","id":"p1026","attributes":{"syncable":false,"level":"overlay","visible":false,"left":{"type":"number","value":"nan"},"right":{"type":"number","value":"nan"},"top":{"type":"number","value":"nan"},"bottom":{"type":"number","value":"nan"},"left_units":"canvas","right_units":"canvas","top_units":"canvas","bottom_units":"canvas","line_color":"black","line_alpha":1.0,"line_width":2,"line_dash":[4,4],"fill_color":"lightgrey","fill_alpha":0.5}}}},{"type":"object","name":"SaveTool","id":"p1031"},{"type":"object","name":"ResetTool","id":"p1032"},{"type":"object","name":"HelpTool","id":"p1033"}]}},"left":[{"type":"object","name":"LinearAxis","id":"p1018","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1019","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1020"},"axis_label":"y","major_label_policy":{"type":"object","name":"AllLabels","id":"p1021"}}}],"below":[{"type":"object","name":"LinearAxis","id":"p1013","attributes":{"ticker":{"type":"object","name":"BasicTicker","id":"p1014","attributes":{"mantissas":[1,2,5]}},"formatter":{"type":"object","name":"BasicTickFormatter","id":"p1015"},"axis_label":"x","major_label_policy":{"type":"object","name":"AllLabels","id":"p1016"}}}],"center":[{"type":"object","name":"Grid","id":"p1017","attributes":{"axis":{"id":"p1013"}}},{"type":"object","name":"Grid","id":"p1022","attributes":{"dimension":1,"axis":{"id":"p1018"}}},{"type":"object","name":"Legend","id":"p1043","attributes":{"items":[{"type":"object","name":"LegendItem","id":"p1044","attributes":{"label":{"type":"value","value":"Temp."},"renderers":[{"id":"p1040"}]}}]}}]}}]}};
  const render_items = [{"docid":"4d4cf6a6-54f1-4d4a-a143-e41d5b6564c7","roots":{"p1001":"fbd0b477-c829-4bc2-9a1e-1ad472aa1e86"},"root_ids":["p1001"]}];
  void root.Bokeh.embed.embed_items_notebook(docs_json, render_items);
  }
  if (root.Bokeh !== undefined) {
    embed_document(root);
  } else {
    let attempts = 0;
    const timer = setInterval(function(root) {
      if (root.Bokeh !== undefined) {
        clearInterval(timer);
        embed_document(root);
      } else {
        attempts++;
        if (attempts > 100) {
          clearInterval(timer);
          console.log("Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing");
        }
      }
    }, 10, root)
  }
})(window);
</script>

::: :::



So we need to merge the two data `div` and `script` one output cell IMO. 
cderv commented 5 months ago

BTW https://github.com/quarto-dev/quarto-cli/blob/7bb03a08c8c2af592bc83cd2de39b8ed6c468d74/src/core/jupyter/jupyter-fixups.ts#L89-L91

I think we are in this case here. application/vnd.bokehjs_load.v0+json is used for this notebook. But it does not have 3 outputs as initialization oddly https://github.com/quarto-dev/quarto-cli/blob/7bb03a08c8c2af592bc83cd2de39b8ed6c468d74/src/core/jupyter/jupyter-fixups.ts#L103-L108

So we stop fixup, and do not merge the two-cells per output that was supposed to be done after https://github.com/quarto-dev/quarto-cli/blob/7bb03a08c8c2af592bc83cd2de39b8ed6c468d74/src/core/jupyter/jupyter-fixups.ts#L112-L123

It seems from the fixup code that we expect cell.outputs to have everything but it seems we have now different cells for the initialization and then the outputs.

As something changed in Jupyter and Bokeh and how they output ? 🤔

We added the warning at 414f5f5d6286f281b13f912cae13eebdb4269b0c to handle this case where the expected outputs weren't there...

.quarto_ipynb produced ````json { "cells": [ { "cell_type": "markdown", "id": "c76d1f8e", "metadata": {}, "source": [ "---\n", "title: \"test \"\n", "format:\n", " html: default\n", "keep-md: true\n", "---" ] }, { "cell_type": "code", "execution_count": 1, "id": "7fefab1f", "metadata": {}, "outputs": [ { "data": { "application/javascript": [ "(function(root) {\n", " function now() {\n", " return new Date();\n", " }\n", "\n", " const force = true;\n", "\n", " if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n", " root._bokeh_onload_callbacks = [];\n", " root._bokeh_is_loading = undefined;\n", " }\n", "\n", "const JS_MIME_TYPE = 'application/javascript';\n", " const HTML_MIME_TYPE = 'text/html';\n", " const EXEC_MIME_TYPE = 'application/vnd.bokehjs_exec.v0+json';\n", " const CLASS_NAME = 'output_bokeh rendered_html';\n", "\n", " /**\n", " * Render data to the DOM node\n", " */\n", " function render(props, node) {\n", " const script = document.createElement(\"script\");\n", " node.appendChild(script);\n", " }\n", "\n", " /**\n", " * Handle when an output is cleared or removed\n", " */\n", " function handleClearOutput(event, handle) {\n", " function drop(id) {\n", " const view = Bokeh.index.get_by_id(id)\n", " if (view != null) {\n", " view.model.document.clear()\n", " Bokeh.index.delete(view)\n", " }\n", " }\n", "\n", " const cell = handle.cell;\n", "\n", " const id = cell.output_area._bokeh_element_id;\n", " const server_id = cell.output_area._bokeh_server_id;\n", "\n", " // Clean up Bokeh references\n", " if (id != null) {\n", " drop(id)\n", " }\n", "\n", " if (server_id !== undefined) {\n", " // Clean up Bokeh references\n", " const cmd_clean = \"from bokeh.io.state import curstate; print(curstate().uuid_to_server['\" + server_id + \"'].get_sessions()[0].document.roots[0]._id)\";\n", " cell.notebook.kernel.execute(cmd_clean, {\n", " iopub: {\n", " output: function(msg) {\n", " const id = msg.content.text.trim()\n", " drop(id)\n", " }\n", " }\n", " });\n", " // Destroy server and session\n", " const cmd_destroy = \"import bokeh.io.notebook as ion; ion.destroy_server('\" + server_id + \"')\";\n", " cell.notebook.kernel.execute(cmd_destroy);\n", " }\n", " }\n", "\n", " /**\n", " * Handle when a new output is added\n", " */\n", " function handleAddOutput(event, handle) {\n", " const output_area = handle.output_area;\n", " const output = handle.output;\n", "\n", " // limit handleAddOutput to display_data with EXEC_MIME_TYPE content only\n", " if ((output.output_type != \"display_data\") || (!Object.prototype.hasOwnProperty.call(output.data, EXEC_MIME_TYPE))) {\n", " return\n", " }\n", "\n", " const toinsert = output_area.element.find(\".\" + CLASS_NAME.split(' ')[0]);\n", "\n", " if (output.metadata[EXEC_MIME_TYPE][\"id\"] !== undefined) {\n", " toinsert[toinsert.length - 1].firstChild.textContent = output.data[JS_MIME_TYPE];\n", " // store reference to embed id on output_area\n", " output_area._bokeh_element_id = output.metadata[EXEC_MIME_TYPE][\"id\"];\n", " }\n", " if (output.metadata[EXEC_MIME_TYPE][\"server_id\"] !== undefined) {\n", " const bk_div = document.createElement(\"div\");\n", " bk_div.innerHTML = output.data[HTML_MIME_TYPE];\n", " const script_attrs = bk_div.children[0].attributes;\n", " for (let i = 0; i < script_attrs.length; i++) {\n", " toinsert[toinsert.length - 1].firstChild.setAttribute(script_attrs[i].name, script_attrs[i].value);\n", " toinsert[toinsert.length - 1].firstChild.textContent = bk_div.children[0].textContent\n", " }\n", " // store reference to server id on output_area\n", " output_area._bokeh_server_id = output.metadata[EXEC_MIME_TYPE][\"server_id\"];\n", " }\n", " }\n", "\n", " function register_renderer(events, OutputArea) {\n", "\n", " function append_mime(data, metadata, element) {\n", " // create a DOM node to render to\n", " const toinsert = this.create_output_subarea(\n", " metadata,\n", " CLASS_NAME,\n", " EXEC_MIME_TYPE\n", " );\n", " this.keyboard_manager.register_events(toinsert);\n", " // Render to node\n", " const props = {data: data, metadata: metadata[EXEC_MIME_TYPE]};\n", " render(props, toinsert[toinsert.length - 1]);\n", " element.append(toinsert);\n", " return toinsert\n", " }\n", "\n", " /* Handle when an output is cleared or removed */\n", " events.on('clear_output.CodeCell', handleClearOutput);\n", " events.on('delete.Cell', handleClearOutput);\n", "\n", " /* Handle when a new output is added */\n", " events.on('output_added.OutputArea', handleAddOutput);\n", "\n", " /**\n", " * Register the mime type and append_mime function with output_area\n", " */\n", " OutputArea.prototype.register_mime_type(EXEC_MIME_TYPE, append_mime, {\n", " /* Is output safe? */\n", " safe: true,\n", " /* Index of renderer in `output_area.display_order` */\n", " index: 0\n", " });\n", " }\n", "\n", " // register the mime type if in Jupyter Notebook environment and previously unregistered\n", " if (root.Jupyter !== undefined) {\n", " const events = require('base/js/events');\n", " const OutputArea = require('notebook/js/outputarea').OutputArea;\n", "\n", " if (OutputArea.prototype.mime_types().indexOf(EXEC_MIME_TYPE) == -1) {\n", " register_renderer(events, OutputArea);\n", " }\n", " }\n", " if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n", " root._bokeh_timeout = Date.now() + 5000;\n", " root._bokeh_failed_load = false;\n", " }\n", "\n", " const NB_LOAD_WARNING = {'data': {'text/html':\n", " \"
\\n\"+\n", " \"

\\n\"+\n", " \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n", " \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n", " \"

\\n\"+\n", " \"
    \\n\"+\n", " \"
  • re-rerun `output_notebook()` to attempt to load from CDN again, or
  • \\n\"+\n", " \"
  • use INLINE resources instead, as so:
  • \\n\"+\n", " \"
\\n\"+\n", " \"\\n\"+\n", " \"from bokeh.resources import INLINE\\n\"+\n", " \"output_notebook(resources=INLINE)\\n\"+\n", " \"\\n\"+\n", " \"
\"}};\n", "\n", " function display_loaded() {\n", " const el = document.getElementById(null);\n", " if (el != null) {\n", " el.textContent = \"BokehJS is loading...\";\n", " }\n", " if (root.Bokeh !== undefined) {\n", " if (el != null) {\n", " el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n", " }\n", " } else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(display_loaded, 100)\n", " }\n", " }\n", "\n", " function run_callbacks() {\n", " try {\n", " root._bokeh_onload_callbacks.forEach(function(callback) {\n", " if (callback != null)\n", " callback();\n", " });\n", " } finally {\n", " delete root._bokeh_onload_callbacks\n", " }\n", " console.debug(\"Bokeh: all callbacks have finished\");\n", " }\n", "\n", " function load_libs(css_urls, js_urls, callback) {\n", " if (css_urls == null) css_urls = [];\n", " if (js_urls == null) js_urls = [];\n", "\n", " root._bokeh_onload_callbacks.push(callback);\n", " if (root._bokeh_is_loading > 0) {\n", " console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n", " return null;\n", " }\n", " if (js_urls == null || js_urls.length === 0) {\n", " run_callbacks();\n", " return null;\n", " }\n", " console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n", " root._bokeh_is_loading = css_urls.length + js_urls.length;\n", "\n", " function on_load() {\n", " root._bokeh_is_loading--;\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n", " run_callbacks()\n", " }\n", " }\n", "\n", " function on_error(url) {\n", " console.error(\"failed to load \" + url);\n", " }\n", "\n", " for (let i = 0; i < css_urls.length; i++) {\n", " const url = css_urls[i];\n", " const element = document.createElement(\"link\");\n", " element.onload = on_load;\n", " element.onerror = on_error.bind(null, url);\n", " element.rel = \"stylesheet\";\n", " element.type = \"text/css\";\n", " element.href = url;\n", " console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n", " document.body.appendChild(element);\n", " }\n", "\n", " for (let i = 0; i < js_urls.length; i++) {\n", " const url = js_urls[i];\n", " const element = document.createElement('script');\n", " element.onload = on_load;\n", " element.onerror = on_error.bind(null, url);\n", " element.async = false;\n", " element.src = url;\n", " console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n", " document.head.appendChild(element);\n", " }\n", " };\n", "\n", " function inject_raw_css(css) {\n", " const element = document.createElement(\"style\");\n", " element.appendChild(document.createTextNode(css));\n", " document.body.appendChild(element);\n", " }\n", "\n", " const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.3.4.min.js\"];\n", " const css_urls = [];\n", "\n", " const inline_js = [ function(Bokeh) {\n", " Bokeh.set_log_level(\"info\");\n", " },\n", "function(Bokeh) {\n", " }\n", " ];\n", "\n", " function run_inline_js() {\n", " if (root.Bokeh !== undefined || force === true) {\n", " for (let i = 0; i < inline_js.length; i++) {\n", " inline_js[i].call(root, root.Bokeh);\n", " }\n", "} else if (Date.now() < root._bokeh_timeout) {\n", " setTimeout(run_inline_js, 100);\n", " } else if (!root._bokeh_failed_load) {\n", " console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n", " root._bokeh_failed_load = true;\n", " } else if (force !== true) {\n", " const cell = $(document.getElementById(null)).parents('.cell').data().cell;\n", " cell.output_area.append_execute_result(NB_LOAD_WARNING)\n", " }\n", " }\n", "\n", " if (root._bokeh_is_loading === 0) {\n", " console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n", " run_inline_js();\n", " } else {\n", " load_libs(css_urls, js_urls, function() {\n", " console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n", " run_inline_js();\n", " });\n", " }\n", "}(window));" ], "application/vnd.bokehjs_load.v0+json": "(function(root) {\n function now() {\n return new Date();\n }\n\n const force = true;\n\n if (typeof root._bokeh_onload_callbacks === \"undefined\" || force === true) {\n root._bokeh_onload_callbacks = [];\n root._bokeh_is_loading = undefined;\n }\n\n\n if (typeof (root._bokeh_timeout) === \"undefined\" || force === true) {\n root._bokeh_timeout = Date.now() + 5000;\n root._bokeh_failed_load = false;\n }\n\n const NB_LOAD_WARNING = {'data': {'text/html':\n \"
\\n\"+\n \"

\\n\"+\n \"BokehJS does not appear to have successfully loaded. If loading BokehJS from CDN, this \\n\"+\n \"may be due to a slow or bad network connection. Possible fixes:\\n\"+\n \"

\\n\"+\n \"
    \\n\"+\n \"
  • re-rerun `output_notebook()` to attempt to load from CDN again, or
  • \\n\"+\n \"
  • use INLINE resources instead, as so:
  • \\n\"+\n \"
\\n\"+\n \"\\n\"+\n \"from bokeh.resources import INLINE\\n\"+\n \"output_notebook(resources=INLINE)\\n\"+\n \"\\n\"+\n \"
\"}};\n\n function display_loaded() {\n const el = document.getElementById(null);\n if (el != null) {\n el.textContent = \"BokehJS is loading...\";\n }\n if (root.Bokeh !== undefined) {\n if (el != null) {\n el.textContent = \"BokehJS \" + root.Bokeh.version + \" successfully loaded.\";\n }\n } else if (Date.now() < root._bokeh_timeout) {\n setTimeout(display_loaded, 100)\n }\n }\n\n function run_callbacks() {\n try {\n root._bokeh_onload_callbacks.forEach(function(callback) {\n if (callback != null)\n callback();\n });\n } finally {\n delete root._bokeh_onload_callbacks\n }\n console.debug(\"Bokeh: all callbacks have finished\");\n }\n\n function load_libs(css_urls, js_urls, callback) {\n if (css_urls == null) css_urls = [];\n if (js_urls == null) js_urls = [];\n\n root._bokeh_onload_callbacks.push(callback);\n if (root._bokeh_is_loading > 0) {\n console.debug(\"Bokeh: BokehJS is being loaded, scheduling callback at\", now());\n return null;\n }\n if (js_urls == null || js_urls.length === 0) {\n run_callbacks();\n return null;\n }\n console.debug(\"Bokeh: BokehJS not loaded, scheduling load and callback at\", now());\n root._bokeh_is_loading = css_urls.length + js_urls.length;\n\n function on_load() {\n root._bokeh_is_loading--;\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: all BokehJS libraries/stylesheets loaded\");\n run_callbacks()\n }\n }\n\n function on_error(url) {\n console.error(\"failed to load \" + url);\n }\n\n for (let i = 0; i < css_urls.length; i++) {\n const url = css_urls[i];\n const element = document.createElement(\"link\");\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.rel = \"stylesheet\";\n element.type = \"text/css\";\n element.href = url;\n console.debug(\"Bokeh: injecting link tag for BokehJS stylesheet: \", url);\n document.body.appendChild(element);\n }\n\n for (let i = 0; i < js_urls.length; i++) {\n const url = js_urls[i];\n const element = document.createElement('script');\n element.onload = on_load;\n element.onerror = on_error.bind(null, url);\n element.async = false;\n element.src = url;\n console.debug(\"Bokeh: injecting script tag for BokehJS library: \", url);\n document.head.appendChild(element);\n }\n };\n\n function inject_raw_css(css) {\n const element = document.createElement(\"style\");\n element.appendChild(document.createTextNode(css));\n document.body.appendChild(element);\n }\n\n const js_urls = [\"https://cdn.bokeh.org/bokeh/release/bokeh-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-gl-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-widgets-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-tables-3.3.4.min.js\", \"https://cdn.bokeh.org/bokeh/release/bokeh-mathjax-3.3.4.min.js\"];\n const css_urls = [];\n\n const inline_js = [ function(Bokeh) {\n Bokeh.set_log_level(\"info\");\n },\nfunction(Bokeh) {\n }\n ];\n\n function run_inline_js() {\n if (root.Bokeh !== undefined || force === true) {\n for (let i = 0; i < inline_js.length; i++) {\n inline_js[i].call(root, root.Bokeh);\n }\n} else if (Date.now() < root._bokeh_timeout) {\n setTimeout(run_inline_js, 100);\n } else if (!root._bokeh_failed_load) {\n console.log(\"Bokeh: BokehJS failed to load within specified timeout.\");\n root._bokeh_failed_load = true;\n } else if (force !== true) {\n const cell = $(document.getElementById(null)).parents('.cell').data().cell;\n cell.output_area.append_execute_result(NB_LOAD_WARNING)\n }\n }\n\n if (root._bokeh_is_loading === 0) {\n console.debug(\"Bokeh: BokehJS loaded, going straight to plotting\");\n run_inline_js();\n } else {\n load_libs(css_urls, js_urls, function() {\n console.debug(\"Bokeh: BokehJS plotting callback run at\", now());\n run_inline_js();\n });\n }\n}(window));" }, "metadata": {}, "output_type": "display_data" } ], "source": [ "from bokeh.io import output_notebook\n", "output_notebook(hide_banner=True)" ] }, { "cell_type": "code", "execution_count": 2, "id": "fig-4", "metadata": {}, "outputs": [ { "data": { "text/html": [ "\n", "
\n" ] }, "metadata": {}, "output_type": "display_data" }, { "data": { "application/javascript": [ "(function(root) {\n", " function embed_document(root) {\n", " const docs_json = {\"7a69bf2b-44b4-4ef5-89ad-944be4f5526f\":{\"version\":\"3.3.4\",\"title\":\"Bokeh Application\",\"roots\":[{\"type\":\"object\",\"name\":\"Figure\",\"id\":\"p1177\",\"attributes\":{\"x_range\":{\"type\":\"object\",\"name\":\"DataRange1d\",\"id\":\"p1178\"},\"y_range\":{\"type\":\"object\",\"name\":\"DataRange1d\",\"id\":\"p1179\"},\"x_scale\":{\"type\":\"object\",\"name\":\"LinearScale\",\"id\":\"p1187\"},\"y_scale\":{\"type\":\"object\",\"name\":\"LinearScale\",\"id\":\"p1188\"},\"title\":{\"type\":\"object\",\"name\":\"Title\",\"id\":\"p1180\",\"attributes\":{\"text\":\"Simple line example\"}},\"renderers\":[{\"type\":\"object\",\"name\":\"GlyphRenderer\",\"id\":\"p1216\",\"attributes\":{\"data_source\":{\"type\":\"object\",\"name\":\"ColumnDataSource\",\"id\":\"p1210\",\"attributes\":{\"selected\":{\"type\":\"object\",\"name\":\"Selection\",\"id\":\"p1211\",\"attributes\":{\"indices\":[],\"line_indices\":[]}},\"selection_policy\":{\"type\":\"object\",\"name\":\"UnionRenderers\",\"id\":\"p1212\"},\"data\":{\"type\":\"map\",\"entries\":[[\"x\",[1,2,3,4,5]],[\"y\",[6,7,2,4,5]]]}}},\"view\":{\"type\":\"object\",\"name\":\"CDSView\",\"id\":\"p1217\",\"attributes\":{\"filter\":{\"type\":\"object\",\"name\":\"AllIndices\",\"id\":\"p1218\"}}},\"glyph\":{\"type\":\"object\",\"name\":\"Line\",\"id\":\"p1213\",\"attributes\":{\"x\":{\"type\":\"field\",\"field\":\"x\"},\"y\":{\"type\":\"field\",\"field\":\"y\"},\"line_color\":\"#1f77b4\",\"line_width\":2}},\"nonselection_glyph\":{\"type\":\"object\",\"name\":\"Line\",\"id\":\"p1214\",\"attributes\":{\"x\":{\"type\":\"field\",\"field\":\"x\"},\"y\":{\"type\":\"field\",\"field\":\"y\"},\"line_color\":\"#1f77b4\",\"line_alpha\":0.1,\"line_width\":2}},\"muted_glyph\":{\"type\":\"object\",\"name\":\"Line\",\"id\":\"p1215\",\"attributes\":{\"x\":{\"type\":\"field\",\"field\":\"x\"},\"y\":{\"type\":\"field\",\"field\":\"y\"},\"line_color\":\"#1f77b4\",\"line_alpha\":0.2,\"line_width\":2}}}}],\"toolbar\":{\"type\":\"object\",\"name\":\"Toolbar\",\"id\":\"p1186\",\"attributes\":{\"tools\":[{\"type\":\"object\",\"name\":\"PanTool\",\"id\":\"p1199\"},{\"type\":\"object\",\"name\":\"WheelZoomTool\",\"id\":\"p1200\",\"attributes\":{\"renderers\":\"auto\"}},{\"type\":\"object\",\"name\":\"BoxZoomTool\",\"id\":\"p1201\",\"attributes\":{\"overlay\":{\"type\":\"object\",\"name\":\"BoxAnnotation\",\"id\":\"p1202\",\"attributes\":{\"syncable\":false,\"level\":\"overlay\",\"visible\":false,\"left\":{\"type\":\"number\",\"value\":\"nan\"},\"right\":{\"type\":\"number\",\"value\":\"nan\"},\"top\":{\"type\":\"number\",\"value\":\"nan\"},\"bottom\":{\"type\":\"number\",\"value\":\"nan\"},\"left_units\":\"canvas\",\"right_units\":\"canvas\",\"top_units\":\"canvas\",\"bottom_units\":\"canvas\",\"line_color\":\"black\",\"line_alpha\":1.0,\"line_width\":2,\"line_dash\":[4,4],\"fill_color\":\"lightgrey\",\"fill_alpha\":0.5}}}},{\"type\":\"object\",\"name\":\"SaveTool\",\"id\":\"p1207\"},{\"type\":\"object\",\"name\":\"ResetTool\",\"id\":\"p1208\"},{\"type\":\"object\",\"name\":\"HelpTool\",\"id\":\"p1209\"}]}},\"left\":[{\"type\":\"object\",\"name\":\"LinearAxis\",\"id\":\"p1194\",\"attributes\":{\"ticker\":{\"type\":\"object\",\"name\":\"BasicTicker\",\"id\":\"p1195\",\"attributes\":{\"mantissas\":[1,2,5]}},\"formatter\":{\"type\":\"object\",\"name\":\"BasicTickFormatter\",\"id\":\"p1196\"},\"axis_label\":\"y\",\"major_label_policy\":{\"type\":\"object\",\"name\":\"AllLabels\",\"id\":\"p1197\"}}}],\"below\":[{\"type\":\"object\",\"name\":\"LinearAxis\",\"id\":\"p1189\",\"attributes\":{\"ticker\":{\"type\":\"object\",\"name\":\"BasicTicker\",\"id\":\"p1190\",\"attributes\":{\"mantissas\":[1,2,5]}},\"formatter\":{\"type\":\"object\",\"name\":\"BasicTickFormatter\",\"id\":\"p1191\"},\"axis_label\":\"x\",\"major_label_policy\":{\"type\":\"object\",\"name\":\"AllLabels\",\"id\":\"p1192\"}}}],\"center\":[{\"type\":\"object\",\"name\":\"Grid\",\"id\":\"p1193\",\"attributes\":{\"axis\":{\"id\":\"p1189\"}}},{\"type\":\"object\",\"name\":\"Grid\",\"id\":\"p1198\",\"attributes\":{\"dimension\":1,\"axis\":{\"id\":\"p1194\"}}},{\"type\":\"object\",\"name\":\"Legend\",\"id\":\"p1219\",\"attributes\":{\"items\":[{\"type\":\"object\",\"name\":\"LegendItem\",\"id\":\"p1220\",\"attributes\":{\"label\":{\"type\":\"value\",\"value\":\"Temp.\"},\"renderers\":[{\"id\":\"p1216\"}]}}]}}]}}]}};\n", " const render_items = [{\"docid\":\"7a69bf2b-44b4-4ef5-89ad-944be4f5526f\",\"roots\":{\"p1177\":\"c07a6c7d-2ef6-40a3-ad08-e054a29bd67f\"},\"root_ids\":[\"p1177\"]}];\n", " root.Bokeh.embed.embed_items_notebook(docs_json, render_items);\n", " }\n", " if (root.Bokeh !== undefined) {\n", " embed_document(root);\n", " } else {\n", " let attempts = 0;\n", " const timer = setInterval(function(root) {\n", " if (root.Bokeh !== undefined) {\n", " clearInterval(timer);\n", " embed_document(root);\n", " } else {\n", " attempts++;\n", " if (attempts > 100) {\n", " clearInterval(timer);\n", " console.log(\"Bokeh: ERROR: Unable to run BokehJS code because BokehJS library is missing\");\n", " }\n", " }\n", " }, 10, root)\n", " }\n", "})(window);" ], "application/vnd.bokehjs_exec.v0+json": "" }, "metadata": { "application/vnd.bokehjs_exec.v0+json": { "id": "p1177" } }, "output_type": "display_data" } ], "source": [ "#| label: fig-4\n", "#| fig-cap: Line Plot from Bokeh\n", "\n", "from bokeh.plotting import figure, show\n", "\n", "# prepare some data\n", "x = [1, 2, 3, 4, 5]\n", "y = [6, 7, 2, 4, 5]\n", "\n", "# create a new plot with a title and axis labels\n", "p = figure(title=\"Simple line example\", x_axis_label=\"x\", y_axis_label=\"y\")\n", "\n", "# add a line renderer with legend and line thickness\n", "p.line(x, y, legend_label=\"Temp.\", line_width=2)\n", "\n", "# show the results\n", "show(p)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3", "path": "C:\\Users\\chris\\Documents\\DEV_R\\quarto-cli\\tests\\.venv\\share\\jupyter\\kernels\\python3" } }, "nbformat": 4, "nbformat_minor": 5 } ````