Skip to content

Latest commit

 

History

History
552 lines (379 loc) · 16.5 KB

how-to.md

File metadata and controls

552 lines (379 loc) · 16.5 KB

How-To La Habra

Hello and welcome to a description of the La Habra API. Please feel free to download/fork and mess around with the library. Tweet me with what you make!

But Beware: This is an active art tool that will shift and evolve as my practice does and it comes with no guarantees of support, updates, or bugfixes. At any point after September 2018, this documentation may be out of date, though I will do my best to be sure it is not.

These functions are best paired with a set of snippets for Atom, so you don't have to remember function signatures in the middle of a set. Those 🤞🏽 should 🤞🏽 always be up-to-date.

Caveats given; on with the show!

The Basics

La Habra is a Clojurescript—Figwheel—Electron app. That means the graphics code is written in Clojurescript (reference | koans | tutorial), which is live-reloaded by Figwheel and run in an Electron app window.

Everything is drawn into a single SVG via Reagent. That means you can use any CSS style that works on an SVG in the map attached to an element. (Check out the link for more about the syntax.)

[:circle {:cx     100
          :cy     100
          :r      20
          :style {:opacity .7}}] // add any style

Starting the App

To start the application, you must start both Figwheel and Electron.

In one terminal window, start Figwheel with

lein cooper

or

npm start

If this fails, try removing the target directory.

Start Electron with

electron .

or

npm server

If you do not have electron installed run

npm install -g electron

and then try the above step again.

Where to Put the Code

All the relevant code is in ui_src/ui. In particular, core.cljs is the only file you will need at performance time. It imports all the necessary dependencies.

Cx List

This is the core of the whole shebang. Everything you want drawn into the SVG must be represented in the list returned cx function, located towards the bottom of the core.cljs file. (Since Clojurescript is read in order, everything must be defined before it is used — no hoisting here!)

(defn cx [frame]
  (list
  
  [:circle {:cx     100
            :cy     100
            :r      20
            :style {:opacity .7}}]
 
    
  )) ;

The frame value is passed to the function each time the frame is incremented.

The Timers

The timers take care of incrementing the frame and redrawing the SVG. Both are evaluated once when the code loads initially. The app must be refreshed manually to reset the values.

start-cx-timer redraws the SVG every 50ms. start frame timer increments the frame value every 500ms. Both values can be changed.

Generating Shapes

Ok, so, you want to add some shapes to your SVG! You could, if you wanted to, add shapes by writing the SVG code with the Reagent/Hiccup syntax.

[:circle {:cx     100
          :cy     100
          :r      20
          :style {:opacity .7}}]

However, La Habra provides functions for generating and varying the underlying hashmaps as data and then drawing them when the manipulation is complete. This is what is produced when the related snippets are invoked. To create the circle as above, we would write:

(->>
  (gen-circ "#e0f" 100 100 20) ; arguments are fill, cx, cy, and r
  (draw))                      ; transforms the data from gen-circ into reagent

If we wanted the circle to only appear every fourth frame, we can use the nth-frame function.

(->>
  (gen-circ "#e0f" 100 100 20)
  (draw)
  (when (nth-frame 4 frame))) ; uses the frame argument passed to `cx`

To show every frame BUT the fourth, we can use when-not.

(->>
  (gen-circ "#e0f" 100 100 20)
  (draw)
  (when-not (nth-frame 4 frame))) ; uses the frame argument passed to `cx`

All of these examples (and snippets) use the Clojurescript thread-last macro, which calls each function, from top to bottom, with the result of the previous function being passed as the last argument to the next one. In this case, the basic circle data is passed to draw and then the code representing the circle is passed to when, which only adds it to the list of elements in the SVG when the predicate, (nth-frame 4 frame), returns true.

Basic Shapes

These basic shapes have generator helpers. Each takes a set of mandatory arguments and most an optional mask argument.

Each must be passed to draw in order to be rendered.


gen-circ

arguments: fill-string x y radius & mask

fill-string is any CSS-acceptable color string: hex, hsla, rgba.

x y and radius are values accepted by SVG: plain numbers, strings with measurement specified (e.g., "100px", "40vw", etc.)

The optional mask argument must be the ID to a mask definition. These can be generated using url(url "mask-name"). See masks below for more information.

EXAMPLE

(->>
  (gen-circ "#e0f" 100 100 20)
  (draw))       

gen-rect

arguments: fill-string x y w h & mask

fill-string is any CSS-acceptable color string: hex, hsla, rgba.

x y w and h are values accepted by SVG: plain numbers, strings with measurement specified (e.g., "100px", "40vw", etc.)

The optional mask argument must be the ID to a mask definition. These can be generated using url(url "mask-name"). See masks below for more information.

EXAMPLE

  (->>
    (gen-rect "#e0f" 100 100 500 100)
    (draw))

gen-line

arguments: first-point second-point color & width

first-point and second-point expect vectors representing the start and end points of the line: [x y],

color is any CSS-acceptable color string: hex, hsla, rgba. It will be the stroke color of the line.

The optional width argument sets the stroke-width. It defaults to 4px if no value is passed.

EXAMPLE

(->>
  (gen-line [40 100] [80 400] "#e0f")
  (draw))

(->>
  (gen-line [40 100] [80 400] "#e0f" 10)
  (draw))

gen-poly

arguments: fill-string points & mask

fill-string is any CSS-acceptable color string: hex, hsla, rgba.

points is a vector of points for the polygon. See the SVG polygon element spec for more information.

The optional mask argument must be the ID to a mask definition. These can be generated using url(url "mask-name"). See masks below for more information.

EXAMPLE

(->>
  (gen-poly "#e0f" [100 100 400 400 300 100 200 50])
  (draw))

gen-shape

arguments: fill-string path & mask

fill-string is any CSS-acceptable color string: hex, hsla, rgba.

path is an SVG path string (i.e., what you would pass to the d attribute). See the SVG path spec for more information.

path can also be one of geometric paths already defined in La Habra — tri square pent hex hept oct.

The optional mask argument must be the ID to a mask definition. These can be generated using url(url "mask-name"). See masks below for more information.

EXAMPLE

(->>
  (gen-shape "#e0f" hex)
  (draw))

(->>
  (gen-shape "#e0f" "M92.6912391 1.03515625 184.526999 51.461505 184.526999 101.419922 184.526999 152.314202 92.6912391 202.740551 0.855479021 152.314202 0.855479021 98.7460938 0.855479021 51.461505z")
  (draw))

Changing Styles

What if you want to change the basic shapes? What if you want to adjust the opacity, add an attribute not covered by the basic shape generation functions, or translate, rotate, and scale?

style to the rescue!

style

arguments: changes shape

changes is a hashmap of style attributes and values to be added to the shape, the map of shape data produced by the gen-* functions.

style is usually used with the thread-last macro. It can be called multiple times and later values will overwrite earlier values. By default, all shapes have transform-origin: center applied as a default style.

EXAMPLE

; draws an octagon with dashed outline, scaled up
; and moved to the center-ish of the viewport
; pink and oct are defined in La Habra
(->>
  (gen-shape "hsla(360, 10%, 10%, 0)" oct)
  (style {:transform "translate(40vw, 40vh) scale(2)")})
  (style {:stroke pink 
          :stroke-width 10 
          :stroke-dasharray 20 
          :stroke-dashoffset 1000
          :stroke-linecap :round
          :stroke-linejoin :round})
  (draw))

Compound Generators

These generate more complex sets of shapes.


gen-bg-lines

arguments: color num style

Generates a number of lines of increasing width.

color is any CSS-acceptable color string: hex, hsla, rgba.

num is the number of lines to draw.

The optional style argument takes the same sort of styles hashmap one would pass to the style function.

EXAMPLE

(gen-bg-lines "#e0f" 
              (mod frame 80)
              {:opacity .5})

gen-cols

arguments: color stroke-width num-cols offset

color is any CSS-acceptable color string: hex, hsla, rgba.

stroke-width num-cols and offset are numbers representing those attributes. offset determines how far apart the lines will be and does not take stroke width into account.

EXAMPLE

(gen-cols "#e0f" 4 40 20)

gen-rows

arguments: color stroke-width num-rows offset

color is any CSS-acceptable color string: hex, hsla, rgba.

stroke-width num-rows and offset are numbers representing those attributes. offset determines how far apart the lines will be and does not take stroke width into account.

EXAMPLE

(gen-rows "#e0f" 4 40 20)

gen-line-grid

arguments: color stroke-width num-cols num-rows offset

This function generates the columns returned by gen-cols and the rows from gen-rows together.

color is any CSS-acceptable color string: hex, hsla, rgba.

stroke-width num-cols and num-rows are numbers representing those attributes.

offset is a map with keys for row and column offsets: {:row 20 :col 10}.

EXAMPLE

(gen-line-grid "#e0f" 4 80 40 {:row 20 :col 10})

gen-grid

arguments: num-cols num-rows offset base-obj

This function creates a grid from whatever base object is passed to it.

num-cols and num-rows are numbers representing those attributes.

offset is a map with keys for row and column offsets: {:row 20 :col 10}.

base-obj is any shape to be repeated.

EXAMPLE

(->>
  (gen-grid
    10 10
    {:col 100 :row 100}
    (gen-circ "#e0f" 10 10 10)) 
   (map #(style {:opacity .5} %)) ; note: style and draw must map over the group
   (map draw))

freak-out

arguments: max-x max-y max-r num color
OR max-x max-y max-r num color style
OR min-x max-x min-y max-y max-r num color
OR min-x max-x min-y max-y max-r num color style

This function generates a new set of circles each redraw (so every 50ms). It gives a sparkle effect.

min-x max-x min-y and max-y are numbers that together define the square in which the circles appear. If only two values, max-x and max-y, are passed, the minimum is set to 0.

max-r is the maximum radius. The minimum is always 0.

color is any CSS-acceptable color string: hex, hsla, rgba.

The optional style argument takes the same sort of styles hashmap one would pass to the style function.

EXAMPLE

;; this example shows freakout with the minimum number of arguments
(freak-out @width   ;@width is a helper that provides the width of the window
           @height  ;@height is a helper that provides the height of the window
           10  
           100
           "#e0f")
           
;; this example shows freakout with the maximum number of arguments           
(freak-out 200 @width
           200 @height
           10  
           100
           "#e0f"
           {:opacity .5})

Fills

As we have seen above, most color arguments take any CSS color string. La Habra includes two types of fill helpers.

Solid

The solid fills, located unsurprisingly in fills.cljs, comprise the base palette of La Habra. They are:

  • gray
  • mint
  • navy
  • blue
  • midnight
  • orange
  • br-orange
  • pink
  • white
  • yellow

Patterns

Patterned fills make use the SVG pattern definition capability. Using Irene Ros's Patternfills utility, I generated SVG patterns. Then using the base-64 encoded images and the pattern-def helper, these are added to the SVG at the bottom of core.cljs.

Available patterns are:

  • blue-dots
  • blue-lines
  • pink-dots
  • pink-lines
  • gray-dots
  • gray-dots-lg
  • gray-lines
  • gray-patch
  • mint-dots
  • mint-lines
  • navy-dots
  • navy-lines
  • orange-dots
  • orange-lines
  • br-orange-dots
  • br-orange-lines
  • yellow-dots
  • yellow-lines
  • white-dots
  • white-dots-lg
  • white-lines
  • shadow

They are passed in the place of color arguments using the following helpers:

(pattern (:id white-lines))

Animating Shapes with Atoms

Unlike the shapes seen above, shapes we want to animate with CSS animations cannot be defined directly in the cx list. That's because the would then start over every 50ms when the SVG is redrawn.

Fortunately, using the atom helper from Reagent, we can define an object that we want to stay the same each time and animate that. We add the most updated value of at atom to the draw list by prefixing it's name with @.

(def my-cool-atom ..) ; define outside the function

(defn cx [frame]
  @my-cool-atom)      ; add it to the list

atom

arguments: drawn-element

atom works like the draw function, taking a drawn shape and making an atom.

EXAMPLE

(def move-me
  (->>
   (gen-shape mint hept)
   (style {:opacity .5 :transform-origin "center" :transform "scale(4.4)"})
   (anim "woosh" "10s" "infinite")
   (draw)
   (atom)))

anim

arguments name duration count opts shape

The anim helper is similar to the styles function in that it takes a number of configuration arguments and the shape structure and adds the animation to the shape. It must be used inside an atom.

name is the name of an animation, given as a string. See make-frames! and make-body for how to create a named animation. A few in-built options are also available:

  • fade-out
  • fade-in
  • fade-in-out
  • rot
  • rev

duration is a string indicating how long an animation should take, with the measurement included, for instance: "10s" or "1000ms".

count takes the number of times the animation should repeat or the string "infinite". Note, the count will restart from 0 whenever the code is saved and reloaded.

The optional opts takes a hashmap with two possible keys: timing and delay. The latter takes values similar to duration. The former takes any transition timing function, with the default being ease.

EXAMPLE

(def move-me
  (->>
   (gen-shape mint hept)
   (style {:opacity .5 :transform-origin "center" :transform "scale(4.4)"})
   (anim "woosh" "10s" "infinite")
   (draw)
   (atom)))

make-frames!

arguments: name frames bodies

make-body

arguments: att values

make-frames! and make-body work together to concatenate animation keyframes and inject them into the stylesheets while La Habra is running, so they are available to used in the anim helper.

make-frames! takes the name of the animation, a vector of frames, and a vector of bodies. make-body takes the attribute to animated and a vector of the values at each of the frame stops. These vectors must be of equal length.

EXAMPLE

(make-frames!
  "woosh"
    [10, 35, 55, 85, 92]
   (make-body "transform" ["translate(80%, 50%) rotate(2deg) scale(1.2)"
                           "translate(604%, 100%) rotate(-200deg) scale(2.4)"
                           "translate(80%, 450%) rotate(120deg) scale(3.4)"
                           "translate(604%, 300%) rotate(-210deg) scale(2.2)"
                           "translate(80%, 50%) rotate(400deg) scale(6.2)"]))

Groups


Masks