James Saunders
James Saunders

Reputation: 105

I need to be able to set CSS class for SVG element

I am creating an app that includes an Angular2 directive that presents an SVG image containing path elements that when clicked emit events up to the app component. This is working well. However, I also want to be able to highlight the elements by changing their CSS class from a selector outside the directive.

As a simple example, I've 'wrapped' a simplified version of the AU map at http://www.petercollingridge.co.uk/sites/files/peter/Australia_compass_with_names.svg into a directive and added the code to add click events to the SVG elements as shown below:

import {Component, Renderer, ElementRef, Input, Output, EventEmitter, OnInit, OnChanges, OnDestroy} from '@angular/core';

@Component({
    selector: '[map]',
    template: `
    <svg>
        <g id="map-transform" transform="matrix(1 0 0 1 0 0)">
            <path
            id="WA"
            class="territory"
            fill="#a9a9a9"
            d="m 38.3, 168.2 c -1.9,-0.6 -3.6,-1.1 -3.8,-1.3 -0.2,-0.2 0.8,-2.5 2.25,-5.2 1.4,-2.7 2.6,-5.5 2.6,-6.2 0,-0.8 -1.8,-4.25 -4,-7.7 -2.2,-3.5 -4,-7.1 -4,-8.1 l 0,-1.8 -6.4,-10.3 -6.4,-10.3 1.8,0 -7,-14.8 0.2,-7.3 c 0.1,-4 0.1,-8.9 0.1,-10.9 l -0.1,-3.5 5.5,-6 c 3,-3.3 5.5,-6.3 5.5,-6.7 0,-0.4 0.9,-0.8 1.9,-0.8 l 1.9,0 9.8,-6.1 13.8,-2.2 4.1,-2.6 8.4,-15 -1.2,-3 3.3,-5 1.8,1.1 4,-2.5 0,-3.1 4.6,-4.3 8.5,0 3.4,3.8 c 1.9,2.1 3.4,4.3 3.4,5 l 0,1.3 2.5,-0.6 L 97.5,33.2 c 0,38.3 0,77 0,113.8 l -4.6,1.6 -4.5,2.2 -2.3,5.6 -1.6,0.5 c -0.9,0.3 -4.8,1 -8.6,1.6 l -7,1.1 -8.2,4.9 -8.2,4.9 -5.3,0 c -2.9,0 -6.9,-0.5 -8.8,-1.1 z"/>

        <path
            id="QLD"
            class="territory"
            fill="#a9a9a9"
            d="m 200.3,123.9 c -11.8,-0.7 -14.8,-0.8 -22.8,-1.3 0,-8.5 0,-21 0,-21 l -16,0 c 0,0 0,-39.3 0,-60 l 6,3.5 5.2,3.5 4.4,0 6,-9.8 3.4,-24.6 2.8,-5.7 c 1.6,-3.1 3.3,-6 3.9,-6.3 l 1,-0.6 1.8,4.3 c 1,2.4 2.2,6.6 2.6,9.3 0.4,2.8 1.2,7.1 1.7,9.8 l 1,4.8 4.8,0 3.2,5 2.8,11 c 1.5,6.1 3.3,11.7 4,12.5 0.6,0.8 3.4,2.9 6.2,4.6 l 5,3.2 5,10 3.8,1 1.2,5.2 11.2,14.8 1.2,7.1 c 0.7,3.9 1.5,8.7 1.9,10.8 l 0.6,3.7 -1.3,5.1 -4.6,-1.1 -1.3,1.6 c -1.6,2 -3.7,2 -4.4,0.1 l -0.6,-1.5 -2.4,0 c -1.3,0 -4.3,0.7 -6.7,1.5 -2.3,0.8 -5.4,1.4 -6.9,1.3 -1.4,-0.1 -12.3,-0.7 -24.1,-1.4 z"/>    

        <path
            id="VIC"
            class="territory"
            fill="#a9a9a9"
            d="m 219.5,177.3 0.8,1 c 0,0.6 2.3,2.7 5,4.8 l 5,3.8 -11.4,3.4 -5.7,5.4 -6.9,-3.7 -2.8,1.4 c -1.5,0.8 -3,1.5 -3.3,1.6 -0.3,0.1 -2.3,-0.9 -4.5,-2.2 c -2.2,-1.3 -6.3,-3.1 -9,-4 l -5,-1.6 -2.3,-2 -2.1,-2.1 c 0,-9 0,-17.9 0,-26.6 l 3.9,-1.3 3.1,0 1.6,3.4 1.9,0.5 2.4,0.5 1.5,0.9 4.7,6 3.2,3.9 c 3.2,0.9 6.5,1.7 9.2,2.4 3,0.8 4.7,0.9 6.2,1 l 2.9,2.6"/>

        <path
            id="NSW"
            class="territory"
            fill="#a9a9a9"
            d="m 220.5,176.4 -2.4,-2.7 -2.4,-1.5 c -2.1,-0.3 -6.2,-1.1 -9.1,-1.8 l -5.4,-1.4 -8.2,-10.4 -2.8,-0.7 -2.8,-0.7 -1,-1.9 -1,-1.9 -3.9,0 -4,1.5 0,-30.9 c 5.8,0.4 22.5,1.4 25.3,1.6 l 23.5,1.4 4.8,-1.2 c 4.6,-1.1 5.7,-2.1 8.3,-2 0,0 0.9,2.3 1.9,2.8 1.1,0.5 2.5,0.1 3.7,-0.3 1.1,-0.4 2,-2 3.1,-1.8 l 2.8,0.5 -1.3,5.1 c -0.7,2.7 -2.1,7.5 -2.9,10.9 -1.8,8.3 -3.1,11.6 -5.3,13.4 l -1.8,1.5 -4.1,12.5 c -2.2,6.9 -4.2,13.5 -4.4,14.7 l -0.3,2.2 -5.3,-4 -4.4,-4.2" />

        <path
            id="SA"
            class="territory"
            fill="#a9a9a9"
            d="m 176,181 -6.8,-9.4 0.7,-3.7 0.7,-3.7 -5.6,-6 -2.2,0.8 0.7,-7.8 -1.1,0 c -1.1,0 -2.2,1.1 -4.6,4.5 l -1.4,2 1,-4.8 c 0.6,-2.7 0.8,-5 0.6,-5.3 -0.8,-0.8 -6.1,2.5 -7.9,4.8 -0.9,1.2 -1.9,2 -2.1,1.8 -0.2,-0.3 -1.6,-2.6 -3.1,-5.2 l -2.7,-4.7 -11.4,-2.6 -19.2,0.2 -5.8,2.6 C 102.7,146.2 99,147.2 99,147 l 0,-44 77,0 z"/>    

        <g
            id="NT"
            class="territory"
            fill="#a9a9a9">
            <path
                d="m 99,33 3.9,0.4 c 2,0.2 4.4,-0.5 5.5,-1 l 1.9,-1 0,-2.5 0,-2.5 -2,0 0,-4 1.9,-1.7 c 1,-0.9 1.7,-2 1.5,-2.4 l -0.5,-0.7 6.8,-4.2 11.5,-1.4 0.8,-2.4 -5.6,-3 1.7,0 c 0.9,0 2,0.4 2.3,1 0.3,0.6 1.2,0.9 1.8,0.8 0.6,-0.1 3.4,0.8 6.2,2.1 l 5,2.4 7.5,0 0,3.7 -4.2,4 1.2,3.2 -3,5.8 17,11.2 0,60.8 -61,0 z" />
            <path
                d="m 115,8.7 -2.8,-0.4 0.8,-2.4 4.2,0 c 2.3,0 4.2,0.2 4.2,0.5 0,0.8 -2.3,3 -2.9,2.8 -0.3,-0 -1.8,-0.3 -3.4,-0.6 z"/>
        </g>

        <g
            id="TAS"
            class="territory"
            fill="#a9a9a9">
            <path
                d="m 202.7,223.9 -2.7,-3.6 -0.1,-3.4 c -0.1,-1.9 -0.4,-3.7 -0.8,-4.1 -0.4,-0.4 -0.7,-1.8 -0.7,-3.1 l 0,-2.3 1,0 c 0.6,0 2.4,0.7 4.2,1.6 l 3.1,1.6 9.6,-1.2 0,7.2 -3.4,5.8 -2.6,-1 -1.7,3 c -0.9,1.6 -2,3 -2.4,3 -0.4,0 -1.9,-1.6 -3.4,-3.6 z" />
        </g>

        <g 
            id="ACT"
            class="territory"
            fill="#a9a9a9">
            <path
            stroke-width="1.5"
            stroke="#ffffff"
            d="m 222,165.7 0.1,2.8 0.4,1.4 1,1 0.4,-0.9 0.2,2.9 2.6,1.6 1,-2.2 0,-3.6 c 0,0 -0.3,-0.5 -0.6,-1.1 0.8,-0.3 1.2,0.1 1.2,0.1 l 0.1,-3.4 1.5,-2 2.4,0.3 0.5,-1 -2.7,-1.3 c 0,0 -0.7,-1.8 -1.8,-2.4 -1.6,1.1 -4,2 -5.2,3.9 -0.4,0.7 -0.5,2.3 -0.5,2.3 z"/>
        </g>
    </g>
</svg>
`
})
export class Map implements OnInit, OnDestroy {
    @Input() public territory:string;
    @Output() territorySelected = new EventEmitter<any>();
    private element:ElementRef;
    private ctx: any;

    listenFunc: Function;   
    globalListenFunc: Function;

    constructor(elementRef: ElementRef, renderer: Renderer) {

        this.listenFunc = renderer.listen(elementRef.nativeElement, 'click', (event) => {
        this.territorySelected.emit(event);
        });
    }
    ngOnInit() {
        console.log(this.ctx);
    }
    removeListeners() {
        this.listenFunc();
        this.globalListenFunc();
    }

    highlightTerritory(id: string)
    {
        console.log('highlighting territory ' + id);
        // need to understand how to access the elements in the SVG and update the CSS class to highlight the territory
    }
    public ngOnChanges(changes: {[propName: string]: any}) {        
        if (changes['territory']) {         
            this.highlightComponent(changes['territory'].currentValue);
        }
    }

    ngOnDestroy() {
        // Remove the listeners!
        this.listenFunc();
        this.globalListenFunc();
    }
}

I want to be able to allow the user to choose a territory from another sub-component of the app, such as a dropdown containing all the territories, and have that highlight the territory in the map in the same way that clicking it on the map would do. Coding the Input and capturing the change is simple enough. It's finding the associated SVG element and updating the fill colour that I'm struggling with.

Any assistance on the most appropriate way of achieving this in NG2 (using best practice!) would be gratefully received.

Upvotes: 2

Views: 407

Answers (2)

G&#252;nter Z&#246;chbauer
G&#252;nter Z&#246;chbauer

Reputation: 657248

You can use

(click)="highlightTerritory('XXX')"

and

[class.className]="selected == 'XXX'"

to get the selected and update the class of the selected territory.

Plunker example

I didn't understand from your code what you need

@Output() territorySelected = new EventEmitter<any>();

You can use it to bind changes to functions on your parent

<map (territorySelected)="dosSomething($event)"></map> 

but I couldn't see from your question if this is was the intention. If not then you don't need it (and also not ´this.territorySelected.emit(id);`)

Upvotes: 2

James Saunders
James Saunders

Reputation: 105

I have found a simple solution to the problem and that is to use d3.js and then use the select function.

I included a reference to the d3.js in the app.component.js file and then added the following declaration to the map.component.ts file:

declare var d3:any;

Finally, I changed the highlightTerritory function to:

highlightTerritory(id: string) {
    console.log('highlighting ' + id);
    d3.selectAll('.territory').style('fill', '#a9a9a9');
    d3.select("#" + id).style("fill", "purple");
}

and that worked as I'd hoped.

Upvotes: 1

Related Questions