Releases: scottpdo/flocc
0.4.3
0.4.2
A few bug fixes:
CanvasRenderer
options:origin
now should take aPoint
({ x: number; y: number; }
) in environment space, not screen space.CanvasRenderer
rendering:window.devicePixelRatio
now respected on non-retina and retina screens forNetwork
- Fix bug where
environment.stat
was caching multiple (different) calls to the same value
0.4.0
Version 0.4.0 introduces the flocc.Terrain
class! This is a new Environment
helper that will likely displace GridEnvironment
s in the near future, as they're much more performant and scalable and produce much more compelling visualizations than the GridEnvironment
with either an ASCIIRenderer
or CanvasRenderer
.
Terrain
A Terrain
is a 2d grid that can act as background data for Agent
s in an Environment
. Under the hood, data is stored in a Uint8ClampedArray
, keeping values in the range 0-255, making it ideal for drawing to canvases. Like other helpers, a terrain should be instantiated and then added to its environment with environment.use
, like this:
const [width, height] = 500, 500;
const environment = new Environment({ width, height });
const terrain = new Terrain(width, height);
environment.use(terrain);
Color Modes
Terrains can operate in either color (default) or grayscale modes. Grayscale mode can be switched on by instantiating a terrain with this configuration option:
const terrain = new Terrain(width, height, { grayscale: true });
Depending on if a terrain is in color or grayscale mode, coordinate values will look different. For color mode, a coordinate value is represented by a pixel-like object with r
, g
, b
, and a
keys (red, green, blue, and alpha/opacity, respectively). For example, if the coordinate at (100, 150)
is completely red, it may look like this:
const pixel = terrain.sample(100, 150);
pixel.r; // 255 = full red
pixel.g; // 0 = no green
pixel.b; // 0 = no blue
pixel.a; // 255 = fully opaque (not transparent)
In grayscale mode, coordinate values are just numbers in the range 0-255. It is assumed that they are fully opaque (not transparent). If coordinate (10, 10)
is white, (10, 15)
is gray, and (10, 20)
is black, the values will look like this:
const white = terrain.sample(10, 10); // 255
const gray = terrain.sample(10, 15); // 127
const black = terrain.sample(10, 20); // 0
Like Agent
s, a Terrain
can have an update rule (a function) that is called once for every (x, y)
coordinate in it (within the ranges x: 0 → width - 1
y: 0 → height - 1
). Once the environment is using the terrain as a helper, the update rule is called with every environment.tick
. There are two possible modes, depending if coordinates only modify themselves or if they modify others — synchronous and asynchronous, respectively.
Synchronous Mode
Terrains default to synchronous mode, where all coordinate updates happen simultaneously. In order to update coordinate values, the update rule has to return a new value — either a pixel-like object or a number, depending on the color mode. In synchronous mode, a coordinate can only update its own value (although it can do so based on neighboring coordinate values).
// turns every coordinate completely red if the x-value is
// greater than 200, completely blue if the x-value is less
// than 100, and leaves them unchanged in between
terrain.addRule((x, y) => {
if (x > 200) {
return {
r: 255,
g: 0,
b: 0,
a: 255
}
} else if (x < 100) {
return {
r: 0,
g: 0,
b: 255,
a: 255
}
}
});
Asynchronous mode
A terrain can be instantiated in async mode by passing an additional configuration parameter:
const terrain = new Terrain(width, height, { async: true });
In async mode, during an update rule, a coordinate may update not only its own value, but those of its neighbors. Async mode is slightly less performant (it runs slower), but may allow for more complex update rules.
terrain.addRule((x, y) => {
// if the coordinate's red value is greater than 100,
// increments its right neighbor's blue value
if (terrain.sample(x, y).r > 100) {
const neighborPixel = terrain.sample(x + 1, y);
terrain.set(x + 1, y, {
r: neighborPixel.r,
g: neighborPixel.g,
b: neighborPixel.b + 1,
a: neighborPixel.a
})
}
});
Find full documentation on flocc.network!
0.3.17
0.3.15
New options for CanvasRenderer
(when visualizing connections on a Network
)
connectionColor
(default"black"
)connectionOpacity
(default1
)connectionWidth
(default1
)
Also fixes visualization on retina devices.
Other under-the-hood improvements:
environment.stat
filters outnull
valuesLineChartRenderer
andHistogram
now useenvironment.stat
to cache data from all agents
0.3.14
Environment
Adds two new Environment
methods: .stat
and .memo
.
environment.stat('key')
returns an array of data associated withagent.get(key)
across all agents in the environment. This is a shorthand for callingenvironment.getAgents().map(a => a.get(key))
. It defaults to caching the result and using it for future calls within the same environment tick. However, this behavior can be turned off (for example, if agents are updating synchronously and the data should reflect this) by passingfalse
as a second parameter:environment.stat('synchronousKey', false)
- Calling
environment.memo(function() { return 'someExpensiveValue' })
will return'someExpensiveValue'
and then cache and use that result for future calls within the same environment tick. The function that is passed as the only parameter is called only the first time.memo
is used within an environment tick, and then its return value is used for future calls, until the next environment tick.
Utils
utils.percentile(arr, n)
calculates the n-th percentile value of an array of numbers, withn
a number between0
(0-th percentile, or minimum value) and1
(100th percentile, or maximum value). If a percentile falls between discrete values of the array, it linearly interpolates between those values.
0.3.13
Updated to utils.sample
to allow for weighted sampling of elements from an array. Now you can pass a second parameter to sample
, an array of weights (numbers) that will allow certain values to be picked more or less often. Given an array of three elements, if you want the first to be chosen about 10% of the time, the second about 15%, and the third about 75%, you can do this:
const arr = ["1st value", "2nd value", "3rd value"];
const randomValue = utils.sample(arr, [10, 15, 75]);
Although in the above example the weights add up to 100, this is just to make the code easier to read and reason about. The weights can add up to anything and it will still calculate the percentage based off of the sum of the weights — for example, weights [1, 1, 3]
map to 20%, 20%, and 60%, respectively.
0.3.12
Streamlining the API for Agent
s. Agent
s can now be instantiated with data, as opposed to instantiating and then setting data, like so:
// Before v0.3.12:
const agent = new Agent();
agent.set({
x: 1,
y: 2,
z: 3
});
// Now:
const agent = new Agent({
x: 1,
y: 2,
z: 3
});
Also, Agent
rule functions can now have a return value, which gets set asynchronously (previously this had to be done using agent.enqueue()
.
// Before v0.3.12:
function tick(agent) {
const averageSize = utils.mean(environment.getAgents().map(a => a.get('size'));
// needs to be set asynchronously, otherwise subsequent agents in
// the loop will get different values for `averageSize`
agent.enqueue(() => agent.set('size', averageSize));
}
// Now:
function tick(agent) {
const averageSize = utils.mean(environment.getAgents().map(a => a.get('size'));
// is automatically set asynchronously
return {
size: averageSize
});
}
0.3.11
Improved LineChartRenderer
visualization — charts now update scale, size, etc. to fit data over time, rather than being fixed at time of instantiation.
New configuration options:
autoScale
(defaults tofalse
): If set totrue
, theLineChartRenderer
will attempt to fit any data that goes out-of-bounds (vertically above or below the given range, or horizontally beyond the width of the chart) and will dynamically resize the existing visualization to fit all lines in the bounds of the chart.autoScroll
(defaults tofalse
): if set totrue
, any data that would go out-of-bound horizontally (as time passes) will push the earliest data off to the left, resulting in a horizontally scrolling view of the data.- If both
autoScale
andautoScroll
are set totrue
, then the chart will only scale vertically (for data above or below the given range) and will respect the horizontally scrolling view of the data
New configuration default:
- The
range
object now defaults to{ max: 1, min: 0 }
, rather than{ max: 500, min: 0 }
This release also introduces an abstract type, NumArray
, to store numeric data in a more performant way than native JavaScript arrays. For now this will probably just remain an internal helper.
0.3.7
New Histogram
configuration options:
buckets
parameter can now take either a single number representing the number of buckets to draw, or an array (containing anything) that will draw discrete values matching agent values, for example[true, false]
or["red", "blue"]
. Ifbuckets
is an array, themin
,max
,belowMin
, andaboveMax
options are ignored.- A new
epsilon
parameter. Ifbuckets
is an array of numbers andepsilon
is used, it will match any values withinepsilon
of the bucket values. For example, ifbuckets
is[1, 2, 3]
, with epsilon0.01
, an agent with a value1.01
would fall into bucket1
but an agent with a value1.5
would not be bucketed.
const histogram = new Histogram({
buckets: [1, 2, 3],
epsilon: 0.01
});