Compare commits

..

4 Commits

Author SHA1 Message Date
Matthieu Baumann
28869645cd fix opacity restore in UI, and screen size shrinking 2025-12-12 11:27:03 +01:00
Matthieu Baumann
da8eb6f76e feat: swap layer orders 2025-12-08 17:46:34 +01:00
Matthieu Baumann
0e3a359108 wip base layer 2025-12-08 14:33:31 +01:00
Matthieu Baumann
af89535a91 first commit tree 2025-12-08 14:33:29 +01:00
27 changed files with 8670 additions and 664 deletions

4
assets/icons/folder.svg Normal file
View File

@@ -0,0 +1,4 @@
<?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 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 1H5L8 3H13V5H3.7457L2.03141 11H4.11144L5.2543 7H16L14 14H0V1Z" fill="#000000"/>
</svg>

After

Width:  |  Height:  |  Size: 322 B

15
assets/icons/swap.svg Normal file
View File

@@ -0,0 +1,15 @@
<?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 48 48" xmlns="http://www.w3.org/2000/svg">
<title>swap-vertical-circle</title>
<g id="Layer_2" data-name="Layer 2">
<g id="invisible_box" data-name="invisible box">
<rect width="48" height="48" fill="none"/>
</g>
<g id="icons_Q2" data-name="icons Q2">
<path d="M19.4,36.4l5-4.9a2.1,2.1,0,0,0,.2-2.7,1.9,1.9,0,0,0-3-.2L20,30.2V15a2,2,0,0,0-4,0V30.2l-1.6-1.6a1.9,1.9,0,0,0-3,.2,2.1,2.1,0,0,0,.2,2.7l5,4.9A1.9,1.9,0,0,0,19.4,36.4Z"/>
<path d="M32,33V17.8l1.6,1.6a1.9,1.9,0,0,0,3-.2,2.1,2.1,0,0,0-.2-2.7l-5-4.9a1.9,1.9,0,0,0-2.8,0l-5,4.9a2.1,2.1,0,0,0-.2,2.7,1.9,1.9,0,0,0,3,.2L28,17.8V33a2,2,0,0,0,4,0Z"/>
<path d="M24,42A18,18,0,1,1,42,24,18.1,18.1,0,0,1,24,42m0,4A22,22,0,1,0,2,24,21.9,21.9,0,0,0,24,46Z"/>
</g>
</g>

After

Width:  |  Height:  |  Size: 948 B

View File

@@ -8,8 +8,8 @@
"dateModified": "2023-01-31",
"issueTracker": "https://github.com/cds-astro/aladin-lite/issues",
"name": "Aladin Lite",
"version": "3.7.2-beta",
"softwareVersion": "3.7.2-beta",
"version": "3.7.3-beta",
"softwareVersion": "3.7.3-beta",
"description": "An astronomical HiPS visualizer in the browser.",
"identifier": "10.5281/zenodo.7638833",
"applicationCategory": "Astronomy, Visualization",

View File

@@ -7,7 +7,7 @@
<script type="module">
import A from '../src/js/A.js';
A.init.then(() => {
let aladin = A.aladin('#aladin-lite-div', {fov: 70,projection: "AIT"});
let aladin = A.aladin('#aladin-lite-div', {target: "23 28 32.46 -00 10 38.9", fov: 4, projection: "AIT"});
let hsc = aladin.newImageSurvey("P/HSC/DR2/deep/g", {colormap:"Purples", imgFormat: "fits"});
aladin.setBaseImageLayer(hsc);

View File

@@ -37,7 +37,10 @@
colorPicker.value = cat.color;
colorPicker.addEventListener('input', function (e) {
// Change the color of the catalog
cat.updateShape({color: this.value});
console.log(this.value)
cat.updateShape({color: () => {
return '#00ff00'
}});
})
// Define the box

View File

@@ -10,7 +10,7 @@
<script type="text/javascript">
var aladin;
A.init.then(() => {
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, survey: 'https://jafar.astro.unistra.fr/data/jafar_hips/Surveys/CFIS/IC0750/asinh_0_1/', fov: 180, showContextMenu: true});
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, cooFrame: "ICRSd", showSimbadPointerControl: true, showShareControl: true, showShareControl: true, fov: 180, showContextMenu: true});
// manage URL parameters
const searchParams = new URL(document.location).searchParams;
if (searchParams.has('baseImageLayer')) {

View File

@@ -553,6 +553,7 @@ impl App {
/*let is_there_new_available_tiles = self
.downloader
.get_resolved_tiles(/*&available_tiles, */&mut self.hipss);*/
//self.tile_fetcher.clear();
if self.request_for_new_tiles
&& Time::now() - self.last_time_request_for_new_tiles > DeltaTime::from(500.0)
@@ -564,18 +565,18 @@ impl App {
}
// Tiles are fetched if:
let fetch_tiles =
// * the user is not panning the view
// * or the user is but did not move for at least 100ms
//(Time::now() - self.camera.get_time_of_last_move() >= DeltaTime(100.0) || !self.dragging) &&
// * no inertia action is in progress
//self.inertia.is_none() &&
// * the user is not zooming
!self.camera.has_zoomed();
//let fetch_tiles =
// * the user is not panning the view
// * or the user is but did not move for at least 100ms
//(Time::now() - self.camera.get_time_of_last_move() >= DeltaTime(100.0) || !self.dragging) &&
// * no inertia action is in progress
//self.inertia.is_none() &&
// * the user is not zooming
// !self.camera.has_zoomed();
if fetch_tiles {
self.tile_fetcher.notify(self.downloader.clone(), None);
}
//if fetch_tiles {
self.tile_fetcher.notify(self.downloader.clone(), None);
//}
}
let rscs_received = self.downloader.borrow_mut().get_received_resources();
@@ -1114,10 +1115,6 @@ impl App {
Ok(())
}
pub(crate) fn rename_layer(&mut self, layer: &str, new_layer: &str) -> Result<(), JsValue> {
self.layers.rename_layer(layer, new_layer)
}
pub(crate) fn swap_layers(
&mut self,
first_layer: &str,

View File

@@ -315,11 +315,7 @@ impl CameraViewPort {
}
pub fn compute_ndc_to_clip_factor(&mut self, proj: &ProjectionType) {
self.ndc_to_clip = if self.height < self.width {
Vector2::new(1.0, (self.height as f64) / (self.width as f64))
} else {
Vector2::new((self.width as f64) / (self.height as f64), 1.0)
};
self.ndc_to_clip = Vector2::new(1.0, (self.height as f64) / (self.width as f64));
let bounds_size_ratio = proj.bounds_size_ratio();
self.ndc_to_clip.y *= bounds_size_ratio;

View File

@@ -372,12 +372,6 @@ impl WebClient {
Ok(())
}
#[wasm_bindgen(js_name = renameLayer)]
pub fn rename_layer(&mut self, layer: String, new_layer: String) -> Result<(), JsValue> {
// Deserialize the hips objects that compose the hips
self.app.rename_layer(&layer, &new_layer)
}
#[wasm_bindgen(js_name = swapLayers)]
pub fn swap_layers(
&mut self,

View File

@@ -351,29 +351,6 @@ impl Layers {
}
}
pub fn rename_layer(&mut self, layer: &str, new_layer: &str) -> Result<(), JsValue> {
let err_layer_not_found =
JsValue::from_str(&format!("Layer {layer:?} not found, so cannot be removed."));
// layer from layers does also need to be removed
let id_layer = self
.layers
.iter()
.position(|l| layer == l)
.ok_or(err_layer_not_found.clone())?;
self.layers[id_layer] = new_layer.to_string();
let meta = self.meta.remove(layer).ok_or(err_layer_not_found.clone())?;
let id = self.ids.remove(layer).ok_or(err_layer_not_found)?;
// Add the new
self.meta.insert(new_layer.to_string(), meta);
self.ids.insert(new_layer.to_string(), id);
Ok(())
}
pub fn swap_layers(&mut self, first_layer: &str, second_layer: &str) -> Result<(), JsValue> {
let id_first_layer =
self.layers

File diff suppressed because it is too large Load Diff

View File

@@ -19,6 +19,44 @@
--aladin-color-border: #fff;
}
.aladin-tree {
width: 100%;
border-top: 1px solid white;
border-bottom: 2px solid white;
}
.aladin-tree .aladin-directory-path {
padding-top: 0.5rem;
padding-bottom: 0.5rem;
width: 100%;
border-bottom: 2px solid white;
}
.aladin-tree ul {
padding:0;
margin:0;
overflow-y: scroll;
height: 300px;
}
.aladin-link {
list-style-type: none;
padding: 0.5em 0px;
cursor:pointer;
margin: 0;
}
.aladin-tree li:hover {
color: yellowgreen;
}
.aladin-tree li:last-child {
border-bottom: none;
}
.aladin-tree li {
border-bottom: 1px solid white;
}
.aladin-dark-theme {
background-color: black;
color: white;
@@ -239,18 +277,15 @@
font-family: monospace;
background: #fff;
line-height: 1.3;
color: #222;
/*box-shadow: 0 0 6px rgba(0,0,0,0.2);*/
/* Allow scrolling but disable scroll bar */
-ms-overflow-style: none; /* for Internet Explorer, Edge */
scrollbar-width: none; /* for Firefox */
/*overflow-y: auto;*/
overflow-y: none;
max-height: 500px;
/*max-width: fit-content;*/
height: fit-content;
height: min-content;
width: min-content;
border: 1px solid white;
}
.aladin-container canvas {
@@ -319,7 +354,7 @@
.aladin-box-separator {
height: 0;
border-top: 1px solid #d2d2d2;
margin: 5px 0px 5px -4px;
padding-bottom: 5px;
}
.aladin-restore {
@@ -509,11 +544,11 @@
}
.aladin-vertical-list > *:first-child {
margin-top: 0;
padding-top: 0;
}
.aladin-vertical-list > * {
margin-top: 0.5rem;
padding-top: 0.5rem;
}
.aladin-horizontal-list {
@@ -1124,6 +1159,7 @@ otherwise it fits its content options. If those are too big the select can go ou
.aladin-link:hover {
color: greenyellow;
text-decoration: underline;
}
.aladin-simbadPointer-control {
@@ -1160,9 +1196,13 @@ otherwise it fits its content options. If those are too big the select can go ou
.aladin-stack-box {
width: 17rem;
}
.aladin-item-selected {
border: 1px solid orange;
}
.aladin-HiPS-filter-box {
margin-top: 0.4rem;
/*width: 250px;*/
}
.aladin-HiPS-filter-box .aladin-horizontal-list {

View File

@@ -2004,7 +2004,7 @@ export let Aladin = (function () {
* </ul>
*/
Aladin.prototype.setBaseImageLayer = function (urlOrHiPSOrFITS) {
return this.setOverlayImageLayer(urlOrHiPSOrFITS, "base");
return this.setOverlayImageLayer(urlOrHiPSOrFITS, (this.view.overlayLayers && this.view.overlayLayers[0]) || Utils.uuidv4());
};
/**
@@ -2014,7 +2014,7 @@ export let Aladin = (function () {
* @returns {HiPS|Image} - Returns the image layer corresponding to the base layer
*/
Aladin.prototype.getBaseImageLayer = function () {
return this.view.getImageLayer("base");
return this.view.getImageLayer(this.view.overlayLayers && this.view.overlayLayers[0]);
};
/**
@@ -3003,7 +3003,7 @@ aladin.customizeShareURLFunction(() => {return 'https://sky.esa.int/esasky/?targ
* @param {Function} [successCallback=<center the view on the FITS file>] - The callback function to be executed on a successful display.
* The callback gives the ra, dec, and fov of the image; By default, it centers the view on the FITS file loaded.
* @param {Function} [errorCallback] - The callback function to be executed if an error occurs during display.
* @param {string} [layer="base"] - The name of the layer. If not specified, it will be replace the base layer.
* @param {string} [layer] - The name of the layer. If not specified, it will be replace the base layer.
*
* @example
aladin.displayFITS(
@@ -3027,7 +3027,7 @@ aladin.displayFITS(
options,
successCallback,
errorCallback,
layer = "base"
layer
) {
successCallback =
successCallback ||
@@ -3041,6 +3041,7 @@ aladin.displayFITS(
successCallback,
errorCallback
);
layer = layer || (this.view.overlayLayers && this.view.overlayLayers[0])
return this.setOverlayImageLayer(image, layer);
};

View File

@@ -51,7 +51,7 @@ export class MocServer {
//expr: "dataproduct_type=image",
get: "record",
fmt: "json",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime",
fields: "ID,hips_creator,hips_copyright,hips_order,hips_tile_width,hips_frame,hips_tile_format,obs_title,obs_description,obs_copyright,obs_regime,client_category",
//fields: "ID,hips_initial_fov,hips_initial_ra,hips_initial_dec,hips_pixel_bitpix,hips_creator,hips_copyright,hips_frame,hips_order,hips_order_min,hips_tile_width,hips_tile_format,hips_pixel_cut,obs_title,obs_description,obs_copyright,obs_regime,hips_data_range,hips_service_url",
};

View File

@@ -384,7 +384,6 @@ Utils.fetch = function(params) {
// localhost url
url = params.url;
}
let request = new Request(url, {
method: params.method || 'GET',

View File

@@ -256,7 +256,7 @@ export let View = (function () {
// some variables for mouse handling
this.dragging = false;
this.dragCoo = null;
this.selectedLayer = 'base';
this.selectedLayer = undefined;
this.needRedraw = true;
@@ -1784,6 +1784,27 @@ export let View = (function () {
// register its promise
this.imageLayersBeingQueried.set(layer, imageLayer);
// Check whether this layer already exist
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layer);
let alreadyPresentImageLayer;
if (idxOverlayLayer == -1) {
// it does not exist so we add it to the stack
this.overlayLayers.push(layer);
} else {
// it exists
alreadyPresentImageLayer = this.imageLayers.get(layer);
if (alreadyPresentImageLayer) {
if (alreadyPresentImageLayer.added === true) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
}
alreadyPresentImageLayer.added = false;
}
// Notify that this image layer has been replaced by the wasm part
this.imageLayers.delete(layer);
}
this.addImageLayer(imageLayer, layer);
return imageLayer;
@@ -1793,33 +1814,13 @@ export let View = (function () {
View.prototype._addLayer = function(imageLayer) {
// Keep the JS frontend in-line with the wasm state
const layerName = imageLayer.layer;
// Check whether this layer already exist
const idxOverlayLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layerName);
let alreadyPresentImageLayer;
if (idxOverlayLayer == -1) {
// it does not exist so we add it to the stack
this.overlayLayers.push(layerName);
} else {
// it exists
alreadyPresentImageLayer = this.imageLayers.get(layerName);
// Notify that this image layer has been replaced by the wasm part
if (alreadyPresentImageLayer && alreadyPresentImageLayer.added === true) {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: alreadyPresentImageLayer });
}
alreadyPresentImageLayer.added = false;
this.imageLayers.delete(layerName);
}
imageLayer.added = true;
this.imageLayers.set(layerName, imageLayer);
// select the layer if he is on top
//if (idxOverlayLayer == -1) {
this.selectLayer(layerName);
//}
this.selectLayer(layerName);
ALEvent.HIPS_LAYER_ADDED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
}
@@ -1877,7 +1878,7 @@ export let View = (function () {
// Remove the settled promise
this.promises.splice(idx, 1);
const noMoreLayersToWaitFor = this.promises.length === 0;
/*const noMoreLayersToWaitFor = this.promises.length === 0;
if (noMoreLayersToWaitFor) {
if (self.empty) {
@@ -1887,48 +1888,19 @@ export let View = (function () {
// it the best I can do if the MOCServer is out
self.aladin.setBaseImageLayer("https://alaskybis.cds.unistra.fr/DSS/DSSColor/");
} else {
// there is surveys that have been queried
// rename the first overlay layer to "base"
self.renameLayer(this.overlayLayers[0], "base");
//self.renameLayer(this.overlayLayers[0], "base");
}
}
}*/
})
}
// The survey at layer must have been added to the view!
View.prototype.renameLayer = function(layer, newLayer) {
if (layer === newLayer) {
return;
}
// Throw an exception if either the first or the second layers are not in the stack
this.wasm.renameLayer(layer, newLayer);
let imageLayer = this.imageLayers.get(layer);
imageLayer.layer = newLayer;
// Change in overlaylayers
const idx = this.overlayLayers.findIndex(overlayLayer => overlayLayer == layer);
this.overlayLayers[idx] = newLayer;
// Change in imageLayers
this.imageLayers.delete(layer);
this.imageLayers.set(newLayer, imageLayer);
if (this.selectedLayer === layer) {
this.selectedLayer = newLayer;
}
// Tell the layer hierarchy has changed
ALEvent.HIPS_LAYER_RENAMED.dispatchedTo(this.aladinDiv, { layer, newLayer });
}
View.prototype.swapLayers = function(firstLayer, secondLayer) {
// Throw an exception if either the first or the second layers are not in the stack
this.wasm.swapLayers(firstLayer, secondLayer);
// Swap in overlaylayers
const idxFirstLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == firstLayer);
const idxSecondLayer = this.overlayLayers.findIndex(overlayLayer => overlayLayer == secondLayer);
const idxFirstLayer = this.overlayLayers.indexOf(firstLayer);
const idxSecondLayer = this.overlayLayers.indexOf(secondLayer);
const tmp = this.overlayLayers[idxFirstLayer];
this.overlayLayers[idxFirstLayer] = this.overlayLayers[idxSecondLayer];
@@ -1967,7 +1939,7 @@ export let View = (function () {
this.overlayLayers.splice(idxOverlaidLayer, 1);
if (this.overlayLayers.length === 0) {
this.empty = true;
//this.empty = true;
} else if (this.selectedLayer === layer) {
// If the layer removed was selected then we select the last layer
this.selectLayer(this.overlayLayers[this.overlayLayers.length - 1]);
@@ -1976,12 +1948,12 @@ export let View = (function () {
ALEvent.HIPS_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: imageLayer });
// check if there are no more surveys
const noMoreLayersToWaitFor = this.promises.length === 0;
/*const noMoreLayersToWaitFor = this.promises.length === 0;
if (noMoreLayersToWaitFor && this.empty) {
// no promises to launch!
const dssId = Aladin.DEFAULT_OPTIONS.survey;
this.aladin.setBaseImageLayer(dssId);
}
}*/
};
View.prototype.contains = function(survey) {
@@ -2028,7 +2000,8 @@ export let View = (function () {
}
}
View.prototype.getImageLayer = function (layer = "base") {
View.prototype.getImageLayer = function (layer) {
layer = layer || (this.overlayLayers && this.overlayLayers[0]);
let imageLayerQueried = this.imageLayersBeingQueried.get(layer);
let imageLayer = this.imageLayers.get(layer);

View File

@@ -33,6 +33,8 @@ import { Utils } from "../../Utils.ts";
import { ActionButton } from "../Widgets/ActionButton.js";
import infoIconUrl from "../../../../assets/icons/info.svg"
import { Icon } from "../Widgets/Icon.js";
import { Tree } from "../Widgets/Tree.js";
import { ALEvent } from "../../events/ALEvent.js";
/******************************************************************************
* Aladin Lite project
@@ -44,32 +46,114 @@ import { Icon } from "../Widgets/Icon.js";
*
*****************************************************************************/
function fillHiPSHierarchy(name, hips, path, hierarchy) {
let folders = path.split('/')
let curFolder = folders.shift()
if(curFolder === 'Image') {
let newPath = folders.join('/')
fillHiPSHierarchy(name, hips, newPath, hierarchy);
} else {
// Some exceptions because the MOCServer client_category field may contain some typos
if (['X', 'X-ray', 'Xray'].includes(curFolder)) {
curFolder = 'X-ray'
}
if (['Radion', 'Radio'].includes(curFolder)) {
curFolder = 'Radio'
}
if (curFolder === "Deprecated")
return;
hierarchy[curFolder] = hierarchy[curFolder] || {};
if (folders.length == 0) {
hierarchy[curFolder][name] = hips
} else {
let newPath = folders.join('/')
fillHiPSHierarchy(name, hips, newPath, hierarchy[curFolder])
}
}
}
export class HiPSBrowserBox extends Box {
static HiPSList = {};
constructor(aladin, options) {
let self;
let filter = (item, params) => {
if (params.regime) {
if (!item.obs_regime)
return false;
if (params.regime.toLowerCase() !== item.obs_regime.toLowerCase()) {
return false;
}
}
if (params.resolution) {
if (!item.hips_tile_width || !item.hips_order) {
return false;
}
let pixelHEALPixOrder = Math.log2(item.hips_tile_width) + (+item.hips_order);
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
if (resPixel > params.resolution)
return false;
}
if (params.title) {
if (!item.obs_title)
return false;
if (!item.obs_title.toLowerCase().includes(params.title.toLowerCase())) {
return false;
}
}
return true;
};
// Search tree
let searchTree = new Tree({
// a JS object describing the tree to show
label: (item) => {
let name = item.obs_title.replace(/:|\'/g, '');
return name;
},
// a callback called when the user selects a leaf item of the tree
click: (item) => {
let image = item.ID || item.hips_service_url;
let name = item.obs_title || item.ID;
self._addHiPS(image, name)
},
// a callback called for filtering
filter,
});
MocServer.getAllHiPSes().then((HiPSes) => {
HiPSBrowserBox.HiPSList = {}
self.HiPSTree = {};
let hipsHierarchy = {};
// Fill the HiPSList from the MOCServer
// Build a hierarchy w.r.t sorted by regime
HiPSes.forEach((h) => {
let name = h.obs_title;
name = name.replace(/:|\'/g, '');
HiPSBrowserBox.HiPSList[name] = h;
self.HiPSTree[h.obs_regime] = self.HiPSTree[h.obs_regime] || {};
if (self.HiPSTree[h.obs_regime]) {
self.HiPSTree[h.obs_regime][name] = h
if (h.client_category) {
let path = h.client_category
fillHiPSHierarchy(name, h, path, hipsHierarchy)
}
});
console.log("jkjk", self.HiPSTree)
self.searchTree.setHierarchy(hipsHierarchy)
// Initialize the autocompletion without any filtering
self._filterHiPSList({})
@@ -98,7 +182,6 @@ export class HiPSBrowserBox extends Box {
if (image) {
self._addHiPS(image, name)
self.searchDropdown.update({title: value});
}
};
@@ -128,14 +211,11 @@ export class HiPSBrowserBox extends Box {
disable: true,
})
self.searchTree.triggerFilter({title: e.target.value});
searchDropdown.removeClass('aladin-valid')
searchDropdown.removeClass('aladin-not-valid')
},
change(e) {
e.stopPropagation();
e.preventDefault()
_parseHiPS(e)
}
},
});
@@ -193,13 +273,9 @@ export class HiPSBrowserBox extends Box {
},
toggled: false,
actionOn: (e) => {
self.filterBox._show({
position: {
nextTo: filterBtn,
direction: "bottom",
aladin,
},
});
self.filterBox._show({position: {
anchor: 'right center'
}});
},
actionOff: (e) => {
self.filterBox._hide();
@@ -216,15 +292,17 @@ export class HiPSBrowserBox extends Box {
size: 'medium',
url: hipsIconUrl,
monochrome: true,
}), "HiPS browser"])
},
onDragged: () => {
if (self.filterBtn.toggled) {
self.filterBtn.toggle();
}
}), "HiPS browser"]),
draggable: true,
},
//onDragged: () => {
//if (self.filterBtn.toggled) {
//self.filterBtn.toggle();
//}
//},
classList: ['aladin-HiPS-browser-box'],
content: Layout.vertical([
searchTree,
Layout.horizontal(["Search:", searchDropdown, infoCurrentHiPSBtn]),
Layout.horizontal(["Filter:", Layout.horizontal([filterEnabler, filterBtn, filterNumberElt])]),
]),
@@ -233,6 +311,9 @@ export class HiPSBrowserBox extends Box {
aladin.aladinDiv
);
self = this;
this.searchTree = searchTree;
this.filterBox = new HiPSFilterBox(aladin, {
callback: (params) => {
self._filterHiPSList(params);
@@ -247,48 +328,42 @@ export class HiPSBrowserBox extends Box {
this.infoCurrentHiPSBtn = infoCurrentHiPSBtn;
self = this;
this.filterCallback = (HiPS, params) => {
if (params.regime) {
if (!HiPS.obs_regime)
return false;
if (params.regime.toLowerCase() !== HiPS.obs_regime.toLowerCase()) {
return false;
}
}
if (params.spatial) {
if (!HiPS.ID)
return false;
if (Array.isArray(params.spatial) && !(params.spatial.includes(HiPS.ID))) {
return false;
}
}
if (params.resolution) {
if (!HiPS.hips_tile_width || !HiPS.hips_order) {
return false;
}
let pixelHEALPixOrder = Math.log2(HiPS.hips_tile_width) + (+HiPS.hips_order);
let resPixel = Math.sqrt(Math.PI / (3*Math.pow(4, pixelHEALPixOrder)));
if (resPixel > params.resolution)
return false;
}
return true;
};
this.filter = filter;
filterEnabler.action({target: {checked: true}});
this._addListeners();
this._requestMOCServer();
}
_addListeners() {
const requestMOCServerDebounced = Utils.debounce(() => {
this._requestMOCServer()
}, 500);
ALEvent.POSITION_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
ALEvent.ZOOM_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
}
_requestMOCServer() {
if (this.isHidden && this.searchTree) {
return;
}
let self = this;
MocServer.getAllHiPSesInsideView(this.aladin)
.then((HiPSes) => {
let HiPSIDs = HiPSes.map((x) => x.ID);
self.searchTree.highlightNodes(HiPSIDs)
})
}
_addHiPS(id, name) {
let self = this;
self.searchDropdown.update({value: name, title: name});
let hips = A.imageHiPS(id, {
name,
successCallback: (hips) => {
@@ -414,8 +489,8 @@ export class HiPSBrowserBox extends Box {
let HiPS = HiPSBrowserBox.HiPSList[key];
// apply filtering
if (
self.filterCallback &&
self.filterCallback(HiPS, params)
self.filter &&
self.filter(HiPS, params)
) {
// search with the name or id
let name = HiPS.obs_title;
@@ -425,14 +500,15 @@ export class HiPSBrowserBox extends Box {
}
}
if (self.searchTree) {
self.searchTree.triggerFilter(params);
}
self.searchDropdown.update({ options: HiPSIDs });
self.filterNumberElt.innerHTML = HiPSIDs.length + "/" + Object.keys(HiPSBrowserBox.HiPSList).length;
}
_hide() {
if (this.filterBox)
this.filterBox.signalBrowserStatus(true)
if (this.filterBtn && this.filterBtn.toggled) {
this.filterBtn.toggle();
}
@@ -441,12 +517,10 @@ export class HiPSBrowserBox extends Box {
}
_show(options) {
this._requestMOCServer();
// Regenerate a new layer name
this.layer = (options && options.layer) || Utils.uuidv4();
if (this.filterBox)
this.filterBox.signalBrowserStatus(false)
super._show(options)
}
}

View File

@@ -19,17 +19,11 @@
import { Box } from "../Widgets/Box.js";
import { Form } from "../Widgets/Form.js";
import { MocServer } from "../../MocServer.js";
import { TogglerActionButton } from "../Button/Toggler.js";
import { Layout } from "../Layout.js";
import { Angle } from "../../libs/astro/angle.js";
import { ALEvent } from "../../events/ALEvent.js";
import { Utils } from "../../Utils.ts";
import { AladinUtils } from "../../AladinUtils.js";
import { Input } from "../Widgets/Input.js";
import freqIconUrl from '../../../../assets/icons/freq.svg';
import inViewIconUrl from '../../../../assets/icons/inside.svg';
import targetIconUrl from '../../../../assets/icons/target.svg';
/******************************************************************************
* Aladin Lite project
@@ -45,120 +39,96 @@ export class HiPSFilterBox extends Box {
constructor(aladin, options) {
let self;
let regimeBtn = new TogglerActionButton({
content: 'Freq',
icon: {
monochrome: true,
size: 'medium',
url: freqIconUrl,
},
tooltip: {content: 'Observation bandwidth', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
},
actionOff: () => {
let regimeBtn = Input.checkbox({
name: 'Freq',
tooltip: {content: 'Observation bandwidth', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
self._triggerFilteringCallback();
}
});
let spatialBtn = new TogglerActionButton({
content: 'In view',
icon: {
monochrome: true,
size: 'medium',
url: inViewIconUrl,
},
tooltip: {content: 'Survey in view only!', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._requestMOCServer();
},
actionOff: () => {
self._triggerFilteringCallback();
}
});
let resolutionBtn = new TogglerActionButton({
content: 'Resolution',
icon: {
monochrome: true,
size: 'medium',
url: targetIconUrl,
},
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'bottom'}},
toggled: false,
actionOn: () => {
self._triggerFilteringCallback();
},
actionOff: () => {
let resolutionBtn = Input.checkbox({
name: 'Resolution',
tooltip: {content: 'Check for HiPS with a specific pixel resolution.', position: {direction: 'left'}},
type: 'checkbox',
checked: false,
click(e) {
self._triggerFilteringCallback();
}
});
let logSlider = new Input({
let regimeOption = Layout.horizontal({
tooltip: {
content: "Observation regime",
position: { direction: "right" },
},
label: 'Freq: ',
layout: [Input.select({
value: "Optical",
options: [
"Radio",
"Infrared",
"Millimeter",
"Optical",
"UV",
"EUV",
"X-ray",
"Gamma-ray",
],
change: (e) => {
let regime = e.target.value;
self.params["regime"] = regime;
self._triggerFilteringCallback();
},
}), regimeBtn]
});
let resolutionOption = Layout.horizontal({
label: "Max resolution [°/px]:",
name: "res",
value: 0.1,
type: 'range',
cssStyle: {
width: '100%'
},
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
stretch: "log",
min: 0.1 / 3600,
max: 0.1,
reversed: true,
change: (e, slider, deg) => {
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
layout: [
new Input({
name: "res",
value: 0.1,
type: 'range',
cssStyle: {
width: '200px'
},
tooltip: {content: AladinUtils.degreesToString(0.1), position: {direction: 'bottom'}},
ticks: [0.1 / 3600, 1 / 3600, 1 / 60, 0.1],
stretch: "log",
min: 0.1 / 3600,
max: 0.1,
reversed: true,
change: (e, slider, deg) => {
slider.update({value: e.target.value, tooltip: {content: AladinUtils.degreesToString(deg), position:{direction:'bottom'}}});
let resolution = new Angle(deg);
self.params["resolution"] = resolution.degrees();
let resolution = new Angle(deg);
self.params["resolution"] = resolution.degrees();
self._triggerFilteringCallback();
},
self._triggerFilteringCallback();
},
}),
resolutionBtn,
]
});
super(
{
classList: ['aladin-HiPS-filter-box'],
header: {
title: 'Filter tags',
draggable: false,
},
close: false,
classList: ['aladin-HiPS-filter-box'],
content: Layout.vertical([
'<b>Filter by:</b>',
Layout.horizontal([regimeBtn, spatialBtn, resolutionBtn]),
'<b>Details:</b>',
new Form({
subInputs: [
{
type: "group",
subInputs: [
{
label: "Freq:",
name: "regime",
value: "Optical",
type: 'select',
options: [
"Radio",
"Infrared",
"Millimeter",
"Optical",
"UV",
"EUV",
"X-ray",
"Gamma-ray",
],
change: (e) => {
let regime = e.target.value;
self.params["regime"] = regime;
//regimeBtn.update({content: regime});
self._triggerFilteringCallback();
},
tooltip: {
content: "Observation regime",
position: { direction: "right" },
},
},
logSlider
regimeOption,
resolutionOption
],
},
],
@@ -170,60 +140,28 @@ export class HiPSFilterBox extends Box {
self = this;
this.browserClosed = false;
this.callback = options.callback;
this.regimeBtn = regimeBtn;
this.spatialBtn = spatialBtn;
this.resolutionBtn = resolutionBtn;
this.params = {
regime: "Optical",
spatial: true,
highlight: true,
resolution: 1, // 1°/pixel
};
this.on = false;
this.aladin = aladin;
this._addListeners();
}
_addListeners() {
const requestMOCServerDebounced = Utils.debounce(() => {
this._requestMOCServer()
}, 500);
ALEvent.POSITION_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
ALEvent.ZOOM_CHANGED.listenedBy(this.aladin.aladinDiv, requestMOCServerDebounced);
}
_requestMOCServer() {
if (!this.spatialBtn.toggled || !this.on || this.browserClosed) {
return;
}
let self = this;
MocServer.getAllHiPSesInsideView(this.aladin)
.then((HiPSes) => {
let HiPSIDs = HiPSes.map((x) => x.ID);
self.params["spatial"] = HiPSIDs;
self._triggerFilteringCallback();
})
}
_triggerFilteringCallback() {
let filterParams = {};
if (this.regimeBtn.toggled) {
if (this.regimeBtn.checked) {
filterParams['regime'] = this.params['regime']
}
if (this.spatialBtn.toggled) {
filterParams['spatial'] = this.params['spatial']
}
if (this.resolutionBtn.toggled) {
if (this.resolutionBtn.checked) {
filterParams['resolution'] = this.params['resolution']
}
@@ -232,21 +170,18 @@ export class HiPSFilterBox extends Box {
}
}
signalBrowserStatus(closed) {
/*signalBrowserStatus(closed) {
this.browserClosed = closed;
// open
if (!closed) {
this._requestMOCServer()
}
}
}*/
enable(enable) {
this.on = enable;
if (this.on)
this._requestMOCServer();
this._triggerFilteringCallback();
}
}

View File

@@ -240,7 +240,7 @@ import { TogglerActionButton } from "../Button/Toggler.js";
let colorSettingsContent = new Form({
subInputs: [{
label: 'colormap:',
label: 'cmap:',
type: 'select',
name: 'cmap',
value: 'native',
@@ -295,13 +295,16 @@ import { TogglerActionButton } from "../Button/Toggler.js";
this.pixelSettingsContent.set('maxcut', +maxCut.toFixed(4))
this.pixelSettingsContent.set('stretch', stretch)
let fmtInput = this.pixelSettingsContent.getInput('fmt')
fmtInput.innerHTML = '';
for (const option of layer.getAvailableFormats()) {
fmtInput.innerHTML += "<option>" + option + "</option>";
if (layer.getAvailableFormats()) {
for (const option of layer.getAvailableFormats()) {
fmtInput.innerHTML += "<option>" + option + "</option>";
}
fmtInput.value = layer.imgFormat;
}
fmtInput.value = layer.imgFormat;
this.colorSettingsContent.set('cmap', colormap);
this.colorSettingsContent.set('reverse', reversed);

View File

@@ -43,7 +43,7 @@ import removeIconUrl from "../../../../assets/icons/remove.svg";
import settingsIconUrl from "../../../../assets/icons/settings.svg";
import searchIconImg from "../../../../assets/icons/search.svg";
import downloadIconUrl from '../../../../assets/icons/download.svg';
import swapIcon from '../../../../assets/icons/swap.svg'
import { TogglerActionButton } from "../Button/Toggler.js";
import { Icon } from "../Widgets/Icon.js";
import { Box } from "../Widgets/Box.js";
@@ -955,8 +955,7 @@ export class OverlayStackBox extends Box {
let deleteBtn = ActionButton.createSmallSizedIconBtn({
icon: { url: removeIconUrl, monochrome: true },
disable: layer.layer === "base",
//disable: layer.layer === "base",
tooltip: { content: "Remove", position: { direction: "top" } },
action(e) {
self.aladin.removeImageLayer(layer.layer);
@@ -965,6 +964,7 @@ export class OverlayStackBox extends Box {
},
});
let prevOpacity = null;
let showBtn = ActionButton.createSmallSizedIconBtn({
icon: {
url: layer.getOpacity() === 0.0 ? hideIconUrl : showIconUrl,
@@ -980,12 +980,15 @@ export class OverlayStackBox extends Box {
let opacity = layer.getOpacity();
if (opacity === 0.0) {
layer.setOpacity(1.0);
let newOpacity = prevOpacity || 1.0;
prevOpacity = null;
layer.setOpacity(newOpacity);
btn.update({
icon: { monochrome: true, url: showIconUrl },
tooltip: { content: "Hide" },
});
} else {
prevOpacity = opacity;
layer.setOpacity(0.0);
btn.update({
icon: { monochrome: true, url: hideIconUrl },
@@ -1106,12 +1109,45 @@ export class OverlayStackBox extends Box {
},
});
self.layer2swap = null;
let swapBtn = new ActionButton({
size: "small",
icon: {
url: swapIcon,
size: "small",
monochrome: true,
},
tooltip: {
content: "Swap 2 layers",
position: { direction: "top" },
},
toggled: false,
action: (_) => {
let toggled = swapBtn.options.toggled;
if (!toggled) {
if (!self.layer2swap) {
self.layer2swap = layer;
} else {
self.aladin.view.swapLayers(self.layer2swap.layer, layer.layer);
}
} else {
if (self.layer2swap) {
self.layer2swap = null;
}
}
swapBtn.update({
toggled: !toggled,
});
},
});
let btns = [showBtn, settingsBtn];
if (!(layer instanceof Image)) {
btns.push(loadMOCBtn);
}
btns.push(deleteBtn);
btns = btns.concat([swapBtn, deleteBtn]);
let item = Layout.horizontal({
layout: [HiPSSelector, Layout.horizontal(btns)],

View File

@@ -1,70 +0,0 @@
// Copyright 2023 - 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 { ALEvent } from "../../events/ALEvent";
import { ActionButton } from "../Widgets/ActionButton";
import mapIconUrl from '../../../../assets/icons/map.svg';
/******************************************************************************
* Aladin Lite project
*
* File gui/ActionButton.js
*
* A context menu that shows when the user right clicks, or long touch on touch device
*
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
export class MainSurveyActionButton extends ActionButton {
/**
* UI responsible for displaying the viewport infos
* @param {Aladin} aladin - The aladin instance.
*/
constructor(aladin, options) {
super({
...options,
tooltip: {content: 'Survey name<br/>Click to change it!', position: { direction: 'bottom' }},
iconURL: mapIconUrl,
})
this.addClass('medium-sized-icon')
this._addListeners(aladin)
}
_addListeners(aladin) {
ALEvent.HIPS_LAYER_ADDED.listenedBy(aladin.aladinDiv, (e) => {
const layer = e.detail.layer;
if (layer.layer === 'base') {
let name = (layer.properties && layer.properties.obsTitle) || layer.name;
this.update({
tooltip: {
content: 'Survey: ' + name,
position: {
direction: 'left'
}
}
})
}
});
}
}

View File

@@ -79,7 +79,7 @@ export class Dropdown extends Input {
super({
type: 'text',
autocomplete: {options: options.options},
//autocomplete: {options: options.options},
...options
})
this.el.classList.add('search')
@@ -91,11 +91,11 @@ export class Dropdown extends Input {
update(options) {
let newOptions = {};
if (options && options.options) {
/*if (options && options.options) {
newOptions['autocomplete'] = {options: options.options};
delete options.options;
}
}*/
// add the other input text options
newOptions = {...newOptions, ...options};

View File

@@ -67,6 +67,47 @@ export class Layout extends DOMElement {
this.appendContent(item)
}
}
if (options.draggable) {
// retrieve the children and add the drag listeners
let draggableFn = options.draggable;
let firstSelected = null;
this.el.childNodes.forEach(div => {
div.addEventListener("click", () => {
// If nothing selected yet → select this one
if (!firstSelected) {
firstSelected = div;
div.classList.add("aladin-item-selected");
return;
}
// If clicking the same one again → unselect
if (firstSelected === div) {
div.classList.remove("aladin-item-selected");
firstSelected = null;
return;
}
// Otherwise: swap the two elements
let a = firstSelected;
let b = div;
let temp = document.createElement("div");
a.parentNode.insertBefore(temp, a);
b.parentNode.insertBefore(a, b);
temp.parentNode.insertBefore(b, temp);
temp.remove();
// Exec callback
draggableFn(a, b)
// Clear selection
a.classList.remove("aladin-item-selected");
firstSelected = null;
});
});
}
}
// The tooltip has to be set once the element
@@ -182,9 +223,5 @@ export class Layout extends DOMElement {
if (this.options.position) {
this.setPosition(this.options.position)
}
//super._show()
// attach to the DOM again
//this.attachTo(this.target);
}
}
}

View File

@@ -137,11 +137,7 @@ export class Box extends DOMElement {
if (this.options.content) {
let content = this.options.content
//if (Array.isArray(content)) {
this.appendContent(content);
//} else {
// this.appendContent(content);
//}
this.appendContent(content);
}
if (this.options.position) {

View File

@@ -74,12 +74,17 @@ export class Input extends DOMElement {
this.el.type = this.type;
this.el.checked = this.options.checked;
this.checked = this.options.checked;
// for checkbox widgets, we authorize calling the callback name click or change
let action = this.options.click || this.options.change;
if (action) {
this.el.removeEventListener('click', this.action);
this.action = action;
this.action = (e) => {
this.checked = this.el.checked;
action(e);
};
this.el.addEventListener('click', this.action);
}
} else if (this.type === "select") {
@@ -374,6 +379,7 @@ export class Input extends DOMElement {
set(value) {
if (this.el.type === "checkbox") {
this.el.checked = value;
this.checked = value;
} else {
this.el.value = value;
}

331
src/js/gui/Widgets/Tree.js Normal file
View File

@@ -0,0 +1,331 @@
// Copyright 2023 - 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 { DOMElement } from "./Widget";
import { Icon } from "./Icon";
import folderIconUrl from "../../../../assets/icons/folder.svg";
/******************************************************************************
* Aladin Lite project
*
* File gui/Tree.js
*
* A tree
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************
*/
export class Tree extends DOMElement {
constructor(options, target, position = "beforeend") {
let el = document.createElement("div");
el.classList.add('aladin-tree');
super(el, options);
this.click = options && options.click;
let rootNode = options && options.root || {};
this.params = null;
this.filter = options && options.filter;
this.label = options && options.label;
this._setRoot(rootNode);
this.attachTo(target, position);
this._show();
this.addClass('aladin-dark-theme')
}
_setRoot(root) {
if (root) {
root.label = new Icon({
size: "small",
url: folderIconUrl,
monochrome: true,
cssStyle: {'display': 'inline-block'}
}).element().outerHTML;
}
this.root = root;
this._createDOM(root);
}
_createDOM(node) {
if (!node) {
return;
}
this.el.innerHTML = "";
this.curNode = node;
// create the parent directory
let curNode = this.curNode;
let directoryLinks = []
var levelsOfParenty = 0;
while (curNode) {
let parentLinkEl = document.createElement('a');
parentLinkEl.innerHTML = curNode.label;
let curLevel = levelsOfParenty;
parentLinkEl.addEventListener('click', () => {
this.navigate(curLevel)
})
levelsOfParenty += 1;
parentLinkEl.classList.add("aladin-link");
directoryLinks.push(parentLinkEl);
curNode = curNode.parent;
}
directoryLinks.reverse()
let directoryListEl = document.createElement('div');
directoryListEl.classList.add('aladin-directory-path');
directoryListEl.style.display = "inline-block"
for (var link of directoryLinks) {
directoryListEl.appendChild(link);
// root node
let spanSplitEl = document.createElement('span')
spanSplitEl.innerText = ' \/ '
directoryListEl.appendChild(spanSplitEl)
}
this.el.appendChild(directoryListEl)
let listElt = document.createElement('ul');
for (const label of Object.keys(node).sort()) {
if (label !== 'parent' && label !== "label") {
let elt = document.createElement('li');
// points towards the parent node
let child = node[label];
let isLeaf = typeof child === "object" && 'ID' in child;
if (isLeaf) {
if(this.params && this.filter && !this.filter(child, this.params)) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
elt.innerHTML = this.label(child);
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
let numFilteringMatching = this.numChildMatchingFilter(child, true);
let numTotal = this.numChildMatchingFilter(child, false);
let name = label;
elt.innerHTML = name + ` (${numFilteringMatching}/${numTotal})`
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
}
elt.style.color = this.hasChildLocatedInFov(child) ? 'yellowgreen' : 'orange';
child.label = label;
child.parent = node;
elt.for = label;
elt.classList.add("aladin-link");
elt.addEventListener('click', (e) => {
if (isLeaf) {
this.click(child)
} else {
// not leaf
this._createDOM(child);
}
})
listElt.appendChild(elt)
}
}
this.el.appendChild(listElt);
}
setHierarchy(root) {
this._setRoot(root)
}
navigate(numOfLevels) {
let curNode = this.curNode;
while (curNode && curNode.parent && numOfLevels >= 1) {
numOfLevels -= 1;
curNode = curNode.parent;
}
this._createDOM(curNode)
}
highlightNodes(highlight) {
this.highlight = highlight
let elts = this.el.querySelectorAll("li");
let i = 0;
for (const label of Object.keys(this.curNode).sort()) {
if (label !== 'parent' && label !== "label") {
let elt = elts[i];
i += 1;
// points towards the parent node
let child = this.curNode[label];
let isLeaf = typeof child === "object" && 'ID' in child;
if (isLeaf) {
// Check if its ID is found in the view
if (this.highlight) {
elt.style.color = this.highlight.includes(child.ID) ? 'yellowgreen' : 'orange';
}
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
elt.style.color = this.hasChildLocatedInFov(child) ? 'yellowgreen' : 'orange';
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
let numFilteringMatching = this.numChildMatchingFilter(child, true);
let numTotal = this.numChildMatchingFilter(child, false);
let name = elt.innerText.split('(');
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
}
}
}
}
// Set params to null, undefined or {} to disable the filtering
triggerFilter(params) {
if (params && params.title) {
params.title = params.title.toLowerCase()
}
this.params = params;
let elts = this.el.querySelectorAll("li");
let i = 0;
for (const label of Object.keys(this.curNode).sort()) {
if (label !== 'parent' && label !== "label") {
let elt = elts[i];
i += 1;
// points towards the parent node
let child = this.curNode[label];
let isLeaf = typeof child === "object" && 'ID' in child;
if (isLeaf) {
if(this.params && this.filter && !this.filter(child, this.params)) {
elt.style.display = "none"
} else {
elt.style.display = "block"
}
} else {
// we see a parent, we must determine:
// * its color: he has at least 1 child inside the FoV => green
// * the number of children matching the filter params
let numFilteringMatching = this.numChildMatchingFilter(child, true);
let numTotal = this.numChildMatchingFilter(child, false);
let name = elt.innerText.split('(');
elt.innerHTML = name[0] + ` (${numFilteringMatching}/${numTotal})`
if (numFilteringMatching == 0) {
elt.style.display = "none";
} else {
elt.style.display = "block";
}
}
}
}
}
hasChildLocatedInFov(node) {
if (!this.highlight) {
return false;
}
if (typeof node !== "object") {
return false;
}
let isLeaf = typeof node === "object" && 'ID' in node;
if (isLeaf) {
if (this.highlight.includes(node.ID)) {
return true;
}
} else {
for (const label of Object.keys(node).sort()) {
if (label === "parent")
continue;
let child = node[label];
if (child && this.hasChildLocatedInFov(child)) {
return true;
}
}
}
return false;
}
numChildMatchingFilter(node, filtering) {
if (typeof node !== "object") {
return 0;
}
let isLeaf = typeof node === "object" && 'ID' in node;
if (isLeaf) {
if (!filtering || (this.params && this.filter && this.filter(node, this.params))) {
return 1;
} else {
return 0;
}
} else {
let num = 0;
for (const label of Object.keys(node).sort()) {
if (label === "parent")
continue;
let child = node[label];
if (child) {
num += this.numChildMatchingFilter(child, filtering);
}
}
return num;
}
}
}

View File

@@ -80,7 +80,7 @@ export class SAMPConnector {
let params = message["samp.params"];
const {url, name} = params;
const image = aladin.createImageFITS(url, {name}, (e) => window.alert(e));
const image = aladin.createImageFITS(url, {name}, undefined, (e) => window.alert(e));
aladin.setOverlayImageLayer(image, name);
};
@@ -92,6 +92,8 @@ export class SAMPConnector {
let url = params['url'];
let name = params['name'] || id;
console.log(id, url, name)
A.catalogFromURL(
url,
{name, onClick: 'showTable'},
@@ -99,7 +101,7 @@ export class SAMPConnector {
(catalog) => {
aladin.addCatalog(catalog)
},
(e) => window.alert(e)
(e) => window.alert(e),
);
};