Public
Edited
Feb 13, 2024
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
makeChart1(data)
Insert cell
makeChart1 = (dataset) => {
const w = 600;
const h = dataset.length * 24;
// We'll return chart1Svg.node() from this function
const chart1Svg = d3.create("svg").attr("width", w).attr("height", h);

// sort the data by downloads
// uses built-in Array.sort() with comparator function
dataset.sort((a, b) => b.downloads - a.downloads);

// our range is limited from 0 to width - 100,
// which is for the 80 pixels on left for axis and
// 20 pixels on right for padding
const xScale = d3
.scaleLinear()
.domain([0, d3.max(dataset, (d) => d.downloads)])
.rangeRound([0, w - 100]);

// using scale band to work with nominal values
// the Array.map() call allows us to get a new array
// by calling a function on each item of the source array
// here it pulls out the app_name
const yScale = d3
.scaleBand()
.domain(dataset.map((d) => d.app_name))
.rangeRound([20, h - 20]);

// d3 allows scaling between colors
// This is here for demonstrating that you *can* do this, though
// in this instance, it's not a particularly good use of color.
// In your assignment, I'd question using a color scale here to
// visualize a third data variable and instead replace this with
// either static values, or change the domain/range to apply to
// a data variable already in use in the chart to reinforce a
// a data variable visually.
const colorScale = d3.scaleLinear().domain([4.5, 5]).range(["#FF0000", "#FFFFFF"]);

chart1Svg
.selectAll("rect")
.data(dataset)
.join("rect")
.attr("x", 80)
.attr("y", (d) => yScale(d.app_name))
.attr("width", (d) => xScale(d.downloads))
.attr("height", 15)
.attr("fill", (d) => colorScale(d.average_rating));

// AXES
chart1Svg
.append("g")
.attr("class", "axis")
.attr("transform", `translate(80, ${h - 20})`)
.call(d3.axisBottom(xScale));

chart1Svg
.append("g")
.attr("class", "axis")
.attr("transform", `translate(80,0)`)
.style("font-size", "12px")
.call(d3.axisLeft(yScale).tickSize(0));

return chart1Svg.node();
}
Insert cell
Insert cell
makeChart2 = (dataset) => {
const w = 600;
const h = dataset.length * 24;
const chart2Svg = d3.create("svg")
.attr('width', w)
.attr('height', h);

// FILL IN HERE, and observe changes below
dataset.sort((a, b) => b.average_rating - a.average_rating);

const xScale = d3
.scaleLinear()
.domain([4.4, d3.max(dataset, (d) => d.average_rating)])
.rangeRound([0, w - 100]);

const yScale = d3
.scaleBand()
.domain(dataset.map((d) => d.app_name))
.rangeRound([20, h - 20]);

// Use a different color scale for makeChart2
const colorScale2 = d3.scaleLinear().domain([4.5, 5]).range(["#00FF00", "#0000FF"]);

chart2Svg
.selectAll("rect")
.data(dataset)
.join("rect")
.attr("x", 80)
.attr("y", (d) => yScale(d.app_name))
.attr("width", (d) => xScale(d.average_rating))
.attr("height", 15)
.attr("fill", (d) => colorScale2(d.average_rating));

// AXES - Note the change in axis and transform for xScale
chart2Svg
.append("g")
.attr("class", "axis")
.attr("transform", `translate(80, ${h - 20})`)
.call(d3.axisBottom(xScale).ticks(5)); // Adjust the number of ticks as needed

chart2Svg
.append("g")
.attr("class", "axis")
.attr("transform", `translate(80,0)`)
.style("font-size", "12px")
.call(d3.axisLeft(yScale).tickSize(0));

return chart2Svg.node();
}
Insert cell
makeChart2(data)
Insert cell
Insert cell
makeChart3 = (dataset) => {
const w = 600;
const h = dataset.length * 24;
const chart3Svg = d3.create("svg")
.attr('width', w)
.attr('height', h);

// FILL IN HERE, and observe changes below
dataset.sort((a, b) => b.thirty_day_keep - a.thirty_day_keep);

const xScale = d3
.scaleLinear()
.domain([70, d3.max(dataset, (d) => d.thirty_day_keep)])
.rangeRound([0, w - 100]);

const yScale = d3
.scaleBand()
.domain(dataset.map((d) => d.app_name))
.rangeRound([20, h - 20]);

// Use a different color scale for makeChart3
const colorScale3 = d3.scaleLinear().domain([65, 100]).range(["#FFA500", "#00FF00"]);

chart3Svg
.selectAll("rect")
.data(dataset)
.join("rect")
.attr("x", 80)
.attr("y", (d) => yScale(d.app_name))
.attr("width", (d) => xScale(d.thirty_day_keep))
.attr("height", 15)
.attr("fill", (d) => colorScale3(d.thirty_day_keep));

// AXES - Note the change in axis and transform for xScale
chart3Svg
.append("g")
.attr("class", "axis")
.attr("transform", `translate(80, ${h - 20})`)
.call(d3.axisBottom(xScale).tickFormat(d => `${d}%`)); // Format ticks as percentages

chart3Svg
.append("g")
.attr("class", "axis")
.attr("transform", `translate(80,0)`)
.style("font-size", "12px")
.call(d3.axisLeft(yScale).tickSize(0));
return chart3Svg.node();
}
Insert cell
makeChart3(data)
Insert cell
Insert cell
makeChart4 = (dataset) => {
const w = 600;
const h = dataset.length * 24;
const chart4Svg = d3.create("svg")
.attr('width', w)
.attr('height', h);

// FILL IN HERE, and observe changes below
// Create scales for x and y axes
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d.downloads)])
.range([80, w - 20]); // Adjust the range for proper positioning

const yScale = d3.scaleLinear()
.domain([4.5, d3.max(dataset, (d) => d.average_rating)])
.range([h - 20, 20]); // Adjust the range for proper positioning

// Create a scale for circle radius based on rating
const radiusScale = d3.scaleSqrt()
.domain([4.5, d3.max(dataset, (d) => d.average_rating)])
.range([2, 20]); // Adjust the range for proper scaling

// Create circles for each data point
chart4Svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.downloads))
.attr("cy", (d) => yScale(d.average_rating))
.attr("r", (d) => radiusScale(d.average_rating))
.attr("fill", "steelblue")
.attr("opacity", 0.7);

// Label the dots with app_name
chart4Svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x", (d) => xScale(d.downloads))
.attr("y", (d) => yScale(d.average_rating) - radiusScale(d.average_rating) + 20) // Adjust the position for proper placement
.text((d) => d.app_name)
.style("font-size", "8px")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle");

// Create x-axis
chart4Svg.append("g")
.attr("class", "axis")
.attr("transform", `translate(0, ${h - 20})`)
.call(d3.axisBottom(xScale));

// Create y-axis
chart4Svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(80, 0)")
.call(d3.axisLeft(yScale));
return chart4Svg.node();
}
Insert cell
makeChart4(data)
Insert cell
Insert cell
makeChart5 = (dataset) => {
const w = 600;
const h = dataset.length * 24;
const chart5Svg = d3.create("svg")
.attr('width', w)
.attr('height', h);

// FILL IN HERE, and observe changes below
const xScale = d3.scaleLinear()
.domain([0, d3.max(dataset, (d) => d.downloads)])
.range([80, w - 20]);

const yScale = d3.scaleLinear()
.domain([70, d3.max(dataset, (d) => d.thirty_day_keep)])
.range([h - 20, 20]);

// Create a scale for circle radius based on thirty_day_keep
const radiusScaleRetention = d3.scaleSqrt()
.domain([70, d3.max(dataset, (d) => d.thirty_day_keep)])
.range([2, 20]);

// Create circles for each data point
chart5Svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", (d) => xScale(d.downloads))
.attr("cy", (d) => yScale(d.thirty_day_keep))
.attr("r", (d) => radiusScaleRetention(d.thirty_day_keep))
.attr("fill", "steelblue")
.attr("opacity", 0.7);

// Label the dots with app_name
chart5Svg.selectAll("text")
.data(dataset)
.enter()
.append("text")
.attr("x", (d) => xScale(d.downloads))
.attr("y", (d) => yScale(d.thirty_day_keep) - radiusScaleRetention(d.thirty_day_keep) + 20) // Adjust the position for proper placement
.text((d) => d.app_name)
.style("font-size", "8px")
.style("text-anchor", "middle")
.style("alignment-baseline", "middle");

// Create x-axis
chart5Svg.append("g")
.attr("class", "axis")
.attr("transform", `translate(0, ${h - 20})`)
.call(d3.axisBottom(xScale));

// Create y-axis with percentage formatting
chart5Svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(80, 0)")
.call(d3.axisLeft(yScale).tickFormat(d => `${d}%`));
return chart5Svg.node();
}
Insert cell
makeChart5(data)
Insert cell
Insert cell
theStory = md`
---
## Conclusions

In Conclusion, the 5 charts above show a story that is actually quite interesting, the correlation between downloads and ratings and between downloads and retention of users is not actually that closely related. An app can have fewer downloads but have better ratings and user retention, and an app that has more downloads can have less user retention and worse ratings. However there do seem to be a close correlation between ratings and user retention as higher rating usually means a better user retention.

`
Insert cell
Insert cell
Insert cell
data = d3.csvParse(dataCsv)
Insert cell
Insert cell
d3 = require("d3@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