Skip to content

Instantly share code, notes, and snippets.

@AleksueiR
Last active June 7, 2019 14:03
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AleksueiR/781abee5f9d4f28e2b19246a26cad9cf to your computer and use it in GitHub Desktop.
Save AleksueiR/781abee5f9d4f28e2b19246a26cad9cf to your computer and use it in GitHub Desktop.
Custom Export Layouts

Approach

Export layout customization will be done through project-specific plugins which can cater to their individual export image requirements. An export plugin is expected to take over the mid-point of the image generation process during export.

Unlike table plugins, an export plugin does not have any UI component (except for possible options added to the export settings panel) and reuses the default export dialogue. Also, an export plugin is not involved in the last stage of merging separate images into a single export file (this includes checking for tainted canvases according to the config options and providing feedback to the user if the image cannot be saved automatically).

This is intended to simplify development of export plugins, since they don't need to generate their own UI (which generally should stay generic) and deal with tainted images. This, however, does not preclude a plugin develop from disabling the default export functionality and writing a full-replacement export plugin with its own custom UI dialogue and image fusion logic.

High-level workflow

  1. An export plugin (plugin) registers with RAMP on map load
  2. Export is triggered by a user or through the API
  3. The export service detects the presence of a plugin and halts the default process
  4. The export service passes the exportComponentsService to the plugin
  5. The plugin can modify the exportComponentsService which is by default controlled through the config
    1. The plugin can add or remove any of the default export components (title, map, mapElements, legend, textblock (new component), timestamp)
    2. The plugin can modify the configurations of component (for example, specifying legend width and desired column width; or modifying the existing legend structure (map.legendBlocks) to exclude/include certain layers)
    3. The plugin can add new, custom generators to the exportComponentsService
    4. The plugin can reorder the components to control the image overlap, if necessary
  6. The plugin executes the exportComponentsService to generate export images for corresponding components
  7. The plugin returns the full size of the export image (this is the size of the canvas where all component images will be placed) to export service
  8. The plugin returns the generated images to the export service along with their x,y pixel coordinates relative to the full canvas (which creates the desired layout) for the final merging
  9. The export service displays images in the export dialog
  10. The user is satisfied and triggers the download
  11. The export service merges the images into a single canvas and attempts to auto-save the file (depending on the cleanCanvas setting it might be impossible if any of the images are tainted)

Generators

Text-Block generator

Right now, we only have a footnote text generator which is a plain text generator. We think that the HTML to Canvas library should be able to handle any simple HTML (and maybe even complex HTML, but plugin authors need to ensure the end result matches their expectations) and that we can build a generic text block generator to replace the footnote generator. An export plugin can use it several times to generate several text blocks and place them in different sections of the final export image.

Plugins can either import that library themselves or it can be provided internally.

Custom generators

A plugin can add a function returning an SVG/canvas wrapped in a promise to the exportComponentsService to include any arbitrary image (like a watermark or a logo) into the final export image.

Finer points

  • Even though the export service is going to perform the final merging of export images, this does not stop an export plugin from merging images by itself and just returning a single canvas to the export service to "merge" again.
  • The order in which a plugin returns component images should be bottom-up - it's easier to figure out what overlaps what in this case, I think. Overlapping of components could be intentional, like when you want your two-layer legend to be displayed on top of the map image, or if you want the scale bar/north arrow to be rendered on the map.
  • By default, the export dialog will give the user an option to turn on/off certain export components (based on the config settings). The same behaviour should apply to any custom generators added by a plugin, unless explicitly prohibited.

Implementation plan

  • Create a generic Text-Block generator

This is important. If we cannot reliably convert HTML into SVG/canvas, we would need to keep the plain-text generator and make export plugins responsible for any fancy text blocks they want to add to the final export image (conceivably, they can do it through the custom generators, but it will be more effort for plugin developers).

  • Allow export plugins to register with the Export Service

  • Modify the Export Service's default logic to invoke an export plugin if present and accept returned component images (including the overall canvas size and components' relative coordinates)

  • Ensure users are still able to interact with certain components like the title component through (or custom component created by a plugin, like a text field), if required

  • Modify the exportComponentsService to accept new custom generators and changes to the existing service configuration (which generators should be used and in what order)

  • Modify the legend export generator to accept legend with/column number configurations as well as the legendBlocks collection from the plugin

  • Create a sample export plugin (CCCS request) which renders the map and legend side-by-side and a text blurb underneath

Acceptance criteria

  • A Text-Block generator creates a close-enough representation of the source HTML code
  • An export plugin can be registered with RAMP and is activated when the export functionality is engaged
  • An export plugin accepts the exportComponentsService and returns
    • the overall canvas size
    • individual export SVG/canvas images with relative pixel coordinates
  • It is still possible for users to interact with export components like the title and modify them
  • Snacks for all dietary restrictions are available in the web-mapping pod

Pontification

tl;dr

Export layouts are predefined; a map author can pre-select which export layout will be used through the config with an option to allow the user to change the layout. An export image with any layout can be generated through an API call.

Predefined layouts

Right now, @spencerwahl and I thinking the best approach in terms of efforts and payoff is to provide a set of predefined export layouts without giving map authors ability to create their own layouts. Arguments as follows:

  • there are only a limited set of way map, legend and some text/markdown can be arranged on the page;
  • right now, we only have a single request for a custom export layout (see below);
  • given a limited number of possible layouts, a new layout would likely mean making changes to the RAMP code and hence a new release;
  • we suspect it will require a lot of effort to provide a way for map authors to create custom export layouts which produce decent results (a drag-and-drop editor with scale support for example would be idea way for map authors to create new layouts; using Markdown or HTML to create layout is a clunky option and can be easily abused); there needs to be a very strong case to spend time implementing this functionality;

Layout Types

Stack

The existing export layout stacks all elements and the only obvious modification is to swap positions of legend and map. Text blocks can be added in between other elements to cover all the options. This layout works well and produces decent results with any legend, so we think it's most suitable for RAMP instances without a predefined config (like catalog maps for example).

Here's a sample (original on the left; modification on the right):

Side-by-side

CCCS requested a layout where the legend is placed besides the map image (on the left or right). With this layout, only two text blocks can be used - above or below the map. This layout will not produce good images if the legend is very long as it will create a lot of white space underneath the map. It would best used for relatively short legend - maps with predefined configs and a few layers.

Here's a sample:

Overlay

This is a variation on the side-by-side arrangement of elements where the legend is placed directly on top of the map image. There are only two text blocks as well. This layout can only work with very small legends (just a couple of layers without any sublayers) because the legend obscures part of the map. The legend can be placed in one of the corners of the map image.

Here's a sample:

Layout Modes

CCCS requests a custom export layout (where legend can be placed to the side of the map image) so the resulting image can be inserted into a report. Let's call this Report Mode.

Layout Modes are separate from the layout types, and both modes should support all the possible arrangements.

Classic Mode

This is the layout used right now. We do not maintain any aspect ratio and don't try to scale font sizes to fit into a regular page. In this mode, the export image can be ridiculously long if you just keep adding layers to the map.

Report Mode

It seems that a crucial point in the CCCS request is being able to insert the export image into a report - the image should fit into a standard page (assume 11 x 8.5) and any text on the image should remain legible after scaling (need to think about font sizes here). From this, we also get Portrait and Landscape variants of the layout.

In the report mode, there will be no option to add blocks of text to the image because that can change the size/ratio of the resulting image. A single line for a title at the top or a caption at the bottom of the image should be possible. Since this image is generated to be included in a report, any additional text information should come direction from the surrounding text.

The last point needs to be confirmed. Would a single caption/title line be enough for CCCS needs or not?

Landscape

In landscape, we further assume that an export image would always fill the full page and therefore has a fixed 11 x 8.5 ratio and correspondingly sized text.

Here are samples of landscape export images (no legend, legend at the bottom, legend on the side, floating legend):

Portrait

In Portrait, the export image can either take the full page (fixed ratio) or be inserted in the middle of the document.

Technically, the full page Portrait layout is a special case of a export image intended to be inserted in the middle of a page. The ratio of this export image should not be smaller than 8.5 x 11. We need to make sure that when the image is scaled to fix the width of the document, the fonts are readable.

In the following example, text above and below the export image belongs to the report document.

Config and API

A specific layout (layout mode, layout type, and any text which can be added to the layout) can be set in the config as the default with a possible option to allow the user to change the layout later.

If we want to allow users to switch layouts, we would need to design a UI component for that. It will probably include a two-step process: selection layout mode and then selection layout type, with some further options where to put the legend (above/below, left/right, one of four corners when floating).

RAMP API should allow to generate any export image of any layout regardless of the config settings.

When selecting the report mode, the map author should test and ensure that the legend looks good in that layout - long legend will overflow the image and be truncated.

Good to have

  • a config option to specify which layers and layer entries should be included in the export legend
  • a UI component that lets users to specify which layer entries should be included in the export legend
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment