new UI StackBox

This commit is contained in:
Matthieu Baumann
2024-04-23 00:17:07 +02:00
committed by Matthieu Baumann
parent 116ba0d2e3
commit 63aebf738a
29 changed files with 1481 additions and 240 deletions

View File

@@ -2,6 +2,7 @@
## 3.3.3
* [feat] New `hipsList` option parameter when instancing a new Aladin object.
* [feat] Zoom smoothing using hermite cubic interpolation functions
* [feat] shape option of Catalog and ProgressiveCat accepts a function returning a Footprint. This allow user to
associate a footprint to a specific source

5
assets/icons/add.svg Normal file
View File

@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11 8C11 7.44772 11.4477 7 12 7C12.5523 7 13 7.44771 13 8V11H16C16.5523 11 17 11.4477 17 12C17 12.5523 16.5523 13 16 13H13V16C13 16.5523 12.5523 17 12 17C11.4477 17 11 16.5523 11 16V13H8C7.44772 13 7 12.5523 7 12C7 11.4477 7.44771 11 8 11H11V8Z" fill="#0F0F0F"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23 4C23 2.34315 21.6569 1 20 1H4C2.34315 1 1 2.34315 1 4V20C1 21.6569 2.34315 23 4 23H20C21.6569 23 23 21.6569 23 20V4ZM21 4C21 3.44772 20.5523 3 20 3H4C3.44772 3 3 3.44772 3 4V20C3 20.5523 3.44772 21 4 21H20C20.5523 21 21 20.5523 21 20V4Z" fill="#0F0F0F"/>
</svg>

After

Width:  |  Height:  |  Size: 811 B

View File

@@ -10,26 +10,8 @@
import A from '../src/js/A.js';
let aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {target: '12 25 41.512 +12 48 47.2', fov: 1, showContextMenu: true});
aladin = A.aladin('#aladin-lite-div', {target: '12 25 41.512 +12 48 47.2', inertia: false, fov: 1, showContextMenu: true});
// define custom draw function
var drawFunction = function(source, canvasCtx, viewParams) {
canvasCtx.beginPath();
canvasCtx.arc(source.x, source.y, source.data['coo_err_min'] * 5, 0, 2 * Math.PI, false);
canvasCtx.closePath();
canvasCtx.strokeStyle = '#c38';
canvasCtx.lineWidth = 3;
canvasCtx.globalAlpha = 0.7,
canvasCtx.stroke();
var fov = Math.max(viewParams['fov'][0], viewParams['fov'][1]);
// object name is displayed only if fov<10°
if (fov>10) {
return;
}
canvasCtx.globalAlpha = 0.9;
canvasCtx.globalAlpha = 1;
};
var drawFunctionFootprint = function(s) {
let a = +s.data.size_maj;
@@ -39,8 +21,8 @@
if (!galaxy)
return;
let angle = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, angle, {color: 'cyan'});
let theta = +s.data.size_angle || 0.0;
return A.ellipse(s.ra, s.dec, a / 60, b / 60, theta, {color: 'cyan'});
};
var hips = A.catalogHiPS('https://axel.u-strasbg.fr/HiPSCatService/Simbad', {onClick: 'showTable', name: 'Simbad', color: 'cyan', hoverColor: 'red', shape: drawFunctionFootprint});

View File

@@ -231,7 +231,7 @@
A.init.then(() => {
var hipsDir="http://alasky.u-strasbg.fr/CDS_P_Coronelli";
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
aladin = A.aladin("#aladin-lite-div", {showSimbadPointerControl: true, expandLayersControl: true, realFullscreen: true, fov: 100, allowFullZoomout: true, showReticle: false });
aladin.createImageSurvey('illenoroC', 'illenoroC', hipsDir, 'equatorial', 4, {imgFormat: 'jpg', longitudeReversed: false});
aladin.createImageSurvey('Coronelli', 'Coronelli', hipsDir, 'equatorial', 4, {imgFormat: 'jpg', longitudeReversed: true});
aladin.setImageSurvey('Coronelli');

View File

@@ -34,7 +34,7 @@
console.log("Object hovered stopped: ", object, "mouse coords xy: ", xyMouseCoords.x, xyMouseCoords.y);
})
const cat = A.catalogFromVizieR('B/assocdata/obscore', 'M 1', 100, {onClick: 'showTable', hoverColor: 'purple', limit: 1000});
const cat = A.catalogFromVizieR('B/assocdata/obscore', 'M 1', 10, {onClick: 'showTable', hoverColor: 'purple', limit: 10000});
aladin.addCatalog(cat);
});
</script>

View File

@@ -0,0 +1,32 @@
<!doctype html>
<html>
<head>
<!--<link rel="stylesheet" href="./layers.css" />-->
</head>
<body>
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
<script type="module">
import A from '../src/js/A.js';
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, survey: 'https://alasky.cds.unistra.fr/DSS/DSSColor/', fov: 180, showContextMenu: true});
// manage URL parameters
let survey1 = aladin.getBaseImageLayer();
survey1.setColormap('magma', {stretch: 'linear'});
let survey2 = aladin.newImageSurvey("CSIRO/P/RACS/low/I");
aladin.setImageLayer(survey2)
survey2.setColormap('rdbu', {stretch: 'linear'});
let survey3 = aladin.newImageSurvey("CSIRO/P/RACS/mid/I");
aladin.setImageLayer(survey3)
survey3.setColormap('cubehelix', {stretch: 'asinh'});
aladin.setImageLayer(survey2);
});
</script>
</body>
</html>

View File

@@ -429,7 +429,7 @@ canvas {
}
.aladin-input-text.aladin-dark-theme.search {
width: 14rem;
width: 15rem;
text-shadow: 0px 0px 2px #000;
}
@@ -824,6 +824,7 @@ canvas {
border-radius: 5px;
font-family: monospace;
font-size: 1rem;
box-sizing: border-box;
}
.aladin-input-text.aladin-dark-theme, .aladin-input-number.aladin-dark-theme {
@@ -853,18 +854,20 @@ canvas {
/* Remove focus outline */
/* Remove IE arrow */
}
.aladin-input-select option {
color: inherit;
background-color: #320a28;
}
.aladin-input-select:focus {
outline: none;
}
.aladin-input-select::-ms-expand {
display: none;
}
/* Frames */
.aladin-input-color {
appearance: none;
@@ -1104,6 +1107,19 @@ canvas {
height: 1.7rem;
}
.aladin-input-text.aladin-dark-theme.search.aladin-HiPS-search {
width: 100%;
}
.aladin-stack-box {
width: 17rem;
}
.aladin-stack-box .content > * {
margin-bottom: 0.5rem;
}
.aladin-location {
position: absolute;
top: 0.2rem;
@@ -1122,6 +1138,8 @@ canvas {
bottom: 0.2rem;
left: 0.2rem;
background-color: red;
font-family: monospace;
font-size: 1rem;
@@ -1129,6 +1147,16 @@ canvas {
line-height: 1.7rem;
}
.aladin-fov .aladin-zoom-in {
margin-right: 0;
border-right: none;
border-radius: 5px 0px 0px 5px;
}
.aladin-fov .aladin-zoom-out {
border-radius: 0px 5px 5px 0px;
}
.aladin-status-bar {
border-radius: 3px;
padding: 0.4rem;

View File

@@ -40,6 +40,7 @@ import { MeasurementTable } from "./MeasurementTable.js";
import { ImageSurvey } from "./ImageSurvey.js";
import { Coo } from "./libs/astro/coo.js";
import { CooConversion } from "./CooConversion.js";
import { MocServer } from './MocServer';
import { ProjectionEnum } from "./ProjectionEnum.js";
@@ -77,6 +78,10 @@ import { CooFrame } from './gui/Input/CooFrame';
* @property {string} [survey="CDS/P/DSS2/color"] URL or ID of the survey to use
* @property {string[]} [surveyUrl]
* Array of URLs for the survey images. This replaces the survey parameter.
* @property {Object[]|string[]} [hipsList] A list of predefined HiPS for the Aladin instance.
* This option is used for searching for a HiPS in a list of surveys
* This list can have string item (either a CDS ID or an HiPS url) or an object that describes the HiPS
* more exhaustively. See the example below to see the different form that this item can have to describe a HiPS.
* @property {string} [target="0 +0"] - Target coordinates for the initial view.
* @property {CooFrame} [cooFrame="J2000"] - Coordinate frame.
* @property {number} [fov=60] - Field of view in degrees.
@@ -86,6 +91,8 @@ import { CooFrame } from './gui/Input/CooFrame';
* This element belongs to the FoV UI thus its CSS class is `aladin-fov`
* @property {boolean} [showLayersControl=true] - Whether to show the layers control toolbar.
* CSS class for that button is `aladin-stack-control`
* @property {boolean} [expandLayersControl=false] - Whether to show the stack box at starting
* CSS class for the stack box is `aladin-stack-box`
* @property {boolean} [showFullscreenControl=true] - Whether to show the fullscreen control toolbar.
* CSS class for that button is `aladin-fullScreen-control`
* @property {boolean} [showSimbadPointerControl=false] - Whether to show the Simbad pointer control toolbar.
@@ -133,7 +140,53 @@ import { CooFrame } from './gui/Input/CooFrame';
* @property {boolean} [samp=false] - Whether to enable SAMP (Simple Application Messaging Protocol).
* @property {boolean} [realFullscreen=false] - Whether to use real fullscreen mode.
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
*/
* @example
* let aladin = A.aladin({
target: 'galactic center',
fov: 10,
hipsList: [
// url
"https://alaskybis.unistra.fr/DSS/DSSColor",
// ID from HiPS list
"CDS/P/2MASS/color",
// Not full HiPS described
{
name: 'DESI Legacy Surveys color (g, r, i, z)',
id: 'CDS/P/DESI-Legacy-Surveys/DR10/color',
},
// Fully described HiPS
{
name: "DECaPS DR2 color",
url: "https://alasky.cds.unistra.fr/DECaPS/DR2/CDS_P_DECaPS_DR2_color/",
properties: {
creatorDid: "ivo://CDS/P/DECaPS/DR2/color",
maxOrder: 11,
cooFrame: "equatorial",
tileSize: 512,
imgFormat: 'png',
},
},
// HiPS with options
{
name: "SDSS9 band-g",
id: "P/SDSS9/g",
properties: {
creatorDid: "ivo://CDS/P/SDSS9/g",
maxOrder: 10,
tileSize: 512,
numBitsPerPixel: 16,
imgFormat: 'fits',
cooFrame: 'equatorial',
},
options: {
minCut: 0,
maxCut: 1.8,
stretch: 'linear',
colormap: "redtemperature",
}
}
]
})*/
/**
* @typedef {Object} CircleSelection
@@ -335,6 +388,84 @@ export let Aladin = (function () {
this.setBaseImageLayer(url);
}
let hipsList = [].concat(options.hipsList);
const fillHiPSCache = () => {
for (var survey of hipsList) {
let id, url, name;
let cachedSurvey = {};
if (typeof survey === "string") {
try {
url = new URL(survey).href;
} catch(e) {
id = survey;
}
name = url || id;
} else if (survey instanceof Object) {
if (survey.id) {
id = survey.id;
}
if (survey.url) {
url = survey.url;
}
name = survey.name || survey.id || survey.url;
if (id && url) {
console.warn('Both "CDS ID" and url are given for ', survey, '. ID is chosen.')
url = null;
}
if (survey.properties) {
cachedSurvey = {...cachedSurvey, ...survey.properties}
}
if (survey.options) {
cachedSurvey = {...cachedSurvey, ...survey.options}
}
} else {
console.warn('unable to parse the survey list item: ', survey)
continue;
}
if (id) {
cachedSurvey['id'] = id;
}
if (url) {
cachedSurvey['url'] = url;
}
if (name) {
cachedSurvey['name'] = name;
}
// at least id or url is defined
let key = id || url;
if (!(key in ImageSurvey.cache)) {
ImageSurvey.cache[key] = cachedSurvey
}
}
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(this.aladinDiv);
}
if (hipsList.length === 0) {
MocServer.getAllHiPSes()
.then((HiPSes) => {
HiPSes.forEach((h) => {
hipsList.push({
id: h.ID,
name: h.obs_title
})
});
fillHiPSCache();
});
} else {
fillHiPSCache();
}
this.view.showCatalog(options.showCatalog);
// FullScreen toolbar icon
@@ -412,6 +543,7 @@ export let Aladin = (function () {
if (!options.showLayersControl) {
stack._hide();
}
// Add the simbad pointer control
if (!options.showSimbadPointerControl) {
simbad._hide();
@@ -443,6 +575,10 @@ export let Aladin = (function () {
this.addUI(new FullScreenActionButton(self))
}
if (options.expandLayersControl) {
stack.toggle();
}
this._applyMediaQueriesUI();
}
@@ -494,6 +630,8 @@ export let Aladin = (function () {
Aladin.wasmLibs = {};
Aladin.DEFAULT_OPTIONS = {
survey: ImageSurvey.DEFAULT_SURVEY_ID,
// surveys suggestion list
hipsList: [],
//surveyUrl: ["https://alaskybis.unistra.fr/DSS/DSSColor", "https://alasky.unistra.fr/DSS/DSSColor"],
target: "0 +0",
cooFrame: "J2000",
@@ -504,6 +642,7 @@ export let Aladin = (function () {
showZoomControl: false,
// Menu toolbar
showLayersControl: true,
expandLayersControl: false,
showFullscreenControl: true,
showSimbadPointerControl: false,
showCooGridControl: false,
@@ -539,7 +678,7 @@ export let Aladin = (function () {
log: true,
samp: false,
realFullscreen: false,
pixelateCanvas: true
pixelateCanvas: true,
};
// realFullscreen: AL div expands not only to the size of its parent, but takes the whole available screen estate
@@ -851,10 +990,11 @@ export let Aladin = (function () {
// try to parse as a position
if (!isObjectName) {
var coo = new Coo();
coo.parse(targetName);
// Convert from view coo sys to icrs
const [ra, dec] = this.wasm.viewToICRSCooSys(coo.lon, coo.lat);
this.view.pointTo(ra, dec);
(typeof successCallback === 'function') && successCallback(this.getRaDec());
@@ -914,14 +1054,14 @@ export let Aladin = (function () {
* @memberof Aladin
* @param {number} lon - longitude in degrees
* @param {number} lat - latitude in degrees
* @param {string} frame - Optional callback options.
* @param {string} [frame] - The name of the coordinate frame. Possible values: 'j2000d', 'j2000', 'gal', 'icrs'. The given string is case insensitive.
*
* @example
* // Move to position
* const aladin = A.aladin('#aladin-lite-div');
* aladin.gotoPosition(20, 10, "galactic");
*/
Aladin.prototype.gotoPosition = function (lon, lat, frame = undefined) {
Aladin.prototype.gotoPosition = function (lon, lat, frame) {
var radec;
// convert the frame from string to CooFrameEnum
if (frame) {
@@ -936,7 +1076,7 @@ export let Aladin = (function () {
radec = [lon, lat];
}
this.view.pointTo(radec[0], radec[1]);
this.gotoRaDec(radec[0], radec[1]);
};
var idTimeoutAnim;
@@ -1140,9 +1280,15 @@ export let Aladin = (function () {
};
/**
* point to a given position, expressed as a ra,dec coordinate
* Moves the Aladin instance to the specified position given in ICRS frame
*
* @API
* @memberof Aladin
* @param {number} ra - Right-ascension in degrees
* @param {number} dec - Declination in degrees
*
* @example
* const aladin = A.aladin('#aladin-lite-div');
* aladin.gotoRaDec(20, 10);
*/
Aladin.prototype.gotoRaDec = function (ra, dec) {
this.view.pointTo(ra, dec);
@@ -1239,7 +1385,15 @@ export let Aladin = (function () {
let surveyOptions = ImageSurvey.cache[id];
if (!surveyOptions) {
surveyOptions = {url, name, maxOrder, cooFrame, ...options};
surveyOptions = {name, maxOrder, cooFrame, ...options};
// differenciate url from CDS Id in the url param given
if (!Utils.isUrl(url)) {
surveyOptions.id = url;
} else {
surveyOptions.url = url;
}
ImageSurvey.cache[id] = surveyOptions;
}

View File

@@ -214,14 +214,15 @@ HiPSProperties.getFasterMirrorUrl = function (metadata, currUrl) {
newUrlResp = validResponses[0];
} else {
// no valid response => we return an error
return Promise.reject('Survey not found. All mirrors urls have been tested:' + urls)
return Promise.reject('All mirrors urls have been tested:' + urls)
}
// check if there is a big difference from the current one
let currUrlResp = validResponses.find((r) => r.baseUrl === currUrl)
// it may happen that the url requested by the user is too slow hence discarded
// for these cases, we automatically switch to the new fastest url.
let urlChosen;
if (Math.abs(currUrlResp.duration - newUrlResp.duration) / Math.min(currUrlResp.duration, newUrlResp.duration) < 0.10) {
if (currUrlResp && Math.abs(currUrlResp.duration - newUrlResp.duration) / Math.min(currUrlResp.duration, newUrlResp.duration) < 0.10) {
// there is not enough difference => do not switch
urlChosen = currUrlResp.baseUrl;
} else {

View File

@@ -144,8 +144,7 @@ export let ImageSurvey = (function () {
* @class
* @constructs ImageSurvey
*
* @param {string} id - Mandatory unique identifier for the layer.
* Can be an arbitrary name
* @param {string} id - Mandatory unique identifier for the layer. Can be an arbitrary name
* @param {string} url - Can be an url to the survey or a "CDS" ID pointing towards a HiPS. One can found the list of IDs {@link https://aladin.cds.unistra.fr/hips/list| here}
* @param {ImageSurveyOptions} [options] - The option for the survey
*
@@ -193,32 +192,29 @@ export let ImageSurvey = (function () {
self.query = (async () => {
if (isMOCServerToBeQueried) {
let properties;
let isCDSId = false;
try {
properties = await HiPSProperties.fetchFromUrl(self.url)
/*.catch((e) => {
// try with the proxy
url = Utils.handleCORSNotSameOrigin(url).href;
return HiPSProperties.fetchFromUrl(url);
})*/
.catch(async (e) => {
// url not valid so we try with the id
try {
isCDSId = true;
// the url stores a "CDS ID" we take it prioritaly
// if the url is null, take the id, this is for some tests
// to pass because some users might just give null as url param and a "CDS ID" as id param
let id = self.url || self.id;
return await HiPSProperties.fetchFromID(id);
} catch(e) {
throw e;
}
})
} catch(e) {
throw e;
}
let properties = await HiPSProperties.fetchFromUrl(self.url)
/*.catch((e) => {
// try with the proxy
url = Utils.handleCORSNotSameOrigin(url).href;
return HiPSProperties.fetchFromUrl(url);
})*/
.catch(async (e) => {
// url not valid so we try with the id
try {
isCDSId = true;
// the url stores a "CDS ID" we take it prioritaly
// if the url is null, take the id, this is for some tests
// to pass because some users might just give null as url param and a "CDS ID" as id param
let id = self.url || self.id;
return await HiPSProperties.fetchFromID(id);
} catch(e) {
throw e;
}
})
//obsTitle = properties.obs_title;
self.creatorDid = properties.creator_did || self.creatorDid;
@@ -238,24 +234,25 @@ export let ImageSurvey = (function () {
if (self.url !== url) {
console.info("Change url of ", self.id, " from ", self.url, " to ", url)
self.url = url;
// save the new url to the cache
self._saveInCache();
// If added to the backend, then we need to tell it the url has changed
if (self.added) {
self.view.wasm.setHiPSUrl(self.creatorDid, url);
}
self.url = url;
// save the new url to the cache
ImageSurvey.cache[self.id].url = self.url;
}
})
/*.catch(e => {
.catch(e => {
//alert(e);
console.error(self)
console.error(e);
// the survey has been added so we remove it from the stack
self.view.removeImageLayer(self.layer)
//self.view.removeImageLayer(self.layer)
//throw e;
})*/
})
}
// Max order
@@ -402,7 +399,13 @@ export let ImageSurvey = (function () {
// append new important infos from the properties queried
...surveyOpt,
}
}
//console.log('new CACHE', ImageSurvey.cache, self.id, surveyOpt, ImageSurvey.cache[self.id], ImageSurvey.cache["CSIRO/P/RACS/mid/I"])
// Tell that the HiPS List has been updated
ALEvent.HIPS_LIST_UPDATED.dispatchedTo(this.view.aladin.aladinDiv);
}
/**
* Checks if the ImageSurvey represents a planetary body.
@@ -704,7 +707,7 @@ export let ImageSurvey = (function () {
},
});
this.added = true;
//this.added = true;
return Promise.resolve(this);
}
@@ -748,7 +751,7 @@ export let ImageSurvey = (function () {
// A cache storing directly surveys important information to not query for the properties each time
ImageSurvey.cache = {
DSS2_color: {
/*DSS2_color: {
creatorDid: "ivo://CDS/P/DSS2/color",
name: "DSS colored",
url: "https://alasky.cds.unistra.fr/DSS/DSSColor",
@@ -913,6 +916,7 @@ export let ImageSurvey = (function () {
stretch: 'linear',
colormap: "redtemperature",
}
*/
/*
{
id: "P/Finkbeiner",

View File

@@ -110,7 +110,7 @@ export let MOC = (function() {
* set MOC data by parsing a MOC serialized in JSON
* (as defined in IVOA MOC document, section 3.1.1)
*/
MOC.prototype.parse = function(data, successCallback) {
MOC.prototype.parse = function(data, successCallback, errorCallback) {
if (typeof data === 'string' || data instanceof String) {
let url = data;
this.promiseFetchData = fetch(url)
@@ -120,7 +120,7 @@ export let MOC = (function() {
}
this.successCallback = successCallback;
this.errorCallback = this.errorCallback;
this.errorCallback = errorCallback;
};
MOC.prototype.setView = function(view) {
@@ -166,7 +166,11 @@ export let MOC = (function() {
self.view.requestRedraw();
})
.catch(e => alert('MOC load error:' + e))
.catch(e => {
console.error('MOC load error:' + e)
if (self.errorCallback)
self.errorCallback(self);
})
};
MOC.prototype.reportChange = function() {

View File

@@ -469,6 +469,14 @@ Utils.fixURLForHTTPS = function (url) {
return url
}
Utils.isUrl = function(url) {
try {
return new URL(url).href;
} catch(e) {
return undefined;
}
}
// generate an absolute URL from a relative URL
// example: getAbsoluteURL('foo/bar/toto') return http://cds.unistra.fr/AL/foo/bar/toto if executed from page http://cds.unistra.fr/AL/
Utils.getAbsoluteURL = function (url) {

View File

@@ -488,8 +488,10 @@ export let View = (function () {
View.prototype.selectLayer = function (layer) {
if (!this.imageLayers.has(layer)) {
throw layer + ' does not exists. So cannot be selected';
console.warn(layer + ' does not exists. So cannot be selected');
return;
}
this.selectedLayer = layer;
};
@@ -1167,7 +1169,7 @@ export let View = (function () {
}
//requestAnimFrame(moveTo)
}*/
}, 30);
}, 40);
}
view.throttledTouchPadZoom();
@@ -1578,10 +1580,20 @@ export let View = (function () {
if (idxOverlayLayer == -1) {
// it does not exist so we add it to the stack
this.overlayLayers.push(layerName);
} else {
// it exists
let alreadyPresentImageLayer = this.imageLayers.get(layerName);
alreadyPresentImageLayer.added = false;
}
imageLayer.added = true;
this.imageLayers.set(layerName, imageLayer);
// select the layer if he is on top
if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
}
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
@@ -1678,11 +1690,6 @@ export let View = (function () {
this.imageLayers.delete(layer);
this.imageLayers.set(newLayer, imageLayer);
// Change the selected layer if this is the one renamed
/*if (this.selectedLayer === layer) {
this.selectedLayer = newLayer;
}*/
// Tell the layer hierarchy has changed
ALEvent.HIPS_LAYER_RENAMED.dispatchedTo(this.aladinDiv, { layer, newLayer });
}
@@ -1734,7 +1741,7 @@ export let View = (function () {
this.empty = true;
} else if (this.selectedLayer === layer) {
// If the layer removed was selected then we select the base layer
this.selectedLayer = 'base';
this.selectLayer(this.overlayLayers[this.overlayLayers.length - 1]);
}
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer });
@@ -1857,11 +1864,11 @@ export let View = (function () {
ra = parseFloat(ra);
dec = parseFloat(dec);
if (isNaN(ra) || isNaN(dec)) {
if (!ra || !dec) {
return;
}
this.viewCenter.lon = ra;
this.viewCenter.lat = dec;
this.viewCenter.lat = dec;
//this.updateLocation({lon: this.viewCenter.lon, lat: this.viewCenter.lat});
// Put a javascript code here to do some animation
@@ -1905,7 +1912,13 @@ export let View = (function () {
this.catalogs = [];
this.overlays = [];
this.mocs = [];
this.allOverlayLayers.forEach((overlay) => {
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: overlay });
})
this.allOverlayLayers = [];
this.mustClearCatalog = true;
this.requestRedraw();
};
@@ -1929,7 +1942,7 @@ export let View = (function () {
this.overlays.splice(indexToDelete, 1);
}
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: layer });
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer });
this.mustClearCatalog = true;
this.requestRedraw();
@@ -2000,7 +2013,7 @@ export let View = (function () {
let closest = null;
footprints.forEach((footprint) => {
if (!footprint.source.tooSmallFootprint) {
if (!footprint.source || !footprint.source.tooSmallFootprint) {
// Hidden footprints are not considered
let lineWidth = footprint.getLineWidth();

View File

@@ -53,6 +53,8 @@ export class ALEvent {
static HIPS_LAYER_RENAMED = new ALEvent("AL:HiPSLayer.renamed");
static HIPS_LAYER_SWAP = new ALEvent("AL:HiPSLayer.swap");
static HIPS_LIST_UPDATED = new ALEvent("AL:HiPSList.updated");
static HIPS_LAYER_CHANGED = new ALEvent("AL:HiPSLayer.changed");
static GRAPHIC_OVERLAY_LAYER_ADDED = new ALEvent("AL:GraphicOverlayLayer.added");

View File

@@ -131,6 +131,7 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
}, aladin)
super({
close: false,
content: Layout.horizontal({
layout: [inputText, loadBtn]
}),
@@ -238,10 +239,10 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
this.loadBtn.update({disable: true}, aladin)
} else {
let self = this;
let layout = [];
let ctxMenu = [];
if (item && item.cs_service_url) {
layout.push({
ctxMenu.push({
label: 'Cone search',
disable: !item.cs_service_url,
action(o) {
@@ -263,6 +264,8 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
})
self._hide();
self.callback && self.callback();
},
position: {
anchor: 'center center',
@@ -270,13 +273,12 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
})
self.box._show();
self.loadBtn.hideMenu()
}
})
}
if (item && item.hips_service_url) {
layout.push({
ctxMenu.push({
label: 'HiPS catalogue',
disable: !item.hips_service_url,
action(o) {
@@ -286,15 +288,22 @@ import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
})
self._hide();
self.callback && self.callback();
}
})
}
this.loadBtn.update({ctxMenu: layout, disable: false}, aladin)
this.loadBtn.update({ctxMenu, disable: false}, aladin)
}
this.loadBtn.hideMenu()
}
attach(options) {
this.callback = options.callback;
super.update(options)
}
_hide() {
if (this.box) {
this.box.remove();

View File

@@ -79,6 +79,7 @@ import { Input } from "../Widgets/Input.js";
super(
{
close: false,
content: Layout.horizontal({
layout: [
inputText,

View File

@@ -40,15 +40,15 @@ import { ColorCfg } from "../../ColorCfg.js";
import { Layout } from "../Layout.js";
import { Input } from "../Widgets/Input.js";
export class LayerEditBox extends Box {
export class HiPSSettingsBox extends Box {
// Constructor
constructor(aladin, options) {
super(
{
super({
cssStyle: {
padding: '4px',
backgroundColor: 'black',
},
close: false,
...options
},
aladin.aladinDiv
@@ -162,21 +162,19 @@ import { ColorCfg } from "../../ColorCfg.js";
let layerOpacity = layer.getOpacity()
self.opacitySettingsContent = Layout.horizontal([
Input.slider({
tooltip: {content: layerOpacity, position: {direction: 'bottom'}},
name: 'opacitySlider',
type: 'range',
min: 0.0,
max: 1.0,
value: layerOpacity,
change(e, slider) {
const opacity = +e.target.value;
layer.setOpacity(opacity)
slider.update({value: opacity, tooltip: {content: opacity.toFixed(2), position: {direction: 'bottom'}}})
}
}),
]);
self.opacitySettingsContent = Input.slider({
tooltip: {content: layerOpacity, position: {direction: 'bottom'}},
name: 'opacitySlider',
type: 'range',
min: 0.0,
max: 1.0,
value: layerOpacity,
change(e, slider) {
const opacity = +e.target.value;
layer.setOpacity(opacity)
slider.update({value: opacity, tooltip: {content: opacity.toFixed(2), position: {direction: 'bottom'}}})
}
})
let brightness = layer.getColorCfg().getBrightness()
let saturation = layer.getColorCfg().getSaturation()
@@ -311,10 +309,10 @@ import { ColorCfg } from "../../ColorCfg.js";
_addListeners() {
ALEvent.HIPS_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, (e) => {
const layerChanged = e.detail.layer;
const hips = e.detail.layer;
let selectedLayer = this.options.layer;
if (selectedLayer && layerChanged.layer === selectedLayer.layer) {
let colorCfg = layerChanged.getColorCfg();
if (selectedLayer && hips.layer === selectedLayer.layer) {
let colorCfg = hips.getColorCfg();
let cmap = colorCfg.getColormap();
let reversed = colorCfg.getReversed();
@@ -323,7 +321,9 @@ import { ColorCfg } from "../../ColorCfg.js";
let [minCut, maxCut] = colorCfg.getCuts();
this.minCutInput.set(+minCut.toFixed(2));
this.maxCutInput.set(+maxCut.toFixed(2));
this.stretchSelector.update({value: stretch})
this.stretchSelector.update({value: stretch});
this.opacitySettingsContent.set(hips.getOpacity())
}
});
}

View File

@@ -35,6 +35,7 @@ export class ShortLivedBox extends Box {
constructor(aladin) {
super(
{
close: false,
cssStyle: {
color: 'white',
backgroundColor: 'black',

874
src/js/gui/Box/StackBox.js Normal file
View File

@@ -0,0 +1,874 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File gui/Stack/Menu.js
*
*
* Author: Matthieu Baumann [CDS, matthieu.baumann@astro.unistra.fr]
*
*****************************************************************************/
import { CatalogQueryBox } from "./CatalogQueryBox.js";
import { ALEvent } from "../../events/ALEvent.js";
import { Layout } from "../Layout.js";
import { ContextMenu } from "../Widgets/ContextMenu.js";
import { ActionButton } from "../Widgets/ActionButton.js";
import A from "../../A.js";
import { Utils } from "../../Utils";
import { View } from "../../View.js";
import { HiPSSettingsBox } from "./HiPSSettingsBox.js";
import searchIconUrl from '../../../../assets/icons/search.svg';
import showIconUrl from '../../../../assets/icons/show.svg';
import addIconUrl from '../../../../assets/icons/plus.svg';
import hideIconUrl from '../../../../assets/icons/hide.svg';
import removeIconUrl from '../../../../assets/icons/remove.svg';
import settingsIconUrl from '../../../../assets/icons/settings.svg';
import { ImageFITS } from "../../ImageFITS.js";
import searchIconImg from '../../../../assets/icons/search.svg';
import { TogglerActionButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { ImageSurvey } from "../../ImageSurvey.js";
import { Box } from "../Widgets/Box.js";
import { CtxMenuActionButtonOpener } from "../Button/CtxMenuOpener.js";
import { HiPSSearch } from "../Input/HiPSSearch.js";
export class OverlayStackBox extends Box {
/*static previewImagesUrl = {
'AllWISE color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_allWISE_color.jpg',
'DSS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DSS2_color.jpg',
'DSS2 Red (F+R)': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_DSS2_red.jpg',
'Fermi color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Fermi_color.jpg',
'GALEXGR6_7 NUV': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_GALEXGR6_7_color.jpg',
'GLIMPSE360': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_GLIMPSE360.jpg',
'Halpha': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_VTSS_Ha.jpg',
'IRAC color I1,I2,I4 - (GLIMPSE, SAGE, SAGE-SMC, SINGS)': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SPITZER_color.jpg',
'IRIS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_IRIS_color.jpg',
'Mellinger colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Mellinger_color.jpg',
'PanSTARRS DR1 color': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_PanSTARRS_DR1_color-z-zg-g.jpg',
'2MASS colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_2MASS_color.jpg',
'AKARI colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_AKARI_FIS_Color.jpg',
'SWIFT': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SWIFT_BAT_FLUX.jpg',
'VTSS-Ha': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_Finkbeiner.jpg',
'XMM PN colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_XMM_PN_color.jpg',
'SDSS9 colored': 'https://aladin.cds.unistra.fr/AladinLite/survey-previews/P_SDSS9_color.jpg',
};*/
static predefinedCats = {
simbad: {url: 'https://axel.u-strasbg.fr/HiPSCatService/SIMBAD', options: {id: 'simbad', name: 'SIMBAD', shape: 'circle', sourceSize: 8, color: '#318d80', onClick: 'showTable'}},
gaia: {url: 'https://axel.u-strasbg.fr/HiPSCatService/I/355/gaiadr3', options: {id: 'gaia-dr3', name: 'Gaia DR3', shape: 'square', sourceSize: 8, color: '#6baed6', onClick: 'showTable'}},
twomass: {url: 'https://axel.u-strasbg.fr/HiPSCatService/II/246/out', options: {id: '2mass', name: '2MASS', shape: 'plus', sourceSize: 8, color: '#dd2233', onClick: 'showTable'}}
};
// Constructor
constructor(aladin) {
super({
close: false,
header: {
title: 'Stack',
},
classList: ['aladin-stack-box'],
content: []
},
aladin.aladinDiv);
this.aladin = aladin;
this.mode = 'stack';
this._addListeners();
this.mocHiPSUrls = {}
this.HiPSui = {}
let self = this;
// Add overlay button
this.addOverlayBtn = new CtxMenuActionButtonOpener({
icon: {
url: addIconUrl,
size: 'small',
monochrome: true,
},
tooltip: {content: 'A catalog, MOC or footprint', position: { direction: 'top' }},
ctxMenu: [
{
label: 'Catalogue',
subMenu: [
{
label: {
icon: {
url: 'https://aladin.cds.unistra.fr/AladinLite/logos/SIMBAD.svg',
cssStyle: {
width: '3rem',
height: '3rem',
cursor: 'help',
},
action(o) {
window.open('https://simbad.cds.unistra.fr/simbad/')
}
},
content: 'database',
tooltip: {content: 'Click to go to the SIMBAD database', position: {direction: 'bottom'}},
},
action(o) {
o.stopPropagation();
o.preventDefault();
//self._hide();
const simbadHiPS = A.catalogHiPS(OverlayStackBox.predefinedCats.simbad.url, OverlayStackBox.predefinedCats.simbad.options);
self.aladin.addCatalog(simbadHiPS);
}
},
{
label: 'Gaia DR3',
action(o) {
o.stopPropagation();
o.preventDefault();
//self._hide();
const simbadHiPS = A.catalogHiPS(OverlayStackBox.predefinedCats.gaia.url, OverlayStackBox.predefinedCats.gaia.options);
self.aladin.addCatalog(simbadHiPS);
}
},
{
label: '2MASS',
action(o) {
o.stopPropagation();
o.preventDefault();
//self._hide();
const simbadHiPS = A.catalogHiPS(OverlayStackBox.predefinedCats.twomass.url, OverlayStackBox.predefinedCats.twomass.options);
self.aladin.addCatalog(simbadHiPS);
}
},
ContextMenu.fileLoaderItem({
label: 'From a VOTable File',
accept: '.xml,.vot',
action(file) {
let url = URL.createObjectURL(file);
A.catalogFromURL(
url,
{onClick: 'showTable'},
(catalog) => {
self.aladin.addCatalog(catalog)
},
e => alert(e)
);
}
}),
{
label: {
icon: {
url: searchIconImg,
monochrome: true,
tooltip: {content: 'Find a specific catalogue <br /> in our database...', position: { direction: 'top' }},
cssStyle: {
cursor: 'help',
},
},
content: 'More...'
},
action(o) {
o.stopPropagation();
o.preventDefault();
self._hide();
if (!self.catBox) {
self.catBox = new CatalogQueryBox(self.aladin);
self.catBox.attach({callback: () => {
self._show();
}});
}
self.catBox._show({position: self.position});
}
},
]
},
{
label: {
icon: {
url: Icon.dataURLFromSVG({svg: Icon.SVG_ICONS.MOC}),
size: 'small',
tooltip: {content: 'Define a selection coverage', position: {direction: 'bottom'}},
monochrome: true,
cssStyle: {
cursor: 'pointer',
},
},
content: 'MOC'
},
subMenu: [
ContextMenu.fileLoaderItem({
label: 'FITS File',
accept: '.fits',
action(file) {
let url = URL.createObjectURL(file);
let moc = A.MOCFromURL(
url,
{name: file.name, lineWidth: 3.0},
);
self.aladin.addMOC(moc)
}
}),
{
label: 'From selection',
subMenu: [
{
label: '◌ Circle',
disabled: self.aladin.view.mode !== View.PAN ? {
reason: 'Exit your current mode<br/>(e.g. disable the SIMBAD pointer mode)'
} : false,
action(o) {
o.preventDefault();
o.stopPropagation();
//self._hide();
self.aladin.select('circle', c => {
try {
let [ra, dec] = self.aladin.pix2world(c.x, c.y, 'j2000');
let radius = self.aladin.angularDist(c.x, c.y, c.x + c.r, c.y);
// the moc needs a
let moc = A.MOCFromCone(
{ra, dec, radius},
{name: 'cone', lineWidth: 3.0},
);
self.aladin.addMOC(moc)
} catch {
console.error('Circle out of projection. Selection canceled')
}
})
}
},
{
label: '⬚ Rect',
disabled: self.aladin.view.mode !== View.PAN ? {
reason: 'Exit your current mode<br/>(e.g. disable the SIMBAD pointer mode)'
} : false,
action(o) {
o.stopPropagation();
o.preventDefault();
//self._hide();
self.aladin.select('rect', r => {
try {
let [ra1, dec1] = self.aladin.pix2world(r.x, r.y, 'j2000');
let [ra2, dec2] = self.aladin.pix2world(r.x + r.w, r.y, 'j2000');
let [ra3, dec3] = self.aladin.pix2world(r.x + r.w, r.y + r.h, 'j2000');
let [ra4, dec4] = self.aladin.pix2world(r.x, r.y + r.h, 'j2000');
let moc = A.MOCFromPolygon(
{
ra: [ra1, ra2, ra3, ra4],
dec: [dec1, dec2, dec3, dec4]
},
{name: 'rect', lineWidth: 3.0},
);
self.aladin.addMOC(moc)
} catch(_) {
alert('Selection covers a region out of the projection definition domain.');
}
})
}
},
{
label: '⛉ Polygon',
disabled: self.aladin.view.mode !== View.PAN ? {
reason: 'Exit your current mode<br/>(e.g. disable the SIMBAD pointer mode)'
} : false,
action(o) {
o.stopPropagation();
o.preventDefault();
//self._hide();
self.aladin.select('poly', p => {
try {
let ra = []
let dec = []
for (const v of p.vertices) {
let [lon, lat] = self.aladin.pix2world(v.x, v.y, 'j2000');
ra.push(lon)
dec.push(lat)
}
let moc = A.MOCFromPolygon(
{ra, dec},
{name: 'poly', lineWidth: 3.0},
);
self.aladin.addMOC(moc)
} catch(_) {
alert('Selection covers a region out of the projection definition domain.');
}
})
}
},
]
}
]
}
],
}, this.aladin)
this.addHiPSBtn = new CtxMenuActionButtonOpener({
icon: {
url: addIconUrl,
size: 'small',
monochrome: true,
},
ctxMenu: [
{
label: {
icon: {
url: searchIconUrl,
monochrome: true,
tooltip: {content: 'From our database...', position: { direction: 'right' }},
cssStyle: {
cursor: 'help',
},
},
content: 'Add new survey'
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
/*self._hide();
self.hipsSelectorBox = new HiPSSelectorBox(self.aladin);
// attach a callback
self.hipsSelectorBox.attach(
(HiPSId) => {
let name = Utils.uuidv4()
self.aladin.setOverlayImageLayer(HiPSId, name)
self.show();
}
);
self.hipsSelectorBox._show({
position: self.position,
});*/
self.aladin.addNewImageLayer()
}
},
ContextMenu.fileLoaderItem({
label: 'FITS image file',
accept: '.fits',
action(file) {
let url = URL.createObjectURL(file);
const image = self.aladin.createImageFITS(
url,
file.name,
undefined,
(ra, dec, fov, _) => {
// Center the view around the new fits object
self.aladin.gotoRaDec(ra, dec);
self.aladin.setFoV(fov * 1.1);
//self.aladin.selectLayer(image.layer);
},
undefined
);
self.aladin.setOverlayImageLayer(image, Utils.uuidv4())
}
}),
],
tooltip: { content: 'Add a HiPS or an FITS image', position: {direction: 'top'} },
}, this.aladin);
this.update({content: this.createLayout()});
}
_addListeners() {
let self = this;
let updateOverlayList = () => {
let wasHidden = self.isHidden;
self._hide();
// recompute the ui
// If it is shown, update it
// show will update the content of the stack
self.update({content: self.createLayout()});
if (!wasHidden)
self._show();
};
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.GRAPHIC_OVERLAY_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_ADDED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_RENAMED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_SWAP.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_REMOVED.listenedBy(this.aladin.aladinDiv, function (e) {
updateOverlayList();
});
ALEvent.HIPS_LAYER_CHANGED.listenedBy(this.aladin.aladinDiv, function (e) {
const hips = e.detail.layer;
let ui = self.HiPSui[hips.layer];
// change the ui from parameter changes
// show button
const opacity = hips.getOpacity();
if (opacity !== 0.0) {
ui.showBtn.update({icon: {monochrome: true, url: showIconUrl}, tooltip: {content: 'Hide'}});
} else {
ui.showBtn.update({icon: {monochrome: true, url: hideIconUrl}, tooltip: {content: 'Show'}});
}
});
updateOverlayList();
// Add a listener for HiPS list changes
ALEvent.HIPS_LIST_UPDATED.listenedBy(this.aladin.aladinDiv, () => {
// Recompute the autocompletion as the cache has changed
HiPSSearch.HiPSList = {};
for (var key in ImageSurvey.cache) {
let HiPS = ImageSurvey.cache[key];
// search with the name or id
HiPSSearch.HiPSList[HiPS.name] = HiPS;
}
let keys = Object.keys(HiPSSearch.HiPSList)
// change the autocomplete of all the search input text
for (var key in this.HiPSui) {
let hips = this.HiPSui[key];
hips.searchInput.setAutocompletionList(keys)
}
});
}
_hide() {
for (var key in this.HiPSui) {
let hips = this.HiPSui[key];
if (hips.settingsBtn.toggled) {
// toggle off
hips.settingsBtn.toggle();
}
}
if (this.catBox) {
this.catBox._hide();
}
if (this.addOverlayBtn)
this.addOverlayBtn.hideMenu();
if (this.addHiPSBtn)
this.addHiPSBtn.hideMenu();
super._hide()
}
createLayout() {
this.HiPSui = {};
let layout = [
Layout.horizontal([this.addOverlayBtn, 'Overlays'])
];
layout = layout.concat(this._createOverlaysList());
layout.push(Layout.horizontal({
layout: [this.addHiPSBtn, 'Surveys'],
}))
layout = layout.concat(this._createSurveysList());
return new Layout({layout, classList: ['content']});
}
_createOverlaysList() {
let self = this;
let layout = []
const overlays = Array.from(this.aladin.getOverlays()).reverse().map((overlay) => {
return overlay;
});
// list of overlays
for(const overlay of overlays) {
const name = overlay.name;
let showBtn = new ActionButton({
size: 'small',
icon: {
url: overlay.isShowing ? showIconUrl : hideIconUrl,
monochrome: true,
},
/*cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},*/
tooltip: {content: overlay.isShowing ? 'Hide' : 'Show', position: {direction: 'top'}},
action(e, btn) {
if (overlay.isShowing) {
overlay.hide()
btn.update({icon: {monochrome: true, url: hideIconUrl}, tooltip: {content: 'Show'}});
} else {
overlay.show()
btn.update({icon: {monochrome: true, url: showIconUrl}, tooltip: {content: 'Hide'}});
}
}
});
let deleteBtn = new ActionButton({
icon: {
url: removeIconUrl,
monochrome: true,
},
size: 'small',
/*cssStyle: {
visibility: Utils.hasTouchScreen() ? 'visible' : 'hidden',
},*/
tooltip: {
content: 'Remove',
position: {direction: 'top'}
},
action(e) {
self.aladin.removeLayer(overlay)
}
});
let item = Layout.horizontal({
layout: [
this._addOverlayIcon(overlay),
'<div style="background-color: rgba(0, 0, 0, 0.6); padding: 3px; border-radius: 3px; word-break: break-word;">' + name + '</div>',
Layout.horizontal({layout: [showBtn, deleteBtn]})
],
cssStyle: {
textAlign: 'center',
display: 'flex',
alignItems: 'center',
listStyle: 'none',
justifyContent: 'space-between',
width: '100%',
}
});
/*if(!Utils.hasTouchScreen()) {
layout.push({
label: item,
cssStyle,
hover(e) {
showBtn.el.style.visibility = 'visible'
deleteBtn.el.style.visibility = 'visible'
},
unhover(e) {
showBtn.el.style.visibility = 'hidden'
deleteBtn.el.style.visibility = 'hidden'
},
})
} else {
layout.push({
label: item,
cssStyle
})
}*/
layout.push(item)
}
return layout;
}
_createSurveysList() {
let self = this;
const layers = Array.from(self.aladin.getImageOverlays()).reverse().map((name) => {
let overlay = self.aladin.getOverlayImageLayer(name);
return overlay;
});
// survey list
let selectedLayer = self.aladin.getSelectedLayer();
/*if (!layers) {
super.attach(layout);
return;
}*/
let layout = [];
const defaultLayers = Object.entries(ImageSurvey.cache).sort(function (e1, e2) {
let a = e1[1]
let b = e2[1]
if (!a.order) {
return a.name > b.name ? 1 : -1;
}
return a.maxOrder && a.maxOrder > b.maxOrder ? 1 : -1;
});
for(const layer of layers) {
let searchInput = new HiPSSearch(self.aladin, {layer})
let deleteBtn = ActionButton.createSmallSizedIconBtn({
icon: {url: removeIconUrl, monochrome: true},
disable: layer.layer === 'base',
tooltip: {content: 'Remove', position: {direction: 'top'}},
action(e) {
self.aladin.removeImageLayer(layer.layer);
}
});
let showBtn = ActionButton.createSmallSizedIconBtn({
icon: {
url: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
monochrome: true,
},
tooltip: {content: layer.getOpacity() === 0.0 ? 'Show' : 'Hide', position: {direction: 'top'}},
action(e, btn) {
e.preventDefault();
e.stopPropagation();
let opacity = layer.getOpacity();
if (opacity === 0.0) {
layer.setOpacity(1.0);
btn.update({icon: {monochrome: true, url: showIconUrl}, tooltip: {content: 'Hide'}});
} else {
layer.setOpacity(0.0);
btn.update({icon: {monochrome: true, url: hideIconUrl}, tooltip: {content: 'Show'}});
}
}
});
let settingsBox = new HiPSSettingsBox(self.aladin);
settingsBox.update({layer})
settingsBox._hide();
let settingsBtn = new TogglerActionButton({
icon: {url: settingsIconUrl, monochrome: true},
size: 'small',
tooltip: {content: 'Settings', position: {direction: 'top'}},
toggled: false,
actionOn: (e) => {
settingsBox._show({position: {nextTo: settingsBtn, direction: 'right', aladin: self.aladin}});
},
actionOff: (e) => {
settingsBox._hide();
},
});
let loadMOCBtn = new ActionButton({
size: 'small',
icon: {url: Icon.dataURLFromSVG({svg: Icon.SVG_ICONS.MOC}), monochrome: true},
tooltip: {content: 'Add coverage', position: {direction: 'top'}},
toggled: (() => {
let overlays = self.aladin.getOverlays();
let found = overlays.find((o) => o.type === "moc" && o.name === layer.name);
return found !== undefined;
})(),
action: (e, btn) => {
if (!btn.options.toggled) {
// load the moc
let moc = A.MOCFromURL(
layer.url + '/Moc.fits',
{lineWidth: 3, name: layer.name},
() => {
self.mocHiPSUrls[layer.url] = moc;
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message: 'Coverage of ' + layer.name + ' loaded',
duration: 2000,
type: 'info'
})
}
btn.update({
toggled: true,
tooltip: {content: 'Remove coverage',
position: {direction: 'top'}}
})
}
);
self.aladin.addMOC(moc)
} else {
// unload the moc
let moc = self.mocHiPSUrls[layer.url];
self.aladin.removeLayer(moc)
delete self.mocHiPSUrls[layer.url];
if (self.aladin.statusBar) {
self.aladin.statusBar.appendMessage({
message: 'Coverage of ' + layer.name + ' removed',
duration: 2000,
type: 'info'
})
}
btn.update({
toggled: false,
tooltip: {content: 'Add coverage', position: {direction: 'top'}}
})
}
},
});
let layerClassName = 'a' + layer.layer.replace(/[.\/ ]/g, '')
let btns = [showBtn, settingsBtn];
if (layer.subtype !== 'fits') {
btns.push(loadMOCBtn)
}
btns.push(deleteBtn)
let item = Layout.horizontal({
layout: [
searchInput,
//'<div class="' + layerClassName + '" style="background-color: rgba(0, 0, 0, 0.6); line-height: 1rem; padding: 3px; border-radius: 3px; word-break: break-word;' + (selectedLayer === layer.layer ? 'border: 1px solid white;' : '') + '">' + (layer.name) + '</div>',
Layout.horizontal(btns)
],
cssStyle: {
display: 'flex',
alignItems: 'center',
listStyle: 'none',
justifyContent: 'space-between',
width: '100%',
}
});
layout.push(item);
if (!(layer.layer in self.HiPSui)) {
self.HiPSui[layer.layer] = {
searchInput,
settingsBox,
settingsBtn,
showBtn,
};
}
}
return layout;
}
/*_findPreviewImageUrl(layer) {
if (layer instanceof ImageFITS) {
return;
}
if (!layer.creatorDid) {
return;
}
const creatorDid = layer.creatorDid;
for (const key in Stack.previewImagesUrl) {
if (creatorDid.includes(key)) {
return Stack.previewImagesUrl[key];
}
}
// if not found
return layer.url + '/preview.jpg'
}*/
_addOverlayIcon(overlay) {
var tooltipText;
var svg = '';
if (overlay.type == 'catalog' || overlay.type == 'progressivecat') {
var nbSources = overlay.getSources().length;
tooltipText = nbSources + ' source' + (nbSources > 1 ? 's' : '');
svg = Icon.SVG_ICONS.CATALOG;
}
else if (overlay.type == 'moc') {
tooltipText = 'Coverage: ' + (100 * overlay.skyFraction()).toFixed(2) + ' % of sky';
svg = Icon.SVG_ICONS.MOC;
}
else if (overlay.type == 'overlay') {
svg = Icon.SVG_ICONS.OVERLAY;
}
let tooltip;
if (tooltipText) {
tooltip = { content: tooltipText, position: {direction: 'bottom'} }
}
// retrieve SVG icon, and apply the layer color
return new Icon({
size: 'small',
url: Icon.dataURLFromSVG({svg, color: overlay.color}),
tooltip
});
}
_show(options) {
if (!this.aladin) {
return;
}
this.position = (options && options.position) || this.position;
if (!this.position)
return;
this.position.aladin = this.aladin;
super._show({
...options,
...{position: this.position},
})
const innerHeight = this.aladin.aladinDiv.offsetHeight;
this.element().querySelectorAll(".surveyItem")
.forEach((surveyItem) => {
surveyItem.querySelectorAll(".aladin-context-sub-menu")
// skip the first menu
.forEach((subMenu) => {
subMenu.style.display = 'block'
let Y = innerHeight - (subMenu.getBoundingClientRect().y - this.aladin.aladinDiv.getBoundingClientRect().y);
subMenu.style.display = 'none'
subMenu.style.maxHeight = Y + 'px';
subMenu.style.overflowY = 'scroll';
})
})
}
}

View File

@@ -38,7 +38,7 @@ import { Icon } from "../Widgets/Icon";
export class StatusBarBox extends Box {
constructor(aladin, options) {
super(options, aladin.aladinDiv)
super({...options, close: false}, aladin.aladinDiv)
this.addClass("aladin-status-bar");

View File

@@ -20,6 +20,8 @@
import { CtxMenuActionButtonOpener } from "./CtxMenuOpener";
import stackOverlayIconUrl from './../../../../assets/icons/stack.svg';
import { OverlayStack } from "../CtxMenu/OverlayStack";
import { OverlayStackBox } from "../Box/StackBox";
import { TogglerActionButton } from "./Toggler";
/******************************************************************************
* Aladin Lite project
*
@@ -35,14 +37,15 @@ import { OverlayStack } from "../CtxMenu/OverlayStack";
* Class representing a Tabs layout
* @extends CtxMenuActionButtonOpener
*/
export class OverlayStackButton extends CtxMenuActionButtonOpener {
export class OverlayStackButton extends TogglerActionButton {
/**
* UI responsible for displaying the viewport infos
* @param {Aladin} aladin - The aladin instance.
*/
constructor(aladin, options) {
let self;
let stack = new OverlayStack(aladin);
let stack = new OverlayStackBox(aladin);
super({
icon: {
size: 'medium',
@@ -56,7 +59,18 @@ import { OverlayStack } from "../CtxMenu/OverlayStack";
direction: 'top right'
}
},
ctxMenu: stack,
toggled: false,
actionOn: (e) => {
stack._show({
position: {
nextTo: self,
direction: 'right'
}
})
},
actionOff: (e) => {
stack._hide()
},
...options
}, aladin);

View File

@@ -43,20 +43,26 @@ export class TogglerActionButton extends ActionButton {
...options,
toggled,
action(o) {
toggled = !toggled;
self.update({toggled, tooltip: toggled ? options.tooltipOn : options.tooltipOff})
if (toggled && options.actionOn) {
options.actionOn(o)
}
if (!toggled && options.actionOff) {
options.actionOff(o)
}
options.action && options.action(o)
self.toggle(o);
}
})
this.toggled = toggled;
self = this;
}
toggle(o) {
this.toggled = !this.toggled;
if (this.toggled && this.options.actionOn) {
this.options.actionOn(o)
}
if (!this.toggled && this.options.actionOff) {
this.options.actionOff(o)
}
// once the actions has been executed, modify the styling
this.update({toggled: this.toggled, tooltip: this.toggled ? this.options.tooltipOn : this.options.tooltipOff})
}
}

View File

@@ -35,7 +35,7 @@ import { CatalogQueryBox } from "../Box/CatalogQueryBox.js";
import A from "../../A.js";
import { Utils } from "../../../js/Utils";
import { View } from "../../View.js";
import { LayerEditBox } from "../Box/SurveyEditBox.js";
import { HiPSSettingsBox } from "../Box/HiPSSettingsBox.js";
import { HiPSSelectorBox } from "../Box/HiPSSelectorBox.js";
import searchIconUrl from '../../../../assets/icons/search.svg';
import showIconUrl from '../../../../assets/icons/show.svg';
@@ -456,70 +456,6 @@ export class OverlayStack extends ContextMenu {
}
}
layout.push({
label: 'Add survey',
subMenu: [
{
label: {
icon: {
url: searchIconUrl,
monochrome: true,
tooltip: {content: 'From our database...', position: { direction: 'right' }},
cssStyle: {
cursor: 'help',
},
},
content: 'Search for a survey'
},
action: (e) => {
e.stopPropagation();
e.preventDefault();
self._hide();
self.hipsSelectorBox = new HiPSSelectorBox(self.aladin);
// attach a callback
self.hipsSelectorBox.attach(
(HiPSId) => {
let name = Utils.uuidv4()
self.aladin.setOverlayImageLayer(HiPSId, name)
self.show();
}
);
self.hipsSelectorBox._show({
position: self.position,
});
self.mode = 'hips';
}
},
ContextMenu.fileLoaderItem({
label: 'FITS image file',
accept: '.fits',
action(file) {
let url = URL.createObjectURL(file);
const image = self.aladin.createImageFITS(
url,
file.name,
undefined,
(ra, dec, fov, _) => {
// Center the view around the new fits object
self.aladin.gotoRaDec(ra, dec);
self.aladin.setFoV(fov * 1.1);
//self.aladin.selectLayer(image.layer);
},
undefined
);
self.aladin.setOverlayImageLayer(image, Utils.uuidv4())
}
}),
]
})
// survey list
let selectedLayer = self.aladin.getSelectedLayer();
@@ -669,7 +605,7 @@ export class OverlayStack extends ContextMenu {
let item = Layout.horizontal({
layout: [
'<div class="' + layerClassName + '" style="background-color: rgba(0, 0, 0, 0.6); line-height: 1rem; padding: 3px; border-radius: 3px; word-break: break-word;' + (selectedLayer === layer.layer ? 'border: 1px solid white;' : '') + '">' + (layer.name) + '</div>',
Layout.horizontal({layout: btns})
Layout.horizontal(btns)
],
/*cssStyle: {
display: 'flex',

View File

@@ -42,29 +42,25 @@ export class FoV extends DOMElement {
// constructor
constructor(aladin, options) {
let layout = [];
if (options.showZoomControl) {
layout.push(new ActionButton({
let zoomIn = new ActionButton({
classList: 'aladin-zoom-in',
size: 'small',
tooltip: {content: 'zoom in', position: {direction: 'top'}},
icon: {
monochrome: true,
size: 'small',
url: plusIconUrl,
},
cssStyle: {
marginRight: 0,
borderRight: 'none',
borderRadius: '5px 0px 0px 5px'
},
action(o) {
aladin.increaseZoom();
}
}))
layout.push(new ActionButton({
})
let zoomOut = new ActionButton({
size: 'small',
cssStyle: {
borderRadius: '0px 5px 5px 0px'
},
classList: 'aladin-zoom-out',
tooltip: {content: 'zoom out', position: {direction: 'top'}},
icon: {
monochrome: true,
size: 'small',
@@ -73,7 +69,12 @@ export class FoV extends DOMElement {
action(o) {
aladin.decreaseZoom();
}
}))
});
zoomIn.el.classList.add('aladin-zoom-in');
zoomOut.el.classList.add('aladin-zoom-out');
layout.push(zoomIn)
layout.push(zoomOut)
}
if (options.showFov) {
@@ -82,10 +83,10 @@ export class FoV extends DOMElement {
'<div class="aladin-monospace-text"></div>'])
}
let el = Layout.horizontal({layout, tooltip: { content: 'FoV', position: {direction: "top"}}});
if (el.tooltip) {
el.tooltip.addClass('aladin-fov');
el.tooltip.addClass('aladin-dark-theme')
let el = Layout.horizontal({layout});
if (el) {
el.addClass('aladin-fov');
el.addClass('aladin-dark-theme')
}
super(el)

View File

@@ -0,0 +1,149 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { Box } from "../Widgets/Box.js";
import { Layout } from "../Layout.js";
import { ActionButton } from "../Widgets/ActionButton.js";
import { ALEvent } from "../../events/ALEvent.js";
/******************************************************************************
* Aladin Lite project
*
* File gui/HiPSSelector.js
*
*
* Author: Thomas Boch, Matthieu Baumann[CDS]
*
*****************************************************************************/
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File Location.js
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import { Input } from "./../Widgets/Input.js";
export class HiPSSearch extends Input {
static HiPSList = {};
// constructor
constructor(aladin, options) {
let self;
let layer = options && options.layer;
aladin.view.catalogCanvas.addEventListener('click', (e) => {
self.el.blur();
});
let prevKey = layer.name;
super({
name: 'HiPS search',
type: 'text',
classList: ['search'],
name: 'survey',
placeholder: "Survey keywords or url",
autocomplete: {options: Object.keys(HiPSSearch.HiPSList)},
title: layer.name,
actions: {
change(e) {
const key = e.target.value;
if (!key) {
self.update({value: prevKey, title: prevKey});
return;
}
let image;
// A user can put an url
try {
image = new URL(key).href;
} catch(e) {
// Or he can select a HiPS from the list given
let hips = HiPSSearch.HiPSList[key]
//console.log("HIPS", key, hips)
if (hips) {
image = hips.id || hips.url || undefined;
} else {
// Finally if not found, interpret the input text value as the HiPS (e.g. ID)
image = key;
}
}
self.el.blur();
if (image) {
prevKey = key;
aladin.setOverlayImageLayer(image, layer.layer);
}
},
/*input(e) {
let value = e.target.value;
self.update({value, title: value})
}*/
},
value: layer.name,
...options
})
this.addClass('aladin-HiPS-search')
self = this;
this.layer = layer;
this._addEventListeners(aladin);
}
setAutocompletionList(options) {
this.update({autocomplete: {options}})
}
_addEventListeners(aladin) {
let self = this;
ALEvent.HIPS_LAYER_ADDED.listenedBy(aladin.aladinDiv, (e) => {
const layer = e.detail.layer;
if (layer.layer === self.layer.layer) {
let value = layer.name
self.update({value, title: value})
}
});
}
};

View File

@@ -28,7 +28,7 @@
*
*****************************************************************************/
import { CooConversion } from "../CooConversion.js";
import { Coo } from "../libs/astro/coo.js";
import { CooFrameEnum } from "../CooFrameEnum.js";
@@ -146,7 +146,15 @@ export class Location extends DOMElement {
let param = e.detail;
if (param.type === 'mouseout') {
let [lon, lat] = aladin.getRaDec();
let radec = aladin.getRaDec();
// convert to the view frame
let lonlat = radec;
if (aladin.getFrame() === "Galactic") {
lonlat = CooConversion.J2000ToGalactic(radec)
}
let [lon, lat] = lonlat;
self.update({
lon, lat,
frame: aladin.view.cooFrame,

View File

@@ -67,6 +67,25 @@ export class Box extends DOMElement {
let self = this;
let close = this.options.close === false ? false : true;
if (close) {
new ActionButton({
size: 'small',
content: '❌',
//tooltip: {content: 'Close the window', position: {direction: 'bottom'}},
action(e) {
self._hide();
},
cssStyle: {
position: 'absolute',
},
position: {
top: 0,
right: 0,
}
}, this.el);
}
// Check for the title
if (this.options.header) {
let header = this.options.header;
@@ -98,23 +117,11 @@ export class Box extends DOMElement {
titleEl.style.cursor = 'move'
}
let closedEl = new ActionButton({
size: 'small',
content: '❌',
tooltip: {content: 'Close the window', position: {direction: 'bottom'}},
cssStyle: {
cursor: 'pointer',
},
action(e) {
self._hide();
}
});
Layout.horizontal({
cssStyle: {
justifyContent: 'space-between',
},
layout: [draggableEl, titleEl, closedEl]
layout: [draggableEl, titleEl]
}, this.el);
let separatorEl = document.createElement('div')

View File

@@ -135,7 +135,7 @@ export class Input extends DOMElement {
}
this.el.appendChild(datalist);
this.el.autocomplete = 'on';
this.el.autocomplete = 'off';
} else {
this.el.autocomplete = autocomplete;
}
@@ -199,6 +199,10 @@ export class Input extends DOMElement {
this.el.name = this.options.name;
}
if (this.options.title) {
this.el.title = this.options.title;
}
this.el.classList.add('aladin-input');
this.el.classList.add('aladin-dark-theme');

View File

@@ -146,9 +146,9 @@ export class DOMElement {
}
const aladinDiv = options && options.aladin && options.aladin.aladinDiv;
if (!aladinDiv) {
return;
}
let innerWidth = aladinDiv && aladinDiv.offsetWidth;
let innerHeight = aladinDiv && aladinDiv.offsetHeight;
let left, top, bottom, right;
let x, y;
@@ -156,11 +156,8 @@ export class DOMElement {
// handle the anchor/dir case with higher priority
const {offsetWidth, offsetHeight} = el;
const innerWidth = aladinDiv.offsetWidth;
const innerHeight = aladinDiv.offsetHeight;
// take on less priority the left and top
if (options && (options.left || options.top || options.right || options.bottom)) {
if (options && (options.left !== undefined || options.top !== undefined || options.right !== undefined || options.bottom !== undefined)) {
el.style.position = 'absolute';
if (options.top !== undefined) {
@@ -177,7 +174,7 @@ export class DOMElement {
}
if (typeof top === 'number') {
if (top + offsetHeight >= innerHeight) {
if (innerHeight && top + offsetHeight >= innerHeight) {
y = '-' + (top + offsetHeight - innerHeight) + 'px';
} else if (top < 0) {
y = Math.abs(top) + 'px';
@@ -189,7 +186,7 @@ export class DOMElement {
bottom = bottom + 'px';
}
if (typeof left === 'number') {
if (left + offsetWidth > innerWidth) {
if (innerWidth && left + offsetWidth > innerWidth) {
x = '-' + (left + offsetWidth - innerWidth) + 'px';
} else if (left < 0) {
x = Math.abs(left) + 'px';