diff --git a/.envrc b/.envrc new file mode 100755 index 0000000..54aba90 --- /dev/null +++ b/.envrc @@ -0,0 +1,5 @@ +#! /bin/env bash +echo "Either start nREPL server directly:" +echo "'clj -M:nrepl'" +echo "or connect to it inside nvim(vim-jack-in): " +echo "':Clj'" diff --git a/deps.edn b/deps.edn index 7d0800d..e885677 100644 --- a/deps.edn +++ b/deps.edn @@ -1,7 +1,13 @@ {:paths ["dev" "notebooks" "resources"] + :mvn/repos {"central" {:url "https://repo1.maven.org/maven2/"} + "clojars" {:url "https://clojars.org/repo"}} :deps {io.github.nextjournal/clerk {:git/sha "6c710b552f7b3591e34c93ea635c435a7e35231e"} io.github.nextjournal/clerk-slideshow {:git/sha "5fe197ca0c8f28128379849dc677cd718be71524"} + ;; python integration + ; clj-python/libpython-clj {:mvn/version "1.37"} + org.clojure/data.csv {:mvn/version "0.1.4"} + ;; /python integration ;; keep 1.10 until `kixi/stats` and `sicmutils` fix warnings org.clojure/clojure {:mvn/version "1.10.3"} @@ -22,6 +28,7 @@ ;; semantic web goodies and box/arrow graphs io.github.jackrusher/mundaneum {:git/sha "d2c934a12388d88ddb3e53fef92ec2eef97d6140"} arrowic/arrowic {:mvn/version "0.1.1"} + clj-python/libpython-clj {:mvn/version "2.024"} ;; OSC server com.illposed.osc/javaosc-core {:mvn/version "0.8" :exclusions [org.slf4j/slf4j-api org.slf4j/slf4j-log4j12]} @@ -45,4 +52,12 @@ "notebooks/sicmutils.clj" "notebooks/rule_30.clj" "notebooks/zipper_with_scars.clj"]} - :main-opts ["-m" "babashka.cli.exec"]}}} + :main-opts ["-m" "babashka.cli.exec"]} + :test {:extra-paths ["test"] + :extra-deps {org.clojure/test.check {:mvn/version "0.10.0"}}} + :runner + {:extra-deps {com.cognitect/test-runner + {:git/url "https://github.com/cognitect-labs/test-runner" + :sha "f7ef16dc3b8332b0d77bc0274578ad5270fbfedd"}} + :main-opts ["-m" "cognitect.test-runner" + "-d" "test"]}}} diff --git a/dev/user.clj b/dev/user.clj index 1213ea1..19cce10 100644 --- a/dev/user.clj +++ b/dev/user.clj @@ -16,17 +16,25 @@ ;; or call `clerk/show!` explicitly (clerk/show! "notebooks/introduction.clj") + (clerk/show! "notebooks/controls.clj") (clerk/show! "notebooks/data_science.clj") (clerk/show! "notebooks/sicmutils.clj") + (clerk/show! "notebooks/dictionary.clj") + (clerk/show! "notebooks/elements.clj") + (clerk/show! "notebooks/git.clj") + (clerk/show! "notebooks/logo.clj") (clerk/show! "notebooks/rule_30.clj") (clerk/show! "notebooks/semantic.clj") (clerk/show! "notebooks/images.clj") + (clerk/show! "notebooks/zipper_with_scars.clj") + (clerk/show! "notebooks/numpy_plot.clj") + (clerk/show! "notebooks/python.clj") (clerk/show! "index.md") ;; TODO If you would like more details about how Clerk works, here's a ;; notebook with some implementation details. - ;; (clerk/show! "notebooks/how_clerk_works.clj") + (clerk/show! "notebooks/how_clerk_works.clj") ;; produce a static app (clerk/build-static-app! {:paths (mapv #(str "notebooks/" % ".clj") diff --git a/notebooks/gigasquid/numpy_plot.clj b/notebooks/gigasquid/numpy_plot.clj new file mode 100644 index 0000000..4f5859f --- /dev/null +++ b/notebooks/gigasquid/numpy_plot.clj @@ -0,0 +1,110 @@ +; from https://raw.githubusercontent.com/gigasquid/libpython-clj-examples/master/src/gigasquid/numpy_plot.clj +(ns gigasquid.numpy-plot + {:nextjournal.clerk/visibility {:code :fold} + :nextjournal.clerk/no-cache true} + (:require + [clojure.zip :as zip] + [arrowic.core :as a] + [nextjournal.clerk :as clerk] + [nextjournal.clerk.viewer :as v] + [gigasquid.plot :as plot] + [clojure.string :as str] + [libpython-clj2.python + :refer [as-python as-jvm + ->python ->jvm + get-attr call-attr call-attr-kw + get-item initialize! + run-simple-string + add-module module-dict + import-module + python-type] + :as py])) + +(py/initialize!) +; Clojure +(require '[libpython-clj2.require :refer [require-python]]) +(require-python '[matplotlib.pyplot :as pyplot]) +(require-python '[numpy :as np]) +(require-python '[pandas :as pd]) +(require-python '[numpy :as numpy]) + +(def dates (pd/date_range "1/1/2000" :periods 8)) +(def table (pd/DataFrame (call-attr np/random :randn 8 4) :index dates :columns ["A" "B" "C" "D"])) +(def row-date (pd/date_range :start "2000-01-01" :end "2000-01-01")) +(get-item (get-attr table :loc) row-date) +;;;; you will need matplotlib, numpy, and pillow installed to run this in python3 + +;;; This uses a macro from printing in the plot namespace that uses the shell "open" command +;;; to show a saved image from pyplot. If you don't have a mac you will need to modify that +;;; to whatever shell command you have. + +(comment + (def x (numpy/linspace 0 2 50)) + + (plot/with-show (matplotlib.pyplot/plot [[1 2 3 4 5] [1 2 3 4 10]] :label "linear")) + + (plot/with-show + (pyplot/plot [x x] :label "linear") + (pyplot/plot [x (py. x "__pow__" 2)] :label "quadratic") + (pyplot/plot [x (py. x "__pow__" 3)] :label "cubic") + (pyplot/xlabel "x label") + (pyplot/ylabel "y label") + (pyplot/title "Simple Plot")) + +;;; numpy printing tutorial http://cs231n.github.io/python-numpy-tutorial/#matplotlib-plotting + (let [x (numpy/arange 0 (* 3 numpy/pi) 0.1) + y (numpy/sin x)] + (plot/with-show + (pyplot/plot x y))) + + (let [x (numpy/arange 0 (* 3 numpy/pi) 0.1) + y-sin (numpy/sin x) + y-cos (numpy/cos x)] + (plot/with-show + (pyplot/plot x y-sin) + (pyplot/plot x y-cos) + (pyplot/xlabel "x axis label") + (pyplot/ylabel "y axis label") + (pyplot/title "Sine and Cosine") + (pyplot/legend ["Sine" "Cosine"]))) + + ;;;; Subplots + + (let [x (numpy/arange 0 (* 3 numpy/pi) 0.1) + y-sin (numpy/sin x) + y-cos (numpy/cos x)] + (plot/with-show + ;;; set up a subplot gird that has a height of 2 and width of 1 + ;; and set the first such subplot as active + (pyplot/subplot 2 1 1) + (pyplot/plot x y-sin) + (pyplot/title "Sine") + + ;;; set the second subplot as active and make the second plot + (pyplot/subplot 2 1 2) + (pyplot/plot x y-cos) + (pyplot/title "Cosine"))) + +;;;;; Images + + (let [img (pyplot/imread "resources/cat.jpg") + img-tinted (numpy/multiply img [1 0.95 0.9])] + (plot/with-show + (pyplot/subplot 1 2 1) + (pyplot/imshow img) + (pyplot/subplot 1 2 2) + (pyplot/imshow (numpy/uint8 img-tinted)))) + +;;;;; pie chart +;;;; from https://org/3.1.1/gallery/pie_and_polar_charts/pie_features.html + + (let [labels ["Frogs" "Hogs" "Dogs" "Logs"] + sizes [15 30 45 10] + explode [0 0.1 0 0] ; only explode the 2nd slice (Hogs) + ] + (plot/with-show + (let [[fig1 ax1] (pyplot/subplots)] + (py. ax1 "pie" sizes :explode explode :labels labels :autopct "%1.1f%%" + :shadow true :startangle 90) + (py. ax1 "axis" "equal")) ;equal aspec ration ensures that pie is drawn as circle + ))) diff --git a/notebooks/gigasquid/plot.clj b/notebooks/gigasquid/plot.clj new file mode 100644 index 0000000..940c8cb --- /dev/null +++ b/notebooks/gigasquid/plot.clj @@ -0,0 +1,79 @@ +(ns gigasquid.plot + (:require + ; [libpython-clj.python :as py :refer [py. py.. py.-]] + [libpython-clj2.python + :refer [as-python as-jvm + ->python ->jvm + get-attr call-attr call-attr-kw + get-item initialize! + run-simple-string + add-module module-dict + import-module + python-type + py. py.. py.-] + :as py] + [libpython-clj2.require :refer [require-python]] + [gigasquid.utils :refer [display-image create-tmp-file]] + [clojure.java.shell :as sh])) + +;;; This uses the headless version of matplotlib to generate a graph then copy it to the JVM +;;; where we can then print it + +;;; have to set the headless mode before requiring pyplot +(def mplt (py/import-module "matplotlib")) +(py. mplt "use" "Agg") + +(require-python '[matplotlib.pyplot :as pyplot]) +(require-python 'matplotlib.backends.backend_agg) +(require-python 'numpy) + +(defmacro with-show + "Takes forms with mathplotlib.pyplot to then show locally" + [& body] + `(let [_# (pyplot/clf) + fig# (pyplot/figure) + agg-canvas# (matplotlib.backends.backend_agg/FigureCanvasAgg fig#) + temp-file# (create-tmp-file "tmp-image" ".png") + temp-image# (.getAbsolutePath temp-file#)] + ~(cons 'do body) + (py. agg-canvas# "draw") + (pyplot/savefig temp-image#) + (display-image temp-image#) + (.deleteOnExit temp-file#))) + +;;;; If you run into mem problems with temporary files try this one + +(defmacro with-show-one + "Takes forms with mathplotlib.pyplot to then show locally" + [& body] + `(let [_# (pyplot/clf) + fig# (pyplot/figure) + agg-canvas# (matplotlib.backends.backend_agg/FigureCanvasAgg fig#)] + ~(cons 'do body) + (py. agg-canvas# "draw") + (pyplot/savefig "temp.png") + (sh/sh "open" "temp.png"))) + +(defmacro with-save + "Takes forms with mathplotlib.pyplot to then show locally" + [fname & body] + `(let [_# (pyplot/clf) + fig# (pyplot/figure) + agg-canvas# (matplotlib.backends.backend_agg/FigureCanvasAgg fig#)] + ~(cons 'do body) + (py. agg-canvas# "draw") + (pyplot/savefig ~fname))) + +(comment + + (def x (numpy/linspace 0 2 100)) + + (with-show + (pyplot/plot [x x] :label "linear") + (pyplot/plot [x (py. x "__pow__" 2)] :label "quadratic") + (pyplot/plot [x (py. x "__pow__" 3)] :label "cubic") + (pyplot/xlabel "x label") + (pyplot/ylabel "y label") + (pyplot/title "Simple Plot")) + + (with-show (pyplot/plot [[1 2 3 4 5] [1 2 3 4 10]] :label "linear"))) diff --git a/notebooks/gigasquid/utils.clj b/notebooks/gigasquid/utils.clj new file mode 100644 index 0000000..4f638bb --- /dev/null +++ b/notebooks/gigasquid/utils.clj @@ -0,0 +1,36 @@ +(ns gigasquid.utils + (:require + [clojure.string :as string] + [clojure.java.shell :as sh] + [clojure.pprint :refer [pprint]]) + (:import [java.io File])) + +(def is-linux? + (= "linux" + (-> "os.name" + System/getProperty + string/lower-case))) + +(def is-mac? + (-> "os.name" + System/getProperty + string/lower-case + (string/starts-with? "mac"))) + +(defn display-image + "Display image on OSX or on Linux based system" + [image-file] + (cond + is-mac? + (sh/sh "open" image-file) + + is-linux? + (sh/sh "display" image-file))) + +(defn create-tmp-file + "Return full path of temporary file. + + Example: + (create-tmp-file \"tmp-image\" \".png\") " + [prefix ext] + (File/createTempFile prefix ext)) diff --git a/utils.clj b/utils.clj new file mode 100644 index 0000000..4f638bb --- /dev/null +++ b/utils.clj @@ -0,0 +1,36 @@ +(ns gigasquid.utils + (:require + [clojure.string :as string] + [clojure.java.shell :as sh] + [clojure.pprint :refer [pprint]]) + (:import [java.io File])) + +(def is-linux? + (= "linux" + (-> "os.name" + System/getProperty + string/lower-case))) + +(def is-mac? + (-> "os.name" + System/getProperty + string/lower-case + (string/starts-with? "mac"))) + +(defn display-image + "Display image on OSX or on Linux based system" + [image-file] + (cond + is-mac? + (sh/sh "open" image-file) + + is-linux? + (sh/sh "display" image-file))) + +(defn create-tmp-file + "Return full path of temporary file. + + Example: + (create-tmp-file \"tmp-image\" \".png\") " + [prefix ext] + (File/createTempFile prefix ext))