Public
Edited
Dec 11, 2023
Insert cell
Insert cell
f9ad710414b74fcd9f97F0ef40bda289 = FileAttachment("f9ad7104-14b7-4fcd-9f97-f0ef40bda289.jpeg").image()
Insert cell
final_dataset.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
// ignorar por enquanto
games_large = FileAttachment("final_dataset.csv").csv({typed: true}).then(function (data) {
// The date has a special formating not automatically detected by d3,
// so we need to parse it using UTC rather than local time
const parseDate = d3.utcParse("%d %B, %Y"); // 25 Jan, 2011
data.forEach(function(d) {
let date = d.release_date
if(date != null){
date = date.replace("Jan", "January");
date = date.replace("Feb", "February");
date = date.replace("Mar", "March");
date = date.replace("Apr", "April");
date = date.replace("May", "May");
date = date.replace("Jun", "June");
date = date.replace("Jul", "July");
date = date.replace("Aug", "August");
date = date.replace("Sep", "September");
date = date.replace("Oct", "October");
date = date.replace("Nov", "November");
date = date.replace("Dec", "December");
d.release_date = parseDate(date);
}
});
return data;
})
Insert cell
top250@4.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
avg_players_df@4.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
avg_players = FileAttachment("avg_players_df@4.csv").csv({typed: true}).then(function (data) {
data.forEach(function(d) {
let non_genrer = [
"Free to Play",
"Massively Multiplayer",
"Indie",
"Casual",
"Animation & Modeling",
"Design & Illustration",
"Photo Editing",
"Utilities",
"Early Access",
"Audio Production"
];
if(d.genre !== null) d.genre = d.genre.split(",").map(g => g.trim());
else d.genre = [];

d.genre = d.genre.filter(g => !non_genrer.includes(g));
});
return data;
})
Insert cell
blue_chart_color = "#00008B"
Insert cell
Insert cell
games_250 = FileAttachment("top250@4.csv").csv({typed: true}).then(function (data) {
data.forEach(function(d) {
// Genre //
let non_genrer = [
"Free to Play",
"Massively Multiplayer",
"Indie",
"Casual",
"Animation & Modeling",
"Design & Illustration",
"Photo Editing",
"Utilities",
"Early Access",
"Audio Production"
];
if(d.genre !== null) d.genre = d.genre.split(",").map(g => g.trim());
else d.genre = [];

d.genre = d.genre.filter(g => !non_genrer.includes(g));

// tags //
d.tags = d.tags.split(",");

// developer //
if(d.developer) d.developer = d.developer.split(",").map(g => g.trim());
else d.developer = [];

// OS //
if(d.platforms){
let res = [];
let has_windows = d.platforms.indexOf("windows") != -1;
let has_linux = d.platforms.indexOf("linux") != -1;
let has_mac = d.platforms.indexOf("mac") != -1;

if(has_windows) res.push("Windows");
if(has_linux) res.push("Linux");
if(has_mac) res.push("Mac");

if(res.length == 1){
d.platforms = res[0];
}else{
let res_str = res[0];
for(let i = 1; i < res.length-1; i++){
res_str += ", " + res[i];
}
res_str += " and " + res[res.length-1];
d.platforms = res_str
}
}else{
d.platforms = [];
}
});
return data;
})
Insert cell
viewof release_by_year_bin_plot = {
const bin_plot = vl.markBar({color:"#3a6490"})
.title("Game releases by year")
.encode(
vl.x().fieldT('release_date').timeUnit("binnedyear").title("Year"),
vl.y().count().title("Games released in a year"),
);

const chartLayer = vl.layer(
bin_plot.params(brush).encode(vl.color().value('lightgrey')),
bin_plot.transform(vl.filter(brush))
).data(games_250).width(900).height(300);
return chartLayer
.render({renderer: "svg"});
}
Insert cell
Insert cell
genre_count_data = {
let genre_count = new Map();
for(let game of games_250){
for(let genre of game.genre){
genre_count.set(genre, 0);
}
}
for(let game of games_250){
for(let genre of game.genre){
genre_count.set(genre, genre_count.get(genre) + 1);
}
}
////////////////////////
let res = []
for(let [k, v] of genre_count){
res.push({name: k, frequence: v});
}
return res;
}
Insert cell

viewof genre_pie_plot2 = {
const arc_plot = vl.markArc({innerRadius:40, tooltip: true, cornerRadius:4})
.encode(
vl.theta().fieldQ('frequence'),
///vl.theta().fieldQ('count').stack(true).scale({range: [0.75 * Math.PI, 2.75 * Math.PI]}),
//vl.radius().fieldN('frequence').scale({type: "sqrt", zero: true, range: [60,100]}),
vl.order({sort: "descending"}).fieldQ('frequence'),
vl.color().fieldN("name").scale({scheme: "tableau10"}).title('Game genre'),
);

const textName = arc_plot
.markText({ radius: 120, align: "center", dy: 7 })
.encode(
vl.text().fieldN("name"),
vl.detail().fieldQ("frequence"), // Without this we will get only the totals
vl.color().value("Black")
);
const textValue = textName
.markText({ ...{ radius: 120, align: "center", dy: 7 }, dy: -7, fontWeight: "bold", fontSize: 14 })
.encode(vl.text().fieldN("genre"));
return vl.layer(
arc_plot,
textName
)
.data(genre_count_data)
.width(300)
.render();
}


Insert cell
let res = []
for(let [k, v] of genre_count){
res.push({name: k, frequence: v});
}
Insert cell
avg_players_filtered
Insert cell
genre_avg_with_date_filtered = {
let genre_date_count = new Map();
for(let avg of avg_players_filtered){
for(let genre of avg.genre){
const k = [genre, avg.date];
const v = avg.avg_players;
if(genre_date_count.has(k)){
genre_date_count.set(k, genre_date_count.get(k) + v)
}else{
genre_date_count.set(k, v)
}
}
}
let res = []
for(let [k, v] of genre_date_count){
let genre= k[0];
let date = k[1];
let avg_players = v;
res.push({genre: genre, date: date, avg_players: avg_players});
}
return res;
}

Insert cell
gender_graphs = {
const generos = ["Action", "Strategy", "Adventure", "RPG", "Simulation", "Racing", "Sports"];

const colorScale = {
domain: generos,
range: d3.schemeTableau10.slice(0, generos.length)
};

const bar_width = 320.

const generoBar = vl.markBar().data(genre_avg_with_date_filtered).encode(
vl.x().fieldN('genre').sort("-y").title(""),
vl.y().aggregate("sum").fieldQ("avg_players").title("Total of players"),
vl.color().fieldN("genre").scale(colorScale).title("Genre")
).width(bar_width).height(300);

const generoBarGray = vl.layer(
generoBar.params(selection).
encode(
vl.opacity().if(selection, vl.value(1.0)).value(0.3),
).width(bar_width).height(300)
);

/// LINE ///
const generoLine = vl.markLine()
.data(genre_avg_with_date_filtered)
.encode(
vl.x().fieldT('date').timeUnit("binnedyearmonth").title(""),
vl.y().aggregate("sum").fieldQ("avg_players").title("Average players"),
vl.color().fieldN("genre").title("Genre"),
vl.opacity().if(selection, vl.value(1.0)).value(0.4)
)
.width(400)
.height(300);

const generoLineGray = vl.layer(
generoLine.transform(vl.filter(selection))
).data(genre_avg_with_date_filtered)
.width(360)
.height(300);

return [generoBarGray, generoLine];
}
Insert cell
vl.hconcat(
vl.hconcat(
gender_graphs[0],
gender_graphs[1],
))
.resolve({scale: {size: 'independent'}})
.render();
Insert cell
//
//viewof genre_pie_plot = {
// const arc_plot = vl.markArc({innerRadius:30, tooltip: true, cornerRadius:4})
// .encode(
// vl.theta().fieldN('genre').count(),
// ///vl.theta().fieldQ('count').stack(true).scale({range: [0.75 * Math.PI, 2.75 * Math.PI]}),
// vl.radius().fieldN('genre').count().scale({type: "sqrt", zero: true, range: [60,100]}),
// vl.order({sort: "descending"}).fieldN('genre').count(),
// vl.color().fieldN("genre").scale({scheme: "tableau10"}).title('Game genre'),
// );
//
// const textConfig = { align: "center", dy: 7 };
// const textName = arc_plot
// .markText(textConfig)
// .encode(
// vl.text().count(),
// vl.detail().fieldN("genre"), // Without this we will get only the totals
// vl.order({sort: "descending"}).fieldN('genre').count(),
// vl.color().value("Black")
// );
// const textValue = textName
// .markText({ ...textConfig, dy: -7, fontWeight: "bold", fontSize: 14 })
// .encode(vl.text().fieldN("genre"));
//
// return vl.layer(
// arc_plot,
// //textName
// )
// .data(games_filtered)
// .transform(
// //{filter: { selection: 'brusho' }},
// vl.flatten('genre'))
// .width(200)
// .render();
//}
//
//
Insert cell
selection = vl.selectSingle().name('selecao').encodings('x')


Insert cell

viewof genre_pie_plot = {

return vl.hconcat(
vl.hconcat(
gender_graphs[0],
gender_graphs[1],
))
.resolve({scale: {size: 'independent'}})
.render();

const bar_plot = vl.markBar({tooltip: true})
.encode(
vl.x().fieldN('genre').sort("-y").title("Genero"),
vl.y().fieldN('genre').count().title("Game count"),
vl.order({sort: "descending"}).fieldN('genre').count(),
vl.color().fieldN("genre").scale({scheme: "tableau10"}).title('Game genre'),
);
const generoBarGray = vl.layer(
bar_plot.params(selection)
.encode(vl.color().value('lightgrey')),
bar_plot.transform(vl.filter(selection))
);

return vl.layer(gender_graphs[0])
.width(250)
.render();
}


Insert cell
avg_players_filtered
Insert cell
genre_selection = vl.selectPoint('genre')
Insert cell
Insert cell
viewof avg_players_by_genre_plot = {

return vl.layer(gender_graphs[1])
.width(360)
.height(300)
.title("Genre popularity by year")
.render();


//////////////////////////////////////////////////////////qq
// select a point for which to provide details-on-demand
const hover = vl.selectPoint('hover')
.encodings('x') // limit selection to x-axis value
.on('mouseover') // select on mouseover events
.toggle(false) // disable toggle on shift-hover
.nearest(true); // select data point nearest the cursor
console.log(selection);

// predicate to test if a point is hover-selected
// return false if the selection is empty
const isHovered = hover.empty(false);
// define our base line chart of stock prices
const line = vl.markLine().encode(
vl.x().fieldT('date').timeUnit("binnedyearmonth").title(null),
vl.y().sum("avg_players").title("Average active players"),
vl.color().fieldN("genre").title("Game genre").scale({scheme: "tableau10"}),
//vl.opacity().if(selection, vl.value(1))
);
// shared base for new layers, filtered to hover selection
const base = line.transform(vl.filter(isHovered));

// mark properties for text label layers
const label = {align: 'left', dx: 5, dy: -5};
const white = {stroke: 'white', strokeWidth: 2};

return vl.data(avg_players_filtered).transform(vl.filter("datum.genre != null"), vl.flatten('genre'))
.layer(
line,
// add a rule mark to serve as a guide line
vl.markRule({color: '#lightgray'})
.transform(vl.filter(isHovered))
.encode(vl.x().fieldT('date')),
// add circle marks for selected time points, hide unselected points
line.markCircle()
.params(hover) // use as anchor points for selection
.encode(vl.opacity().if(isHovered, vl.value(1)).value(0)),
// add white stroked text to provide a legible background for labels
base.markText(label, white).encode(vl.text().fieldN('genre')),
// add text labels for stock prices
base.markText(label).encode(vl.text().fieldN('genre'))
)
.width(360)
.height(300)
.title("Genre popularity by year")
.render();
}
Insert cell
Insert cell
import {WordCloud} from "@d3/word-cloud"
Insert cell
words = {

let words = [];

for(let game of games_filtered){
for(let tag of game.tags){
words.push(tag);
}
}
return words
}
Insert cell
viewof tag_word_cloud_plot = WordCloud(words, {
width: 900,
height: 400,
fontScale: 4,
padding: 0.2,
maxWords: 100,
invalidation // a promise to stop the simulation when the cell is re-run
})

Insert cell
/* UNUSED
viewof genres_by_year_plot = {
const line_plot = vl.markLine()
.title("Released games by year")
.encode(
vl.x().fieldT('release_date').timeUnit("binnedyear").title("Year"),
vl.y().count().title("Released games by Gender"),
vl.color().fieldN("genre").title("Genres")
);

const chartLayer = vl.layer(
line_plot,
).data(games_250).transform(vl.flatten('genre')).width(700).height(400);
return chartLayer
.render({renderer: "svg"});
} */
Insert cell
Insert cell
reduce_avg_players = {
let name2avg = new Map();
let name2price = new Map();
let name2review = new Map();

for(let g of avg_players_filtered){
name2avg.set(g.name, 0);
name2price.set(g.name, g.initial_price / 100);
name2review.set(g.name, g.positive / (g.total_reviews));
}
for(let g of avg_players_filtered){
let nm = g.name;
let avg = name2avg.get(g.name);
console.log(nm, avg);
name2avg.set(nm, avg+g.avg_players);
}
let qtd_month = avg_players_filtered.length / name2avg.size;
let res = [];
for(let [k, v] of name2avg){
let price = name2price.get(k);
let rev = name2review.get(k);
res.push({name: k, avg_players: v / qtd_month, initial_price: price, review_rate: rev});
}
return res;
}
Insert cell
top_game_avg_players = {
let res = reduce_avg_players.sort((a, b) => a.avg_players > b.avg_players ? -1 : 1);
return res.slice(0, 10);
}
Insert cell
viewof top_10_games_bar_plot = {
const line_plot = vl.markBar({tooltip: true, color:"#3a6490"})
.title("Average players from 2019 to 2023")
.encode(
vl.y().fieldN('name').title("Title").sort("avg_players"),
vl.x().fieldQ('avg_players').title("Average active players"),
);

const chartLayer = vl.layer(
line_plot,
).data(top_game_avg_players)
.width(700).height(400);

return chartLayer
.render({renderer: "svg"});
}


Insert cell
Insert cell
viewof review_popularity_scatter_plot = {
const line_plot = vl.markPoint({tooltip: true, color:"#3a6490"})
.title("Average active players by review rate")
.encode(
vl.x().fieldQ('review_rate').title("Review rate (positive / total)"),
vl.y().fieldQ('avg_players').title("Average active players"),
vl.tooltip().field('name'),
//vl.tooltip().fieldQ('review_rate'),
);

const chartLayer = vl.layer(
line_plot,
).data(reduce_avg_players).transform(vl.filter("datum.avg_players < 20000"))
.width(700).height(400);

return chartLayer
.render({renderer: "svg"});
}
Insert cell
Insert cell
viewof price_popularity_scatter_plot = {
const line_plot = vl.markPoint({tooltip: true, color:"#3a6490"})
.title("Games popularity by price")
.encode(
vl.x().fieldQ('initial_price').title("Initial price (US$)"),
vl.y().fieldQ('avg_players').title("Average active players"),
vl.tooltip().field('name'),
//vl.tooltip().fieldQ('review_rate'),
);

const chartLayer = vl.layer(
line_plot,
).data(reduce_avg_players).transform(vl.filter("datum.avg_players < 20000"))
.width(700).height(400);

return chartLayer
.render({renderer: "svg"});
}
Insert cell
Insert cell

viewof free_games_pie_plot = {
const arc_plot = vl.markArc({innerRadius:20, tooltip: true, cornerRadius:8, outerRadius:80})
.encode(
vl.theta().fieldN('pay').count(),
vl.color().fieldN("pay").scale({scheme: "tableau10"}).title("Payment type"),
);
return vl.layer(
arc_plot,
)
.data(games_filtered)
.transform( vl.calculate("datum.initial_price > 0 ? 'Paid' : 'Free'").as("pay"))
.render();
}


Insert cell
Insert cell

viewof platforms_pie_plot = {
const arc_plot = vl.markArc({innerRadius:20, tooltip: true, cornerRadius:8, outerRadius:80})
.encode(
vl.theta().fieldN('platforms').count(),
vl.color().fieldN("platforms").scale({scheme: "tableau10"}).title("Platforms"),
vl.radius().fieldN('platforms').count().scale({type: "sqrt", zero: true, range: [60,100]}),
vl.order({sort: "descending"}).fieldN('platforms').count(),
);
return vl.layer(
arc_plot,
)
.data(games_filtered).transform(vl.filter("datum.platforms != ''"))
.render();
}


Insert cell
Insert cell
viewof graph_developer_game = {
// Specify the dimensions of the chart.
const width = 938;
const height = 800;
var selected_name = "";

// Specify the color scale.
const color = d3.scaleOrdinal(d3.schemeSet1);

// The force simulation mutates links and nodes, so create a copy
// so that re-evaluating this cell produces the same result.
const links = data_graph.links.map(d => ({...d}));
const nodes = data_graph.nodes.map(d => ({...d}));

function limit_min_max(value, min, max){
return Math.max(Math.min(value, max), min);
}

function calculate_size(node){
if(node.group == "Game") return limit_min_max(Math.sqrt(node.popularity)/10, 4, 23)*0.9;
else return limit_min_max(Math.sqrt(node.popularity)/10, 6, 23) + 3;
}

const node_radius = new Map();
data_graph.nodes.forEach(d => { node_radius.set(d.id, calculate_size(d))})
// Create a simulation with several forces.
const simulation = d3.forceSimulation(nodes)
.force("link", d3.forceLink(links).id(d => d.id))
.force("charge", d3.forceManyBody())
.force("x", d3.forceX(10))
.force("y", d3.forceY(10));

// Create the SVG container.
const svg = d3.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto;")
.on("click",
(event, data) => {
event.stopPropagation();
svg.selectAll("circle").attr("stroke-opacity", 1);
svg.selectAll("circle").attr("fill-opacity", 1);
svg.selectAll("text").text("Select a node");
});

const text = svg.append('text')
.attr('x', -400)
.attr('y', -300)
.text('Select a node')
.attr('font-size', '14px')
.attr('fill', 'black');

// Add a line for each link, and a circle for each node.
const link = svg.append("g")
.attr("stroke", "#999")
.attr("stroke-opacity", 0.6)
.selectAll("line")
.data(links)
.join("line")
.attr("stroke-width", d => Math.sqrt(d.value));

const node = svg.append("g")
.attr("stroke", "#fff")
.attr("stroke-width", 1.5)
.selectAll("circle")
.data(nodes)
.join("circle")
.attr("r", d => node_radius.get(d.id) )
.attr("fill", d => color(d.group))
.text("amogous")
.on("click", click_function);

function click_function(event, data){
event.stopPropagation();
svg.selectAll("circle").attr("stroke-opacity", 0.2);
svg.selectAll("circle").attr("fill-opacity", 0.2);
svg.selectAll("text").text(data["id"]);
d3.select(this).attr("fill-opacity", 1).attr("stroke-opacity", 1);
}


node.append("title")
.text(d => d.id);

// Add a drag behavior.
node.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended));
// Set the position attributes of links and nodes each time the simulation ticks.
simulation.on("tick", () => {
link
.attr("x1", d => d.source.x)
.attr("y1", d => d.source.y)
.attr("x2", d => d.target.x)
.attr("y2", d => d.target.y);

node
.attr("cx", d => d.x)
.attr("cy", d => d.y);
});

// Reheat the simulation when drag starts, and fix the subject position.
function dragstarted(event) {
if (!event.active) simulation.alphaTarget(0.3).restart();
event.subject.fx = event.subject.x;
event.subject.fy = event.subject.y;
}

// Update the subject (dragged node) position during drag.
function dragged(event) {
event.subject.fx = event.x;
event.subject.fy = event.y;
}

// Restore the target alpha so the simulation cools after dragging ends.
// Unfix the subject position now that it’s no longer being dragged.
function dragended(event) {
if (!event.active) simulation.alphaTarget(0);
event.subject.fx = null;
event.subject.fy = null;
}

// When this cell is re-run, stop the previous simulation. (This doesn’t
// really matter since the target alpha is zero and the simulation will
// stop naturally, but it’s a good practice.)
invalidation.then(() => simulation.stop());

return svg.node();
}
Insert cell
data_graph.nodes[400];
Insert cell
data_graph = {
let nodes = [];
let links = [];

let dev2popularity = new Map();

// create game nodes
for(let game of games_filtered){
const id = game.name;
const group = "Game";
const popularity = game['avgplayers_September 2023'];
nodes.push({id: id, popularity: popularity, group: group});
}

// create links and count developer's games
for(let game of games_filtered){
const game_id = game.name;
const game_popularity = game['avgplayers_September 2023'];
for(let dev of game.developer){
const dev_id = dev;
// create or update dev count
if(!dev2popularity.has(dev_id)){
dev2popularity.set(dev_id, game_popularity);
}else{
dev2popularity.set(dev_id, dev2popularity.get(dev_id)+game_popularity);
}
// create link
links.push({source: dev_id, target: game_id});
}
}

for(let [dev, popularity] of dev2popularity){
nodes.push({id: dev, group: "Developer", popularity: popularity});
}

return {nodes: nodes, links: links};
}
Insert cell
data.nodes
Insert cell
Insert cell
devs_country_processed.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
devs_country = FileAttachment("devs_country_processed@1.csv").csv({typed: true}).then(function (data) {
return data;
})
Insert cell
country_name_2_dev_count = {
let freq_map = new Map();
for(let cn of countryNames){
freq_map[cn] = 0;
}
for(let data of devs_country){
if(!data.country) continue;
freq_map[data.country] = freq_map[data.country] + 1;
}
let res = []
for(let key of countryNames){
res.push({name: key, value: freq_map[key]});
}
return res;
}

Insert cell
geodata = FileAttachment("countries-50m.json").json()
Insert cell
countryNames = d3.map(topojson.feature(geodata, geodata.objects.countries).features, d => d.properties.name)
Insert cell
countryNames.join(',')
Insert cell
data_color = country_name_2_dev_count.map((x) => x)
Insert cell
data = country_name_2_dev_count
//data = d3.map(countryNames, function(d) { return {name: d, value: d3.randomInt(1, 10)()}; })
Insert cell
nameColumnInGeo = "name"
Insert cell
nameColumnInData = "name"
Insert cell
valueColumn = "value"
Insert cell
legendTitle = "Steam's developers in the World"
Insert cell
viewof projectionType = Inputs.select(projectionOptions, {value: projectionOptions[7], label: "Projection types"})
Insert cell
interpolator = null
Insert cell
renameMapping = new Map()
Insert cell
geoColumn = "countries"
Insert cell
height = width / 2
Insert cell
viewof interpolatorType = Inputs.select(interpolateOptions, {value: interpolateOptions[12], label: "Interpolate for values"})
Insert cell
import { d3geo, projectionOptions, appendDropShadow, interpolateSequentialOptions as interpolateOptions } from "@chiahsun-ws/d3-functions"
Insert cell
d3.schemeBlues[8]
Insert cell
import { Legend } from "@d3/color-legend"
Insert cell
viewof world_dev_plot = {
const data = country_name_2_dev_count;
const margin = {top: 46, right: 20, bottom: 20, left: 20};

const svg_width = width + margin.left + margin.right;
const svg_height = height + margin.top + margin.bottom;;

const svg = d3.create("svg")
.attr("width", svg_width)
.attr("height", svg_height)
.attr("viewBox", [0, 0, svg_width, svg_height])
.attr("style", "max-width: 100%; height: auto; font: 10px sans-serif;");

const chart = svg.append("g")
.attr("transform", `translate(${margin.left}, ${margin.top})`);

let path;
const sea_color = "white";
if (projectionType) {
const type = "Sphere"
const projection = d3geo[projectionType]().fitSize([width, height], {type: type});
path = d3geo.geoPath(projection);
chart.append("path")
.datum({type: type})
.attr("fill", sea_color) //.attr("fill", "#85bdde")
.attr("stroke", "currentColor")
.attr("d", path);
} else {
path = d3geo.geoPath();
}

const valuemap = new Map(data.map(d => [renameMapping.get(d[nameColumnInData]) || d[nameColumnInData], d[valueColumn]]));
const color_max = 100;
const colorvaluemap = new Map(data.map(d => [d[nameColumnInData], Math.min(Math.floor(d[valueColumn]), color_max)]));
// https://github.com/d3/d3-interpolate
// https://github.com/d3/d3-scale-chromatic
//const color = d3.scaleSequential(d3.extent(valuemap.values()), interpolator || d3[interpolatorType]);
//const color = d3.scaleQuantize(d3.extent(colorvaluemap.values()), d3.schemeBlues[8]);
//color.domain([0,150]);
//const color = d3.scaleSequential([0, 150], interpolator || d3[interpolatorType]);

const color = d3.scaleThreshold()
.domain([1, 10, 20, 50, 100, 200, 300])
.range(["#DDDDDD", "#c6dbef", "#9ecae1", "#6baed6", "#4292c6", "#084594", "#084594", "#084594"])

svg.append("g")
.attr("transform", `translate(${margin.left}, 0)`)
.append(() => Legend(color, {title: legendTitle, width: 260}, {}));

chart.append("g")
.selectAll("path")
.data(topojson.feature(geodata, geodata.objects[geoColumn]).features)
.join("path")
.attr("fill", d => color(valuemap.get(d.properties[nameColumnInGeo])))
.attr("d", path)
.on("mouseover", function(d) {
d3.select(this).style("filter", "url(#drop-shadow)");
})
.on("mouseout", function(d){
d3.select(this).style("filter", null);
})
.append("title")
.classed("tooltip", true)
.text(d => `${d.properties[nameColumnInGeo]}: ${valuemap.get(d.properties[nameColumnInGeo]) || "0"}`);
appendDropShadow(svg);

chart.append("path")
.datum(topojson.mesh(geodata, geodata.objects[geoColumn], (a, b) => a !== b))
.attr("fill", "none")
.attr("stroke", "#64867e")
.attr("d", path);


return svg.node();
}
Insert cell
Insert cell
signal = 'brusho'
Insert cell
brush = vl.selectInterval().name('brusho').encodings('x').resolve('intersect')
Insert cell
games_filtered = Generators.observe((notify) => {
console.log(notify);
const games_filtered = (selection, predicates) => {
let within = [...games_250];
for (let [key, [min, max]] of Object.entries(predicates)) {
if (key == "binnedyear_release_date") {
key = "release_date";
min = new Date(min);
max = new Date(max);
}
within = within.filter(d => {
return !(isNaN(+d[key]) || d[key] < min || d[key] > max) == true;
})
}
notify(within);
};
release_by_year_bin_plot.addSignalListener(signal, games_filtered);
//budget_by_vote_avg.addSignalListener(signal, games_filtered);
notify(games_250);
return () => release_by_year_bin_plot.removeEventListener(signal, games_filtered);
})
Insert cell
games_250
Insert cell
avg_players
Insert cell
avg_players_filtered
Insert cell
avg_players_filtered = {
let filtered_ids = new Set();

for(let game of games_filtered){
filtered_ids.add(game.appid);
}

let res = [];
for(let avg_p of avg_players){
if(filtered_ids.has(avg_p.app_id)){
res.push(avg_p);
}
}
return res;
}
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