openseadragon / svg-overlay

An OpenSeadragon plugin that adds SVG overlay capability.
BSD 3-Clause "New" or "Revised" License
58 stars 28 forks source link

Typescript/Angular implementation #34

Open daanvanrobays opened 5 years ago

daanvanrobays commented 5 years ago

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!

iangilman commented 5 years ago

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.

daanvanrobays commented 5 years ago

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';
iangilman commented 5 years ago

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

gerstej9 commented 2 years ago

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?

iangilman commented 2 years ago

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?

gerstej9 commented 2 years ago

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!

iangilman commented 2 years ago

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!

gerstej9 commented 2 years ago

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.

iangilman commented 2 years ago

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 :)

gerstej9 commented 2 years ago

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!