Published
Edited
Jun 29, 2021
Importers
1 star
Insert cell
Insert cell
Insert cell
cooked
Insert cell
Insert cell
aspect = ({
"facets": {
width,
height: width / games.length,
strip: "rows",
facet: "x",
},
"views": {
width: width / views.length,
height: width / views.length * games.length * .8,
strip: "columns",
facet: "y"
},
})[rows ?? "views"]
Insert cell
views = [
cumulativeFlowDiagram,
runPlot,
leadtimeDistibutionPlot,
]
Insert cell
view = perspective => html`<div style="display: grid; grid-template-${aspect.strip}: repeat(${perspective.views.length}, 1fr);">
${
perspective.views.map(
view => Plot.plot({
...defaults.global,
...view(aspect),
})
)
}
</div>`
Insert cell
Insert cell
view({views})
Insert cell
views
Insert cell
cumulativeFlowDiagram = (aspect) => ({
x: {nice: 10, axis: "top"},
facet: {data: cooked, [aspect.facet]: "series"},
marks: cfd(cooked)
})
Insert cell
runPlot = (aspect) => ({
x: {nice: 10, axis: "top"},
y: {nice: 3},
facet: {data: leadtimes, [aspect.facet]: "series"},
color: {range: ["limegreen", "red"]},
marks: [
Plot.dot(leadtimes, {x: "hello", y: "leadtime", fill: "leadtime", r: "leadtime", title: "hello"}),
percentiles.map(percentileRule),
percentiles.map(percentileLabel),
customerLeadtime(leadtimes),
Plot.frame(),
]
})
Insert cell
leadtimeDistibutionPlot = (aspect) => ({
x: {axis: "top"},
y: {nice: 3},
facet: {data: leadtimes, [aspect.facet]: "series"},
color: {range: ["limegreen", "red"]},
marks: [
Plot.rectX(leadtimes, Plot.binY({x: "count"}, {y: "leadtime", fill: "leadtime"})),
Plot.frame(),
]
})
Insert cell
Plot.plot({
width: 400,
height: 300,
grid: true,
nice: 2,
x: {zero:true, reverse:false, label: "throughput (#/minute)"},
y: {zero:true, reverse:true, label: "↑ speed (s)"},
color: {range: ["limegreen","red"]},
marks: [
Plot.dot(
stats,
{
x: "throughput",
y: "customerLeadtime",
fill:"customerLeadtime",
r: 10,
stroke: "gray",
strokeWidth: 1,
}
),
Plot.text(
stats,
{
x: "throughput",
y: "customerLeadtime",
text: (d, i) => i + 1,
fill: "white",
stroke: "black",
strokeWidth: .1,
fontWeight: 800,
fontSize: 16
}
),
]
})
Insert cell
stats = mapi((k, i) => ({
count: k.leadtimes.length,
leadtime: k.leadtime / 1000,
customerLeadtime: customerLeadtimes[i],
// throughput: k.throughput * 1000 * 60,
throughput: k.leadtimes.length / customerLeadtimes[i] * 60,
deviation: k.deviation / 1000,
}))(demoBag.game)
Insert cell
games
Insert cell
leadtimes
Insert cell
customerLeadtimes = maxima("time")
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
leadtimes = mapi((k, i) => index({name: "series", number: i + 1})(k))(map(prop("leadtimes"))(games)).flat()
Insert cell
w = width / 1
Insert cell
mapi = R.addIndex(map)
Insert cell
index = curry(({number = 0, name="index", ...options}) => map(k => ({[name]: number, ...k})))
Insert cell
Insert cell
defaults = ({
global: {
width: aspect.width,
height: aspect.height,
grid: true,
style: {
// background: "snow",
fontFamily: "Avenir Next",
// fontSize: 12,
fontWeight: 700,
},
color: {type: "ordinal", scheme: "set1"},
},
single: {
width,
height: width * 9 / 16,
color: {type: "ordinal", scheme: "set1"},
}
})
Insert cell
Insert cell
Insert cell
games = map(game => {
const {gameNumber, states, leadtimes, ...rest} = game
return {
gameNumber,
states: map(normalize)(game.states),
leadtimes: map(leadtime => map(milli)(leadtime))(game.leadtimes)
}
})(demoBag.game)
// demoBag.game // series: just the list of games or runs
Insert cell
devation = map(x => +x.toFixed(1))(map(milli)(map(k => k.deviation)(demoBag.game)))
Insert cell
leadtime = map(x => +x.toFixed(1))(map(milli)(map(k => k.leadtime)(demoBag.game)))
Insert cell
throughput = map(x => +x.toFixed(1))(map(k => k.throughput*1000*60)(demoBag.game))
Insert cell
demoBag.game[0]
Insert cell
map(game => {
const {gameNumber, states, leadtimes, ...rest} = game
return {
gameNumber,
states: map(normalize)(game.states),
leadtimes: map(leadtime => map(milli)(leadtime))(game.leadtimes)
}
})(demoBag.game)
Insert cell
normalize = state => {
const {name, hello} = state
return {name, hello: hello.map(milli)}
}
Insert cell
map(normalize)(game.states)
Insert cell
milli = x => x / 1000
Insert cell
game = games[gameIndex] // current game for single display
Insert cell
map(k => map(x => x/1000)(k))(game.leadtimes)
Insert cell
map(
game => ({
...game,
leadtimes: map(k => map(x => x/1000)(k))(game.leadtimes),
})
)(games)
Insert cell
raw = map(entrify)(games).flat() // formalized, raw, flattened
Insert cell
cooked = sentinel(raw, max.time) // includes sentinel entry per series per state so area() draws upto maxTime
Insert cell
maxima = (variable) => map(k => maximum("time")(getSeries(raw)(k)) / 1)(series(raw))
Insert cell
max = ({ // some maxima used at more than one place
time: maximum("time")(raw),
count: maximum("count")(raw),
})
Insert cell
Insert cell
Insert cell
// takes list of games, and optionally radius of dot
// returns array of marks, ready to be plotted
cfd = (data, r = 1) => [
Plot.area(data, {x1: "time", y1: 0, y2: "count", fill: "state", curve: "step-after"}),
showDots ? Plot.dot(data, {x: "time",y: "count", fill: "lightgrey", r, title: "time"}) : [],
Plot.frame(), // frame on top, so it covers any other marks
]
Insert cell
Insert cell
// takes a game
// returns list with flattened states and formalized entries including count (# items arrived per state)
entrify = game => game.states.reduce(
(accu, state) => [
...accu,
...state.hello.map(
(time, i) => ({
series: game.gameNumber,
state: state.name,
time,
count: i + 1
})
)
],
[]
)
Insert cell
// takes property and list
// returns list of unique property values
unique = curry((property, list) => [...new Set(map(prop(property), list))])
Insert cell
// example
unique("series")(raw)
Insert cell
// example
unique("state")(raw)
Insert cell
// takes a list (of games)
// return lsit of unique series or runs
series = unique("series")
Insert cell
// example
series(raw)
Insert cell
// takes list (of games)
// returns list of unique states from source list
states = unique("state")
Insert cell
// example
states(raw)
Insert cell
// takes a list (of games) and a series number
// returns list subset containing only entries for specified series
getSeries = curry((list, series) => filter(k => k.series === series, list))
Insert cell
// example
getSeries(raw, 1)
Insert cell
// takes state and series
// returns list subset with matching state only
getState = (state, series) => filter(k => k.state === state, series)
Insert cell
// example
getState("doing", getSeries(raw, 1))
Insert cell
// takes list with all series
// returns list with all series and all states closed
sentinel = (list, time) =>
map(
serie =>
closeAllStates(
time,
getSeries(list)(serie)
)
)
(series(list))
.flat()
Insert cell
// takes maximum lead time and list with single series
// returns list with all states closed with sentinel
closeAllStates = (time, singleSerie) => map(
(state) => closeSingleState(time, getState(state, singleSerie))
)(states(singleSerie)).flat()
Insert cell
// takes list of single series and single state
// returns list with sentinel added
closeSingleState = (time, list) => [...list, ({...list[0], time, count: maximum("count")(list)})]
Insert cell
// takes property and list
// returns maximum value for given property
maximum = curry((property, list) => d3.max(list, prop(property)))
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