Skip to content

Commit

Permalink
Use different marker shapes for different categories of data
Browse files Browse the repository at this point in the history
  • Loading branch information
timbrock committed Nov 16, 2023
1 parent a94aa40 commit a1e95eb
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 17 deletions.
29 changes: 18 additions & 11 deletions R/app.R
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
#' @import shiny dplyr
geocatApp <- function(...) {
timeoutTime = 15
circleRadius = 7
customCol = "#ECAC7C"
gbifCol = "#509E2F"
csvCol = "#0078b4"
gbifOffCol = "#a5cd96"
csvOffCol = "#7fbbd9"

#### ui ####
ui <- fluidPage(
theme = shinythemes::shinytheme("darkly"),
shinyjs::useShinyjs(),

tags$html(lang = "en", `data-timeout-mins` = timeoutTime),
tags$html(lang = "en", `data-timeout-mins` = timeoutTime, `data-circle-radius` = circleRadius),
tags$head(
tags$title("ShinyGeoCAT - Geospatial Conservation Assessment Tools"), # WCAG modification
tags$link(rel = "stylesheet", href = "style.css"),
Expand Down Expand Up @@ -393,11 +400,11 @@ geocatApp <- function(...) {
targetGroup = 'mappoints',
circleMarkerOptions=drawCircleMarkerOptions(
color="#FFFFFF",
radius=7,
radius = circleRadius,
stroke=T,
weight=2.5,
fill=T,
fillColor="#ECAC7C",
fillColor=customCol,
opacity=1,
fillOpacity=0.5,
repeatMode = TRUE,
Expand Down Expand Up @@ -500,11 +507,11 @@ geocatApp <- function(...) {
lng = long,
lat = lat,
color="#FFFFFF",
radius=7,
radius = circleRadius,
stroke=T,
weight=2.5,
fill=T,
fillColor="#ECAC7C",
fillColor=customCol,
opacity=1,
fillOpacity=0.5
)
Expand Down Expand Up @@ -675,12 +682,12 @@ geocatApp <- function(...) {
points <- filter(values$analysis_data, geocat_source != "User point")

used_pal <- colorFactor(
palette=c("#509E2F", "#0078b4"),
palette=c(gbifCol, csvCol),
domain=c("GBIF", "User CSV")
)

unused_pal <- colorFactor(
palette=c("#a5cd96", "#7fbbd9"),
palette=c(gbifOffCol, csvOffCol),
domain=c("GBIF", "User CSV")
)

Expand All @@ -701,29 +708,29 @@ geocatApp <- function(...) {
popup = ~pcontent,
layerId = ~geocat_id,
group="mappoints",
radius = 7,
radius = circleRadius,
color="#FFFFFF",
stroke = T,
weight = 2.5,
fill = T,
fillColor = ~used_pal(geocat_source),
fillOpacity = 0.5,
#options = markerOptions(draggable = FALSE),
options = pathOptions(pane = "mappoints"),
options = pathOptions(pane = "mappoints", className = ~factor(geocat_source, levels=c("GBIF", "User CSV"), labels=c("shape-square", "shape-hexagon"))),
data=used_points
) %>%
leaflet::addCircleMarkers(
layerId = ~geocat_id,
group="unmappoints",
radius = 7,
radius = circleRadius,
color="#BBBBBB",
stroke = T,
weight = 2,
fill = T,
fillColor = ~unused_pal(geocat_source),
fillOpacity = 0.2,
#options = markerOptions(draggable = FALSE),
options = pathOptions(pane = "unmappoints"),
options = pathOptions(pane = "unmappoints", className = ~factor(geocat_source, levels=c("GBIF", "User CSV"), labels=c("shape-square", "shape-hexagon"))),
data=unused_points
)
})
Expand Down
108 changes: 102 additions & 6 deletions www/js/map.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@

function fixA11yIssues () {
function fixSearchInput() {
// Search bar should be of type search rather than have that role
$('.search-input')
.attr('type', 'search')
.removeAttr('role');
.attr('type', 'search')
.removeAttr('role');
}

// The rest of this function makes sure tabbing around works properly.

function fixKeyboard() {
// Make sure tabbing around works properly.
// Specifically, the focus doesn't get lost when opening or closing
// the layers popup
const $searchButton = $('.leaflet-control-search')
Expand Down Expand Up @@ -39,7 +41,7 @@ function fixA11yIssues () {
evt.preventDefault();
});


// Add floating dialog for keyboard users
const setupFloatingDialog = function($control, $dialog) {
let clicking = false;

Expand Down Expand Up @@ -98,6 +100,100 @@ function fixA11yIssues () {
}


function fixShapes() {
const $html = $(document.documentElement);
const circleRadius = parseFloat($html.attr('data-circle-radius'));

const area = Math.PI * Math.pow(circleRadius, 2);


const createPathTransformer = function(edgeLength, coords) {
const getPathMid = path => `M${path.split(/[A-Z]/i)[1]} m ${circleRadius}, 0`;

const end = coords.map(function([x, y], i) {
if (i === 0) {
return `m ${x * edgeLength}, ${y * edgeLength}`;
}
const [px, py] = coords[i-1];
const dx = (x - px) * edgeLength;
const dy = (y - py) * edgeLength;
const z = i === (coords.length - 1) ? 'Z' : '';
return `l ${dx}, ${dy} ${z}`;
}, '');

return function(path) {
const start = getPathMid(path);
return `${start} ${end}`;
}
}


const squarifyPath = (function() {
const edgeLength = Math.sqrt(area);

const coords = [
[-1/2, -1/2],
[1/2, -1/2],
[1/2, 1/2],
[-1/2, 1/2]
];

return createPathTransformer(edgeLength, coords);
})();


const hexifyPath = (function() {
const edgeLength = Math.sqrt((2*area) / (3*Math.sqrt(3)));
const rt3o2 = Math.sqrt(3) / 2;

const coords = [
[-1/2, -rt3o2],
[1/2, -rt3o2],
[1, 0],
[1/2, rt3o2],
[-1/2, rt3o2],
[-1, 0]
];

return createPathTransformer(edgeLength, coords);
})();

const transformMarkerPath = function(marker, shape) {
const path = marker.getAttribute('d');
if (path === "M0 0") { return; } // offscreen
if (path === marker.savedPath) { return; } // already altered shape
const pathTransformer = { square: squarifyPath, hexagon: hexifyPath }[shape];
marker.savedPath = pathTransformer(path);
marker.setAttribute('d', marker.savedPath);
}

const childListMutateCallback = function (mutationList) {
mutationList.forEach(function(d) {
const mutatedElements = d.type === 'childList' ? d.addedNodes : [d.target];
mutatedElements.forEach(function(el) {
const $el = $(el);
if ($el.hasClass('shape-square')) { transformMarkerPath(el, 'square'); }
else if ($el.hasClass('shape-hexagon')) { transformMarkerPath(el, 'hexagon'); }
});
});
}

const $panes = $('#mymap :is(.leaflet-mappoints-pane, .leaflet-unmappoints-pane)');

const elementMutatedObserver = new MutationObserver(childListMutateCallback);
const config = { childList: true, subtree: true, attributes: true, attributeList: ['d'] };
$panes.each(function() { elementMutatedObserver.observe(this, config); });

}


function fixA11yIssues () {
fixSearchInput();
fixKeyboard();
fixShapes();
}


function elementAppendedMutation(mutationList, observer) {
const mutation = mutationList[0];
const $target = $(mutation.target);
Expand Down

0 comments on commit a1e95eb

Please sign in to comment.