Create a gist now

Instantly share code, notes, and snippets.

@bricof /.block
Last active Apr 21, 2017

What would you like to do?
Inventory Cycle

This is a cleaned and simplified version of the inventory cycle animation used in the Stitch Fix Algorithms Tour.

Inventory events (e.g. scan points) are simulated for inventory items over time and their events are their locations within the process flow are mapped to x-y coordinates.

<!DOCTYPE html>
<meta charset="utf-8">
<style>
.first-point-rect {
fill: #fff;
stroke: #000;
stroke-miterlimit: 10;
}
.later-points-rect {
fill: #fff;
stroke: #939598;
stroke-miterlimit: 10;
stroke-dasharray: 4.023,4.023;
}
.label {
fill: #939598;
font-size: 14px;
}
</style>
<body>
<svg width="960" height="500" viewBox="-150 0 960 665">
<g id="inventory-cycle">
</g>
<g id="point-1">
<rect class="first-point-rect" x="200" y="133.4" width="176" height="144"/>
<text class="label" transform="matrix(1 0 0 1 200.4859 297.0509)">point 1</text>
</g>
<g id="point-2">
<rect class="later-points-rect" x="392.1" y="429.2" width="38" height="144" transform="matrix(0.7071 -0.7071 0.7071 0.7071 -233.9965 437.497)"/>
<text class="label" transform="matrix(0.7 -0.7 0.7 0.7 341 458)">point 2</text>
</g>
<g id="point-3">
<rect class="later-points-rect" x="269" y="480.2" width="38" height="144"/>
<text class="label" transform="matrix(1 0 0 1 269 472.3668)">point 3</text>
</g>
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="inventory-cycle.js"></script>
<script>
start_inventory_animation()
</script>
function form_outline_d(form_n, x, y){
// square
if (form_n == 0) {
return "M" + (x) + "," + (y) + "l10,0 l0,10 l-10,0 z"
}
// triangle
if (form_n == 1) {
return "M" + (x) + "," + (y + 10) + "l5,-10 l5,10 z"
}
// circle
if (form_n == 2) {
return "M" + (x) + "," + (y + 5) + "a 5 5 0 1 0 0.01 0 z"
}
}
function start_inventory_animation() {
// vis config variables
var svg = d3.select("#inventory-cycle")
var speed = 0.00015 // ticks per millisecond
var outer_r = 210
var inner_r = 160
var center_x = 289
var center_y_main = 380
var center_y_in = -30
// mapping functions
var coord_x_in = function(r,a) { return center_x + r * Math.cos(a) }
var coord_y_in = function(r,a) { return center_y_in+ r * Math.sin(a) }
var coord_x_main = function(r,a) { return center_x + r * Math.cos(a) }
var coord_y_main = function(r,a) { return center_y_main + r * Math.sin(a) }
var angle_in = function(t, t1, t2) {
var rel_t
if (t2 == t1) { rel_t = 1 }
else { rel_t = (t - t1) / (t2 - t1) }
var a = (180.0 - 90.0 * rel_t) * (Math.PI/180.0)
return a
}
var angle_main = function(t, t1, t2, e1, e2) {
// compute angle in circle for a dot,
// given its previous and next events, their timestamps, and the current time
var rel_t
if (t2 == t1) { rel_t = 1 }
else { rel_t = (t - t1) / (t2 - t1) }
if (e2 == 1) {
return (90.0 + 180.0 * rel_t) * (Math.PI/180.0)
}
if (e2 == 2) {
return (-90.0 + 135.0 * rel_t) * (Math.PI/180.0)
}
if (e2 == 3) {
return (-90.0 + 135.0 + 45.0 * rel_t) * (Math.PI/180.0)
}
}
// internal state variables
var particle_count = 0
var current_events = []
d3.timer(function(t_ms) {
var t = t_ms * speed
// *** SIMULATION ***
// check for expired events since last t
// if previous event was #1, then move to #2 with random speed
// if previous event was #2, then move to either #3 or #4 with some probability and random speed
// if previous event was #3, then move to either #4 or #1 with some probability and random speed
// if previous event was #4, then exit
var removals = []
current_events.forEach(function(d,i){
if (d.t2 < t) {
d.t1 = d.t2
d.e1 = d.e2
d.t2 = d.t1 + 0.3 + Math.random() * 0.4
if (d.e1 == 1) {
d.e2 = 2
} else if (d.e1 == 2) {
if (Math.random() < 0.5) { d.e2 = 4; d.t2 = d.t1 + 0.5 * ( 0.3 + Math.random() * 0.4 ) }
else { d.e2 = 3 }
} else if (d.e1 == 3) {
if (Math.random() < 0.5) { d.e2 = 4; d.t2 = d.t1 + 0.5 * ( 0.3 + Math.random() * 0.4 ) }
else { d.e2 = 1 }
} else {
removals.push(i)
}
}
})
for (var i=removals.length - 1; i>-1; i--){
current_events.splice(removals[i],1)
}
// add some new particles, with some probability and random speed
for (var i=0; i<2; i++) {
if (Math.random() < 0.3) {
particle_count += 1
current_events.push({
'particle_id': particle_count,
'rad': inner_r + (outer_r - inner_r) * Math.random(),
'sil': Math.floor(Math.random() * 3),
't1': t,
't2': t + 0.5 * ( 0.3 + Math.random() * 0.4 ),
'e1': 0,
'e2': 1
})
}
}
// *** UPDATE SVG ANIMATION ***
var inventory_items = svg.selectAll(".inventory-cycle-silhouette")
.data(current_events, function(d) { return d.particle_id })
inventory_items.enter().append("path")
.attr("class", "inventory-cycle-silhouette")
.attr("fill", "#fff")
.attr("stroke", "#000")
.attr("stroke-width", 0.5)
.attr("opacity", 1)
.attr("d", function(d) { return form_outline_d(d.sil, 290, 0) })
inventory_items
.attr("d", function(d){
var tx, ty
if (d.e1 == 0) {
var new_rad = inner_r + (outer_r - d.rad)
if (coord_y_in(new_rad, angle_in(t, d.t1, d.t2, d.e1, d.e2)) > center_y_main - outer_r + new_rad) {
tx = coord_x_in(new_rad, angle_in(t, d.t1, d.t2, d.e1, d.e2))
} else {
tx = -90 + d.rad * 2
}
ty = coord_y_in(new_rad, angle_in(t, d.t1, d.t2, d.e1, d.e2))
} else if (d.e2 == 4) {
tx = 280
ty = 550
} else {
tx = coord_x_main(d.rad, angle_main(t, d.t1, d.t2, d.e1, d.e2))
ty = coord_y_main(d.rad, angle_main(t, d.t1, d.t2, d.e1, d.e2))
}
return form_outline_d(d.sil, tx, ty)
})
inventory_items.exit().remove()
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment