I compiled a go code with tinygo to WebAssembly and I don't understand why the function took 17 minutes to execute while the same function in JavaScript only took 4000 ms. what I'm doing wrong? also it is possible to modify the function in Go so I don't have to use "syscall/js"? I tried using function exports but couldn't return the array with different types to JavaScript.
Here is my Go code:
package main
import (
"fmt"
"math"
"syscall/js"
)
type p struct {
x float64
y float64
}
type z struct {
x float64
y float64
}
func mandelbrotTinyGo(_ js.Value, args []js.Value) interface{} {
maxIteration := args[0].Int()
var newZ = z{0, 0}
var newP = p{0, 0}
n := 0
cx := args[1].Float()
cy := args[2].Float()
d := 0.0
for {
newP = p{math.Pow(newZ.x, 2) - math.Pow(newZ.y, 2), 2 * newZ.x * newZ.y}
newZ = z{newP.x + cx, newP.y + cy}
d = 0.5 * (math.Pow(newZ.x, 2) + math.Pow(newZ.y, 2))
n += 1
if d >= 2 || maxIteration <= n {
break
}
}
arr := []interface{}{n, d <= 2}
return arr
}
func main() {
fmt.Println("TinyGo")
js.Global().Set("mandelbrotTinyGo", js.FuncOf(mandelbrotTinyGo))
<-make(chan bool)
}
Compiled with:
tinygo build -o tinygo.wasm -target wasm ./main.go
JavaScript code:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="wasm_exec.js"></script>
<script>
const go = new Go();
const WASM_URL = 'tinygo.wasm';
var wasm;
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
} else {
fetch(WASM_URL).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
wasm = obj.instance;
go.run(wasm);
})
)
}
</script>
</head>
<body>
<button id="btnDraw">Draw</button>
<canvas id="myCanvas" style="display: block; margin-left: auto;margin-right: auto;">
</canvas>
<script>
const MAX_ITERATION = 80
var canvas = document.getElementById('myCanvas')
var ctx = canvas.getContext('2d')
const WIDTH = window.innerWidth
const HEIGHT = window.innerHeight
ctx.canvas.width = WIDTH
ctx.canvas.height = HEIGHT
const REAL_SET = { start: -2, end: 1 }
const IMAGINARY_SET = { start: -1, end: 1 }
const colors = new Array(16).fill(0).map((_, i) => i === 0 ? '#000' : '#' + Math.random().toString(16).substr(2, 6))
function draw() {
for (let i = 0; i < WIDTH; i++) {
for (let j = 0; j < HEIGHT; j++) {
complex = {
x: REAL_SET.start + (i / WIDTH) * (REAL_SET.end - REAL_SET.start),
y: IMAGINARY_SET.start + (j / HEIGHT) * (IMAGINARY_SET.end - IMAGINARY_SET.start)
}
//Call the JS function
//const [m, isMandelbrotSet] = mandelbrot(complex)
//Call the WebAssembly/tinyGo function
const [m, isMandelbrotSet] = mandelbrotTinyGo(MAX_ITERATION, complex.x, complex.y)
ctx.fillStyle = colors[isMandelbrotSet ? 0 : (m % colors.length - 1) + 1]
ctx.fillRect(i, j, 1, 1)
}
}
}
function mandelbrot(c) {
let z = { x: 0, y: 0 }, n = 0, p, d;
do {
p = {
x: Math.pow(z.x, 2) - Math.pow(z.y, 2),
y: 2 * z.x * z.y
}
z = {
x: p.x + c.x,
y: p.y + c.y
}
d = Math.sqrt(Math.pow(z.x, 2) + Math.pow(z.y, 2))
n += 1
} while (d <= 2 && n < MAX_ITERATION)
return [n, d <= 2]
}
function start(){
let startTime = performance.now()
draw()
let endTime = performance.now()
console.log(`Call to doSomething took ${endTime - startTime} milliseconds`)
}
myButton = document.getElementById("btnDraw");
myButton.addEventListener("click", function() {
start();
});
</script>
</body>
</html>
The file wasm_exec.js must to be copied from your ..\tinygo\0.25.0\targets\ directory
tinygo version 0.25.0 windows/amd64 (using go version go1.19.1 and LLVM version 14.0.0)
Related
Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed last month.
Improve this question
I have a 2D cellular automaton open source project here, where the automaton is basically this:
export interface CA {
i: Uint8ClampedArray;
load: Uint8ClampedArray;
max: number;
move: Array<RuleType> | Array<(n: Uint8ClampedArray) => number>;
n: Uint8ClampedArray;
save: Uint8ClampedArray;
size: number;
}
export type RuleType = {
make: number;
test: Array<(r: number) => boolean>;
};
type CA2DPropsType = {
h: number;
max: number;
move: Array<RuleType> | Array<(n: Uint8ClampedArray) => number>;
w: number;
};
export class CA2D implements CA {
load: Uint8ClampedArray;
save: Uint8ClampedArray;
i: Uint8ClampedArray;
n: Uint8ClampedArray;
w: number;
h: number;
size: number;
// 0 means it doesn't care
move: Array<RuleType> | Array<(n: Uint8ClampedArray) => number>;
max: number;
constructor({ w, h, move, max }: CA2DPropsType) {
this.w = w;
this.h = h;
this.max = max;
this.size = w * h;
this.i = new Uint8ClampedArray(9);
this.n = new Uint8ClampedArray(9);
this.load = new Uint8ClampedArray(this.size);
this.save = new Uint8ClampedArray(this.size);
this.move = move;
}
update() {
update2d(this);
}
seed() {
seed(this);
}
}
function loadNeighborhood2d(ca: CA2D, x: number, y: number) {
let x1;
let x2;
let x3;
if (x === 0) {
x1 = ca.w - 1;
x2 = x;
x3 = x + 1;
} else if (x === ca.w - 1) {
x1 = x - 1;
x2 = x;
x3 = 0;
} else {
x1 = x - 1;
x2 = x;
x3 = x + 1;
}
let y1;
let y2;
let y3;
if (y === 0) {
y1 = ca.h - 1;
y2 = y;
y3 = y + 1;
} else if (y === ca.h - 1) {
y1 = y - 1;
y2 = y;
y3 = 0;
} else {
y1 = y - 1;
y2 = y;
y3 = y + 1;
}
let i00 = y1 * ca.h + x1;
let i01 = y1 * ca.h + x2;
let i02 = y1 * ca.h + x3;
let i10 = y2 * ca.h + x1;
let i11 = y2 * ca.h + x2;
let i12 = y2 * ca.h + x3;
let i20 = y3 * ca.h + x1;
let i21 = y3 * ca.h + x2;
let i22 = y3 * ca.h + x3;
// indexes
ca.i[0] = i00; // upper left
ca.i[1] = i01;
ca.i[2] = i02;
ca.i[3] = i10;
ca.i[4] = i11; // middle
ca.i[5] = i12;
ca.i[6] = i20;
ca.i[7] = i21;
ca.i[8] = i22; // lower right
// neighborhoods
ca.n[0] = ca.save[i00]; // upper left
ca.n[1] = ca.save[i01];
ca.n[2] = ca.save[i02];
ca.n[3] = ca.save[i10];
ca.n[4] = ca.save[i11]; // middle
ca.n[5] = ca.save[i12];
ca.n[6] = ca.save[i20];
ca.n[7] = ca.save[i21];
ca.n[8] = ca.save[i22]; // lower right
}
export function update2d(ca: CA2D) {
let y = 0;
while (y < ca.h) {
let x = 0;
while (x < ca.w) {
loadNeighborhood2d(ca, x, y);
loadUpdate(y * ca.h + x, ca);
x++;
}
y++;
}
saveUpdates(ca);
}
export function loadUpdate(p: number, ca: CA): void {
let k = 0;
ruleLoop: while (k < ca.move.length) {
let rule = ca.move[k++];
if (typeof rule === "function") {
const make = rule(ca.n);
if (make >= 0) {
ca.load[p] = make;
break ruleLoop;
}
} else {
let i = 0;
const { test, make } = rule;
while (i < ca.n.length) {
const v = ca.n[i];
const t = test[i++];
if (!t(v)) {
continue ruleLoop;
}
}
ca.load[p] = make;
break ruleLoop;
}
}
}
export function randomIntBetween(min: number, max: number) {
// min and max included
return Math.floor(Math.random() * (max - min + 1) + min);
}
export function saveUpdates(ca: CA) {
let i = 0;
while (i < ca.load.length) {
ca.save[i] = ca.load[i];
i++;
}
}
export function seed(ca: CA) {
let i = 0;
while (i < ca.save.length) {
const rand = randomIntBetween(0, ca.max);
ca.save[i++] = rand;
}
}
I am generating a Gif from it in the terminal like this:
import { any, CA2D, CA2DRenderer, eq, wait } from './src/index.js'
import CanvasGifEncoder from '#pencil.js/canvas-gif-encoder'
import GIFEncoder from 'gif-encoder-2'
import fs from 'fs'
import { createCanvas } from 'canvas'
import { writeFile } from 'fs'
import _ from 'lodash'
const COLOR = {
black: 'rgba(40, 40, 40, 0)',
blue: 'rgba(56, 201, 247)',
green: 'hsl(165, 92%, 44%)',
greenLight: 'hsl(165, 92%, 79%)',
purple: 'rgba(121, 85, 243, 0.8)',
purpleLight: 'hsl(254, 87%, 70%)',
red: 'rgba(238, 56, 96)',
white: 'rgba(255, 255, 255)',
white2: 'rgba(244, 244, 244)',
white3: 'rgba(222, 222, 222)',
yellow: 'rgba(246, 223, 104)',
}
export const caProps = {
h: 60,
max: 1,
move: [
(n: Uint8ClampedArray) => {
const sum = _.sum(n)
const isLive = n[4] === 1
if (isLive && sum >= 3 && sum <= 4) {
return 1
} else {
return -1
}
},
(n: Uint8ClampedArray) => {
const sum = _.sum(n)
const isDead = n[4] === 0
if (isDead && sum === 3) {
return 0
} else {
return -1
}
},
(n: Uint8ClampedArray) => {
const isLive = n[4] === 1
return isLive ? 0 : -1
},
],
w: 60,
}
const ca = new CA2D(caProps)
ca.seed()
// const saveByteArray = (function () {
// var a = document.createElement('a')
// document.body.appendChild(a)
// a.style.display = 'none'
// return function (data: Array<BlobPart>, name: string) {
// var blob = new Blob(data, { type: 'octet/stream' }),
// url = window.URL.createObjectURL(blob)
// a.href = url
// a.download = name
// a.click()
// window.URL.revokeObjectURL(url)
// }
// })()
start()
async function start() {
const canvas = createCanvas(500, 500)
const renderer = new CA2DRenderer(canvas)
renderer.setDimensions({
gap: 1,
width: 500,
rowSize: ca.h,
columnSize: ca.w,
})
const encoder = new GIFEncoder(500, 500)
encoder.setDelay(100)
encoder.start()
// document.body.appendChild(renderer.canvas)
const colors = [COLOR.white2, COLOR.green, COLOR.purple, COLOR.red]
renderer.draw(ca, colors)
if (renderer.context) encoder.addFrame(renderer.context)
let i = 0
while (i < 5) {
console.log(i)
await wait(20)
ca.update()
renderer.draw(ca, colors)
if (renderer.context) encoder.addFrame(renderer.context)
i++
}
encoder.finish()
const buffer = encoder.out.getData()
fs.writeFileSync('example.gif', buffer)
// saveByteArray([buffer], 'example.gif')
}
You can run the project like this:
git clone git#github.com:lancejpollard/ca.js.git
npm install
npm run test
node dist.test/test
Notice the rules in the second code snippet:
[
(n: Uint8ClampedArray) => {
const sum = _.sum(n)
const isLive = n[4] === 1
if (isLive && sum >= 3 && sum <= 4) {
return 1
} else {
return -1
}
},
(n: Uint8ClampedArray) => {
const sum = _.sum(n)
const isDead = n[4] === 0
if (isDead && sum === 3) {
return 0
} else {
return -1
}
},
(n: Uint8ClampedArray) => {
const isLive = n[4] === 1
return isLive ? 0 : -1
},
]
It gets a neighborhood array (including self), and then implements the 3 rules on the Wiki page:
Any live cell with two or three live neighbours survives.
Any dead cell with three live neighbours becomes a live cell.
All other live cells die in the next generation. Similarly, all other dead cells stay dead.
I am getting it stabilizing after 5 generations though, what did I do wrong?
What do I need to change in the rules or the CA2D implementation to get it working? I don't think the CA2D implementation needs to change, I think I got that right, but maybe I misinterpreted the rules?
The main issue is in the second rule: if the if condition is true -- when the cell is dead and has three live neighbors -- the cell should become alive and so the return value should be 1 instead 0.
Less of an issue if the grid is a square, but the formula for converting an x, y pair to an index is wrong. The y coordinate should be multiplied by the width of the grid, not the height. You have this mistake in more than one spot in the code.
// variables
// arr has elements to be sorted
var arr = []
// temp is to store the intermediate results after merging
var temp = []
// seen is for marking visited i.e. sorted half as green
var seen = []
// length of array
var len = 100
// canvas initialisations
var canvas = document.getElementById("myCanvas")
canvas.width = canvas.height = 1000
var canvaswidth = canvas.width
var canvasheight = canvas.height
var ctx = canvas.getContext("2d")
// random array
for (let i = 0; i < len; i++) {
arr.push(parseInt(Math.random() * 500))
temp.push(parseInt(0))
seen.push(parseInt(0))
}
// initial contents of array to be sorted
// console.log(arr)
// draw the bars
draw = (s, e) => {
ctx.clearRect(0, 0, 1000, 1000)
// this loop will make unvisited bars in the upper half as black
// and visited bars in the upper half as green
for (let i = 0; i < len; i++) {
ctx.fillStyle = "#000000"
ctx.fillRect(15 * i, 500 - arr[i], 10, arr[i])
if (seen[i]) {
ctx.fillStyle = "#00ff00"
ctx.fillRect(15 * i, 500 - arr[i], 10, arr[i])
}
}
// the part that was merged is made blue in the lower half
// also its equivalent in the uper half is made white
for (let i = s; i <= e; i++) {
ctx.fillStyle = "#ffffff"
ctx.fillRect(15 * i, 500 - arr[i], 10, arr[i])
ctx.fillStyle = "#0000ff"
ctx.fillRect(15 * i, 500, 10, arr[i])
seen[i] = 1
}
}
// merge
merge = (s, e) => {
let m = parseInt((s + e) / 2)
let p1 = s
let p2 = m + 1
let n1 = m
let n2 = e
let idx = s
while (p1 <= n1 && p2 <= n2) {
if (arr[p1] <= arr[p2]) {
temp[idx++] = arr[p1++]
}
else {
temp[idx++] = arr[p2++]
}
}
while (p1 <= n1) {
temp[idx++] = arr[p1++]
}
while (p2 <= n2) {
temp[idx++] = arr[p2++]
}
idx = s
while (idx <= e) {
arr[idx] = temp[idx++]
}
}
// delay
function mytimeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// mergesort
const mergesort = async (s, e) => {
if (s < e) {
let m = parseInt((s + e) / 2)
await mergesort(s, m)
await mergesort(m + 1, e)
await merge(s, e)
// await console.log(`merged ${s} to ${e} now draw...`)
await draw(s, e)
await mytimeout(500)
}
}
// calls merge sort and at last
// makes all bars become green in upper half
const performer = async () => {
await mergesort(0, len - 1)
// await console.log(arr)
await draw()
}
performer()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="myCanvas">
Your browser does not support the canvas element.
</canvas>
<script src="testmerge.js"></script>
</body>
</html>
I am trying to make a visualisation of merge sort using plain javascript with HTML canvas.
If i change the len variable to 50 or below my code works
currently i have kept the len variable at 100
the entire array after getting sorted, in the final picture, the bars in the histogram decrease in height automatically
i am not sure if there is a better/cleaner way to approach this
There is a visual glitch because the width of your canvas element is fixed to 1000 pixels, and the bars always have a width of 10 pixels with 5 pixel gaps between them. This means that the bar chart gets clipped as soon as the number of bars gets too large to fit in those 1000 pixels.
One way to fix this is to make the width of the bars (and the inter-gap) dynamically smaller as the number of bars increases.
Here is a fix, which introduces the variables bardistance, barwidth and bargap, although the latter is only there to pinpoint what the gap-value is: it is not used in the rest of the code.
// variables
// arr has elements to be sorted
var arr = [];
// temp is to store the intermediate results after merging
var temp = [];
// seen is for marking visited i.e. sorted half as green
var seen = [];
// length of array
var len = 100;
// canvas initialisations
var canvas = document.getElementById("myCanvas");
canvas.width = canvas.height = 1000;
var canvaswidth = canvas.width;
var canvasheight = canvas.height;
var ctx = canvas.getContext("2d");
var bardistance = Math.floor(canvaswidth / len);
if (!bardistance) bardistance = 1;
var barwidth = Math.floor(bardistance * 0.7);
if (!barwidth) barwidth = 1;
var bargap = bardistance - barwidth;
// random array
for (let i = 0; i < len; i++) {
arr.push(parseInt(Math.random() * 500));
temp.push(parseInt(0));
seen.push(parseInt(0));
}
// initial contents of array to be sorted
// console.log(arr)
// draw the bars
var draw = (s, e) => {
ctx.clearRect(0, 0, 1000, 1000);
// this loop will make unvisited bars in the upper half as black
// and visited bars in the upper half as green
for (let i = 0; i < len; i++) {
ctx.fillStyle = "#000000";
ctx.fillRect(bardistance * i, 500 - arr[i], barwidth, arr[i]);
if (seen[i]) {
ctx.fillStyle = "#00ff00";
ctx.fillRect(bardistance * i, 500 - arr[i], barwidth, arr[i]);
}
}
// the part that was merged is made blue in the lower half
// also its equivalent in the uper half is made white
for (let i = s; i <= e; i++) {
ctx.fillStyle = "#ffffff";
ctx.fillRect(bardistance * i, 500 - arr[i], barwidth, arr[i]);
ctx.fillStyle = "#0000ff";
ctx.fillRect(bardistance * i, 500, barwidth, arr[i]);
seen[i] = 1;
}
}
// merge
merge = (s, e) => {
let m = parseInt((s + e) / 2);
let p1 = s;
let p2 = m + 1;
let n1 = m;
let n2 = e;
let idx = s;
while (p1 <= n1 && p2 <= n2) {
if (arr[p1] <= arr[p2]) {
temp[idx++] = arr[p1++];
}
else {
temp[idx++] = arr[p2++];
}
}
while (p1 <= n1) {
temp[idx++] = arr[p1++];
}
while (p2 <= n2) {
temp[idx++] = arr[p2++];
}
idx = s;
while (idx <= e) {
arr[idx] = temp[idx++];
}
}
// delay
function mytimeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// mergesort
const mergesort = async (s, e) => {
if (s < e) {
let m = parseInt((s + e) / 2);
await mergesort(s, m);
await mergesort(m + 1, e);
await merge(s, e);
// await console.log(`merged ${s} to ${e} now draw...`)
await draw(s, e);
await mytimeout(500);
}
}
// calls merge sort and at last
// makes all bars become green in upper half
const performer = async () => {
await mergesort(0, len - 1);
// await console.log(arr)
await draw();
}
performer();
<canvas id="myCanvas"></canvas>
I am making a simple data visualizer using react-vis, but I can't figure out how to make the crosshair show only the y-value of the most recently mouseover'd Series. Also, I can not seem to find any documentation on the event object specified in react-vis's docs. For example:
<LineSeries
...
onSeriesMouseOver={(event)=>{
// does something on mouse over
// you can access the value of the event
}}
What properties does event have? I cannot find anything on this.
This is the code of the Chart component I have so far. I tried storing each series' selected state in an array (currently commented out) and only setting the crosshair value to that series
when said series' state was true:
import React from 'react';
import {XYPlot, XAxis, YAxis, VerticalGridLines, HorizontalGridLines,
LineSeries, DiscreteColorLegend, Crosshair} from 'react-vis';
function hslToHex(h, s, l) {
h /= 360;
s /= 255;
l /= 255;
let r, g, b;
if (s === 0) {
r = g = b = l; // achromatic
}
else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
const toHex = x => {
const hex = Math.round(x * 255).toString(16);
return hex.length === 1 ? '0' + hex : hex;
};
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}
const series = [];
const invisiSeries = [];
const seriesColor = [];
const Chart = (props) => {
const [crossVals, setCrossVals] = React.useState([]);
const dataArr = [];
for (var i = 0; i < props.data.length; i++) {
dataArr.push(props.data[i].map((d)=> {
return {x: d.TIME, y: d.Value};
}));
}
const seriesNames = props.items.map(e => {
return e.LOCATION + ' ' + e.SUBJECT;
});
const isSelected = Array(dataArr.length).fill(false);
for (i = 0; i < dataArr.length; i++) {
if (seriesColor.length <= i) {
seriesColor.push(hslToHex( (i * 360 * 3/10) % 360, 255, 128));
}
series.push(<LineSeries
data={dataArr[i]}
style={{stroke: seriesColor[i], strokeWidth: 3}}
/*
onSeriesMouseOver={(d) => {
isSelected.fill(false);
isSelected[i] = true;
}}
onSeriesMouseOut={(event)=>{
isSelected[i] = false;
}}
*/
onNearestX={(value, {index}) => {
// Tried only setting crossvals for the single LineSeries that has
// its isSelected[i] set to true
//if(isSelected[i] == true) {
// setCrossVals([d]));
//}
setCrossVals(dataArr.map(d => d[index]));
}}/>);
/*invisiSeries.push(<LineSeries
data={dataArr[i]}
style={{opacity: 0, strokeWidth: 10}}
onNearestX={v => {
setCrossVals([v]);
}}/>);
*/
}
return (
<div>
<XYPlot
xType="ordinal"
width={1000}
height={500}
onMouseLeave={() => {setCrossVals([])}}>
<VerticalGridLines />
<HorizontalGridLines />
<XAxis title="Year" tickLabelAngle={35} tickPadding={24}/>
<YAxis title="KG per Capita" />
{series}
<Crosshair values={crossVals}>
</Crosshair>
</XYPlot>
<DiscreteColorLegend height={200} width={300}
items={props.items.map(e => {
return {title: e.LOCATION + ' ' + e.SUBJECT,
color: seriesColor[e.INDEX],
strokeWidth: 3}})} />
</div>
);
}
export default Chart;
hi check my answer in this question . i have used it to display the X,Y values of currently hovered bar in VerticalBarseries i hope it will work for LineSeries also.
I have a for loop like this:
var speed = 100;
var curve = [];
for (var i = 0; i < 5; i++) {
curve.push(i*speed);
}
So for the last loop its 400, the question is how do i implement ease in and out in the for loop? roughly in the end the result should be like this? [0,52,200,348,400]
EDIT:
var defaultSpin = 24;
var totalSlices = 12;
for (var i = 0; i < defaultSpin; i++) {
highlight(divs[i%totalSlices], i*100, 100);
}
function highlight(el, delay, duration) {
setTimeout(function() {
el.className += ' active';
setTimeout(function() {
el.className = 'pie';
}, duration)
}, delay)
}
It is a spin wheel with highlight instead of actually spinning it. I'm calling the above function with the loop. for now it only has constant speed because each loop difference is only 100 so the 1st hightlight delay is 0 and it start immediately. 2nd is 100, 3rd is 200 and so on.
Lots of common easing functions are shown here:
http://gizma.com/easing/
Here is an example of how to use one:
// from http://gizma.com/easing/
var easeInOutQuad = function (t, b, c, d) {
t /= d/2;
if (t < 1) return c/2*t*t + b;
t--;
return -c/2 * (t*(t-2) - 1) + b;
};
var steps = 4
var speed = 100
var curve = []
for (var i = 0; i < steps+1; i++) {
var stepValue = easeInOutQuad(i, 0, speed*steps, steps);
curve.push(stepValue);
}
console.log(curve); // [0, 50, 200, 350, 400]
Hey take a note of this snippet
/*\
* Raphael.easing_formulas
[ property ]
**
* Object that contains easing formulas for animation. You could extend it with your own. By default it has following list of easing:
# <ul>
# <li>“linear”</li>
# <li>“<” or “easeIn” or “ease-in”</li>
# <li>“>” or “easeOut” or “ease-out”</li>
# <li>“<>” or “easeInOut” or “ease-in-out”</li>
# <li>“backIn” or “back-in”</li>
# <li>“backOut” or “back-out”</li>
# <li>“elastic”</li>
# <li>“bounce”</li>
# </ul>
# <p>See also Easing demo.</p>
\*/
var ef = R.easing_formulas = {
linear: function (n) {
return n;
},
"<": function (n) {
return pow(n, 1.7);
},
">": function (n) {
return pow(n, .48);
},
"<>": function (n) {
var q = .48 - n / 1.04,
Q = math.sqrt(.1734 + q * q),
x = Q - q,
X = pow(abs(x), 1 / 3) * (x < 0 ? -1 : 1),
y = -Q - q,
Y = pow(abs(y), 1 / 3) * (y < 0 ? -1 : 1),
t = X + Y + .5;
return (1 - t) * 3 * t * t + t * t * t;
},
backIn: function (n) {
var s = 1.70158;
return n * n * ((s + 1) * n - s);
},
backOut: function (n) {
n = n - 1;
var s = 1.70158;
return n * n * ((s + 1) * n + s) + 1;
},
elastic: function (n) {
if (n == !!n) {
return n;
}
return pow(2, -10 * n) * math.sin((n - .075) * (2 * PI) / .3) + 1;
},
bounce: function (n) {
var s = 7.5625,
p = 2.75,
l;
if (n < (1 / p)) {
l = s * n * n;
} else {
if (n < (2 / p)) {
n -= (1.5 / p);
l = s * n * n + .75;
} else {
if (n < (2.5 / p)) {
n -= (2.25 / p);
l = s * n * n + .9375;
} else {
n -= (2.625 / p);
l = s * n * n + .984375;
}
}
}
return l;
}
};
ef.easeIn = ef["ease-in"] = ef["<"];
ef.easeOut = ef["ease-out"] = ef[">"];
ef.easeInOut = ef["ease-in-out"] = ef["<>"];
ef["back-in"] = ef.backIn;
ef["back-out"] = ef.backOut;
This is a snippet from Raphael. Here you see you have a list of animation ease-in formulas.
Lets try one of them, e.g. ease-in
var pow = Math.pow;
function easeIn(n) {
return pow(n, 1.7);
}
function easeOut(n) {
return pow(n, .48);
}
function process(min, max, intervals, fN) {
var diff = 1 / intervals,
difference = max - min,
curve = [];
for (i = diff; i <= 1; i += diff) {
curve.push(min + (difference * fN(i)));
}
return curve;
}
console.log('easeIn: \n', process(0, 400, 5, easeIn));
console.log('easeOut: \n', process(0, 400, 5, easeOut));
This might not be in sync with the output you have expected. But these are the formulas a renowned JS SVG library like Rapahel uses. You would love this demo
i'm doing a "Steve Reich - Clapping Music" kinda thing with "xoxoxoxx" with x as the clapping. but i want it to write the pattern while it keep going to the right. so you'd have this kinda writing:
xoxoxoxxxoxoxoxxxoxoxoxxxoxoxoxx
xoxoxoxxoxoxoxxxoxoxxoxoxxxoxoxo
so it prints the X or O and then goes a bit to the right and prints again. I hope this is clear, english isn't my first language, so i'm sorry if it's hard to understand. here is the full code for 2 lines because i'm bad at explaining:
var noise, env;
var seq = "o x o x o x o x o x o x o x x x";
var steps = seq.split(" ");
var speed = 8;
var count = 0;
var count2 = 0;
var count3=0;
var shift = 0;
var repeat = 1;
var sf, sf2, sf3;
var f;
function preload() {
sf = loadSound("./files/clap.wav");
sf2 = sf;
sf3 = sf;
}
function setup() {
createCanvas(400, 400);
env = new p5.Env(0.01, 1, 0.2, 0.1);
env2 = new p5.Env(0.1, 0.8, 0.01, 0.1);
env3 = new p5.Env(0.05, 0.9, 0.1, 0.1);
}
function hitMeSteve(when, env, loc) {
if (when == 'x' && frameCount % speed == 0) {
env.play();
}
}
function draw() {
if (frameCount % speed == 0) {
count++;
}
if (frameCount % (steps.length * speed * repeat) == 0) {
shift++;
count2=count2+2;
count3=count3+4;
}
if(shift==4){
shift=0;
count2=0;
count3=0;
}
shift = shift % steps.length;
shift2 = shift + 2;
var now = steps[count % steps.length];
hitMeSteve(now, sf, 10);
var canon = steps[(count + shift) % steps.length];
hitMeSteve(canon, sf2, width / 2 + 10);
var canon2 = steps[(count + shift+count2) % steps.length];
hitMeSteve(canon2, sf3, width / 2 + 20);
textSize(30);
//1
for (var i = 0; i < steps.length; i++) {
if (i == count % steps.length) {
fill(255, 180, 0);
} else {
fill(0);
}
text(steps[i],10+ ( + i) * 15,20);
//text(steps[i], 110 + (shift / 2 + i) * 15, height / 2);
}
//2
for (var i = 0; i < steps.length; i++) {
if (i == (count + shift) % steps.length) {
fill(255, 180, 0);
} else {
fill(0);
}
text(steps[i],10+( + i)*15,40);
//text(steps[i], 110 + (-shift / 2 + i) * 15, height / 2 + 20);
}
}
Just a proposal with setInterval, maybe this works for you.
var content = "oxoxoxoxoxoxoxxx",
target = document.getElementById('ticker'),
i = 0,
timer = setInterval(addChar, 800);
function addChar() {
if (i < content.length) {
target.innerHTML += ' ' + content[i];
i++;
} else {
target.innerHTML = '';
i=0;
}
}
<div id="ticker"></div>