Public
Edited
Jul 5, 2024
Importers
3 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
class d3PointLayer extends ol.layer.Layer {
constructor(options) {
super(options);
this.data = options.data;
this.extent = options.extent;
this.initial_zoom = options.initial_zoom;
this.fill = options.fill;
this.fillOpacity = options.fillOpacity;
this.stroke = options.stroke;
this.strokeWidth = options.strokeWidth;
this.r0 = options.r0;
this.b = options.r_zoom_factor;
this.pointerenter = options.pointerenter;
this.pointerleave = options.pointerleave;
this.svg = d3
.select(document.createElement("div"))
.append("svg")
.style("position", "absolute");
}

getSourceState() {
return "ready";
}

render(frameState) {
const width = frameState.size[0];
const height = frameState.size[1];
const projection = frameState.viewState.projection;
const d3Projection = d3.geoMercator().scale(1).translate([0, 0]);
let d3Path = d3.geoPath().projection(d3Projection);

const geoBoundsLeftBottom = ol.proj.fromLonLat(this.extent[0], projection);
const geoBoundsRightTop = ol.proj.fromLonLat(this.extent[1], projection);

const geoBoundsWidth = geoBoundsRightTop[0] - geoBoundsLeftBottom[0];
if (geoBoundsWidth < 0) {
geoBoundsWidth += ol.extent.getWidth(projection.getExtent());
}
const geoBoundsHeight = geoBoundsRightTop[1] - geoBoundsLeftBottom[1];

let pixel_lower_left = d3Projection(this.extent[0]);
let pixel_upper_right = d3Projection(this.extent[1]);
let pixelBoundsWidth = pixel_upper_right[0] - pixel_lower_left[0];
let pixelBoundsHeight = pixel_lower_left[1] - pixel_upper_right[1];

const widthResolution = geoBoundsWidth / pixelBoundsWidth;
const heightResolution = geoBoundsHeight / pixelBoundsHeight;
const r = Math.max(widthResolution, heightResolution);
const scale = r / frameState.viewState.resolution;

const center = ol.proj.toLonLat(
ol.extent.getCenter(frameState.extent),
projection
);
const angle = (-frameState.viewState.rotation * 180) / Math.PI;

d3Projection
.scale(scale)
.center(center)
.translate([width / 2, height / 2])
.angle(angle);

this.svg.attr("width", width);
this.svg.attr("height", height);

this.data.forEach(function (pt) {
let [x, y] = d3Projection([pt.lon, pt.lat]);
pt.x = x;
pt.y = y;
});

this.svg.selectAll("circle").remove();

let b;
if (typeof this.b == "number") {
b = this.b;
} else {
b = 1.414;
}
let z0 = this.initial_zoom;
let z = frameState.viewState.zoom;

let fill = this.fill || "#555";
let stroke = this.stroke || "black";
let strokeWidth;
if (this.strokeWidth === 0) {
strokeWidth = 0;
} else {
strokeWidth = this.strokeWidth || 1;
}
let fillOpacity;
if (this.fillOpacity === 0) {
fillOpacity = 0;
} else {
fillOpacity = this.fillOpacity || 0.8;
}
let r0 = this.r0;
function R(d) {
if (typeof r0 == "number") {
return r0 * b ** (z - z0);
} else if (typeof r0 == "function") {
return r0(d) * b ** (z - z0);
} else {
return 3 * b ** (z - z0);
}
}

let pts = this.svg.append("g");
pts
.selectAll("circle")
.data(this.data)
.join("circle")
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y)
.attr("r", R)
.attr("fill", fill)
.attr("stroke", stroke)
.attr("stroke-width", strokeWidth)
.attr("fill-opacity", fillOpacity);
if (this.pointerenter) {
pts.selectAll("circle").on("pointerenter", this.pointerenter);
}
if (this.pointerleave) {
pts.selectAll("circle").on("mouseleave", this.pointerleave);
}

return this.svg.node();
}
}
Insert cell
Insert cell
styleFunction = function (feature) {
return styles[feature.getGeometry().getType()];
}
Insert cell
styles = ({
MultiPolygon: new ol.style.Style({
stroke: new ol.style.Stroke({
color: "black",
width: 2
}),
fill: new ol.style.Fill({
color: "rgba(173, 216, 230, 0.1)"
})
})
})
Insert cell
Insert cell
// TopoJSON for the state boundary
nc_boundary = {
let nc = await FileAttachment("nc_boundary.json").json();
let boundary = topojson.feature(nc, nc.objects.nc);
return boundary;
}
Insert cell
// CSV for the points
pt_data = {
let pt_data = await FileAttachment("system_schools_enrollment_colors@1.csv").csv({
typed: true
});
let max = d3.max(pt_data, (o) => o.students);
pt_data.forEach((o) => (o.student_ratio = o.students / max));
return pt_data; //.slice(-1);
}
Insert cell
Insert cell
ol = {
const ol = await require("ol@7.3.0/dist/ol.js").catch(() => window.ol);
if (!ol._style)
ol._style = document.head.appendChild(
html`<link rel=stylesheet href="${await require.resolve(
"ol@7.3.0/ol.css"
)}">`
);
return ol;
}
Insert cell
width = 1100
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