Public
Edited
Mar 27, 2023
Insert cell
Insert cell
totalViews = postsData.filter(post => post.visibility > 0).reduce((accumulator, currentVideo) => {
return accumulator + currentVideo.internalReferrerViews;
}, 0);
Insert cell
//totalEarn = postsData.filter(post => (post.visibility > 0) && ((post.earnings/100) / post.internalReferrerViews * 1000) > 70)
postsData.filter(post => (post.visibility > 0))
Insert cell
postsData = FileAttachment("medium-stats@1.json").json()
Insert cell
chart = Histogram(postsData.filter(post => post.visibility > 0) , {
//value: d => (d.earnings/100),
//value: d => (d.earnings/100) / d.views * 1000,
value: d => (d.earnings/100) / d.internalReferrerViews * 1000,
label: "$US per article (%) →",
width: 720,
height: 200,
//color: "limegreen",
domain:[0,80],
color: "#badbb9",
//stroke: "green"
})
Insert cell
chart2 = Histogram(postsData.filter(post => post.visibility > 0) , {
value: d => (d.earnings/100),
//value: d => (d.earnings/100) / (d.reads * d.readingTime) * 60,
// value: d => (d.earnings/100) / d.internalReferrerViews * 1000,
//label: "$US per article (%) →",
width: 800,
height: 300,
domain: [0,450],
color: "#badbb9"
})
Insert cell
barChart = BarChart(postsData.filter(post => post.visibility > 0) , {
x: d => d.readingTime,
y: d => d.earnings / 100 / d.internalReferrerViews *1000,
xDomain: [1,2,3,4,5,6,7,8,9,10,11,12,13], // sort by descending frequency
//yFormat: "%",
yLabel: "↑ Frequency",
label: "Average reading time (mins) →",
width: 600,
height: 200,
color: "#badbb9"
})
Insert cell
{
postsData.filter(post => post.visibility > 0).filter(
}
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/histogram
function Histogram(data, {
value = d => d, // convenience alias for x
domain, // convenience alias for xDomain
label, // convenience alias for xLabel
format, // convenience alias for xFormat
type = d3.scaleLinear, // convenience alias for xType
x = value, // given d in data, returns the (quantitative) x-value
y = () => 1, // given d in data, returns the (quantitative) weight
thresholds = 40, // approximate number of bins to generate, or threshold function
normalize, // whether to normalize values to a total of 100%
marginTop = 20, // top margin, in pixels
marginRight = 30, // right margin, in pixels
marginBottom = 30, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
width = 640, // outer width of chart, in pixels
height = 200, // outer height of chart, in pixels
insetLeft = 0.5, // inset left edge of bar
insetRight = 0.5, // inset right edge of bar
xType = type, // type of x-scale
xDomain = domain, // [xmin, xmax]
xRange = [marginLeft, width - marginRight], // [left, right]
xLabel = label, // a label for the x-axis
xFormat = format, // a format specifier string for the x-axis
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
yLabel = "↑ Frequency", // a label for the y-axis
yFormat = normalize ? "%" : undefined, // a format specifier string for the y-axis
color = "currentColor", // bar fill color
stroke,
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y0 = d3.map(data, y);
const I = d3.range(X.length);

// Compute bins.
const bins = d3.bin().thresholds(thresholds).value(i => X[i])(I);
const Y = Array.from(bins, I => d3.sum(I, i => Y0[i]));
if (normalize) {
const total = d3.sum(Y);
for (let i = 0; i < Y.length; ++i) Y[i] /= total;
}

// Compute default domains.
if (xDomain === undefined) xDomain = [bins[0].x0, bins[bins.length - 1].x1];
if (yDomain === undefined) yDomain = [0, d3.max(Y)];

// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 80, xFormat).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);
yFormat = yScale.tickFormat(100, yFormat);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));

svg.append("g")
.attr("fill", color)
//.attr("stroke", stroke)
.selectAll("rect")
.data(bins)
.join("rect")
.attr("x", d => xScale(d.x0) + insetLeft)
.attr("width", d => Math.max(0, xScale(d.x1) - xScale(d.x0) - insetLeft - insetRight))
.attr("y", (d, i) => yScale(Y[i]))
.attr("height", (d, i) => yScale(0) - yScale(Y[i]))
.append("title")
.text((d, i) => [`${d.x0} ≤ x < ${d.x1}`, yFormat(Y[i])].join("\n"));

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.call(g => g.append("text")
.attr("x", width - marginRight)
.attr("y", 27)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xLabel));

return svg.node();
}
Insert cell
scatter = Scatterplot(postsData.filter(post => post.visibility > 0), {
//x: d => d.views,
x: d => d.internalReferrerViews,
//x: d => d.views,
y: d => d.earnings / 100,
title: d => d.name,
xLabel: "Views →",
yLabel: "↑ Earnings",
stroke: "steelblue",
width: 400,
height: 400,
xDomain: [0,2000],
yDomain: [0,100]
})
Insert cell
scatter_reads = Scatterplot(postsData.filter(post => post.visibility > 0), {
//x: d => d.views,
//x: d => d.internalReferrerViews,
x: d => (d.readingTime * d.internalReferrerViews / 60),
y: d => d.earnings / 100,
title: d => d.name,
xLabel: "Reading time (hours) →",
yLabel: "↑ Earnings",
stroke: "none",
fill: "limegreen",
width: 400,
height: 400,
xDomain: [0,200],
yDomain: [0,100]
})
Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/scatterplot
function Scatterplot(data, {
x = ([x]) => x, // given d in data, returns the (quantitative) x-value
y = ([, y]) => y, // given d in data, returns the (quantitative) y-value
r = 3, // (fixed) radius of dots, in pixels
title, // given d in data, returns the title
marginTop = 20, // top margin, in pixels
marginRight = 30, // right margin, in pixels
marginBottom = 30, // bottom margin, in pixels
marginLeft = 40, // left margin, in pixels
inset = r * 2, // inset the default range, in pixels
insetTop = inset, // inset the default y-range
insetRight = inset, // inset the default x-range
insetBottom = inset, // inset the default y-range
insetLeft = inset, // inset the default x-range
width = 640, // outer width, in pixels
height = 400, // outer height, in pixels
xType = d3.scaleLinear, // type of x-scale
xDomain, // [xmin, xmax]
xRange = [marginLeft + insetLeft, width - marginRight - insetRight], // [left, right]
yType = d3.scaleLinear, // type of y-scale
yDomain, // [ymin, ymax]
yRange = [height - marginBottom - insetBottom, marginTop + insetTop], // [bottom, top]
xLabel, // a label for the x-axis
yLabel, // a label for the y-axis
xFormat, // a format specifier string for the x-axis
yFormat, // a format specifier string for the y-axis
fill = "none", // fill color for dots
stroke = "currentColor", // stroke color for the dots
strokeWidth = 1.5, // stroke width for dots
halo = "#fff", // color of label halo
haloWidth = 3 // padding around the labels
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);
const T = title == null ? null : d3.map(data, title);
const I = d3.range(X.length).filter(i => !isNaN(X[i]) && !isNaN(Y[i]));

// Compute default domains.
if (xDomain === undefined) xDomain = d3.extent(X);
if (yDomain === undefined) yDomain = d3.extent(Y);

// Construct scales and axes.
const xScale = xType(xDomain, xRange);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).ticks(width / 80, xFormat);
const yAxis = d3.axisLeft(yScale).ticks(height / 50, yFormat);

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("y2", marginTop + marginBottom - height)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", width)
.attr("y", marginBottom - 4)
.attr("fill", "currentColor")
.attr("text-anchor", "end")
.text(xLabel));

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));

if (T) svg.append("g")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.selectAll("text")
.data(I)
.join("text")
.attr("dx", 7)
.attr("dy", "0.35em")
.attr("x", i => xScale(X[i]))
.attr("y", i => yScale(Y[i]))
.text(i => T[i])
.call(text => text.clone(true))
.attr("fill", "none")
.attr("stroke", halo)
.attr("stroke-width", haloWidth);

svg.append("g")
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.selectAll("circle")
.data(I)
.join("circle")
.attr("cx", i => xScale(X[i]))
.attr("cy", i => yScale(Y[i]))
.attr("r", r);

return svg.node();
}

Insert cell
// Copyright 2021 Observable, Inc.
// Released under the ISC license.
// https://observablehq.com/@d3/bar-chart
function BarChart(data, {
x = (d, i) => i, // given d in data, returns the (ordinal) x-value
y = d => d, // given d in data, returns the (quantitative) y-value
title, // given d in data, returns the title text
marginTop = 20, // the top margin, in pixels
marginRight = 0, // the right margin, in pixels
marginBottom = 30, // the bottom margin, in pixels
marginLeft = 40, // the left margin, in pixels
width = 640, // the outer width of the chart, in pixels
height = 400, // the outer height of the chart, in pixels
xDomain, // an array of (ordinal) x-values
xRange = [marginLeft, width - marginRight], // [left, right]
yType = d3.scaleLinear, // y-scale type
yDomain, // [ymin, ymax]
yRange = [height - marginBottom, marginTop], // [bottom, top]
xPadding = 0.1, // amount of x-range to reserve to separate bars
yFormat, // a format specifier string for the y-axis
yLabel, // a label for the y-axis
color = "currentColor" // bar fill color
} = {}) {
// Compute values.
const X = d3.map(data, x);
const Y = d3.map(data, y);

// Compute default domains, and unique the x-domain.
if (xDomain === undefined) xDomain = X;
if (yDomain === undefined) yDomain = [0, d3.max(Y)];
xDomain = new d3.InternSet(xDomain);

// Omit any data not present in the x-domain.
const I = d3.range(X.length).filter(i => xDomain.has(X[i]));

// Construct scales, axes, and formats.
const xScale = d3.scaleBand(xDomain, xRange).padding(xPadding);
const yScale = yType(yDomain, yRange);
const xAxis = d3.axisBottom(xScale).tickSizeOuter(0);
const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat);

// Compute titles.
if (title === undefined) {
const formatValue = yScale.tickFormat(100, yFormat);
title = i => `${X[i]}\n${formatValue(Y[i])}`;
} else {
const O = d3.map(data, d => d);
const T = title;
title = i => T(O[i], i, data);
}

const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; height: intrinsic;");

svg.append("g")
.attr("transform", `translate(${marginLeft},0)`)
.call(yAxis)
.call(g => g.select(".domain").remove())
.call(g => g.selectAll(".tick line").clone()
.attr("x2", width - marginLeft - marginRight)
.attr("stroke-opacity", 0.1))
.call(g => g.append("text")
.attr("x", -marginLeft)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(yLabel));

const bar = svg.append("g")
.attr("fill", color)
.selectAll("rect")
.data(I)
.join("rect")
.attr("x", i => xScale(X[i]))
.attr("y", i => yScale(Y[i]))
.attr("height", i => yScale(0) - yScale(Y[i]))
.attr("width", xScale.bandwidth());

if (title) bar.append("title")
.text(title);

svg.append("g")
.attr("transform", `translate(0,${height - marginBottom})`)
.call(xAxis);

return svg.node();
}
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