8.5 Arrays in JSCAD / Loops

Aligned outcomes:

SLO 3

Describe, design, implement, and test structured programs using currently accepted methodology.

Learning Objectives

By the end of this section, you will be able to:

In this section, you will learn to:
  • return an array of shapes from main() to build repeated geometry;
  • use a for loop to place a row of shapes;
  • use Array.prototype.map as an alternative to a for loop;
  • arrange shapes in a circle using a loop and trigonometry;
  • declare parametric controls in a getParameterDefinitions block;
  • build a grid of shapes with two nested for loops;
  • map an array of data objects into geometry.

Because main() can return an array, ordinary JS loops and array methods are how JSCAD produces repeated or patterned geometry — no new JSCAD API is needed beyond the primitives and transforms already covered.

One return, many shapes

main() does not have to return a single shape. Return an array of shapes and JSCAD shows them all at once. That one fact is what lets you build rows, grids, and rings with plain JavaScript.

Returning an array of shapes

The key idea is that main() can return an array. Each element of the array is one geometry, and JSCAD renders them all together. To build a pattern, you collect shapes into an array and return it.

Arrays are the pattern tool

A loop creates many shapes, and an array holds them. Return the array and you have a pattern. No special JSCAD function is needed — just the JavaScript you already know.

Example 8.5.1: Returning two shapes at once

The fact that main() can return an array is the whole basis of repeated geometry. Build a main that returns two circles — one at the origin, one shifted right. Each comment below is one line for you to write. Press Run when you are done.

Editor
renders in your browser

Solution
Editor
renders in your browser

You should see two radius-10 circles, the second sitting 25 units to the right. The array is what lets one main draw many shapes. Change the offset to [25, 25] and run again — the second circle moves diagonally instead.

Try It Now 8.5.1

Write a main() function that returns an array containing two circles: one of radius 10 at the origin and one of radius 10 translated to (25, 0).

Editor
renders in your browser

Solution

Step 1 — import the functions. We need circle from primitives and translate from transforms.

Step 2 — build the two circles. The first is circle({ radius: 10 }). The second is translate([25, 0], circle({ radius: 10 })).

Step 3 — return them in an array.

Editor
renders in your browser

Answer: The script above returns two radius-10 circles, the second shifted 25 units to the right.

A row with a for loop

A for loop is the most direct way to build a row of shapes. Start with an empty array, push one translated shape per loop step, then return the array.

Editor
renders in your browser

The loop is the repetition

Each pass of the loop places one circle. The counter i controls the position — i * 25 spaces the circles 25 units apart, so the loop turns one shape into a whole row.

Example 8.5.2: A row with a for loop

A for loop turns one shape into a whole row: each pass builds a circle, positions it with the counter, and pushes it onto an array. Each comment below is one line for you to write. Press Run when you are done.

Editor
renders in your browser

Solution
Editor
renders in your browser

You should see a row of 5 radius-10 circles, each 25 units to the right of the last. The counter i is what spaces them: i 25 puts circle 0 at 0, circle 1 at 25, and so on. Change the step to i 50 and run again — the row stretches out twice as wide.

Try It Now 8.5.2

Write a main() function that uses a for loop to build a row of 4 circles, each of radius 10, spaced 30 units apart along the x-axis.

Editor
renders in your browser

Solution

Step 1 — import the functions. We need circle from primitives and translate from transforms.

Step 2 — start an empty array. const shapes = [].

Step 3 — loop 4 times. For i from 0 to 3, push a circle translated by i * 30 along x.

Step 4 — return the array.

Editor
renders in your browser

Answer: The script above returns a row of 4 radius-10 circles, each 30 units to the right of the last.

The same row with map

The same row, written with Array.prototype.map instead of a for loop:

Editor
renders in your browser

Map is a loop in disguise

map runs a function on every element of an array and collects the results. Here the array is the list of positions, and the function builds a circle at each one — the same work as the for loop, in one expression.

Try It Now 8.5.3

Rewrite the row of 4 circles from Try It Now 8.25 using Array.prototype.map instead of a for loop.

Editor
renders in your browser

Solution

Step 1 — build the list of positions. We want 4 circles, so the array is [0, 1, 2, 3].

Step 2 — map each position to a shape. For each i, translate a radius-10 circle by i * 30 along x.

Editor
renders in your browser

Answer: [0, 1, 2, 3].map(i => translate([i * 30, 0], circle({ radius: 10 }))) — the same row of 4 circles, built with map.

A circular arrangement

A circular arrangement combines a loop with trigonometry — the same Math.cos/Math.sin pattern used for any circular layout in JS. Because main(params) reads params.count and params.ringRadius, those parameters must be declared first in a getParameterDefinitions block — otherwise jscad.app hands main an empty parameter set and the viewport stays empty. A parameter definition gives each control a name, a default initial value, and a sensible min/max range the user can drag through:

Editor
renders in your browser

Trig places things on a ring

To put shapes in a circle, you need an x and y for each one. Math.cos and Math.sin turn an angle into those coordinates, and the loop steps the angle around a full turn so the shapes land evenly on the ring.

Try It Now 8.5.4

Write a main(params) function that arranges params.count circles of radius 5 evenly around a ring of radius params.ringRadius, using a for loop and trigonometry. Declare both parameters in a getParameterDefinitions block first.

Editor
renders in your browser

Solution

Step 1 — declare the parameters. Add a getParameterDefinitions block that names count and ringRadius, giving each an initial, min, and max. Without it, main(params) reads undefined and renders nothing.

Step 2 — read the parameters. count is the number of circles and ringRadius is the ring's radius.

Step 3 — loop over each position. For each i, compute the angle as (i / count) Math.PI 2.

Step 4 — compute x and y. x = ringRadius Math.cos(angle) and y = ringRadius Math.sin(angle).

Step 5 — translate and push a circle. Push translate([x, y], circle({ radius: 5 })) into the array.

Step 6 — return the array.

Editor
renders in your browser

Answer: The function above declares count and ringRadius, then returns count radius-5 circles spaced evenly around a ring of radius ringRadius.

A grid with nested loops

A row is one loop over one dimension. A grid is two loops — one over rows, one over columns — with the inner loop nested inside the outer one. Each pass of the outer loop runs the entire inner loop, so the inner loop places a whole row before the outer loop advances to the next row. As with the ring, the parameters that size the grid are declared in a getParameterDefinitions block so main(params) always receives real numbers:

Editor
renders in your browser

The inner variable must not leak

Each loop declares its own counter — let row outside, let col inside. Because col is scoped to the inner loop, it is re-created on every pass of the outer loop and can never leak out or clash with row. If both loops reused the same variable name, the counters would overwrite each other and the grid would collapse into a diagonal or a single row.

Try It Now 8.5.5

Write a main(params) function that uses two nested for loops to build a grid of circles, one circle per cell, spaced 40 units apart in both directions. Declare rows and cols in a getParameterDefinitions block first.

Editor
renders in your browser

Solution

Step 1 — declare the parameters. Add a getParameterDefinitions block naming rows and cols, each with an initial, min, and max.

Step 2 — start the outer loop. For each row from 0 up to params.rows, run an inner loop.

Step 3 — start the inner loop. For each col from 0 up to params.cols, place one circle.

Step 4 — position each circle. Translate by [col 40, row 40] so cells land 40 units apart in both directions, and push the result.

Step 5 — return the array.

Editor
renders in your browser

Answer: The function above declares rows and cols, then returns rows by cols circles spaced 40 units apart in a grid.

Building shapes from data

Real parametric models rarely build each shape by hand — they map an array of data objects into geometry. Each object carries the numbers one shape needs (an x, a y, and a radius r), and one map call turns the whole array into an array of shapes. That is the pattern every real parametric model uses: data first, shapes second.

Editor
renders in your browser

Data shapes the design

The data objects hold every number that varies — position and radius — and the geometry is a pure function of that data. To change the model you edit the data, not the drawing code. That separation is what makes parametric design scalable: dozens of shapes from a few lines of logic.

Try It Now 8.5.6

Write a main() function that maps an array of data objects [{x, y, r}, ...] into circles, placing each circle at its x/y position with radius r. Use three data objects with different radii.

Editor
renders in your browser

Solution

Step 1 — define the data. Start an array of objects, each holding an x, a y, and a radius r. Use different radii so the shapes vary.

Step 2 — map the data into shapes. For each object d, translate a circle({ radius: d.r }) by [d.x, d.y].

Step 3 — return the result. map already returns the array of shapes, so main returns it directly.

Editor
renders in your browser

Answer: The function above maps the three data objects into three circles, each placed at its x/y position with its own radius.

Problem Set

Problem 1. Explain why main() being able to return an array is what lets JSCAD build repeated geometry.

Problem 2. Write a main() function that uses a for loop to build a row of 6 circles, each of radius 8, spaced 20 units apart along the x-axis.

Problem 3. Rewrite the row from problem 8.5.2 using Array.prototype.map instead of a for loop.

Problem 4. Write a main(params) function that arranges params.count circles of radius 4 evenly around a ring of radius params.ringRadius, using a for loop and trigonometry. Declare both parameters in a getParameterDefinitions block first.

Problem 5. Describe the role of Math.cos and Math.sin in a circular arrangement, and explain how the loop steps the angle around a full turn.

Problem 6. Write a main(params) function that uses two nested for loops to build a grid of circles spaced 25 units apart in both directions, with rows and cols declared in a getParameterDefinitions block.

Problem 7. Write a main() function that maps an array of data objects [{x, y, r}, ...] into circles, placing each circle at its x/y position with radius r.

Key Terms

array — an ordered list of values; main() can return an array of shapes to render them together.

for loop — a JavaScript loop that repeats a block of code a set number of times, often used to build a row of shapes.

nested loop — a loop placed inside another loop; the inner loop runs to completion once per pass of the outer loop, used to build a grid.

Array.prototype.map — an array method that runs a function on each element and collects the results, an alternative to a for loop.

translate — a transform that moves a geometry by an offset, such as [x, y].

trigonometry — the use of Math.cos and Math.sin to turn an angle into x and y coordinates for a circular layout.

getParameterDefinitions — a block that declares the controls main(params) reads, giving each a name, an initial value, and min/max bounds.

data object — an object holding the values one shape needs (such as x, y, and r), mapped into geometry.