|
1 | 1 | ;; # 🎨 Making a Clerk Logo |
2 | 2 | ^{:nextjournal.clerk/visibility {:code :fold}} |
3 | 3 | (ns logo |
4 | | - "A notebook generating the Clerk's logo. |
5 | | -
|
6 | | - Note that to run this, the `:clojure2d` needs to be added." |
7 | | - (:require [nextjournal.clerk :as clerk] |
8 | | - [clojure2d.core :as c2d] |
9 | | - [fastmath.complex :as complex] |
10 | | - [fastmath.vector :as v])) |
11 | | - |
12 | | -^{:nextjournal.clerk/visibility {:code :hide :result :hide}} |
13 | | -(System/setProperty "java.awt.headless" "true") |
| 4 | + "A notebook generating the Clerk's logo." |
| 5 | + (:require [nextjournal.clerk :as clerk])) |
14 | 6 |
|
15 | 7 | ;; The new Clerk header image is made from a fifth order [Hilbert |
16 | 8 | ;; Curve](https://en.wikipedia.org/wiki/Hilbert_curve), so we will |
|
21 | 13 | (hilbert-curve 0 0 x-size 0 0 y-size order)) |
22 | 14 | ([x y xi xj yi yj n] |
23 | 15 | (if (<= n 0) |
24 | | - [(v/vec2 (+ x (/ (+ xi yi) 2.0)) |
25 | | - (+ y (/ (+ xj yj) 2.0)))] |
| 16 | + [[(+ x (/ (+ xi yi) 2.0)) |
| 17 | + (+ y (/ (+ xj yj) 2.0))]] |
26 | 18 | (mapcat (partial apply hilbert-curve) |
27 | 19 | [[x y (/ yi 2.0) (/ yj 2.0) (/ xi 2.0) (/ xj 2.0) (dec n)] |
28 | 20 | [(+ x (/ xi 2.0)) (+ y (/ xj 2.0)) (/ xi 2.0) (/ xj 2.0) (/ yi 2.0) (/ yj 2.0) (dec n)] |
|
33 | 25 | (def hilbert-points |
34 | 26 | (hilbert-curve 800 800 5)) |
35 | 27 |
|
36 | | -;; But they're much more interesting if we use |
37 | | -;; [Clojure2D](https://github.com/Clojure2D/clojure2d) canvas to draw |
38 | | -;; a path made from points to show the complete curve: |
39 | | -(c2d/with-canvas-> (c2d/canvas 800 800 :highest) |
40 | | - (c2d/set-background 255 255 255) |
41 | | - (c2d/set-color 66 66 66) |
42 | | - (c2d/set-stroke 4) |
43 | | - (c2d/path hilbert-points) |
44 | | - c2d/to-image) |
| 28 | +;; But they're much more interesting if we use the browser's built-in |
| 29 | +;; support for SVG to draw a path made from points to show the |
| 30 | +;; complete curve. First, we'll make a little helper function to |
| 31 | +;; convert a sequence of points into an SVG path: |
| 32 | + |
| 33 | +(defn points->path |
| 34 | + "Turn a sequence of points into an SVG path string." |
| 35 | + [[[start-x start-y] & pts]] |
| 36 | + (reduce str |
| 37 | + (str "M " start-x "," start-y) |
| 38 | + (map (fn [[x y]] (str " L" x "," y)) pts))) |
| 39 | + |
| 40 | +;; And then we'll use that to visualize the curve: |
| 41 | + |
| 42 | +(clerk/html |
| 43 | + [:svg {:stroke "#666666" |
| 44 | + :stroke-width 4 |
| 45 | + :fill "none" |
| 46 | + :viewBox "0 0 800 800"} |
| 47 | + [:path {:d (points->path hilbert-points)}]]) |
45 | 48 |
|
46 | 49 | ;; The trick to getting the effect we want is to apply a conformal |
47 | 50 | ;; mapping to the original Hilbert Curve to convert it into an 👁 shape |
48 | 51 | ;; in celebration of Clerk's viewers. We can do this by treating the |
49 | 52 | ;; original point coordinates as complex numbers, squaring them, then |
50 | 53 | ;; taking the real and imaginary portions of each of those complex |
51 | | -;; numbers as the _x_ and _y_ coordinates of a new set of points. This |
52 | | -;; is made especially easy because Clojure2D happens to include the |
53 | | -;; author's [Fastmath](https://github.com/generateme/fastmath) |
54 | | -;; library. 🎉 |
55 | | - |
56 | | -(c2d/with-canvas-> (c2d/canvas 1000 600 :highest) |
57 | | - (c2d/set-background 33.0 5.0 24.0) ; RGB deep purple |
58 | | - (c2d/translate 500 300) ; origin to center |
59 | | - (c2d/rotate (/ Math/PI 2)) ; rotate the canvas, ⬯ → ⬭ |
60 | | - ;; colour and stroke width |
61 | | - (c2d/set-color 147.0 189.0 154.0) |
62 | | - (c2d/set-stroke 4) |
63 | | - ;; ellipses to fill in the center of the "eye" |
64 | | - (c2d/ellipse 0 0 22 22) |
65 | | - (c2d/ellipse 0 -10 20 20) |
66 | | - (c2d/ellipse 0 10 20 20) |
67 | | - ;; draw a path using the complex square of our hilbert curve points |
68 | | - (c2d/path (map #(-> (v/sub % (v/vec2 400 400)) ; -[½w ½h] from vectors to center the curve |
69 | | - complex/sq ; square each vector as a complex number |
70 | | - (v/mult 0.0015)) ; scale those squared vectors down |
71 | | - hilbert-points)) |
72 | | - c2d/to-image) |
| 54 | +;; numbers as the _x_ and _y_ coordinates of a new set of points. To |
| 55 | +;; make this work, we'll use a couple of helper functions to perform |
| 56 | +;; those calculations: |
| 57 | + |
| 58 | +(defn complex-multiply |
| 59 | + "Multiply two complex numbers." |
| 60 | + [z1 z2] |
| 61 | + [(- (* (first z1) (first z2)) |
| 62 | + (* (second z1) (second z2))) |
| 63 | + (+ (* (first z1) (second z2)) |
| 64 | + (* (second z1) (first z2)))]) |
| 65 | + |
| 66 | +(defn complex-square |
| 67 | + "Square a complex number." |
| 68 | + [z] |
| 69 | + (complex-multiply z z)) |
| 70 | + |
| 71 | +;; And a helpers for multiplying a vector by a scalar: |
| 72 | + |
| 73 | +(defn v* |
| 74 | + "Multiply vector `v` by scalar `s`." |
| 75 | + [v s] |
| 76 | + [(* (first v) s) (* (second v) s)]) |
| 77 | + |
| 78 | +;; After which we can generate the complete logo: |
| 79 | + |
| 80 | +(clerk/html |
| 81 | + [:svg {:stroke "rgb(147.0 189.0 154.0)" |
| 82 | + :stroke-width 4 |
| 83 | + :fill "none" |
| 84 | + :viewBox "0 0 1000 600"} |
| 85 | + [:rect {:width 1000 :height 600 :stroke "none" :fill "rgb(33.0 5.0 24.0)"}] ; deep purple bg |
| 86 | + [:ellipse {:cx 500 :cy 300 :rx 11 :ry 11 :stroke "none" :fill "rgb(147.0 189.0 154.0)"}] ; the pupil |
| 87 | + [:ellipse {:cx 490 :cy 300 :rx 11 :ry 11 :stroke "none" :fill "rgb(147.0 189.0 154.0)"}] |
| 88 | + [:ellipse {:cx 510 :cy 300 :rx 11 :ry 11 :stroke "none" :fill "rgb(147.0 189.0 154.0)"}] |
| 89 | + (let [ |
| 90 | + points (map #(-> [(- (first %) 400) (- (second %) 400)] ; -[½w ½h] to center the curve |
| 91 | + complex-square ; square each vector as a complex number |
| 92 | + (v* 0.0015)) ; scale those squared vectors down |
| 93 | + hilbert-points)] |
| 94 | + [:path {:transform "rotate(90) translate(300,-500)" ; rotate and center "eye" |
| 95 | + :stroke-linejoin "round" |
| 96 | + :stroke-linecap "round" |
| 97 | + :d (points->path points)}])]) |
73 | 98 |
|
74 | 99 | ;; What I find so special and enchanting about the $$w = z^{2}$$ |
75 | 100 | ;; mapping that we're using here is that it maintains the angle of |
|
0 commit comments