Published
Edited
Jun 11, 2020
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
patterns = [
{name:'Large spike',probability:.2625, maxMultiplier:[2,6]},
{name:'Small spike',probability:.25,maxMultiplier:[1.4,2.0]},
{name:'Fluctuating',probability:.35, maxMultiplier:[0.9,1.4]},
{name:'Decreasing',probability:.1375, maxMultiplier:[0.9,0.9]}]
Insert cell
max = function(chances,n){
// rather than simulating n times, we'll do some math and only simulate patterns that have a chance of returning the highest price in the group. This allows us to get a reasonably accurate output for large values of n without sacrificing as much performance.
// choose a random number to check against our pattern probabilities
let f = Math.random()
let c = 0
// once n gets high enough, this will almost always return the large spike pattern because someone in the group will likely get that pattern
function getPattern(){
for(let i = 0; i < chances.length; i++){
c += chances[i]
if(f < c){
return i
}
}
}

let pattern = getPattern()
// each individual person still has the same chance of getting the max pattern so we will run this (max pattern probability) * n times
function getMaxPrice(){
let prices = []
let [minMult,maxMult] = patterns[pattern].maxMultiplier
for(let p = 0; p < Math.ceil(n * patterns[pattern].probability); p++){
let buyPrice = (Math.random() * (110-90)) + 90
let multiplier = (Math.random() * (maxMult - minMult)) + minMult
prices.push(Math.ceil(buyPrice * multiplier))
}
return Math.max(...prices)
}

return getMaxPrice()
}
Insert cell
simulate = function(n){
// since we only care about the maximum price, we remap the probability based on the chance that at least one person in the group of n people has each pattern and go through them from highest possible price to lowest
let chances = patterns.map(d => 1 - Math.pow(1 - d.probability,n))
let results = []
for(let x = 0; x < iterations; x++){
results.push(max(chances,n))
}
return results
}
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
let data = simulate(pop)
let height = 500
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
let margin = ({top: 20, right: 20, bottom: 30, left: 40})
let x = d3.scaleLinear()
.domain([80,660])
.range([margin.left, width - margin.right])
let bins = d3.histogram()
.domain(x.domain())
.thresholds(x.ticks((x.domain()[1] - x.domain()[0])/10))
(data)
let megabins = d3.histogram()
.domain(x.domain())
.thresholds([100,200,300,400,500,600])
(data)
console.log(megabins)
let xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80 ).tickSizeOuter(0))
.call(g => g.append("text")
.attr("x", width - margin.right)
.attr("y", -4)
.attr("fill", "currentColor")
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text(data.x))
let y = d3.scaleLinear()
.domain([0,iterations/2])
.range([height - margin.bottom, margin.top])
let yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.call(d3.axisLeft(y).ticks(height / 40).tickFormat(d => `${Math.ceil(100*(d/iterations))}%`))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 4)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data.y))
let color = d3.scaleLinear()
.domain([0,1])
.range([d3.color("rgba(255, 201, 38,0)"),d3.color("rgba(255, 201, 38,0.8)")])
svg.append("g")
.selectAll('rect')
.data(megabins)
.join("rect")
.attr('x', d => x(d.x0) + 1)
.attr('width',d => Math.max(0,x(d.x1) - x(d.x0) - 1))
.attr('y',y.range()[1])
.attr('height',y(y.domain()[0]))
.attr('fill',d => color(d.length/data.length))
svg.append('g')
.selectAll('text')
.data(megabins)
.join('text')
.attr('class','label')
.attr('x', d => x(d.x0) + 4)
.attr('width',d => Math.max(0,x(d.x1) - x(d.x0) - 1))
.attr('y',40)
.text(d => {
return `${Math.round(d.length/data.length*100)}%`
})

svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", d => x(d.x0) + 1)
.attr("width", d => Math.max(0, x(d.x1) - x(d.x0) - 1))
.attr("y", d => y(d.length))
.attr("height", d => y(0) - y(d.length));
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);
return svg.node();
}
Insert cell
import {slider} from "@jashkenas/inputs"
Insert cell
import {table} from "@tmcw/tables@513"
Insert cell
d3 = require('d3')
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