Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow changing the feature.mouseover order. #807

Merged
merged 2 commits into from
Apr 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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