Published unlisted
Edited
Apr 13, 2021
Insert cell
Insert cell
Insert cell
d3.csvParse(await FileAttachment("testing_output_v8@1.csv").text(), d3.autoType)
Insert cell
data = {
const text = await FileAttachment("testing_output_v8@1.csv").text();
const parseDate1 = d3.utcParse(" %H:%M:%S.%f");
const parseDate2 = d3.utcParse("%Y-%m-%d %H:%M:%S"); // some dates do not have partial seconds,
const parseDateOuter = function(timestamp){ // this function handles both date parsers
var date1 = parseDate1(timestamp);
if (date1 == null){
return parseDate2(timestamp)
}
return date1;
}
return d3.csvParse(text, ({platform, timestamp, polarity, subjectivity, word_count, sent_or_received, other_person}) => ({
platform: platform,
date: parseDateOuter(timestamp),
polarity: polarity,
subjectivity: subjectivity,
word_count: word_count,
sent_or_received: sent_or_received,
contact: other_person
}));
}
Insert cell
// check for null dates
nullDates = data.filter( x =>
x.date == null );
Insert cell
// notebook is too slow with 68k data points. Downselect based on the most contacted people
// create dict with key:value => contact:number of messages
tally_messages = function(msgs)
{
var tally = {};
for (var i = 0; i < msgs.length; i++){
if (msgs[i].contact in tally){
tally[msgs[i].contact] = tally[msgs[i].contact] + 1;
} else{
tally[msgs[i].contact] = 1;
}
}
return tally;
}
Insert cell
// count up the number of exchanged messages
myTally = tally_messages(data)
// this might be an interesting bubble chart by itself

Insert cell
// turn into a sort-able array
to_array = function(m_dict){
var to_ret = []
for (var attrib in m_dict){
to_ret.push({"contact":attrib, "count":m_dict[attrib]})
}
return to_ret
}
Insert cell
myTallyArr = to_array(myTally)
Insert cell
// sort the array
myTallyArr.sort((b, a) => {
return a.count - b.count;
});
Insert cell
// might be interesting to visualize the distribution of message count across contacts
// How evenly distributed are my social interactions?
// let's make a histogram
hist_margin = ({top: 10, right: 30, bottom: 30, left: 40});
Insert cell
hist_width = 460 - margin.left - margin.right;
Insert cell
hist_height = 400 - margin.top - margin.bottom;
Insert cell
hist_var = "count"
Insert cell
bins = {
const bin = d3.bin().value(d => d.count).thresholds(100);
return bin(myTallyArr);
}
Insert cell
bins[9].x0
Insert cell
hist_x = d3.scaleLinear()
.domain([bins[0].x0, bins[bins.length - 1].x1])
.range([hist_margin.left, hist_width - hist_margin.right])
Insert cell
hist_y = d3.scaleLinear()
.domain([0, d3.max(bins, d => d.length)]).nice()
.range([hist_height - hist_margin.bottom, hist_margin.top])
Insert cell
hist_xAxis = g => g
.attr("transform", `translate(0,${hist_height - hist_margin.bottom})`)
.call(d3.axisBottom(hist_x).ticks(hist_width / 60 ).tickSizeOuter(0))
.call(g => g.append("text")
.attr("x", hist_width - hist_margin.right)
.attr("y", -4)
.attr("fill", "currentColor")
.attr("font-weight", "bold")
.attr("text-anchor", "end")
.text(data.x))
Insert cell
hist_yAxis = g => g
.attr("transform", `translate(${hist_margin.left},0)`)
.call(d3.axisLeft(hist_y).ticks(hist_height / 40))
.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))
Insert cell
// I finally get it!! Everything is based off the data...
// simply changing the bins data structure changes literally everything about the viz... scales, axis, etc...
// so a function that modifies the bins data structure could use inputs from a control, making the viz controllable
chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, hist_width, hist_height]);
svg.append("g")
.attr("fill", "steelblue")
.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", d => hist_x(d.x0) + 1)
.attr("width", d => Math.max(0, hist_x(d.x1) - hist_x(d.x0) - 0.1))
.attr("y", d => hist_y(d.length))
.attr("height", d => hist_y(0) - hist_y(d.length));
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", hist_width-125)
.attr("y", hist_height - 4)
.attr("font-size", "9px")
.text("Quantity of Messages Exchanged");
svg.append("text")
.attr("class", "title")
.attr("text-anchor", "end")
.attr("x", hist_width-80)
.attr("y", 15)
.attr("font-size", "10px")
.text("Histogram of Total Message Count by Contact");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 2)
.attr("x", -120)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.attr("font-size", "9px")
.text("Count of Contacts");
svg.append("g")
.call(hist_xAxis);
svg.append("g")
.call(hist_yAxis);

return svg.node();
}
Insert cell
// Let's make a bubble chart to show the relative sizes of contacts
Insert cell
num_contacts = 150; // start with dunbar number
Insert cell
top_contacts = myTallyArr.slice(0, num_contacts)
Insert cell
// pack circles of the top contacts
Insert cell
// prep sizes
margin = ({top: 25, right: 20, bottom: 35, left: 40})
Insert cell
width = 750
Insert cell
height = 600
Insert cell
x_var = "date"
Insert cell
y_var = "word_count"
Insert cell
// start with something simple - maybe just an xy plot of word_count vs. time. That would be interesting
x_scale_time = d3.scaleTime()
.domain([d3.min(data, d => d[x_var]), d3.max(data, d => d[x_var])])
.range([margin.left, width - margin.right])
Insert cell
d3.min(data, d => d[x_var])
Insert cell
d3.max(data, d => d[x_var])
Insert cell
x_axis_time = g => g
.attr("transform", `translate(0, ${height - margin.bottom})`)
.call(d3.axisBottom(x_scale_time))
Insert cell
y_scale_word_count = d3.scaleLinear()
.domain([0, d3.max(data, d => d[y_var])])
.range([height - margin.bottom, margin.top])
Insert cell
y_axis_word_count = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y_scale_word_count))
Insert cell
// color scale for sentiment
sentiment_color_scale = d3.scaleLinear().domain([-1,1])
.range(["white", "blue"])
Insert cell
sentiment_color_scale(0)
Insert cell
// make the scatterplot
// make the svg canvas that we will bind the axis to

scatterplot = {
const svg = d3.create("svg")
.attr("viewBox", [0,0, width, height]);
svg.append("g")
.call(x_axis_time);
svg.append("g")
.call(y_axis_word_count);
svg.append("g")
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => x_scale_time(d[x_var]))
.attr("cy", d => y_scale_word_count(d[y_var]))
.attr("r", 1)
.attr("opacity", .5);
//.attr("color", "rgb(0, 0, 255)")
//.attr("color", d => sentiment_color_scale(d.polarity));
svg.append("text")
.attr("class", "x label")
.attr("text-anchor", "end")
.attr("x", width-350)
.attr("y", height - 4)
.attr("font-size", "12px")
.attr("font-family", "Helvetica")
.text("Date");
svg.append("text")
.attr("class", "title")
.attr("text-anchor", "end")
.attr("x", width-200)
.attr("y", 15)
.attr("font-size", "14px")
.attr("font-family", "Helvetica")
.text("Scatterplot of Message Word Count and Date");
svg.append("text")
.attr("class", "y label")
.attr("text-anchor", "end")
.attr("y", 2)
.attr("x", -200)
.attr("dy", ".75em")
.attr("transform", "rotate(-90)")
.attr("font-size", "12px")
.attr("font-family", "Helvetica")
.text("Message Word Count");
return svg.node(); // returns handle to svg
}
Insert cell
chart_weekly = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.style("overflow", "visible");

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);

const path = svg.append("g")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.selectAll("path")
.data(data2.series)
.join("path")
.style("mix-blend-mode", "multiply")
.attr("d", d => line(d.values));

svg.call(hover, path);

return svg.node();
}
Insert cell
data2 = {
const data2 = d3.csvParse(await FileAttachment("weekly_time_series@3.csv").text());
const columns = data2.columns.slice(1);
return {
y: "Weekly Message Exchanges",
series: data2.map(d => ({
name: d.other_person,
values: columns.map(k => +d[k])
})),
dates: columns.map(d3.utcParse("%Y-%m-%d"))
};
}
Insert cell
function hover(svg, path) {
if ("ontouchstart" in document) svg
.style("-webkit-tap-highlight-color", "transparent")
.on("touchmove", moved)
.on("touchstart", entered)
.on("touchend", left)
else svg
.on("mousemove", moved)
.on("mouseenter", entered)
.on("mouseleave", left);

const dot = svg.append("g")
.attr("display", "none");

dot.append("circle")
.attr("r", 2.5);

dot.append("text")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("text-anchor", "middle")
.attr("y", -8);

function moved(event) {
event.preventDefault();
const pointer = d3.pointer(event, this);
const xm = x.invert(pointer[0]);
const ym = y.invert(pointer[1]);
const i = d3.bisectCenter(data2.dates, xm);
const s = d3.least(data2.series, d => Math.abs(d.values[i] - ym));
path.attr("stroke", d => d === s ? null : "#ddd").filter(d => d === s).raise();
dot.attr("transform", `translate(${x(data2.dates[i])},${y(s.values[i])})`);
dot.select("text").text(s.name);
}

function entered() {
path.style("mix-blend-mode", null).attr("stroke", "#ddd");
dot.attr("display", null);
}

function left() {
path.style("mix-blend-mode", "multiply").attr("stroke", null);
dot.attr("display", "none");
}
}
Insert cell
height_m = 600
Insert cell
margin_m = ({top: 20, right: 20, bottom: 30, left: 30})
Insert cell
x = d3.scaleUtc()
.domain(d3.extent(data2.dates))
.range([margin_m.left, width - margin_m.right])
Insert cell
y = d3.scaleLinear()
.domain([0, d3.max(data2.series, d => d3.max(d.values))]).nice()
.range([height_m - margin_m.bottom, margin_m.top])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height_m - margin_m.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80).tickSizeOuter(0))
Insert cell
yAxis = g => g
.attr("transform", `translate(${margin_m.left},0)`)
.call(d3.axisLeft(y))
.call(g => g.select(".domain").remove())
.call(g => g.select(".tick:last-of-type text").clone()
.attr("x", 3)
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text(data2.y))
Insert cell
line = d3.line()
.defined(d => !isNaN(d))
.x((d, i) => x(data2.dates[i]))
.y(d => y(d))
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