Public
Edited
May 9, 2023
Insert cell
Insert cell
raw = FileAttachment("data.csv").csv()
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
function myArc(borough, price, scale, innerRadius, outerRadius, i, max){
let startangle = Math.PI/2
let endangle = Math.PI/2
if (borough == "Manhattan"){
startangle += theta(0, 5)
endangle += theta(1, 5)
} else if(borough == "Bronx"){
startangle += theta(1, 5)
endangle += theta(2, 5)
}else if(borough == "Queens"){
startangle += theta(2, 5)
endangle += theta(3, 5)
}else if(borough == "Brooklyn"){
startangle += theta(3, 5)
endangle += theta(4, 5)
}else{
startangle += theta(4, 5)
endangle += theta(5, 5)
}
let angleRange = (endangle - startangle)*scale;
var angle1 = startangle + (price-1)/4 * angleRange
var angle2 = angle1 + 1/4 * angleRange-0.02
var rDiff = outerRadius - innerRadius
let arc = d3.arc()
.startAngle(d => angle1)
.endAngle(d => angle2)
.padAngle(d => Math.min((angle1 - angle2) / 2, 0.005))
.padRadius(innerRadius * 1.5)
.innerRadius(d => innerRadius)
.outerRadius(d => innerRadius + i * rDiff) // Number((i/max)* rDiff)
let res = arc()
return res
}
Insert cell
function myBoroughLabel(borough, scale, innerRadius, outerRadius){
let startangle = Math.PI/2
let endangle = Math.PI/2
if (borough == "Manhattan"){
startangle += theta(0, 5)
endangle += theta(1, 5)
} else if(borough == "Bronx"){
startangle += theta(1, 5)
endangle += theta(2, 5)
}else if(borough == "Queens"){
startangle += theta(2, 5)
endangle += theta(3, 5)
}else if(borough == "Brooklyn"){
startangle += theta(3, 5)
endangle += theta(4, 5)
}else{
startangle += theta(4, 5)
endangle += theta(5, 5)
}
let angleRange = (endangle - startangle)*scale;
var angle1 = startangle
var angle2 = startangle+angleRange
let arc = d3.arc()
.startAngle(d => angle1)
.endAngle(d => angle2)
.padAngle(d => Math.min((angle1 - angle2) / 2, 0.005))
.padRadius(innerRadius * 1.5)
.innerRadius(d => outerRadius)
.outerRadius(d => outerRadius)
let res = arc()
return res
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// dp right main histogram
final ={
var width = 1400,
height = 1100,
w_break = 500;
var svg = d3.select(DOM.svg(width, height));
var myColor = d3.scaleOrdinal().domain([1, 2, 3, 4])
.range(d3.schemeSet1);
svg.append("text")
.attr("x", 200)
.attr("y", 80)
.attr("font-size", 50)
.attr("font-weight", "bold")
.text("NYC Price Distribution of Cuisines")
svg.append("rect")
.attr("fill", "steelblue")
.attr("x", 0)
.attr("width", width)
.attr("y", 0)
.attr("height", height)
.attr('opacity', 0.1)

svg.append("rect")
.attr("fill", myColor(1))
.attr("x", width-500)
.attr("width", 20)
.attr("y", 120)
.attr("height", 10)
.attr('opacity', 0.8)
svg.append("text")
.attr("x", width-460)
.attr("y", 128)
.attr("font-size", 10)
.attr("font-weight", "bold")
.text("$")
svg.append("rect")
.attr("fill", myColor(2))
.attr("x", width-500)
.attr("width", 20)
.attr("y", 140)
.attr("height", 10)
.attr('opacity', 0.6)
svg.append("text")
.attr("x", width-460)
.attr("y", 148)
.attr("font-size", 10)
.attr("font-weight", "bold")
.text("$$")

svg.append("rect")
.attr("fill", myColor(3))
.attr("x", width-500)
.attr("width", 20)
.attr("y", 160)
.attr("height", 10)
.attr('opacity', 0.6)
svg.append("text")
.attr("x", width-460)
.attr("y", 168)
.attr("font-size", 10)
.attr("font-weight", "bold")
.text("$$$")
svg.append("rect")
.attr("fill", myColor(4))
.attr("x", width-500)
.attr("width", 20)
.attr("y", 180)
.attr("height", 10)
.attr('opacity', 0.5)
svg.append("text")
.attr("x", width-460)
.attr("y", 188)
.attr("font-size", 10)
.attr("font-weight", "bold")
.text("$$$$")
// flower chart
let innerR = 130,
midR = 130,
outerR = 230,
cx = 780,
cy = 600,
gapper = 0.8,
arcinnerR = outerR+30,
arcouterR = arcinnerR+100,
labelouterR = arcouterR+10;

const boroughs = ["Staten Island","Manhattan", "Bronx","Queens","Brooklyn", ];
const labelRadius = outerR + 20;
const boroughLabels = boroughs.map((borough, index) => {
const startAngle = theta(index, 5);
const endAngle = theta(index + 1, 5);
let angleRange = (endAngle - startAngle);
var midAngle = 0.5 * angleRange + startAngle+0.15;
const position = labelPosition(cx, cy, midAngle, labelRadius);
const textAnchor = midAngle > Math.PI ? "end" : "start";
return { name: borough, x: position[0]-10, y: position[1], textAnchor: textAnchor };
});

// returants point
let resturantsdata = data.map(function(d,i){
let res = resturant_locations(cx, cy, midR, outerR, gapper, d.borough, d.price)
let temp = d
temp["x"] = res[0]
temp["y"] = res[1]
return temp
})
const circles = svg.append('g').attr('class', 'circles');
const binding = circles.selectAll('.data-point').data(resturantsdata, d => d.id);

// 5 boroughs data point
binding.enter().append('circle')
.classed('data-point', true)
.attr("cx", function(d) {
return d.x
})
.attr("cy", function(d) {
return d.y })
.attr("r", 2)
.attr('opacity', "0.07")
.style("fill", d => myColor(d.price.length))
.on("mouseover", function(i, d) {
d3.select(this).style("cursor", "pointer");
if (d3.select(this).style("opacity") == "1"){
var xpos = d.x;
var ypos = d.y;
var tgrp = svg.append("g")
.attr("id", "hover-tooltip")
.attr("transform", (d, i) => `translate(${xpos},${ypos})`);
tgrp.append("rect")
.attr("width", "140px")
.attr("height", "22px")
.attr("fill", "burlywood")
tgrp.append("text")
.attr("x", 10)
.attr("y", 14)
.attr("text-anchor", "left")
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(`${d.name}`);
}
})
.on("mouseout", function(d) {
d3.select("#hover-tooltip").remove();
})
.on("click", function(i, d) {
d3.select("#tooltip").remove();
var xpos = cx - 75;
var ypos = cy - 80;
var tgrp = svg.append("g")
.attr("id", "tooltip")
.attr("transform", (d, i) => `translate(${xpos},${ypos})`)
.datum(d);
tgrp.append("rect")
.attr("width", "180px")
.attr("height", "90px")
.attr("fill", "white")
.attr("stroke", "black");
tgrp.append("text")
.attr("x", 10)
.attr("y", 14)
.attr("text-anchor", "left")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(`Name: ${d.name}`);
tgrp.append("text")
.attr("x", 10)
.attr("y", 30)
.attr("text-anchor", "left")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(`Rating: ${d.rating}`);
tgrp.append("text")
.attr("x", 10)
.attr("y", 46)
.attr("text-anchor", "left")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(`Price: ${d.price}`);
tgrp.append("text")
.attr("x", 10)
.attr("y", 62)
.attr("text-anchor", "left")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "black")
.text(`Phone: ${d.display_phone}`);
tgrp.append("a")
.attr("xlink:href", d.url)
.attr("target", "_blank")
.append("text")
.attr("x", 10)
.attr("y", 78)
.attr("text-anchor", "left")
.attr("font-size", "11px")
.attr("font-weight", "bold")
.attr("fill", "blue")
.text("Visit restaurant website");
});
let counts = []
for (const x of boroughs){
counts.push({id: `${x}1`, count: 0})
counts.push({id: `${x}2`, count: 0})
counts.push({id: `${x}3`, count: 0})
counts.push({id: `${x}4`, count: 0})
}
var arcgroup = svg.append("g")
.attr("transform", "translate(" + cx + "," + cy + ")")
.attr("id", "dparc");
const path = arcgroup.append("g")
.selectAll("path")
.data(counts)
.join("path")
.attr("id", (d) =>{
return `${d.id}`
})
.attr("fill", d => myColor(Number(d.id.slice(-1))))
.attr("fill-opacity", d => {return 0.8})
.attr("d", (d,i) => {
return myArc(d.id.slice(0, -1), Number(d.id.slice(-1)), gapper, arcinnerR, arcouterR, d.count, 100)
})
// arcgroup
// .selectAll(".path")
// .data(counts)
// .enter()
// .append("text")
// .style('font-size', '10px')
// // .attr("x", d=> 18* d.count/tempmax + 30) //Move the text from the start angle of the arc
// .attr("dy", 4)
// .append("textPath") //append a textPath to the text element
// .attr("xlink:href", d=> {
// return `#${d.id}`
// }) //place the ID of the path here
// .style("text-anchor","start") //place the text halfway on the arc
// // .attr("startOffset", "40%")
// .text(d => "$".repeat(Number(d.id.slice(-1))));
let onCategoryClick = (d, i, flag) => {
arcgroup
.selectAll(".foo").remove()
circles.selectAll('circle').each(
(d,i,nodes) => {
nodes[i].setAttribute("opacity", "0.07");
nodes[i].setAttribute("r", 2)
})
let selectedSuperCategory = "";
let subCategories = [];
if (flag){
selectedSuperCategory = i.category_name;
subCategories = categoryHierarchy[selectedSuperCategory];
} else {
subCategories = [i]
}
const createBoroughMask = (borough) => {
return data.map(restaurant => {
const fixedCategoriesString = restaurant.categories.replace(/'/g, '"');
const parsed_res_category = JSON.parse(fixedCategoriesString);
const categories = parsed_res_category.map(category => category.title);
const isIncluded = categories.some(category => subCategories.includes(category));
return restaurant.borough == borough && isIncluded;
});
};
const bronxMask = createBoroughMask("Bronx");
const brooklynMask = createBoroughMask("Brooklyn");
const manhattanMask = createBoroughMask("Manhattan");
const queensMask = createBoroughMask("Queens");
const statenIslandMask = createBoroughMask("Staten Island");
var filteredData = [
data.filter((_, idx) => statenIslandMask[idx]),
data.filter((_, idx) => manhattanMask[idx]),

data.filter((_, idx) => bronxMask[idx]),

data.filter((_, idx) => queensMask[idx]),
data.filter((_, idx) => brooklynMask[idx]),
];
const totalfilteredData = filteredData.flat(1)

circles.selectAll('circle').each(
(d,i,nodes) => {
if (totalfilteredData.includes(d)) {
nodes[i].setAttribute("opacity", "1");
nodes[i].setAttribute("r", 3)
}
})
let target_counts = []
let tempmax = 0
for (const x of boroughs){
target_counts.push({id: `${x}1`, count: 0})
target_counts.push({id: `${x}2`, count: 0})
target_counts.push({id: `${x}3`, count: 0})
target_counts.push({id: `${x}4`, count: 0})
}
for (const d of totalfilteredData){
target_counts.forEach( (c, i) => {
let tag = `${d.borough}${d.price.length}`
if (c.id == tag){
target_counts[i].count+=1
if (target_counts[i].count >tempmax){
tempmax = target_counts[i].count
}
}
})
}
const t = svg.transition().duration(400);

path.transition(t)
.tween("data", (d, i) => {
const f = d3.interpolateNumber(d.count, target_counts[i].count/tempmax);
return t => d.count = f(t);
})
.attr("id", (d) =>{
return `${d.id}`
})
.attrTween("d", d => () => {
let res = myArc(d.id.slice(0, -1), Number(d.id.slice(-1)), gapper, arcinnerR, arcouterR, d.count, tempmax);
return res
});
counts = target_counts
arcgroup
.selectAll(".path")
.data(counts)
.enter()
.append("text")
.attr('class', 'foo')
.style('font-size', '10px')
.attr("x", d=> 18* d.count/tempmax + 30) //Move the text from the start angle of the arc
.attr("dy", 4)
.append("textPath") //append a textPath to the text element
.attr("xlink:href", d=> {
return `#${d.id}`
}) //place the ID of the path here
.style("text-anchor","middle") //place the text halfway on the arc
// .attr("startOffset", "0.8cm")
.text(d => d.count);
};

//////////////////////////////////////////////////////////////////////////////////////////
// search bar
const searchBar = svg.append("foreignObject")
.attr('x', 29)
.attr('y', 370)
.attr('width', 300)
.attr('height', 50)
.attr("max-width", "120%")
.attr("overflow", "auto")
.html(`<input type="text" placeholder="Search" width = "190px"></input>`);
// Get all keys in categoryHierarchy object
const allKeys = Object.keys(categoryHierarchy);
const parentCatsData = allKeys.map((key, index) => ({ id: index, category_name: key }))
// Add mouseover event to display children values
// Get parent category elements
const parentCats = svg.selectAll(".parent-cat")
.data(parentCatsData, d => d.category_name)
.enter().append("g")
.attr("class", "parent-cat")
.attr("transform", (d, i) => `translate(20, ${400 + i * 20})`)
.on("mouseover", function() {
d3.select(this).style("cursor", "pointer");
})
// .on("mouseout", function() {
// // Remove any existing child categories
// svg.selectAll(".child-cat").remove();
// })
.on("click", function(d, i) {
d3.selectAll(".parent-cat").attr("fill", "black");
d3.select(this).attr("fill", "blue")
onCategoryClick(d,i, true);

// Get the text of the hovered parent category
const hoveredParentCat = d3.select(this).select("text").text();
// Get the child categories of the hovered parent category
const childCats = categoryHierarchy[hoveredParentCat];
// Remove any existing child categories
svg.selectAll(".child-cat").remove();
// Create child category elements
// Create child category elements
const childCatsEl = svg.selectAll(".child-cat")
.data(childCats)
.enter().append("g")
.attr("class", "child-cat")
.attr("transform", (d, i) => `translate(210, ${ i * 20})`)
.on("mouseover", function() {
d3.select(this).style("cursor", "pointer");
})
.on("click", function(d, i){
const clickedChildCat = d3.select(this).text();
d3.selectAll(".child-cat").attr("fill", "black");
d3.select(this).attr("fill", "blue")
onCategoryClick(d,i, false)
});
childCatsEl.append("rect")
.attr("x", 22)
.attr("y", 400)
.attr("width", 165)
.attr("height", 20)
.attr("stroke", "black")
.attr("fill", "none");
childCatsEl.append("text")
.text(d => d)
.attr("x", 25)
.attr("y", 415);
});
// Add border and text to parent category elements
const parentCatRects = parentCats.append("rect")
.attr("x", 10)
.attr("width", 195)
.attr("height", 20)
.attr("stroke", "black")
.attr("fill", "none");
parentCats.append("text")
.text(d => d.category_name)
.attr("x", 10)
.attr("y", 15);
// Add event listener to search bar
const searchInput = searchBar.select("input");
searchInput.on("input", function() {
const searchString = this.value.toLowerCase();
const filteredKeys = parentCatsData.filter(data => data.category_name.toLowerCase().startsWith(searchString));
// Show all parent categories
parentCats.attr("display", "block");
// Add class to matching parent categories and remove from non-matching ones
parentCats.classed("matching-parent-cat", d => filteredKeys.includes(d));
parentCats.classed("non-matching-parent-cat", d => !filteredKeys.includes(d));
// Update transform attribute of matching parent categories
parentCats.filter(".matching-parent-cat")
.attr("transform", (d, i) => `translate(20, ${400 + filteredKeys.indexOf(d) * 20})`);
// Hide non-matching parent categories
parentCats.filter(".non-matching-parent-cat")
.attr("display", "none");
// Remove any existing child categories
svg.selectAll(".child-cat").remove();
// Create child category elements for first matching parent category
// const firstMatchingParentCat = filteredKeys[0];
// if (firstMatchingParentCat) {
// const childCats = categoryHierarchy[firstMatchingParentCat.category_name];
// const childCatsEl = svg.selectAll(".child-cat")
// .data(childCats)
// .enter().append("text")
// .attr("class", "child-cat")
// .attr("x", 225)
// .attr("y", (d, i) => 400 + i * 20)
// .text(d => d)
// .on("click", d => {
// searchInput.node().value = d;
// searchInput.dispatch("input");
// });
// }
});

//////////////////////////////////////////////////////////////////////////////////////////

const labelsGroup = svg.append("g").attr("class", "labels").attr("transform", "translate(" + cx + "," + cy + ")");
labelsGroup
.selectAll(".borough-label")
.data(boroughLabels)
.enter()
.append("path")
.attr("id", d=> `${d.name}label`)
.attr("d", d=> myBoroughLabel(d.name, gapper, arcouterR, labelouterR)) //SVG path
.style("fill", "red")
.style("stroke", "#AAAAAA");
labelsGroup
.selectAll(".borough-label")
.data(boroughLabels)
.enter()
.append("text")
.append("textPath") //append a textPath to the text element
.attr("xlink:href", d=> `#${d.name}label`) //place the ID of the path here
.style("text-anchor","middle") //place the text halfway on the arc
.attr("startOffset", "19%")
.text(d => d.name);


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