{"id":12056,"date":"2025-01-07T22:04:08","date_gmt":"2025-01-07T22:04:08","guid":{"rendered":"https:\/\/www.blopig.com\/blog\/?p=12056"},"modified":"2025-01-08T16:13:37","modified_gmt":"2025-01-08T16:13:37","slug":"making-pretty-interactive-graphs-the-simple-way-use-plotly","status":"publish","type":"post","link":"https:\/\/www.blopig.com\/blog\/2025\/01\/making-pretty-interactive-graphs-the-simple-way-use-plotly\/","title":{"rendered":"Making pretty, interactive graphs the simple way &#8211; Use Plotly."},"content":{"rendered":"\n<p>Using an <a href=\"https:\/\/en.wikipedia.org\/wiki\/ESP8266\" data-type=\"link\" data-id=\"https:\/\/en.wikipedia.org\/wiki\/ESP8266\">ESP8266 <\/a>and some <a href=\"https:\/\/www.analog.com\/media\/en\/technical-documentation\/data-sheets\/DS18B20.pdf\">DS18B20 <\/a>one-wire temperature sensors, I have been automatically recording temperature data from various parts of my pond, to see how it fluctuated with air temperature, depth and filter configuration.  <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?ssl=1\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"625\" height=\"264\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=625%2C264&#038;ssl=1\" alt=\"\" class=\"wp-image-12190\" srcset=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=1024%2C432&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=300%2C127&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=768%2C324&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=1536%2C648&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=2048%2C864&amp;ssl=1 2048w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?resize=624%2C263&amp;ssl=1 624w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?w=1250&amp;ssl=1 1250w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/fish-montage.png?w=1875&amp;ssl=1 1875w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/figure>\n\n\n\n<p>Despite the help I was receiving from the feline fish monitor, I was getting a bit irked at the quality of the graphs I was getting using matplotlib.<\/p>\n\n\n\n<p>Matplotlib has been around since 2003, more than 20 years now. It&#8217;s arguably the defacto method of producing graphs in python and it&#8217;s not going away. However, it&#8217;s also a pain to use and by default produces some quite ugly plots unless you put in the mileage. In fact, when attempting to quickly explore data, Michael L. Waskom&#8217;s frustrations with matplotlib were directly related to the production of the <a href=\"https:\/\/seaborn.pydata.org\/\" data-type=\"link\" data-id=\"https:\/\/seaborn.pydata.org\/\">seaborn<\/a> library. <a href=\"https:\/\/joss.theoj.org\/papers\/10.21105\/joss.03021\">&#8220;By producing complete graphics from a single function call with minimal<br>arguments, seaborn facilitates rapid prototyping and exploratory data analysis.&#8221;<\/a><\/p>\n\n\n\n<p>Seaborn makes use of matplotlib and integrates tightly with <a href=\"http:\/\/comixtalk.com\/images\/dec2005\/bspanda.png\">pandas <\/a>provide a neat wrapper for matplotlib functions, allowing you to avoid a lot of the data herding needed to view a graph.<\/p>\n\n\n\n<p>You may think &#8220;OK, so seaborn finally tames matplotlib, why should I use anything else?&#8221;  In short, <em>interactivity<\/em>.  Seaborn and Matplotlib may produce graphs, but a graph alone doesn&#8217;t really let you explore the data.  If you look at a graph you&#8217;re limited to the scale the author thought made sense, you can&#8217;t zoom in or out and if one line is behind another, you&#8217;re kind of stuck.  <\/p>\n\n\n\n<p>Where plotly really shines is with just two lines you can generate your figure and then either save it as the image below, or as an interactive HTML graph such as <a href=\"https:\/\/users.ox.ac.uk\/~stat0142\/temps.html\" data-type=\"link\" data-id=\"https:\/\/users.ox.ac.uk\/~stat0142\/temps.html\">this.<\/a> <br><\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?ssl=1\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"625\" height=\"230\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=625%2C230&#038;ssl=1\" alt=\"\" class=\"wp-image-12188\" srcset=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=1024%2C377&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=300%2C110&amp;ssl=1 300w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=768%2C283&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=1536%2C565&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=2048%2C753&amp;ssl=1 2048w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?resize=624%2C230&amp;ssl=1 624w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?w=1250&amp;ssl=1 1250w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2025\/01\/pond-temperatures-over-time.png?w=1875&amp;ssl=1 1875w\" sizes=\"auto, (max-width: 625px) 100vw, 625px\" \/><\/a><\/figure>\n\n\n\n<!--more-->\n\n\n\n<p>A full guide to plotly should start here: <a href=\"https:\/\/plotly.com\/python\/getting-started\">https:\/\/plotly.com\/python\/getting-started<\/a><br>But in the interim, here&#8217;s a quick intro.  <\/p>\n\n\n\n<p>Install plotly:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">sudo apt-get install -y python3-plotly<\/pre>\n\n\n\n<p>I&#8217;ve got a honking great data file called pond_temperatures.log that looks like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Date, Bottom Sensor, Top Sensor, Air Sensor\n2024-04-10 08:36:25,12.00,11.63,11.06\n2024-04-10 08:51:29,12.25,11.69,11.69\n2024-04-10 09:06:32,12.25,11.63,12.56\n...<\/pre>\n\n\n\n<p>I  want to see how these three variables (Bottom Sensor, Top Sensor and Air Sensor) change over time and explore the data.  Below is a quick script that reads that data and generates both a png of it, as well as an explorable <a href=\"https:\/\/users.ox.ac.uk\/~stat0142\/temps.html\" data-type=\"link\" data-id=\"https:\/\/users.ox.ac.uk\/~stat0142\/temps.html\">HTML version.<\/a><\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import pandas as pd\nimport plotly.express as px\n\ntemps_df = pd.read_csv(\"pond_temperatures.log\")\nprint(temps_df.tail(3))\n\nfig = px.line(temps_df, x='Date', y=temps_df.columns,title=\"Pond temperature over time\").update_layout(\n&amp;nbsp;&amp;nbsp;&amp;nbsp; xaxis_title=\"Date\", yaxis_title=\"Temperature (degrees C)\")\n\nfig.write_html(\"temps.html\")\nfig.write_image(\"current.png\")\n<\/pre>\n\n\n\n<p>Literally, that&#8217;s it, seven lines of code:  <\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Import libraries (x2)<\/li>\n\n\n\n<li>Suck data into a pandas dataframe<\/li>\n\n\n\n<li>Show me the last few lines of the data that&#8217;s been read (totally optional)<\/li>\n\n\n\n<li>Generate a plotly object<\/li>\n\n\n\n<li>Save that object as an interactive chart <\/li>\n\n\n\n<li>Save it as an image (optional).<\/li>\n<\/ul>\n\n\n\n<p>If you find the above doesn&#8217;t work for you, you may see an error similar to that below:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">eoin@mymachine:\/tmp$ .\/grapher.py\n\nDate Bottom Sensor Top Sensor Air Sensor\n2913 2024-06-15 05:54:25 14.75 14.44 11.19\n2914 2024-06-15 06:09:29 14.75 14.44 11.31\n2915 2024-06-15 06:24:33 14.75 14.44 11.38\n\nTraceback (most recent call last):\nFile \"\/tmp\/.\/grapher.py\", line 24, in\nfig.write_image(\"current.png\")\nFile \"\/usr\/local\/lib\/python3.10\/dist-packages\/plotly\/basedatatypes.py\", line 3835, in write_image\nreturn pio.write_image(self, *args, **kwargs)\nFile \"\/usr\/local\/lib\/python3.10\/dist-packages\/plotly\/io\/_kaleido.py\", line 266, in write_image\nimg_data = to_image(\nFile \"\/usr\/local\/lib\/python3.10\/dist-packages\/plotly\/io\/_kaleido.py\", line 132, in to_image\nraise ValueError(\nValueError:\nImage export using the \"kaleido\" engine requires the kaleido package,\nwhich can be installed using pip:\n&lt;strong>$ pip install -U kaleido&lt;\/strong><\/pre>\n\n\n\n<p>The newer versions of plotly have switched to a package called kaleido to generate the images.  On Ubuntu 22.04, this can be installed with:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">&lt;strong>eoin@mymachine:\/tmp$ pip install -U kaleido&lt;\/strong><\/pre>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"bash\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">Defaulting to user installation because normal site-packages is not writeable\nCollecting kaleido\nDownloading kaleido-0.2.1-py2.py3-none-manylinux1_x86_64.whl.metadata (15 kB)\nDownloading kaleido-0.2.1-py2.py3-none-manylinux1_x86_64.whl (79.9 MB)\n\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501 79.9\/79.9 MB 102.5 MB\/s eta 0:00:00\nInstalling collected packages: kaleido\nSuccessfully installed kaleido-0.2.1\n\neoin@mymachine:\/tmp$ .\/grapher.py\n\nDate Bottom Sensor Top Sensor Air Sensor\n2913 2024-06-15 05:54:25 14.75 14.44 11.19\n2914 2024-06-15 06:09:29 14.75 14.44 11.31\neoin@mymachine:\/tmp$ .\/grapher.py<\/pre>\n\n\n\n<p>Happy plotting!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Using an ESP8266 and some DS18B20 one-wire temperature sensors, I have been automatically recording temperature data from various parts of my pond, to see how it fluctuated with air temperature, depth and filter configuration. Despite the help I was receiving from the feline fish monitor, I was getting a bit irked at the quality of [&hellip;]<\/p>\n","protected":false},"author":15,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","wikipediapreview_detectlinks":true,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"ngg_post_thumbnail":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[29,361,621,736,227],"tags":[],"ppma_author":[507],"class_list":["post-12056","post","type-post","status-publish","format-standard","hentry","category-code","category-data-science","category-data-visualization","category-hardware","category-python-code"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"authors":[{"term_id":507,"user_id":15,"is_guest":0,"slug":"eoin","display_name":"Eoin Malins","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/e91a9cf8b77625a1bc34e56f5dc36439a1f61476804b087e5b47554425879210?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/12056","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/users\/15"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/comments?post=12056"}],"version-history":[{"count":5,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/12056\/revisions"}],"predecessor-version":[{"id":12200,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/12056\/revisions\/12200"}],"wp:attachment":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/media?parent=12056"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/categories?post=12056"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/tags?post=12056"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=12056"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}