openseadragon/svg-overlay

Typescript/Angular implementation

daanvanrobays opened this issue · 10 comments

Hello,

I am developing an angular application that uses openseadragon viewer and I'd like to place some svg overlays on this viewer.
However, I am having trouble getting the svg-overlay package initialized into the openseaviewer.

I've installed both npm packages and am importing them as shown below.

import * as OpenSeadragon from 'openseadragon';
import * as OsdSvgOverlay from 'svg-overlay';

this.openSeadragonViewer = OpenSeadragon({
     id: "openSeadragonContainer",
     ...
});

var overlay = this.openSeadragonViewer.svgOverlay();

After this code executes it throws an error saying that '.svgOverlay is not a function'.
I've also tried declaring them in another way

declare var OsdSvgOverlay: any;

But this throws the same error.

Is there something I am doing wrong here? Or can anyone who succesfully added this in a typescript / angular project share their expertise?

Thanks in advance!

I don't think there is an npm package for the svg-overlay:

https://www.npmjs.com/package/svg-overlay

Did you not get an error when you tried to install it?

At any rate, you may need to do some work to make it function with import. I'm actually not entirely sure how plugins should work in that kind of environment.

I referenced the package straight from github using the following:

"svg-overlay": "github:openseadragon/svg-overlay"

I eventually got it working by adding the file to my vendor.ts and writing this inside the openseadragon-svg-overlay.js file:

function OsdSvgOverlay( viewer ) {
    return new OsdSvgOverlay.Overlay( viewer );
}

and the import

import * as OsdSvgOverlay from '../../../../../wwwroot/lib/osd-svg-overlay/openseadragon-svg-overlay.js';

Good to know! Is there anything we can do to the plugin to make that process easier for the next person?

Have there been any updates on this in relation to React? I have been attempting to replicate this solution with a React/typescript app to be able to utilize the svg-overlay function with no luck. Any suggestions @iangilman or @daanvanrobays?

No updates as far as I'm aware of. What issues are you encountering? Are you getting any errors? Can you tell which part is working?

Thank you for the prompt response @iangilman! I had been trying to replicate the work done by daan and finally was able to get things working in React utilizing his suggestions. Below is how I setup the svg-overlay plugin within my repo.

// OpenSeadragon SVG Overlay plugin 0.0.5

var $ = window.OpenSeadragon;

if (!$) {
    $ = require('openseadragon');
    if (!$) {
        throw new Error('OpenSeadragon is missing.');
    }
}

var svgNS = 'http://www.w3.org/2000/svg';

// ----------
$.Viewer.prototype.svgOverlay = function () {
    if (this._svgOverlayInfo) {
        return this._svgOverlayInfo;
    }

    this._svgOverlayInfo = new Overlay(this);
    return this._svgOverlayInfo;
};

// ----------
var Overlay = function (viewer) {
    var self = this;

    this._viewer = viewer;
    this._containerWidth = 0;
    this._containerHeight = 0;

    this._svg = document.createElementNS(svgNS, 'svg');
    this._svg.style.position = 'absolute';
    this._svg.style.left = 0;
    this._svg.style.top = 0;
    this._svg.style.width = '100%';
    this._svg.style.height = '100%';
    this._viewer.canvas.appendChild(this._svg);

    this._node = document.createElementNS(svgNS, 'g');
    this._svg.appendChild(this._node);

    this._viewer.addHandler('animation', function () {
        self.resize();
    });

    this._viewer.addHandler('open', function () {
        self.resize();
    });

    this._viewer.addHandler('rotate', function (evt) {
        self.resize();
    });

    this._viewer.addHandler('flip', function () {
        self.resize();
    });

    this._viewer.addHandler('resize', function () {
        self.resize();
    });

    this.resize();
};

// ----------
Overlay.prototype = {
    // ----------
    node: function () {
        return this._node;
    },

    // ----------
    resize: function () {
        if (this._containerWidth !== this._viewer.container.clientWidth) {
            this._containerWidth = this._viewer.container.clientWidth;
            this._svg.setAttribute('width', this._containerWidth);
        }

        if (this._containerHeight !== this._viewer.container.clientHeight) {
            this._containerHeight = this._viewer.container.clientHeight;
            this._svg.setAttribute('height', this._containerHeight);
        }

        var p = this._viewer.viewport.pixelFromPoint(new $.Point(0, 0), true);
        var zoom = this._viewer.viewport.getZoom(true);
        var rotation = this._viewer.viewport.getRotation();
        var flipped = this._viewer.viewport.getFlip();
        // TODO: Expose an accessor for _containerInnerSize in the OSD API so we don't have to use the private variable.
        var containerSizeX = this._viewer.viewport._containerInnerSize.x;
        var scaleX = containerSizeX * zoom;
        var scaleY = scaleX;

        if (flipped) {
            // Makes the x component of the scale negative to flip the svg
            scaleX = -scaleX;
            // Translates svg back into the correct coordinates when the x scale is made negative.
            p.x = -p.x + containerSizeX;
        }

        this._node.setAttribute(
            'transform',
            'translate(' + p.x + ',' + p.y + ') scale(' + scaleX + ',' + scaleY + ') rotate(' + rotation + ')'
        );
    },
    // ----------
    onClick: function (node, handler) {
        // TODO: Fast click for mobile browsers

        new $.MouseTracker({
            element: node,
            clickHandler: handler,
        }).setTracking(true);
    },
};

export const OsdSvgOverlay = (viewer) => {
    return new Overlay(viewer);
};

I then import const { OsdSvgOverlay } = require('../../../utils/openseadragon/openseadragon-svg-overlay');

and call the function by passing in viewer const overlay = OsdSvgOverlay(viewer);

from there I can treat it the same as if it const overlay = viewer.svgOverlay()

Thanks again for the prompt response!

Looks great! Do you think it might be possible to update this repository to support this modality but also still support the "old fashioned" loading technique? I don't have a lot of experience with creating code that supports both, but surely people do it!

TBH I am not really sure how to go about doing that but if it would be helpful I would be more than happy to add a PR that adds a file of the overlay plugin configured in the way daan did it that I copied and then update the README with a section on implementation utilizing the new plugin. I don't know if that would be helpful or not but unfortunately that is probably the limit of what I would be able to do to help enhance the functionality.

I wouldn't want a duplicate copy of the code, since it would be harder to maintain, but I suppose one option would be to introduce a build step that spits out two copies of the code, each with the right "framing" elements. I wonder if that's how people generally do it?

No rush on this... Even having this conversation here is helpful to people! If you do run across any information on how to support both modalities, please let me know. And, of course, if you do have energy around implementing it, please do! We just need to make sure we're doing it cleanly :)

Yeah I hear you loud and clean on the "clean" approach. I'll definitely think through it and see if I can come up with anything!