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

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more