Public
Edited
Dec 6, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
shootings.csv
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
Insert cell
Insert cell
d3 = require("d3@6")
Insert cell
import {Legend, swatches} from "@d3/color-legend"
Insert cell
data = FileAttachment("shootings.csv").csv()
Insert cell
data_days = d3.rollups(
data,
group => group.length,
d => d.date
)
Insert cell
data_month = d3.rollups(
data,
group => group.length,
d => d.date.substring(0,7)
)
Insert cell
data_year = d3.rollups(
data,
group => group.length,
d => d.date.substring(0,4)
)
Insert cell
years = d3.map(data_year, d=>d[0])
Insert cell
typeof(years[0])
Insert cell
months =
{let months = [];
for (let i = 0; i < 12; i++) {
let date = new Date(2020, i, 1);
let month = date.toLocaleString('en-US', { month: 'long' });
months.push(month);
}
return months}
Insert cell
monthData = {let aggregatedData = [];


data.forEach(item => {
let date = new Date(item.date);
let year = date.getFullYear().toString();
let month = date.toLocaleString('en-US', { month: 'long' });

// 查找或创建年份对象
let yearData = aggregatedData.find(y => y.index === year);
if (!yearData) {
yearData = { index: year };
// 初始化所有月份的计数为 0
months.forEach(m => yearData[m] = 0);
aggregatedData.push(yearData);
}

// 累加月份计数
yearData[month]++;
});


return aggregatedData
}
Insert cell
month_Changes = {let monthDataChanges = [];

for (let i = 0; i < monthData.length; i++) {
let yearData = { index: monthData[i].index };
for (let j = 0; j < months.length; j++) {
const month = months[j];
const previousMonth = j === 0 ? "December" : months[j - 1];
const previousYear = j === 0 ? i - 1 : i;

if (i === 0 && j === 0) {
yearData[month] = 0;
} else {
const previousValue = previousYear >= 0 ? monthData[previousYear][previousMonth] : 0;
const change = monthData[i][month] - previousValue;
yearData[month] = change;
}
}
monthDataChanges.push(yearData);
}

return monthDataChanges;
}
Insert cell
monthData[5].June = 38
Insert cell
max_value = 106
Insert cell
Legend(d3.scaleSequential([0, max_value], d3.interpolateYlOrBr), {
title: "Shooting number",
})
Insert cell
fun1 = ()=> {
const width = 700;
const height = width+200;
const margin = ({top: width/10, right: width/10, bottom: width/5, left: width/5});
// const x = d3.scaleBand()
// .domain(years)
// .range([margin.left, width - margin.right])
// .padding(0.02);

// const y = d3.scaleBand()
// // .domain([...months].reverse())
// .domain(months)
// .range([height - margin.bottom, margin.top])
// .padding(0.02)

const squareSize = 50;

const xRange = [margin.left, margin.left + squareSize * years.length+16];
const yRange = [height - margin.bottom+20, height - margin.bottom - squareSize * months.length];


const x = d3.scaleBand()
.domain(years)
.range(xRange)
.padding(0.03); // 根据需要调整 padding

const y = d3.scaleBand()
.domain(months)
.range(yRange)
.padding(0.03); // 根据需要调整 padding






const color = d3.scaleSequential()
.domain([0,max_value])
.interpolator(d3.interpolateYlOrBr)

const xAxis = g => g
.attr("transform", `translate(0, ${height - margin.bottom+25})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.style("text-anchor", "start")
.attr("dx", "-.8em")
.attr("dy", ".8em")
// .attr("transform", "rotate(30)")
const yAxis = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.text(d => d)


const svg = d3.select(DOM.svg(width, height))
.style("width", "100%")
.style("height", "auto")
.style("font", "1rem verdana");
const make_class = (item) => item.toLowerCase().split(' ').join('_').split('-').join('')
const make_id = d => `coords_${Math.floor(x(d.xval))}_${Math.floor(y(d.yval))}`
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "8px")
.style("font", "12px sans-serif")
.style("background", "lightsteelblue")
.style("border", "0px")
.style("border-radius", "8px")
.style("pointer-events", "none");


const rects = svg.append("g")
.selectAll("g")
.data(monthData)
.enter().append("g")
.attr("class", (d, i) => `${i} bar`)
.selectAll("g")
.data(d => {
const innerData = [];
for (const month in d) {
if (month !== "index") {
innerData.push({
xval: d.index,
yval: month,
count: d[month]
});
}
}
console.log(innerData)
return innerData;
})
.enter().append("g");
// rects.append("rect")
// .attr("x", d => x(d.xval))
// .attr("y", d => y(d.yval))
// .attr("width", x.bandwidth())
// .attr("height", y.bandwidth())
// .style("fill", d => d.count === 0 ? "lightgray" : color(d.count))
// .append("title")
// .text(d => d.count)



rects.append("rect")
.attr("x", d => x(d.xval))
.attr("y", d => y(d.yval))
.attr("width", squareSize)
.attr("height", squareSize)
.style("fill", d => d.count === 0 ? "lightgray" : color(d.count))
.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", 0.9);
tooltip.html(`${d.yval}<br/>${d.xval}<br/>Shootings: ${d.count}`)
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");

d3.select(this)
.style("stroke", "white") // 设置边框颜色为白色
.style("stroke-width", 4); // 设置边框宽度,可根据需要调整
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
d3.select(this).style("stroke", "none");
})
;



svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);

return svg.node();



}
Insert cell
max = 61
Insert cell
Legend(d3.scaleSequential([max, -max], d3.interpolatePiYG), {
title: "Shooting number changes",
})
Insert cell
fun2 = ()=>{
const width = 700;
const height = width+200;
const margin = ({top: width/10, right: width/10, bottom: width/5, left: width/5-50});
const max_value = 61
// const x = d3.scaleBand()
// .domain(years)
// .range([margin.left, width - margin.right])
// .padding(0.02);

// const y = d3.scaleBand()
// // .domain([...months].reverse())
// .domain(months)
// .range([height - margin.bottom, margin.top])
// .padding(0.02)

const squareSize = 50;

const xRange = [margin.left, margin.left + squareSize * years.length+16];
const yRange = [height - margin.bottom+20, height - margin.bottom - squareSize * months.length];


const x = d3.scaleBand()
.domain(years)
.range(xRange)
.padding(0.03); // 根据需要调整 padding

const y = d3.scaleBand()
.domain(months)
.range(yRange)
.padding(0.03); // 根据需要调整 padding






const color = d3.scaleSequential()
.domain([max_value, -max_value])
.interpolator(d3.interpolatePiYG)


const xAxis = g => g
.attr("transform", `translate(0, ${height - margin.bottom+25})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.style("text-anchor", "start")
.attr("dx", "-.8em")
.attr("dy", ".8em")
// .attr("transform", "rotate(30)")
const yAxis = g => g
.attr("transform", `translate(${margin.left}, 0)`)
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.selectAll(".domain").remove())
.selectAll("text")
.text(d => d)


const svg = d3.select(DOM.svg(width, height))
.style("width", "100%")
.style("height", "auto")
.style("font", "1rem verdana");
const make_class = (item) => item.toLowerCase().split(' ').join('_').split('-').join('')
const make_id = d => `coords_${Math.floor(x(d.xval))}_${Math.floor(y(d.yval))}`
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("opacity", 0)
.style("position", "absolute")
.style("text-align", "center")
.style("padding", "8px")
.style("font", "12px sans-serif")
.style("background", "lightsteelblue")
.style("border", "0px")
.style("border-radius", "8px")
.style("pointer-events", "none");


const rects = svg.append("g")
.selectAll("g")
.data(month_Changes)
.enter().append("g")
.attr("class", (d, i) => `${i} bar`)
.selectAll("g")
.data(d => {
const innerData = [];
for (const month in d) {
if (month !== "index") {
innerData.push({
xval: d.index,
yval: month,
count: d[month]
});
}
}
console.log(innerData)
return innerData;
})
.enter().append("g");
// rects.append("rect")
// .attr("x", d => x(d.xval))
// .attr("y", d => y(d.yval))
// .attr("width", x.bandwidth())
// .attr("height", y.bandwidth())
// .style("fill", d => d.count === 0 ? "lightgray" : color(d.count))
// .append("title")
// .text(d => d.count)



rects.append("rect")
.attr("x", d => x(d.xval))
.attr("y", d => y(d.yval))
.attr("width", squareSize)
.attr("height", squareSize)
.style("fill", d => d.count === 0 ? "lightgray" : color(d.count))
.on("mouseover", function(event, d) {
tooltip.transition()
.duration(200)
.style("opacity", 0.9);
tooltip.html(`${d.yval}<br/>${d.xval}<br/>Changes: ${d.count}`)
.style("left", (event.pageX) + "px")
.style("top", (event.pageY - 28) + "px");

d3.select(this)
.style("stroke", "white") // 设置边框颜色为白色
.style("stroke-width", 4); // 设置边框宽度,可根据需要调整
})
.on("mouseout", function(d) {
tooltip.transition()
.duration(500)
.style("opacity", 0);
d3.select(this).style("stroke", "none");
});
svg.append("g")
.call(xAxis);
svg.append("g")
.call(yAxis);

return svg.node();



}
Insert cell
{
const ages = fun1();
const barchart_gender = fun2()
return html`
<div style="display: flex; width: 120%; max-width: 1500px; margin: auto; height: 100%; ">
<div style="width: 50%; padding: 0px;">
${ages}
</div>
<div style="width: 50%; padding: 0px;">
${barchart_gender}
</div>
</div>
`;



}
Insert cell
{
const height = width;
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);

const margin = ({top: 20, right: 30, bottom: 80, left: 40});
const x = d3.scaleBand()
.domain(data_month.map(d => d[0]))
.rangeRound([margin.left, width - margin.right])
.padding(0.1)

// const y = d3.scaleLinear()
// .domain([0, d3.max(data_month, d => d[1])])
// .rangeRound([height - margin.bottom, margin.top])

const y = d3.scaleLinear()
.domain([20, d3.max(data_month, d => d[1])]) // 将起始值设为 20
.rangeRound([height - margin.bottom, margin.top]);
// const xAxis = g => g
// .attr("transform", `translate(0,${height - margin.bottom})`)
// .call(d3.axisBottom(x)
// // .tickValues(d3.ticks(...d3.extent(x.domain()), width / 40).filter(v => x(v) !== undefined))
// .tickSizeOuter(0))

const xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x)
.tickValues(data_month.map(d => d[0]).filter(v => v.endsWith("-01") || v.endsWith("-07")))
.tickSizeOuter(0))

const yAxis = g => g
.attr("transform", `translate(${margin.left},0)`)
.style("color", "steelblue")
.call(d3.axisLeft(y).ticks(null, "s"))
.call(g => g.select(".domain").remove())
.call(g => g.append("text")
.attr("x", -margin.left)
.attr("y", 10)
.attr("fill", "currentColor")
.attr("text-anchor", "start")
.text(data_month.y))

svg.append("g")
.attr("fill", "steelblue")
.attr("fill-opacity", 0.8)
.selectAll("rect")
.data(data_month)
.join("rect")
.attr("x", d => x(d[0]))
.attr("width", x.bandwidth())
.attr("y", d => y(d[1]))
.attr("height", d =>(y(20) - y(d[1])));



svg.append("g")
.attr("fill", "none")
.attr("pointer-events", "all")
.selectAll("rect")
.data(data_month)
.join("rect")
.attr("x", d => x(d.year))
.attr("width", x.bandwidth())
.attr("y", 0)
.attr("height", height)
// .append("title")
// .text(d => `${d.year}
// ${d[0].toLocaleString("en")} new cars sold`);

svg.append("g")
.call(xAxis);

svg.append("g")
.call(yAxis);



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