Published
Edited
Jan 28, 2022
Insert cell
Insert cell
a=FileAttachment("中美贸易LDA.html").html()
Insert cell
styles = html`<style>

@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;600&display=swap');

.star-background {
background: url('${backgroundUrl}');
background-repeat: no-repeat;
background-size: cover;
}

.axis line, .axis path {
stroke: white;
stroke-width: 1px;
}

text {
fill: white;
font-size: 1.2em;
font-family: 'Open Sans', sans-serif;
}

.profile {
width: 50px;
border-radius: 50%;
float: left;
padding: 0 10px 10px 0;
}
div {
font-family: 'Open Sans', sans-serif;
}
</style>`
Insert cell
backgroundUrl = FileAttachment("background-1.jpeg").url()
Insert cell
symbolUrl = FileAttachment("symbol-1 (1).png").url();
Insert cell
d32 = require("https://d3js.org/d3.v5.min.js")
Insert cell
Insert cell
Insert cell
Insert cell
margin = 60
Insert cell
height = 500
Insert cell
countryStats = {
const group = {};
list.forEach(elem => {
const country = elem.countries[0];
if (group[country]) {
group[country]++;
}
else {
group[country] = 1;
}
});
return group;
}//没用
Insert cell
countryColors = ({
'北京市': 'rgba(255, 0, 0, 1)',
'重庆市': 'rgba(61, 190, 255, 1)',
'湖南省': 'rgba(255, 156, 181, 1)',
'湖北省': 'rgba(255, 156, 181, 1)',
'河北省': 'rgba(130, 240, 46, 1)',
'北京市': 'rgba(255, 118, 77, 1)',
'浙江省': 'rgba(240, 198, 46, 1)',
'安徽省': 'rgba(168, 168, 168, 1)',
'新疆维吾尔自治区': 'rgba(168, 110, 168, 1)',
'上海市': 'rgba(110, 168, 168, 1)',
'河南省': 'rgba(168, 168, 110, 1)',
'四川省': 'rgba(168, 255, 168, 1)',
'山东省': 'rgba(255, 255, 168, 1)',
'贵州省': 'rgba(255, 0, 255, 1)',
'台湾省': 'rgba(255, 110, 110, 1)',
'other': 'rgba(168, 168, 168, 1)',
'ALL':'rgba(255,255,255,1)'
})
Insert cell
quadtree = d32.quadtree()
.x(d => scaleTime(d.missions[0]))
.y(d => scaleAge(d.ageFirstMission))
.addAll(list)
Insert cell
scaleTime = d32.scaleTime()
.domain([scaleStats.minMissionDate, scaleStats.maxMissionDate])
.range([margin, 650 - margin]);//图的长度
Insert cell
scaleAge = d32.scaleLinear()
.domain([scaleStats.minAge, scaleStats.maxAge])
.range([height - margin, margin])//图的高度
Insert cell
function getColor(d) {
if (countryColors[d.countries[0]]) {
return countryColors[d.countries[0]];
}
return countryColors['Other']; //如果国家在countryColors中能找到,则返回countryColor中所定义的颜色,如果没有返回other的颜色rgba(168, 168, 168, 1)
}
Insert cell
function createTooltip(tooltip) {
const tooltip_height = 150;
const tooltip_width = 400;

tooltip
.append('text')
.attr('id', 'name')
.attr('x', margin + 15)
.attr('y', 30)
.style('fill', "white")
.style('font-size', '12px')
.style('font-weight', 'bold')
.text('Click on each point for more info');

tooltip
.append('foreignObject')
.attr('x', margin + 15)
.attr('y', 50)
.attr('width', tooltip_width - 10)
.attr('height', tooltip_height - 10)
.append('xhtml:div')
.attr('xmlns', 'http://www.w3.org/1999/xhtml')
.attr('id', 'description')
.style('font-size', '12px')
.style('color', "white");
}
Insert cell
function getDescription(d) {//获取每个人的相关描述及图片(左上角)
let desc = '';
if (d.profile_image) {
desc += `<img class='profile' src='${d.profile_image.src}'>`;
}
desc += d.comment;
const missionYears = d.missions.map(date => date.getFullYear());
console.log(missionYears);
// if (missionYears.length > 1) {
// desc += `</br>出道日期: ${missionYears.join(', ')}.`;
// } else {
// desc += `</br>出道日期: ${missionYears[0]}.`
// }
desc += `</br>出道日期: ${missionYears[0]}.`
desc += `</br>出道时的年龄: ${d.ageFirstMission}.`
desc +=`</br> Read more on <a href='${d.wiki_page}' target='_blank'>Baidu</>.`
return desc;
}
Insert cell
function removeGuidelines(chart){
d32.select('.guideline-group').selectAll('.guideline')
.remove();
}
Insert cell
function addGuideline(orientation, data, chart) {//加标注线(点击每个点的时候的辅助线)
const x1 = orientation === 'horizontal' ? margin : scaleTime(data.missions[0]);
const y1 = orientation === 'horizontal' ? scaleAge(data.ageFirstMission) : height - margin;
const x2 = scaleTime(data.missions[0]);
const y2 = scaleAge(data.ageFirstMission);
d32.select('.guideline-group').append('line')
.attr('class', 'guideline')
.attr('x1', x1)
.attr('y1', y1)
.attr('x2', x2)
.attr('y2', y2)
.style('stroke-dasharray', '2, 2')
.style('stroke', 'rgba(255, 255, 255, 0.3)');
}
Insert cell
viewof svg = {
let value='ALL';
const chart = d32
.create("svg")
.attr("class", "star-background")
.attr("width", 650)
.attr("height", height);

chart.append(() => styles);

const axisTime = d32
.axisBottom()
.scale(scaleTime)
.ticks(width > 500 ? 10 : 5)
.tickFormat(d32.timeFormat("%Y"))
.tickSize(5)
.tickPadding(8);

chart
.append("g")
.attr("class", "axis")
.attr("transform", `translate(0, ${height - margin})`)
.call(axisTime);

chart
.append("text")//横坐标
.attr("x", width / 2 - 100)
.attr("y", height - 10)
.text("出道日期")
.style("opacity", 0.8)
.style("font-size", "12px");

const axisAge = d32
.axisLeft()
.scale(scaleAge)
.ticks(10)
.tickSize(5)
.tickPadding(8);//

chart
.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + margin + ", 0)")
.call(axisAge);

chart
.append("text")//纵坐标
.attr("x", 0)
.attr("y", 0)
.attr("transform", `translate(20,${height / 2 + 100}) rotate(270)`)
.text("出道时的年龄")
.style("opacity", 0.8)
.style("font-size", "12px");

chart.append("g").attr("class", "guideline-group");

const entry = chart.selectAll(null).data(list).join("g");

entry
.append("circle")//画散点图
.attr("cx", (d) => scaleTime(d.missions[0]))
.attr("cy", (d) => scaleAge(d.ageFirstMission))
.attr("r", 2)
.style("fill", (d) => getColor(d))//颜色设置
.style("stroke", "none");

entry
.selectAll(null)
.data((d) => d.missions)
.join("circle")
.attr("cx", function (d) {
return scaleTime(d32.select(this.parentNode).datum().missions[0]);
})
.attr("cy", function (d) {
return scaleAge(d32.select(this.parentNode).datum().ageFirstMission);
})
.attr("r", (d, i) => 4 + i * 4)//调整图标半径大小
.style("stroke", function (d) {
return getColor(d32.select(this.parentNode).datum());
})
.style("stroke-width", 0.5)
.style("fill", "none");

const tooltip = chart.append("g");

tooltip.on("click", (event) => {
event.stopPropagation();
});

let lastPoint = null;
chart.on("click", function (event) {
const node = chart.node();
const point = quadtree.find(...d32.mouse(this));
node.value = value = point.name;
node.dispatchEvent(new CustomEvent("input"));
if (lastPoint !== point) {
if (lastPoint)
entry
.filter((e) => e === lastPoint)
.select("circle")
.attr("r", 2);
removeGuidelines(chart);
entry
.filter((e) => e === point)
.select("circle")
.attr("r", 6);
tooltip
.select("#name")
.text(`${point.name} - ${point.countries.join(", ")}`);
tooltip.select("#description").html(getDescription(point));
addGuideline("horizontal", point);
addGuideline("vertical", point);
}
lastPoint = point;
});
createTooltip(tooltip);
return Object.assign(chart.node(), { value});
}
Insert cell
Insert cell
Insert cell
Insert cell
chart = {
const svg = d31.create("svg")
.attr("class", "star-background")
.attr("font-size", "8pt")
.style("cursor", "default")
.attr("viewBox", [0, 0, width1, height1]);

const g = svg.selectAll("g")
.data(bubbles(chartData).leaves())
.join("g")
.attr("opacity", 1)
.attr("transform", d => `translate(${d.x},${d.y})`)
.on("mouseenter", e => {
e.currentTarget.parentElement.appendChild(e.currentTarget);
legend.node().parentElement.appendChild(legend.node());
})
.on("mouseover", (e, d) => {
g.transition().duration(500).attr("opacity", a => a === d ? 1 : 0.3)
g.selectAll(".ctext").transition().duration(500).attr("opacity", 0.5);
showLegend(legend, d);
})
.on("mouseout", () => {
g.transition().duration(500).attr("opacity", 1);
g.selectAll(".ctext").transition().duration(500).attr("opacity", 1);
legend.attr("opacity", 0);
});

g.append("g") //绘制散点气泡图
.call(g => g.append("circle").attr("r", d => d.r).attr("fill", d => bubbleColor(d.value)))
.call(g => g.append("g").attr("fill", d => invertedColor(d.value)).call(g => bubbleText(g, true, "ctext")));

g.append("g") //绘制饼图
.attr("class", "pie")
.call(g => g.append("g")
.attr("font-weight", "bold")
.attr("transform", d => `translate(0,${d.r + 10})`)
.call(bubbleText)
)
.selectAll("path")
.data(d => d31.pie()(d.data.values).map(p => ({pie: p, data: d})))
.join("path")
.attr("d", drawPie)
.attr("fill", (d, i) => pieColor(years[i]));

const legend = svg.append("g").attr("font-weight", "bold").attr("opacity", 1);
return svg.node();

}
Insert cell
bubbles = data => d31.pack()
.size([width1, height1])
.padding(3)(
d31.hierarchy({children: data})
.sum(d => d.total)
)
Insert cell
drawPie = d => d31.arc()
.innerRadius(0)
.outerRadius(d.data.r)
.startAngle(d.pie.startAngle)
.endAngle(d.pie.endAngle)()
Insert cell
bubbleText = (g, short, className) => {
g.append("text")
.attr("class", className)
.attr("text-anchor", "middle")
.text(d => short ? d.data.code : d.data.state);
g.append("text")
.attr("class", className)
.attr("text-anchor", "middle")
.attr("dy", "1em")
.text(d => short ? d31.format(".2s")(d.value) : toCurrency(d.value));
}
Insert cell
showLegend = (legend, d) => {
legend.selectAll("g").remove();
legend
.selectAll("g")
.data(years)
.join("g")
.attr("transform", (d, i) => `translate(0,${15 * i})`)
.call(g => g.append("rect")
.attr("width", 15).attr("height", 12)
.attr("rx", 3).attr("ry", 3)
.attr("fill", y => pieColor(y)))
.call(g => g.append("text")
.attr("dx", 20)
.attr("dy", "0.8em")
.text((y, i) => {
const v = d.data.values[i];
return `${y} ${toCurrency(v)} (${(v / d.value * 100).toFixed(1)}%)`
}));
legend.attr("opacity", 1)
.attr("transform", `translate(${d.x + d.r + 10},${d.y - d.r})`);
}
Insert cell
pieColor = d31.scaleOrdinal()
.domain(years)
.range(["#4e79a7","#f28e2c","#e15759","#76b7b2","#59a14f","#edc949","#af7aa1","#ff9da7","#9c755f","#bab0ab"])
Insert cell
bubbleColor = d31.scaleSequential(d31.interpolateViridis)
.domain(d31.extent(chartData.map(d => d.total)))
Insert cell
invertedColor = d31.scaleSequential(d31.interpolateCubehelixDefault)
.domain(d31.extent(chartData.map(d => d.total)))
Insert cell
toCurrency = (num) => d31.format(".2f")(num)
Insert cell
data1 = FileAttachment("stars@3.csv").csv({typed: true})
Insert cell
data2={
if(x=="ALL"){
let data2=data1;return data2;}
else{
let data2=data1.filter(d => d.chinese==x);return data2}
}
Insert cell
years = ["5月","6月","7月","8月"]
Insert cell
chartData = data2.map(d => {
const values = years.map(y => d[y])
return {
state: d["CH"],
code: d["chinese"],
values: values,
total: values.reduce((a, b) => a + b)
}
})
Insert cell
width1 = 1224
Insert cell
height1 = 968
Insert cell
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