async function Chart()
{
const div = d3.create("div");
const info_div = div.append("div");
info_div.style("display", "none")
.style("position", "absolute")
.style("font-size", "85%")
.style("background", "white")
.style("border", "1px solid #bbb")
.style("border-radius", ".3em")
.style("box-shadow", "1px 1px 4px rgba(0, 0, 0, .05)")
.style("padding", "0 .5em");
const svg = div.append("svg")
.style("display", "block")
.attr("viewBox", [0, 0, chart_size[0], chart_size[1]]);
svg.append("defs").append("pattern")
.attr('id','bgimage')
.attr("width", "100%")
.attr("height", "100%")
.attr("x", "0")
.attr("y", "0")
.attr("viewBox", `0 0 ${img_size[0]} ${img_size[1]}`)
.append('image')
.attr('width', img_size[0])
.attr('height', img_size[1])
.attr('xlink:href', await FileAttachment("roads2.webp").url())
var defs_extra;
var transform_timeout;
var k = 1;
var clip_rect = svg.append("defs")
.append('clipPath')
.attr('id', 'map-clip-box')
.append('rect')
.attr('x', chart_content_rect[0][0])
.attr('y', chart_content_rect[0][1])
.attr('width', chart_content_rect[1][0])
.attr('height', chart_content_rect[1][1]);
var clip_layer = svg.append('g').attr('clip-path', 'url(#map-clip-box)');
const map_layer = clip_layer.append("g");
const sa_layer = map_layer.append("g");
const img_layer = clip_layer.append("g");
img_layer.append("path")
.datum(img_bounds_path)
.attr("d", path)
.attr("style", "pointer-events: none;")
.attr('fill','url(#bgimage)')
const select_layer = clip_layer.append("g")
.attr("stroke-width", 1.5);
// click event
function sa_clicked(event, d)
{
select_layer.selectAll("*").remove();
select_layer.append("path")
.datum(d)
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "white")
.attr("stroke-linejoin", "round");
select_layer.append("path")
.datum(d)
.attr("id", "select-strokes")
.attr("d", path)
.attr("fill", "none")
.attr("stroke", "blue")
.attr("stroke-dasharray", [3/k, 3/k])
.attr("stroke-linejoin", "round");
// read back title from DOM
var text = d3.select(this).select('title').text().split(/\r?\n/);
info_div.selectAll("*").remove();
info_div.style("display", "block");
info_div.append("span")
.style("cursor", "pointer")
.style("margin-left", "1em")
.style("margin-right", "-.5em")
.style("float", "right")
.text("✖")
.on("click", close_info);
text.forEach(l =>
info_div.append("span").text(l) && info_div.append("br"));
}
function close_info()
{
select_layer.selectAll("*").remove();
info_div.selectAll("*").remove();
info_div.style("display", "none");
}
var obj = {
node: div.node(),
svg: svg,
map_layer: map_layer,
img_layer: img_layer,
transform_callback: null };
obj.update_fill = function(collection, fill_f, title_f)
{
sa_layer.selectAll("path")
.data(collection.features)
.join("path")
.attr("fill", fill_f)
.attr("d", path)
.on("click", sa_clicked)
.selectAll("title")
.data(f => [f])
.join("title")
.text(title_f);
}
obj.zoomTransform = function()
{
return d3.zoomTransform(obj.svg.node());
}
function zoomed({transform}) {
k = transform.k;
map_layer.attr("transform", transform);
img_layer.attr("transform", transform);
select_layer.attr("transform", transform)
.attr("stroke-width", 1.5 / k);
select_layer.selectAll("#select-strokes")
.attr("stroke-dasharray", [3/k, 3/k]);
clip_rect
.attr('x', transform.x + transform.k * chart_content_rect[0][0])
.attr('y', transform.y + transform.k * chart_content_rect[0][1])
.attr('width', transform.k * chart_content_rect[1][0])
.attr('height', transform.k * chart_content_rect[1][1])
}
function zoomed_end({transform})
{
// update list of definitions after a delay
// (updating patterns is potentially slow)
if (obj.transform_callback)
{
clearTimeout(transform_timeout);
transform_timeout = setTimeout(function() {
obj.transform_callback(transform, obj)
transform_timeout = undefined;
}, 1000);
}
}
obj.set_transform_callback = function(f)
{
obj.transform_callback = f;
zoomed_end({transform: obj.zoomTransform()});
}
obj.trigger_transform_callback = function()
{
zoomed_end({transform: obj.zoomTransform()});
};
// zoom behaviour
svg.call(d3.zoom()
.extent([[0, 0], chart_size])
// ideally we would exclude the areas outside the clipping box but
// that doesn’t work well.
.translateExtent([[0, 0], chart_size])
.scaleExtent([1, max_zoom * img_size[0] / width])
.on("zoom", zoomed)
.on("end", zoomed_end));
return obj;
}