fgpv-vpgf / fgpv-vpgf

The Reusable Accessible Mapping Platform (RAMP), also known as the Federal Geospatial Platform Visualiser (FGPV), is a Javascript based web mapping platform that provides a reusable, responsive and WCAG 2.0 "AA" compliant common viewer platform for the Government of Canada.
https://fgpv-vpgf.github.io/fgpv-vpgf/master/docs/#/
Other
45 stars 48 forks source link

Ability to toggle ranges via legend #2368

Closed dan-bowerman closed 6 years ago

dan-bowerman commented 7 years ago

image

As previously discussed for CESI. The "EcoGeo" way to do this was to add the geodatabase to the service several times for each data range or symbol, and filter the geodatabase on those parameters. However, this makes the data grid impractical for CESI, so we need to develop a method for toggling ranges in the mapping interface. These selections should also toggle what is visible in the datagrid, however this is lesser priority.

Sample service: http://section917.cloudapp.net/arcgis/rest/services/CESI/CESI_Air_Ambient_OzoneAverage/MapServer

Here are some Acceptance Criteria (by @mweech):

  1. Esri layer renderers should allow visibility to be toggled. This will allow for layers to be filtered via their individual symbology stack.
  2. The viewer should not maintain visibility state of the symbology stack if the layer itself has it's visibility change.
  3. Parent layer visibility will always override renderer visibility.
  4. Renderer visibility should persist when using full state bookmarks
  5. Datagrid should reflect the visibility of the renderers
  6. Autolegend output will by default allow for the toggling of renderer visibility
  7. Structured legend should allow for the toggling of renderer visibility based on the following values being present in the config: a. the custom symbol config element has a mapping to a specific layer renderer b. the custom symbol config element specifies renderer visibility as toggleable c. the custom symbol config element can optionally specify an initial state (default: visible)
  8. Print map legend output will not omit hidden layer renderers from the legend, however the map itself will reflect the current visibility state.

WMS layer toggling will be considered out of scope for this issue.

AleksueiR commented 7 years ago

Duplicate of https://github.com/fgpv-vpgf/fgpv-vpgf/issues/1442.

AleksueiR commented 7 years ago

Filtering using symbology will work nicely for autogenerated legends. With structured legends, since the config author may override layer's symbology to provide better images for example. It can be a limitation - if you want filtering by symbology, you cannot specify your own symbols. Or maybe we can implement a mechanism of some sorts to link custom symbols with layer actual renderers to filter by.

dan-bowerman commented 7 years ago

At least for CESI, filtering on existing symbology is preferred. If the clients make any changes to their symbology, it will be through the MXD which will need to be republished. The symbols will always come from the service.

mweech commented 7 years ago

i've added some acceptance criteria to this following a conversation with @AleksueiR .

mweech commented 7 years ago

Adding to FGP project board due to original issue #1442 being created by FGP as a result of Usability testing.

milespetrov commented 7 years ago

Currently investigating the UniqueValueRenderer being used in the CESI FeatureLayer.

The renderer is available through geoApi as featureRecord._layer.renderer.

Initial tests on the method setVisualVariables in this renderer were semi-successful. The symbol outlines remain on the map, but the colors are now gone. Here's the parameter I fed to setVisualVariables:

[{"type": "opacityInfo",
"field": "Ozone_Symbol",
"stops": [{
    "value": 0,
    "opacity": 0
  }, {
    "value": 1,
    "opacity": 0
  },
{
    "value": 2,
    "opacity": 0
  },
{
    "value": 3,
    "opacity": 0
  },
{
    "value": 4,
    "opacity": 0
  },
{
    "value": 5,
    "opacity": 0
  }]}]

There are others to try such as sizeInfo.

If this fails we can override values via addValue. For example, addValue(0) hides all values of 0 in the Ozone_Symbol field (since we leave the symbol parameter undefined). We would do this for each hidden symbol. Though we would need to store the symbol properties separately and restore them manually when users are toggling symbols back to visible. I'd prefer to use setVisualVariables as it seems more appropriate (if possible).

milespetrov commented 7 years ago

Or the easy way is we just document that symbol outlines can't be toggled and proceed with the above setVisualVariables with opacity controls.

milespetrov commented 7 years ago
  1. How many renderers do we want to support. CESI uses UniqueValueRenderer so that one for sure. Do we need support for more types of renderers?

  2. Are we ok with symbol outlines not being hidden? CESI sample service has a grey outline for all the symbols, but this could be removed. Ideally we keep them but the implementation won't be as pretty.

dan-bowerman commented 7 years ago

I can't answer #1, but for #2, if I'm understanding correctly, in the case that a layer would be comprised of opaque coloured circles, we would instead see only outlines of the circles? I think I'd be OK with that (it's a rather cool way to show that there's till data turned off... spooky ghost data), but how would this impact the data grid?

milespetrov commented 7 years ago

That's right, if symbols have a defined outline (the service posted above as an example has a grey outline around all symbols but its not very noticeable with the colors present) then they will remain when toggled off. I'd imagine if that's not ideal you could always remove the outline from the symbols on the service itself.

Data table would not be affected (symbols toggled off would not appear). This only affects the visual renderer.

AleksueiR commented 7 years ago

We scrape the rendered only once and create an svg representation of the symbol to use in a symbology stack. If the renderer changes when it's toggled off, it would not affect the appearance of the generated symbol in the symbology stack.

milespetrov commented 6 years ago
  1. Will toggling visibility be:

    • Always active for all layers?
    • Active by default, unless made inactive through the config?
    • Inactive by default, unless made active through the config?
  2. Do we strictly toggle on different symbology?

    • This assumes there is a universal way to compare symbology so that points with the same symbols can be grouped together
    • Strictly toggle by default, but allow a field name to be set in the config to toggle on instead of by symbology (though some groupings would have different symbology)
    • Only toggle by the field name set in the config.

Possible layer property

"toggleSymbology": {
    "field": "OBJECTID",
    "groupByValue": [[1,2], [3,4]]
},

groupByValue above would put any points with OBJECTID 1 or 2 in the "same" symbology toggle.

james-rae commented 6 years ago

Few thoughts on this.

Miles brings up a good point about for Do we strictly toggle on different symbology. Basing this solely off the Renderer is tricky, as the renderer can have multiple entries for the same symbol. What is shown in the layer selector is the Legend, which is derived from the Renderer, and that derivation includes collapsing similar entries.

Another thing is it looks like up to now we are approaching this by changing visibility via the layer renderer. I don't think this is even possible when using a non-dynamic dynamic layer. An alternate approach I think worth looking at is leveraging the current filtering engine. Essentially clicking the checkbox beside the legend entry applies a filter; this results in the map updating but also the grid reflecting the filter. Some things to consider:

AleksueiR commented 6 years ago
  1. I think filtering by symbology should be active by default (if supported) unless disabled through the config.
  2. Toggling only by the field name specified in the config would be the easiest option, but this won't work for the autogenerated legends (which is what CESI will be using). I don't know much about renderers, but setting the field in the config should override whatever logic is used to hide using renderers themselves :(

I think this is a good idea to re-use the table filtering engine:

milespetrov commented 6 years ago

So I have a working demo here. Using sample 1, open the crops symbology and check one or more symbols.

Now go to config 46, expand "Dynamic Layer with only one child with filter". Expand "Power plants" symbology and toggle one or more symbols. In this case all symbols vanish. index-mobile page has some working and other not.

My code that sets the query is here.

I'd like to know if using self.block.definitionQuery = ... is the right way to go. Curious as to why this fails for some layers (is code incorrect, or is it the layer?).

Note: If you open a dev console in the above demo links I'm outputting the LegendNode and DynamicRecord if that helps.

james-rae commented 6 years ago

Problem appears to be that the query strings being generated are always being applied against the name field. The symbology/renderers are not tied to the name field; the cases where it works is when they align.

E.g. config 46, you are querying against field Facility, but renderer is based on field PrimSource. So nothing matches, and everything gets turned off.

Computing the query string becomes more complex — there are two main situations to deal with.

Class breaks renderers need to do a range. e.g. (renderer_field >= min_range_number AND renderer_field < max_range_number).

Unique value renderers need to compare against a field, but can be compared across up to three fields. e.g. of a two field UVR (renderer_field1 = ‘VALUE1’ AND renderer_field2 = ‘VALUE2’). These structures would be OR’d across each checked value in the legend.

Simple renderers are basically on/off for entire layer.

So the code that generates these queries will need to inspect the renderer of each layer. Additional complexity comes from having to consolidate the duplicates when going from renderer to legend (I think these would get OR’d).

Further complicating things is the geoApi proxy currently doesn’t expose the renderers (you can steal it from a feature layer esri object, but the dynamic layer children are more complex as we steal renderers from the server definition).

It might make sense to enhance the geoApi proxy’s symbology property. It currently returns an array of objects with svgcode and name properties, I could add a third property called queryString or something like that. This way all the renderer parsing remains on the geoApi (you can see similar code here that messes with renderers, but from an opposite direction). Your UI logic that generates a full query string would essentially be OR’ing each queryString that is checked.

So take a gander at what you’re currently doing, then let me know if doing that kind of enhancement on the geoApi makes sense.

milespetrov commented 6 years ago

Thanks for the detailed explanation, makes much more sense now. I agree that the query string should be generated in geoApi (best spot for that kind of logic). I'm already going through each symbol and generating a query string, so this would fit in nicely.

Keep this issue updated when geoApi is updated and I'll re-work my logic