Published
Edited
Jan 15, 2021
2 forks
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//Pull in JSON of agent and path data from Gist
data=d3.json("https://gist.githubusercontent.com/tripplybrand/eb938c5d20c131869a21f2b309336c49/raw/de925eaa24a41f51a6c64b92c19fda1d9f2b581d/FdM_Animation_Data.json")
Insert cell
backgroundImg = FileAttachment("Line_2_Landmarks.png")
Insert cell
Insert cell
Insert cell
Insert cell
//Updates the displayed completed parts number
updatePartsCompleted = () => {
//Update global state
globalState.completed += 1
//Use global state to update DOM element
const text = d3.select("#completedCount")
//Set format of text to be three digits at all timed: 001
const textArray =
globalState.completed < 10 ? ["0", "0", globalState.completed].join("")
: globalState.completed < 100 ? ["0", globalState.completed].join("")
: globalState.completed
const throughputRate = (globalState.completed*800)/(globalState.animationCurrentTime/1000)
updateGauge(throughputGauge, throughputRate)
//Transition DOM element to new completed parts number
return text.transition()
.text(textArray)
}
Insert cell
//Resets the completed parts number to 000
resetPartsCompleted = () => {
//Update global state
globalState.completed = 0
//Use global state to update DOM element
const text = d3.select("#completedCount")
//Transition DOM element to completed parts number 000
return text.transition()
.text("000")
}
Insert cell
//Updates the displayed scraps gauge
updateScraps = () => {
//Update global state
globalState.scrap += 1
const throughputRate = (globalState.scrap*800)/(globalState.animationCurrentTime/1000)
return updateGauge(scrapRateGauge, throughputRate)
}
Insert cell
Insert cell
//Create transition between two points, and final exit transition from the scene when the agent has finished its travels.
transitionMaster = (transition, pathIndex) => {
//Create the transition path between two points
transition
//Amount of time agent should stay at specific location. pathIndex < d.path.length is used throughout because the length of the path varies from agent to agent
.delay(function(d) {
return pathIndex < d.path.length ? d.path[pathIndex-1].rest
: null;
})
//Calculate the interpolated value between point for both the x and y coordinate
.attrTween("cx", function(d) {
return pathIndex < d.path.length ? d3.interpolateNumber(this.getAttribute("cx"), d.path[pathIndex].x) : null;
})
.attrTween("cy", function(d) {
return pathIndex < d.path.length ? d3.interpolateNumber(this.getAttribute("cy"), d.path[pathIndex].y) : null;
})
//Type of easing between points
.ease(d3.easeLinear)
//Length of time in millisecond to transition between points
.duration(function(d) {
//Establish rate of travel. Certain paths involve being moved by a robot on the shop floor, which results in a slower travel rate.
const rateLocal =
pathIndex < d.path.length ?
(
d.path[pathIndex].pathType === "robot" ?
0.1 :
0.2
)
: null;
//Distance between points divided by rate = the time of travel
return pathIndex < d.path.length ?
(
distance(d.path[pathIndex-1].x, d.path[pathIndex-1].y, d.path[pathIndex].x,
d.path[pathIndex].y)/rateLocal
)
: null;
})
//Once the transition between points is completed the pathIndex of the agent is updated in the data
.on('end', function(d) {
d.pathIndex = pathIndex
})
//Fade out and remove agent when final path is complete, going from o.8 to 0 opacity over 500 milliseconds
//pathIndex >= d.path.length identifies that the agent's path of travel is completed
.transition()
.styleTween("opacity", function(d) {
return pathIndex >= d.path.length ? d3.interpolate(0.8, 0)
: null;
})
.duration(500)
//Once this transition is completed update the global state to reflect the current count of scraps or completed parts.
.on('end', function(d) {
return pathIndex >= d.path.length ?
(
//The length of scrap path. Probably better way to do this.
d.path.length === 4 ? updateScraps()
: updatePartsCompleted()
)
: null;
})
//Remove agent once its path of travel is completed
.remove()
}
Insert cell
//Because path length varies between agents find the maximum path length
maxPathIndex = Math.max(...data.map(x => x.path.length))
Insert cell
//Create array of path indexes
pathIndexArray = Array.apply(null, Array(maxPathIndex)).map(function (_, i) {return i+1;})
Insert cell
//Call transitionMaster for each path index, creating and attaching all transitions for a single agent
transitionMap = (g) => {
pathIndexArray.forEach((x) => {
g = g.transition().call(transitionMaster, x)
})
}
Insert cell
animationTimer = () => {
//Right now this timer never stops. Is that ok?
const t = d3.interval(function() {
globalState.animationCurrentTime = (d3.now() - globalState.animationStartTime);
}, 500)
return t
}
Insert cell
Insert cell
//Animate an array of agents
startSingleAnimation = (data) => {
//Select svg #lineMap
const svg = d3.select("#lineMap")

//Bind each agent data object to a circle
const circles = svg.selectAll("circle")
//Verify that bound agent does not already exist in the scene
.data(data, function(d) { return d ? d.name : this.id; })

//Establish fading into existence
return circles
.enter().append("circle")
.attr("class", "agent")
.attr("cx", function(d) { return d.path[0].x; })
.attr("cy", function(d) { return d.path[0].y; })
.attr("r", 5)
.attr("id", function(d) { return d.name; })
.style("fill", function(d) { return "#e85116"; /*d.color;*/ })
.style("opacity", 0)
.transition()
.duration(300)
.style("opacity", 0.8)
//Attach all of the agents transitions with transitionMap
.transition().call(transitionMap);
}
Insert cell
//Attach a timeout to trigger the animation when the agent is suppose to enter the scene, based upon the data element "start"
setStartTime = (data) => {
var t = d3.timeout(
function() {
startSingleAnimation([data]);
}, data.start)
return t
}
Insert cell
//Kick off the animation
startAnimation = () => {
//Calculate the current time in the world of the animation and update the global state
animationTimer()
//Update global state with the time the animation began
globalState.animationStartTime = d3.now();
//Create plot line
createLinePlot([])
plotLineTimer()
//Apply setStartTime to every agent in data and update the global state with an array of all timeouts established
globalState.timeouts = data.map((d) => {
return setStartTime(d)
})
return globalState.timeouts
}
Insert cell
//Reset animation
restartAnimation = (data) => {
//Select all agents
const selection = d3.selectAll(".agent")
//Reset the parts completed ticker to 000 and the global state of count of scraps to 0
resetPartsCompleted()
resetScraps()
//Stop all established timeouts
globalState.timeouts.forEach((timeout) => timeout.stop())
//Interrupt all transitions attached to .agent
selection.interrupt();
//Remove all .agent
selection.remove();
//Reset gauges
updateGauge(throughputGauge, 0)
updateGauge(scrapRateGauge, 0)
//Reset line plot
globalState.currentLinePoint = 0
d3.select("#uptimeLineG").remove()
globalState.plotLineTimer.forEach((timeout) => timeout.stop())
//Kick off the animation again
return startAnimation();
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
//Render gauges
{
createGauge(scrapRateGauge)
createGauge(throughputGauge)
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more