Skip to content

Commit

Permalink
Merge pull request #807 from OpenGeoscience/feature-order
Browse files Browse the repository at this point in the history
Allow changing the feature.mouseover order.
  • Loading branch information
manthey authored Apr 11, 2018
2 parents 9e1317f + 12706fc commit 644aa5f
Show file tree
Hide file tree
Showing 5 changed files with 221 additions and 36 deletions.
13 changes: 13 additions & 0 deletions src/event.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,13 @@ geo_event.feature = {
* @event geo.event.feature.mouseover
*/
mouseover: 'geo_feature_mouseover',
/**
* The event contains the `feature`, the `mouse` record, the `previous`
* record of data elements that were under the mouse, and `over`, the new
* record of data elements that are unrder the mouse.
* @event geo.event.feature.mouseover_order
*/
mouseover_order: 'geo_feature_mouseover_order',
/**
* The event is the feature version of {@link geo.event.mouseout}.
* @event geo.event.feature.mouseout
Expand All @@ -343,6 +350,12 @@ geo_event.feature = {
* @event geo.event.feature.mouseclick
*/
mouseclick: 'geo_feature_mouseclick',
/**
* The event contains the `feature`, the `mouse` record, and `over`, the
* record of data elements that are unrder the mouse.
* @event geo.event.feature.mouseclick_order
*/
mouseclick_order: 'geo_feature_mouseclick_order',
/**
* The event is the feature version of {@link geo.event.brushend}.
* @event geo.event.feature.brushend
Expand Down
44 changes: 44 additions & 0 deletions src/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,22 @@ var feature = function (arg) {
if (!m_selectedFeatures.length && !over.index.length) {
return;
}

extra = over.extra || {};

// if we are over more than one item, trigger an event that is allowed to
// reorder the values in evt.over.index. Event handlers don't have to
// maintain evt.over.found. Handlers should not modify evt.over.extra or
// evt.previous.
if (over.index.length > 1) {
m_this.geoTrigger(geo_event.feature.mouseover_order, {
feature: this,
mouse: mouse,
previous: m_selectedFeatures,
over: over
});
}

// Get the index of the element that was previously on top
if (m_selectedFeatures.length) {
lastTop = m_selectedFeatures[m_selectedFeatures.length - 1];
Expand Down Expand Up @@ -311,6 +326,16 @@ var feature = function (arg) {
over = m_this.pointSearch(mouse.geo),
extra = over.extra || {};

// if we are over more than one item, trigger an event that is allowed to
// reorder the values in evt.over.index. Event handlers don't have to
// maintain evt.over.found. Handlers should not modify evt.over.extra.
if (over.index.length > 1) {
m_this.geoTrigger(geo_event.feature.mouseclick_order, {
feature: this,
mouse: mouse,
over: over
});
}
mouse.buttonsDown = evt.buttonsDown;
feature.eventID += 1;
over.index.forEach(function (i, idx) {
Expand Down Expand Up @@ -733,6 +758,25 @@ var feature = function (arg) {
return this;
};

/**
* If the selectionAPI is on, then setting
* `this.geoOn(geo.event.feature.mouseover_order, this.mouseOverOrderHighestIndex)`
* will make it so that the mouseon events prefer the highest index feature.
*
* @param {geo.event} evt The event; this should be triggered from
* `geo.event.feature.mouseover_order`.
*/
this.mouseOverOrderHighestIndex = function (evt) {
// sort the found indices. The last one is the one "on top".
evt.over.index.sort();
// this isn't necessary, but ensures that other event handlers have
// consistent information
var data = evt.feature.data();
evt.over.index.forEach(function (di, idx) {
evt.over.found[idx] = data[di];
});
};

/**
* Initialize the class instance. Derived classes should implement this.
*
Expand Down
55 changes: 55 additions & 0 deletions src/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,16 @@ var polygonFeature = function (arg) {
return coordinates;
}

/**
* Get the set of normalized polygon coordinates.
*
* @returns {object[]} An array of polygon positions. Each has `outer` and
* `inner` if it has any coordinates, or is undefined.
*/
this.polygonCoordinates = function () {
return m_coordinates;
};

/**
* Get the style for the stroke of the polygon. Since polygons can have
* holes, the number of stroke lines may not be the same as the number of
Expand Down Expand Up @@ -484,6 +494,51 @@ var polygonFeature = function (arg) {
return m_this;
};

/**
* If the selectionAPI is on, then setting
* `this.geoOn(geo.event.feature.mouseover_order, this.mouseOverOrderClosestBorder)`
* will make it so that the mouseon events prefer the polygon with the
* closet border, including hole edges.
*
* @param {geo.event} evt The event; this should be triggered from
* `geo.event.feature.mouseover_order`.
*/
this.mouseOverOrderClosestBorder = function (evt) {
var data = evt.feature.data(),
map = evt.feature.layer().map(),
pt = transform.transformCoordinates(map.ingcs(), evt.feature.gcs(), evt.mouse.geo),
coor = evt.feature.polygonCoordinates(),
dist = {};
evt.over.index.forEach(function (di, idx) {
var poly = coor[di], mindist;
poly.outer.forEach(function (line1, pidx) {
var line2 = poly.outer[(pidx + 1) % poly.outer.length];
var dist = util.distance2dToLineSquared(pt, line1, line2);
if (mindist === undefined || dist < mindist) {
mindist = dist;
}
});
poly.inner.forEach(function (inner) {
inner.forEach(function (line1, pidx) {
var line2 = inner[(pidx + 1) % inner.length];
var dist = util.distance2dToLineSquared(pt, line1, line2);
if (mindist === undefined || dist < mindist) {
mindist = dist;
}
});
});
dist[di] = mindist;
});
evt.over.index.sort(function (i1, i2) {
return dist[i1] - dist[i2];
}).reverse();
// this isn't necessary, but ensures that other event handlers have
// consistent information
evt.over.index.forEach(function (di, idx) {
evt.over.found[idx] = data[di];
});
};

/**
* Destroy.
*/
Expand Down
11 changes: 11 additions & 0 deletions tests/cases/feature.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ describe('geo.feature', function () {
feat.geoOn(geo.event.feature.brushend, function (evt) { events.brushend = evt; });
map.interactor().simulateEvent('mousemove', {map: {x: 20, y: 20}});
expect(events.mouseover.index).toBe(1);
points.index = [1, 2];
map.interactor().simulateEvent('mousedown', {map: {x: 20, y: 20}, button: 'left'});
map.interactor().simulateEvent('mouseup', {map: {x: 20, y: 20}, button: 'left'});
expect(events.mouseclick.index).toBe(2);
expect(events.mouseclick.top).toBe(true);
});
it('_unbindMouseHandlers', function () {
feat._unbindMouseHandlers();
Expand Down Expand Up @@ -254,6 +259,12 @@ describe('geo.feature', function () {
feat.updateStyleFromArray('radius', [11, 12, 13, 14], true);
expect(count).toBe(1);
});
it('mouseOverOrderHighestIndex', function () {
var evt = {over: {index: [3, 4, 2], found: []}, feature: feat};
expect(feat.mouseOverOrderHighestIndex(evt)).toBe(undefined);
expect(evt.over.index).toEqual([2, 3, 4]);
expect(evt.over.found.length).toBe(3);
});
});
describe('Check class accessors', function () {
var map, layer, feat;
Expand Down
134 changes: 98 additions & 36 deletions tests/cases/polygonFeature.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,44 +147,106 @@ describe('geo.polygonFeature', function () {
});

describe('Public utility methods', function () {
describe('pointSearch', function () {
it('basic usage', function () {
mockVGLRenderer();
var map, layer, polygon, data, pt;
map = createMap();
layer = map.createLayer('feature', {renderer: 'vgl'});
polygon = geo.polygonFeature({layer: layer});
polygon._init();
data = testPolygons;
polygon.data(data);
pt = polygon.pointSearch({x: 5, y: 5});
expect(pt.index).toEqual([0]);
expect(pt.found.length).toBe(1);
expect(pt.found[0][0]).toEqual(data[0][0]);
pt = polygon.pointSearch({x: 21, y: 10});
expect(pt.index).toEqual([1]);
expect(pt.found.length).toBe(1);
pt = polygon.pointSearch({x: 30, y: 10});
expect(pt.index).toEqual([]);
expect(pt.found.length).toBe(0);
pt = polygon.pointSearch({x: 51, y: 10});
expect(pt.index).toEqual([2, 3]);
expect(pt.found.length).toBe(2);
pt = polygon.pointSearch({x: 57, y: 10});
expect(pt.index).toEqual([3]);
expect(pt.found.length).toBe(1);
/* If the inner hole extends past the outside, it doesn't make that
* point in the polygon */
pt = polygon.pointSearch({x: 60, y: 13});
expect(pt.index).toEqual([]);
expect(pt.found.length).toBe(0);
it('pointSearch', function () {
mockVGLRenderer();
var map, layer, polygon, data, pt;
map = createMap();
layer = map.createLayer('feature', {renderer: 'vgl'});
polygon = geo.polygonFeature({layer: layer});
polygon._init();
data = testPolygons;
polygon.data(data);
pt = polygon.pointSearch({x: 5, y: 5});
expect(pt.index).toEqual([0]);
expect(pt.found.length).toBe(1);
expect(pt.found[0][0]).toEqual(data[0][0]);
pt = polygon.pointSearch({x: 21, y: 10});
expect(pt.index).toEqual([1]);
expect(pt.found.length).toBe(1);
pt = polygon.pointSearch({x: 30, y: 10});
expect(pt.index).toEqual([]);
expect(pt.found.length).toBe(0);
pt = polygon.pointSearch({x: 51, y: 10});
expect(pt.index).toEqual([2, 3]);
expect(pt.found.length).toBe(2);
pt = polygon.pointSearch({x: 57, y: 10});
expect(pt.index).toEqual([3]);
expect(pt.found.length).toBe(1);
/* If the inner hole extends past the outside, it doesn't make that
* point in the polygon */
pt = polygon.pointSearch({x: 60, y: 13});
expect(pt.index).toEqual([]);
expect(pt.found.length).toBe(0);

// enable stroke and test very close, but outside, of an edge
polygon.style({stroke: true, strokeWidth: 20});
pt = polygon.pointSearch({x: 5, y: 2.499});
expect(pt.index).toEqual([0]);
restoreVGLRenderer();
// enable stroke and test very close, but outside, of an edge
polygon.style({stroke: true, strokeWidth: 20});
pt = polygon.pointSearch({x: 5, y: 2.499});
expect(pt.index).toEqual([0]);
restoreVGLRenderer();
});

it('polygonCoordinates', function () {
mockVGLRenderer();
var map, layer, polygon;
map = createMap();
layer = map.createLayer('feature', {renderer: 'vgl'});
polygon = geo.polygonFeature({layer: layer});
polygon._init();
polygon.data(testPolygons);
var result = polygon.polygonCoordinates();
expect(result.length).toEqual(testPolygons.length);
expect(result[0].outer.length).toBe(3);
expect(result[0].inner.length).toBe(0);
expect(result[1].outer.length).toBe(4);
expect(result[1].inner.length).toBe(1);
expect(result[1].inner[0].length).toBe(4);
restoreVGLRenderer();
});

it('mouseOverOrderClosestBorder', function () {
mockVGLRenderer();
var map, layer, polygon, data;
map = createMap();
layer = map.createLayer('feature', {renderer: 'vgl'});
polygon = geo.polygonFeature({layer: layer});
polygon._init();
// define some overlapping polygons for testing
data = [{
outer: [[29, 20], [35, 20], [32, 25]]
}, {
outer: [[29, 22], [35, 22], [32, 27]],
inner: [[[30, 22.6], [34, 22.6], [32, 26]]]
}, {
outer: [[22, 30], [27, 32], [24, 35], [22, 35]]
}, {
outer: [[20, 30], [25, 32], [22, 35], [20, 35]]
}];
polygon.data(data).position(function (vertex) {
return {x: vertex[0], y: vertex[1]};
});

var evt = {
over: {index: [2, 3], found: []},
feature: polygon,
mouse: {geo: {x: 22.7, y: 34}}
};
expect(polygon.mouseOverOrderClosestBorder(evt)).toBe(undefined);
expect(evt.over.index).toEqual([2, 3]);
evt.mouse.geo = {x: 22.2, y: 34};
polygon.mouseOverOrderClosestBorder(evt);
expect(evt.over.index).toEqual([3, 2]);
evt.over.index = [0, 1];
evt.mouse.geo = {x: 32, y: 22.2};
polygon.mouseOverOrderClosestBorder(evt);
expect(evt.over.index).toEqual([0, 1]);
evt.mouse.geo = {x: 30.5, y: 22.2};
polygon.mouseOverOrderClosestBorder(evt);
expect(evt.over.index).toEqual([1, 0]);
evt.mouse.geo = {x: 30.7, y: 22.5};
polygon.mouseOverOrderClosestBorder(evt);
expect(evt.over.index).toEqual([0, 1]);

restoreVGLRenderer();
});

describe('rdpSimplifyData', function () {
Expand Down

0 comments on commit 644aa5f

Please sign in to comment.