Skip to content

fix issue #252, support subgroup aesthetic in geom_polygon for holes#320

Open
Nishita-shah1 wants to merge 2 commits into
animint:masterfrom
Nishita-shah1:fix/252-polygon-holes-subgroup
Open

fix issue #252, support subgroup aesthetic in geom_polygon for holes#320
Nishita-shah1 wants to merge 2 commits into
animint:masterfrom
Nishita-shah1:fix/252-polygon-holes-subgroup

Conversation

@Nishita-shah1
Copy link
Copy Markdown

Fixes #252

Problem

animint2 rendered isoband hole polygons as two separate filled shapes
instead of one polygon with a transparent hole. The subgroup aesthetic
(added to ggplot2 in 2019) was completely unknown to animint2's compiler
and renderer.

Real-world use case: Hi-C genomic cluster contour visualization where
holes in contour polygons are scientifically meaningful.

After (fix)

Screenshot 2026-04-04 200117 Screenshot 2026-04-04 200133

Solution

Three-layer fix across the full pipeline:

R/geom-polygon.r - added subgroup = NULL to default_aes so
GeomPolygon accepts the aesthetic.

R/geom-.r -- when subgroup is present in polygon data:

  • sets g$data_has_subgroup <- TRUE (serialized into plot.json)
  • converts subgroup to character so it survives TSV write
  • adds it to g$types so JS type converter keeps it as string

inst/htmljs/animint.js - when data_has_subgroup is true, uses
d3.geo.path().projection(null) with GeoJSON Polygon format instead
of d3.svg.line(). GeoJSON coordinates map directly to subgroup
structure: [outerRing, holeRing1, ...]. SVG fill-rule: evenodd
ensures hole rings appear transparent.

Tests

17 tests, 0 failures covering:

  • compiler: subgroup in TSV, data_has_subgroup in plot.json,
    no flag without subgroup
  • renderer: SVG path used, multiple M commands in d attribute,
    evenodd fill-rule applied
  • interactive: clickID and path checks for all 3 polygon cases
    (hole_and_mid, only_hole, no_hole) as specified in the issue
Screenshot 2026-04-04 201101

@tdhock @suhaani-agarwal @Faye-yufan could you please review this, thank you! Fixes #252

@Nishita-shah1
Copy link
Copy Markdown
Author

Hi @tdhock , @Faye-yufan and @suhaani-agarwal . Could you please review this.
Thank You!

Comment thread inst/htmljs/animint.js
Comment on lines +1382 to +1401
points.forEach(function(pt){
var sg = pt.subgroup !== undefined ? pt.subgroup : "1";
if(!rings_map.hasOwnProperty(sg)){
rings_map[sg] = [];
ring_order.push(sg);
}
rings_map[sg].push([scales.x(pt.x), scales.y(pt.y)]);
});
var coords = ring_order.map(function(sg){
var ring = rings_map[sg];
if(ring.length > 0){
ring = ring.concat([ring[0]]);
}
return ring;
});
var geojson = {
type: "Polygon",
coordinates: coords
};
return geoPath(geojson);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not understand this code.
I was expecting some library function call here, instead of these forEach/map calls. is that possible?
or can you please add comments and/or link some documentation?

Copy link
Copy Markdown
Author

@Nishita-shah1 Nishita-shah1 May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forEach and map here are plain JavaScript built-in array methods, forEach loops over each point to group them by subgroup into rings_map, and map transforms those groups into closed GeoJSON rings.

Is there any other library function to use? Could you please tell me about it? I'll read about it and make the changes in the code.

Also, I looked into d3.group() (https://d3js.org/d3-array/group) as a possible replacement for the forEach grouping step. Would that be a good direction to explore?

Copy link
Copy Markdown
Contributor

@suhaani-agarwal suhaani-agarwal May 9, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I looked into d3.group() (https://d3js.org/d3-array/group) as a possible replacement for the forEach grouping step. Would that be a good direction to explore?

hi nishita! d3.group() is from D3v7+, but animint.js uses D3v3. take a look at how grouping is done elsewhere in this file (search for d3.nest). The same pattern could likely apply to your code.

you can try refactoring the forEach grouping block using the existing library pattern you find.

Comment thread inst/htmljs/animint.js
var rings_map = {};
var ring_order = [];
points.forEach(function(pt){
var sg = pt.subgroup !== undefined ? pt.subgroup : "1";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @Nishita-shah1 , would g.data pass in null/na if subgroup is true? In what scenario that subgroup == undefined but g_info.data_has_subgroup == true?
Any test case for this scenario?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

draw polygon / path with holes

4 participants