You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
468 lines
14 KiB
468 lines
14 KiB
'use strict';
|
|
/*
|
|
* Copyright (C) 1998-2023 by Northwoods Software Corporation. All Rights Reserved.
|
|
*/
|
|
|
|
/**
|
|
* This class implements a zoom slider for GoJS diagrams.
|
|
* The constructor takes two arguments:
|
|
* - `diagram` ***Diagram*** a reference to a GoJS Diagram
|
|
* - `options` ***Object*** an optional JS Object describing options for the slider
|
|
*
|
|
* Options:
|
|
* - `alignment` ***Spot*** see {@link #alignment}
|
|
* - `alignmentFocus` ***Spot*** see {@link #alignmentFocus}
|
|
* - `size` ***number*** see {@link #size}
|
|
* - `buttonSize` ***number*** see {@link #buttonSize}
|
|
* - `orientation` ***string*** see {@link #orientation}
|
|
* - `opacity` ***number*** see {@link #opacity}
|
|
*
|
|
* Example usage of ZoomSlider:
|
|
* ```js
|
|
* var zoomSlider = new ZoomSlider(myDiagram,
|
|
* {
|
|
* alignment: go.Spot.TopRight, alignmentFocus: go.Spot.TopRight,
|
|
* size: 150, buttonSize: 30, orientation: 'horizontal'
|
|
* });
|
|
* ```
|
|
*
|
|
* This is the basic HTML Structure that the ZoomSlider creates as a sibling div of the diagram:
|
|
* ```html
|
|
* <div class="zoomSlider">
|
|
* <button id="zoomSliderOut" class="zoomButton">-</button>
|
|
* <div id="zoomSliderRangeCtn" class="zoomRangeContainer">
|
|
* <input id="zoomSliderRange" class="zoomRangeInput" type="range" min="-50" max="100">
|
|
* </div>
|
|
* <button id="zoomSliderIn" class="zoomButton">+</button>
|
|
* </div>
|
|
* ```
|
|
*
|
|
* <p class="box">
|
|
* The diagram div's parent element should use `position: relative` to ensure the slider gets positioned properly.
|
|
*
|
|
* @constructor
|
|
* @class
|
|
* @param {Diagram} diagram the Diagram which the slider zooms in/out
|
|
* @param {Object} options the options for the slider
|
|
*/
|
|
function ZoomSlider(diagram, options) {
|
|
this._diagram = diagram;
|
|
this._initialScale = diagram.scale;
|
|
this._diagramDiv = diagram.div;
|
|
this._sliderDiv = null;
|
|
|
|
// Options defaults:
|
|
this._size = 125;
|
|
this._buttonSize = 25;
|
|
this._alignment = go.Spot.BottomRight;
|
|
this._alignmentFocus = go.Spot.BottomRight;
|
|
this._orientation = 'vertical';
|
|
this._opacity = .75;
|
|
|
|
// Set properties based on options
|
|
if (options !== undefined) {
|
|
if (options['size'] !== undefined) this._size = options['size'];
|
|
if (options['buttonSize'] !== undefined) this._buttonSize = options['buttonSize'];
|
|
if (options['alignment'] !== undefined) this._alignment = options['alignment'];
|
|
if (options['alignmentFocus'] !== undefined) this._alignmentFocus = options['alignmentFocus'];
|
|
if (options['orientation'] !== undefined) this._orientation = options['orientation'];
|
|
if (options['opacity'] !== undefined) this._opacity = options['opacity'];
|
|
}
|
|
|
|
// Prepare change listeners
|
|
var self = this;
|
|
this.updateOnViewportBoundsChanged = function (e) { self.scaleToValue(); };
|
|
|
|
this.init();
|
|
}
|
|
|
|
// Public properties
|
|
|
|
/**
|
|
* This read-only property returns the diagram for which the slider is handling zoom.
|
|
* @name ZoomSlider#diagram
|
|
|
|
* @return {Diagram}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'diagram', {
|
|
get: function() { return this._diagram; }
|
|
});
|
|
|
|
/**
|
|
* Gets or sets the overall length, in pixels, that the slider will occupy.
|
|
* The default value is 125.
|
|
* @name ZoomSlider#size
|
|
|
|
* @return {number}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'size', {
|
|
get: function() { return this._size; },
|
|
set: function(val) {
|
|
var old = this._size;
|
|
if (old !== val) {
|
|
this._size = val;
|
|
this.resize();
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Gets or sets the height/width of the buttons at each end of the slider.
|
|
* The default value is 25.
|
|
* @name ZoomSlider#buttonSize
|
|
|
|
* @return {number}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'buttonSize', {
|
|
get: function() { return this._buttonSize; },
|
|
set: function(val) {
|
|
var old = this._buttonSize;
|
|
if (old !== val) {
|
|
this._buttonSize = val;
|
|
this.resize();
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Gets or sets the alignment Spot of this slider to determine where it should be placed relative to the diagram.
|
|
* The default value is Spot.BottomRight.
|
|
* @name ZoomSlider#alignment
|
|
|
|
* @return {Spot}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'alignment', {
|
|
get: function() { return this._alignment; },
|
|
set: function(val) {
|
|
var old = this._alignment;
|
|
if (old !== val) {
|
|
this._alignment = val;
|
|
this.realign();
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Gets or sets the Spot on this slider to be used as the alignment point when placing it relative to the diagram.
|
|
* The default value is Spot.BottomRight.
|
|
* @name ZoomSlider#alignmentFocus
|
|
|
|
* @return {Spot}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'alignmentFocus', {
|
|
get: function() { return this._alignmentFocus; },
|
|
set: function(val) {
|
|
var old = this._alignmentFocus;
|
|
if (old !== val) {
|
|
this._alignmentFocus = val;
|
|
this.realign();
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Gets or sets whether the slider is oriented vertically or horizontally.
|
|
* Must be either 'horizontal' or 'vertical' and is case-sensitive.
|
|
* The default value is `'vertical'`.
|
|
* @name ZoomSlider#orientation
|
|
|
|
* @return {string}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'orientation', {
|
|
get: function() { return this._orientation; },
|
|
set: function(val) {
|
|
if (val !== 'horizontal' && val !== 'vertical') {
|
|
throw new Error('Orientation must be "horizontal" or "vertical"');
|
|
}
|
|
var old = this._orientation;
|
|
if (old !== val) {
|
|
this._orientation = val;
|
|
this.resize(true);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Gets or sets the opacity of the slider.
|
|
* The default value is 0.75.
|
|
* @name ZoomSlider#opacity
|
|
|
|
* @return {Spot}
|
|
*/
|
|
Object.defineProperty(ZoomSlider.prototype, 'opacity', {
|
|
get: function() { return this._opacity; },
|
|
set: function(val) {
|
|
var old = this._opacity;
|
|
if (old !== val) {
|
|
this._opacity = val;
|
|
this._sliderDiv.style.opacity = val;
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* @ignore
|
|
* Initialize the slider.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.init = function() {
|
|
// Sets up the slider div and inner div's basic attributes and ids
|
|
this.sliderDivSetup();
|
|
this.resize(true);
|
|
|
|
// Set up the runtime code
|
|
this.sliderListenerSetup();
|
|
};
|
|
|
|
/**
|
|
* @ignore
|
|
* Create the necessary divs for the slider and add the slider as a sibling of the diagram.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.sliderDivSetup = function() {
|
|
this._sliderDiv = document.createElement('div');
|
|
this._sliderDiv.className = 'zoomSlider';
|
|
|
|
// Initialize buttons and range input
|
|
var zoomOutBtn = document.createElement('button');
|
|
this.zoomSliderOut = zoomOutBtn;
|
|
zoomOutBtn.className = 'zoomButton';
|
|
zoomOutBtn.innerHTML = '-';
|
|
zoomOutBtn.type = 'button';
|
|
this._sliderDiv.appendChild(zoomOutBtn);
|
|
|
|
var zoomRangeContainer = document.createElement('div');
|
|
this.zoomSliderRangeCtn = zoomRangeContainer;
|
|
zoomRangeContainer.className = 'zoomRangeContainer';
|
|
this._sliderDiv.appendChild(zoomRangeContainer);
|
|
|
|
var zoomRangeInput = document.createElement('input');
|
|
this.zoomSliderRange = zoomRangeInput;
|
|
zoomRangeInput.className = 'zoomRangeInput';
|
|
zoomRangeInput.type = 'range';
|
|
zoomRangeInput.min = -50;
|
|
zoomRangeInput.max = 100;
|
|
zoomRangeContainer.appendChild(zoomRangeInput);
|
|
|
|
var zoomInBtn = document.createElement('button');
|
|
this.zoomSliderIn = zoomInBtn;
|
|
zoomInBtn.className = 'zoomButton';
|
|
zoomInBtn.innerHTML = '+';
|
|
zoomInBtn.type = 'button';
|
|
this._sliderDiv.appendChild(zoomInBtn);
|
|
|
|
// Adds the slider as a sibling of the diagram
|
|
// IMPORTANT: the diagram div's parent element should use position: relative
|
|
if (this._diagramDiv !== null) {
|
|
var diagramParent = this._diagramDiv.parentElement;
|
|
if (diagramParent !== null) {
|
|
diagramParent.appendChild(this._sliderDiv);
|
|
}
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @ignore
|
|
* Add listeners to the buttons and range input.
|
|
* Add a diagram listener
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.sliderListenerSetup = function() {
|
|
var zoomOutBtn = this.zoomSliderOut;
|
|
var zoomInBtn = this.zoomSliderIn;
|
|
var zoomRangeInput = this.zoomSliderRange;
|
|
|
|
if (zoomOutBtn === null || zoomInBtn === null || zoomRangeInput === null) return;
|
|
|
|
// Set up diagram listener so the slider can be kept in sync with the diagram's scale
|
|
this.diagram.addDiagramListener('ViewportBoundsChanged', this.updateOnViewportBoundsChanged);
|
|
|
|
// Set up event handlers for buttons and input range slider
|
|
var self = this;
|
|
zoomOutBtn.onclick = function() {
|
|
zoomRangeInput.stepDown();
|
|
self.valueToScale();
|
|
};
|
|
|
|
zoomInBtn.onclick = function() {
|
|
zoomRangeInput.stepUp();
|
|
self.valueToScale();
|
|
};
|
|
|
|
var valChanged = function() {
|
|
self.valueToScale();
|
|
};
|
|
zoomRangeInput.oninput = valChanged;
|
|
zoomRangeInput.onchange = valChanged;
|
|
};
|
|
|
|
/**
|
|
* @ignore
|
|
* Resize the slider.
|
|
* @this {ZoomSlider}
|
|
* @param {boolean} reorient whether or not to reorient the slider/buttons
|
|
*/
|
|
ZoomSlider.prototype.resize = function(reorient) {
|
|
var sliderWidth = 0;
|
|
var sliderHeight = 0;
|
|
|
|
var zoomOutBtn = this.zoomSliderOut;
|
|
var zoomInBtn = this.zoomSliderIn;
|
|
var zoomRangeContainer = this.zoomSliderRangeCtn;
|
|
var zoomRangeInput = this.zoomSliderRange;
|
|
|
|
if (this._sliderDiv === null || zoomOutBtn === null || zoomInBtn === null ||
|
|
zoomRangeContainer === null || zoomRangeInput === null) return;
|
|
|
|
if (this.orientation === 'horizontal') {
|
|
sliderWidth = this.size;
|
|
sliderHeight = this.buttonSize;
|
|
var rangeWidth = sliderWidth - sliderHeight * 2;
|
|
|
|
zoomOutBtn.style.width = sliderHeight + 'px';
|
|
zoomOutBtn.style.height = sliderHeight + 'px';
|
|
|
|
zoomRangeContainer.style.width = rangeWidth + 'px';
|
|
zoomRangeContainer.style.height = sliderHeight + 'px';
|
|
|
|
zoomRangeInput.style.width = rangeWidth + 'px';
|
|
zoomRangeInput.style.height = sliderHeight + 'px';
|
|
zoomRangeInput.style.transformOrigin = null;
|
|
|
|
zoomInBtn.style.width = sliderHeight + 'px';
|
|
zoomInBtn.style.height = sliderHeight + 'px';
|
|
} else {
|
|
sliderHeight = this.size;
|
|
sliderWidth = this.buttonSize;
|
|
var rangeHeight = sliderHeight - sliderWidth * 2;
|
|
|
|
zoomInBtn.style.width = sliderWidth + 'px';
|
|
zoomInBtn.style.height = sliderWidth + 'px';
|
|
|
|
zoomRangeContainer.style.width = sliderWidth + 'px';
|
|
zoomRangeContainer.style.height = rangeHeight + 'px';
|
|
|
|
zoomRangeInput.style.width = rangeHeight + 'px';
|
|
zoomRangeInput.style.height = sliderWidth + 'px';
|
|
zoomRangeInput.style.transformOrigin = rangeHeight / 2 + 'px ' + rangeHeight / 2 + 'px';
|
|
|
|
zoomOutBtn.style.width = sliderWidth + 'px';
|
|
zoomOutBtn.style.height = sliderWidth + 'px';
|
|
}
|
|
this._sliderDiv.style.width = sliderWidth + 'px';
|
|
this._sliderDiv.style.height = sliderHeight + 'px';
|
|
|
|
// Reorient the slider, if necessary
|
|
if (reorient) {
|
|
this.reorient();
|
|
}
|
|
|
|
// Realign based on new size
|
|
this.realign();
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* Reorient the slider, changing the transform and the order of the buttons within the div.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.reorient = function() {
|
|
var zoomOutBtn = this.zoomSliderOut;
|
|
var zoomInBtn = this.zoomSliderIn;
|
|
var zoomRangeInput = this.zoomSliderRange;
|
|
|
|
if (this._sliderDiv === null || zoomOutBtn === null || zoomInBtn === null || zoomRangeInput === null) return;
|
|
|
|
// Need to set the transform of the range input and move the buttons to the correct sides
|
|
if (this.orientation === 'horizontal') {
|
|
zoomRangeInput.style.transform = null;
|
|
this._sliderDiv.insertBefore(zoomOutBtn, this._sliderDiv.firstChild);
|
|
this._sliderDiv.appendChild(zoomInBtn);
|
|
} else {
|
|
zoomRangeInput.style.transform = 'rotate(-90deg)';
|
|
this._sliderDiv.insertBefore(zoomInBtn, this._sliderDiv.firstChild);
|
|
this._sliderDiv.appendChild(zoomOutBtn);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* Realigns to slider relative to the diagram.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.realign = function() {
|
|
if (this._diagramDiv === null || this._sliderDiv === null) return;
|
|
|
|
var sliderWidth = 0;
|
|
var sliderHeight = 0;
|
|
if (this.orientation === 'horizontal') {
|
|
sliderWidth = this.size;
|
|
sliderHeight = this.buttonSize;
|
|
} else {
|
|
sliderHeight = this.size;
|
|
sliderWidth = this.buttonSize;
|
|
}
|
|
|
|
// Finds the diagram and diagram's parent in the page
|
|
var diagramParent = this._diagramDiv.parentElement;
|
|
var diagramLoc = this._diagramDiv.getBoundingClientRect();
|
|
if (diagramParent !== null) {
|
|
var parentLoc = diagramParent.getBoundingClientRect();
|
|
|
|
var top = diagramLoc.top - parentLoc.top +
|
|
this.alignment.y * this._diagramDiv.clientHeight + this.alignment.offsetY -
|
|
this.alignmentFocus.y * sliderHeight + this.alignmentFocus.offsetY;
|
|
|
|
var left = diagramLoc.left - parentLoc.left +
|
|
this.alignment.x * this._diagramDiv.clientWidth + this.alignment.offsetX -
|
|
this.alignmentFocus.x * sliderWidth + this.alignmentFocus.offsetX;
|
|
|
|
this._sliderDiv.style.top = top + 'px';
|
|
this._sliderDiv.style.left = left + 'px';
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* Update the value of the slider input to match the diagram's scale.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.scaleToValue = function() {
|
|
var slider = this.zoomSliderRange;
|
|
var diagram = this.diagram;
|
|
var A = this._initialScale;
|
|
var B = diagram.commandHandler.zoomFactor;
|
|
var y1 = diagram.scale;
|
|
slider.value = Math.round(Math.log(y1/A) / Math.log(B)).toString();
|
|
}
|
|
|
|
/**
|
|
* @ignore
|
|
* Update the diagram's scale to match the value of the slider input.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.valueToScale = function() {
|
|
var slider = this.zoomSliderRange;
|
|
var diagram = this.diagram;
|
|
var x = parseFloat(slider.value);
|
|
var A = this._initialScale;
|
|
var B = diagram.commandHandler.zoomFactor;
|
|
diagram.scale = A * Math.pow(B, x);
|
|
}
|
|
|
|
/**
|
|
* Remove the slider from the page.
|
|
* @this {ZoomSlider}
|
|
*/
|
|
ZoomSlider.prototype.remove = function() {
|
|
// Remove the listener attached to diagram
|
|
this.diagram.removeDiagramListener('ViewportBoundsChanged', this.updateOnViewportBoundsChanged);
|
|
|
|
if (this._sliderDiv !== null) {
|
|
this._sliderDiv.innerHTML = '';
|
|
if (this._sliderDiv.parentElement) {
|
|
this._sliderDiv.parentElement.removeChild(this._sliderDiv);
|
|
}
|
|
this._sliderDiv = null;
|
|
}
|
|
}; |