scy
scy

Reputation: 327

Dynamically add elements to SVG

I'm using a modified version of a map, created by https://azgaar.github.io/Fantasy-Map-Generator/ as an Angular(9) template svg. The file is 12,000 lines long and therefore slows down compilation significantly. I have experienced a further performance reduction when enumerating all 5248 grid-cells (shown on the linked map by pressing the shortcut e) and setting an id to every single cell. This, unfortunately is necessary for my use case.

As this map features 12 different kinds of overlay-groups (p.e. cities, lakes, routes, coastline) and not all are needed at once, I thought I could strip down this .svg to a minimum skeletton, requesting the different Elements via REST once the user chooses to do so.

The following will add the correct lines (exactly as cropped out from the original svg) into the template. Yet, the group would not show up. Do I need to rerender the entire SVG? How can I do this?

public async toggleVisibility(elementToDisplay): void {
  try {
    // if elementToDisplay already is part of the .svg, set it visible / invisible here
  } catch(_) {
    this._http.get<string>(`${environment.backend}/worldmap/data/${elementToDisplay}`, { headers: new HttpHeaders({ 'Content-Type': 'application/json' }) }).toPromise()
    .then((res: string) => {
       // as an unrelated issue: my spring application is returning a String, yet angular would not resolve this promise
     }.catch((rej: any) => {
       const parser = new DOMParser();
       const doc = parser.parseFromString(rej.error.text, "image/svg+xml");
       this.svgMap.nativeElement.children.namedItem('viewbox').appendChild(doc.lastChild);
     }
  }

My SVG before the call looks something similar to this:

<svg _ngcontent-lys-c61 xmlns:dc="http://purl.org/dc/elements/1.1/" 
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg" 
[...]
id="worldmap"
width="100%" height="100%"
background-color="#000000"
viewBox="145 45 1000 1000">
[...]
  <g _ngcontent-lys-c61 id="viewbox" style="cursor:default;">
    <g _ngcontent-lys-c61 id="ocean"> [...] </g>
    <g _ngcontent-lys-c61 id="lakes"> [...] </g>
    <g _ngcontent-lys-c61 id="landmass">[...] </g>      
     [...]
  </g>
</svg>

after the call, there is an additional <g id="biomes"> [...] </g> after the landmass group (if i request the biomes obviously). The only difference I can spot is that the _ngContent-lys-c61 is missing. Yet event a changeDetectorRef.detectChanges() wouldn't lead to the group being displayed. It's not beyond something else either. I erased all groups in my chrome debugger and the biomes still didn't show up.

On a side-note: I found a lot of similar problems, all utilizing D3.js. I do not want to include that.

Upvotes: 0

Views: 1527

Answers (1)

scy
scy

Reputation: 327

Gonna completely erase and edit this answer as I finally found a solution. The problem had to do with sending a String which couldn't be converted into a json - and I was defining a json header to be used as standard response type.

long story short, I'll show the entire process of adding svg groups dynamically to an existing svg. Technologies: Angular & Spring Boot

angular:

// component.ts
@ViewChild('svg') public svgMap: ElementRef<SVGElement>;

// function
const svg = await this._http.get(`pathToServer`, { responseType: 'text' }).toPromise();
const document: Document = new DOMParser().parseFromString(svg, "image/svg+xml");
const group: HTMLCollectionOf<SVGElement> = document.getElementsByTagNameNS('http://www.w3.org/2000/svg', 'g');
const groupToAttach: SVGElement = this.svgMap.nativeElement.children.namedItem('myGroup') as SVGElement;
groupToAttach.appendChild(group.namedItem('paths'))

// component.svg
<svg id="svg">
  <g id="myGroup"></g>
</svg>

spring boot:

// imports:
import java.io.InputStreamReader;
import java.io.BufferedReader;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import java.nio.charset.StandardCharsets;
import java.util.stream.Collectors;

// class-level
@Autowired
private ResourceLoader resourceLoader;

//function
@GetMapping(path = "endpoint/{element}")
public ResponseEntity<String> function(@PathVariable String element) throws Exception {
Resource elementResource = resourceLoader.getResource("classpath:/" + element + ".svg");
InputStreamReader is = new InputStreamReader(elementResource.getInputStream(), StandardCharsets.UTF_8);
String data = new BufferedReader(is).lines().collect(Collectors.joining("\n"));

HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.valueOf("image/svg+xml"));

return ResponseEntity.ok().headers(headers).contentLength(data.length()).body(data);

Lazy loaded svg

<svg xmlns="http://www.w3.org/2000/svg">
  <g id="paths">
    // ...
  </g>
</svg>

Upvotes: 1

Related Questions