Published
Edited
May 22, 2020
Insert cell
Insert cell
Insert cell
Insert cell
md`## Appendix`
Insert cell
Insert cell
d3 = require('d3@5')
Insert cell
height = 1000
Insert cell
color = d3.scaleSequential().domain([0, 1]).interpolator(d3.interpolateBlues)
Insert cell
color(1)
Insert cell
Insert cell
Insert cell
interest_color = (interest, a)=> {
return `hsla(${(interest.id_idx/interest_list.length*0.4+0.3)*360},30%,50%,${a})`
}
Insert cell
card_close = {
const el = html`<button>close</button>`
el.onclick= ()=> graph.close_card()
return el
}
Insert cell
interest_card = d=> md`
## Interest: ${d.title}

${card_close}
`
Insert cell
persona_card = d=> md`
${card2get(influencers[0])}
${card_close}
`
Insert cell
ndata1 = {
const nodes = d3.range(0,30).map(a=> ({
id: a,
}))
const links = [] || d3.range(4).map(a=> ({
source: xs_rnd(nodes).id,
target: xs_rnd(nodes).id,
distance: 100,
value: 0.5,
}))

return {nodes, links}
}
Insert cell
{
const a = d3.create('div')
a.text('hello')
a.style('color', 'red')
a.style('border', '1px solid red')
return a.node()
}
Insert cell
ndata = {
const nodes = [...persona_nodes, ...interest_nodes]
return {nodes, links: [...persona_interest_links]}
}
Insert cell
interest_nodes = interest_list
Insert cell
persona_nodes = {
const nodes = d3.range(0, render_count, 1).map(i=> {
const age_obj = age_count_interpolate_get(Math.random())
const obj = ({
type: 'persona',
id: 'persona_'+i,
age_split: age_obj.k,
})
obj.interest_map = interpolate_discrete_expanded_get(age_obj.list, 'universe_count')()
.map(([v, has_it])=> {
if (!has_it) return null
const m = interest_list.find(r=> r.title==v.interest)
if (!m) return m
return [m.id, v.index/100]
}).filter(Boolean)
/* obj.interests = interest_dist_get().map(([o, has_it])=> {
if (!has_it) return 0
return interpolate_discrete_get(o.subs, 'universe_count')(Math.random())
})*/
obj.to_rem = obj.interest_map.length===0 || Math.random()<amount_to_remove
return obj
})
return nodes
}
Insert cell
persona_nodes[1461]
Insert cell
interest_nodes[6]
Insert cell
graph.clicked_node
Insert cell
amount_to_remove = 0.8
Insert cell
Insert cell
persona_nodes[2]
Insert cell
render_count = 2000
Insert cell
interest_dist_get = ()=> interest_list.map(o=> [o, Math.random() < o.count/interest_list_count_tot])
Insert cell
interest_list_count_tot = xs_sum(interest_list.map(a=> a.count))
Insert cell
interest_list = Object.entries(interest_buckets).map(([k, v], i)=> {
// const age_group_list = Object.entries(obj_map(xs_groupby(v, 'segment_sub'), v=> v[0]))
return ({
type: 'interest',
id: 'interest_'+i,
id_idx: i,
title: k,
count: xs_sum(v.map(v=> v.universe_count)),
subs: v,
})
})
Insert cell
agesplit_list = Object.entries(interest_buckets).map(([k, v], i)=> {
// const age_group_list = Object.entries(obj_map(xs_groupby(v, 'segment_sub'), v=> v[0]))
return ({
id: 'interest_'+i,
title: k,
count: xs_sum(v.map(v=> v.universe_count)),
subs: v,
})
})
Insert cell
interest_buckets
Insert cell
age_count_interpolate_get = interpolate_discrete_get(Object.values(age_count_map), 'count')
Insert cell
interpolate_discrete_get = (xs, key)=> {
const count_list = xs.map(x=> x[key])
const count_tot = xs_sum(count_list)
const p_list = count_list.map(v=> v/count_tot)
let p_max = 0
const tally_list = p_list.map(p=> (p_max = p_max + p))
// assume 0<=v<=1
const fn = v=> {
const _idx = tally_list.findIndex(p=> v<p)
const idx = _idx===-1?tally_list.length-1:_idx
return xs[idx]
}
return fn
}
Insert cell
interpolate_discrete_expanded_get = (xs, key)=> {
const count_list = xs.map(x=> x[key])
const count_tot = xs_sum(count_list)
const p_list = count_list.map(v=> v/count_tot)
let p_max = 0
const fn = v=> {
return xs.map((v, i)=> [v, Math.random() < p_list[i]])
}
return fn
}
Insert cell
xs_sum = xs=> xs.reduce((a, b)=> a+b, 0)
Insert cell
interest_buckets = xs_groupby(age_unique_items, 'interest')
Insert cell
age_buckets = xs_groupby(age_unique_items, 'segment_sub')
Insert cell
age_buckets_list = Object.entries(age_buckets).map(([k, v])=> ({id: k, list: v}))
Insert cell
age_count_map = { // TODO: provide as separate indata, calc is faulty
const a = obj_map(age_buckets, (v, k)=> ({k, count: xs_sum(v.map(v=> v.universe_count)), list: v}))
// const count_tot = xs_sum(Object.values(a))
// const ps = obj_map(a, v=> v/count_tot)
return a
}
Insert cell
obj_map = (obj, fn)=> Object.fromEntries(Object.entries(obj).map(([k, v], i)=> [k, fn(v, k, i)]))
Insert cell
universe_count_extent = d3.extent(age_unique_items, d=> d.universe_count)
Insert cell
universe_count = universe_count_extent[1]*5 // TODO: get correct value from data source
Insert cell
xs_groupby = (xs, key)=> {
const buckets = {}
xs.forEach(v=> {
if (!buckets[v[key]]) buckets[v[key]] = []
buckets[v[key]].push(v)
})
return buckets
}
Insert cell
age_unique_items = {
const reg = {}
indata.filter(a=> a.segment_kind==='age').forEach(a=> a.items.forEach(v=> {
const id = `${v.interest} - ${v.segment_sub}`
reg[id] = v
}))
return Object.values(reg)
}
Insert cell
xs_concat = xs=> xs.reduce((a, b)=> a.concat(b), [])
Insert cell
indata = {
const title_regex = /([^ ]*) cluster - ([^ ]*) segmentation.csv/
const res = obj_map_kv_xs(indata_files, (k, v, i)=> {
const m = k.toLowerCase().match(title_regex)
if (!m) return null
const [_, cluster, segment_kind] = m
const csv_rows_raw = d3.csvParseRows(v)
const items = dsv_rows_to_objects(segment_csv_clean(csv_rows_raw))
.map(v=> segment_obj_clean(v))
return {cluster, segment_kind, items}
})
return res
}
Insert cell
// indata.slice(0,1).map(a=> dsv_rows_to_objects(segment_csv_clean(a.csv_rows_raw)))[0].map(segment_obj_clean)
Insert cell
segment_csv_clean = csv_text=> {
let idone = null
const res = csv_text.filter((r, i)=> {
if (idone) return false
if (i>2 && !r[1]) {idone = true; return false}
return i!=1 && r.length>1
})
res[0][1] = "key"
return res
}
Insert cell
dsv_rows_to_objects = (xs)=> {
const header = xs[0]
return xs.slice(1)
.map(r=> r.map((v, i)=> [header[i], v])
.filter(([k])=> k!==null))
.map(e=> Object.fromEntries(e))
}
Insert cell
segment_csv_clean_demo = segment_csv_clean(d3.csvParseRows(indata_files["Fitness Cluster - Age Segmentation.csv"]))
Insert cell
segment_obj_uncleaned_demo = dsv_rows_to_objects(segment_csv_clean_demo)
Insert cell
segment_obj_clean = o=> {
const m = o.key.split(' - ')
if (m.length!==2) throw new Error('no match '+o.key)
const [interest, segment_sub] = m
const res = {
interest, segment_sub,
universe_count: o["Universe"]*1,
response_count: o["Responses"]*1,
index: o["Index"]*1,
}
return res
}
Insert cell
segment_obj_clean(segment_obj_uncleaned_demo[0])
Insert cell
Insert cell
indata_files = ({
"Business Cluster - Age Segmentation.csv":
await FileAttachment("Business Cluster - Age Segmentation@1.csv").text(),
"Business Cluster - Gender Segmentation.csv":
await FileAttachment("Business Cluster - Gender Segmentation@1.csv").text(),
"Fitness Cluster - Age Segmentation.csv":
await FileAttachment("Fitness Cluster - Age Segmentation.csv").text(),
"Fitness Cluster - Gender Segmentation.csv":
await FileAttachment("Fitness Cluster - Gender Segmentation@1.csv").text(),
})
Insert cell
xs_rnd = xs=> xs[Math.random()*xs.length | 0]
Insert cell
drag = simulation=> {
return d3.drag()
// .on('start', )
}
Insert cell
md`# Appendix - Card`
Insert cell
influencers = [{
yt: {
"playlist_uploads": "UUoOae5nYA7VqaXzerajD0lg",
"country": "GB",
"image_default": "https://yt3.ggpht.com/a/AATXAJyfrIQvkSQJHHgw5OL0LhrHpI8zzPxvhds7nw=s88-c-k-c0xffffffff-no-rj-mo",
"image_medium": "https://yt3.ggpht.com/a/AATXAJyfrIQvkSQJHHgw5OL0LhrHpI8zzPxvhds7nw=s240-c-k-c0xffffffff-no-rj-mo",
"title": "Ali Abdaal",
"subscriber_count": 565000,
"video_count": 232,
"id": "UCoOae5nYA7VqaXzerajD0lg",
"image_high": "https://yt3.ggpht.com/a/AATXAJyfrIQvkSQJHHgw5OL0LhrHpI8zzPxvhds7nw=s800-c-k-c0xffffffff-no-rj-mo",
"slug": "aliabdaal",
"view_count": 31227276
},
attention: {
relative_size: 0.55,
overlap: 0.45,
},
trust: {
audience_relevancy: 0.7,
relative_growth: 0.4,
consistency: 0.6,
engagement_rate: 0.8,
sentiment: 0.74,
},
influence_score: 44340,
top_stats: {
age: '24-45: 84%',
gender: '68% Male',
country: 'UK: 32%',
}
}]
Insert cell
html`<div>${card2get(influencers[0])}<div>.</div></div>`
Insert cell
style = html`<style>
.p_card {
padding: 20px;
border: 1px solid #ddd;
margin: 20px;
}
.p_card > .inner {
display: grid;
grid-template-areas: "void side" "bottom bottom";
grid-template-rows: 1fr auto;
grid-template-columns: 1fr auto;
position: relative;
}

.p_card .p_side {
grid-area: side;
}
.p_card .p_bottom {
grid-area: bottom;
}

.p_card .p_image {
background-size: cover;
background-repeat: no-repeat;
padding-top: 40%;
border-radius: 10000px;
position: absolute;
width: 40%;
top: 0px;
left: 0px;
opacity: 0.5;
}



.p_section {
margin-bottom: 20px;
}
.p_section > .row {
display: flex;
flex-flow: row;
}
.p_section > .row > span {
flex-basis: 1;
}
.p_section > .row > span:first-child {
width: 200px;
}


.pbar {
display: inline-block;
width: 200px;
background: #ddd;
height: 12px;
border-radius: 100px;
border: 4px solid #ddd;
position: relative;
}
.pbar > .track {
position: absolute;
height: 100%;
background-color: #777;
border-radius: 100px;
}
.pbar > .label {
position: absolute;
right: 5px;
font-size: 12px;
top: -3px;
}

</style>`
Insert cell
card2get = d=> {
const attention_section = p_section(d, {
title: 'Attention',
rows: [{
title: 'Relative audience size',
v_get: d=> d.attention.relative_size,
}, {
title: 'Audience overlap',
v_get: d=> d.attention.overlap,
}],
})

const trust_section = p_section(d, {
title: 'Trust',
rows: [{
title: 'Audience relevancy',
v_get: d=> d.trust.audience_relevancy,
}, {
title: 'Relative growth',
v_get: d=> d.trust.relative_growth,
}, {
title: 'Consistency',
v_get: d=> d.trust.consistency,
}, {
title: 'Engagement rate',
v_get: d=> d.trust.engagement_rate,
}, {
title: 'Sentiment',
v_get: d=> d.trust.sentiment,
}],
})

const side = html`<div class="p_side">
${attention_section}
${trust_section}
<h3>Influence score: ${d.influence_score}</h3>
</div>`
const bottom = html`<div class="p_bottom">
<h1>${d.yt.title}</h3>
</div>`
const image = html`<div class="p_image" style="background-image: url(${d.yt.image_high});"></div>`
return html`<div class="p_card">
<div class="inner">
${image}
${side}
${bottom}
</div>
</div>`
}
Insert cell
p_section_row = (d, row)=> html`<div class="row">
<span>${row.title}</span>
<span>${pbar_get(row.v_get(d))}</span>
</div>`

Insert cell
p_section = (d, section)=> html`<div class="p_section">
<h3>${section.title}</h3>
${section.rows.map(r=> p_section_row(d, r))}
</div>`
Insert cell
pbar_get = (p)=> {
const el = html`<div class="pbar">
<div class="track" style="width: ${p*100}%;"></div>
<span class="label">${(p*100).toPrecision(2)}%</span>
</div>`
return el
}
Insert cell
pbar_get(0.7)
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