Skip to content

Add clustering access methods #1388

@manthey

Description

@manthey

Point features (and by extension marker features) support clustering. Once clustered, point.data() returns the clustered data, which is a list of unclustered point records followed by the clustered point records for a particular zoom level. The style accessors reference these records, not the original records set in data(). There isn't currently any official way to get the original data value or to get the points within different clusters.

As such, this results in messy code like

var original_data = [... some array of point data that contains style information on each point ...];
var points = layer.createFeature('point');
points.data(original_data);
points.clustering({radius: 10});

var firstPoint = (prop, d, i) => {
  if (d.__cluster) {
    d = d.obj;
  }
  if (d._points === undefined) {
    return d[prop];
  }
  if (d._points.length) {
    // we have to access the original data, since we don't have a public way to get to it now.
    return original_data[d._points[0].index][prop];
  }
  return firstPoint(prop, d._clusters[0], i);
}

points.style({
  radius: (d, i) => {
    // scale the radius based on how many points are included
    if (d.__cluster) {
      return 5 * (d.obj._count ** 0.5);
    }
    return 5; 
  },
  // have other styles use style of the first point
  fillColor: (d, i) => firstPoint('fillColor', d, i),
  strokeColor: (d, i) => firstPoint('strokeColor', d, i),
  fillOpacity: (d, i) => firstPoint('fillOpacity', d, i),
  strokeOpacity: (d, i) => firstPoint('strokeOpacity', d, i) 
});

This is a mess -- we shouldn't need to access the internal methods and properties to do this. There is also an internal __data property that is not used and not set as commented.

We should remove __data and add some convenience methods. (update: see #1389)

I think the following would be appropriate:

  • points.unclusteredData() : return the original data (rather than adding an overload parameter to the data method, which would be the other approach to this).
  • points.clustered(d): where d is the data value passed to a style accessor, returns a boolean
  • points.clusterPointCount(d): returns the count of points (1 for unclustered)
  • points.clusterPoint(d, cluster_point_index): returns a specific point within the cluster. This is the original data record, not the cluster point record.
  • points.clusterPoints(d, onlyDirect=false, limit=null): return the points associated with a cluser. If onlyDirect was set, this would not include sub-clusters. If onlyDirect is false, this is functionally the list that is used to pull points from points.getClusterPoint(d, cluster_point_index). These are the original data records, not the cluster point records.
  • points.clusterClusters(d): return a list of immediate clusters that are children of this cluster. These could be passed to the cluster* methods above to recurse manually.

With this , our code would now look like:

var original_data = [... some array of point data that contains style information on each point ...];
var points = layer.createFeature('point');
points.data(original_data);
points.clustering({radius: 10});

points.style({
  radius: (d, i) => 5 * this.clusterPointCount(d) ** 0.5,
  fillColor: (d, i) => this.clusterPoint(d, 0).fillColor,
  strokeColor: (d, i) => this.clusterPoint(d, 0).strokeColor,
  fillOpacity: (d, i) => this.clusterPoint(d, 0).fillOpacity,
  strokeOpacity: (d, i) => this.clusterPoint(d, 0).strokeOpacity,
});

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions