Skip to content

Support for plotly.py 6+ #12842

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

Merged
merged 4 commits into from
Jun 3, 2025
Merged

Support for plotly.py 6+ #12842

merged 4 commits into from
Jun 3, 2025

Conversation

cderv
Copy link
Collaborator

@cderv cderv commented May 27, 2025

Plotly.py 6+ has changed the way it includes the plotly.js library from

   "outputs": [
    {
     "data": {
      "text/html": [
       "        <script type=\"text/javascript\">\n",
       "        window.PlotlyConfig = {MathJaxConfig: 'local'};\n",
       "        if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}\n",
       "        if (typeof require !== 'undefined') {\n",
       "        require.undef(\"plotly\");\n",
       "        requirejs.config({\n",
       "            paths: {\n",
       "                'plotly': ['https://cdn.plot.ly/plotly-2.35.2.min']\n",
       "            }\n",
       "        });\n",
       "        require(['plotly'], function(Plotly) {\n",
       "            window._Plotly = Plotly;\n",
       "        });\n",
       "        }\n",
       "        </script>\n",
       "        "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },

to

   "outputs": [
    {
     "data": {
      "text/html": [
       "        <script type=\"text/javascript\">\n",
       "        window.PlotlyConfig = {MathJaxConfig: 'local'};\n",
       "        if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}\n",
       "        </script>\n",
       "        <script type=\"module\">import \"https://cdn.plot.ly/plotly-3.0.1.min\"</script>\n",
       "        "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },

So our Plotly library detection logic needs to adapt. I did not change it.

The logic we have in Quarto is to detect the cell with raw html containing this libary loading so that is moved in the content added to headers. The cell is cell removed and not shown in intermediate md.

This PR adds a test (with a new verify function) to ensure we have the expected structure with Plotly and subfigures (2 plotly graph should lead to 2 subfigures)

@posit-snyk-bot
Copy link
Collaborator

posit-snyk-bot commented May 27, 2025

🎉 Snyk checks have passed. No issues have been found so far.

security/snyk check is complete. No issues have been found. (View Details)

license/snyk check is complete. No issues have been found. (View Details)

@cderv cderv changed the title Support for plotly 6+ Support for plotly.py 6+ May 27, 2025
@cderv
Copy link
Collaborator Author

cderv commented May 27, 2025

But looking at the intermediate .quarto_ipynb content, I see something odd...

The following cell has

    {
     "data": {
      "text/html": [
       "<div>            <script src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG\"></script><script type=\"text/javascript\">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}</script>                <script type=\"text/javascript\">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>\n",
       "        <script charset=\"utf-8\" src=\"https://cdn.plot.ly/plotly-3.0.1.min.js\"></script>                <div id=\"3cc4492a-8e96-468d-9a9a-8e3a192a5397\" class=\"plotly-graph-div\" style=\"height:525px; width:100%;\"></div>            <script type=\"text/javascript\">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById(\"3cc4492a-8e96-468d-9a9a-8e3a192a5397\"

So somehow there is duplicated loading... This is very odd. Do we inject content in cell output 🤔 ? Or is it plotly.py?

Full .quarto_ipynb

{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4d52dd3f",
   "metadata": {},
   "source": [
    "---\n",
    "title: \"Bugged plotly figure: phantom subfigure\"\n",
    "keep-ipynb: true\n",
    "keep-md: true\n",
    "---"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "fig-gapminder",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "        <script type=\"text/javascript\">\n",
       "        window.PlotlyConfig = {MathJaxConfig: 'local'};\n",
       "        if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}\n",
       "        </script>\n",
       "        <script type=\"module\">import \"https://cdn.plot.ly/plotly-3.0.1.min\"</script>\n",
       "        "
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>            <script src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG\"></script><script type=\"text/javascript\">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}</script>                <script type=\"text/javascript\">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>\n",
       "        <script charset=\"utf-8\" src=\"https://cdn.plot.ly/plotly-3.0.1.min.js\"></script>                <div id=\"3cc4492a-8e96-468d-9a9a-8e3a192a5397\" class=\"plotly-graph-div\" style=\"height:525px; width:100%;\"></div>            <script type=\"text/javascript\">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById(\"3cc4492a-8e96-468d-9a9a-8e3a192a5397\")) {                    Plotly.newPlot(                        \"3cc4492a-8e96-468d-9a9a-8e3a192a5397\",                        [{\"hovertemplate\":\"\\u003cb\\u003e%{hovertext}\\u003c\\u002fb\\u003e\\u003cbr\\u003e\\u003cbr\\u003egdpPercap=%{x}\\u003cbr\\u003elifeExp=%{y}\\u003cbr\\u003epop=%{marker.size}\\u003cextra\\u003e\\u003c\\u002fextra\\u003e\",\"hovertext\":[\"Afghanistan\",\"Albania\",\"Algeria\",\"Angola\",\"Argentina\",\"Australia\",\"Austria\",\"Bahrain\",\"Bangladesh\",\"Belgium\",\"Benin\",\"Bolivia\",\"Bosnia and Herzegovina\",\"Botswana\",\"Brazil\",\"Bulgaria\",\"Burkina Faso\",\"Burundi\",\"Cambodia\",\"Cameroon\",\"Canada\",\"Central African Republic\",\"Chad\",\"Chile\",\"China\",\"Colombia\",\"Comoros\",\"Congo, Dem. Rep.\",\"Congo, Rep.\",\"Costa Rica\",\"Cote d'Ivoire\",\"Croatia\",\"Cuba\",\"Czech Republic\",\"Denmark\",\"Djibouti\",\"Dominican Republic\",\"Ecuador\",\"Egypt\",\"El Salvador\",\"Equatorial Guinea\",\"Eritrea\",\"Ethiopia\",\"Finland\",\"France\",\"Gabon\",\"Gambia\",\"Germany\",\"Ghana\",\"Greece\",\"Guatemala\",\"Guinea\",\"Guinea-Bissau\",\"Haiti\",\"Honduras\",\"Hong Kong, China\",\"Hungary\",\"Iceland\",\"India\",\"Indonesia\",\"Iran\",\"Iraq\",\"Ireland\",\"Israel\",\"Italy\",\"Jamaica\",\"Japan\",\"Jordan\",\"Kenya\",\"Korea, Dem. Rep.\",\"Korea, Rep.\",\"Kuwait\",\"Lebanon\",\"Lesotho\",\"Liberia\",\"Libya\",\"Madagascar\",\"Malawi\",\"Malaysia\",\"Mali\",\"Mauritania\",\"Mauritius\",\"Mexico\",\"Mongolia\",\"Montenegro\",\"Morocco\",\"Mozambique\",\"Myanmar\",\"Namibia\",\"Nepal\",\"Netherlands\",\"New Zealand\",\"Nicaragua\",\"Niger\",\"Nigeria\",\"Norway\",\"Oman\",\"Pakistan\",\"Panama\",\"Paraguay\",\"Peru\",\"Philippines\",\"Poland\",\"Portugal\",\"Puerto Rico\",\"Reunion\",\"Romania\",\"Rwanda\",\"Sao Tome and Principe\",\"Saudi Arabia\",\"Senegal\",\"Serbia\",\"Sierra Leone\",\"Singapore\",\"Slovak Republic\",\"Slovenia\",\"Somalia\",\"South Africa\",\"Spain\",\"Sri Lanka\",\"Sudan\",\"Swaziland\",\"Sweden\",\"Switzerland\",\"Syria\",\"Taiwan\",\"Tanzania\",\"Thailand\",\"Togo\",\"Trinidad and Tobago\",\"Tunisia\",\"Turkey\",\"Uganda\",\"United Kingdom\",\"United States\",\"Uruguay\",\"Venezuela\",\"Vietnam\",\"West Bank and Gaza\",\"Yemen, Rep.\",\"Zambia\",\"Zimbabwe\"],\"legendgroup\":\"\",\"marker\":{\"color\":\"#636efa\",\"size\":{\"dtype\":\"i4\",\"bdata\":\"ZgGNAJmHFgCIuJwA0ZlFAKo7KwG5M5QAZEpqAJ8dAgBcxg8DtymJADVgHQDaATEAoO8uAA8+BwBDO+gDtr90AMjrRwD+sygAKDdRADPJUQDqjQMBnD4VAAcsLADqjGsAABP+JekJ3QCwmwIATLPtAKpZDgDs+BAAoFoyAMrmPABwVGUAHiuRAJd6RACrGAEAsposABHtPQBNnn0BXfIjANqNAwDTiRcAfiNcAaD6QQBPIaQC2KIGAE7uBAA9qjsE+IVhANqJewAsjjcANuUrAAcsCQD1hTUAlgMbAKzAKQCYIZYA9oQCAEDYYBjgLl8FgAAuAcNYXwAM6ysAUasdADB17gJybBcAASR1BT9kCwA7wHEANZuPAGAGWQFuPwMANCMZABppDABO5A4AqlUSAO8QTwD2JjEAYxd2ANy5QAB0bhAAGE4JAHxLFgLWdQ0AzcEGAA4MrgBTZGsABJpLAfBcCACivZMAzz+oAJ8EIgDsuxQAmFY4AFw4NwJiSDUAOZMIAIhHyAJSOhAAlgUbAPSOiwCC1I0BUtauAfKLhgAgfCIA3LUEAM8NEAHCDysAje8AAEJwQwDTmy4A3\\u002fJuAH4HIwApEBYAtag6AI5kFwD\\u002fbCoA\\u002fXP2AM5YxwFiSosAMNOUAFX8BADaXHAAcDdOAJRSPwD3F5sAGj2QAP0bfgGFthQA5KsLAAFJPAAbtYcBLdxlAHDCEAOAREAKfwAlAExGZgCPe7oBZ1UQAOrkUwBABS4AhKM3AA==\"},\"sizemode\":\"area\",\"sizeref\":177057.77777777778,\"symbol\":\"circle\"},\"mode\":\"markers\",\"name\":\"\",\"orientation\":\"v\",\"showlegend\":false,\"x\":{\"dtype\":\"f8\",\"bdata\":\"KdQuAdOmiUBW8NsQI1meQEpiSbnzi6dAEeSghOHnrUBzqrUw28i6QOjewyXTYsVAAz4\\u002fjExFwUBlqmBU5rnGQKswbIMZrYRAZcix9Xr5wkAbDksDz\\u002fyNQMoYH2Zfn6BA9KeN6vQnlUBu9UE73LGMQGQD6WK7bqNAF4OHaVeBp0C7n2S8d0mDQHN+ebcIuXdA8QOhBp0ge0DiP91AMYSUQJ7qkJv5ZMhAY+yEl2CbkkCZfol4+3GUQHhHxmqf27BA+1S6YOX\\u002fgUCP4hx1nCeiQIuH9xyY7JJAOmxswOFOjEC9UwH3HBaiQHb51ocFXKdArfpcbZVzl0AzcEBLO\\u002fKwQMWayqIszLdAQUmBBSwgwEBPr5Rl1K3FQK++uirwYaZAzqW4qpwhmECnzqPiF4mtQMcrED2py5ZAW8064wu7qkAdSTjjiqF6QPBiqxWXgnVA8+PUc3eud0A3pbxWanm9QMAK8N1q68BAX7adtjJws0C5WoLnaUeAQJ\\u002fNqs\\u002fp5cNA+CmOAz9OkEBuh4bFTDSzQIMY6NpPcqRA2bW93SICgkCH3tG1pfx6QNRi8DCN+5pAsoF0sflYoUAaNV8lJ1qsQOxtMxUumLdA6bZELgAOwkDdQ8L3fnCCQGLwH8Ez14pAFobI6YO0qUAwuVFkVVW4QEZda+8T37VAipKQSEcJtUCXAtL+p2i4QNxnlZmGlLJAfZbnwbHdsEBodXKGUnidQLhKZquBg41A+RQA44mMmEDdzynIXz6XQBrAWyAyt\\u002ftAHrKBdMnJt0CSjfYu9P90QI1nK4rCZ4NAS7A4nJHwqkAOLbKdz9SYQKCdHbrqBXpAYwSNmURInEB1F81vHaZ+QMxzjkv2cIpAesN95CbIn0CnIarwiyOwQGpsrwVNhYxARIoBEoXErEBL6ZleAqiZQEV7UKtj+X5AAAAAAADgdUBssdtn5XqkQFsTHKh9r4JA3lSkwhgGxkC6g9iZsuvHQL1TAffUAqtAKmGm7S8cikBJx9XIXjKRQNogk4z8wsZAmMPuO36FoUAXclsRq1iHQGhcOBCaI6dA++k\\u002fa574n0DyyvW2QZWwQEp\\u002fL4XHL5hARGraxUB+skBvnX+7JH2tQP0tAfhPhq5AysUYWOeipUBTliGOvc6uQCRQDrBQ4oBAs7owLeXlikDVZwdcl92\\u002fQF7Ymq2cfphAgPChRBd1s0DHf4Eg4GOPQNGwGHU1NqZAgUOoUkPNt0AgfCjRRua2QAX4bvOWqJNAn6wYrhpvtUD8471qzdSxQLZHb7gvwpBAUzntKVmpm0CoVl9d1XKTQECmtWnwW8NAeH+8V1990UAeMuVDeIqgQH15AfZxj5dA\\u002fud67EjUhUDOkaaLnsyIQF+KYT1E74xAP8bctWQEsEB3ZRcM7syVQI83+S2CVaFAbvQg8\\u002fcyiEApyxDHlgnGQFXZd0WQ\\u002f8xA0t9L4cUGuEBevB+3OyXDQLct25hIIoVAQ6ooXkWMnEB6Cm\\u002fFpCaJQLNfd7rTf5RAAveXOB02gEA=\"},\"xaxis\":\"x\",\"y\":{\"dtype\":\"f8\",\"bdata\":\"okW28\\u002f1UPkCkcD0K16NNQEjhehSu10ZAObTIdr7\\u002fP0Coxks3iRlQQIXrUbgelVFAH4XrUbjeUEDRItv5fupKQAaBlUOLrENAj8L1KFxPUUDn+6nx0i1EQFK4HoXr8URAmpmZmZk5TUDJdr6fGs9IQBSuR+F6pEpA16NwPQqnUECHFtnO93NBQE5iEFg5RERAAyuHFtmuREAQWDm0yDZEQD0K16NwfVFACKwcWmS7QkBU46WbxPBDQLbz\\u002fdR4CUxA9Wc\\u002fUkRGSUDJdr6fGo9LQHsUrkfhOkVA+n5qvHRTREAQWDm0yIZGQBfZzvdTA05AeekmMQg8RUDhehSuRzFQQJqZmZmZKU9AUrgehetBUUCkcD0K1\\u002fNRQESLbOf7qUJARIts5\\u002fvpSEAhsHJoka1JQEa28\\u002f3UOEZAKVyPwvVISEDp+6nx0v1BQLx0kxgEBkNATDeJQWBVQkCPwvUoXN9QQOxRuB6FO1FAHVpkO99\\u002fQ0C4HoXrUQhAQGZmZmZmRlFAwcqhRbZjRkDXo3A9CvdQQBkEVg4tEkZAgZVDi2xHQUA830+Nl75AQAwCK4cWWURAhetRuB5VRkAAAAAAADBQQArXo3A9mlBArkfhehReUkAdWmQ73x9EQC\\u002fdJAaB9UNAukkMAiuXR0AOLbKd7zdIQJqZmZmZOVFA9ihcj8L1UECkcD0K1\\u002fNQQK5H4XoUTk9AAAAAAABgUEASg8DKodVGQCyHFtnOV0ZA7nw\\u002fNV4KS0C6SQwCK1dKQE5iEFg5BE1AO99PjZe+TUC8dJMYBIZGQJHtfD81vkNAokW28\\u002f2kRkAfhetRuG5DQNEi2\\u002fl+mkJAkxgEVg4NSkCc76fGS6dBQCUGgZVDK0VACKwcWmQLTUC4HoXrUZhLQDu0yHa+n0ZA001iEFi5TkCgGi\\u002fdJLZGQMHKoUW240BApHA9CtfzRECxcmiR7ZxGQCuHFtnO10JAj8L1KFw\\u002fUkBxPQrXo5BRQJ7vp8ZLt0ZABoGVQ4tMQ0Atsp3vp+ZCQFyPwvUoXFJACtejcD0KRECe76fGS8dGQH0\\u002fNV66mU1ADgIrhxaZT0CNbOf7qSFHQJhuEoPAqklA4XoUrkdxUEDhehSuR8FOQMP1KFyPIlFA7FG4HoWLS0BmZmZmZgZQQAAAAAAAwERAKVyPwvV4SEDJdr6fGm9FQCcxCKwcqkNASOF6FK7XTkBSuB6F65E\\u002fQPT91Hjplk9AzczMzMzcUEBmZmZmZvZQQJMYBFYOfUFArkfhehT+R0AK16NwPapQQO58PzVeuk5AHVpkO9\\u002fPQ0CDwMqhRbZFQI\\u002fC9ShcH1JApHA9CtejUUAxCKwcWiRIQDMzMzMzM09A6SYxCKx8RUBxPQrXo9BKQLTIdr6fmkRAZmZmZmbmTkDNzMzMzIxHQCYxCKwcCkhADgIrhxZJRUB7FK5H4ZpRQI\\u002fC9ShcX1FAiUFg5dDCUEBqvHSTGPRMQKjGSzeJcUVA2s73U+PVRkBcj8L1KPxAQGDl0CLbCUZAeekmMQg8SUA=\"},\"yaxis\":\"y\",\"type\":\"scatter\"}],                        {\"template\":{\"data\":{\"histogram2dcontour\":[{\"type\":\"histogram2dcontour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"choropleth\":[{\"type\":\"choropleth\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"histogram2d\":[{\"type\":\"histogram2d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmap\":[{\"type\":\"heatmap\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"contourcarpet\":[{\"type\":\"contourcarpet\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"contour\":[{\"type\":\"contour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"surface\":[{\"type\":\"surface\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"mesh3d\":[{\"type\":\"mesh3d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"scatter\":[{\"fillpattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2},\"type\":\"scatter\"}],\"parcoords\":[{\"type\":\"parcoords\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolargl\":[{\"type\":\"scatterpolargl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"bar\":[{\"error_x\":{\"color\":\"#2a3f5f\"},\"error_y\":{\"color\":\"#2a3f5f\"},\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"bar\"}],\"scattergeo\":[{\"type\":\"scattergeo\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolar\":[{\"type\":\"scatterpolar\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"histogram\":[{\"marker\":{\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"histogram\"}],\"scattergl\":[{\"type\":\"scattergl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatter3d\":[{\"type\":\"scatter3d\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattermap\":[{\"type\":\"scattermap\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattermapbox\":[{\"type\":\"scattermapbox\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterternary\":[{\"type\":\"scatterternary\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattercarpet\":[{\"type\":\"scattercarpet\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"carpet\":[{\"aaxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"baxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"type\":\"carpet\"}],\"table\":[{\"cells\":{\"fill\":{\"color\":\"#EBF0F8\"},\"line\":{\"color\":\"white\"}},\"header\":{\"fill\":{\"color\":\"#C8D4E3\"},\"line\":{\"color\":\"white\"}},\"type\":\"table\"}],\"barpolar\":[{\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"barpolar\"}],\"pie\":[{\"automargin\":true,\"type\":\"pie\"}]},\"layout\":{\"autotypenumbers\":\"strict\",\"colorway\":[\"#636efa\",\"#EF553B\",\"#00cc96\",\"#ab63fa\",\"#FFA15A\",\"#19d3f3\",\"#FF6692\",\"#B6E880\",\"#FF97FF\",\"#FECB52\"],\"font\":{\"color\":\"#2a3f5f\"},\"hovermode\":\"closest\",\"hoverlabel\":{\"align\":\"left\"},\"paper_bgcolor\":\"white\",\"plot_bgcolor\":\"#E5ECF6\",\"polar\":{\"bgcolor\":\"#E5ECF6\",\"angularaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"radialaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"ternary\":{\"bgcolor\":\"#E5ECF6\",\"aaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"baxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"caxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"coloraxis\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"colorscale\":{\"sequential\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"sequentialminus\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"diverging\":[[0,\"#8e0152\"],[0.1,\"#c51b7d\"],[0.2,\"#de77ae\"],[0.3,\"#f1b6da\"],[0.4,\"#fde0ef\"],[0.5,\"#f7f7f7\"],[0.6,\"#e6f5d0\"],[0.7,\"#b8e186\"],[0.8,\"#7fbc41\"],[0.9,\"#4d9221\"],[1,\"#276419\"]]},\"xaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"yaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"scene\":{\"xaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"yaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"zaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2}},\"shapedefaults\":{\"line\":{\"color\":\"#2a3f5f\"}},\"annotationdefaults\":{\"arrowcolor\":\"#2a3f5f\",\"arrowhead\":0,\"arrowwidth\":1},\"geo\":{\"bgcolor\":\"white\",\"landcolor\":\"#E5ECF6\",\"subunitcolor\":\"white\",\"showland\":true,\"showlakes\":true,\"lakecolor\":\"white\"},\"title\":{\"x\":0.05},\"mapbox\":{\"style\":\"light\"},\"margin\":{\"b\":0,\"l\":0,\"r\":0,\"t\":30}}},\"xaxis\":{\"anchor\":\"y\",\"domain\":[0.0,1.0],\"title\":{\"text\":\"gdpPercap\"}},\"yaxis\":{\"anchor\":\"x\",\"domain\":[0.0,1.0],\"title\":{\"text\":\"lifeExp\"}},\"legend\":{\"tracegroupgap\":0,\"itemsizing\":\"constant\"}},                        {\"responsive\": true}                    ).then(function(){\n",
       "                            \n",
       "var gd = document.getElementById('3cc4492a-8e96-468d-9a9a-8e3a192a5397');\n",
       "var x = new MutationObserver(function (mutations, observer) {{\n",
       "        var display = window.getComputedStyle(gd).display;\n",
       "        if (!display || display === 'none') {{\n",
       "            console.log([gd, 'removed!']);\n",
       "            Plotly.purge(gd);\n",
       "            observer.disconnect();\n",
       "        }}\n",
       "}});\n",
       "\n",
       "// Listen for the removal of the full notebook cells\n",
       "var notebookContainer = gd.closest('#notebook-container');\n",
       "if (notebookContainer) {{\n",
       "    x.observe(notebookContainer, {childList: true});\n",
       "}}\n",
       "\n",
       "// Listen for the clearing of the current output cell\n",
       "var outputEl = gd.closest('.output');\n",
       "if (outputEl) {{\n",
       "    x.observe(outputEl, {childList: true});\n",
       "}}\n",
       "\n",
       "                        })                };            </script>        </div>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/html": [
       "<div>            <script src=\"https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG\"></script><script type=\"text/javascript\">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: \"STIX-Web\"}});}</script>                <script type=\"text/javascript\">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>\n",
       "        <script charset=\"utf-8\" src=\"https://cdn.plot.ly/plotly-3.0.1.min.js\"></script>                <div id=\"747473c1-91bc-4d9b-8622-8800dd890701\" class=\"plotly-graph-div\" style=\"height:525px; width:100%;\"></div>            <script type=\"text/javascript\">                window.PLOTLYENV=window.PLOTLYENV || {};                                if (document.getElementById(\"747473c1-91bc-4d9b-8622-8800dd890701\")) {                    Plotly.newPlot(                        \"747473c1-91bc-4d9b-8622-8800dd890701\",                        [{\"hovertemplate\":\"\\u003cb\\u003e%{hovertext}\\u003c\\u002fb\\u003e\\u003cbr\\u003e\\u003cbr\\u003egdpPercap=%{x}\\u003cbr\\u003elifeExp=%{y}\\u003cbr\\u003epop=%{marker.size}\\u003cextra\\u003e\\u003c\\u002fextra\\u003e\",\"hovertext\":[\"Afghanistan\",\"Albania\",\"Algeria\",\"Angola\",\"Argentina\",\"Australia\",\"Austria\",\"Bahrain\",\"Bangladesh\",\"Belgium\",\"Benin\",\"Bolivia\",\"Bosnia and Herzegovina\",\"Botswana\",\"Brazil\",\"Bulgaria\",\"Burkina Faso\",\"Burundi\",\"Cambodia\",\"Cameroon\",\"Canada\",\"Central African Republic\",\"Chad\",\"Chile\",\"China\",\"Colombia\",\"Comoros\",\"Congo, Dem. Rep.\",\"Congo, Rep.\",\"Costa Rica\",\"Cote d'Ivoire\",\"Croatia\",\"Cuba\",\"Czech Republic\",\"Denmark\",\"Djibouti\",\"Dominican Republic\",\"Ecuador\",\"Egypt\",\"El Salvador\",\"Equatorial Guinea\",\"Eritrea\",\"Ethiopia\",\"Finland\",\"France\",\"Gabon\",\"Gambia\",\"Germany\",\"Ghana\",\"Greece\",\"Guatemala\",\"Guinea\",\"Guinea-Bissau\",\"Haiti\",\"Honduras\",\"Hong Kong, China\",\"Hungary\",\"Iceland\",\"India\",\"Indonesia\",\"Iran\",\"Iraq\",\"Ireland\",\"Israel\",\"Italy\",\"Jamaica\",\"Japan\",\"Jordan\",\"Kenya\",\"Korea, Dem. Rep.\",\"Korea, Rep.\",\"Kuwait\",\"Lebanon\",\"Lesotho\",\"Liberia\",\"Libya\",\"Madagascar\",\"Malawi\",\"Malaysia\",\"Mali\",\"Mauritania\",\"Mauritius\",\"Mexico\",\"Mongolia\",\"Montenegro\",\"Morocco\",\"Mozambique\",\"Myanmar\",\"Namibia\",\"Nepal\",\"Netherlands\",\"New Zealand\",\"Nicaragua\",\"Niger\",\"Nigeria\",\"Norway\",\"Oman\",\"Pakistan\",\"Panama\",\"Paraguay\",\"Peru\",\"Philippines\",\"Poland\",\"Portugal\",\"Puerto Rico\",\"Reunion\",\"Romania\",\"Rwanda\",\"Sao Tome and Principe\",\"Saudi Arabia\",\"Senegal\",\"Serbia\",\"Sierra Leone\",\"Singapore\",\"Slovak Republic\",\"Slovenia\",\"Somalia\",\"South Africa\",\"Spain\",\"Sri Lanka\",\"Sudan\",\"Swaziland\",\"Sweden\",\"Switzerland\",\"Syria\",\"Taiwan\",\"Tanzania\",\"Thailand\",\"Togo\",\"Trinidad and Tobago\",\"Tunisia\",\"Turkey\",\"Uganda\",\"United Kingdom\",\"United States\",\"Uruguay\",\"Venezuela\",\"Vietnam\",\"West Bank and Gaza\",\"Yemen, Rep.\",\"Zambia\",\"Zimbabwe\"],\"legendgroup\":\"\",\"marker\":{\"color\":\"#636efa\",\"size\":{\"dtype\":\"i4\",\"bdata\":\"A5rmAYvwNgDgn\\u002fwBfIW9AGf1ZgIAzTcBZx59AN3PCgDTqPcIopKeAOpDewCwJYsABnZFANsCGQAXVVML6rxvALuZ2gBpB4AAkqLXACUGDgE9fv0BjqpCAFc7nABFfPgA2IGZTt7bogIw2QoAJ9LZAyL+OQD8Ez8A4dwSAQCQRACbNa4ACBScANhvUwD2kgcAxjSOACDl0QBfvcgEKORpACFpCABZ3koAj3qPBLzuTwAMEaQDEzMWACfDGQDkVukE+gRdAXJdowAA2b8ApsqXACl2FgAevoEAczFyADyDagAM65cAa5sEAKtNL0J4DlMNAscjBHacowEesz4ANxBiAJVDdwPkayoAxAGZB0ldXABBXh8CXY5jATZd7AJXOyYAftU7AOm1HgBWvDAAsh1cAKZ5JAHnWssAJr56ATOXtwCx5TEAQhYTANukegYP2ysAwHIKAPcXAwIocDABPMrYAqhbHwCeAbkB9dj8ADvNPgBcmVYAkcLEAHxpDAjWnUYAIecwAFndFgq9eDEAi7tlAMWKtQGnum0F4b1LApRlogBbKDwAji0MANjnUwGsM4cAmwsDAI4opQHlL7sAeeGaADLCXQAxeUUATh9TAJ2oHgA1JIsAhFqfAr8waQJ\\u002f8jYBwVaFAgpKEQCwzYkAZUZzADu4JgGWnGEB+PZFAnXc4APL\\u002f1YAYB8QAD7NnAB3yz0E3hq9AS5fnwPrB\\u002fMRyJo0ADYFjgEUABUFnFA9AJ\\u002fsUgHzOrMAZ9q7AA==\"},\"sizemode\":\"area\",\"sizeref\":366300.86,\"symbol\":\"circle\"},\"mode\":\"markers\",\"name\":\"\",\"orientation\":\"v\",\"showlegend\":false,\"x\":{\"dtype\":\"f8\",\"bdata\":\"54R1iKR0jkBgFASPBzG3QG2tLxJeT7hApWlQNDu9skAgJAuYsPXIQOeHEcJr0OBAWMoyxM+j4UDGpwAYAxndQPLPDOIDvZVAb7vQXJNz4EAOar+1I4WWQJzB3y9G3K1A8nhafkwWvUDboswG7YzIQGkAb4HmtMFAzzEge2XcxEDb2y3JIQSTQKHhg40h4XpAat\\u002fcXx3HmkAWNZiGYeifQAexM4Xnu+FAZJEm3iEQhkDiP91AQaCaQIY41sXRuclA0V0SZx1fs0D67laWlF67QBI+Stsu0Y5ANsjJadRYcUCMD7OXHWGsQKlNnNyH1sJAWTFcHQAjmEAUxhaCnI3MQIGzlCwNesFAH7qgvlNM1kCxbVFmzTnhQKwn84\\u002f2RKBAHkC\\u002f71+Jt0Dl9ssnQ9m6QKSK4lUuzbVArrzkf1pgtkBoke18C73HQM8Iysj0CoRAkQfH0XGWhUD5oGez4jbgQHbgnBGBwd1AIVnABD7LyUCOc5tw\\u002f4WHQL5Nf\\u002faXat9A1GAahm++lEB47j1cmuTaQLQh\\u002f8wMQrRAcXkH0zt1jUCc8BKc2hmCQBAiGXKMxpJACJylZKm4q0Byv0NRn2XjQEJ4tHE8ltFAC2MLQZmq4UCzX3e6ayijQIDuy5lNqatAqYdodNuqxkBtVRLZD3exQCxlGeJ\\u002f3ONAbqMBvNHs2EC8lpAPbubbQOu12VjhmLxAi1QYWwTq3kDdek0PdqexQO3Vx0P\\u002f3JZAK2owDUPkmEATGVbxCM3WQEgbR6xfGedAd4TTgoduxEAVGohlU4WYQHXmHhIe6HlAxjNo6L+Mx0Ay\\u002f+ibFFOQQFKgqp3MuodAXCBB8dNRyEAKn62DU0qQQOZ1xCGbLJxANSkF3X5mxUBBDkqYyWTHQE7BGmeLL6hAS+fDs\\u002fISwkCOHr+3WditQL3HmSZ8vYlAAAAAAACAjUC7YkZ4D8uyQEjfpGlwDZFAlufB3b334UAEHEKVQJjYQFJEhlWkeqVASAeQRmpdg0DeVKTC6HefQFZl3xWmGehAmGn7VwzL1UCwrDQp5VukQHui68KXKMNA4KKTpdZMsECIf9jS5\\u002fC8QLWpukf27KhAowvqW\\u002fYOzkBVTRB1aQfUQKJ6a2At4NJA3Qn2Xx\\u002f2vUDT2cngPBzFQAWrjyy1+IpAZof4h735mEAPRYE+tSXVQA+dnnfjwZpAWyOCcUQdw0DoJO93U\\u002fSKQKJinL\\u002flBOdAYHZPHpQ90kByxFp8ECrZQAW4augg8YxAhnMNM9QawkAwKqkTRCXcQJ6xL9kwBK9AK97IPMpUpEANbmsLe6GxQP+ye\\u002fJ3iOBAGH0FaU1Q4kBMio9PjFiwQI4ev7eRC9xA4UIewe1NkUCDr691ZSK9QKkf5XHCl4tAdF5jlyCW0UBkzF1L7LS7QJIIjWAjhcBAF35wPoWBkEDj32dcaDbgQOv\\u002fHOb0+ORAMJ5BQ7u5xEBblNkg50vGQJHSbB4nE6NAajS5GLOip0D9hR4xitGhQEDc1avY3JNA2On2SFlbfUA=\"},\"xaxis\":\"x\",\"y\":{\"dtype\":\"f8\",\"bdata\":\"RIts5\\u002fvpRUBQjZduEhtTQCUGgZVDE1JAIbByaJFdRUAUrkfhetRSQNejcD0KT1RAkxgEVg71U0BxPQrXo+hSQIcW2c73A1BATmIQWDncU0B3vp8aL11MQPp+arx0Y1BASgwCK4e2UkB3vp8aL11JQClcj8L1GFJAuB6F61FAUkD2KFyPwiVKQArXo3A9ykhABoGVQ4vcTUDXo3A9CjdJQG8Sg8DKKVRAAyuHFtleRkAX2c73U1NJQAisHFpko1NAL90kBoE9UkA3iUFg5ThSQH0\\u002fNV66SVBAQmDl0CI7R0Dwp8ZLN6lLQDVeukkMslNARIts5\\u002fspSEAdWmQ73+9SQLbz\\u002fdR4kVNAyXa+nxofU0Boke18P5VTQGiR7Xw\\u002fZUtA16NwPQoPUkBWDi2ynb9SQBSDwMqh1VFA1XjpJjH4UUAmMQisHMpJQIXrUbgeBU1A8KfGSzd5SkB56SYxCNRTQDVeukkMKlRArkfhehReTEDTTWIQWLlNQESLbOf72VNAiUFg5dACTkD0\\u002fdR46d5TQH9qvHSTkFFAN4lBYOUATECNbOf7qTFHQGiR7Xw\\u002fdU5A6iYxCKyMUUBaZDvfT41UQBSDwMqhVVJAnMQgsHJwVEDqJjEIrCxQQJqZmZmZqVFABFYOLbK9UUD2KFyPwsVNQHE9CtejuFNASOF6FK4vVEBt5\\u002fup8SJUQD81XrpJJFJAO99PjZemVEAK16NwPSJSQK5H4XoUDktAXrpJDALTUEAdWmQ736dTQBSDwMqhZVNAZDvfT43\\u002fUUCyne+nxktFQBBYObTI1kZAsHJoke18UkBkEFg5tLhNQBBYObTIJkhAgZVDi2yPUkCyne+nxjtLQNEi2\\u002fl+ClBAJQaBlUMzUkAUrkfhegxTQAisHFpks1BAmG4Sg8CiUkDRItv5fspRQNEi2\\u002fl+CkVARrbz\\u002fdQIT0CJFtnO93NKQBSuR+F65E9AVOOlm8TwU0CTGARWDg1UQKjGSzeJOVJA5dAi2\\u002fluTEDLoUW2821HQAaBlUOLDFRAKVyPwvXoUkD0\\u002fdR46V5QQOx8PzVe4lJA46WbxCDwUUBt5\\u002fup8dpRQHnpJjEI7FFAeekmMQjkUkCDwMqhRYZTQDu0yHa+r1NAPzVeukkcU0BYObTIdh5SQOXQItv5HkdAbxKDwMphUEB9PzVeujFSQA4tsp3vh09A46WbxCCAUkBkEFg5tEhFQJHtfD81\\u002flNA30+Nl26qUkAlBoGVQ3tTQDEIrBxaFEhACKwcWmSrSEBOYhBYOTxUQNNNYhBYGVJAukkMAitHTUBYObTIds5DQH9qvHSTOFRAwJ8aL91sVED+1HjpJolSQJqZmZmZmVNAGQRWDi1CSkCBlUOLbKdRQPYoXI\\u002fCNU1AI9v5fmp0UUBQjZduEntSQH0\\u002fNV668VFATDeJQWDFSUAzMzMzM9tTQHNoke18j1NAf2q8dJMYU0ArhxbZzm9SQA4tsp3vj1JAXrpJDAJbUkDTTWIQWFlPQP3UeOkmMUVAdZMYBFa+RUA=\"},\"yaxis\":\"y\",\"type\":\"scatter\"}],                        {\"template\":{\"data\":{\"histogram2dcontour\":[{\"type\":\"histogram2dcontour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"choropleth\":[{\"type\":\"choropleth\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"histogram2d\":[{\"type\":\"histogram2d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"heatmap\":[{\"type\":\"heatmap\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"contourcarpet\":[{\"type\":\"contourcarpet\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"contour\":[{\"type\":\"contour\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"surface\":[{\"type\":\"surface\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"},\"colorscale\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]]}],\"mesh3d\":[{\"type\":\"mesh3d\",\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}],\"scatter\":[{\"fillpattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2},\"type\":\"scatter\"}],\"parcoords\":[{\"type\":\"parcoords\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolargl\":[{\"type\":\"scatterpolargl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"bar\":[{\"error_x\":{\"color\":\"#2a3f5f\"},\"error_y\":{\"color\":\"#2a3f5f\"},\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"bar\"}],\"scattergeo\":[{\"type\":\"scattergeo\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterpolar\":[{\"type\":\"scatterpolar\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"histogram\":[{\"marker\":{\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"histogram\"}],\"scattergl\":[{\"type\":\"scattergl\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatter3d\":[{\"type\":\"scatter3d\",\"line\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattermap\":[{\"type\":\"scattermap\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattermapbox\":[{\"type\":\"scattermapbox\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scatterternary\":[{\"type\":\"scatterternary\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"scattercarpet\":[{\"type\":\"scattercarpet\",\"marker\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}}}],\"carpet\":[{\"aaxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"baxis\":{\"endlinecolor\":\"#2a3f5f\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"minorgridcolor\":\"white\",\"startlinecolor\":\"#2a3f5f\"},\"type\":\"carpet\"}],\"table\":[{\"cells\":{\"fill\":{\"color\":\"#EBF0F8\"},\"line\":{\"color\":\"white\"}},\"header\":{\"fill\":{\"color\":\"#C8D4E3\"},\"line\":{\"color\":\"white\"}},\"type\":\"table\"}],\"barpolar\":[{\"marker\":{\"line\":{\"color\":\"#E5ECF6\",\"width\":0.5},\"pattern\":{\"fillmode\":\"overlay\",\"size\":10,\"solidity\":0.2}},\"type\":\"barpolar\"}],\"pie\":[{\"automargin\":true,\"type\":\"pie\"}]},\"layout\":{\"autotypenumbers\":\"strict\",\"colorway\":[\"#636efa\",\"#EF553B\",\"#00cc96\",\"#ab63fa\",\"#FFA15A\",\"#19d3f3\",\"#FF6692\",\"#B6E880\",\"#FF97FF\",\"#FECB52\"],\"font\":{\"color\":\"#2a3f5f\"},\"hovermode\":\"closest\",\"hoverlabel\":{\"align\":\"left\"},\"paper_bgcolor\":\"white\",\"plot_bgcolor\":\"#E5ECF6\",\"polar\":{\"bgcolor\":\"#E5ECF6\",\"angularaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"radialaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"ternary\":{\"bgcolor\":\"#E5ECF6\",\"aaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"baxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"},\"caxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\"}},\"coloraxis\":{\"colorbar\":{\"outlinewidth\":0,\"ticks\":\"\"}},\"colorscale\":{\"sequential\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"sequentialminus\":[[0.0,\"#0d0887\"],[0.1111111111111111,\"#46039f\"],[0.2222222222222222,\"#7201a8\"],[0.3333333333333333,\"#9c179e\"],[0.4444444444444444,\"#bd3786\"],[0.5555555555555556,\"#d8576b\"],[0.6666666666666666,\"#ed7953\"],[0.7777777777777778,\"#fb9f3a\"],[0.8888888888888888,\"#fdca26\"],[1.0,\"#f0f921\"]],\"diverging\":[[0,\"#8e0152\"],[0.1,\"#c51b7d\"],[0.2,\"#de77ae\"],[0.3,\"#f1b6da\"],[0.4,\"#fde0ef\"],[0.5,\"#f7f7f7\"],[0.6,\"#e6f5d0\"],[0.7,\"#b8e186\"],[0.8,\"#7fbc41\"],[0.9,\"#4d9221\"],[1,\"#276419\"]]},\"xaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"yaxis\":{\"gridcolor\":\"white\",\"linecolor\":\"white\",\"ticks\":\"\",\"title\":{\"standoff\":15},\"zerolinecolor\":\"white\",\"automargin\":true,\"zerolinewidth\":2},\"scene\":{\"xaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"yaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2},\"zaxis\":{\"backgroundcolor\":\"#E5ECF6\",\"gridcolor\":\"white\",\"linecolor\":\"white\",\"showbackground\":true,\"ticks\":\"\",\"zerolinecolor\":\"white\",\"gridwidth\":2}},\"shapedefaults\":{\"line\":{\"color\":\"#2a3f5f\"}},\"annotationdefaults\":{\"arrowcolor\":\"#2a3f5f\",\"arrowhead\":0,\"arrowwidth\":1},\"geo\":{\"bgcolor\":\"white\",\"landcolor\":\"#E5ECF6\",\"subunitcolor\":\"white\",\"showland\":true,\"showlakes\":true,\"lakecolor\":\"white\"},\"title\":{\"x\":0.05},\"mapbox\":{\"style\":\"light\"},\"margin\":{\"b\":0,\"l\":0,\"r\":0,\"t\":30}}},\"xaxis\":{\"anchor\":\"y\",\"domain\":[0.0,1.0],\"title\":{\"text\":\"gdpPercap\"}},\"yaxis\":{\"anchor\":\"x\",\"domain\":[0.0,1.0],\"title\":{\"text\":\"lifeExp\"}},\"legend\":{\"tracegroupgap\":0,\"itemsizing\":\"constant\"}},                        {\"responsive\": true}                    ).then(function(){\n",
       "                            \n",
       "var gd = document.getElementById('747473c1-91bc-4d9b-8622-8800dd890701');\n",
       "var x = new MutationObserver(function (mutations, observer) {{\n",
       "        var display = window.getComputedStyle(gd).display;\n",
       "        if (!display || display === 'none') {{\n",
       "            console.log([gd, 'removed!']);\n",
       "            Plotly.purge(gd);\n",
       "            observer.disconnect();\n",
       "        }}\n",
       "}});\n",
       "\n",
       "// Listen for the removal of the full notebook cells\n",
       "var notebookContainer = gd.closest('#notebook-container');\n",
       "if (notebookContainer) {{\n",
       "    x.observe(notebookContainer, {childList: true});\n",
       "}}\n",
       "\n",
       "// Listen for the clearing of the current output cell\n",
       "var outputEl = gd.closest('.output');\n",
       "if (outputEl) {{\n",
       "    x.observe(outputEl, {childList: true});\n",
       "}}\n",
       "\n",
       "                        })                };            </script>        </div>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#| label: fig-gapminder\n",
    "#| fig-cap: Life Expectancy and GDP\n",
    "#| fig-subcap:\n",
    "#|   - 'Gapminder: 1957'\n",
    "#|   - 'Gapminder: 2007'\n",
    "#| layout-ncol: 2\n",
    "#| column: page\n",
    "\n",
    "import plotly.express as px\n",
    "import plotly.io as pio\n",
    "gapminder = px.data.gapminder()\n",
    "def gapminder_plot(year):\n",
    "    gapminderYear = gapminder.query(\"year == \" + \n",
    "                                    str(year))\n",
    "    fig = px.scatter(gapminderYear, \n",
    "                     x=\"gdpPercap\", y=\"lifeExp\",\n",
    "                     size=\"pop\", size_max=60,\n",
    "                     hover_name=\"country\")\n",
    "    fig.show()\n",
    "    \n",
    "gapminder_plot(1957)\n",
    "gapminder_plot(2007)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3",
   "path": "C:\\Users\\chris\\Documents\\DEV_OTHER\\00-TESTS\\test-quarto\\.venv\\share\\jupyter\\kernels\\python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}

@cderv
Copy link
Collaborator Author

cderv commented May 27, 2025

#12842 fixes the problem, but plotly 6+ has more changes. It seems that now, 6+ is equivalent to setting include_plotlyjs = cdn https://plotly.com/python-api-reference/generated/plotly.io.to_html.html

This will format the script tag
https://github.com/plotly/plotly.py/blob/7bc7ce53fcdcb6a18541dfb7bce79382ba590a52/plotly/io/_html.py#L254-L260
and include it in the div
https://github.com/plotly/plotly.py/blob/7bc7ce53fcdcb6a18541dfb7bce79382ba590a52/plotly/io/_html.py#L323-L341

So I don't understand why we get it twice...

It seems that the above includes for the text/html output for the graph, but the following is done only once for connected notebook mode
https://github.com/plotly/plotly.py/blob/7bc7ce53fcdcb6a18541dfb7bce79382ba590a52/plotly/io/_base_renderers.py#L277-L283
and for not connected it is using non module script
https://github.com/plotly/plotly.py/blob/7bc7ce53fcdcb6a18541dfb7bce79382ba590a52/plotly/io/_base_renderers.py#L298

So I wonder if in Quarto we really need to keep both... 🤔

@cderv
Copy link
Collaborator Author

cderv commented May 27, 2025

It seems both are included because notebook renderer is used

# plotly use connected mode
try:
import plotly.io as pio
if plotly_connected:
pio.renderers.default = "notebook_connected"
else:
pio.renderers.default = "notebook"

And I understand this sets global_init = True
https://github.com/plotly/plotly.py/blob/7bc7ce53fcdcb6a18541dfb7bce79382ba590a52/plotly/io/_base_renderers.py#L364-L389

So, for Plotly, we can probably assume that the first plotly cell maybe be remove... Though I am puzzled because the other plotly load is different

  • Global init
<script type="module"">import "https://cdn.plot.ly/plotly-3.0.1.min"</script>
  • In cell loading
<script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script> 

Or are both needed ?

After this PR, the resulting HTML will have this moved in <head>

<script type="text/javascript">
window.PlotlyConfig = {MathJaxConfig: 'local'};
if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}
</script>
<script type="module">import "https://cdn.plot.ly/plotly-3.0.1.min"</script>

And this still in the figure div

<div>            <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_SVG"></script><script type="text/javascript">if (window.MathJax && window.MathJax.Hub && window.MathJax.Hub.Config) {window.MathJax.Hub.Config({SVG: {font: "STIX-Web"}});}</script>                <script type="text/javascript">window.PlotlyConfig = {MathJaxConfig: 'local'};</script>
        <script charset="utf-8" src="https://cdn.plot.ly/plotly-3.0.1.min.js"></script>                <div id="e5b8111d-ae5b-4762-bed8-39b47a123e0f" class="plotly-graph-div" ...

I don't know plotly.js well enough, but it is worth understanding this better.

@cderv
Copy link
Collaborator Author

cderv commented May 28, 2025

When I read the plotly doc for plotly.io.renderers, I expected a new issue, and this is confirmed. Setting plotly-connected: false will embed the plotly.js, which breaks subfigures, too. plotly-connected: false is the default in dashboard... so possibly it'll reproduce there too.

image

Fix is probably to catch the output cell from plotly also in this case.

In this case, plotly.py will embed the full plotly.js library so we need to dectect this code cell and put it in the html head to not have a non figure div in the output
@cderv
Copy link
Collaborator Author

cderv commented Jun 3, 2025

So, I decided to handle the non-connected notebook mode the same way. We detect it by looking for the copyrighted part they embed. It's not foolproof for the long term, but I believe it's still quite robust.

I am open to other ideas anyways.

@cderv cderv requested a review from gordonwoodhull June 3, 2025 14:22
Copy link
Contributor

@gordonwoodhull gordonwoodhull left a comment

Choose a reason for hiding this comment

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

I haven't looked at all the cases, but the renderings example for plotly-python in quarto-examples invokes the "module" case. I've confirmed that this fix works and we no longer need the bogus foo rendering.

The fact that the foo workaround was okay, dropping the module script, shows that the module include is superfluous for what we are doing. IIUC it would be used for modern JS with imports and modules, but it's not invoked by plotly-python, at least with my example.

I tried removing the text/javascript imports and only using the module, and it doesn't work. As written, without .js extension, it is generating a 403, further proving its irrelevance. But even adding the extension

<script type="module">import "https://cdn.plot.ly/plotly-3.0.1.min.js"</script>

does not fix my plots although it eliminates the 403. (I wouldn't expect it to, since ES6 imports are a different namespace.)

I guess it is safer to keep it if we don't know why it's there. I haven't tested the interaction with MathJax inside Plotly (?) which also seems to be configured here, before the module import.

@cderv
Copy link
Collaborator Author

cderv commented Jun 3, 2025

Thanks for having looked into it too.

I guess it is safer to keep it if we don't know why it's there.

Yeah that was my conclusion too. I am thinking there is something not right in plotly.py about this module change and addition. But I was not sure what and how to report to them.

I haven't tested the interaction with MathJax inside Plotly (?) which also seems to be configured here, before the module import.

Yep. Same question. I think in some situations we could try remove it to not insert this when we know this will be available in the HTML output because Quarto will add the mathjax dependency.

Or maybe find the way to opt out its insertion in this context (as it seems it can be:
https://github.com/plotly/plotly.py/blob/1d6d3bc99edef16b57c4bfc11c36f11608633f11/plotly/io/_html.py#L30)

@cderv cderv merged commit 6ca904a into main Jun 3, 2025
49 checks passed
@cderv cderv deleted the fix/plotly-subfigures branch June 3, 2025 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Subfigures with Plotly python are wrongly identified, leading to Phantom subfigure
3 participants