Jump to content

Dynamic SVG? How might you try to solve this...?


BrendonKoz
 Share

Recommended Posts

I worked for quite some time coming up with this mock-up interface which originated from an architectural PDF of our building's floor plan. It's a (partially convoluted) interactive SVG, taking advantage of CSS and JS, along with a form control to highlight areas on the map, swap out active floor(s), and provide textual directions on how to find the particular location within our building (there's no good studies on accessibility for maps, so it's my best attempt at an accessible alternative/supplement for the SVG image).

Example page in question.

I'd like to, if possible, get the location name and description (directions) to be powered internally by ProcessWire.

Each item also has required attributes:

  • Level (floor)
  • Attribute type: Collection, Service, Collection, Location
  • An identifier that matches the SVG's hardcoded object path. (Ex: identifier is "space.glasby" points to #sspl-floorplan .space.glasby .item -- in this scenario, "space" could also refer to its attribute type)

The biggest problem is that the SVG is not created dynamically, it is manually created/maintained from an Illustrator file, exported as SVG, and then customized with classes and IDs. So, currently, ProcessWire would need to be able to examine the structure of the SVG to identify items (and at a minimum, its floor and attribute type).

I've been trying to think of a way to allow for integration for a couple weeks now, but I'm a little stumped. While creating the mock-up I was not considering that the data would need to come from somewhere and unfortunately(?) took complete advantage of static content. Now I'm not sure how I'd re-integrate things. All of the text that is displayed directly on the map is part of the SVG itself, so if it's to be powered by ProcessWire, I'm not sure if replacing it with JavaScript, re-processing it on save, or converting it to a PHP file (to allow dynamic string interpolation from a data source) would be the best solution here...or if I should admit defeat and leave it all as static content.

I now have an idea of how to proceed, but I'd love to hear how you might've solved this, either from the beginning, or from my current sticking point above.

I'm going to use a separate PHP (template for the SVG) file that will generate all of the various shapes except for objects/structures that will not change, and insert them dynamically into the SVG, and then overwrite the static SVG with what is generated; this will allow me complete control over the markup. This will get run on save of the ProcessWire page file. Unfortunately this means that I'll have to store the SVG path string in the associated field in ProcessWire as well. I suppose that's one (giant) step away from integrating an SVG drawing tool.

Link to comment
Share on other sites

Interesting problem. If I understand correctly, you want to have two sources of truth that you need to connect somehow, i.e. the data inside the SVG and data from ProcessWire? I wonder if that’s worthwhile, because what are the chances you’ll want to change something on one side and not the other? To get translatable strings inside the SVG you could always use {placeholders} and populate them with WireTextTools, but as soon as you significantly change a text’s length, you’ll need to touch the SVG anyway, plus you will need to keep the placeholder names in sync.

Obviously authoring the SVG is the most annoying part of managing this content, but I don’t see how you would get around it, so maybe try and make it the only part? You could cram everything into the SVG in some standardized way, like predefined classes and data- attributes, then analyze it with JS and build all the toggles dynamically.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

I did something similar in a project, using this library: https://github.com/Rct567/DomQuery to manipulate the content of the SVG file (it's basically XML), using file_get_contents (from file), and then modifying texts, assigning/removing css classes, hiding/removing elements, etc. In this particular case, I ended rendering the modified SVG to PNG using Imagick for the final output (delivered by AJAX). The SVG document must be prepared in advance for that. Using the proper IDs and classes. Visual styles were applied by CSS classes and not directly on elements, in order to manipulate them. Modifications were not interactive, they were captured with a form.

 

  • Thanks 1
Link to comment
Share on other sites

I should've put this topic into the Dev Talk forum section; my mistake! If a moderator wants to move it, by all means... ?

I'm sure this isn't the most elegant way to do this, but it's what I came up with in the time I had.

I ended up using a (ProField) Combo Field (see attached image for schema) to store each individual object's attributes that I wanted to have some level of editability within the CMS, and then stored those in a simple Repeater field as part of the "floorplan" template. I'll lock down the class_name, class_addition, and svg_path fields to administrator level accounts as they will be maintained in a much more fragile way.

I used a Pages::saveReady hook in ready.php to detect changes on the repeater template (all repeaters have a hidden system template) in order to read in a version of the floor plan SVG that had valid, but easily identifiable XML strings to be used for (dynamic) replacement - and then write out a modified version using said replacement. An example of that string is below -- the floor number is the only thing that differs between each {placeholder}-esque string.

<g id="floor0_fill"></g>

The hook would then loop through all of the data provided in each combo field to generate the layers as needed, and then save the new version of the SVG file, completed as expected. I'll provide the code below, but it's only intended as an example for what I did.

// Floor plan page - dynamically generate the static SVG from a template, and saved page fields' data
$wire->addHookBefore('Pages::saveReady', function(HookEvent $event) {
	// Get values of arguments sent to hook (and optionally modify them)
	$page = $event->arguments(0);
	if ($page->template != 'floorplan') return;

	// Only process if the content of the floorplan items has changed
	$changes = $page->getChanges(true);
	if (!isset($changes['floorplan_repeater'])) return;

	// Generate the individual item SVG element entries
	$repeater = $page->floorplan_repeater;
	$svg = [];
	foreach ($repeater as $item) {
		$name  = $item->floorplan_item->name;
		$type  = implode(' ', $item->floorplan_item->type);
		$path  = str_replace("\n", "\n\t\t\t\t", $item->floorplan_item->svg_path);
		$level = $item->floorplan_item->level - 1; // fix offset as set from PW ComboField's Select type
		$class = $item->floorplan_item->class_name;
		$full_class = $item->floorplan_item->class_addition ? "{$class} {$item->floorplan_item->class_addition}" : $class;
		$svg[$level][] =
"		<g id='floor{$level}_{$class}' class='{$type} {$full_class}'>
			<a class='item' xlink:href='#content-skip'>
				{$path}
				<desc>{$name}</desc>
				<text class='label' x='40' y='70'>{$name}</text>
			</a>
		</g>";
	}

	// Open the SVG template (floorplan-template.svg), replace the content where needed, and save to the final SVG file (floorplan.svg)
	$path = $this->wire()->config->path('templates') . 'svgs/';
	$svg_template = $this->wire('files')->fileGetContents("{$path}floorplan-template.svg");
	if ($svg_template === false) {
		$this->error("The SVG template file could not be found: \"{$path}floorplan-template.svg\"", Notice::log);
		return;
	}
	foreach ($svg as $index => $floor) {
		$svg_template = str_replace('<g id="floor'.$index.'_fill"></g>', implode("\n", $floor), $svg_template);
	}
	$this->wire('files')->filePutContents($path.'floorplan.svg', $svg_template);
});

It took me quite some time to find the proper way to identify a file path in ready.php!! That alone might be useful to someone. PW's filePutContents method (which also sets chmod) needed a path formatted in a specific way before it would work, and I wanted to do it the PW way, rather than the standard PHP way. Anyway, moving on...

At this point all of the data was in the database, the SVG was generated dynamically from a template, and I moved on to dynamically generating the hidden definition list elements (that provide directions to each selectable object on the map, per floor), and the form elements that filter which map object is being highlighted on the map (with directions) at any given time. Lots of class/id/data massaging trickery, for sure! ...like the one below which was especially fun (and extremely ugly code).

// Example Output: <span class="badge">G</span> <span class="badge">2</span>
// Example Input:  [0 => 'ground', 2 => 'two']
$badges = '<span class="badge">' . implode('</span> <span class="badge">', explode(',', str_replace('0', 'G', implode(',',array_keys($properties['floors']))))) . '</span> ';

?

It's all working as of today, I only have some cleanup and optimizations to do - and the staff have just decided to do major furniture changes. ?‍♂️ At least the coding part is done! Back to the drawing board on the Illustrator file though!

combo-floorplan_item.png

page.png

  • Like 3
Link to comment
Share on other sites

@BrendonKozThanks for the interesting write-up.

Generally speaking, with maps or chart graphics, there are two main ways to ensure accessibility:

One is described neatly in this article: https://css-tricks.com/accessible-svgs/#aa-interactive-images

e.g. the use of role=group vs. role=img, using role=list role=listitem, tabindex etc. In your map, you could turn your three floor groups into three lists.

The other strategy is to output an unstyled + visually hidden table or ul/ol somewhere that represents the floors and rooms/areas. You would then simply put aria-hidden=true on the entire SVG. This strategy is certainly the most efficient. However, if you want to enable focusing areas with the keyboard (for people who can't use the mouse), you'd have to alter your SVG further, like the above example.

  • Like 1
  • Thanks 1
Link to comment
Share on other sites

Thanks, dragan! I very much appreciate you sharing what you know of accessibility in this regard. When I researched this, I specifically looked for guidelines related to "maps", interactive or otherwise. I figured with the number of shopping malls around the world, surely someone would've been sued a large enough amount of money to end up being forced to determine best practices. Unfortunately I was wrong. There isn't much out there aside from providing a text-based alternative, an audio-alternative for the text (if feasible), and high contrast colors and large text. I'll admit I can work on the high contrast, though that can come with a CSS media override via browser setting detection, though I still need to write those style overrides (and fix up my dark mode styles).

From your two examples, I went the hidden DL (DT / DD) element route -- that's where the directions (on how to get to/find) related to each of the entered items within the repeater get rendered straight to HTML. I need to review the DL's aria attributes though, so thank you for the reminder! I haven't hidden the SVG via ARIA, but still need to think if that's the best decision here (it likely is).

I'll also look over the CSS-Tricks article since it's more SVG specific and likely why I didn't see it. I'd be curious to see how simple it might be to implement into my SVG implementation. When I was looking, I read through things like these:

Digital Maps & Accessibility
Accessible Maps

The first one mentions pointing out accessible parking and highlighting drop-offs and ramp access which I either forgot about, or this wasn't an article I read before (but instead was very similar). I'll have to add some supplements to this relating to outdoor access. The second one, from the W3C basically says that there aren't any guidelines because the level of detail in a map is too high, and the needs of the map and the person vary too much to directly identify any set rules (paraphrased).

Link to comment
Share on other sites

On 8/27/2022 at 9:26 PM, BrendonKoz said:

Thanks, dragan! I very much appreciate you sharing what you know of accessibility in this regard.

You're welcome ?

Your current implementation of your DL has display: none on the entire definition list (inline styles). I understand this is work in progress, but display: none will be completely ignored by screen readers. I would put the class .sr-only not just on the h2, but on the entire section instead.

I just now, on second sight, realized that unter "map options" you have a lot more stuff going on, in an accordion, with all kinds of filters. Previously I just looked at the first accordion item "library floor". 

As you have correctly mentioned, complex visuals that convey information, need special attention re: a11y. There are no quick shortcuts or golden recipes that help you out. It's all about context.

You have put a tremendous amount of work into exploring the building, making sure the user can filter / find everything. This whole "map options" thing is sure a great help for "regular" users who are not visually impaired. Kudos to that.

Now, if you look at the big picture, you will realize that all these fine-grained options don't do anything for the non-visual users. They will most likely be confused. If you wanted to build the entire filter/options functionality with a11y in mind, you would need to invest a lot of work. You'd have to use aria-live to inform the user that something has changed in the map after the user made a new selection. 

Currently, if the user selects the "Glasby Room" radio button, the visually impaired persons don't get the information that regular users get (by toggling/removing the inline display:none). You would have to map these with either an aria-label right at the input level, or with aria-describedby or some such. (I strongly encourage you to test it out yourself with a screen reader - don't rely on automated tests like Lighthouse - they all have their limits, especially when it comes to context)

I would personally visually hide all filter stuff under "map options", but provide a table in each accordion item. Tables are well-supported by assistive technologies, while DLs are only so-so. If budget is tight (and it always is tight for accessibility-related work) I'd opt for one big .sr-only table, and use aria-hidden on both the SVG and the filters.

Just my 0.02.
 

  • Like 2
  • Thanks 1
Link to comment
Share on other sites

@dragan Absolutely excellent feedback. I'll need to take this all in and review it in more detail -- and as you suggest, run it through some screen readers. I'm completely unfamiliar with aria-live aspects, and (unfortunately) only mildly aware of aria itself. What I need is to take a course on accessibility. Reading about it is one thing - seeing it in practice and understanding it is another. ♥️ I definitely want to do better.

What you're playing with above in the example is a static HTML mockup template, prior to ProcessWire integration. Although the interface is pretty much the same at the moment, it will divert as development continues, so if I do improve things, it won't be visible!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...