├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── examples ├── bar-charts.clj ├── bubble-charts.clj ├── horizontal-bar-charts.clj ├── line-charts.clj ├── scatter-3d.clj ├── scatter.clj ├── subplot.clj └── surface.clj ├── project.clj ├── resources └── plotly.min.js ├── src └── plotly_clj │ ├── core.clj │ └── scale.clj └── test └── plotly_clj └── core_test.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | plotly.html 13 | .gorilla-port 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. This change log follows the conventions of [keepachangelog.com](http://keepachangelog.com/). 3 | 4 | ## [0.1.1] 5 | ### Changed 6 | 7 | - `subplot` now support 3d plots. 8 | 9 | ## [0.1.0] 10 | ### Added 11 | - Add `plotly` to create a plotly object with dataset, traces and layout. 12 | - Add `save-html` to save a plotly object into a html page. 13 | - Add `online-init` and `offline-init` to insert plotly.min.js into a Gorilla-REPL notebook. 14 | - Add `iplot` to show a plotly object in Gorilla-REPL notebook. 15 | - Add `plot` to send a plotly object to https://plot.ly and get a sharable url. 16 | - Add `set-credentials` to save user-name and api-key in local file. 17 | - Add `group-dataset` to split a dataset into map. 18 | - Add `add-scatter` to plot lines and markers. 19 | - Add `set-dataset`, `set-layout`, `update-layout`, `set-configs`, `update-configs` to set or update a plotly object. 20 | - Add `embed-url`, embed a sharable url in Gorilla-REPL notebook. 21 | - Add `plot-seq`, enable for comprehension to apply a sequence of add-fn. The implementation is a little ugly. A macro is needed. 22 | - Add `add-annotations`, like geom_text in R. 23 | - Add `add-bar`, plot bar charts. 24 | - Add `subplot`, make subplots of existing traces. 25 | - Add `scatter-3d`, show 3d scatters. 26 | - Add `add-surface`. 27 | - Add `add-histogram-2d` and `add-histogram` 28 | - Add `add-mesh3d`, `add-contour`, `add-heatmap` 29 | 30 | [0.1.1]: https://github.com/findmyway/plotly-clj/tree/0.1.1 31 | [0.1.0]: https://github.com/findmyway/plotly-clj/tree/0.1.0 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Tian Jun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # plotly-clj 2 | 3 | [![Clojars Project](https://img.shields.io/clojars/v/plotly-clj.svg)](https://clojars.org/plotly-clj) 4 | 5 | A Clojure library designed for creating interactive web graphics via the open source JavaScript graphing library [plotly.js](https://github.com/plotly/plotly.j://github.com/plotly/plotly.js). 6 | 7 | This library is still under development. Any suggestions are welcome! 8 | 9 | ## Installation 10 | 11 | Install with Leiningen, add the following to your `:dependencies`: 12 | 13 | ``` 14 | [plotly-clj "0.1.1"] 15 | ``` 16 | 17 | ## Usage 18 | 19 | There are three ways to use this library. 20 | 21 | ### Plot offline with Gorilla-REPL 22 | 23 | This is the most common way to use this library. By integrating with [Gorilla-REPL](http://gorilla-repl.org/), 24 | you can easily plot figures like what you do in ipython notebooks. 25 | 26 | ```clojure 27 | (ns x (:use [plotly-clj.core])) 28 | 29 | (offline-init) 30 | 31 | (-> (plotly [2 1 3]) 32 | add-scatter 33 | iplot) 34 | ``` 35 | 36 | Checkout the following examples for how to use: 37 | 38 | 1. [Scatter](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/scatter.clj) 39 | 2. [Bubble-Charts](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/bubble-charts.clj) 40 | 3. [Line-Charts](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/line-charts.clj) 41 | 4. [Bar-Charts](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/bar-charts.clj) 42 | 4. [Horizontal-Bar-Charts](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/horizontal-bar-charts.clj) 43 | 5. [Subplot](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/subplot.clj) 44 | 6. [Scater-3d](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/scatter-3d.clj) 45 | 7. [Surface](http://viewer.gorilla-repl.org/view.html?source=github&user=findmyway&repo=plotly-clj&path=examples/surface.clj) 46 | 47 | ### Plot offline in a html page 48 | 49 | In this way, you can save the plot in a html page and check it out in a browser. 50 | 51 | ```clojure 52 | (ns x (:use [plotly-clj.core])) 53 | 54 | (-> (plotly [2 1 3]) 55 | add-scatter 56 | (save-html "plotly.html" :open)) 57 | ``` 58 | 59 | ### Plot online 60 | 61 | You can also send your figure to the [plot.ly](https://plot.ly) and get a sharable url. 62 | Notice that you should set the user-name and api-key first. You can register a count first 63 | and find your api-key at [here](https://plot.ly/settings/api). 64 | 65 | ```clojure 66 | (ns x (:use [plotly-clj.core])) 67 | 68 | (set-credentials "your-name" "your-api-key") 69 | 70 | (-> (plotly [2 1 3]) 71 | add-scatter 72 | (plot "filename")) 73 | ;; https://plot.ly/~findmyway/98 74 | ``` 75 | 76 | ## License 77 | 78 | ``` 79 | MIT License 80 | 81 | Copyright (c) 2017 Tian Jun 82 | 83 | Permission is hereby granted, free of charge, to any person obtaining a copy 84 | of this software and associated documentation files (the "Software"), to deal 85 | in the Software without restriction, including without limitation the rights 86 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 87 | copies of the Software, and to permit persons to whom the Software is 88 | furnished to do so, subject to the following conditions: 89 | 90 | The above copyright notice and this permission notice shall be included in all 91 | copies or substantial portions of the Software. 92 | 93 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 94 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 95 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 96 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 97 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 98 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 99 | SOFTWARE. 100 | ``` 101 | -------------------------------------------------------------------------------- /examples/bar-charts.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; # Bar Charts 5 | ;; ** 6 | 7 | ;; @@ 8 | (ns bar 9 | (:require [clojure.core.matrix :as m] 10 | [clojure.core.matrix.dataset :as d] 11 | [clojure.core.matrix.random :as rnd] 12 | [clojure.data.csv :as csv]) 13 | (:use [plotly-clj.core] :reload-all)) 14 | ;; @@ 15 | ;; => 16 | ;;; {"type":"html","content":"nil","value":"nil"} 17 | ;; <= 18 | 19 | ;; @@ 20 | (online-init) 21 | ;; @@ 22 | ;; => 23 | ;;; {"type":"html","content":"","value":"pr'ed value"} 24 | ;; <= 25 | 26 | ;; @@ 27 | (-> (plotly {:x ["giraffes" "orangutans" "monkeys"]}) 28 | (add-bar 29 | :x :x 30 | :y [20 14 23] 31 | :name "SF Zoo") 32 | (plot "Basic-Bar-Chart" :fileopt "overwrite") 33 | embed-url) 34 | ;; @@ 35 | ;; => 36 | ;;; {"type":"html","content":"","value":"pr'ed value"} 37 | ;; <= 38 | 39 | ;; @@ 40 | (-> (plotly {:x ["giraffes" "orangutans" "monkeys"]}) 41 | (add-bar 42 | :x :x 43 | :y [20 14 23] 44 | :name "SF Zoo") 45 | (add-bar 46 | :x :x 47 | :y [12 18 29] 48 | :name "LA Zoo") 49 | (plot "Grouped-Bar-Chart" :fileopt "overwrite") 50 | embed-url) 51 | ;; @@ 52 | ;; => 53 | ;;; {"type":"html","content":"","value":"pr'ed value"} 54 | ;; <= 55 | 56 | ;; @@ 57 | (-> (plotly {:x ["giraffes" "orangutans" "monkeys"]}) 58 | (add-bar 59 | :x :x 60 | :y [20 14 23] 61 | :name "SF Zoo") 62 | (add-bar 63 | :x :x 64 | :y [12 18 29] 65 | :name "LA Zoo") 66 | (set-layout :barmode "stack") 67 | (plot "Stacked-Bar-Chart" :fileopt "overwrite") 68 | embed-url) 69 | ;; @@ 70 | ;; => 71 | ;;; {"type":"html","content":"","value":"pr'ed value"} 72 | ;; <= 73 | 74 | ;; ** 75 | ;;; ##Bar Chart with Hover Text 76 | ;; ** 77 | 78 | ;; @@ 79 | (-> (plotly) 80 | (add-bar 81 | :x ["Product A" "Product B" "Product C"] 82 | :y [20 14 23] 83 | :text ["27% market share" "24% market share" "19% market share"] 84 | :marker {:color "rgb(158,202,225)" 85 | :line {:color "rgb(8,48,107)" :width 1.5}} 86 | :opacity 0.5) 87 | (set-layout :title "January 2013 Sales Report") 88 | (plot "Bar-Chart-with-Hover-Text" :fileopt "overwrite") 89 | embed-url) 90 | ;; @@ 91 | ;; => 92 | ;;; {"type":"html","content":"","value":"pr'ed value"} 93 | ;; <= 94 | 95 | ;; ** 96 | ;;; ## Styled Bar Chart with Direct Labels 97 | ;; ** 98 | 99 | ;; @@ 100 | (let [xs ["Product A" "Product B" "Product C"] 101 | ys [20 14 23]] 102 | (-> (plotly) 103 | (add-bar 104 | :x xs 105 | :y ys 106 | :text ["27% market share" "24% market share" "19% market share"] 107 | :marker {:color "rgb(158,202,225)" 108 | :line {:color "rgb(8,48,107)" :width 1.5}} 109 | :opacity 0.5) 110 | (set-layout :title "January 2013 Sales Report") 111 | (add-annotations 112 | (for [[x y] (map vector xs ys)] 113 | {:x x :y y :text y :showarrow false :yanchor "bottom"})) 114 | (plot "Styled-Bar-Chart-with-Direct-Labels" :fileopt "overwrite") 115 | embed-url)) 116 | ;; @@ 117 | ;; => 118 | ;;; {"type":"html","content":"","value":"pr'ed value"} 119 | ;; <= 120 | 121 | ;; ** 122 | ;;; ## Rotated Bar Chart Labels 123 | ;; ** 124 | 125 | ;; @@ 126 | (-> (plotly {:x ["Jan" "Feb" "Mar" "Apr" "May" "Jun""Jul" "Aug" "Sep" "Oct" "Nov" "Dec"]}) 127 | (add-bar 128 | :x :x 129 | :y [20 14 25 16 18 22 19 15 12 16 14 17] 130 | :name "Primary Product" 131 | :marker {:color "rgb(49,130,189)"}) 132 | (add-bar 133 | :x :x 134 | :y [19 14 22 14 16 19 15 14 10 12 12 16] 135 | :name "Secondary Product" 136 | :marker {:color "rgb(204,204,204)"}) 137 | (set-layout :xaxis {:tickangle -45}) 138 | (plot "Rotated Bar Chart Labels" :fileopt "overwrite") 139 | embed-url) 140 | ;; @@ 141 | ;; => 142 | ;;; {"type":"html","content":"","value":"pr'ed value"} 143 | ;; <= 144 | 145 | ;; ** 146 | ;;; ##Customizing Individual Bar Colors 147 | ;; ** 148 | 149 | ;; @@ 150 | (-> (plotly) 151 | (add-bar 152 | :x ["Feature A" "Feature B" "Feature C""Feature D" "Feature E"] 153 | :y [20 14 23 25 22] 154 | :marker {:color ["rgba(204,204,204,1)" "rgba(222,45,38,0.8)" 155 | "rgba(204,204,204,1)" "rgba(204,204,204,1)" "rgba(204,204,204,1)"]}) 156 | (plot "Customizing-Individual-Bar-Colors" :fileopt "overwrite") 157 | embed-url) 158 | ;; @@ 159 | ;; => 160 | ;;; {"type":"html","content":"","value":"pr'ed value"} 161 | ;; <= 162 | 163 | ;; ** 164 | ;;; ##Colored and Styled Bar Chart 165 | ;; ** 166 | 167 | ;; @@ 168 | (-> (plotly {:x [1995 1996 1997 1998 1999 2000 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2012]}) 169 | (add-bar 170 | :x :x 171 | :y [219 146 112 127 124 180 236 207 236 263 350 430 474 526 488 537 500 439] 172 | :name "Rest of world" 173 | :marker {:color "rgb(55, 83, 109)"}) 174 | (add-bar 175 | :x :x 176 | :y [16 13 10 11 28 37 43 55 56 88 105 156 270 299 340 403 549 499] 177 | :name "China" 178 | :marker {:color "rgb(26, 118, 255)"}) 179 | (set-layout 180 | :title "US Export of Plastic Scrap" 181 | :xaxis {:tickfont {:size 14 :color "rgb(107, 107, 107)"}} 182 | :yaxis {:title "USD (millions)" 183 | :titlefont {:size 14 :color "rgb(107, 107, 107)"} 184 | :tickfont {:size 14 :color "rgb(107, 107, 107)"}} 185 | :legend {:x 0 186 | :y 1.0} 187 | :bargap 0.15 188 | :bargroupgap 0.1) 189 | (plot "Colored-and-Styled-Bar-Chart" :fileopt "overwrite") 190 | embed-url) 191 | ;; @@ 192 | ;; => 193 | ;;; {"type":"html","content":"","value":"pr'ed value"} 194 | ;; <= 195 | 196 | ;; ** 197 | ;;; ##Waterfall Bar Chart 198 | ;; ** 199 | 200 | ;; @@ 201 | 202 | (let [ds (d/dataset {:xs ["Product
Revenue" "Services
Revenue" 203 | "Total
Revenue" "Fixed
Costs" 204 | "Variable
Costs" "Total
Costs" "Total"] 205 | :ys [400 660 660 590 400 400 340] 206 | :text ["$430K" "$260K" "$690K" "$-120K" "$-200K" "$-320K" "$370K"]})] 207 | (->(plotly ds) 208 | (add-bar :x :xs 209 | :y [0 430 0 570 370 370 0] 210 | :marker {:color "rgba(1,1,1, 0.0)"}) 211 | (add-bar :x :xs 212 | :y [430 260 690 0 0 0 0] 213 | :marker {:color "rgba(55, 128, 191, 0.7)" 214 | :line {:color "rgba(55, 128, 191, 1.0)" :width 2}}) 215 | (add-bar :x :xs 216 | :y [0 0 0 120 200 320 0] 217 | :marker {:color "rgba(219, 64, 82, 0.7)" 218 | :line {:color "rgba(219, 64, 82, 1.0)" :width 2}}) 219 | (add-bar :x :xs 220 | :y [0 0 0 0 0 0 370] 221 | :marker {:color "rgba(50, 171, 96, 0.7)" 222 | :line {:color "rgba(50, 171, 96, 1.0)" :width 2}}) 223 | (set-layout :title "Annual Profit- 2015" 224 | :barmode "stack" 225 | :showlegend false 226 | :paper_bgcolor "rgba(245, 246, 249, 1)" 227 | :plot_bgcolor "rgba(245, 246, 249, 1)") 228 | (add-annotations 229 | (for [[x y t] ds] 230 | {:x x :y y :text t :showarrow false 231 | :font {:family "Arial" :size 14 :color "rgba(245, 246, 249, 1)"}})) 232 | (plot "Waterfall Bar Chart" :fileopt "overwrite") 233 | embed-url)) 234 | ;; @@ 235 | ;; => 236 | ;;; {"type":"html","content":"","value":"pr'ed value"} 237 | ;; <= 238 | 239 | ;; ** 240 | ;;; ##Bar Chart with Relative Barmode 241 | ;;; 242 | ;; ** 243 | 244 | ;; @@ 245 | (let [x [1 2 3 4]] 246 | (-> (plotly) 247 | (add-bar :x x :y [1 4 9 16]) 248 | (add-bar :x x :y [6 -8 -4.5 8]) 249 | (add-bar :x x :y [-15 -3 4.5 -8]) 250 | (add-bar :x x :y [-1 3 -3 -4]) 251 | (set-layout :barmode "relative") 252 | (plot "Bar Chart with Relative Barmode" :fileopt "overwrite") 253 | embed-url)) 254 | ;; @@ 255 | ;; => 256 | ;;; {"type":"html","content":"","value":"pr'ed value"} 257 | ;; <= 258 | 259 | ;; @@ 260 | 261 | ;; @@ 262 | -------------------------------------------------------------------------------- /examples/bubble-charts.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; #Bubble-Charts 5 | ;; ** 6 | 7 | ;; ** 8 | ;;; If you haven't read the **scatter.clj**, I strongly suggest you go through that file first. 9 | ;; ** 10 | 11 | ;; @@ 12 | (ns bubble 13 | (:require [clojure.core.matrix :as m] 14 | [clojure.core.matrix.dataset :as d] 15 | [clojure.core.matrix.random :as rnd] 16 | [clojure.data.csv :as csv]) 17 | (:use [plotly-clj.core] :reload-all)) 18 | ;; @@ 19 | ;; => 20 | ;;; {"type":"html","content":"nil","value":"nil"} 21 | ;; <= 22 | 23 | ;; ** 24 | ;;; 25 | ;; ** 26 | 27 | ;; @@ 28 | (online-init) 29 | ;; @@ 30 | ;; => 31 | ;;; {"type":"html","content":"","value":"pr'ed value"} 32 | ;; <= 33 | 34 | ;; ** 35 | ;;; ## Simple Bubble Chart 36 | ;; ** 37 | 38 | ;; @@ 39 | (-> (plotly [10, 11, 12, 13]) 40 | (add-scatter :mode "markers" 41 | :text ["A
size: 40" "B
size: 60""C
size: 80""D
size: 100"] 42 | :marker {:size [40, 60, 80, 100] 43 | :color ["rgb(93, 164, 214)" "rgb(255, 144, 14)" "rgb(44, 160, 101)" "rgb(255, 65, 54)"] 44 | :opacity [1 0.8 0.6 0.4]}) 45 | (set-layout :xaxis {:zeroline false}) 46 | (plot "simple-bubble-chart" :fileopt "overwrite") 47 | embed-url) 48 | ;; @@ 49 | ;; => 50 | ;;; {"type":"html","content":"","value":"pr'ed value"} 51 | ;; <= 52 | 53 | ;; ** 54 | ;;; ## Scaling the Size and Text of Bubble Charts 55 | ;; ** 56 | 57 | ;; @@ 58 | (-> (plotly {:x (vec (range 4)) 59 | :y [10 11 12 13] 60 | :s [400 600 800 1000]}) 61 | (add-scatter :mode "markers" 62 | :marker {:size :s 63 | :sizemode "area"}) 64 | (add-scatter :mode "markers" 65 | :y #(m/add (d/column % :y) 4) 66 | :marker {:size #(m/mul (d/column % :s) 0.2) 67 | :sizemode "area"}) 68 | (add-scatter :mode "markers" 69 | :y #(m/add (d/column % :y) 8) 70 | :marker {:size #(m/mul (d/column % :s) 2) 71 | :sizemode "area"}) 72 | (plot "scaling-the-size-and-text-of-bubbe-charts" :fileopt "overwrite") 73 | embed-url) 74 | ;; @@ 75 | ;; => 76 | ;;; {"type":"html","content":"","value":"pr'ed value"} 77 | ;; <= 78 | 79 | ;; ** 80 | ;;; ## Bubble Charts with Colorscale 81 | ;; ** 82 | 83 | ;; @@ 84 | (-> (plotly {:x [1 3.2 5.4 7.6 9.8 12.5] 85 | :y [1 3.2 5.4 7.6 9.8 12.5] 86 | :colors [120 125 130 135 140 145] 87 | :sizes [15 30 55 70 90 110]}) 88 | (add-scatter :mode "markers" 89 | :marker {:size :sizes 90 | :color :colors 91 | :showscale true}) 92 | (plot "bubble-charts-with-colorscale" :fileopt "overwrite") 93 | embed-url) 94 | ;; @@ 95 | ;; => 96 | ;;; {"type":"html","content":"","value":"pr'ed value"} 97 | ;; <= 98 | 99 | ;; @@ 100 | (def data 101 | (let [in-file (slurp "https://raw.githubusercontent.com/plotly/datasets/master/gapminderDataFiveYear.csv") 102 | data (doall (csv/read-csv in-file))] 103 | (d/dataset (map keyword (first data)) (rest data)))) 104 | ;; @@ 105 | ;; => 106 | ;;; {"type":"html","content":"#'bubble/data","value":"#'bubble/data"} 107 | ;; <= 108 | 109 | ;; @@ 110 | (defn parse-float 111 | [x] 112 | (Float/parseFloat x)) 113 | 114 | (def data-2007 (d/dataset (map (fn [row] (-> row 115 | (update :pop parse-float) 116 | (update :lifeExp parse-float) 117 | (update :gdpPercap parse-float))) 118 | (filter #(= "2007" (:year %)) (d/row-maps data))))) 119 | ;; @@ 120 | ;; => 121 | ;;; {"type":"html","content":"#'bubble/data-2007","value":"#'bubble/data-2007"} 122 | ;; <= 123 | 124 | ;; @@ 125 | (defn format-row 126 | [r] 127 | (format "Country: %s
Pop: %s
Life Expectancy: %s
GDP per capita: %s
" 128 | (first r) (nth r 2) (nth r 4) (nth r 5))) 129 | 130 | (-> (plotly data-2007) 131 | (group-dataset :continent) 132 | (add-scatter :mode "markers" 133 | :x :gdpPercap 134 | :y :lifeExp 135 | :text (map format-row data-2007) 136 | :marker {:symbol 0 137 | :sizemode "diameter" 138 | :sizeref 1.5 139 | :size #(m/sqrt (m/mul (d/column % :pop) 2.666051223553066e-05))}) 140 | (set-layout 141 | :title "Life Expectancy v. Per Capita GDP, 2007" 142 | :xaxis {:title "GDP per capita (2000 dollars)" 143 | :type "log" 144 | :range [2.003297660701705 5.191505530708712] 145 | :zerolinewidth 1 146 | :ticklen 5 147 | :gridwidth 2} 148 | :yaxis {:title "Life Expectancy (years)" 149 | :range [36.12621671352166 91.72921793264332] 150 | :zerolinewidth 1 151 | :ticklen 5 152 | :gridwidth 2} 153 | :paper_bgcolor "rgb(243, 243, 243)" 154 | :plot_bgcolor "rgb(243, 243, 243)") 155 | (plot "Life-Expectancy-v.-Per-Capita-GDP,-2007" :fileopt "overwrite") 156 | embed-url) 157 | ;; @@ 158 | ;; => 159 | ;;; {"type":"html","content":"","value":"pr'ed value"} 160 | ;; <= 161 | 162 | ;; @@ 163 | 164 | ;; @@ 165 | -------------------------------------------------------------------------------- /examples/horizontal-bar-charts.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; #Horizontal-Bar-Charts 5 | ;; ** 6 | 7 | ;; @@ 8 | (ns horizontal-bar 9 | (:require [clojure.core.matrix :as m] 10 | [clojure.core.matrix.dataset :as d] 11 | [clojure.core.matrix.random :as rnd] 12 | [clojure.data.csv :as csv]) 13 | (:use [plotly-clj.core] :reload-all)) 14 | ;; @@ 15 | ;; => 16 | ;;; {"type":"html","content":"nil","value":"nil"} 17 | ;; <= 18 | 19 | ;; @@ 20 | (online-init) 21 | ;; @@ 22 | ;; => 23 | ;;; {"type":"html","content":"","value":"pr'ed value"} 24 | ;; <= 25 | 26 | ;; @@ 27 | (-> (plotly) 28 | (add-bar :x [20 14 23] 29 | :y ["giraffes" "orangutans" "monkeys"] 30 | :orientation "h") 31 | (plot "Basic Horizontal Bar Chart" :fileopt "overwrite") 32 | embed-url) 33 | ;; @@ 34 | ;; => 35 | ;;; {"type":"html","content":"","value":"pr'ed value"} 36 | ;; <= 37 | 38 | ;; ** 39 | ;;; ##Colored Horizontal Bar Chart 40 | ;; ** 41 | 42 | ;; @@ 43 | (-> (plotly) 44 | (add-bar :x [20 14 23] 45 | :y ["giraffes" "orangutans" "monkeys"] 46 | :name "SF Zoo" 47 | :orientation "h" 48 | :marker {:color "rgba(246, 78, 139, 0.6)" :line {:color "rgba(246, 78, 139, 1.0)" :width 3}}) 49 | (add-bar :x [12 18 19] 50 | :y ["giraffes" "orangutans" "monkeys"] 51 | :name "LA Zoo" 52 | :orientation "h" 53 | :marker {:color "rgba(58, 71, 80, 0.6)" :line {:color "rgba(58, 71, 80, 1.0)" :width 3}}) 54 | (set-layout :barmode "stack") 55 | (plot "Colored Horizontal Bar Chart" :fileopt "overwrite") 56 | embed-url) 57 | ;; @@ 58 | ;; => 59 | ;;; {"type":"html","content":"","value":"pr'ed value"} 60 | ;; <= 61 | 62 | ;; ** 63 | ;;; ##Color Palette for Bar Chart 64 | ;; ** 65 | 66 | ;; @@ 67 | (let [top-labels ["Strongly
agree" "Agree" "Neutral" "Disagree" "Strongly
disagree"] 68 | colors ["rgba(38 24 74 0.8)" "rgba(71 58 131 0.8)" "rgba(122 120 168 0.8)" 69 | "rgba(164 163 204 0.85)" "rgba(190 192 213 1)"] 70 | x-data [[21 30 21 16 12] [24 31 19 15 11] [27 26 23 11 13] [29 24 15 18 14]] 71 | y-data ["The course was effectively
organized" 72 | "The course developed my
abilities and skills for
the subject" 73 | "The course developed my
ability to think critically about
the subject" 74 | "I would recommend this
course to a friend"] 75 | get-mid (fn [xs] 76 | (let [s (butlast (reductions + 0 xs)) 77 | halves (m/div xs 2)] 78 | (m/add s halves))) 79 | xs-mid (map get-mid x-data)] 80 | (-> (plotly) 81 | (plot-seq 82 | (for [[x c] (map vector (m/transpose x-data) colors)] 83 | #(add-bar % 84 | :x x 85 | :y y-data 86 | :orientation "h" 87 | :marker {:color c 88 | :line {:color "rgb(248, 248, 249)" :width 1}}))) 89 | (set-layout :xaxis {:showgrid false 90 | :showline false 91 | :showticklabels false 92 | :zeroline false 93 | :domain [0.15 1]} 94 | :yaxis {:showgrid false 95 | :showline false 96 | :showticklabels false 97 | :zeroline false} 98 | :barmode "stack" 99 | :showlegend false 100 | :paper_bgcolor "rgb(248, 248, 255)" 101 | :plot_bgcolor "rgb(248, 248, 255)" 102 | :margin {:l 120 :r 10 :t 120 :b 80}) 103 | (add-annotations 104 | (for [[x y] (map vector x-data y-data)] 105 | {:xref "paper" :yref "y" :x 0.14 :y y :xanchor "right" :text y :showarrow false :align "right"})) 106 | (add-annotations 107 | (for [idy (range (count y-data)) idx (range (count (first xs-mid)))] 108 | {:xref "x" 109 | :yref "y" 110 | :x (nth (nth xs-mid idy) idx) 111 | :y (nth y-data idy) 112 | :text (str (nth (nth x-data idy) idx) "%") 113 | :showarrow false 114 | :font {:color "rgb(248, 248, 255)" 115 | :family "Arial" 116 | :size 14}})) 117 | (add-annotations 118 | (for [[x label] (map vector (last xs-mid) top-labels)] 119 | {:xref "x" :yref "paper" 120 | :x x :y 1.1 :text label :showarrow false})) 121 | 122 | (plot "Color Palette for Bar Chart" :fileopt "overwrite") 123 | embed-url)) 124 | ;; @@ 125 | ;; => 126 | ;;; {"type":"html","content":"","value":"pr'ed value"} 127 | ;; <= 128 | 129 | ;; @@ 130 | (let [y-saving [1.3586 2.2623 4.9822 6.5097 7.4812 7.5133 15.2148 17.5205] 131 | y-net-worth [93453.92 81666.57 69889.62 78381.53 141395.3 92969.02 66090.18 122379.3] 132 | x-saving ["Japan" "United Kingdom" "Canada" "Netherlands" 133 | "United States" "Belgium" "Sweden" "Switzerland"] 134 | x-net-worth ["Japan" "United Kingdom" "Canada" "Netherlands" 135 | "United States" "Belgium" "Sweden" "Switzerland"]] 136 | (-> (plotly) 137 | (add-bar :x y-saving 138 | :y x-saving 139 | :xaxis "x1" 140 | :yaxis "y1" 141 | :name "Household savings, percentage of household disposable income" 142 | :orientation "h" 143 | :marker {:color "rgba(50, 171, 96, 0.6)" 144 | :line {:color "rgba(50, 171, 96, 1.0)" :width 1}}) 145 | (add-scatter :x y-net-worth 146 | :xaxis "x2" 147 | :yaxis "y2" 148 | :y x-saving 149 | :mode "lines+markers" 150 | :name "Household net worth, Million USD/capita" 151 | :line {:color "rgb(128, 0, 128)"}) 152 | (set-layout :title "Household savings & net worth for eight OECD countries" 153 | :yaxis1 {:showticklabels true 154 | :domain [0 0.85]} 155 | :yaxis2 {:anchor "x2" 156 | :linecolor "rgba(102, 102, 102, 0.8)" 157 | :domain [0 0.85] 158 | :showticklabels false} 159 | :xaxis1 {:domain [0 0.45] 160 | :zeroline false 161 | :showline false 162 | :showticklabels true 163 | :showgrid true} 164 | :xaxis2 {:domain [0.47 1] 165 | :zeroline false 166 | :showline false 167 | :showticklabels true 168 | :showgrid true 169 | :dtick 25000 170 | :side "top"} 171 | :margin {:l 100 :r 20 :t 70 :b 70} 172 | :legend {:x 0.029 :y 1.05 :font {:size 10}}) 173 | (plot "Bar Chart with Line Plot" :fileopt "overwrite") 174 | embed-url)) 175 | ;; @@ 176 | ;; => 177 | ;;; {"type":"html","content":"","value":"pr'ed value"} 178 | ;; <= 179 | 180 | ;; @@ 181 | 182 | ;; @@ 183 | -------------------------------------------------------------------------------- /examples/line-charts.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; # Line-Charts 5 | ;; ** 6 | 7 | ;; @@ 8 | (ns bubble 9 | (:require [clojure.core.matrix :as m] 10 | [clojure.core.matrix.dataset :as d] 11 | [clojure.core.matrix.random :as rnd] 12 | [clojure.data.csv :as csv]) 13 | (:use [plotly-clj.core] :reload-all)) 14 | ;; @@ 15 | ;; => 16 | ;;; {"type":"html","content":"nil","value":"nil"} 17 | ;; <= 18 | 19 | ;; @@ 20 | (online-init) 21 | ;; @@ 22 | ;; => 23 | ;;; {"type":"html","content":"","value":"pr'ed value"} 24 | ;; <= 25 | 26 | ;; @@ 27 | (-> (plotly (rnd/sample-normal 500)) 28 | (add-scatter) 29 | (plot "simple-line-charts") 30 | embed-url) 31 | ;; @@ 32 | ;; => 33 | ;;; {"type":"html","content":"","value":"pr'ed value"} 34 | ;; <= 35 | 36 | ;; @@ 37 | (def data 38 | (d/dataset 39 | {:month ["January" "February" "March" "April" "May" "June" "July" "August" "September" "October" "November" "December"] 40 | :high-2000 [32.5 37.6 49.9 53.0 69.1 75.4 76.5 76.6 70.7 60.6 45.1 29.3] 41 | :low-2000 [13.8 22.3 32.5 37.2 49.9 56.1 57.7 58.3 51.2 42.8 31.6 15.9] 42 | :high-2007 [36.5 26.6 43.6 52.3 71.5 81.4 80.5 82.2 76.0 67.3 46.1 35.0] 43 | :low-2007 [23.6 14.0 27.0 36.8 47.6 57.7 58.9 61.2 53.3 48.5 31.0 23.6] 44 | :high-2014 [28.8 28.5 37.0 56.8 69.7 79.7 78.5 77.8 74.1 62.6 45.3 39.9] 45 | :low-2014 [12.7 14.3 18.6 35.5 49.9 58.0 60.0 58.6 51.7 45.2 32.2 29.1]})) 46 | 47 | (let [colors (flatten (repeat 3 ["rgb(205, 12, 24)" "rgb(22, 96, 167)"])) 48 | line-types ["dashdot" "dashdot" "dash" "dash" "dot" "dot"] 49 | cols (map #(d/column-name data %) (range 1 7))] 50 | (-> (plotly data) 51 | (plot-seq 52 | (for [[col color line-type] (map vector cols colors line-types)] 53 | #(add-scatter % 54 | :x :month 55 | :y col 56 | :name (str col) 57 | :line {:color color :width 4 :dash line-type}))) 58 | (plot "Average-High-and-Low-Temperatures-in-New-York" :fileopt "overwrite") 59 | embed-url)) 60 | ;; @@ 61 | ;; => 62 | ;;; {"type":"html","content":"","value":"pr'ed value"} 63 | ;; <= 64 | 65 | ;; @@ 66 | (-> (plotly [10 20 nil 15 10 5 15 nil 20 10 10 15 25 20 10]) 67 | (add-scatter :connectgaps true) 68 | (add-scatter :y [15 25 nil 20 15 10 20 nil 25 15 15 20 30 25 15]) 69 | (plot "line-gaps" :fileopt "overwrite") 70 | embed-url) 71 | ;; @@ 72 | ;; => 73 | ;;; {"type":"html","content":"","value":"pr'ed value"} 74 | ;; <= 75 | 76 | ;; @@ 77 | (-> (plotly) 78 | (plot-seq 79 | (for [[line-type y] (map vector 80 | ["linear" "spline" "vhv" "hvh" "vh" "hv"] 81 | (map #(m/add (* 5 %) [1 3 2 3 1]) (range 6)))] 82 | #(add-scatter % :y y :line {:shape line-type}))) 83 | (set-layout :legend {:y 0.5 :traceorder "reversed"}) 84 | (plot "Interpolation-with-Line-Plots" :fileopt "overwrite") 85 | embed-url) 86 | ;; @@ 87 | ;; => 88 | ;;; {"type":"html","content":"","value":"pr'ed value"} 89 | ;; <= 90 | 91 | ;; @@ 92 | (let [rates [[74 82 80 74 73 72 74 70 70 66 66 69] 93 | [45 42 50 46 36 36 34 35 32 31 31 28] 94 | [13 14 20 24 20 24 24 40 35 41 43 50] 95 | [18 21 18 21 16 14 13 18 17 16 19 23]] 96 | labels ["Television" "Newspaper" "Internet" "Radio"] 97 | marker-sizes [8 8 12 8] 98 | colors ["rgba(67,67,67,1)" "rgba(115,115,115,1)" "rgba(49,130,189, 1)" "rgba(189,189,189,1)"]] 99 | (-> (plotly {:years [2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 2013]}) 100 | (plot-seq 101 | (for [[rate color] (map vector rates colors)] 102 | #(add-scatter % 103 | :x :years 104 | :y rate 105 | :mode "lines" 106 | :line {:color color :width 2}))) 107 | (plot-seq 108 | (for [[rate color marker-size] (map vector rates colors marker-sizes)] 109 | #(add-scatter % 110 | :x [2001 2013] 111 | :y [(first rate) (last rate)] 112 | :mode "markers" 113 | :marker {:color color :size marker-size}))) 114 | (set-layout :xaxis {:showline true 115 | :showgrid false 116 | :showticklabels true 117 | :linecolor "rgb(204, 204, 204)" 118 | :autotick false 119 | :ticks "outside" 120 | :tickcolor "rgb(204, 204, 204)" 121 | :tickwidth 2 122 | :ticklen 5 123 | :tickfont {:family "Arial" :size 12 :color "rgb(82, 82, 82)"}} 124 | :yaxis {:showgrid false 125 | :zeroline false 126 | :showline false 127 | :showticklabels false} 128 | :showlegend false 129 | :autosize false 130 | :margin {:autoexpand false :l 100 :r 20 :t 110}) 131 | (add-annotations 132 | (for [[rate color label] (map vector rates colors labels)] 133 | {:xref "paper" 134 | :x 0.05 135 | :y (first rate) 136 | :xanchor "right" 137 | :yanchor "middle" 138 | :text (str label (first rate) "%") 139 | :font {:family "Arial" :size 16 :color color} 140 | :showarrow false})) 141 | 142 | (add-annotations 143 | (for [[rate color label] (map vector rates colors labels)] 144 | {:xref "paper" 145 | :x 0.95 146 | :y (last rate) 147 | :xanchor "left" 148 | :yanchor "middle" 149 | :text (str (last rate) "%") 150 | :font {:family "Arial" :size 16 :color color} 151 | :showarrow false})) 152 | (add-annotations 153 | {:xref "paper" 154 | :yref "paper" 155 | :x 0.5 156 | :y -0.1 157 | :xanchor "center" 158 | :yanchor "top" 159 | :text "Source: PewResearch Center & Storytelling with data" 160 | :font {:family "Arial" :size 12 :color "rgb(150,150,150)"} 161 | :showarrow false}) 162 | (add-annotations 163 | {:xref "paper" 164 | :yref "paper" 165 | :x 0.0 166 | :y 1.05 167 | :xanchor "left" 168 | :yanchor "bottom" 169 | :text "Main Source for News" 170 | :font {:family "Arial" :size 30 :color "rgb(37, 37, 37)"} 171 | :showarrow false}) 172 | (plot "Label-Lines-with-Annotations" :fileopt "overwrite") 173 | embed-url)) 174 | ;; @@ 175 | ;; => 176 | ;;; {"type":"html","content":"","value":"pr'ed value"} 177 | ;; <= 178 | 179 | ;; @@ 180 | 181 | ;; @@ 182 | -------------------------------------------------------------------------------- /examples/scatter-3d.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; #Scatter-3d 5 | ;; ** 6 | 7 | ;; @@ 8 | (ns bar 9 | (:require [clojure.core.matrix :as m] 10 | [clojure.core.matrix.dataset :as d] 11 | [clojure.core.matrix.random :as rnd] 12 | [clojure.data.csv :as csv]) 13 | (:use [plotly-clj.core] :reload-all)) 14 | ;; @@ 15 | ;; => 16 | ;;; {"type":"html","content":"nil","value":"nil"} 17 | ;; <= 18 | 19 | ;; @@ 20 | (online-init) 21 | ;; @@ 22 | ;; => 23 | ;;; {"type":"html","content":"","value":"pr'ed value"} 24 | ;; <= 25 | 26 | ;; @@ 27 | (let [steps (range 0 20 0.1)] 28 | (-> (plotly) 29 | (add-scatter-3d :x (m/sin steps) :y (m/cos steps) :z steps :mode "lines") 30 | (plot "simple lines 3d" :fileopt "overwrite") 31 | embed-url)) 32 | ;; @@ 33 | ;; => 34 | ;;; {"type":"html","content":"","value":"pr'ed value"} 35 | ;; <= 36 | 37 | ;; @@ 38 | (def data 39 | (let [in-file (slurp "https://raw.githubusercontent.com/plotly/datasets/master/3d-scatter.csv") 40 | data (doall (csv/read-csv in-file))] 41 | (d/dataset (map keyword (first data)) (rest data)) 42 | )) 43 | ;; @@ 44 | ;; => 45 | ;;; {"type":"html","content":"#'bar/data","value":"#'bar/data"} 46 | ;; <= 47 | 48 | ;; @@ 49 | (-> (plotly data) 50 | (add-scatter-3d :x :x1 :y :y1 :z :z1 :mode "markers" 51 | :marker {:size 12 52 | :opacity 0.8 53 | :line {:color "rgba(217, 217, 217, 0.14)" :width 0.5}}) 54 | (plot "simple scatter 3d" :fileopt "overwrite") 55 | embed-url) 56 | ;; @@ 57 | ;; => 58 | ;;; {"type":"html","content":"","value":"pr'ed value"} 59 | ;; <= 60 | -------------------------------------------------------------------------------- /examples/scatter.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; # SCATTER 5 | ;;; 6 | ;;; This page shows almost all the examples related to scatter in [Plotly Python Library](https://plot.ly/python/) and [Plotly R Library](https://plot.ly/r/) 7 | ;; ** 8 | 9 | ;; @@ 10 | (ns scatter 11 | (:require [clojure.core.matrix :as m] 12 | [clojure.core.matrix.dataset :as d] 13 | [clojure.core.matrix.random :as rnd] 14 | [clojure.data.csv :as csv]) 15 | (:use [plotly-clj.core] :reload-all)) 16 | ;; @@ 17 | ;; => 18 | ;;; {"type":"html","content":"nil","value":"nil"} 19 | ;; <= 20 | 21 | ;; ** 22 | ;;; ## Initial 23 | ;;; 24 | ;;; In order to play offline, we need to call ``offline-init`` first and it will insert the whole ``plotly.min.js`` in this page. 25 | ;;; However, this will make the page a little large. The size of ``plotly.min.js`` is about **1.7M** . To reduce the page size, I use the ``online-init`` instead here. 26 | ;; ** 27 | 28 | ;; @@ 29 | (online-init) ;; Call offline-init here if you want to play offline 30 | ;; @@ 31 | ;; => 32 | ;;; {"type":"html","content":"","value":"pr'ed value"} 33 | ;; <= 34 | 35 | ;; ** 36 | ;;; ##Simple Scatter Plot 37 | ;; ** 38 | 39 | ;; @@ 40 | ;; If you just want to play offline, just use ``iplot`` 41 | ;; 42 | ;;(let [n 200] 43 | ;; (-> (plotly (rnd/sample-normal n) 44 | ;; (rnd/sample-normal n)) 45 | ;; (add-scatter :mode "markers") 46 | ;; iplot)) 47 | 48 | ;; In order to make the output more visual when viewed through gorilla-repl viewer. 49 | ;; I publish the figure to plot.ly server first and then embed the iframe here 50 | (let [n 200] 51 | (-> (plotly (rnd/sample-normal n) 52 | (rnd/sample-normal n)) 53 | (add-scatter :mode "markers") 54 | (plot "simple-scatter-plot" :fileopt "overwrite") 55 | embed-url)) 56 | ;; @@ 57 | ;; => 58 | ;;; {"type":"html","content":"","value":"pr'ed value"} 59 | ;; <= 60 | 61 | ;; ** 62 | ;;; ## Line and Scatter Plots 63 | ;; ** 64 | 65 | ;; @@ 66 | (let [n 100] 67 | (-> (plotly (rnd/sample-normal n)) 68 | (add-scatter :mode "lines+markers") 69 | (add-scatter 70 | :y #(m/add 5 (d/column % 0)) ;; you can use a function which accept a dataset to create data 71 | :mode "markers") 72 | (add-scatter 73 | :y #(m/add -5 (d/column % :x)) ;; you can also use the default column name :x 74 | :mode "lines") 75 | (plot "line-and-scatter-plots" :fileopt "overwrite") 76 | embed-url)) 77 | ;; @@ 78 | ;; => 79 | ;;; {"type":"html","content":"","value":"pr'ed value"} 80 | ;; <= 81 | 82 | ;; ** 83 | ;;; ## Style Scatter Plots 84 | ;; ** 85 | 86 | ;; @@ 87 | (let [n 200] 88 | (-> (plotly) 89 | (add-scatter :x (rnd/sample-normal n) 90 | :y (m/add 2 (rnd/sample-normal n)) 91 | :mode "markers" 92 | :name "above" 93 | :marker {:size 10 94 | :color "rgba(152, 0, 0, .8)" 95 | :line {:width 2 96 | :color "rgb(0, 0, 0)"}}) 97 | (add-scatter :x (rnd/sample-normal n) 98 | :y (m/add -2 (rnd/sample-normal n)) 99 | :mode "markers" 100 | :name "below" 101 | :marker {:color "rgba(255, 182, 193, .9)" 102 | :size 10 103 | :line {:width 2}}) 104 | (set-layout :xaxis {:zeroline false} 105 | :yaxis {:zeroline false} 106 | :title "Styled Scatter") 107 | (plot "style-scatter-plots" :fileopt "overwrite") 108 | embed-url)) 109 | ;; @@ 110 | ;; => 111 | ;;; {"type":"html","content":"","value":"pr'ed value"} 112 | ;; <= 113 | 114 | ;; ** 115 | ;;; ## Data Labels on Hover 116 | ;; ** 117 | 118 | ;; @@ 119 | (def usa-states 120 | (let [in-file (slurp "https://raw.githubusercontent.com/plotly/datasets/master/2014_usa_states.csv") 121 | data (doall (csv/read-csv in-file))] 122 | (d/dataset (map keyword (first data)) (rest data)) 123 | )) 124 | ;; @@ 125 | ;; => 126 | ;;; {"type":"html","content":"#'scatter/usa-states","value":"#'scatter/usa-states"} 127 | ;; <= 128 | 129 | ;; @@ 130 | (m/pm usa-states) 131 | ;; @@ 132 | ;; -> 133 | ;;; [[:rank :state :postal :pop] 134 | ;;; [ 1 Alabama AL 4849377.0] 135 | ;;; [ 2 Alaska AK 736732.0] 136 | ;;; [ 3 Arizona AZ 6731484.0] 137 | ;;; [ 4 Arkansas AR 2966369.0] 138 | ;;; [ 5 California CA 38802500.0] 139 | ;;; [ 6 Colorado CO 5355866.0] 140 | ;;; [ 7 Connecticut CT 3596677.0] 141 | ;;; [ 8 Delaware DE 935614.0] 142 | ;;; [ 9 District of Columbia DC 658893.0] 143 | ;;; [ 10 Florida FL 19893297.0] 144 | ;;; [ 11 Georgia GA 10097343.0] 145 | ;;; [ 12 Hawaii HI 1419561.0] 146 | ;;; [ 13 Idaho ID 1634464.0] 147 | ;;; [ 14 Illinois IL 12880580.0] 148 | ;;; [ 15 Indiana IN 6596855.0] 149 | ;;; [ 16 Iowa IA 3107126.0] 150 | ;;; [ 17 Kansas KS 2904021.0] 151 | ;;; [ 18 Kentucky KY 4413457.0] 152 | ;;; [ 19 Louisiana LA 4649676.0] 153 | ;;; [ 20 Maine ME 1330089.0] 154 | ;;; [ 21 Maryland MD 5976407.0] 155 | ;;; [ 22 Massachusetts MA 6745408.0] 156 | ;;; [ 23 Michigan MI 9909877.0] 157 | ;;; [ 24 Minnesota MN 5457173.0] 158 | ;;; [ 25 Mississippi MS 2994079.0] 159 | ;;; [ 26 Missouri MO 6063589.0] 160 | ;;; [ 27 Montana MT 1023579.0] 161 | ;;; [ 28 Nebraska NE 1881503.0] 162 | ;;; [ 29 Nevada NV 2839098.0] 163 | ;;; [ 30 New Hampshire NH 1326813.0] 164 | ;;; [ 31 New Jersey NJ 8938175.0] 165 | ;;; [ 32 New Mexico NM 2085572.0] 166 | ;;; [ 33 New York NY 19746227.0] 167 | ;;; [ 34 North Carolina NC 9943964.0] 168 | ;;; [ 35 North Dakota ND 739482.0] 169 | ;;; [ 36 Ohio OH 11594163.0] 170 | ;;; [ 37 Oklahoma OK 3878051.0] 171 | ;;; [ 38 Oregon OR 3970239.0] 172 | ;;; [ 39 Pennsylvania PA 12787209.0] 173 | ;;; [ 40 Puerto Rico PR 3548397.0] 174 | ;;; [ 41 Rhode Island RI 1055173.0] 175 | ;;; [ 42 South Carolina SC 4832482.0] 176 | ;;; [ 43 South Dakota SD 853175.0] 177 | ;;; [ 44 Tennessee TN 6549352.0] 178 | ;;; [ 45 Texas TX 26956958.0] 179 | ;;; [ 46 Utah UT 2942902.0] 180 | ;;; [ 47 Vermont VT 626562.0] 181 | ;;; [ 48 Virginia VA 8326289.0] 182 | ;;; [ 49 Washington WA 7061530.0] 183 | ;;; [ 50 West Virginia WV 1850326.0] 184 | ;;; [ 51 Wisconsin WI 5757564.0] 185 | ;;; [ 52 Wyoming WY 584153.0]] 186 | ;;; 187 | ;; <- 188 | ;; => 189 | ;;; {"type":"html","content":"nil","value":"nil"} 190 | ;; <= 191 | 192 | ;; @@ 193 | (defn parse-float 194 | [x] 195 | (Float/parseFloat x)) 196 | 197 | (defn mark-usa 198 | [p i] 199 | (-> p 200 | (add-scatter :x :rank 201 | :y #(m/add (* i 1000000) (map parse-float (d/column % :pop))) 202 | :mode "markers" 203 | :marker {:size 14 204 | :color (str "hsl(" (float (* i (/ 360 (count usa-states)))) ",50%,50%)") 205 | :line {:width 1} 206 | :opacity 0.3} 207 | :name (+ 2000 i) 208 | :text (nth (d/column usa-states :state) i)))) 209 | 210 | (-> (reduce mark-usa 211 | (plotly usa-states) 212 | (range (count usa-states))) 213 | (set-layout :showlegend false 214 | :hovermode "closest") 215 | (plot "data-labels-on-hover" :fileopt "overwrite") 216 | embed-url) 217 | ;; @@ 218 | ;; => 219 | ;;; {"type":"html","content":"","value":"pr'ed value"} 220 | ;; <= 221 | 222 | ;; ** 223 | ;;; ## Scatter with a Color Dimension 224 | ;; ** 225 | 226 | ;; @@ 227 | (-> (plotly (rnd/sample-normal 100)) 228 | (add-scatter :mode "markers" 229 | :marker {:size 16 230 | :color :x 231 | :colorscale "Viridis" 232 | :showscale true}) 233 | (plot "scatter-with-a-color-dimension" :fileopt "overwrite") 234 | embed-url) 235 | ;; @@ 236 | ;; => 237 | ;;; {"type":"html","content":"","value":"pr'ed value"} 238 | ;; <= 239 | 240 | ;; ** 241 | ;;; ## Categorical Dot Plot 242 | ;; ** 243 | 244 | ;; @@ 245 | (let [data {:country ["Switzerland (2011)" "Chile (2013)" "Japan (2014)" "United States (2012)" "Slovenia (2014)" "Canada (2011)" "Poland (2010)" "Estonia (2015)" "Luxembourg (2013)" "Portugal (2011)"] 246 | :voting_pop [40 45.7 52 53.6 54.1 54.2 54.5 54.7 55.1 56.6] 247 | :reg_voters [49.1 42 52.7 84.3 51.7 61.1 55.3 64.2 91.1 58.9]}] 248 | 249 | (-> (plotly data) 250 | (add-scatter 251 | :x :voting_pop 252 | :y :country 253 | :mode "markers" 254 | :name "Percent of estimated voting age population" 255 | :marker {:size 16 256 | :symbol "circle" 257 | :color "rgba(156, 165, 196, 0.95)" 258 | :line {:color "rgba(156, 165, 196, 1.0)" 259 | :width 1}}) 260 | (add-scatter 261 | :x :reg_voters 262 | :y :country 263 | :name "Percent of estimated registered voters" 264 | :mode "markers" 265 | :marker {:color "rgba(204, 204, 204, 0.95)" 266 | :symbol "circle" 267 | :size 16 268 | :line {:color "rgba(217, 217, 217, 1.0)" 269 | :width 1}}) 270 | (set-layout :title "Votes cast for ten lowest voting age population in OECD countries" 271 | :xaxis {:showgrid false 272 | :showline true 273 | :linecolor "rgb(102, 102, 102)" 274 | :titlefont {:color "rgb ()102,102,102"} 275 | :tickfont {:color "rgb(204, 204, 204)" 276 | :autotick false :dtick 10 277 | :ticks "outside" 278 | :tickcolor "rgb(102, 102, 102)"}} 279 | :margin {:l 140 :r 40 :b 50 :t 80} 280 | :legend {:font {:size 10} 281 | :yanchor "middle" 282 | :xanchor "right"} 283 | :width 800 284 | :height 600 285 | :paper_bgcolor "rgb(254, 247, 234)" 286 | :plot_bgcolor "rgb(254, 247, 234)" 287 | :hovermode "closest") 288 | (plot "categorial-dot-plot" :fileopt "overwrite") 289 | embed-url)) 290 | ;; @@ 291 | ;; => 292 | ;;; {"type":"html","content":"","value":"pr'ed value"} 293 | ;; <= 294 | 295 | ;; ** 296 | ;;; ## Large Data Sets 297 | ;;; 298 | ;;; Because the params of ``add-scatter`` will overwrite any predetermined settings, we can just set the ``:type`` to ``"scattergl"`` and then plotly will use WebGL instead of SVG. 299 | ;;; 300 | ;;; **Be careful to use this feature!** 301 | ;; ** 302 | 303 | ;; @@ 304 | (let [n 10000] 305 | (-> (plotly (rnd/sample-normal n) (rnd/sample-normal n)) 306 | (add-scatter :mode "markers" 307 | :type "scattergl" ;; you can set a larger n and comment out this line, 308 | ;; then you will see the load difference 309 | :marker {:color "FFBAD2" 310 | :line {:width 1}}) 311 | (plot "large-data-sets" :fileopt "overwrite") 312 | embed-url)) 313 | ;; @@ 314 | ;; => 315 | ;;; {"type":"html","content":"","value":"pr'ed value"} 316 | ;; <= 317 | 318 | ;; ** 319 | ;;; ## Custom Color Scales 320 | ;; ** 321 | 322 | ;; @@ 323 | (def iris 324 | (let [in-file (slurp "https://raw.githubusercontent.com/uiuc-cse/data-fa14/gh-pages/data/iris.csv") 325 | data (doall (csv/read-csv in-file))] 326 | (d/dataset (map keyword (first data)) (rest data)) 327 | )) 328 | ;; @@ 329 | ;; => 330 | ;;; {"type":"html","content":"#'scatter/iris","value":"#'scatter/iris"} 331 | ;; <= 332 | 333 | ;; @@ 334 | (-> (plotly iris) 335 | (group-dataset :species) 336 | (add-scatter :x :sepal_length 337 | :y :petal_length 338 | :mode "markers" 339 | :gp-colors ["red" "green" "purple"] 340 | :gp-symbols ["circle" "x" "circle-open"]) 341 | (set-layout :hovermode "closest") 342 | (plot "custom-color-scales" :fileopt "overwrite") 343 | embed-url) 344 | ;; @@ 345 | ;; => 346 | ;;; {"type":"html","content":"","value":"pr'ed value"} 347 | ;; <= 348 | -------------------------------------------------------------------------------- /examples/subplot.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; # Subplots 5 | ;; ** 6 | 7 | ;; @@ 8 | (ns scatter 9 | (:require [clojure.core.matrix :as m] 10 | [clojure.core.matrix.dataset :as d] 11 | [clojure.core.matrix.random :as rnd] 12 | [clojure.data.csv :as csv]) 13 | (:use [plotly-clj.core] :reload-all)) 14 | ;; @@ 15 | ;; => 16 | ;;; {"type":"html","content":"nil","value":"nil"} 17 | ;; <= 18 | 19 | ;; @@ 20 | (online-init) 21 | ;; @@ 22 | ;; => 23 | ;;; {"type":"html","content":"","value":"pr'ed value"} 24 | ;; <= 25 | 26 | ;; @@ 27 | (-> (plotly) 28 | (add-scatter :x [1 2 3] :y [2 3 4]) 29 | (add-scatter :x [20 30 40] :y [50 60 70]) 30 | (subplot :nrow 1 :ncol 2) 31 | (plot "simple subplots 1" :fileopt "overwrite") 32 | embed-url) 33 | ;; @@ 34 | ;; => 35 | ;;; {"type":"html","content":"","value":"pr'ed value"} 36 | ;; <= 37 | 38 | ;; @@ 39 | (-> (plotly) 40 | (add-scatter :x [1 2 3] :y [2 3 4]) 41 | (add-scatter :x [20 30 40] :y [5 5 5]) 42 | (add-scatter :x [2 3 4] :y [600 700 800]) 43 | (add-scatter :x [4000 5000 6000] :y [7000 8000 9000]) 44 | (subplot :nrow 2 :ncol 2) 45 | (plot "simple subplots 2" :fileopt "overwrite") 46 | embed-url) 47 | ;; @@ 48 | ;; => 49 | ;;; {"type":"html","content":"","value":"pr'ed value"} 50 | ;; <= 51 | 52 | ;; @@ 53 | (-> (plotly) 54 | (plot-seq 55 | (for [_ (range 10)] 56 | #(add-scatter % :x [1 2 3] :y [2 3 4]))) 57 | (subplot :nrow 2 :ncol 5 :sharex true) 58 | (plot "simple subplots 3" :fileopt "overwrite") 59 | embed-url) 60 | ;; @@ 61 | ;; => 62 | ;;; {"type":"html","content":"","value":"pr'ed value"} 63 | ;; <= 64 | 65 | ;; @@ 66 | (-> (plotly) 67 | (add-scatter :x [1 2 3] :y [2 3 4]) 68 | (add-scatter-3d :x [1 2 3] :y [2 3 4] :z [1 1 1]) 69 | (add-scatter :x [1 2 3] :y [2 3 4]) 70 | (add-scatter-3d :x [1 2 3] :y [2 3 4] :z [1 1 1]) 71 | (subplot :nrow 2) 72 | (plot "simple subplots 4" :fileopt "overwrite") 73 | embed-url) 74 | ;; @@ 75 | ;; => 76 | ;;; {"type":"html","content":"","value":"pr'ed value"} 77 | ;; <= 78 | 79 | ;; @@ 80 | 81 | ;; @@ 82 | -------------------------------------------------------------------------------- /examples/surface.clj: -------------------------------------------------------------------------------- 1 | ;; gorilla-repl.fileformat = 1 2 | 3 | ;; ** 4 | ;;; #Surface 5 | ;; ** 6 | 7 | ;; @@ 8 | (ns scatter 9 | (:require [clojure.core.matrix :as m] 10 | [clojure.core.matrix.dataset :as d] 11 | [clojure.core.matrix.random :as rnd] 12 | [clojure.data.csv :as csv]) 13 | (:use [plotly-clj.core] :reload-all)) 14 | ;; @@ 15 | ;; => 16 | ;;; {"type":"html","content":"nil","value":"nil"} 17 | ;; <= 18 | 19 | ;; @@ 20 | (online-init) 21 | ;; @@ 22 | ;; => 23 | ;;; {"type":"html","content":"","value":"pr'ed value"} 24 | ;; <= 25 | 26 | ;; @@ 27 | (def data 28 | (let [in-file (slurp "https://raw.githubusercontent.com/plotly/datasets/master/api_docs/mt_bruno_elevation.csv") 29 | data (doall (csv/read-csv in-file))] 30 | (m/emap #(Float/parseFloat %) (d/dataset (rest data))) 31 | )) 32 | ;; @@ 33 | ;; => 34 | ;;; {"type":"html","content":"#'scatter/data","value":"#'scatter/data"} 35 | ;; <= 36 | 37 | ;; @@ 38 | (-> (plotly) 39 | (add-surface :z (m/to-nested-vectors data)) 40 | (plot "3D surface" :fileopt "overwrite") 41 | embed-url) 42 | ;; @@ 43 | ;; => 44 | ;;; {"type":"html","content":"","value":"pr'ed value"} 45 | ;; <= 46 | 47 | ;; @@ 48 | (-> (plotly) 49 | (add-surface :z [[0 0 0] 50 | [0 1 0] 51 | [0 0 0] 52 | [0 -1 0] 53 | [0 0 0]]) 54 | (plot "3D surface 2" :fileopt "overwrite") 55 | embed-url) 56 | ;; @@ 57 | ;; => 58 | ;;; {"type":"html","content":"","value":"pr'ed value"} 59 | ;; <= 60 | 61 | ;; @@ 62 | 63 | ;; @@ 64 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject plotly-clj "0.1.1" 2 | :description "Plotly Clojure Client" 3 | :url "https://github.com/findmyway/plotly-clj" 4 | :license {:name "MIT License" 5 | :url "https://opensource.org/licenses/MIT" 6 | :year 2017 7 | :key "mit"} 8 | :dependencies [[org.clojure/clojure "1.8.0"] 9 | [hiccup "1.0.5"] 10 | [org.clojure/data.json "0.2.6"] 11 | [net.mikera/core.matrix "0.57.0"] 12 | [com.rpl/specter "0.13.2"] 13 | [http-kit "2.2.0"] 14 | [lein-gorilla "0.4.0"] 15 | [org.clojure/data.csv "0.1.3"]] 16 | :plugins [[lein-gorilla "0.4.0"]] 17 | :repositories [["releases" {:url "https://clojars.org/repo" 18 | :creds :gpg}]]) 19 | -------------------------------------------------------------------------------- /src/plotly_clj/core.clj: -------------------------------------------------------------------------------- 1 | (ns plotly-clj.core 2 | (:require [hiccup.core :refer [html]] 3 | [clojure.data.json :as json] 4 | [clojure.core.matrix :as m] 5 | [clojure.core.matrix.random :as rnd] 6 | [clojure.core.matrix.dataset :as d] 7 | [com.rpl.specter :as sp] 8 | [clojure.set :as s] 9 | [plotly-clj.scale :refer [scale-color factor]] 10 | [gorilla-renderable.core :as render] 11 | [clojure.java.io :refer [resource make-parents as-file]] 12 | [org.httpkit.client :as http] 13 | [clojure.java.browse :refer [browse-url]] 14 | [clojure.string :as string]) 15 | (:import [java.io File])) 16 | 17 | (declare group?) 18 | 19 | (defn make-div 20 | "Make a div element given traces, layout and configs" 21 | [[traces layout configs]] 22 | (let [uuid (str (java.util.UUID/randomUUID))] 23 | (html [:div.plotly-graph-div {:id uuid}] 24 | [:script {:type "text/javascript"} 25 | (str "window.PLOTLYENV=window.PLOTLYENV || {};" 26 | "window.PLOTLYENV.BASE_URL=\"https://plot.ly\";" 27 | "Plotly.newPlot(\"" uuid "\"," traces "," layout "," configs ")")]))) 28 | (defn wrap-div 29 | "Wrap a div element in a html page" 30 | [div] 31 | (let [content (html [:html 32 | [:head [:meta {:charset "utf-8"}]] 33 | [:body 34 | [:script {:type "text/javascript"} 35 | (slurp (resource "plotly.min.js"))] 36 | div]])] 37 | content)) 38 | 39 | (defn to-string 40 | "Format a plot object" 41 | [p] 42 | (map json/write-str (vals (select-keys p [:traces :layout :configs])))) 43 | 44 | (defn- make-plotly 45 | "Initial a plotly object with dataset" 46 | [ds] 47 | {:ds ds :traces [] :layout {} :configs {}}) 48 | 49 | (defn- make-dataset 50 | "A flexiable function to create a dataset from different kinds of " 51 | ([] nil) 52 | ([x] 53 | (cond 54 | (nil? x) nil 55 | (d/dataset? x) x 56 | (group? x) x 57 | (= 1 (m/dimensionality x)) (d/dataset {:x x}) 58 | ;; The x maybe a column-based map or row-based matrix. 59 | :else (d/dataset x))) 60 | ([x y] 61 | (d/dataset {:x x :y y})) 62 | ([x y z] 63 | (d/dataset {:x x :y y :z z}))) 64 | 65 | (defn plotly 66 | "Make a plotly object from: 67 | 1. a vector (will be given name :x) 68 | 2. two vectors (will be given name :x and :y) 69 | 3. three vectors (will be given name :x, :y and :z) 70 | 4. a dataset(clojure.core.matrix.dataset) 71 | TODO: add checkers 72 | " 73 | [& xs] 74 | (make-plotly (apply make-dataset xs))) 75 | 76 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 77 | (defn deep-merge [a b] 78 | "Used to merge recursive maps" 79 | (merge-with (fn [x y] 80 | (cond 81 | (map? y) (deep-merge x y) 82 | (sequential? y) (vec (concat x y)) 83 | :else y)) 84 | a b)) 85 | 86 | (defn set-dataset 87 | "Set the dataset of a plotly object. 88 | xs is the same input as ``plotly``" 89 | [p & xs] 90 | (assoc p :ds (apply make-dataset xs))) 91 | 92 | (defn set-layout 93 | "Set the layout of a plotly object. 94 | This function will clear all the layout and reset the layout! 95 | So functions like `add-annotations` will not work if called before 96 | this function." 97 | [p & {:as params}] 98 | (assoc p :layout params)) 99 | 100 | (defn update-layout 101 | "Update the layout of a plotly object" 102 | [p & {:as params}] 103 | (update p :layout #(deep-merge % params))) 104 | 105 | (defn set-configs 106 | "Set configs of a plotly object" 107 | [p & {:as params}] 108 | (assoc p :configs params)) 109 | 110 | (defn update-configs 111 | "Update configs of a plotly object" 112 | [p & {:as params}] 113 | (update p :configs #(deep-merge % params))) 114 | 115 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 116 | (defn- group-dataset-by 117 | "Helper function for group-dataset" 118 | [p ks] 119 | (let [group-idx (group-by #(nth ks %) (range (count (:ds p)))) 120 | idx->dataset (fn [[k idx]] (vector k (d/select-rows (:ds p) idx)))] 121 | (assoc p :ds (into {} (map idx->dataset group-idx))))) 122 | 123 | (defn group-dataset 124 | "Group a dataset based on k. 125 | k can be 126 | 1. a column name in dataset 127 | 2. a sequence of column names 128 | 3. a seq which has the same length as the dataset 129 | 4. a function takes dataset as input and 130 | returns a seq which has the same length as the dataset 131 | TODO: 132 | defmulti 133 | add checkers" 134 | [p k] 135 | (cond 136 | (keyword? k) ;; a column name 137 | (group-dataset-by p (d/column (:ds p) k)) 138 | (and (seq?) (every? keyword? k)) ;; a seq of column names 139 | (group-dataset-by p (d/select-columns (:ds p) k)) 140 | (fn? k) 141 | (group-dataset-by p (k (:ds p))) 142 | :else 143 | (group-dataset-by p k))) 144 | 145 | (defn group? 146 | "Check if dataset is a grouped dataset" 147 | [d] 148 | (and (map? d) 149 | (every? d/dataset? (vals d)))) 150 | 151 | (defn merge-group-dataset 152 | "Merge the dataset group of a plotly object into dataset" 153 | [p] 154 | (let [ds (:ds p)] 155 | (if (group? ds) 156 | (assoc p :ds (apply d/merge-datasets (vals ds))) 157 | p))) 158 | 159 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 160 | (defn trans-params 161 | "TODO: Add checkers. 162 | marker size must be numerical 163 | marker color can be numerical or categorized" 164 | [params dataset] 165 | (->> params 166 | 167 | (sp/transform 168 | (sp/multi-path 169 | [(sp/must :marker) (sp/must :size) keyword?] 170 | [(sp/must :x) keyword?] 171 | [(sp/must :y) keyword?] 172 | [(sp/must :z) keyword?]) 173 | #(d/column dataset %)) 174 | 175 | (sp/transform 176 | [(sp/must :marker) (sp/must :color) keyword?] 177 | #(factor (d/column dataset %))) 178 | 179 | (sp/transform 180 | (sp/multi-path 181 | [(sp/must :x) fn?] 182 | [(sp/must :y) fn?] 183 | [(sp/must :z) fn?] 184 | [(sp/must :marker) (sp/must :size) fn?] 185 | [(sp/must :marker) (sp/must :color) fn?]) 186 | #(% dataset)))) 187 | 188 | (defn trans-gp-params 189 | "Transform grouped params" 190 | [params ks] 191 | (let [gp-colors (or (:gp-colors params) (scale-color ks)) 192 | gp-names (or (:gp-names params) ks) 193 | gp-symbols (or (:gp-symbols params) (range (count ks))) 194 | gp-texts (or (:gp-texts params) ks)] 195 | (for [i (range (count ks))] 196 | (-> params 197 | (dissoc :gp-colors :gp-names :gp-texts) 198 | (update :name #(or % (nth gp-names i))) 199 | (update :text #(or % (nth gp-texts i))) 200 | (update-in [:marker :color] #(or % (nth gp-colors i))) 201 | (update-in [:marker :symbol] #(or % (nth gp-symbols i))) 202 | (#(mapcat identity %)))))) 203 | 204 | (defn get-default-xy 205 | "Get default x and y from dataset. 206 | 207 | If the columns of dataset contains both :x and :y, 208 | then the coresponding columns will be selected. 209 | Else the first two columns will be selected 210 | (only if the column size of the dataset is >= 2). 211 | 212 | If the dataset contains only one column 213 | :y will be the only column and 214 | :x will be a vector of indexes." 215 | [dataset] 216 | (cond 217 | (s/subset? #{:x :y} (set (d/column-names dataset))) 218 | (d/to-map (d/select-columns dataset [:x :y])) 219 | (<= 2 (count (d/column-names dataset))) 220 | {:x (d/column dataset 0) :y (d/column dataset 1)} 221 | (= 1 (count (d/column-names dataset))) 222 | {:x (vec (range (count dataset))) :y (d/column dataset 0)} 223 | :else {})) 224 | 225 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 226 | (defn plot-seq 227 | "Apply a sequence add-fn commands to a plotly object. 228 | cmds is a sequence of functions that take a plotly object as param." 229 | [p cmds] 230 | (reduce #(%2 %1) p cmds)) 231 | 232 | (def traces-3d #{"scatter3d" "surface" "mesh3d"}) 233 | 234 | (defn subplot 235 | "Bind each trace to a xaxis and a yaxis. 236 | This function supports only limited params currently. 237 | 238 | Example: 239 | traces: [trace1 trace2 trace3 trace4 trace5 trace6] 240 | nrow: 2 241 | ncol: 3 242 | x-bind(sharex true): 243 | [[1 2] 244 | [1 2] 245 | [1 2]] 246 | y-bind(sharey true): 247 | [[1 1] 248 | [2 2] 249 | [3 3]] 250 | x-bind(sharex false): 251 | [[1 2] 252 | [3 4] 253 | [5 6]] 254 | y-bind(sharey false): 255 | [[1 4] 256 | [2 5] 257 | [3 6]] 258 | 259 | x-domains: ([0 0.4][0.6 1][0 0.4][0.6 1][0 0.4][0.6 1]) 260 | y-domains: ([0.7333 1][0.3666 0.6333][0 0.2666][0.7333 1][0.3666 0.6333][0 0.2666]) 261 | TODO: Check nrow ncol zero?" 262 | [p & {:keys [nrow ncol sharex sharey reversey titles]}] 263 | (let [n (count (:traces p)) 264 | [ncol nrow] (cond 265 | (and ncol nrow) [ncol nrow] 266 | ncol [ncol (inc (quot (dec n) ncol))] 267 | nrow [(inc (quot (dec n) nrow)) nrow] 268 | :else [1 n]) 269 | x-bind (if sharex 270 | (m/array (repeat nrow (range 1 (inc ncol)))) 271 | (m/reshape (range 1 (inc (* ncol nrow))) [nrow ncol])) 272 | y-bind (if sharey 273 | (m/transpose (m/array (repeat ncol (range 1 (inc nrow))))) 274 | (m/transpose (m/reshape (range 1 (inc (* ncol nrow))) [ncol nrow]))) 275 | get-domains (fn [n] (let [gap (if (= 1 n) 0 (/ 0.1 (dec n))) 276 | span (if (= 1 n) 1 (/ 0.9 n))] 277 | (map #(vector (* % (+ span gap)) 278 | (+ (* gap %) (* span (inc %)))) 279 | (range n)))) 280 | x-domains (apply concat (repeat nrow (get-domains ncol))) 281 | y-domains (apply concat (repeat ncol (if reversey 282 | (get-domains nrow) 283 | (reverse (get-domains nrow))))) 284 | 285 | is-trace-3d (map #(traces-3d (:type %)) (:traces p)) 286 | scenes (map-indexed (fn [i is-3d] 287 | (when is-3d 288 | (let [x (nth (m/eseq x-bind) i) 289 | y (nth (m/eseq y-bind) i)] 290 | {(keyword (str "scene" (inc i))) 291 | {:domain {:x (nth x-domains (dec x)) 292 | :y (nth y-domains (dec y))}}}))) 293 | is-trace-3d) 294 | x-axis (map-indexed (fn [i is-3d] 295 | (when-not is-3d 296 | (let [x (nth (m/eseq x-bind) i) 297 | y (nth (m/eseq y-bind) i)] 298 | {(keyword (str "xaxis" x)) 299 | {:domain (nth x-domains (dec x)) 300 | :anchor (str "y" y)}}))) 301 | is-trace-3d) 302 | y-axis (map-indexed (fn [i is-3d] 303 | (when-not is-3d 304 | (let [x (nth (m/eseq x-bind) i) 305 | y (nth (m/eseq y-bind) i)] 306 | {(keyword (str "yaxis" y)) 307 | {:domain (nth y-domains (dec y)) 308 | :anchor (str "x" x)}}))) 309 | is-trace-3d)] 310 | (-> p 311 | (update :traces #(map (fn [t i x y] 312 | (if (traces-3d (:type t)) 313 | (assoc t :scene (str "scene" i)) 314 | (-> t 315 | (assoc :xaxis (str "x" x)) 316 | (assoc :yaxis (str "y" y))))) 317 | % (range 1 (inc (count %))) (m/eseq x-bind) (m/eseq y-bind))) 318 | (update :layout #(apply merge % scenes)) 319 | (update :layout #(apply merge % x-axis)) 320 | ;; The reverse is needed here, which makes y-axis align to left if sharey is set. 321 | (update :layout #(apply merge % (reverse y-axis)))))) 322 | 323 | (defn add-annotations 324 | "This function will update the layout of a plotly object." 325 | [p ann] 326 | (cond 327 | (map? ann) ;; only one annotation 328 | (update-layout p :annotations [ann]) 329 | (sequential? ann) 330 | (update-layout p :annotations ann) 331 | :else 332 | p)) 333 | 334 | (defn add-fn 335 | "Add general trace to a plotly object. 336 | This function supports grouped dataset. 337 | " 338 | [{:keys [ds traces] :as p} 339 | add-type 340 | params] 341 | (cond 342 | (group? ds) 343 | (let [gp-ds (vec (vals ds)) 344 | gp-params (vec (trans-gp-params params (keys ds))) 345 | p-new (reduce #(apply add-fn 346 | (assoc %1 :ds (nth gp-ds %2)) 347 | (nth gp-params %2)) 348 | p 349 | (range (count ds)))] 350 | ;; recover dataset 351 | (set-dataset p-new ds)) 352 | 353 | (nil? ds) 354 | (update p :traces #(conj % (merge {:type add-type} params))) 355 | 356 | :else 357 | (let [params (trans-params params ds) 358 | data (get-default-xy ds) 359 | trace (merge {:type add-type} data params)] 360 | (update p :traces #(conj % trace))))) 361 | 362 | (def add-scatter (fn [p & {:as params}] (add-fn p "scatter" params))) 363 | (def add-bar (fn [p & {:as params}] (add-fn p "bar" params))) 364 | (def add-histogram (fn [p & {:as params}] (add-fn p "histogram" params))) 365 | (def add-histogram-2d (fn [p & {:as params}] (add-fn p "histogram2d" params))) 366 | 367 | (defn add-fn-3d 368 | "Add general 3d trace." 369 | [{:keys [ds traces] :as p} 370 | add-type 371 | params] 372 | (let [params (trans-params params ds) 373 | trace (merge {:type add-type} params)] 374 | (update p :traces #(conj % trace)))) 375 | 376 | (def add-scatter-3d (fn [p & {:as params}] (add-fn-3d p "scatter3d" params))) 377 | (def add-surface (fn [p & {:as params}] (add-fn-3d p "surface" params))) 378 | (def add-mesh3d (fn [p & {:as params}] (add-fn-3d p "mesh3d" params))) 379 | (def add-contour (fn [p & {:as params}] (add-fn-3d p "contour" params))) 380 | (def add-heatmap (fn [p & {:as params}] (add-fn-3d p "heatmap" params))) 381 | 382 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 383 | (def cred-path (str (System/getProperty "user.home") (File/separator) ".plotly" (File/separator) ".credentials")) 384 | 385 | (defn set-credentials 386 | "Write the username and api-key into local file ~/.plotly/.credentials 387 | This function only needs to be called once." 388 | [username api-key] 389 | (let [cred {:username username :api-key api-key}] 390 | (when-not (.exists (as-file cred-path)) 391 | (make-parents cred-path)) 392 | (spit cred-path (json/write-str cred)))) 393 | 394 | (defn get-credentials 395 | "Read username and api-key from local file. 396 | The default credential file path is ~/.plotly/.credentials" 397 | [] 398 | (json/read-str (slurp cred-path) :key-fn keyword)) 399 | ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 400 | (defn save-html 401 | "Save to local html." 402 | ([p filename] (save-html p filename nil)) 403 | ([p filename is-open] 404 | (do (make-parents filename) 405 | (-> p 406 | to-string 407 | make-div 408 | wrap-div 409 | (#(spit filename %))) 410 | (when is-open (browse-url filename))))) 411 | 412 | (defn plot 413 | "Publish to server. 414 | Return the generated url of figure. 415 | TODO: Add support for api of V2" 416 | [{:keys [traces layout]} 417 | filename 418 | & {:keys [fileopt world-readable] :as params}] 419 | (let [{:keys [username api-key]} (get-credentials) 420 | data (merge {:origin "plot" 421 | :platform "clojure" 422 | :args (json/write-str traces) 423 | :kwargs (json/write-str {:filename filename 424 | :fileopt (or fileopt "new") 425 | :layout layout 426 | :world_readable (if (nil? world-readable) true world-readable)})} 427 | {:un username :key api-key}) 428 | resp @(http/post "https://plot.ly/clientresp" {:form-params data}) 429 | res (json/read-str (:body resp) :key-fn keyword)] 430 | (let [url (:url res)] 431 | (if (empty? url) 432 | (:error res) 433 | url)))) 434 | 435 | ;;;;;;;;;;;;;;;;;;;;;;;;;;===== Gorilla REPL rendering =====;;;;;;;;;;;;;;;;;;;;;;;; 436 | (defrecord PlotlyView [contents]) 437 | 438 | (defn plotly-view [p] (PlotlyView. p)) 439 | 440 | (extend-type PlotlyView 441 | render/Renderable 442 | (render [self] {:type :html :content (:contents self) :value "pr'ed value"})) 443 | 444 | (defn online-init 445 | "Insert the plotly.min.js into gorilla repl" 446 | [] 447 | (plotly-view 448 | (html [:script {:type "text/javascript" 449 | :src "https://cdn.plot.ly/plotly-latest.min.js"}]))) 450 | 451 | (defn offline-init 452 | "Insert the plotly.min.js into gorilla repl" 453 | [] 454 | (plotly-view 455 | (html [:script {:type "text/javascript"} 456 | (slurp (resource "plotly.min.js"))]))) 457 | 458 | (defn iplot 459 | "Inline plot" 460 | [p] 461 | (-> p 462 | to-string 463 | make-div 464 | plotly-view)) 465 | 466 | (defn embed-url 467 | [url & {:keys [width height]}] 468 | (plotly-view 469 | (html [:iframe {:width (or width "800") 470 | :height (or height "600") 471 | :src (str (string/replace-first url "https:" "") 472 | ".embed")}]))) 473 | -------------------------------------------------------------------------------- /src/plotly_clj/scale.clj: -------------------------------------------------------------------------------- 1 | (ns plotly-clj.scale 2 | (:require [clojure.core.matrix :as m] 3 | [clojure.core.matrix.dataset :as d])) 4 | 5 | (defn scale-size 6 | "TODO: 7 | Check every? number? 8 | 0 <= min-size <= max-size" 9 | ([xs] 10 | (scale-size xs 10 50)) 11 | ([xs min-size max-size] 12 | (let [min-x (m/emin xs) 13 | max-x (m/emax xs)] 14 | (if (= max-x min-x) 15 | (m/add (m/zero-array (m/shape xs)) min-size) 16 | (m/add min-size 17 | (m/mul (- max-size min-size) 18 | (m/div (m/add xs (- min-x)) 19 | (- max-x min-x)))))))) 20 | 21 | (defn factor 22 | [xs] 23 | (if (every? number? xs) 24 | xs 25 | (let [val-idx (into {} (map-indexed #(vector %2 %1) (set xs)))] 26 | (mapv val-idx xs)))) 27 | 28 | (defn scale-color 29 | [xs] 30 | (if (every? number? xs) 31 | xs 32 | (let [val-idx (into {} (map-indexed #(vector %2 %1) (set xs))) 33 | interval (float (/ 360 (count val-idx)))] 34 | (map #(str "hsl(" (* (val-idx %) interval) ",0.5,0.5)") xs)))) 35 | -------------------------------------------------------------------------------- /test/plotly_clj/core_test.clj: -------------------------------------------------------------------------------- 1 | (ns plotly-clj.core-test 2 | (:require [clojure.test :refer :all] 3 | [plotly-clj.core :refer :all])) 4 | 5 | (deftest a-test 6 | (testing "FIXME, I fail." 7 | (is (= 0 1)))) 8 | --------------------------------------------------------------------------------