javascript - 将 observable 适配到 D3 但网页中没有渲染
问题描述
我正在关注https://observablehq.com/@d3/bar-chart-race-explained中的教程。
我正在使用版本 89.0.4389.90 的 chrome 来测试代码。
我主要是复制代码并下载了数据 csv 文件,我正在尝试在本地运行代码 - 这里是:
<!DOCTYPE html>
<html>
<head>
<title>Assignment1</title>
<script src="d3.v6.min.js"></script>
</head>
<body>
<!--<svg width="1600" height="800" id="mainsvg" class="svgs"></svg>-->
<script>
let margin = { top: 16, right: 6, bottom: 6, left: 0 };
let barSize = 48;
let n = 12;
let width = 1600;
let duration = 250;
let height = margin.top + barSize * n + margin.bottom;
d3.csv("category-brands.csv").then((data) => {
const x = d3.scaleLinear([0, 1], [margin.left, width - margin.right]);
const y = d3
.scaleBand()
.domain(d3.range(n + 1))
.rangeRound([margin.top, margin.top + barSize * (n + 1 + 0.1)])
.padding(0.1);
const names = new Set(data.map((d) => d.name));
console.log(names);
let datevalues = Array.from(
d3.rollup(
data,
([d]) => +d.value,
(d) => d.date,
(d) => d.name
)
);
console.log(datevalues);
datevalues = datevalues
.map(([date, data]) => [new Date(date), data])
.sort(([a], [b]) => d3.ascending(a, b));
console.log("datavalues:", datevalues);
function rank(value) {
const data = Array.from(names, (name) => ({
name,
value: value(name),
}));
data.sort((a, b) => d3.descending(a.value, b.value));
for (let i = 0; i < data.length; ++i) data[i].rank = Math.min(n, i);
return data;
}
console.log(
"rank",
rank((name) => datevalues[0][1].get(name))
);
const k = 10;
const keyframes = (function () {
const keyframes = [];
let ka, a, kb, b;
for ([[ka, a], [kb, b]] of d3.pairs(datevalues)) {
for (let i = 0; i < k; ++i) {
const t = i / k;
keyframes.push([
new Date(ka * (1 - t) + kb * t),
rank(
(name) =>
(a.get(name) || 0) * (1 - t) + (b.get(name) || 0) * t
),
]);
}
}
keyframes.push([new Date(kb), rank((name) => b.get(name) || 0)]);
return keyframes;
})();
console.log("total frames:", keyframes.length);
console.log("total frames:", keyframes);
let nameframes = d3.groups(
keyframes.flatMap(([, data]) => data),
(d) => d.name
);
console.log("name frames number:", nameframes.length);
console.log("name frames:", nameframes);
let prev = new Map(
nameframes.flatMap(([, data]) => d3.pairs(data, (a, b) => [b, a]))
);
let next = new Map(nameframes.flatMap(([, data]) => d3.pairs(data)));
console.log("pref:", prev);
console.log("next:", next);
function bars(svg) {
let bar = svg.append("g").attr("fill-opacity", 0.6).selectAll("rect");
return ([date, data], transition) =>
(bar = bar
.data(data.slice(0, n), (d) => d.name)
.join(
(enter) =>
enter
.append("rect")
.attr("fill", color)
.attr("height", y.bandwidth())
.attr("x", x(0))
.attr("y", (d) => y((prev.get(d) || d).rank))
.attr("width", (d) => x((prev.get(d) || d).value) - x(0)),
(update) => update,
(exit) =>
exit
.transition(transition)
.remove()
.attr("y", (d) => y((next.get(d) || d).rank))
.attr("width", (d) => x((next.get(d) || d).value) - x(0))
)
.call((bar) =>
bar
.transition(transition)
.attr("y", (d) => y(d.rank))
.attr("width", (d) => x(d.value) - x(0))
));
}
function labels(svg) {
let label = svg
.append("g")
.style("font", "bold 12px var(--sans-serif)")
.style("font-variant-numeric", "tabular-nums")
.attr("text-anchor", "end")
.selectAll("text");
return ([date, data], transition) =>
(label = label
.data(data.slice(0, n), (d) => d.name)
.join(
(enter) =>
enter
.append("text")
.attr(
"transform",
(d) =>
`translate(${x((prev.get(d) || d).value)},${y(
(prev.get(d) || d).rank
)})`
)
.attr("y", y.bandwidth() / 2)
.attr("x", -6)
.attr("dy", "-0.25em")
.text((d) => d.name)
.call((text) =>
text
.append("tspan")
.attr("fill-opacity", 0.7)
.attr("font-weight", "normal")
.attr("x", -6)
.attr("dy", "1.15em")
),
(update) => update,
(exit) =>
exit
.transition(transition)
.remove()
.attr(
"transform",
(d) =>
`translate(${x((next.get(d) || d).value)},${y(
(next.get(d) || d).rank
)})`
)
.call((g) =>
g
.select("tspan")
.tween("text", (d) =>
textTween(d.value, (next.get(d) || d).value)
)
)
)
.call((bar) =>
bar
.transition(transition)
.attr(
"transform",
(d) => `translate(${x(d.value)},${y(d.rank)})`
)
.call((g) =>
g
.select("tspan")
.tween("text", (d) =>
textTween((prev.get(d) || d).value, d.value)
)
)
));
}
function textTween(a, b) {
const i = d3.interpolateNumber(a, b);
return function (t) {
this.textContent = formatNumber(i(t));
};
}
formatNumber = d3.format(",d");
function axis(svg) {
const g = svg
.append("g")
.attr("transform", `translate(0,${margin.top})`);
const axis = d3
.axisTop(x)
.ticks(width / 160)
.tickSizeOuter(0)
.tickSizeInner(-barSize * (n + y.padding()));
return (_, transition) => {
g.transition(transition).call(axis);
g.select(".tick:first-of-type text").remove();
g.selectAll(".tick:not(:first-of-type) line").attr(
"stroke",
"white"
);
g.select(".domain").remove();
};
}
function ticker(svg) {
const now = svg
.append("text")
.style("font", `bold ${barSize}px var(--sans-serif)`)
.style("font-variant-numeric", "tabular-nums")
.attr("text-anchor", "end")
.attr("x", width - 6)
.attr("y", margin.top + barSize * (n - 0.45))
.attr("dy", "0.32em")
.text(formatDate(keyframes[0][0]));
return ([date], transition) => {
transition.end().then(() => now.text(formatDate(date)));
};
}
let formatDate = d3.utcFormat("%Y");
let color = (function () {
const scale = d3.scaleOrdinal(d3.schemeTableau10);
if (data.some((d) => d.category !== undefined)) {
const categoryByName = new Map(
data.map((d) => [d.name, d.category])
);
scale.domain(categoryByName.values());
return (d) => scale(categoryByName.get(d.name));
}
return (d) => scale(d.name);
})();
const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);
const updateBars = bars(svg);
const updateAxis = axis(svg);
const updateLabels = labels(svg);
const updateTicker = ticker(svg);
const start = async function () {
for (const keyframe of keyframes) {
const transition = svg
.transition()
.duration(duration)
.ease(d3.easeLinear);
console.log("iteration..");
// Extract the top bar’s value.
x.domain([0, keyframe[1][0].value]);
updateAxis(keyframe, transition);
updateBars(keyframe, transition);
updateLabels(keyframe, transition);
updateTicker(keyframe, transition);
await transition.end();
}
};
start();
});
</script>
</body>
</html>
我已经更改了代码以在没有 observablehq 的环境的情况下运行,但是在运行代码之后,网页上没有显示任何内容。
控制台日志显示数据处理逻辑正常,但是对于渲染部分,除了打印什么都不做iteration
。
我的代码有什么问题?
解决方案
您需要将svg
刚刚创建的内容附加到 HTML 中的某个元素以查看输出。
d3.create
只是创建一个分离的元素。由于您没有将其附加到任何地方,因此我们没有在屏幕中看到输出。
我们可以使用d3.select
选择我们想要放置此图表的位置,然后使用 then .append
。
d3.select('#chart').append('svg')
const fileUrl =
'https://static.observableusercontent.com/files/aec3792837253d4c6168f9bbecdf495140a5f9bb1cdb12c7c8113cec26332634a71ad29b446a1e8236e0a45732ea5d0b4e86d9d1568ff5791412f093ec06f4f1?response-content-disposition=attachment%3Bfilename*%3DUTF-8%27%27category-brands.csv';
let margin = {
top: 16,
right: 6,
bottom: 6,
left: 0,
};
let barSize = 48;
let n = 12;
let width = 1600;
let duration = 250;
let height = margin.top + barSize * n + margin.bottom;
d3.csv(fileUrl).then((data) => {
const x = d3.scaleLinear([0, 1], [margin.left, width - margin.right]);
const y = d3
.scaleBand()
.domain(d3.range(n + 1))
.rangeRound([margin.top, margin.top + barSize * (n + 1 + 0.1)])
.padding(0.1);
const names = new Set(data.map((d) => d.name));
let datevalues = Array.from(
d3.rollup(
data,
([d]) => +d.value,
(d) => d.date,
(d) => d.name
)
);
datevalues = datevalues
.map(([date, data]) => [new Date(date), data])
.sort(([a], [b]) => d3.ascending(a, b));
function rank(value) {
const data = Array.from(names, (name) => ({
name,
value: value(name),
}));
data.sort((a, b) => d3.descending(a.value, b.value));
for (let i = 0; i < data.length; ++i) data[i].rank = Math.min(n, i);
return data;
}
const k = 10;
const keyframes = (function () {
const keyframes = [];
let ka, a, kb, b;
for ([[ka, a], [kb, b]] of d3.pairs(datevalues)) {
for (let i = 0; i < k; ++i) {
const t = i / k;
keyframes.push([
new Date(ka * (1 - t) + kb * t),
rank((name) => (a.get(name) || 0) * (1 - t) + (b.get(name) || 0) * t),
]);
}
}
keyframes.push([new Date(kb), rank((name) => b.get(name) || 0)]);
return keyframes;
})();
console.log('total frames:', keyframes.length);
console.log('total frames:', keyframes);
let nameframes = d3.groups(
keyframes.flatMap(([, data]) => data),
(d) => d.name
);
console.log('name frames number:', nameframes.length);
console.log('name frames:', nameframes);
let prev = new Map(
nameframes.flatMap(([, data]) => d3.pairs(data, (a, b) => [b, a]))
);
let next = new Map(nameframes.flatMap(([, data]) => d3.pairs(data)));
console.log('pref:', prev);
console.log('next:', next);
function bars(svg) {
let bar = svg.append('g').attr('fill-opacity', 0.6).selectAll('rect');
return ([date, data], transition) =>
(bar = bar
.data(data.slice(0, n), (d) => d.name)
.join(
(enter) =>
enter
.append('rect')
.attr('fill', color)
.attr('height', y.bandwidth())
.attr('x', x(0))
.attr('y', (d) => y((prev.get(d) || d).rank))
.attr('width', (d) => x((prev.get(d) || d).value) - x(0)),
(update) => update,
(exit) =>
exit
.transition(transition)
.remove()
.attr('y', (d) => y((next.get(d) || d).rank))
.attr('width', (d) => x((next.get(d) || d).value) - x(0))
)
.call((bar) =>
bar
.transition(transition)
.attr('y', (d) => y(d.rank))
.attr('width', (d) => x(d.value) - x(0))
));
}
function labels(svg) {
let label = svg
.append('g')
.style('font', 'bold 12px var(--sans-serif)')
.style('font-variant-numeric', 'tabular-nums')
.attr('text-anchor', 'end')
.selectAll('text');
return ([date, data], transition) =>
(label = label
.data(data.slice(0, n), (d) => d.name)
.join(
(enter) =>
enter
.append('text')
.attr(
'transform',
(d) =>
`translate(${x((prev.get(d) || d).value)},${y(
(prev.get(d) || d).rank
)})`
)
.attr('y', y.bandwidth() / 2)
.attr('x', -6)
.attr('dy', '-0.25em')
.text((d) => d.name)
.call((text) =>
text
.append('tspan')
.attr('fill-opacity', 0.7)
.attr('font-weight', 'normal')
.attr('x', -6)
.attr('dy', '1.15em')
),
(update) => update,
(exit) =>
exit
.transition(transition)
.remove()
.attr(
'transform',
(d) =>
`translate(${x((next.get(d) || d).value)},${y(
(next.get(d) || d).rank
)})`
)
.call((g) =>
g
.select('tspan')
.tween('text', (d) =>
textTween(d.value, (next.get(d) || d).value)
)
)
)
.call((bar) =>
bar
.transition(transition)
.attr('transform', (d) => `translate(${x(d.value)},${y(d.rank)})`)
.call((g) =>
g
.select('tspan')
.tween('text', (d) =>
textTween((prev.get(d) || d).value, d.value)
)
)
));
}
function textTween(a, b) {
const i = d3.interpolateNumber(a, b);
return function (t) {
this.textContent = formatNumber(i(t));
};
}
formatNumber = d3.format(',d');
function axis(svg) {
const g = svg.append('g').attr('transform', `translate(0,${margin.top})`);
const axis = d3
.axisTop(x)
.ticks(width / 160)
.tickSizeOuter(0)
.tickSizeInner(-barSize * (n + y.padding()));
return (_, transition) => {
g.transition(transition).call(axis);
g.select('.tick:first-of-type text').remove();
g.selectAll('.tick:not(:first-of-type) line').attr('stroke', 'white');
g.select('.domain').remove();
};
}
function ticker(svg) {
const now = svg
.append('text')
.style('font', `bold ${barSize}px var(--sans-serif)`)
.style('font-variant-numeric', 'tabular-nums')
.attr('text-anchor', 'end')
.attr('x', width - 6)
.attr('y', margin.top + barSize * (n - 0.45))
.attr('dy', '0.32em')
.text(formatDate(keyframes[0][0]));
return ([date], transition) => {
transition.end().then(() => now.text(formatDate(date)));
};
}
let formatDate = d3.utcFormat('%Y');
let color = (function () {
const scale = d3.scaleOrdinal(d3.schemeTableau10);
if (data.some((d) => d.category !== undefined)) {
const categoryByName = new Map(data.map((d) => [d.name, d.category]));
scale.domain(categoryByName.values());
return (d) => scale(categoryByName.get(d.name));
}
return (d) => scale(d.name);
})();
const svg = d3.select('#chart').append('svg').attr('viewBox', [0, 0, width, height]);
const updateBars = bars(svg);
const updateAxis = axis(svg);
const updateLabels = labels(svg);
const updateTicker = ticker(svg);
const start = async function () {
for (const keyframe of keyframes) {
const transition = svg
.transition()
.duration(duration)
.ease(d3.easeLinear);
// Extract the top bar’s value.
x.domain([0, keyframe[1][0].value]);
updateAxis(keyframe, transition);
updateBars(keyframe, transition);
updateLabels(keyframe, transition);
updateTicker(keyframe, transition);
await transition.end();
}
};
start();
});
<script src="https://d3js.org/d3.v6.min.js"></script>
<div id="chart"></div>
推荐阅读
- if-statement - 如果各种变量的条件如何使用SPSS?
- .net - OWIN ADFS 的用户可以在隐身 VS 中拥有不同的声明类型。普通浏览器?
- laravel - Laravel - 如何在不删除销售详细信息的情况下更新输入数组
- spring - 没有 springboot-starter-web-flux 的 Spring WebClient
- javascript - 单击 npm start 时 react.js 中的错误
- node.js - 如何在 Azure 机器人消息文本中添加超链接
- python - Python,Scrapy Pipeline csv out 问题,for 循环中的错误
- cors - 从前端 (Vue 3) 到后端 (influxDB 1.8) 的 Cors 策略阻止请求
- javascript - React Project-单击并移动鼠标时,div尝试移动
- google-cloud-platform - GCP存储桶错误cname解析