Published
Edited
Oct 23, 2020
Insert cell
Insert cell
{
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g")
.call(xAxis);

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

svg.append("g")
.attr("transform", `translate(50,800`)
.attr("stroke", "#000")
.attr("stroke-opacity", 0.2)
.selectAll("circle")
.data(data)
.join("circle")
.attr("cx", d => x(d.date))
.attr("cy", d => y(d.value))
.attr("fill", d => z(d.value))
.attr("r", 4);
svg.append("path")
.datum(smoothdata)
.attr("fill", "none")
.attr("stroke", "#000")
.attr("stroke-width", 2)
.attr("stroke-linejoin", "round")
.attr("d", line);
const tooltip = svg.append("g");
svg.on("touchmove mousemove", function(event) {
const {date, value,text} = bisect(d3.pointer(event, this)[0]);

tooltip
.attr("transform", `translate(${x(date)},${y(value)})`)
.call(callout, `${text}
${formatDate(date)}`);
});

svg.on("touchend mouseleave", () => tooltip.call(callout, null));
return svg.node();
}
Insert cell
x = d3.scaleTime()
.domain(d3.extent(data, d => d.date))
.range([margin.left, width - margin.right])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).ticks(width / 80))
.call(g => g.select(".domain").remove())
Insert cell
y = d3.scaleLinear()
.domain([-1, 1])
.range([height - margin.bottom -30, margin.top +30])
Insert cell
yAxis = g => g
.attr("transform", `translate(60,0)`)
.call(d3.axisLeft(y).ticks(null, "+"))
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line")
.filter(d => d === 0)
.clone()
.attr("x2", width - margin.right - margin.left+60)
.attr("stroke", "#ccc"))
.call(g => g.append("text")
.attr("fill", "#000")
.attr("x", -55)
.attr("y", margin.bottom)
.attr("dy", "0.32em")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Positive Emotions"))
.call(g => g.append("text")
.attr("fill", "#000")
.attr("x", -55)
.attr("y", 510)
.attr("dy", "0.32em")
.attr("text-anchor", "start")
.attr("font-weight", "bold")
.text("Negative Emotions"))
Insert cell
datainjson = FileAttachment("Amit_Goldenb.json").json()
Insert cell
excludedrts = datainjson.filter(function(ort) {
return ort.is_retweet == false;
});
Insert cell
englishtweets = excludedrts.filter(function(language) {
return language.lang == "en";
});
Insert cell
data = {
const data =[];
for (let i = englishtweets.length; i > 0; --i) {
data.push({
date: parser(englishtweets[i-1].created_at),
value: englishtweets[i-1].compound,
text: englishtweets[i-1].full_text,
category: englishtweets[i-1].category
});
}
return data
}
Insert cell
parser = d3.timeParse("%Y-%m-%d %H:%M:%S")
Insert cell
z = {
const max = d3.max(data, d => Math.abs(d.value));
return d3.scaleSequential(d3.interpolateRdBu).domain([-max, max]);
}
Insert cell
yvalues = {
const yvalues =[];
for (let i = 0; i < data.length; ++i) {
yvalues.push(data[i].value)
}
return yvalues
}
Insert cell
smoothy = smooth(yvalues, numberofpoints)
Insert cell
smoothx={
let smoothx =[];
for (let i = 0; i < smoothy.length; ++i) {
smoothx.push(data[Math.trunc(i*index_date)].date)
}
smoothx[smoothy.length-1]= data[data.length-1].date
return smoothx
}
Insert cell
smoothdata = {
const smoothdata =[];
for (let i = 0; i < smoothy.length; ++i) {
smoothdata.push({
date: smoothx[i],
value: smoothy[i]
});
}
return smoothdata
}
Insert cell
index_date = (data.length-1)/(smoothy.length)
Insert cell
function smooth(data, resolution) {
if (resolution < data.length) {
data = SMA(data, Math.trunc(data.length / resolution),
Math.trunc(data.length / resolution));
}
var metrics = new Metrics(data);
var originalKurt = metrics.kurtosis();
var minObj = metrics.roughness();
var windowSize = 1;
for (var w = Math.round(data.length / 10);
w >= 2; w -=1) {
var smoothed = SMA(data, w, 1);
metrics = new Metrics(smoothed);
var roughness = metrics.roughness();
if (roughness < minObj) {
if (metrics.kurtosis() >= originalKurt) {
minObj = roughness;
windowSize = w;
}
}
}
return SMA(data, windowSize, 1);
}

Insert cell
function SMA(data, range, slide) {
var windowStart = 0;
var sum = 0;
var count = 0;
var values = [];

for (var i = 0; i < data.length; i ++) {
if (isNaN(data[i])) { data[i] = 0; }
if (i - windowStart >= range) {
values.push(sum / count);
var oldStart = windowStart;
while (windowStart < data.length && windowStart - oldStart < slide) {
sum -= data[windowStart];
count -= 1;
windowStart += 1;
}
}
sum += data[i];
count += 1;
}
if (count == range) {
values.push(sum / count);
}
return values;
}
Insert cell
class Metrics {
constructor(values) {
this.len = values.length;
this.values = values;
this.m = Metrics.mean(values);
}

static mean(values) {
var m = 0;
for (var i = 0; i < values.length; i += 1) {
m += values[i];
}
return m / values.length;
}

static std(values) {
var m = Metrics.mean(values);
var std = 0;
for (var i = 0; i < values.length; i += 1) {
std += Math.pow((values[i] - m), 2);
}
return Math.sqrt(std / values.length);
}

kurtosis() {
var u4 = 0, variance = 0;
for (var i = 0; i < this.len; i ++) {
u4 += Math.pow((this.values[i] - this.m), 4);
variance += Math.pow((this.values[i] - this.m), 2);
}
return this.len * u4 / Math.pow(variance, 2);
}

roughness() {
return Metrics.std(this.diffs());
}

diffs() {
var diff = new Array(this.len - 1);
for (var i = 1; i < this.len; i += 1) {
diff[i - 1] = this.values[i] - this.values[i - 1];
}
return diff;
}
}
Insert cell
line = d3.line()
.curve(d3.curveBundle)
.x(d => x(d.date))
.y(d => y(d.value))

Insert cell
callout = (g, value) => {
if (!value) return g.style("display", "none");

g
.style("display", null)
.style("pointer-events", "none")
.style("font", "10px sans-serif");

const path = g.selectAll("path")
.data([null])
.join("path")
.attr("fill", "white")
.attr("stroke", "black");

const text = g.selectAll("text")
.data([null])
.join("text")
.call(text => text
.selectAll("tspan")
.data((value + "").split(/\n/))
.join("tspan")
.attr("x", 0)
.attr("y", (d, i) => `${i * 1.1}em`)
.style("font-weight", (_, i) => i ? null : "bold")
.text(d => d));

const {x, y, width: w, height: h} = text.node().getBBox();

text.attr("transform", `translate(${-w / 2},${15 - y})`);
path.attr("d", `M${-w / 2 - 10},5H-5l5,-5l5,5H${w / 2 + 10}v${h + 20}h-${w + 20}z`);
}
Insert cell
bisect = {
const bisect = d3.bisector(d => d.date).left;
return mx => {
const date = x.invert(mx);
const index = bisect(data, date, 1);
const a = data[index - 1];
const b = data[index];
return b && (date - a.date > b.date - date) ? b : a;
};
}
Insert cell
function formatDate(date) {
return date.toLocaleString("en", {
month: "short",
day: "numeric",
year: "numeric",
timeZone: "UTC"
});
}
Insert cell
d3 = require("d3@6")
Insert cell
margin = ({top: 20, right: 30, bottom: 30, left: 120})
Insert cell
height = 550
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