From 60a074ee8720c3252f1364cf8ea7a3fdac3e6c87 Mon Sep 17 00:00:00 2001 From: Sam Ritchie Date: Thu, 8 Jun 2023 05:54:11 -0600 Subject: [PATCH] Update to Mafs 0.16.0, document components (#17) --- CHANGELOG.md | 26 ++ build.clj | 2 +- deps.edn | 10 +- dev/mafs/notebook.clj | 104 +++-- dev/user.clj | 4 +- package-lock.json | 54 ++- package.json | 2 +- .../org.mentat/mafs.cljs/config.edn | 4 - .../mafs.cljs/hooks/mafs/macros.clj_kondo | 21 - resources/mafs/clerk/dev/user.tmpl | 4 +- src/deps.cljs | 2 +- src/mafs/coordinates.cljs | 70 +++- src/mafs/core.cljs | 392 +++++++++++++----- src/mafs/debug.cljs | 28 +- src/mafs/line.cljs | 147 +++++-- src/mafs/macros.cljc | 19 - src/mafs/{matrix.cljs => matrix.cljc} | 0 src/mafs/plot.cljs | 283 ++++++++++--- src/mafs/{vec.cljs => vec.cljc} | 0 19 files changed, 852 insertions(+), 320 deletions(-) delete mode 100644 resources/clj-kondo.exports/org.mentat/mafs.cljs/config.edn delete mode 100644 resources/clj-kondo.exports/org.mentat/mafs.cljs/hooks/mafs/macros.clj_kondo delete mode 100644 src/mafs/macros.cljc rename src/mafs/{matrix.cljs => matrix.cljc} (100%) rename src/mafs/{vec.cljs => vec.cljc} (100%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ba7c96..aa0164f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ ## [unreleased] +## [0.3.0] + +- #17: + + - Upgrades Mafs to 0.17.0 (see the [Mafs CHANGELOG entry + here](https://github.com/stevenpetryk/mafs/blob/main/CHANGELOG.md#0170)) + + - Adds `mafs.core/Polyline` and `mafs.plot/Inequality`. + + - `mafs.core/Mafs` now supports an `:on-click` handler. + + - `mafs.vector` and `mafs.matrix` are now `cljc` files, so that projects like + [Emmy-Viewers](https://github.com/mentat-collective/emmy-viewers) can + construct Mafs-style vectors and matrices on the JVM. + + - Removes the former `defcomponent` macro in favor of explicit `defn`s. This + makes the project compatible with [Portal](https://github.com/djblue/portal) + and other libraries that need to load cljs files via + [SCI](https://github.com/babashka/sci). + + - Upgrades our `reagent` dependency to 1.2.0, and upgrades Clerk for the docs + site build to the latest `main`. + + - All components are now well-documented! + + ## [0.2.1] - #14: diff --git a/build.clj b/build.clj index 1637b90..ea2dc04 100644 --- a/build.clj +++ b/build.clj @@ -17,7 +17,7 @@ ;; ## Variables (def lib 'org.mentat/mafs.cljs) -(def version "0.2.1") +(def version "0.3.0") (def pom-deps {'org.babashka/sci {:mvn/version "0.6.37" diff --git a/deps.edn b/deps.edn index d1f4019..af58072 100644 --- a/deps.edn +++ b/deps.edn @@ -1,5 +1,5 @@ {:paths ["src" "resources"] - :deps {reagent/reagent {:mvn/version "1.1.1"}} + :deps {reagent/reagent {:mvn/version "1.2.0"}} :aliases {:nextjournal/clerk @@ -7,17 +7,17 @@ :extra-deps {org.clojure/clojure {:mvn/version "1.11.1"} org.clojure/clojurescript {:mvn/version "1.11.60"} - org.mentat/clerk-utils {:mvn/version "0.4.1"} + org.mentat/clerk-utils {:mvn/version "0.5.1"} io.github.nextjournal/clerk - {:git/sha "fad499407d979916d21b33cc7e46e73f7a485e37"} + {:git/sha "1f6c5331418aaf9c5a4335fc2e6e95f07dc3af6b"} io.github.nextjournal/clerk.render {:git/url "https://github.com/nextjournal/clerk" - :git/sha "fad499407d979916d21b33cc7e46e73f7a485e37" + :git/sha "1f6c5331418aaf9c5a4335fc2e6e95f07dc3af6b" :deps/root "render"}} :exec-fn user/build!} :build - {:deps {io.github.clojure/tools.build {:git/tag "v0.8.2" :git/sha "ba1a2bf"} + {:deps {io.github.clojure/tools.build {:git/tag "v0.9.4" :git/sha "76b78fe"} slipset/deps-deploy {:mvn/version "0.2.0"}} :ns-default build}}} diff --git a/dev/mafs/notebook.clj b/dev/mafs/notebook.clj index 25631ce..deb057e 100644 --- a/dev/mafs/notebook.clj +++ b/dev/mafs/notebook.clj @@ -1,8 +1,7 @@ -^#:nextjournal.clerk -{:toc true - :no-cache true - :visibility :hide-ns} +^{:nextjournal.clerk/visibility {:code :hide}} (ns mafs.notebook + #:nextjournal.clerk + {:toc true :no-cache true} (:require [mentat.clerk-utils.docs :as docs] [mentat.clerk-utils.show :refer [show-sci]] [nextjournal.clerk :as clerk])) @@ -10,8 +9,8 @@ ^{::clerk/visibility {:code :hide :result :hide}} (clerk/eval-cljs ;; These aliases only apply inside this namespace. - '(require '[mafs.core :as mafs]) - '(require '[reagent.core :as reagent])) + '(do (require '[mafs.core :as mafs]) + (require '[reagent.core :as reagent]))) ;; # Mafs.cljs ;; @@ -58,9 +57,8 @@ :xy (let [[ax ay] @!point] (fn [[x y]] - (js/Array. - (- (- y ay) (- x ax)) - (- (- (- x ax)) (- y ay))))) + [(- (- y ay) (- x ax)) + (- (- (- x ax)) (- y ay))])) :xy-opacity (fn [[x y]] (/ (+ (Math/abs x) (Math/abs y)) @@ -108,9 +106,9 @@ ;; ```clj ;; (mentat.clerk-utils.css/set-css! -;; "https://unpkg.com/mafs@0.15.2/core.css" +;; "https://unpkg.com/mafs@0.17.0/core.css" ;; "https://unpkg.com/computer-modern@0.1.2/cmu-serif.css" -;; "https://unpkg.com/mafs@0.15.2/font.css") +;; "https://unpkg.com/mafs@0.17.0/font.css") ;; ``` ;; ;; Otherwise find some way to load these CSS files in your project's header. @@ -569,9 +567,26 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} :stroke-style "dashed"}] [mafs/Polygon {:points [@!c a b] - :color (:blue mafs/Theme)}] + :color :blue}] [mafs/MovablePoint {:atom !c}]])) +;; A Polyline is a polygon that doesn't connect its first and last points. + +^{::clerk/width :wide} +(show-sci + (reagent/with-let + [a [-2 -2] + !bc (reagent/atom {:b [-1 1] :c [1 -1]}) + d [2 2]] + [mafs/Mafs + [mafs.coordinates/Cartesian] + [mafs/Polyline + (let [{:keys [b c]} @!bc] + {:points [a b c d] + :color :blue})] + [mafs/MovablePoint {:atom !bc :path :b}] + [mafs/MovablePoint {:atom !bc :path :c}]])) + ;; ### Circle ;; Circles take a center vector and a radius. @@ -632,14 +647,14 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} :constrain "vertical"}]] [mafs/MovablePoint {:atom !state :path :rotate - :color (:blue mafs/Theme) + :color :blue ;; Constrain this point to only move in a circle :constrain (fn [p] (mafs.vec/with-mag p 3))}]] [mafs/MovablePoint {:atom !state :path :translate - :color (:orange mafs/Theme)}]])) + :color :orange}]])) ;; ### Plot @@ -655,8 +670,40 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} [mafs/Mafs [mafs.coordinates/Cartesian] - [mafs.plot/OfX {:y Math/sin :color (:blue mafs/Theme)}] - [mafs.plot/OfY {:x sigmoid1 :color (:pink mafs/Theme)}]]) + [mafs.plot/OfX {:y Math/sin :color :blue}] + [mafs.plot/OfY {:x sigmoid1 :color :pink}]]) + +;; #### Inequalities of $x$ and $y$ + +;; Inequalities represent the region less than or greater than one or two +;; functions. Mafs allows you to plot the region between two functions, or a +;; function and a constant. The inequality can be a function of $x$ or $y$. + +;; You cannot provide an `:x` and a `:y` prop to `Inequality` - it will throw a +;; runtime exception. Similarly, you cannot pass conflicting inequality +;; operators — like both `"<"` and `"<="`. + +^{::clerk/width :wide} +(show-sci + (reagent/with-let + [!point (reagent/atom [0 -1])] + [mafs/Mafs + [mafs.coordinates/Cartesian] + [mafs.plot/Inequality + {:x (let [[ax ay] @!point] + {:<= (fn [y] + (- (Math/cos (+ y ay)) ax)) + :>(fn [y] + (+ (Math/sin (- y ay)) ax))}) + :color :blue}] + [mafs.plot/Inequality + {:y (let [[ax ay] @!point] + {:<= (fn [x] + (- (Math/cos (+ x ax)) ay)) + :>(fn [x] + (+ (Math/sin (- x ax)) ay))}) + :color :pink}] + [mafs/MovablePoint {:atom !point}]])) ;; #### Parametric functions @@ -701,9 +748,8 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} :xy (let [[ax ay] @!point] (fn [[x y]] - (js/Array. - (- (- y ay) (- x ax)) - (- (- (- x ax)) (- y ay))))) + [(- (- y ay) (- x ax)) + (- (- (- x ax)) (- y ay))])) :xy-opacity (fn [[x y]] (/ (+ (Math/abs x) (Math/abs y)) @@ -825,10 +871,10 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} [HelloBox]] [mafs/MovablePoint {:atom !state :path :scale - :color (:blue mafs/Theme)}]] + :color :blue}]] [mafs/MovablePoint {:atom !state :path :rotate - :color (:green mafs/Theme) + :color :green :constrain mafs.vec/normalize}]] [mafs/MovablePoint {:atom !state :path :translate}]])) @@ -958,12 +1004,10 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} ;; Beyond constraining horizontally or vertically, points can also be ;; constrained to arbitrary paths. This is done by passing a function to -;; `:constrain`. The function is expected to take a point `[x y], which is where -;; the user is trying to move to, and to return a new point, `[x' y']`, which is -;; where the point should _actually_ go. +;; `:constrain`. The function is expected to take a point `[x y]`, which is +;; where the user is trying to move to, and to return a new point, `[x' y']`, +;; which is where the point should _actually_ go. ;; -;; > Note that for now you'll need to return a `js/Array` instead of a vector. - ;; To demonstrate this, imagine constraining a point to "snap" to the nearest ;; whole number point. We take where the user is trying to move to, and round it ;; to the nearest whole number. @@ -971,9 +1015,8 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} ;; ```clj ;; [mafs/MovablePoint ;; {:constrain (fn [[x y]] -;; (js/Array. -;; (Math/round x) -;; (Math/round y)))}] +;; [(Math/round x) +;; (Math/round y)])}] ;; ``` ;; Another common use case is to constrain motion to be circular — @@ -982,8 +1025,7 @@ clojure -Sdeps '{:deps {io.github.mentat-collective/mafs.cljs {:git/sha \"%s\"}} ;; ```clj ;; [mafs/MovablePoint ;; {:constrain (fn [point] -;; (clj->js -;; (mafs.vec/with-mag point 1)))}] +;; (mafs.vec/with-mag point 1))}] ;; ``` ;; You can also constrain a point to follow a straight line by setting constrain diff --git a/dev/user.clj b/dev/user.clj index c9acd02..0e3d338 100644 --- a/dev/user.clj +++ b/dev/user.clj @@ -4,8 +4,8 @@ (css/set-css! "https://unpkg.com/computer-modern@0.1.2/cmu-serif.css" - "https://unpkg.com/mafs@0.15.2/core.css" - "https://unpkg.com/mafs@0.15.2/font.css") + "https://unpkg.com/mafs@0.17.0/core.css" + "https://unpkg.com/mafs@0.17.0/font.css") (def index "dev/mafs/notebook.clj") diff --git a/package-lock.json b/package-lock.json index 69ba0b4..e48e70c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "d3-require": "1.3.0", "framer-motion": "6.5.1", "katex": "0.12.0", - "mafs": "0.15.2", + "mafs": "0.17.0", "markdown-it": "12.3.2", "markdown-it-block-image": "0.0.3", "markdown-it-footnote": "3.0.3", @@ -1120,14 +1120,15 @@ } }, "node_modules/mafs": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/mafs/-/mafs-0.15.2.tgz", - "integrity": "sha512-NPLfponLPlfez2uk7O38lRQLSRmtvctF2WpsGs1UU4VxoZY5frRHW06qk5L1nx/5Q2H9T62X4G0qNE7FBEVSBg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/mafs/-/mafs-0.17.0.tgz", + "integrity": "sha512-iIoYBHpIXoIi7jxWkaBSGCpPf0HBdpU05gKLycbtqdpKudq39Q+xain4fwaecB7Uv69n+IXkkSkEnewdam3xyw==", "dependencies": { "@react-hook/resize-observer": "^1.2.6", "@swc/helpers": "^0.4.14", "@use-gesture/react": "^10.2.23", "computer-modern": "^0.1.2", + "katex": "^0.16.4", "tiny-invariant": "^1.3.1", "use-resize-observer": "^9.0.0" }, @@ -1136,6 +1137,29 @@ "react-dom": ">=16.8" } }, + "node_modules/mafs/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, + "node_modules/mafs/node_modules/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/make-dir": { "version": "3.1.0", "dev": true, @@ -2825,16 +2849,32 @@ } }, "mafs": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/mafs/-/mafs-0.15.2.tgz", - "integrity": "sha512-NPLfponLPlfez2uk7O38lRQLSRmtvctF2WpsGs1UU4VxoZY5frRHW06qk5L1nx/5Q2H9T62X4G0qNE7FBEVSBg==", + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/mafs/-/mafs-0.17.0.tgz", + "integrity": "sha512-iIoYBHpIXoIi7jxWkaBSGCpPf0HBdpU05gKLycbtqdpKudq39Q+xain4fwaecB7Uv69n+IXkkSkEnewdam3xyw==", "requires": { "@react-hook/resize-observer": "^1.2.6", "@swc/helpers": "^0.4.14", "@use-gesture/react": "^10.2.23", "computer-modern": "^0.1.2", + "katex": "^0.16.4", "tiny-invariant": "^1.3.1", "use-resize-observer": "^9.0.0" + }, + "dependencies": { + "commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" + }, + "katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-Xk9C6oGKRwJTfqfIbtr0Kes9OSv6IFsuhFGc7tW4urlpMJtuh+7YhzU6YEG9n8gmWKcMAFzkp7nr+r69kV0zrA==", + "requires": { + "commander": "^8.3.0" + } + } } }, "make-dir": { diff --git a/package.json b/package.json index f3f6886..95131d5 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "d3-require": "1.3.0", "framer-motion": "6.5.1", "katex": "0.12.0", - "mafs": "0.15.2", + "mafs": "0.17.0", "markdown-it": "12.3.2", "markdown-it-block-image": "0.0.3", "markdown-it-footnote": "3.0.3", diff --git a/resources/clj-kondo.exports/org.mentat/mafs.cljs/config.edn b/resources/clj-kondo.exports/org.mentat/mafs.cljs/config.edn deleted file mode 100644 index d2e1da6..0000000 --- a/resources/clj-kondo.exports/org.mentat/mafs.cljs/config.edn +++ /dev/null @@ -1,4 +0,0 @@ -{:hooks - {:analyze-call - {mafs.macros/defcomponent - hooks.mafs.macros/defcomponent}}} diff --git a/resources/clj-kondo.exports/org.mentat/mafs.cljs/hooks/mafs/macros.clj_kondo b/resources/clj-kondo.exports/org.mentat/mafs.cljs/hooks/mafs/macros.clj_kondo deleted file mode 100644 index 6ce7e7d..0000000 --- a/resources/clj-kondo.exports/org.mentat/mafs.cljs/hooks/mafs/macros.clj_kondo +++ /dev/null @@ -1,21 +0,0 @@ -(ns hooks.mafs.macros - (:require [clj-kondo.hooks-api :as api])) - -(defn defcomponent - "Converts a node representing an invocation of the [[mafs.macros/defcomponent]] - macro into a matching `defn` call." - [{:keys [node]}] - (let [[_ sym & body] (:children node) - [docstring body] (if (api/string-node? (first body)) - [(first body) (rest body)] - [nil body]) - [attrs body] (if (api/map-node? (first body)) - [(first body) (rest body)] - [nil body])] - {:node - (api/list-node - (concat [(api/token-node 'defn) sym] - (when docstring [docstring]) - (when attrs [attrs]) - [(api/vector-node [])] - body))})) diff --git a/resources/mafs/clerk/dev/user.tmpl b/resources/mafs/clerk/dev/user.tmpl index eff856a..9ca3980 100644 --- a/resources/mafs/clerk/dev/user.tmpl +++ b/resources/mafs/clerk/dev/user.tmpl @@ -5,8 +5,8 @@ (css/set-css! "https://unpkg.com/computer-modern@0.1.2/cmu-serif.css" - "https://unpkg.com/mafs@0.15.2/core.css" - "https://unpkg.com/mafs@0.15.2/font.css") + "https://unpkg.com/mafs@0.17.0/core.css" + "https://unpkg.com/mafs@0.17.0/font.css") (def index "notebooks/{{top/file}}/{{main/file}}.clj") diff --git a/src/deps.cljs b/src/deps.cljs index ac511a5..a635940 100644 --- a/src/deps.cljs +++ b/src/deps.cljs @@ -1,2 +1,2 @@ {:npm-deps - {"mafs" "^0.15.2"}} + {"mafs" "^0.17.0"}} diff --git a/src/mafs/coordinates.cljs b/src/mafs/coordinates.cljs index e1c46c6..bdf28da 100644 --- a/src/mafs/coordinates.cljs +++ b/src/mafs/coordinates.cljs @@ -1,18 +1,54 @@ (ns mafs.coordinates - (:require ["mafs" :as m] - [mafs.macros :refer [defcomponent]])) - -(defcomponent Cartesian - " - - `:x-axis` - - `:y-axis` - - `:subdivisions`" - (.-Cartesian m/Coordinates)) - -(defcomponent Polar - " - - `:x-axis` - - `:y-axis` - - `:lines` - - `:subdivisions`" - (.-Polar m/Coordinates)) + (:require ["mafs" :as m])) + +(defn Cartesian + "Takes an (optional) options map `opts` and renders a cartesian coordinate plane + into the Mafs scene. + + Supported options: + + - `:x-axis`: `false` to disable the axis, or an axis options map (see below). + + - `:y-axis`: `false` to disable the axis, or an xis options map (see below). + + - `:subdivisions`: How many subdivisions to draw per line as a default for + both axes, or `false` to draw none. + + Supported axis options: + + - `:axis`: boolean that specifies whether or not to draw the axis line. + + - `:lines`: The spacing between each primary line orthogonal to the axis, or + false to draw none. + + - `:subdivisions`: How many subdivisions to draw per line, or `false` to draw + none. + + - `:labels`: A quoted ClojureScript function that returns a label for each + line, or [[mafs.core/labelPi]]. " + ([] [:> (.-Cartesian m/Coordinates)]) + ([opts] [:> (.-Cartesian m/Coordinates) opts])) + +(defn Polar + "Takes an (optional) options map `opts` and renders a polar coordinate plane + into the Mafs scene. + + Supported options: + + - `:x-axis`: `false` to disable the axis, or an axis options map (see below). + + - `:y-axis`: `false` to disable the axis, or an xis options map (see below). + + - `:lines`: The spacing between each radial line, or false to draw none. + + - `:subdivisions`: How many subdivisions to draw per line as a default for + both axes, or `false` to draw none. + + Supported axis options: + + - `:axis`: boolean that specifies whether or not to draw the axis line. + + - `:labels`: A quoted ClojureScript function that returns a label for each + line, or [[mafs.core/labelPi]]. " + ([] [:> (.-Polar m/Coordinates)]) + ([opts] [:> (.-Polar m/Coordinates) opts])) diff --git a/src/mafs/core.cljs b/src/mafs/core.cljs index c135e66..ffde13f 100644 --- a/src/mafs/core.cljs +++ b/src/mafs/core.cljs @@ -1,118 +1,251 @@ (ns mafs.core "Main components and helper functions for mafs." (:require ["mafs" :as m] - [mafs.macros :refer [defcomponent]] [reagent.core :as reagent])) -;; TODO process the function argument return values to return js arrays vs -;; vectors. - ;; ## Helpers -(def Theme - (js->clj m/Theme :keywordize-keys true)) +(def labelPi + "Labeling function that, suitable for use with the `:labels` options of various + components." + m/labelPi) -(def labelPi m/labelPi) (def useTransformContext m/useTransformContext) (def useStopwatch m/useStopwatch) +(def Theme + "Map of keyword => CSS Mafs theming variable." + (js->clj m/Theme :keywordize-keys true)) + +(defn ^:no-doc split-opts + "Given a sequence of component children that may or may not start with an + options map, returns a pair of + + - `options` (or {} if none existed) + - `children`" + [children] + (if (map? (first children)) + [(first children) (rest children)] + [{} children])) + +(defn ^:no-doc process-color + "Given an options map `m`, attempts to replace the `:color` entry (if present) + with an entry from [[Theme]]. Returns the updated options." + [m] + (if-let [c (:color m)] + (if (keyword? c) + (assoc m :color (Theme c (name c))) + m) + m)) + ;; ## Components -(defcomponent Mafs - " - - `:width` - - `:height` - - `:pan` - - `:viewbox` - - `:preserve-aspect-ratio` - - `:ssr` - " - m/Mafs) - -(defcomponent Point - " - - `:x` - - `:y` - - `:color` - - `:opacity` - - `:svg-circle-props` -" - m/Point) - -(defcomponent Polygon - " - - `:points` - - `:svg-polygon-props` - - `:color` - - `:weight` - - `:fill-opacity` - - `:stroke-opacity` - - `:stroke-style` - " - m/Polygon) - -(defcomponent Ellipse - " - - `:center` - - `:radius` - - `:svg-ellipse-props` - - `:color` - - `:weight` - - `:fill-opacity` - - `:stroke-opacity` - - `:stroke-style` - " - m/Ellipse) +(defn Mafs + "Given an optional map of options and any number of children, renders a Mafs 2D + scene. + + Supported options: + + - `:width`: number (width in pixels) or \"auto\" + + - `:height`: number (height in pixels) + + - `:pan`: If true (default), enable panning with the mouse and keyboard. + + - `:view-box`: If true, enable zooming with the mouse and keyboard. + + can also be a map with keys `:min` (in range `(0, 1]`) and `:max` (in range + `[1, ∞)`). + + - `:preserve-aspect-ratio`: boolean or \"contain\" (default). Whether to + squish the graph to fill the Mafs viewport or to preserve the aspect ratio of + the coordinate space. + + - `:on-click`: `(fn [point, mouse-event] ...)`, called when the view is + clicked on, and passed the point where it was clicked." + [& children] + (into [:> m/Mafs] children)) + +(defn Point + "Takes a map with `:x` and `:y` entries required and renders a dot onto a Mafs + scene at location $(x, y)$. + + Supported options: + + - `:x`: x-coordinate of the point. + + - `:y`: y-coordinate of the point. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:svg-circle-props`: A map of property name => value of any property + accepted + by [SVGCircleElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGCircleElement) + or any parent." + [opts] + [:> m/Point (process-color opts)]) + +(defn Polygon + "Takes a map with `:point` entry required and renders a polygon onto a Mafs + scene bounded by the specified points. + + Supported options: + + - `:points`: a sequence of `[ ]` coordinates. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the polygon's boundary line. + + - `:fill-opacity`: Double in the range [0.0, 0.1] inclusive specifying the + opacity of the interior of the polygon. + + - `:stroke-opacity`: Double in the range [0.0, 0.1] inclusive specifying the + opacity of the boundary of the polygon. + + - `:stroke-style`: \"solid\" or \"dashed\". Defaults to \"solid\". + + - `:svg-polygon-props`: A map of property name => value of any property + accepted + by [SVGPolygonElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPolygonElement) + or any parent." + [opts] + [:> m/Polygon (process-color opts)]) + +(defn Polyline + "Similar to [[polygon]], except the first and last points are not connected. + + See [[polygon]] for supported inputs." + [opts] + [:> m/Polyline (process-color opts)]) + +(defn Ellipse + "Takes a map with `:center` and `:radius` entries required and renders the + specified ellipse onto a Mafs scene. + + Supported options: + + - `:center`: a 2-vector specifying the `[ ]` coordinate of the center. + + - `:radius`: a 2-vector specifying `[ ]` + + - `:angle`: a counter-clockwise angle in radians to rotate the ellipse. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/colors]]. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the ellipse's boundary line. + + - `:fill-opacity`: Double in the range [0.0, 0.1] inclusive specifying the + opacity of the interior of the ellipse. + + - `:stroke-opacity`: Double in the range [0.0, 0.1] inclusive specifying the + opacity of the boundary of the ellipse. + + - `:stroke-style`: \"solid\" or \"dashed\". Defaults to \"solid\". + + - `:svg-ellipse-props`: A map of property name => value of any property + accepted + by [SVGEllipseElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGEllipseElement) + or any parent." + [opts] + [:> m/Ellipse (process-color opts)]) (defn Circle - " - - `:center` - - `:radius` - - `:svg-ellipse-props` - - `:color` - - `:weight` - - `:fill-opacity` - - `:stroke-opacity` - - `:stroke-style` - " + "Similar to [[ellipse]] but takes a single number for `:radius`. + + See [[ellipse]] for a description of all other supported options." [{:keys [radius] :as props}] [Ellipse (assoc props :radius [radius radius])]) -(defcomponent Text - " - - `:x` - - `:y` - - `:attach` - - `:attach-distance` - - `:size` - - `:color` - - `:svg-text-props` - " - m/Text) - -(defcomponent Vector - " - - `:tail` - - `:tip` - - `:svg-line-props` - - `:color` - - `:opacity` - - `:weight` - - `:style` - " - m/Vector) - -(defcomponent Transform - " - - `:matrix` - - `:translate` - - `:scale` - - `:rotate` - - `:shear` - " - m/Transform) +(defn Text + "Takes an options map with `:x` and `:y` required and any number of string + children and renders the concatenated string children onto a Mafs scene. + + Supported options: + + - `:x`: The x-coordinate of the element the text should attach to. + + - `:y`: The y-coordinate of the element the text should attach to. + + - `:attach`: The cardinal direction that `s` should be offset from its + element. One of \"n\" | \"ne\" | \"e\" | \"se\" | \"s\" | \"sw\" | \"w\" | + \"nw\". + + - `:attach-distance`: The distance away from the attaching element. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the polygon's boundary line. + + - `:size`: font size. + + - `:svg-text-props`: A map of property name => value of any property accepted + by [SVGTextElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGTextElement) + or any parent." + [& children] + (let [[opts children] (split-opts children)] + (into [:> m/Text (process-color opts)] + children))) + +(defn Vector + "Takes a map with `:tip` entry required and renders a vector onto a Mafs scene. + + Supported options: + + - `:tail`: 2-vector `[ ]` specifying the coordinates of the vector tip. + + - `:tip`: 2-vector `[ ]` specifying the coordinates of the vector tip. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the polygon's boundary line. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\". + + - `:svg-line-props`: A map of property name => value of any property accepted + by [SVGLineElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGLineElement) + or any parent." + [opts] + [:> m/Vector (process-color opts)]) + +(defn Transform + "Takes an options map and any number of children and transforms the children as + specified by the options. + + Supported options: + + - `:matrix`: Matrix object generated by the code in [[mafs.matrix]]. + + - `:translate`: 2-vector of the form `[ ]`. + + - `:scale`: either a number (scale factor) or a 2-vector of the form + `[ ]`. + + - `:rotate`: number of radians to rotate the children. + + - `:translate`: 2-vector of the form `[ ]`." + [& children] + (into [:> m/Transform] children)) (defn ^:no-doc named-constraints [s initial] @@ -124,9 +257,16 @@ "Invalid constraint name: " {:constrain s})))) +(defn ^:no-doc ->array-fn [f] + (fn [input] + (let [ret (f input)] + (if (vector? ret) + (apply array ret) + ret)))) + (defn ^:no-doc constrain->fn [constrain initial] (cond (nil? constrain) nil - (fn? constrain) (comp clj->js constrain) + (fn? constrain) (->array-fn constrain) (or (keyword? constrain) (string? constrain)) @@ -148,16 +288,56 @@ :else #(swap! %1 assoc path %2))) (defn MovablePoint - "This version takes an atom and, optionally, a path into the atom. - - `:atom` - - `:path` optional - - `:constrain` - - `:color` + "Takes an options map and renders a movable point onto a Mafs scene. + + Movable points can be dragged around the coordinate plane, or moved via the + keyboard. These points can also synchronize their current position into an + atom specified by the user, optionally at some nested path. + + Control the point by either specifying + + - `:atom` (and `:path`, optionally) + - `:point` and `:on-move`. + + Supported options: + + - `:atom`: atom into which the movable point should synchronize its current + coordinates `[ ]`. By default, `reset!`s the atom. Use `:path` to + synchronize with some internal path. + + - `:path`: the (optional) path into the atom. For example, any of these forms + are valid: + + ```clojure + (reagent.core/with-let [!xy (reagent.core/atom [0 0])] + (movable-point {:atom !xy})) + + (reagent.core/with-let [!state (reagent.core/atom {:coords [0 0]})] + (movable-point {:atom !state :path :coords})) + + (reagent.core/with-let + [!state (reagent.core/atom {:nested {:coords [0 0]}})] + (movable-point {:atom !state :path [:nested :coords]})) + ``` + + - `:point`: the controlled coordinates `[ ]` of the point. + + - `:on-move`: called on each update with the new coordinates of the point. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:constrain`: Either \"horizontal\" | \"vertical\" | + + If you supply a function, it will be called on each point update with the + proposed position; return a new 2-vector with the constrained position. - also discuss + For example, the following will constrain the point to a sine curve: - - `:point` - - `:on-move`" + ```clojure + (movable-point {:constrain (fn [[x _]] [x (Math/sin x)])}) + ```" [{!state :atom :keys [point path constrain on-move] :as opts}] (when (and !state point) (js/console.warn @@ -186,7 +366,9 @@ (fn [{!state :atom on-move :on-move}] (reagent/with-let [!state (or !state (reagent/atom initial))] [:> m/MovablePoint - (assoc opts + ;; TODO is this a problem, that the color can't come in? Can color not + ;; update? + (assoc (process-color opts) :point (get !state) :on-move (fn [new-point] diff --git a/src/mafs/debug.cljs b/src/mafs/debug.cljs index 74d7397..183689e 100644 --- a/src/mafs/debug.cljs +++ b/src/mafs/debug.cljs @@ -1,11 +1,23 @@ (ns mafs.debug - (:require ["mafs" :as m] - [mafs.macros :refer [defcomponent]])) + (:require ["mafs" :as m])) -(defcomponent TransformWidget - (.-TransformWidget m/Debug)) +(defn TransformWidget + "Component that wraps any supplied children in an interactive transformation + widget that lets you rotate, scale and translate all children." + [& children] + (into [:> (.-TransformWidget m/Debug)] + children)) -(defcomponent ViewportInfo - " - - `:precision`" - (.-ViewportInfo m/Debug)) +(defn ViewportInfo + "Component that mounts a `ViewportInfo` component into the Mafs scene. + + This component displays Mafs' understanding of the world space that's in view, + showing both the minimum and maximum x and y values, as well as what panes are + visible according to the pane context. + + Supported options: + + - `:precision`: The number of decimal places to which to round the displayed + values. Defaults to 3." + ([] [:> (.-ViewportInfo m/Debug)]) + ([opts] [:> (.-ViewportInfo m/Debug) opts])) diff --git a/src/mafs/line.cljs b/src/mafs/line.cljs index d2a0d77..b8a3f3c 100644 --- a/src/mafs/line.cljs +++ b/src/mafs/line.cljs @@ -1,47 +1,104 @@ (ns mafs.line (:require ["mafs" :as m] - [mafs.macros :refer [defcomponent]])) - -(defcomponent Segment - " - - `:point1` - - `:point2` - - `:color` - - `:opacity` - - `:weight` - - `:style` - " - (.-Segment m/Line)) - -(defcomponent ThroughPoints - " - - `:point1` - - `:point2` - - `:color` - - `:opacity` - - `:weight` - - `:style` - " - (.-ThroughPoints m/Line)) - -(defcomponent PointSlope - " - - `:point` - - `:slope` - - `:color` - - `:opacity` - - `:weight` - - `:style` - " - (.-PointSlope m/Line)) - -(defcomponent PointAngle - " - - `:point` - - `:angle` - - `:color` - - `:opacity` - - `:weight` - - `:style` - " - (.-PointAngle m/Line)) + [mafs.core :as mafs])) + +(defn Segment + "Given a map `opts`, renders a line segment between points `:point1` and + `:point2` into a Mafs scene. + + Supported options: + + - `:point1`: a 2-vector of the form `[ ]`. + + - `:point2`: a 2-vector of the form `[ ]`. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the line. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\"." + [opts] + [:> + (.-Segment m/Line) + (mafs/process-color opts)]) + +(defn ThroughPoints + "Given a map `opts`, renders a line between points `:point1` and `:point2` into + a Mafs scene. + + Supported options: + + - `:point1`: a 2-vector of the form `[ ]`. + + - `:point2`: a 2-vector of the form `[ ]`. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the line. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\"." + [opts] + [:> + (.-ThroughPoints m/Line) + (mafs/process-color opts)]) + +(defn PointSlope + "Given a map `opts`, renders a line passing through point `:point` with slope + `:slope` into a Mafs scene. + + Supported options: + + - `:point`: a 2-vector of the form `[ ]`. + + - `:slope`: number defining the slope of the line (amount of increase in the + `y` direction for each unit increase in the `x` direction). + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the line. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\"." + [opts] + [:> + (.-PointSlope m/Line) + (mafs/process-color opts)]) + +(defn PointAngle + "Given a map `opts`, renders a line passing through point `:point` with angle + `:angle` into a Mafs scene. + + Supported options: + + - `:point`: a 2-vector of the form `[ ]`. + + - `:angle`: angle in radians. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the line. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\"." + [opts] + [:> + (.-PointAngle m/Line) + (mafs/process-color opts)]) diff --git a/src/mafs/macros.cljc b/src/mafs/macros.cljc deleted file mode 100644 index 6623ba1..0000000 --- a/src/mafs/macros.cljc +++ /dev/null @@ -1,19 +0,0 @@ -(ns mafs.macros - #?(:cljs - (:require [reagent.core])) - #?(:cljs - (:require-macros [mafs.macros]))) - -(defmacro defcomponent - ([sym component] - `(defcomponent ~sym "" {} ~component)) - ([sym docstring component] - `(defcomponent ~sym ~docstring {} ~component)) - ([sym docstring attr component] - {:pre [(string? docstring) - (map? attr)]} - (when (:ns &env) - (let [m (-> (meta sym) - (merge (assoc attr :doc docstring)))] - `(def ~(with-meta sym m) - (reagent.core/adapt-react-class ~component)))))) diff --git a/src/mafs/matrix.cljs b/src/mafs/matrix.cljc similarity index 100% rename from src/mafs/matrix.cljs rename to src/mafs/matrix.cljc diff --git a/src/mafs/plot.cljs b/src/mafs/plot.cljs index 4e91be3..096bb84 100644 --- a/src/mafs/plot.cljs +++ b/src/mafs/plot.cljs @@ -1,53 +1,234 @@ (ns mafs.plot (:require ["mafs" :as m] - [mafs.macros :refer [defcomponent]])) - -(defcomponent OfX - " - - `:y` - - `:svg-path-props` - - `:color` - - `:min-sampling-depth` - - :`max-sampling-depth` - - `:opacity` - - `:weight` - - `:style` - " - (.-OfX m/Plot)) - -(defcomponent OfY - " - - `:x` - - `:svg-path-props` - - `:color` - - `:min-sampling-depth` - - :`max-sampling-depth` - - `:opacity` - - `:weight` - - `:style` - " - (.-OfY m/Plot)) - -(defcomponent Parametric - " - - `:xy` - - `:t` - - `:svg-path-props` - - `:color` - - `:min-sampling-depth` - - :`max-sampling-depth` - - `:opacity` - - `:weight` - - `:style` - " - (.-Parametric m/Plot)) - -(defcomponent VectorField - " - - `:xy` - - `:xy-opacity` - - `:step` - - `:opacity-step` - - `:color` - - `:style`" - (.-VectorField m/Plot)) + [mafs.core :as mafs])) + +(defn OfX + "Takes either + + - a function `f: R => R` and an optional `opts` map + - a map with `:y` entry required + + and returns a fragment that will plot `y = f(x)` values onto a Mafs scene. + + Supported options: + + - `:y`: a function `f: R => R`. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:min-sampling-depth`: The minimum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:max-sampling-depth`: The maximum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\". + + - `:svg-path-props`: A map of property name => value of any property + accepted + by [SVGPathElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement) + or any parent." + [opts] + [:> + (.-OfX m/Plot) + (mafs/process-color opts)]) + +(defn OfY + "Takes either + + - a function `f: R => R` and an optional `opts` map + - a map with `:x` entry required + + and returns a fragment that will plot `x = f(y)` values onto a Mafs scene. + + Supported options: + + - `:x`: a function `f: R => R`. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:min-sampling-depth`: The minimum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:max-sampling-depth`: The maximum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\". + + - `:svg-path-props`: A map of property name => value of any property + accepted + by [SVGPathElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement) + or any parent." + [opts] + [:> + (.-OfY m/Plot) + (mafs/process-color opts)]) + +(defn Inequality + "given a map `opts`, returns a fragment that will plot the region between two + functions (or constants, or either function-constant combination). The + inequality can be a function of $x$ or $y$. + + Supported options: + + - `:y`: map with some combination of keys `:<`, `:>`, `:<=` or `:>=` + representing inequality symbols, each bound to either + + - a static `x` value, or + + - a function of type `f: R => R` + + Mixing conflicting inequality operators like `:<` and `:<=` or `:>` and + `:>=` will result in a runtime exception. + + - `:x`: same as `:y`, but functions are passed `y` values instead of `x` + values. Only one of `:x` or `:y` is supported! Passing both will result in a + runtime exception. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the lines on the plot. + + - `:stroke-color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:stroke-opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:fill-color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:fill-opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:min-sampling-depth`: The minimum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:max-sampling-depth`: The maximum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:upper-color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:upper-weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the lines on the plot. + + - `:upper-opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:lower-color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:lower-weight`: Double in the range [0.0, 0.1] inclusive specifying the weight of + the lines on the plot. + + - `:lower-opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:svg-upper-path-props`: A map of property name => value of any property + accepted + by [SVGPathElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement) + or any parent. + + - `:svg-lower-path-props`: A map of property name => value of any property + accepted + by [SVGPathElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement) + or any parent. + + - `:svg-fill-props`: A map of property name => value of any property + accepted + by [SVGPathElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement) + or any parent." + [opts] + [:> + (.-Inequality m/Plot) + (mafs/process-color opts)]) + +(defn Parametric + "Given a map `opts`, returns a fragment that will plot $(x, y)$ points as a + function of the parametric function provided to `:xy` for the range specified + by `:t` onto a Mafs scene. + + Supported options: + + - `:xy`: a function `(fn [t] [ ])`. + + - `:t`: 2-vector of the form `[ ]` inclusive defining the + domain in which to evaluate `:xy`. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:min-sampling-depth`: The minimum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:max-sampling-depth`: The maximum recursive depth of the sampling + algorithm. See [the Mafs docs](https://mafs.dev/guides/display/plots) for more + detail. + + - `:opacity`: Double in the range [0.0, 0.1] inclusive. + + - `:weight`: Double in the range [0.0, 0.1] inclusive. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\". + + - `:svg-path-props`: A map of property name => value of any property + accepted + by [SVGPathElement](https://developer.mozilla.org/en-US/docs/Web/API/SVGPathElement) + or any parent." + [opts] + [:> + (.-Parametric m/Plot) + (-> (mafs/process-color opts) + (update :xy mafs/->array-fn))]) + +(defn VectorField + "Given a map `opts`, plots a vector at every point in a grid on a coordinate + plane by calling a function `:xy` that receives the vector's tail and returns + its tip. + + Supported options: + + - `:xy`: a function `(fn [[ ]] [ ])`. + + - `:xy-opacity`: function from a point `[ ]` to a double in the + range [0.0, 0.1] inclusive. + + - `:step`: spacing between each vector in the vector field. + + - `:opacity-step`: opacity is quantized; this value (between 0.01 and 1) + defines the width of an opacity layer bucket. + + - `:color`: any valid [CSS + color](https://developer.mozilla.org/en-US/docs/Web/CSS/color), or any keyword + from [[mafs.core/Theme]]. + + - `:style`: \"solid\" or \"dashed\". Defaults to \"solid\"." + [opts] + [:> + (.-VectorField m/Plot) + (-> (mafs/process-color opts) + (update :xy mafs/->array-fn))]) diff --git a/src/mafs/vec.cljs b/src/mafs/vec.cljc similarity index 100% rename from src/mafs/vec.cljs rename to src/mafs/vec.cljc