Filament Tutorial

Welcome to Filament, a humanist programming language. Computers are amazing. They can calculate incredible things super fast. They can answer questions and draw graphics. However, computers are actually very dumb. All they do is simple arithmetic. What makes them amazing is that they can do it super duper fast . To do smart things humans have to teach them. This is called programming. Anyone can program, including you!

Arithmetic

Filament understands arithmetic. Try typing in a math question like 2+2 then press the 'run' button (or type control-return on your keyboard). Filament will show you the answer: 4 . Now try dividing 4 by 2 or multiplying 3 by 5. Type in 4/2 . Run it. Then type 3 * 5 . Filament uses / to mean division and * for multiplication.

Filament understands longer math equations too. For example, imagine you have a refrigerator box that is 7 feet tall by 4 feet wide by 4 feet deep. You could find out the volume of the box by multiplying all of the sides.

7 * 4 * 4
112

Units

In the above problem only we know that the 7 meant 7 feet . The computer doesn't know because we didn't tell it. Fortunately Filament lets us tell the computer exactly what units we mean. Let's try that again with units:

7feet * 4feet * 4feet
112 foot

Now we get 112 cuft . Cool. Filament knows to convert the answer into cubic feet. But what if we didn't want cubic feet. We are talking about volume and there are several different units that could represent volume. Let's ask Filament to convert it into gallons instead.

7ft * 4ft * 4ft as gal
837.815 gallon

which gives us the answer 837.81 gallons .

Notice that this time we abbreviated feet to ft and gal for gallons . Filament understands the full names and abbrevations for over a hundred kinds of units, and it can convert between any of them. Here's a few more examples to try.

Convert your height into centimeters. I'm 5 foot four inches so

5feet + 4inches as cm
162.560 centimeter

Some kitchen math:

2cups + 4tablespoons
36 tablespoon

Calculate your age in seconds.

today() - date(month : 1, day : 15, year : 2003) as seconds
570585600 second

If you try to convert something that can't be converted, like area to volume, then Filament will let you know. Try this:

7ft * 4ft as gal
209.454 gallon

results in Error. Cannot convert ft^2 to gallons.

Units are very important. They help make sure our calculations are correct. Even professionals get this wrong some times. NASA once lost a space probe worth over 100 million dollars because the software didn't convert correctly between imperial and metric units.

Superman

Now lets try a more complex problem. In one of the Superman movies he flies so fast that the world turns backwards and reverses time. That got me thinking. Is that realistic? The earth is pretty big. How long would it really take him to fly around the world?

We need some information first. How fast can Superman fly? Apparently the comics are pretty vague about his speed. Some say it's faster than light, some say it's infinite, some say it's just slighly slower than The Flash. Since this is about the real world let's go with an older claim, that Superman is faster than a speeding bullet . According to the internet, the fastest bullet ever made was was the .220 Swift which can regularly exceed 4,000 feet per second. The fastest recorded shot was at 4,665 ft/s , so we'll go with that.

Now wee need to know how big the earth is. The earth isn't perfectly spherical and of course it would depend on exactly which part of the earth superman flew, but according to Wikipedia the average (mean) radius of the Earth is 6,371.0 kilometers. We also know the circumference of a circle is 2 * pi * radius . So the equation is

(6371.0km * pi * 2) / 4000ft/s as hours

Pretty fast. He could almost go three times around the earth in a single 24 hour day. But not as fast as the movie said.

Programming is both fun and useful. We can instruct computers to help us answer all sorts of interesting questions. In the next section we'll learn about groups of numbers called lists, and how to do interesting math with them.

Lists

Now let's take a look at lists. Imagine you want to add up some numbers. You could do it by adding each number separately like this:

4 + 5 + 6 + 7 + 8
30

or you could make them a list and use the sum function.

sum([4,5,6,7,8])
30

Sum is a built in function that will add all of the items in a list. There are a lot of other cool functions that work on lists. You can sort a list

sort([8,4,7,1])
1, 4, 7, 8

get the length

length([8,4,7,1])
4

or combine sum and length to find the average

nums << [8,4,7,1] sum(nums) / length(nums)
5

Making Lists

Sometimes you need to generate a list. Suppose you wanted to know the sum of every number from 0 to 100. Of course you could write out the numbers directly, but Filament has a way to generate lists for you. It's called range .

range(10) sum(range(100))
4950

Range is flexible. You can give it both a start and end number, or even jump by steps.

range(min : 20, max : 30)
20, 21, 22, 23, 24, 25, 26, 27, 28, 29
range(100, step : 10)
0, 10, 20, 30, 40, 50, 60, 70, 80, 90

Remember that range will start at the minmum and go to one less than the max. So 0 to 10 will go up to 9.

Filament can handle big lists. If you ask for range(0,10_000_000) it will show the the first few and then ... before the last few.

range(10000)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24 ... 9975, 9976, 9977, 9978, 9979, 9980, 9981, 9982, 9983, 9984, 9985, 9986, 9987, 9988, 9989, 9990, 9991, 9992, 9993, 9994, 9995, 9996, 9997, 9998, 9999]

Lists are very useful for lots of things, but sometimes you get more numbers than you need. Suppose you wanted all the numbers from from 0 to 20 minus the first three. Use take(list,3). Want just the last three use take(list,-3)

list << range(10) take(list, 3)
0, 1, 2
list << range(10) take(list, -3)
7, 8, 9

You can also remove items from a list with drop

drop(range(10), 8)
8, 9

And finally you can join two lists together

join([4,2], [8,6])
4, 2, 8, 6

In addition to holding data, lists let you do things that you could do on a single number, but in bulk. You can add a single number to a list

1 + [1,2,3]
2, 3, 4

or add two lists together

[1,2,3] + [4,5,6]
5, 7, 9

Math with Lists

It might seem strange to do math on lists but it's actually quite useful. Image you had a list of prices and you would like to offer a 20% discount. You can do that with a single multiplication.

prices << [4.86,5.23,10.99,8.43] sale_prices << 0.8 * prices
3.888, 4.184, 8.792, 6.744

Suppose you sold lemonade on four weekends in april, and another four in july. It would be nice to compare the sales for the different weekends to see if july did better thanks to warmer weather. You can do this by just subtracting two lists.

april << [34,44,56,42] july << [67,45,77,98] july - april
33, 1, 21, 56

Doing math with lists is also great for working with vectors. You can add them, multiply as the dot product, and calculate the magnitude.

V1 << [0,0,5] V2 << [1,0,1] V1 + V2 V1 * V2 sqrt(sum(power(V1, 2)))
5

Filtering and Mapping Lists

Lists let you search for data too. You can find items using select and a small function. Let's find all of the prime numbers up to 10000

select(range(100), where : is_prime)
2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97

or all numbers evenly divisible by 5

def div5 ( x : ? ) { x mod5 =0 } select(range(100), where : div5)
0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95

And one of the best parts about lists is that they can hold more than numbers. You can work with lists of strings, numbers, booleans. Consider this simple list of people.

friends << ["Bart","homer","Ned"]
"Bart", "homer", "Ned"

or a list of booleans

[true,false,true]
true, false, true

Drawing Data

Bar Charts

One of the coolest things about lists is that you can draw them. Just send a list into the chart function to see it as a bar chart. Suppose you had a list of heights of your friends.

chart([88,64,75,59])

or just want to draw the numbers from 0 to 9.

chart(range(10))

Plotting equations

While you could use range , map , and chart to draw pictures of x , power(x,2) , sin() or other math equations, there is a much better way: using the plot function.

A quadratic equation

plot(y : (x)->{(x ** 2 - 3 * x - 4)})

Sine wave

plot(y : theta->sin(theta * 2))

A polar Archimedes spiral

spiral << theta->0.25 * theta plot(polar : spiral, min : 0, max : pi * 32)

Charts Datasets

Even better than pulling in your own data, is working with curated datasets that have already been assembled. Filament comes with datasets for

  • Periodic table of elements
  • Letters of the English Alphabet
  • Planets of the solar system
  • Countries of the world
  • When you load a dataset it will be shown as a table.

    elements << dataset("elements")
    namenumberweightsymboldiscovered_bydiscovery_dateprovenance
    Hydrogen11.008HHenry CavendishJanuary 1, 1766https://en.wikipedia.org/wiki/Hydrogen
    Helium24.0026022HePierre JanssenAugust 18, 1868https://en.wikipedia.org/wiki/Helium
    Lithium36.94LiJohan August ArfwedsonJanuary 1, 1800https://en.wikipedia.org/wiki/Lithium
    Beryllium49.01218315BeLouis Nicolas VauquelinJanuary 1, 1798https://en.wikipedia.org/wiki/Beryllium
    Boron510.81BJoseph Louis Gay-LussacJanuary 1, 1808https://en.wikipedia.org/wiki/Boron
    Carbon612.011CAncient EgyptJanuary 1, 2500https://en.wikipedia.org/wiki/Carbon
    Nitrogen714.007NDaniel RutherfordJanuary 1, 1772https://en.wikipedia.org/wiki/Nitrogen
    Oxygen815.999OCarl Wilhelm ScheeleJanuary 1, 1771https://en.wikipedia.org/wiki/Oxygen
    Fluorine918.9984031636FAndré-Marie AmpèreAugust 26, 1812https://en.wikipedia.org/wiki/Fluorine
    Neon1020.17976NeMorris Travershttps://en.wikipedia.org/wiki/Neon
    Sodium1122.989769282NaHumphry Davyhttps://en.wikipedia.org/wiki/Sodium
    Magnesium1224.305MgJoseph Blackhttps://en.wikipedia.org/wiki/Magnesium
    Aluminium1326.98153857Alnullhttps://en.wikipedia.org/wiki/Aluminium
    Silicon1428.085SiJöns Jacob Berzeliushttps://en.wikipedia.org/wiki/Silicon
    Phosphorus1530.9737619985PHennig Brandhttps://en.wikipedia.org/wiki/Phosphorus
    Sulfur1632.06SAncient chinahttps://en.wikipedia.org/wiki/Sulfur
    Chlorine1735.45ClCarl Wilhelm Scheelehttps://en.wikipedia.org/wiki/Chlorine
    Argon1839.9481ArLord Rayleighhttps://en.wikipedia.org/wiki/Argon
    Potassium1939.09831KHumphry Davyhttps://en.wikipedia.org/wiki/Potassium
    Calcium2040.0784CaHumphry Davyhttps://en.wikipedia.org/wiki/Calcium
    Scandium2144.9559085ScLars Fredrik Nilsonhttps://en.wikipedia.org/wiki/Scandium
    Titanium2247.8671TiWilliam Gregorhttps://en.wikipedia.org/wiki/Titanium
    Vanadium2350.94151VAndrés Manuel del Ríohttps://en.wikipedia.org/wiki/Vanadium
    Chromium2451.99616CrLouis Nicolas Vauquelinhttps://en.wikipedia.org/wiki/Chromium
    Manganese2554.9380443MnTorbern Olof Bergmanhttps://en.wikipedia.org/wiki/Manganese
    Iron2655.8452Fe5000 BChttps://en.wikipedia.org/wiki/Iron
    Cobalt2758.9331944CoGeorg Brandthttps://en.wikipedia.org/wiki/Cobalt
    Nickel2858.69344NiAxel Fredrik Cronstedthttps://en.wikipedia.org/wiki/Nickel
    Copper2963.5463CuMiddle Easthttps://en.wikipedia.org/wiki/Copper
    Zinc3065.382ZnIndiahttps://en.wikipedia.org/wiki/Zinc
    Gallium3169.7231GaLecoq de Boisbaudranhttps://en.wikipedia.org/wiki/Gallium
    Germanium3272.6308GeClemens Winklerhttps://en.wikipedia.org/wiki/Germanium
    Arsenic3374.9215956AsBronze Agehttps://en.wikipedia.org/wiki/Arsenic
    Selenium3478.9718SeJöns Jakob Berzeliushttps://en.wikipedia.org/wiki/Selenium
    Bromine3579.904BrAntoine Jérôme Balardhttps://en.wikipedia.org/wiki/Bromine
    Krypton3683.7982KrWilliam Ramsayhttps://en.wikipedia.org/wiki/Krypton
    Rubidium3785.46783RbRobert Bunsenhttps://en.wikipedia.org/wiki/Rubidium
    Strontium3887.621SrWilliam Cruickshank (chemist)https://en.wikipedia.org/wiki/Strontium
    Yttrium3988.905842YJohan Gadolinhttps://en.wikipedia.org/wiki/Yttrium
    Zirconium4091.2242ZrMartin Heinrich Klaprothhttps://en.wikipedia.org/wiki/Zirconium
    Niobium4192.906372NbCharles Hatchetthttps://en.wikipedia.org/wiki/Niobium
    Molybdenum4295.951MoCarl Wilhelm Scheelehttps://en.wikipedia.org/wiki/Molybdenum
    Technetium4398TcEmilio Segrèhttps://en.wikipedia.org/wiki/Technetium
    Ruthenium44101.072RuKarl Ernst Claushttps://en.wikipedia.org/wiki/Ruthenium
    Rhodium45102.905502RhWilliam Hyde Wollastonhttps://en.wikipedia.org/wiki/Rhodium
    Palladium46106.421PdWilliam Hyde Wollastonhttps://en.wikipedia.org/wiki/Palladium
    Silver47107.86822Agunknown, before 5000 BChttps://en.wikipedia.org/wiki/Silver
    Cadmium48112.4144CdKarl Samuel Leberecht Hermannhttps://en.wikipedia.org/wiki/Cadmium
    Indium49114.8181InFerdinand Reichhttps://en.wikipedia.org/wiki/Indium
    Tin50118.7107Snunknown, before 3500 BChttps://en.wikipedia.org/wiki/Tin
    Antimony51121.7601Sbunknown, before 3000 BChttps://en.wikipedia.org/wiki/Antimony
    Tellurium52127.603TeFranz-Joseph Müller von Reichensteinhttps://en.wikipedia.org/wiki/Tellurium
    Iodine53126.904473IBernard Courtoishttps://en.wikipedia.org/wiki/Iodine
    Xenon54131.2936XeWilliam Ramsayhttps://en.wikipedia.org/wiki/Xenon
    Cesium55132.905451966CsRobert Bunsenhttps://en.wikipedia.org/wiki/Cesium
    Barium56137.3277BaCarl Wilhelm Scheelehttps://en.wikipedia.org/wiki/Barium
    Lanthanum57138.905477LaCarl Gustaf Mosanderhttps://en.wikipedia.org/wiki/Lanthanum
    Cerium58140.1161CeMartin Heinrich Klaprothhttps://en.wikipedia.org/wiki/Cerium
    Praseodymium59140.907662PrCarl Auer von Welsbachhttps://en.wikipedia.org/wiki/Praseodymium
    Neodymium60144.2423NdCarl Auer von Welsbachhttps://en.wikipedia.org/wiki/Neodymium
    Promethium61145PmChien Shiung Wuhttps://en.wikipedia.org/wiki/Promethium
    Samarium62150.362SmLecoq de Boisbaudranhttps://en.wikipedia.org/wiki/Samarium
    Europium63151.9641EuEugène-Anatole Demarçayhttps://en.wikipedia.org/wiki/Europium
    Gadolinium64157.253GdJean Charles Galissard de Marignachttps://en.wikipedia.org/wiki/Gadolinium
    Terbium65158.925352TbCarl Gustaf Mosanderhttps://en.wikipedia.org/wiki/Terbium
    Dysprosium66162.5001DyLecoq de Boisbaudranhttps://en.wikipedia.org/wiki/Dysprosium
    Holmium67164.930332HoMarc Delafontainehttps://en.wikipedia.org/wiki/Holmium
    Erbium68167.2593ErCarl Gustaf Mosanderhttps://en.wikipedia.org/wiki/Erbium
    Thulium69168.934222TmPer Teodor Clevehttps://en.wikipedia.org/wiki/Thulium
    Ytterbium70173.0451YbJean Charles Galissard de Marignachttps://en.wikipedia.org/wiki/Ytterbium
    Lutetium71174.96681LuGeorges Urbainhttps://en.wikipedia.org/wiki/Lutetium
    Hafnium72178.492HfDirk Costerhttps://en.wikipedia.org/wiki/Hafnium
    Tantalum73180.947882TaAnders Gustaf Ekeberghttps://en.wikipedia.org/wiki/Tantalum
    Tungsten74183.841WCarl Wilhelm Scheelehttps://en.wikipedia.org/wiki/Tungsten
    Rhenium75186.2071ReMasataka Ogawahttps://en.wikipedia.org/wiki/Rhenium
    Osmium76190.233OsSmithson Tennanthttps://en.wikipedia.org/wiki/Osmium
    Iridium77192.2173IrSmithson Tennanthttps://en.wikipedia.org/wiki/Iridium
    Platinum78195.0849PtAntonio de Ulloahttps://en.wikipedia.org/wiki/Platinum
    Gold79196.9665695AuMiddle Easthttps://en.wikipedia.org/wiki/Gold
    Mercury80200.5923Hgunknown, before 2000 BCEhttps://en.wikipedia.org/wiki/Mercury (Element)
    Thallium81204.38TlWilliam Crookeshttps://en.wikipedia.org/wiki/Thallium
    Lead82207.21PbMiddle Easthttps://en.wikipedia.org/wiki/Lead_(element)
    Bismuth83208.980401BiClaude François Geoffroyhttps://en.wikipedia.org/wiki/Bismuth
    Polonium84209PoPierre Curiehttps://en.wikipedia.org/wiki/Polonium
    Astatine85210AtDale R. Corsonhttps://en.wikipedia.org/wiki/Astatine
    Radon86222RnFriedrich Ernst Dornhttps://en.wikipedia.org/wiki/Radon
    Francium87223FrMarguerite Pereyhttps://en.wikipedia.org/wiki/Francium
    Radium88226RaPierre Curiehttps://en.wikipedia.org/wiki/Radium
    Actinium89227AcFriedrich Oskar Gieselhttps://en.wikipedia.org/wiki/Actinium
    Thorium90232.03774ThJöns Jakob Berzeliushttps://en.wikipedia.org/wiki/Thorium
    Protactinium91231.035882PaWilliam Crookeshttps://en.wikipedia.org/wiki/Protactinium
    Uranium92238.028913UMartin Heinrich Klaprothhttps://en.wikipedia.org/wiki/Uranium
    Neptunium93237NpEdwin McMillanhttps://en.wikipedia.org/wiki/Neptunium
    Plutonium94244PuGlenn T. Seaborghttps://en.wikipedia.org/wiki/Plutonium
    Americium95243AmGlenn T. Seaborghttps://en.wikipedia.org/wiki/Americium
    Curium96247CmGlenn T. Seaborghttps://en.wikipedia.org/wiki/Curium
    Berkelium97247BkLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Berkelium
    Californium98251CfLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Californium
    Einsteinium99252EsLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Einsteinium
    Fermium100257FmLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Fermium
    Mendelevium101258MdLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Mendelevium
    Nobelium102259NoJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Nobelium
    Lawrencium103266LrLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Lawrencium
    Rutherfordium104267RfJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Rutherfordium
    Dubnium105268DbJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Dubnium
    Seaborgium106269SgLawrence Berkeley National Laboratoryhttps://en.wikipedia.org/wiki/Seaborgium
    Bohrium107270BhGesellschaft für Schwerionenforschunghttps://en.wikipedia.org/wiki/Bohrium
    Hassium108269HsGesellschaft für Schwerionenforschunghttps://en.wikipedia.org/wiki/Hassium
    Meitnerium109278MtGesellschaft für Schwerionenforschunghttps://en.wikipedia.org/wiki/Meitnerium
    Darmstadtium110281DsGesellschaft für Schwerionenforschunghttps://en.wikipedia.org/wiki/Darmstadtium
    Roentgenium111282RgGesellschaft für Schwerionenforschunghttps://en.wikipedia.org/wiki/Roentgenium
    Copernicium112285CnGesellschaft für Schwerionenforschunghttps://en.wikipedia.org/wiki/Copernicium
    Nihonium113286NhRIKENhttps://en.wikipedia.org/wiki/Ununtrium
    Flerovium114289FlJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Flerovium
    Moscovium115289McJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Ununpentium
    Livermorium116293LvJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Livermorium
    Tennessine117294TsJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Tennessine
    Oganesson118294OgJoint Institute for Nuclear Researchhttps://en.wikipedia.org/wiki/Oganesson
    Ununennium119315UueGSI Helmholtz Centre for Heavy Ion Researchhttps://en.wikipedia.org/wiki/Ununennium

    Each column in the table is a field of each record in the datasets.

    Let's suppose you want to compare the sizes of the planets. First load the planets dataset.

    planets << dataset("planets")
    namemean_radiusorbital_radiusorbital_period
    Mercury24400.38710.2408
    Venus60520.72330.6152
    Earth637111
    Mars33901.52731.8809
    Jupiter699115.202811.862
    Saturn582329.538829.458
    Uranus2536219.191484.01
    Neptune2462230.0611164.79

    Now add a chart to draw the planets.

    planets << dataset("planets") chart(planets)

    Hmm. That doesn't look right. Chart doesn't know what part of the planets dataset we want to draw. We have to tell it. Let's use mean_radius for the height of the bar chart. For the label under each bar we can use the name property. We can tell the chart function what to do using the named arguments x_label and y .

    Now let's compare the radius of the orbit to the radius of the planet. This will show us if the smaller planets are clustered together or spread out.

    planets << dataset("planets") chart(planets, type : "scatter", x : "orbital_radius", y : "mean_radius")

    Now lets make the size of the circle represent the size of the planet and show the names

    planets << dataset("planets") chart(planets, type : "scatter", x : "orbital_radius", y : "mean_radius", size : "mean_radius", name : "name")

    Here's a fun one. Let's see which letters have one syllable vs two.

    //TODO: this is broken when doing builddocs chart(dataset('letters'), y_value:'syllables')

    Let's check out the relative heights of the tallest buildings in the world:

    buildings << dataset("tallest_buildings") b2 << take(buildings, 5) chart(b2, y : "height", x : "name")

    Histograms

    states << dataset("states") first_letter << (state : ?)->{take(get_field(state, "name"), 1)} states << map(states, first_letter) histogram(states)

    Statehood date by year by decade

    states << dataset("states") get_year << state-> field << get_field(state, "statehood_date") dt << date(field, format : "MMMM dd, yyyy") get_field(dt, "year") years << map(states, with : get_year) histogram(years, bucket : 10)

    Random Numbers

    Filament can generate random numbers for you. Now you might wonder why this is useful. It turns out random numnbers are incredibly useful for all sorts of things, from drawing graphics to simulations, and of course they are heavily used in video games.

    To make a random number just call random

    random()
    0.568

    Every time you run this function it will return a different number.

    By default the number will always be between 0 and 1, but you can choose a different max and min if you want. Suppose you want twenty random numbers between 5 and 10 you could do this:

    range(20) >> map(with : n->random(min : 5, max : 10))
    8.416, 5.059, 6.922, 9.190, 8.431, 5.786, 8.621, 8.192, 5.903, 5.280, 5.415, 6.291, 5.616, 8.128, 5.377, 5.691, 9.626, 6.747, 7.131, 5.057

    Mapping a range is an easy way to create a list, then call random on each one to make the final numbers. However, making lists random numbers is so common you can just call random directly using count .

    random(min : 5, max : 10, count : 20)
    6.921, 8.424, 8.157, 9.260, 6.057, 7.271, 5.732, 7.119, 9.913, 8.501, 9.278, 9.445, 5.478, 7.077, 9.210, 7.632, 5.488, 5.918, 9.123, 8.844

    Making Shapes

    Filament makes it easy to draw shapes. Let's start with some simple squares.

    Drawing Squares

    To create a square call the rect function with a width and height, then send it into the draw() function to show up on the screen.

    rect(width : 100, height : 100) >> draw()

    By default the shape is the upper left, but you can change the x and y to put it wherever you want.

    rect(x : 200, y : 100, width : 100, height : 100) >> draw()

    If you want to draw more than one shape, just put them into a list. You can even use units for the size of your shapes. Below are two rectangles, one in centimeters and one in inches. If you don't include any unit then Filament assumes it is in pixels.

    [rect(x : 0, width : 1cm, height : 5cm),rect(x : 50, width : 1in, height : 1in)] >> draw()

    Along with size, you can also set the color of shapes using the fill argument. You can set it to named colors as strings like "red" , "green" , and "green" or use a list of three numbers between 0 and 1. These will be interpreted as RGB values.

    so

    rect(width : 10cm, height : 10cm, fill : "blue") >> draw()

    is the same as

    rect(width : 10cm, height : 10cm, fill : [0,0,1]) >> draw()

    You can draw multiple shapes by putting them into a list, but then they might draw on top of each other unless you manually set their x,y positions. Since it's common to want to put shapes next to each other, you can use the row function to space them out, with an optional gap parameter

    [rect(width : 10cm, height : 20cm, fill : "red"),rect(width : 10cm, height : 20cm, fill : "green"),rect(width : 10cm, height : 20cm, fill : "blue")] >> row(gap : 1cm) >> draw()

    Can you guess what column does? :)

    This means you could make your own bar chart from scratch!

    range(5) >> map(with : n->rect(width : 1cm, height : n * 5cm)) >> row(valign : "bottom", halign : "center") >> draw()

    Other Shapes

    circle

    circle(x : 4cm, y : 4cm, radius : 2cm, fill : "aqua") >> draw()

    making art

    Make some cool artwork with random numbers

    make << (n)->{ rect(x : random() * 200, y : random() * 200, width : 100, height : 100, fill : [n / 100,random(),0]) } range(100) >> map(with : make) >> draw()

    Random Colors

    Now let's use it to make some random colors. First I want to give Martin Ankerl credit for his original article on making pretty random colors. Thanks Martin.

    In Filament, colors are made using a list of three numbers, each from 0 to 1. They represent the red, green, and blue (RGB) parts of the color. Thus [0,1,0] would be pure green and [1,1,1] is pure white.

    Let's make 20 randomly colored squares.

    make_square << ()->{ rect(width : 100, height : 100, fill : [random(),random(),random()]) } range(50) >> map(with : make_square) >> row(gap : 0) >> draw()

    Hmm. Those are pretty ugly. Some colors are too dark. Some are too similar. The problem is that we are using the RGB color space . We don't have a way to make the colors more similar easily. However, there is another color space we can use called HSL for Hue, Saturation, and Lightness. This way we can have a single number for the hue (the 'color'), and keep the saturation and lightness fixed. Then we can convert them back to RGB to draw them.

    For the conversion we have a handy function called HSL_TO_RGB function. It takes the hue, saturation, and lightness the 0-1 range, returns RGB in then 0-1 range.

    make_square << ()->{ color << [random(),0.8,0.9] rect(width : 100, height : 100, fill : HSL_TO_RGB(color)) } range(50) >> map(with : make_square) >> row(gap : 0) >> draw()

    In the code above we choose a random hue but keep the saturation at 0.8 (sort of pastel like) and the lightness almost at 100%.

    Let's try that again with different S and L values. Lower the saturation to 0.5, meaning it's partly gray, and move lightness to 50% (0.5)

    make_square << ()->{ color << [random(),0.5,0.5] rect(width : 100, height : 100, fill : HSL_TO_RGB(color)) } range(50) >> map(with : make_square) >> row(gap : 0) >> draw()

    These look better. Each color is the same distance from black and white, so we are getting some nice hues. However, it's still pretty random. You'll notice that sometimes the colors clump together. Instead we'd like to have them distributed evenly across the hue.

    There's a clever trick we can use with the golden ratio. Start with a random number then add the golden ratio (1.618) to it. Each time we wrap it back around at 1. This works because the hue is like a circle. When you go far enough to the right edge it wraps back around. Thanks to the golden ratio it will keep looping around and always pick a new hue value distant from the previous ones.

    Note that I'm using the Golden Ratio Conjugate, which is the reciprical of the Golden Ratio. Also note that Filament doesn't have loops yet, so I'm using a recursive version.

    GRC << 0.618033988749895 make_square << (depth,h)->{ color << [h,0.5,0.5] ifdepth <=0 then rect(width : 100, height : 100, fill : HSL_TO_RGB(color)) else [rect(width : 100, height : 100, fill : HSL_TO_RGB(color)),make_square(depth - 1, h + GRC)] } make_square(50, random()) >> row(gap : 0) >> draw()

    There we go. That looks great! Why don't you try it yourself and change the saturation and lightness values.

    Images

    New Images from Scratch

    Filament can create images with colors, just like shapes. The difference is that an image is made of pixels and you must specify a width and height.

    make_image(width : 100, height : 100) >> map_image(with : (x,y)->{ [1,0,0] })

    The code above creates an image filled with rect. The map_image calls the function you pass to it for every pixel. This function should return a new color for that pixel. Colors are represented as RGB, in the range 0 to 1, just like with shapes.

    This means red << [1,0,0] , blue << [0,0,1] , and gray << [0.5,0.5,0.5] or [50%, 50%, 50%]

    Let's make another image where instead of using a single color, we calculate a new random grayscale value for each pixel.

    make_image(width : 100, height : 100) >> map_image(with : (x,y,color)->{ n << random(min : 0, max : 1) [n,n,n] })

    So far we have ignored the x and y values, but if we use them we can create patterns that vary over space. Let's make stripes where teh color changes based on if the x value is even or odd.

    red << [1,0,0] green << [0,1,0] make_image(width : 100, height : 100) >> map_image(with : (x,y,color)->{ ifx mod2 =0 then red else green })

    We can even draw shapes pixel by pixel using implict equations. For example if let's calculate the distance of a pixel from the origin. If it's greater than some threshold then make it be red, otherwise green. This is similar to how GPU pixel shaders work.

    dist << (x,y)->{sqrt(x ** 2 + y ** 2)} make_image(width : 100, height : 100) >> map_image(with : (x,y,c)->{ ifdist(x, y) <100 then[0,1,0]else[1,0,0] })

    Loading Images from the Web

    We can load an image from the web using the load_image function. filament load_image(src:'https://vr.josh.earth/webxr-experiments/nonogram/thumb.png')

    Once we have the image we can modify it. Let's drop the red channel by returning a new color that only includes the green and blue values from the original pixels.

    load_image(src : "https://vr.josh.earth/webxr-experiments/nonogram/thumb.png") >> mapimage(with : (x,y,c)->{ v1 << 0 v2 << (c[1]) v3 << (c[2]) [v1,v2,v3] })

    We can also make an image grayscale by averaging the colors.

    load_image(src : "https://vr.josh.earth/webxr-experiments/nonogram/thumb.png") >> mapimage(with : (x,y,c)->{ n << sum(c) / 3 [n,n,n] })

    Converting to grayscale by averaging the red green and blue does work, but it doesn't loook very good. Taht's because it doesn't acocunt for the fact that the human eye is more sensitive to blue than to green or red. Let's create a few functions to make it work better.

    first we need brightness base on a more accurate grayscale conversion

    brightness << (c)->{c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114}
    UNKNOWN

    Now let's create a function to mix two colors together using a t value that goes from 0 to one. If t is 0 then you get only the first color. If it's 1 then you get only the second color. Otherwise you get a mixture of hte two. Sometimes this is called a lerp function, short for interpolate.

    mix << (t,a,b)->{a * t + b * (1 - t)}
    UNKNOWN

    Now that we can mix two colors, we can use black and white for grayscale. But we could also use white and brown for a sepia town, or even something crazier like red and blue.

    brightness << (c)->{c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114} mix << (t,a,b)->{a * t + b * (1 - t)} white << [1,1,1] brown << [0.5,0.4,0.1] sepia << (x,y,color)->{ brightness(color) >> mix(white, brown) } load_image(src : "https://vr.josh.earth/webxr-experiments/nonogram/thumb.png") >> mapimage(with : sepia)
    brightness << (c)->{c[0] * 0.299 + c[1] * 0.587 + c[2] * 0.114} mix << (t,a,b)->{a * t + b * (1 - t)} red << [1,0,0] blue << [0,0,1] rb << (x,y,color)->{ brightness(color) >> mix(red, blue) } load_image(src : "https://vr.josh.earth/webxr-experiments/nonogram/thumb.png") >> mapimage(with : rb)

    Turtle Graphics

    Based on this tutorial. link

    Turtle graphics is a different way of generating images than setting pixels or drawing shapes on the cartesian plane. Instead of saying "put a shape here", you have a turtle with a pen which draws the shapes for you. I know that sounds confusing, so let's start with some examples.

    How Turtles Move

    Image a turtle walking around in an infinite plane. Starts at the center and is pointed north. You can tell turtle to move forward or to turn. to start, let's tell it to move forward by 100 pixels.

    turtle_start(0, 0, 0) turtle_pendown() turtle_forward(100) turtle_done()

    Now let's tell it to turn right and move forward another hundred.

    turtle_start(0, 0, 0) turtle_pendown() turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_done()

    Now we have an L shape. The turtle is carrying a pen, so every where it moves it draws a line.

    Now let's make a square by doing it four times.

    turtle_start(0, 0, 0) turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_right(90) turtle_forward(100) turtle_right(90) turtle_done()

    Now we have a square. Of course, writing 'right' 'forward' over and over is annoying. Instead let's use a loop to do it four times.

    turtle_start(0, 0, 0) range(4) >> map(with : ()->{ turtle_forward(100) turtle_right(90) }) turtle_done()

    Much better.

    Shapes

    Now that we know how to make a square, we can clearly see how to draw a hexagon. Loop 6 times and turn right by 60 degrees

    turtle_start(0, 0, 0) range(6) >> map(with : ()->{ turtle_forward(100) turtle_right(60) }) turtle_done()

    We can start to see a pattern. For any regular polygon with N sides we loop N times and turn by 360/N. Let's make some code to draw any sort of polygon. We can change the value of N to draw a different shape. Here it is for an octogon.

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } ngon(8, 60) turtle_done()

    If we change N to 100 and size to 10 it looks like a circle.

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } ngon(100, 10) turtle_done()

    Colors

    What's even cooler than drawing a shape from lines, is drawing a shape over and over. Let's go back to drawing a square, but do it over and over with a slight rotation.

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } range(50) >> map(with : ()->{ ngon(4, 100) turtle_right(10) }) turtle_done()

    Woah!

    Now let's switch colors each time with turtle_pencolor()

    turtle_start(0, 0, 0) ngon << (N,side)->{ range(N) >> map(with : ()->{ turtle_forward(side) turtle_right(360 / N) }) } range(50) >> map(with : (i)->{ ngon(4, 100) turtle_right(10) t << (i / 50) turtle_pencolor([0,1 - t,t]) }) turtle_done()

    Turtle graphics is a really fun way to draw shapes incrementally. What we've done is define a small thing, a side, then repeat it to make a shape, then repeat that to make a more complex shape.

    Complex Shapes

    For our final example let's draw a flower. How could we do this? Why don't we start with a petal, figure that out, then draw it a bunch of times to make the flower. But how do we draw a petal? By breaking it down into even smaller pieces.

    We learned to draw a curve by doing lots of short lines. Let's do just a partial circle.

    turtle_start(0, 0, 0) arc << ()->{ map(range(120), with : (n)->{ turtle_forward(2) turtle_right(1) }) } arc() turtle_done()

    Perfect. Now we can rotate and do it again

    turtle_start(0, 0, 0) turtle_pendown() arc << ()->{ map(range(120), with : (n)->{ turtle_forward(2) turtle_right(1) }) } leaf << ()->{ arc() turtle_right(60) arc() turtle_right(60) } leaf() turtle_done()

    Now we have a full petal. To get a complete flower we just need to do it a bunch of times.

    turtle_start(0, 0, 0) turtle_pendown() arc << ()->{ map(range(120), with : (n)->{ turtle_forward(2) turtle_right(1) }) } leaf << ()->{ arc() turtle_right(60) arc() turtle_right(60) } range(36) >> map(with : ()->{ leaf() turtle_right(10) }) turtle_penup() turtle_done()

    Now we have a complete flower. Most programming is like this. We build big complex things out of lots of smaller, simpler pieces.