Skip to content

Commit 5b6dc25

Browse files
visualize
1 parent ac26d30 commit 5b6dc25

3 files changed

Lines changed: 57 additions & 14 deletions

File tree

cybench/runs/viz/dashboard_template.html

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ <h3>Drill-down plots</h3>
327327
try {
328328
const geo = await d3.json(href);
329329
if (!geo || !geo.features) return null;
330-
const features = geo.features.map(f => ({
330+
const features = geo.features.map(f => rewindFeatureForSvg({
331331
...f,
332332
properties: { ...f.properties, loc: String(f.properties.loc ?? "") },
333333
}));
@@ -339,6 +339,48 @@ <h3>Drill-down plots</h3>
339339
}
340340
}
341341

342+
// Mapbox geojson-rewind (RFC 7946): outer rings CCW for D3/SVG fills.
343+
function rewindRing(ring, clockwise) {
344+
let area = 0;
345+
for (let i = 0, j = ring.length - 1; i < ring.length; j = i++) {
346+
const p0 = ring[i];
347+
const p1 = ring[j];
348+
area += (p1[0] - p0[0]) * (p1[1] + p0[1]);
349+
}
350+
if ((area < 0) === clockwise) ring.reverse();
351+
return ring;
352+
}
353+
354+
function rewindGeometry(geometry, outerClockwise) {
355+
if (!geometry) return geometry;
356+
if (geometry.type === "Polygon") {
357+
return {
358+
...geometry,
359+
coordinates: geometry.coordinates.map((ring, i) =>
360+
rewindRing(ring.slice(), i === 0 ? outerClockwise : !outerClockwise),
361+
),
362+
};
363+
}
364+
if (geometry.type === "MultiPolygon") {
365+
return {
366+
...geometry,
367+
coordinates: geometry.coordinates.map(poly =>
368+
poly.map((ring, i) =>
369+
rewindRing(ring.slice(), i === 0 ? outerClockwise : !outerClockwise),
370+
),
371+
),
372+
};
373+
}
374+
return geometry;
375+
}
376+
377+
function rewindFeatureForSvg(feature) {
378+
return {
379+
...feature,
380+
geometry: rewindGeometry(feature.geometry, false),
381+
};
382+
}
383+
342384
function yieldColor(value, extent) {
343385
if (value == null || !extent) return REGION_FILL_MISSING;
344386
const [lo, hi] = extent;
@@ -412,19 +454,24 @@ <h3>Drill-down plots</h3>
412454
{ type: "FeatureCollection", features: fitFeatures },
413455
);
414456
const path = d3.geoPath(projection);
457+
svg.append("rect")
458+
.attr("x", 0)
459+
.attr("y", 0)
460+
.attr("width", REGION_MAP_WIDTH)
461+
.attr("height", REGION_MAP_HEIGHT)
462+
.attr("fill", "#f8f9fa");
415463
svg.selectAll("path.region")
416464
.data(plotFeatures)
417465
.join("path")
418466
.attr("class", "region")
419-
.attr("fill-rule", "evenodd")
420467
.attr("d", path)
421468
.attr("fill", d => {
422469
const loc = d.properties.loc;
423470
const v = valueByLoc ? valueByLoc[loc] : null;
424471
return yieldColor(v, extent);
425472
})
426-
.attr("stroke", "#333")
427-
.attr("stroke-width", 0.35)
473+
.attr("stroke", "#fff")
474+
.attr("stroke-width", 0.5)
428475
.selectAll("title")
429476
.data(d => [d])
430477
.join("title")

cybench/runs/viz/region_map_lib.py

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,8 @@
1212

1313
try:
1414
from shapely.geometry.base import BaseGeometry
15-
from shapely.ops import orient as orient_polygon
1615
except ImportError: # pragma: no cover
1716
BaseGeometry = object # type: ignore[misc,assignment]
18-
orient_polygon = None # type: ignore[assignment]
1917

2018
# Skip geometries that break map fitting (dateline-spanning admin units).
2119
_MAX_MAP_LON_SPAN = 60.0
@@ -39,11 +37,9 @@ def dataset_crop(dataset: str) -> str:
3937

4038

4139
def prepare_geometry_for_geojson(geometry: BaseGeometry | None) -> BaseGeometry | None:
42-
"""Rewind rings for GeoJSON/SVG and drop pathological footprints."""
40+
"""Drop pathological footprints; ring winding is fixed in the dashboard (D3/SVG)."""
4341
if geometry is None or geometry.is_empty:
4442
return None
45-
if orient_polygon is not None:
46-
geometry = orient_polygon(geometry, sign=1.0)
4743
minx, miny, maxx, maxy = geometry.bounds
4844
if (maxx - minx) > _MAX_MAP_LON_SPAN or (maxy - miny) > _MAX_MAP_LAT_SPAN:
4945
return None

tests/runs/test_region_map_lib.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,18 @@ def test_dataset_crop():
3030
assert dataset_crop("wheat_NL") == "wheat"
3131

3232

33-
def test_prepare_geometry_rewinds_clockwise_ring():
33+
def test_prepare_geometry_keeps_ring_orientation():
3434
from shapely.geometry import Polygon
3535

3636
cw = Polygon([(0, 0), (0, 1), (1, 1), (1, 0), (0, 0)])
37-
fixed = prepare_geometry_for_geojson(cw)
38-
assert fixed is not None
39-
ring = list(fixed.exterior.coords)
37+
kept = prepare_geometry_for_geojson(cw)
38+
assert kept is not None
39+
ring = list(kept.exterior.coords)
4040
signed = sum(
4141
(ring[i + 1][0] - ring[i][0]) * (ring[i + 1][1] + ring[i][1])
4242
for i in range(len(ring) - 1)
4343
)
44-
assert signed < 0
44+
assert signed > 0
4545

4646

4747
def test_prepare_geometry_drops_dateline_spanning_polygon():

0 commit comments

Comments
 (0)