Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3476c535e6 | ||
|
|
af3f844bcc | ||
|
|
6dc1722cd2 | ||
|
|
9a47a6b11d | ||
|
|
543fc7ef8e | ||
|
|
1780d6c55d | ||
|
|
abb75ddb47 | ||
|
|
d29d900354 | ||
|
|
3a6455e018 | ||
|
|
fb0a562b09 | ||
|
|
19f06fe0bf | ||
|
|
2fed96e2a4 | ||
|
|
5f5fd7b555 | ||
|
|
bdb4f4aa5b | ||
|
|
5567b14b6a | ||
|
|
bf290e13e4 | ||
|
|
f41765adcd | ||
|
|
cf8c793312 | ||
|
|
abd97ffade | ||
|
|
55cdf454b7 | ||
|
|
a7c412088c | ||
|
|
887284da23 | ||
|
|
ff6399471c | ||
|
|
0861294c66 | ||
|
|
7aab92cb54 | ||
|
|
c8a75b98db | ||
|
|
47cbcee65a | ||
|
|
9d29a4f500 | ||
|
|
ab5019603b | ||
|
|
5a9561a13a |
2
.gitignore
vendored
@@ -39,6 +39,8 @@ jsdoc-make-responsivedocs
|
||||
test-results/
|
||||
playwright-report/
|
||||
|
||||
tests/run-examples.spec.ts-snapshots
|
||||
|
||||
## packaged tar obtained from the cmd: npm run predeploy or npm run deploy
|
||||
aladin-lite*.tgz
|
||||
# folder where the generated bundle lies
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
### 3.8.0
|
||||
|
||||
|
||||
* [fix] Fix shape hover colors when overlay uses named color (increaseBrightness() accepts names now) <https://github.com/cds-astro/aladin-lite/issues/355>
|
||||
* [feat] Selected and hovered items (shapes, sources, ...) are rendered at last. <https://github.com/cds-astro/aladin-lite/issues/337>
|
||||
* [feat] Add selectionLineWidth option for shapes and catalogs. <https://github.com/cds-astro/aladin-lite/pull/354>
|
||||
* [fix] horizontal/vertical overlay lines appearing correctly <https://github.com/cds-astro/aladin-lite/issues/334>
|
||||
* [fix] layer opacity restored when switching from not visible to visible <https://github.com/cds-astro/aladin-lite/issues/332>
|
||||
* [feat] dark/light mode for the interface
|
||||
|
||||
@@ -8,8 +8,8 @@
|
||||
"dateModified": "2023-01-31",
|
||||
"issueTracker": "https://github.com/cds-astro/aladin-lite/issues",
|
||||
"name": "Aladin Lite",
|
||||
"version": "3.7.3-beta",
|
||||
"softwareVersion": "3.7.3-beta",
|
||||
"version": "3.8.1",
|
||||
"softwareVersion": "3.8.1",
|
||||
"description": "An astronomical HiPS visualizer in the browser.",
|
||||
"identifier": "10.5281/zenodo.7638833",
|
||||
"applicationCategory": "Astronomy, Visualization",
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
A.init.then(() => {
|
||||
let aladin = A.aladin('#aladin-lite-div', {target: "23 28 32.46 -00 10 38.9", fov: 4, projection: "AIT"});
|
||||
let aladin = A.aladin('#aladin-lite-div', {survey: ["lksldf", 'ljkjsdf', "https://alasky.cds.unistra.fr/HIPS3D/LGLBSHI16"], 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);
|
||||
//let hsc = aladin.newImageSurvey("P/HSC/DR2/deep/g", {colormap:"Purples", imgFormat: "fits"});
|
||||
//aladin.setBaseImageLayer(hsc);
|
||||
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="aladin-lite-div" style="width: 1024px; height: 768px"></div>
|
||||
<div id="aladin-lite-div" style="width: 200px; height: 768px"></div>
|
||||
<script>let aladin;</script>
|
||||
<script type="module">
|
||||
import A from '../src/js/A.js';
|
||||
@@ -14,7 +14,6 @@
|
||||
target: "M31",
|
||||
fov: 89.78,
|
||||
showContextMenu: true,
|
||||
fullScreen: true,
|
||||
showSimbadPointerControl: true,
|
||||
showShareControl: true,
|
||||
showSettingsControl: true,
|
||||
|
||||
@@ -23,6 +23,9 @@ Image Opacity: <br/> <input id="slider" type="range" value=1 min=0 max=1 step=0.
|
||||
aladin = A.aladin('#aladin-lite-div', {target: "0 0", cooFrame: "gal"});
|
||||
|
||||
var callback = function(ra, dec, fov) {
|
||||
aladin.gotoRaDec(ra, dec)
|
||||
aladin.setFov(fov)
|
||||
|
||||
A.catalogFromSimbad( {ra: ra, dec: dec} , 1 , {shape: 'circle', color: '#5d5', onClick: 'showTable'}, (cat) => {
|
||||
aladin.addCatalog(cat)
|
||||
});
|
||||
@@ -37,16 +40,14 @@ Image Opacity: <br/> <input id="slider" type="range" value=1 min=0 max=1 step=0.
|
||||
aladin.setBaseImageLayer('P/Mellinger/color');
|
||||
|
||||
//let fits = aladin.displayFITS('http://goldmine.mib.infn.it/data//B/fits/A04_VC1316_ooooog.fits', 'overlay');
|
||||
let jpg = aladin.displayJPG(
|
||||
aladin.setOverlayImageLayer(A.image(
|
||||
// the JPG to transform to HiPS
|
||||
'https://owncloud.tuebingen.mpg.de/index.php/s/sdxfNgcEaaXoBp7/download/nightskycam3_2025_08_07_05_17_30_healpix1024_red.fits',
|
||||
'https://www.astropix.org/archive/esahubble/potm2602b/esahubble_potm2602b_1024.jpg',
|
||||
// no options
|
||||
{
|
||||
transparency: 1.0,
|
||||
successCallback: callback
|
||||
},
|
||||
// A callback fn once the overlay is set
|
||||
callback
|
||||
);
|
||||
));
|
||||
|
||||
$('input[name=survey]').change(function() {
|
||||
let surveyName = $(this).val();
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
import A from '../src/js/A.js';
|
||||
A.init.then(() => {
|
||||
var aladin = A.aladin('#aladin-lite-div', {showContextMenu: true, target: '05 37 58 +08 17 35', fov: 12, backgroundColor: 'rgb(120, 0, 0)'});
|
||||
|
||||
aladin.on('stackChanged', function(state) {
|
||||
console.log(state)
|
||||
});
|
||||
var cat = A.catalog({sourceSize: 20, onClick: (s) => {console.log("kjk", s)}});
|
||||
aladin.addCatalog(cat);
|
||||
cat.addSources([A.source(83.784490, 9.934156, {name: 'Meissa'}), A.source(88.792939, 7.407064, {name: 'Betelgeuse'}), A.source(81.282764, 6.349703, {name: 'Bellatrix'})]);
|
||||
@@ -68,10 +72,6 @@
|
||||
console.log(pos)
|
||||
});
|
||||
|
||||
aladin.on('stackChanged', function(state) {
|
||||
console.log(state)
|
||||
});
|
||||
|
||||
cat.sources[0].actionClicked();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
A.init.then(() => {
|
||||
// Start up Aladin Lite
|
||||
aladin = A.aladin('#aladin-lite-div', {target: 'M 1', fov: 0.2, showContextMenu: true, fullScreen: true});
|
||||
|
||||
var overlay = A.graphicOverlay({color: 'purple', lineWidth: 3, lineDash: [2, 2]});
|
||||
aladin.addOverlay(overlay);
|
||||
overlay.addFootprints([
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<div id="aladin-lite-div" style="width: 768px; height: 512px"></div>
|
||||
<script>let aladin;</script>
|
||||
<script type="module">
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
<head>
|
||||
</head>
|
||||
<body>
|
||||
<div id="al1" style="width: 500px; height: 500px">
|
||||
<div id="al1" style="width: 512px; height: 512px">
|
||||
</div>
|
||||
<div id="al2" style="width: 500px; height: 500px">
|
||||
<div id="al2" style="width: 512px; height: 512px">
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
@@ -15,7 +15,7 @@
|
||||
A.init.then(() => {
|
||||
// Start up Aladin Lite
|
||||
al1 = A.aladin('#al1', {target: 'M51', fov: 0.3, survey: 'P/DSS2/color', fullScreen: false, showContextMenu: true, showShareControl: true});
|
||||
al2 = A.aladin('#al2', {target: 'M51', fov: 180, survey: 'P/PanSTARRS/DR1/z', fullScreen: false, showContextMenu: true, showShareControl: true});
|
||||
al2 = A.aladin('#al2', {target: 'M51', fov: 180, survey: 'P/PanSTARRS/DR1/z', showColorPickerControl: true, fullScreen: false, showContextMenu: true, showShareControl: true});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
let aladin;
|
||||
A.init.then(() => {
|
||||
aladin = A.aladin('#aladin-lite-div', {survey: 'data/hips/CDS_P_DSS2_color', target: '05 34 30.89 +22 00 52.9', fov: 0.3, log: false, showSettingsControl: true});
|
||||
var cat = A.catalogFromURL('data/votable/labels.vot', {sourceSize:12, color: '#cc99bb', displayLabel: true, labelColumn: 'main_id', labelColor: '#ae4', labelFont: '9px sans-serif'});
|
||||
var cat = A.catalogFromURL('data/votable/labels.vot', {sourceSize:20, shape: 'cross', color: '#cc99bb', displayLabel: true, labelColumn: 'main_id', labelColor: '#ae4', labelFont: '9px sans-serif'});
|
||||
aladin.addCatalog(cat);
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
expandLayersControl: true,
|
||||
hipsList: [
|
||||
// High energy (Gamma and X-rays)
|
||||
'https://alasky.cds.unistra.fr/HIPS3D/GalfaHI',
|
||||
'CDS/P/HGPS/Flux',
|
||||
'CDS/P/Fermi/5',
|
||||
'CDS/P/Fermi/4',
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
|
||||
<meta name="viewport" content="width=device-width, height=device-height, maximum-scale=1.0, initial-scale=1.0, user-scalable=no">
|
||||
<script type="text/javascript" src="./../dist/aladin.umd.cjs" charset="utf-8"></script>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="aladin-lite-div" style="width: 500px; height: 500px"></div>
|
||||
|
||||
<script type="text/javascript" src="./../dist/aladin.umd.cjs" charset="utf-8"></script>
|
||||
<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, fov: 180, showContextMenu: true});
|
||||
aladin = A.aladin('#aladin-lite-div', {fullScreen: true, showCooLocation: true, showSettingsControl: 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')) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"homepage": "https://aladin.cds.unistra.fr/",
|
||||
"name": "aladin-lite",
|
||||
"type": "module",
|
||||
"version": "3.8.0-beta",
|
||||
"version": "3.8.2",
|
||||
"description": "An astronomical HiPS visualizer in the browser",
|
||||
"author": "Thomas Boch and Matthieu Baumann",
|
||||
"license": "GPL-3",
|
||||
@@ -48,7 +48,7 @@
|
||||
"test:build": "cd src/core && cargo test --release --features webgl2",
|
||||
"test:playwright": "npx playwright test",
|
||||
"test:update-snapshots": "npx playwright test --update-snapshots",
|
||||
"doc": "jsdoc -c jsdoc.json src/js src/js/shapes src/js/libs/astro && cp aladin-logo.png docs/ && cp jsdoc-custom-style.css docs/ && cp jsdoc-make-responsive.js docs/",
|
||||
"doc": "jsdoc -c jsdoc.json src/js src/js/gui src/js/shapes src/js/libs/astro && cp aladin-logo.png docs/ && cp jsdoc-custom-style.css docs/ && cp jsdoc-make-responsive.js docs/",
|
||||
"doc:dev": "npm run doc && open docs/index.html",
|
||||
"analyze": "vite build --mode analyze"
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ name = "aladin-lite"
|
||||
description = "Aladin Lite v3 introduces a new graphical engine written in Rust with the use of WebGL"
|
||||
license = "BSD-3-Clause"
|
||||
repository = "https://github.com/cds-astro/aladin-lite"
|
||||
version = "3.8.0-beta"
|
||||
version = "3.8.2"
|
||||
authors = [ "baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr",]
|
||||
edition = "2018"
|
||||
|
||||
@@ -49,8 +49,7 @@ version = "0.7.3"
|
||||
|
||||
[dependencies.moclib]
|
||||
package = "moc"
|
||||
git = "https://github.com/cds-astro/cds-moc-rust"
|
||||
branch = "main"
|
||||
version = "0.19.1"
|
||||
|
||||
[dependencies.serde]
|
||||
version = "^1.0.183"
|
||||
@@ -64,7 +63,7 @@ path = "./al-api"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
features = [ "console", "CssStyleDeclaration", "Document", "Element", "HtmlCollection", "CustomEvent", "CustomEventInit", "HtmlElement", "HtmlImageElement", "HtmlCanvasElement", "Blob", "ImageBitmap", "ImageData", "CanvasRenderingContext2d", "WebGlBuffer", "WebGlContextAttributes", "WebGlFramebuffer", "WebGlProgram", "WebGlShader", "WebGlUniformLocation", "WebGlTexture", "WebGlActiveInfo", "Headers", "Window", "Request", "RequestInit", "RequestMode", "RequestCredentials", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream", "File", "FileList",]
|
||||
features = [ "console", "CssStyleDeclaration", "Document", "Element", "HtmlCollection", "CustomEvent", "CustomEventInit", "HtmlElement", "HtmlImageElement", "HtmlCanvasElement", "Blob", "Url", "ImageBitmap", "ImageData", "CanvasRenderingContext2d", "WebGlBuffer", "WebGlContextAttributes", "WebGlFramebuffer", "WebGlProgram", "WebGlShader", "WebGlUniformLocation", "WebGlTexture", "WebGlActiveInfo", "Headers", "Window", "Request", "RequestInit", "RequestMode", "RequestCredentials", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream", "File", "FileList", "OffscreenCanvas", "OffscreenCanvasRenderingContext2d", "Worker", "WorkerOptions", "WorkerType",]
|
||||
|
||||
[dev-dependencies.image-decoder]
|
||||
package = "image"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "al-api"
|
||||
version = "3.8.0"
|
||||
version = "3.8.2"
|
||||
authors = ["baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "al-core"
|
||||
version = "3.8.0"
|
||||
version = "3.8.2"
|
||||
authors = ["baumannmatthieu0@gmail.com", "matthieu.baumann@astro.unistra.fr"]
|
||||
edition = "2018"
|
||||
|
||||
@@ -35,43 +35,8 @@ webgl2 = [
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.56"
|
||||
features = [
|
||||
'console',
|
||||
'CssStyleDeclaration',
|
||||
'Document',
|
||||
'Element',
|
||||
'HtmlCollection',
|
||||
'HtmlElement',
|
||||
'HtmlImageElement',
|
||||
'HtmlCanvasElement',
|
||||
'Blob',
|
||||
'ImageBitmap',
|
||||
'ImageData',
|
||||
features = [ "console", "CssStyleDeclaration", "Document", "Element", "HtmlCollection", "CustomEvent", "CustomEventInit", "HtmlElement", "HtmlImageElement", "HtmlCanvasElement", "Blob", "ImageBitmap", "ImageData", "CanvasRenderingContext2d", "WebGlBuffer", "WebGlContextAttributes", "WebGlFramebuffer", "WebGlProgram", "WebGlShader", "WebGlUniformLocation", "WebGlTexture", "WebGlActiveInfo", "Headers", "Window", "Request", "RequestInit", "RequestMode", "RequestCredentials", "Response", "XmlHttpRequest", "XmlHttpRequestResponseType", "PerformanceTiming", "Performance", "Url", "ReadableStream", "File", "FileList", "OffscreenCanvas", "OffscreenCanvasRenderingContext2d", "Worker", "WorkerOptions", "WorkerType"]
|
||||
|
||||
'CanvasRenderingContext2d',
|
||||
|
||||
'WebGlBuffer',
|
||||
'WebGlContextAttributes',
|
||||
'WebGlFramebuffer',
|
||||
'WebGlProgram',
|
||||
'WebGlShader',
|
||||
'WebGlUniformLocation',
|
||||
'WebGlTexture',
|
||||
'WebGlActiveInfo',
|
||||
|
||||
'Window',
|
||||
'Request',
|
||||
'RequestInit',
|
||||
'RequestMode',
|
||||
'Response',
|
||||
'XmlHttpRequest',
|
||||
'XmlHttpRequestResponseType',
|
||||
'PerformanceTiming',
|
||||
'Performance',
|
||||
'Url',
|
||||
'File',
|
||||
'FileList'
|
||||
]
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 's'
|
||||
|
||||
@@ -58,11 +58,23 @@ impl Texture3D {
|
||||
pixel_type: F::PIXEL_TYPE,
|
||||
})));
|
||||
|
||||
Ok(Texture3D {
|
||||
let s = Texture3D {
|
||||
texture,
|
||||
gl: gl.clone(),
|
||||
metadata,
|
||||
})
|
||||
};
|
||||
let voxel_count = F::NUM_CHANNELS * (width as usize) * (height as usize) * (depth as usize);
|
||||
let zeros = vec![0xff; voxel_count];
|
||||
s.bind().tex_sub_image_3d_with_opt_u8_array(
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
Some(&zeros[..]),
|
||||
);
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
pub fn generate_mipmap(&self) {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "al-task-exec"
|
||||
version = "0.1.0"
|
||||
authors = ["Matthieu Baumann <matthieu.baumann@astro.unistra.fr>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.5"
|
||||
futures-task = "0.3.5"
|
||||
wasm-bindgen = "0.2.79"
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.40"
|
||||
features = [
|
||||
'Document',
|
||||
'Window',
|
||||
'PerformanceTiming',
|
||||
'Performance',
|
||||
]
|
||||
@@ -1,338 +0,0 @@
|
||||
use {
|
||||
futures::{
|
||||
future::FutureExt,
|
||||
task::{waker_ref, ArcWake},
|
||||
},
|
||||
std::{
|
||||
future::Future,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
},
|
||||
};
|
||||
|
||||
struct Thread {
|
||||
_thread: std::thread::Thread,
|
||||
}
|
||||
|
||||
use std::thread;
|
||||
thread_local! {
|
||||
static CURRENT_THREAD_NOTIFY: Arc<Thread> = Arc::new(Thread {
|
||||
_thread: thread::current(),
|
||||
});
|
||||
}
|
||||
|
||||
impl ArcWake for Thread {
|
||||
fn wake_by_ref(_arc_self: &Arc<Self>) {}
|
||||
}
|
||||
|
||||
use std::cell::RefCell;
|
||||
/// `Spawner` spawns new futures onto the task channel.
|
||||
use std::collections::VecDeque;
|
||||
use std::pin::Pin;
|
||||
use std::rc::{Rc, Weak};
|
||||
|
||||
use std::collections::HashMap;
|
||||
// A queue that contains keyed element
|
||||
struct KeyedVecDeque<K, V>
|
||||
where
|
||||
K: Hash + Eq + Clone,
|
||||
{
|
||||
keys: VecDeque<K>,
|
||||
values: HashMap<K, V>,
|
||||
}
|
||||
|
||||
use std::hash::Hash;
|
||||
impl<K, V> KeyedVecDeque<K, V>
|
||||
where
|
||||
K: Hash + Eq + Clone,
|
||||
{
|
||||
fn new() -> Self {
|
||||
let keys = VecDeque::new();
|
||||
let values = HashMap::new();
|
||||
Self { keys, values }
|
||||
}
|
||||
|
||||
fn push_front(&mut self, key: K, value: V) {
|
||||
self.keys.push_front(key.clone());
|
||||
self.values.insert(key, value);
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) -> Option<(K, V)> {
|
||||
if self.keys.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let mut v = None;
|
||||
while !self.keys.is_empty() && v.is_none() {
|
||||
let k = self.keys.pop_back().unwrap();
|
||||
v = self.values.remove(&k).map(|v| (k, v));
|
||||
}
|
||||
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, k: &K) -> Option<V> {
|
||||
self.values.remove(k)
|
||||
}
|
||||
}
|
||||
|
||||
type Incoming<K, T> = RefCell<KeyedVecDeque<K, Pin<Box<dyn Future<Output = T> + 'static>>>>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Spawner<K, T>
|
||||
where
|
||||
K: Hash + Eq + Clone,
|
||||
{
|
||||
tasks: Weak<Incoming<K, T>>,
|
||||
}
|
||||
|
||||
impl<K, T> Spawner<K, T>
|
||||
where
|
||||
K: Hash + Eq + Clone,
|
||||
{
|
||||
pub fn spawn(&mut self, key: K, future: impl Future<Output = T> + 'static) {
|
||||
let future = future.boxed_local();
|
||||
self.tasks
|
||||
.upgrade() // convert to Rc
|
||||
.unwrap()
|
||||
.borrow_mut() // Push the new task to the front of the queue
|
||||
.push_front(key, future);
|
||||
}
|
||||
}
|
||||
|
||||
/// Task executor that receives tasks off of a channel and runs them.
|
||||
pub struct Executor<K, T>
|
||||
where
|
||||
K: Hash + Eq + Clone,
|
||||
{
|
||||
tasks: Rc<Incoming<K, T>>,
|
||||
spawner: Spawner<K, T>,
|
||||
}
|
||||
|
||||
impl<K, T> Default for Executor<K, T>
|
||||
where
|
||||
K: Hash + Eq + Clone,
|
||||
{
|
||||
fn default() -> Self {
|
||||
let tasks = Rc::new(RefCell::new(KeyedVecDeque::new()));
|
||||
let spawner = Spawner {
|
||||
tasks: Rc::downgrade(&tasks),
|
||||
};
|
||||
Executor { tasks, spawner }
|
||||
}
|
||||
}
|
||||
|
||||
impl<K, T> Executor<K, T>
|
||||
where
|
||||
K: Hash + Eq + Clone + Sized,
|
||||
{
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn spawner(&mut self) -> &mut Spawner<K, T> {
|
||||
&mut self.spawner
|
||||
}
|
||||
|
||||
pub fn run(&mut self, timeout: f32) -> Vec<T> {
|
||||
let mut results = vec![];
|
||||
|
||||
CURRENT_THREAD_NOTIFY.with(|thread| {
|
||||
// Create a `LocalWaker` from the current thread itself
|
||||
let waker = waker_ref(thread);
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
// Take all the task available from the channel
|
||||
// Exit the loop when either the channel is disconnected or
|
||||
// there are no tasks available to process.
|
||||
let mut tasks = self.tasks.borrow_mut();
|
||||
|
||||
let window = web_sys::window().expect("should have a window in this context");
|
||||
let performance = window
|
||||
.performance()
|
||||
.expect("performance should be available");
|
||||
let start = performance.now() as f32;
|
||||
|
||||
while let Some((k, mut task)) = tasks.pop_back() {
|
||||
// Take the future, and if it has not yet completed (is still Some),
|
||||
// poll it in an attempt to complete it.
|
||||
|
||||
// We store `Pin<Box<dyn Future<Output = T> + 'static>>`.
|
||||
// We can get a `Pin<&mut dyn Future + 'static>`
|
||||
// from it by calling the `Pin::as_mut` method.
|
||||
let r = task.as_mut().poll(&mut cx);
|
||||
match r {
|
||||
Poll::Pending => {
|
||||
// Wake up the task pending immediately
|
||||
cx.waker().clone().wake();
|
||||
// Reinsert not finished futures into the tasks queue
|
||||
tasks.push_front(k, task);
|
||||
}
|
||||
Poll::Ready(result) => {
|
||||
// If the future is completed, get the result
|
||||
// and return it to the user
|
||||
results.push(result);
|
||||
}
|
||||
}
|
||||
let now = performance.now() as f32;
|
||||
// Break the running if we exceed the timeout
|
||||
if (now - start) >= timeout {
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
results
|
||||
}
|
||||
|
||||
// Remove a task from the executor so that
|
||||
// it won't be polled anymore
|
||||
pub fn remove(&mut self, k: &K) {
|
||||
self.tasks.borrow_mut().remove(k);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Executor;
|
||||
use futures::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
// Define some futures to run concurrently on a single thread
|
||||
struct LearnTask(usize);
|
||||
impl LearnTask {
|
||||
fn new() -> Self {
|
||||
LearnTask(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for LearnTask {
|
||||
type Output = u8;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
|
||||
self.0 += 1;
|
||||
println!("I'm learning {}", self.0);
|
||||
|
||||
if self.0 == 10000 {
|
||||
Poll::Ready(0)
|
||||
} else {
|
||||
//cx.waker().clone().wake();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct DanceTask(usize);
|
||||
impl DanceTask {
|
||||
fn new() -> Self {
|
||||
DanceTask(0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for DanceTask {
|
||||
type Output = u8;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _cx: &mut Context) -> Poll<Self::Output> {
|
||||
self.0 += 1;
|
||||
println!("I'm dancing {}", self.0);
|
||||
|
||||
if self.0 == 10000 {
|
||||
Poll::Ready(0)
|
||||
} else {
|
||||
//cx.waker().clone().wake();
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
let mut executor = Executor::new();
|
||||
let spawner = executor.spawner();
|
||||
// Spawn a task to print before and after waiting on a timer.
|
||||
spawner.spawn("learning", async {
|
||||
println!("LEARN begin!");
|
||||
// Wait for our timer future to complete after two seconds.
|
||||
LearnTask::new().await;
|
||||
println!("LEARN done!");
|
||||
|
||||
10
|
||||
});
|
||||
spawner.spawn("dancing", async {
|
||||
println!("DANCE begin!");
|
||||
// Wait for our timer future to complete after two seconds.
|
||||
DanceTask::new().await;
|
||||
println!("DANCE done!");
|
||||
|
||||
10
|
||||
});
|
||||
|
||||
// Run the executor for a duration of 5 milliseconds
|
||||
executor.run(5_f32);
|
||||
}
|
||||
use futures::stream::Stream;
|
||||
|
||||
pub struct ParseTable {
|
||||
table: Vec<u32>,
|
||||
ready: bool,
|
||||
idx: u32,
|
||||
}
|
||||
|
||||
impl ParseTable {
|
||||
pub fn new(table: Vec<u32>) -> Self {
|
||||
let idx = 0;
|
||||
let ready = false;
|
||||
Self { table, idx, ready }
|
||||
}
|
||||
}
|
||||
|
||||
impl Stream for ParseTable {
|
||||
type Item = u32;
|
||||
|
||||
/// Attempt to resolve the next item in the stream.
|
||||
/// Returns `Poll::Pending` if not ready, `Poll::Ready(Some(x))` if a value
|
||||
/// is ready, and `Poll::Ready(None)` if the stream has completed.
|
||||
fn poll_next(mut self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
// Deserialize row by row.
|
||||
let len = self.table.len();
|
||||
let idx = self.idx as usize;
|
||||
while self.idx < len as u32 {
|
||||
if !self.ready {
|
||||
self.table[idx] += 10;
|
||||
self.ready = true;
|
||||
return Poll::Pending;
|
||||
} else {
|
||||
println!("{}", idx);
|
||||
let row = self.table[idx];
|
||||
|
||||
self.idx += 1;
|
||||
self.ready = false;
|
||||
return Poll::Ready(Some(row));
|
||||
}
|
||||
}
|
||||
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
use futures::stream::StreamExt; // for `next`
|
||||
#[test]
|
||||
fn it_works2() {
|
||||
let mut executor = Executor::new();
|
||||
let spawner = executor.spawner();
|
||||
// Spawn a task to print before and after waiting on a timer.
|
||||
spawner.spawn("parsing", async {
|
||||
println!("BEGIN parsing!");
|
||||
let mut stream = ParseTable::new((0..100000).collect());
|
||||
let mut results: Vec<u32> = vec![];
|
||||
while let Some(item) = stream.next().await {
|
||||
results.push(item);
|
||||
}
|
||||
println!("END parsing!");
|
||||
|
||||
10
|
||||
});
|
||||
|
||||
// Run the executor for a duration of 5 milliseconds
|
||||
executor.run(50_f32);
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@ pub struct App {
|
||||
//ui: GuiRef,
|
||||
shaders: ShaderManager,
|
||||
pub camera: CameraViewPort,
|
||||
worker: Worker,
|
||||
|
||||
downloader: Rc<RefCell<Downloader>>,
|
||||
tile_fetcher: TileFetcherQueue,
|
||||
@@ -105,6 +106,8 @@ pub struct App {
|
||||
|
||||
pub projection: ProjectionType,
|
||||
|
||||
cubic_tile_recv: async_channel::Receiver<WorkerResponse>,
|
||||
|
||||
// Async data receivers
|
||||
//img_send: async_channel::Sender<ImageLayer>,
|
||||
img_recv: async_channel::Receiver<ImageLayer>,
|
||||
@@ -199,8 +202,7 @@ impl App {
|
||||
|
||||
let (_, img_recv) = async_channel::unbounded::<ImageLayer>();
|
||||
let (ack_img_send, _) = async_channel::unbounded::<ImageParams>();
|
||||
|
||||
//let line_renderer = RasterizedLineRenderer::new(&gl)?;
|
||||
let (cubic_tile_send, cubic_tile_recv) = async_channel::unbounded::<WorkerResponse>();
|
||||
|
||||
let dist_dragging = 0.0;
|
||||
let time_start_dragging = Time::now();
|
||||
@@ -210,6 +212,48 @@ impl App {
|
||||
let browser_features_support = BrowserFeaturesSupport::new();
|
||||
|
||||
let vel_history = vec![];
|
||||
let worker = create_worker()?;
|
||||
// Send the ack to the js promise so that she finished
|
||||
let onmessage = Closure::<dyn FnMut(web_sys::MessageEvent)>::new(
|
||||
move |event: web_sys::MessageEvent| {
|
||||
let data = event.data();
|
||||
|
||||
let bytes_js = js_sys::Reflect::get(&data, &"bytes".into())
|
||||
.unwrap()
|
||||
.dyn_into::<js_sys::Uint8Array>()
|
||||
.expect("is not a uint8 buffer");
|
||||
|
||||
// Zero-copy clone from JS memory
|
||||
let mut bytes = vec![0u8; bytes_js.length() as usize];
|
||||
bytes_js.copy_to(&mut bytes);
|
||||
|
||||
let meta: WorkerResponseMeta = serde_wasm_bindgen::from_value(data).unwrap();
|
||||
|
||||
let response = WorkerResponse {
|
||||
bytes,
|
||||
tile_size: meta.tile_size,
|
||||
tile_depth: meta.tile_depth,
|
||||
cell: meta.cell,
|
||||
hips_cdid: meta.hips_cdid,
|
||||
};
|
||||
let c = cubic_tile_send.clone();
|
||||
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
c.send(response).await.unwrap_throw();
|
||||
})
|
||||
},
|
||||
);
|
||||
|
||||
worker.set_onmessage(Some(onmessage.as_ref().unchecked_ref()));
|
||||
|
||||
// 🚨 VERY IMPORTANT: prevent the closure from being dropped
|
||||
onmessage.forget();
|
||||
|
||||
gl.blend_func(
|
||||
WebGl2RenderingContext::SRC_ALPHA,
|
||||
WebGl2RenderingContext::ONE_MINUS_SRC_ALPHA,
|
||||
);
|
||||
|
||||
Ok(App {
|
||||
gl,
|
||||
//ui,
|
||||
@@ -247,6 +291,7 @@ impl App {
|
||||
time_mouse_high_vel,
|
||||
dragging,
|
||||
vel_history,
|
||||
worker,
|
||||
|
||||
prev_cam_position,
|
||||
out_of_fov,
|
||||
@@ -262,6 +307,7 @@ impl App {
|
||||
//img_send,
|
||||
img_recv,
|
||||
ack_img_send,
|
||||
cubic_tile_recv,
|
||||
|
||||
browser_features_support, //ack_img_recv,
|
||||
})
|
||||
@@ -543,6 +589,27 @@ impl App {
|
||||
})
|
||||
}
|
||||
|
||||
if let Ok(WorkerResponse {
|
||||
cell,
|
||||
hips_cdid,
|
||||
tile_size,
|
||||
tile_depth,
|
||||
bytes,
|
||||
..
|
||||
}) = self.cubic_tile_recv.try_recv()
|
||||
{
|
||||
if let Some(HiPS::D3(hips)) = self.layers.get_mut_hips_from_cdid(&hips_cdid) {
|
||||
hips.push_tile_from_png(
|
||||
&cell,
|
||||
bytes.into_boxed_slice(),
|
||||
(tile_size, tile_size, tile_depth),
|
||||
Time::now(),
|
||||
)?;
|
||||
}
|
||||
|
||||
self.request_redraw = true;
|
||||
}
|
||||
|
||||
let has_camera_moved = self.camera.has_moved();
|
||||
|
||||
{
|
||||
@@ -692,92 +759,40 @@ impl App {
|
||||
ImageType::ImageRgba8u {
|
||||
image: Bitmap { image, .. },
|
||||
} => {
|
||||
let document = web_sys::window()
|
||||
.unwrap_abort()
|
||||
.document()
|
||||
.unwrap_abort();
|
||||
let canvas = document
|
||||
.create_element("canvas")?
|
||||
.dyn_into::<web_sys::HtmlCanvasElement>()?;
|
||||
canvas.set_width(image.width());
|
||||
canvas.set_height(image.height());
|
||||
let context = canvas
|
||||
.get_context("2d")?
|
||||
.unwrap_abort()
|
||||
.dyn_into::<web_sys::CanvasRenderingContext2d>()?;
|
||||
// Get the data once for all for the whole image
|
||||
// This takes time so better do it once and not repeatly
|
||||
context.draw_image_with_image_bitmap(
|
||||
image, 0.0, 0.0,
|
||||
let msg = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&msg,
|
||||
&"bitmap".into(),
|
||||
image,
|
||||
)?;
|
||||
js_sys::Reflect::set(
|
||||
&msg,
|
||||
&"tileSize".into(),
|
||||
&JsValue::from_f64(*tile_size as f64),
|
||||
)?;
|
||||
js_sys::Reflect::set(
|
||||
&msg,
|
||||
&"tileDepth".into(),
|
||||
&JsValue::from_f64(*tile_depth as f64),
|
||||
)?;
|
||||
js_sys::Reflect::set(
|
||||
&msg,
|
||||
&"cell".into(),
|
||||
&serde_wasm_bindgen::to_value(&cell)
|
||||
.expect("Failed to serialize"),
|
||||
)?;
|
||||
|
||||
// Cut the png in several tile images. See page 3 of
|
||||
// https://aladin.cds.unistra.fr/java/DocTechHiPS3D.pdf
|
||||
let tile_depth = *tile_depth;
|
||||
let num_cols =
|
||||
(tile_depth as f32).sqrt().floor() as u32;
|
||||
let num_rows = ((tile_depth as f32)
|
||||
/ (num_cols as f32))
|
||||
.ceil()
|
||||
as u32;
|
||||
js_sys::Reflect::set(
|
||||
&msg,
|
||||
&"HiPS".into(),
|
||||
&JsValue::from_str(&tile.hips_cdid),
|
||||
)?;
|
||||
|
||||
let tile_size = *tile_size;
|
||||
// Transfer ownership (zero-copy)
|
||||
let transfer = js_sys::Array::of1(image);
|
||||
|
||||
let bytes = context
|
||||
.get_image_data(
|
||||
0_f64,
|
||||
0_f64,
|
||||
(num_cols * tile_size) as f64,
|
||||
(num_rows * tile_size) as f64,
|
||||
)?
|
||||
.data()
|
||||
.0;
|
||||
|
||||
let mut decoded_bytes = vec![
|
||||
0_u8;
|
||||
(tile_size * tile_size * tile_depth * 2)
|
||||
as usize
|
||||
];
|
||||
|
||||
let mut k = 0;
|
||||
let mut num_tiles_cropped = 0;
|
||||
for y in 0..num_rows {
|
||||
let sy = y * tile_size;
|
||||
|
||||
for x in 0..num_cols {
|
||||
let sx = x * tile_size;
|
||||
|
||||
for i in sy..(sy + tile_size) {
|
||||
for j in sx..(sx + tile_size) {
|
||||
let id_byte = (j + i
|
||||
* num_cols
|
||||
* tile_size)
|
||||
* 4;
|
||||
|
||||
decoded_bytes[k] =
|
||||
bytes[id_byte as usize];
|
||||
decoded_bytes[k + 1] =
|
||||
bytes[id_byte as usize + 3];
|
||||
k += 2;
|
||||
}
|
||||
}
|
||||
|
||||
num_tiles_cropped += 1;
|
||||
|
||||
if num_tiles_cropped == tile_depth {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if num_tiles_cropped == tile_depth {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
hips.push_tile_from_png(
|
||||
cell,
|
||||
decoded_bytes.into_boxed_slice(),
|
||||
(tile_size, tile_size, tile_depth),
|
||||
tile.request.time_request,
|
||||
self.worker.post_message_with_transfer(
|
||||
&msg, &transfer,
|
||||
)?;
|
||||
}
|
||||
ImageType::ImageRgb8u {
|
||||
@@ -1312,6 +1327,18 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_hips_frequency_window(&mut self, layer: &str) -> Result<[Freq; 2], JsValue> {
|
||||
let hips = self
|
||||
.layers
|
||||
.get_mut_hips_from_layer(layer)
|
||||
.ok_or_else(|| JsValue::from_str("Layer not found"))?;
|
||||
|
||||
match hips {
|
||||
HiPS::D2(_) => Err(JsValue::from_str("layer do not refers to a cube")),
|
||||
HiPS::D3(hips) => Ok(hips.get_freq_window()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn get_freq_from_hash(&mut self, layer: &str, hash: u64) -> Result<f64, JsValue> {
|
||||
let hips = self
|
||||
.layers
|
||||
@@ -1842,3 +1869,132 @@ impl App {
|
||||
self.rendering
|
||||
}
|
||||
}
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct WorkerResponse {
|
||||
#[serde(rename = "tileSize")]
|
||||
pub tile_size: u32,
|
||||
|
||||
#[serde(rename = "tileDepth")]
|
||||
pub tile_depth: u32,
|
||||
|
||||
pub bytes: Vec<u8>,
|
||||
|
||||
#[serde(rename = "HiPSCDid")]
|
||||
pub hips_cdid: String,
|
||||
|
||||
pub cell: HEALPixFreqCell,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct WorkerResponseMeta {
|
||||
#[serde(rename = "tileSize")]
|
||||
pub tile_size: u32,
|
||||
|
||||
#[serde(rename = "tileDepth")]
|
||||
pub tile_depth: u32,
|
||||
|
||||
#[serde(rename = "HiPSCDid")]
|
||||
pub hips_cdid: String,
|
||||
|
||||
pub cell: HEALPixFreqCell,
|
||||
}
|
||||
|
||||
use web_sys::{Worker, WorkerOptions};
|
||||
pub fn create_worker() -> Result<Worker, JsValue> {
|
||||
// JS source code of the worker
|
||||
let worker_source = r#"
|
||||
self.onmessage = (e) => {
|
||||
const { bitmap, tileDepth, tileSize, HiPS, cell } = e.data;
|
||||
|
||||
// Compute tiling layout
|
||||
const numCols = Math.floor(bitmap.width / tileSize);
|
||||
|
||||
// See HiPS3D doc
|
||||
const numRows = Math.ceil(tileDepth / numCols);
|
||||
|
||||
// Create OffscreenCanvas
|
||||
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
||||
const context = canvas.getContext("2d");
|
||||
|
||||
// Draw full image once
|
||||
context.drawImage(bitmap, 0, 0);
|
||||
|
||||
// Extract full region needed
|
||||
const imageData = context.getImageData(
|
||||
0,
|
||||
0,
|
||||
numCols * tileSize,
|
||||
numRows * tileSize
|
||||
);
|
||||
|
||||
const bytes = imageData.data; // Uint8ClampedArray (RGBA)
|
||||
|
||||
// Allocate output buffer (2 bytes per pixel)
|
||||
const decodedBytes = new Uint8Array(
|
||||
tileSize * tileSize * tileDepth * 2
|
||||
);
|
||||
|
||||
let k = 0;
|
||||
let numTilesCropped = 0;
|
||||
|
||||
for (let y = 0; y < numRows; y++) {
|
||||
const sy = y * tileSize;
|
||||
|
||||
for (let x = 0; x < numCols; x++) {
|
||||
const sx = x * tileSize;
|
||||
|
||||
for (let i = sy; i < sy + tileSize; i++) {
|
||||
for (let j = sx; j < sx + tileSize; j++) {
|
||||
|
||||
const idByte = (j + i * numCols * tileSize) * 4;
|
||||
|
||||
// Copy R channel
|
||||
decodedBytes[k] = bytes[idByte];
|
||||
|
||||
// Copy A channel
|
||||
decodedBytes[k + 1] = bytes[idByte + 3];
|
||||
|
||||
k += 2;
|
||||
}
|
||||
}
|
||||
|
||||
numTilesCropped++;
|
||||
|
||||
if (numTilesCropped === tileDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (numTilesCropped === tileDepth) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
self.postMessage(
|
||||
{
|
||||
tileSize,
|
||||
tileDepth,
|
||||
HiPSCDid: HiPS,
|
||||
cell,
|
||||
bytes: decodedBytes,
|
||||
},
|
||||
[decodedBytes.buffer] // transfer of ownership
|
||||
);
|
||||
};
|
||||
"#;
|
||||
|
||||
// Create Blob
|
||||
let parts = js_sys::Array::of1(&JsValue::from_str(worker_source));
|
||||
let blob = web_sys::Blob::new_with_str_sequence(&parts)?;
|
||||
|
||||
// Create object URL
|
||||
let url = web_sys::Url::create_object_url_with_blob(&blob)?;
|
||||
|
||||
let opts = WorkerOptions::new();
|
||||
|
||||
let worker = Worker::new_with_options(&url, &opts)?;
|
||||
Ok(worker)
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
|
||||
pub struct HEALPixCell(pub u8, pub u64);
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -492,7 +492,7 @@ impl Ord for HEALPixCell {
|
||||
}
|
||||
|
||||
/// A simple object describing a cubic tile of a HiPS3D
|
||||
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
|
||||
#[derive(Eq, Hash, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize)]
|
||||
pub struct HEALPixFreqCell {
|
||||
pub hpx: HEALPixCell,
|
||||
pub f_hash: u64,
|
||||
|
||||
@@ -410,6 +410,13 @@ impl WebClient {
|
||||
self.app.get_hips_frequency(&layer)
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = getFreqWindow)]
|
||||
pub fn get_hips_frequency_window(&mut self, layer: String) -> Result<Vec<f32>, JsValue> {
|
||||
let fw = self.app.get_hips_frequency_window(&layer)?;
|
||||
|
||||
Ok(vec![fw[0].0 as f32, fw[1].0 as f32])
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = freq2hash)]
|
||||
pub fn get_freq_hash(&mut self, layer: String, freq: f64) -> Result<u64, JsValue> {
|
||||
self.app.get_freq_hash(&layer, freq)
|
||||
|
||||
@@ -579,8 +579,14 @@ use std::fmt::Display;
|
||||
impl Display for Angle<f64> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self.fmt {
|
||||
AngleFormatter::Sexagesimal { prec, plus, hours } => {
|
||||
AngleFormatter::Sexagesimal {
|
||||
mut prec,
|
||||
plus,
|
||||
hours,
|
||||
} => {
|
||||
let unit = if hours {
|
||||
// to preserve the same angular precision the new prec is p+log10(15) ≈ p+1.18 < p+2
|
||||
prec += 2;
|
||||
self.to_hours()
|
||||
} else {
|
||||
self.to_degrees()
|
||||
|
||||
@@ -215,7 +215,7 @@ impl ProjetedGrid {
|
||||
let start_lon = bbox.lon_min() - (bbox.lon_min() % step_lon);
|
||||
let mut stop_lon = bbox.lon_max();
|
||||
if bbox.all_lon() {
|
||||
stop_lon -= 1e-3;
|
||||
stop_lon += 1e-3;
|
||||
}
|
||||
|
||||
let mut meridians = vec![];
|
||||
|
||||
@@ -309,6 +309,19 @@ impl Cursor {
|
||||
fn get_freq(&self) -> Freq {
|
||||
self.freq
|
||||
}
|
||||
|
||||
fn get_freq_window(&self) -> [Freq; 2] {
|
||||
let FrequencyWindow {
|
||||
window_pixel_hash: f_hash_val,
|
||||
pixel_depth,
|
||||
..
|
||||
} = self.get_window_frequency_range();
|
||||
|
||||
let f0 = Freq::from_hash_with_order(f_hash_val.start, pixel_depth);
|
||||
let f1 = Freq::from_hash_with_order(f_hash_val.end, pixel_depth);
|
||||
|
||||
[f0, f1]
|
||||
}
|
||||
}
|
||||
|
||||
use super::HpxTileBuffer;
|
||||
@@ -724,6 +737,10 @@ impl HiPS3D {
|
||||
self.cursor.get_freq()
|
||||
}
|
||||
|
||||
pub fn get_freq_window(&self) -> [Freq; 2] {
|
||||
self.cursor.get_freq_window()
|
||||
}
|
||||
|
||||
fn recompute_vertices(&mut self, camera: &CameraViewPort, proj: &ProjectionType) {
|
||||
self.cells.clear();
|
||||
|
||||
|
||||
@@ -377,7 +377,7 @@ impl HpxFreqTex {
|
||||
// size of the cube
|
||||
size: (u32, u32, u32),
|
||||
) -> Result<(), JsValue> {
|
||||
let cubic_tile = ImageBuffer::<R16I>::new(decoded_bytes, size.0, size.1, size.2);
|
||||
let cubic_tile = ImageBuffer::<R8U>::new(decoded_bytes, size.0, size.1, size.2);
|
||||
|
||||
cubic_tile.insert_into_3d_texture(&self.texture, &Vector3::<i32>::new(0, 0, 0))?;
|
||||
|
||||
|
||||
@@ -19,17 +19,6 @@ use wasm_bindgen::JsValue;
|
||||
|
||||
mod subdivide;
|
||||
|
||||
/*
|
||||
pub(crate) trait HpxTile {
|
||||
// Getter
|
||||
// Returns the current time if the texture is not full
|
||||
fn start_time(&self) -> Time;
|
||||
|
||||
fn time_request(&self) -> Time;
|
||||
|
||||
fn cell(&self) -> &HEALPixCell;
|
||||
}*/
|
||||
|
||||
pub(crate) trait HpxTileBuffer {
|
||||
type T;
|
||||
type C;
|
||||
|
||||
12770
src/core/src/shaders.rs
@@ -7,7 +7,7 @@
|
||||
overscroll-behavior-x: none;
|
||||
overscroll-behavior-y: none; /* Prevents vertical pull-to-refresh */
|
||||
/* Hide the draggable boxes that goes out of the view */
|
||||
overflow: hidden;
|
||||
/*overflow: hidden;*/
|
||||
/* media query on the aladin lite container. not supported everywhere.
|
||||
There can be a more supported alternative here: https://caniuse.com/?search=grid-template-columns */
|
||||
/*container-type: inline-size;*/
|
||||
@@ -343,7 +343,6 @@
|
||||
color: #222;
|
||||
|
||||
/* Allow scrolling but disable scroll bar */
|
||||
height: min-content;
|
||||
width: min-content;
|
||||
|
||||
border: var(--border-size) solid var(--border-color);
|
||||
|
||||
@@ -21,7 +21,7 @@ vec4 val2c_f32(float x) {
|
||||
// apply reversed
|
||||
alpha = mix(alpha, 1.0 - alpha, reversed);
|
||||
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(isinf(x)));
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(isinf(x) || isnan(x)));
|
||||
return apply_tonal(new_color);
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ vec4 val2c(float x) {
|
||||
// apply reversed
|
||||
alpha = mix(alpha, 1.0 - alpha, reversed);
|
||||
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(x == blank || isnan(x)));
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(x == blank));
|
||||
return apply_tonal(new_color);
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ vec4 val2c_f32(float x) {
|
||||
// apply reversed
|
||||
alpha = mix(alpha, 1.0 - alpha, reversed);
|
||||
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(isinf(x)));
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(isinf(x) || isnan(x)));
|
||||
return apply_tonal(new_color);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ vec4 val2c(float x) {
|
||||
// apply reversed
|
||||
alpha = mix(alpha, 1.0 - alpha, reversed);
|
||||
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(x == blank || isnan(x)));
|
||||
vec4 new_color = mix(colormap_f(alpha), vec4(0.0), float(x == blank));
|
||||
return apply_tonal(new_color);
|
||||
}
|
||||
|
||||
@@ -96,12 +96,12 @@ vec4 uvw2c_f32(vec3 uv) {
|
||||
|
||||
vec4 uvw2c_i32(vec3 uv) {
|
||||
float val = float(decode_i32(texture(tex, uv).rgba));
|
||||
return val2c(val);
|
||||
return mix(val2c(val), vec4(0.0), float(val == -1.0));
|
||||
}
|
||||
|
||||
vec4 uvw2c_i16(vec3 uv) {
|
||||
float val = float(decode_i16(texture(tex, uv).rg));
|
||||
return val2c(val);
|
||||
return mix(val2c(val), vec4(0.0), float(val == -1.0));
|
||||
}
|
||||
|
||||
vec4 uvw2c_u8(vec3 uv) {
|
||||
|
||||
12
src/js/A.js
@@ -29,7 +29,6 @@
|
||||
* Author: Thomas Boch[CDS]
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
import { MOC } from "./MOC.js";
|
||||
import { GraphicOverlay } from "./Overlay.js";
|
||||
import { Circle } from "./shapes/Circle.js";
|
||||
@@ -58,7 +57,6 @@ import aladinCSS from './../css/aladin.css?inline';
|
||||
///////////////////////////////
|
||||
/////// Aladin Lite API ///////
|
||||
///////////////////////////////
|
||||
|
||||
/**
|
||||
* @namespace A
|
||||
* @description Aladin Lite API namespace for creating celestial objects.
|
||||
@@ -105,9 +103,9 @@ A.aladin = function (divSelector, options) {
|
||||
const theme = storedPreference || (systemPrefersDark ? "dark" : "light");
|
||||
return theme;
|
||||
}
|
||||
|
||||
|
||||
let theme;
|
||||
if (options.mode) {
|
||||
if (options && options.mode) {
|
||||
let mode = options.mode.toLowerCase();
|
||||
|
||||
if (mode === 'dark' || mode === 'light') {
|
||||
@@ -421,7 +419,7 @@ A.coo = function (longitude, latitude, prec) {
|
||||
*
|
||||
* @param {Circle[]|Polyline[]|Ellipse[]|Vector[]} shapes - an array of A.polygon objects
|
||||
* @param {Source} [source] - a A.source object associated with the footprint
|
||||
*
|
||||
*
|
||||
* @returns {Footprint} Returns a new Footprint object
|
||||
*/
|
||||
A.footprint = function(shapes, source) {
|
||||
@@ -618,6 +616,7 @@ A.catalogFromURL = function (url, options, successCallback, errorCallback, usePr
|
||||
fp.setColor(c.color);
|
||||
fp.setHoverColor(c.hoverColor);
|
||||
fp.setSelectionColor(c.selectionColor);
|
||||
fp.setSelectionLineWidth(c.selectionLineWidth);
|
||||
|
||||
return fp;
|
||||
})
|
||||
@@ -1061,6 +1060,7 @@ A.Utils = AladinUtils;
|
||||
* console.error('Error initializing Aladin Lite:', error);
|
||||
* });
|
||||
*/
|
||||
|
||||
A.init = (async () => {
|
||||
const isWebGL2Supported = document
|
||||
.createElement('canvas')
|
||||
@@ -1075,6 +1075,6 @@ A.init = (async () => {
|
||||
// According to caniuse, https://caniuse.com/webgl2, webgl2 is supported by 89% of users
|
||||
throw "WebGL2 not supported by your browser";
|
||||
}
|
||||
})();
|
||||
})()
|
||||
|
||||
export default A;
|
||||
|
||||
@@ -308,6 +308,29 @@ export let Aladin = (function () {
|
||||
|
||||
const self = this;
|
||||
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.listenedBy(
|
||||
aladinDiv,
|
||||
(e) => {
|
||||
const {overlay} = e.detail;
|
||||
let callback = this.callbacksByEventName["stackChanged"];
|
||||
callback && callback({
|
||||
change: 'added',
|
||||
overlay,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.listenedBy(
|
||||
aladinDiv, (e) => {
|
||||
const {overlay} = e.detail;
|
||||
let callback = this.callbacksByEventName["stackChanged"];
|
||||
callback && callback({
|
||||
change: 'removed',
|
||||
overlay,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
ALEvent.LAYER_ADDED.listenedBy(aladinDiv, (e) => {
|
||||
const {layer} = e.detail;
|
||||
let callback = this.callbacksByEventName["stackChanged"];
|
||||
@@ -440,6 +463,7 @@ export let Aladin = (function () {
|
||||
if (typeof hips === "string") {
|
||||
try {
|
||||
url = new URL(hips).href;
|
||||
id = url;
|
||||
} catch (e) {
|
||||
id = hips;
|
||||
}
|
||||
@@ -487,7 +511,7 @@ export let Aladin = (function () {
|
||||
|
||||
this._setupUI(options);
|
||||
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document.body, this.hipsFavorites);
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document, this.hipsFavorites);
|
||||
|
||||
if (options.survey) {
|
||||
if (Array.isArray(options.survey)) {
|
||||
@@ -630,10 +654,10 @@ export let Aladin = (function () {
|
||||
|
||||
////////////////////////////////////////////////////
|
||||
let widgets = {};
|
||||
|
||||
let stack;
|
||||
// Add the layers control
|
||||
if (options.showLayersControl) {
|
||||
let stack = new Stack(this);
|
||||
stack = new Stack(this);
|
||||
widgets["stack"] = stack
|
||||
}
|
||||
|
||||
@@ -683,7 +707,7 @@ export let Aladin = (function () {
|
||||
}
|
||||
|
||||
if (options.expandLayersControl) {
|
||||
stack.click();
|
||||
stack && stack.click();
|
||||
}
|
||||
|
||||
this._applyMediaQueriesUI();
|
||||
@@ -1600,7 +1624,7 @@ export let Aladin = (function () {
|
||||
this.view.addCatalog(catalog);
|
||||
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.dispatchedTo(this.aladinDiv, {
|
||||
layer: catalog,
|
||||
overlay: catalog,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1613,9 +1637,7 @@ export let Aladin = (function () {
|
||||
Aladin.prototype.addOverlay = function (overlay) {
|
||||
this.view.addOverlay(overlay);
|
||||
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.dispatchedTo(this.aladinDiv, {
|
||||
layer: overlay,
|
||||
});
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.dispatchedTo(this.aladinDiv, { overlay });
|
||||
};
|
||||
|
||||
|
||||
@@ -1797,7 +1819,7 @@ export let Aladin = (function () {
|
||||
if (idx >= 0) {
|
||||
this.hipsFavorites.splice(idx, 1);
|
||||
// Send a change of favorites for the UI selector to adapt their optional list
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document.body, this.hipsFavorites);
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document, this.hipsFavorites);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1834,7 +1856,7 @@ export let Aladin = (function () {
|
||||
})
|
||||
|
||||
// send the final event
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document.body, this.hipsFavorites);
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document, this.hipsFavorites);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2161,6 +2183,10 @@ export let Aladin = (function () {
|
||||
* view in the counter clockwise order (or towards the east)
|
||||
*/
|
||||
Aladin.prototype.setRotation = function (rotation) {
|
||||
if (!rotation) {
|
||||
console.warn("Rotation angle is not valid:", rotation)
|
||||
return;
|
||||
}
|
||||
this.view.setRotation(rotation);
|
||||
};
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ If a function is given, user can return Image, HTMLImageCanvas, HTMLImageElement
|
||||
* @property {string} [decField] - The ID or name of the field holding Declination (dec).
|
||||
* @property {function} [filter] - The filtering function for sources.
|
||||
* @property {string} [selectionColor="#00ff00"] - The color to apply to selected sources in the catalog.
|
||||
* @property {string} [selectionLineWidth] - The line width to apply to selected source footprints in the catalog (i.e. the shapes returned by a custom shape function).
|
||||
* @property {string} [hoverColor=color] - The color to apply to sources in the catalog when they are hovered.
|
||||
* @property {boolean} [displayLabel=false] - Whether to display labels for sources.
|
||||
* @property {string} [labelColumn] - The name of the column to be used for the label.
|
||||
@@ -113,6 +114,7 @@ export let Catalog = (function () {
|
||||
// allows for filtering of sources
|
||||
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
|
||||
this.selectionColor = options.selectionColor || "#00ff00";
|
||||
this.selectionLineWidth = options.selectionLineWidth || undefined;
|
||||
this.hoverColor = options.hoverColor || undefined;
|
||||
|
||||
// when footprints are associated to source, do we need to draw the point source as well ?
|
||||
@@ -143,19 +145,6 @@ export let Catalog = (function () {
|
||||
// cacheCanvas permet de ne créer le path de la source qu'une fois, et de le réutiliser (cf. http://simonsarris.com/blog/427-increasing-performance-by-caching-paths-on-canvas)
|
||||
this.updateShape(options);
|
||||
|
||||
this.cacheMarkerCanvas = document.createElement("canvas");
|
||||
this.cacheMarkerCanvas.width = this.markerSize;
|
||||
this.cacheMarkerCanvas.height = this.markerSize;
|
||||
var cacheMarkerCtx = this.cacheMarkerCanvas.getContext("2d");
|
||||
cacheMarkerCtx.fillStyle = this.color;
|
||||
cacheMarkerCtx.beginPath();
|
||||
var half = this.markerSize / 2;
|
||||
cacheMarkerCtx.arc(half, half, half - 2, 0, 2 * Math.PI, false);
|
||||
cacheMarkerCtx.fill();
|
||||
cacheMarkerCtx.lineWidth = 2;
|
||||
cacheMarkerCtx.strokeStyle = "#ccc";
|
||||
cacheMarkerCtx.stroke();
|
||||
|
||||
this.isShowing = true;
|
||||
}
|
||||
|
||||
@@ -505,6 +494,7 @@ export let Catalog = (function () {
|
||||
* @param {Object} [options] - shape options
|
||||
* @param {string} [options.color] - the color of the shape
|
||||
* @param {string} [options.selectionColor] - the color of the shape when selected
|
||||
* @param {string} [selectionLineWidth] - The line width to apply to selected source footprints in the catalog.
|
||||
* @param {number} [options.sourceSize] - size of the shape
|
||||
* @param {string} [options.hoverColor=options.color] - the color to apply to sources in the catalog when they are hovered.
|
||||
* @param {string|Function|HTMLImageCanvas|HTMLImageElement} [options.shape="square"] - the type of the shape. Can be square, rhomb, plus, cross, triangle, circle.
|
||||
@@ -515,9 +505,25 @@ export let Catalog = (function () {
|
||||
options = options || {};
|
||||
this.color = options.color || this.color || Color.getNextColor();
|
||||
this.selectionColor = options.selectionColor || this.selectionColor || Color.getNextColor();
|
||||
this.selectionLineWidth = options.selectionLineWidth || this.selectionLineWidth;
|
||||
this.hoverColor = options.hoverColor || this.hoverColor || undefined;
|
||||
this.sourceSize = options.sourceSize || this.sourceSize || 6;
|
||||
this.shape = options.shape || this.shape || "square";
|
||||
|
||||
this.markerSize = this.sourceSize;
|
||||
this.cacheMarkerCanvas = document.createElement("canvas");
|
||||
this.cacheMarkerCanvas.width = this.markerSize;
|
||||
this.cacheMarkerCanvas.height = this.markerSize;
|
||||
var cacheMarkerCtx = this.cacheMarkerCanvas.getContext("2d");
|
||||
cacheMarkerCtx.fillStyle = this.color;
|
||||
cacheMarkerCtx.beginPath();
|
||||
var half = this.markerSize / 2;
|
||||
cacheMarkerCtx.arc(half, half, half - 2, 0, 2 * Math.PI, false);
|
||||
cacheMarkerCtx.fill();
|
||||
cacheMarkerCtx.lineWidth = 2;
|
||||
cacheMarkerCtx.strokeStyle = '#ccc';
|
||||
cacheMarkerCtx.stroke();
|
||||
|
||||
if (typeof this.shape === "function") {
|
||||
this.shapeFn = this.shape;
|
||||
this.shape = "custom"
|
||||
@@ -560,7 +566,7 @@ export let Catalog = (function () {
|
||||
this.reportChange();
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Add sources to the catalog
|
||||
@@ -629,11 +635,12 @@ export let Catalog = (function () {
|
||||
let hoverColor = this.hoverColor || color;
|
||||
|
||||
for (var shape of shapes) {
|
||||
// Set the same color of the shape than the catalog.
|
||||
// Set the same color of the shape than the catalog.
|
||||
// FIXME: the color/shape could be a parameter at the source level, allowing the user single catalogs handling different shapes
|
||||
shape.setColor(color)
|
||||
shape.setSelectionColor(this.selectionColor);
|
||||
shape.setHoverColor(hoverColor);
|
||||
shape.setSelectionLineWidth(this.selectionLineWidth);
|
||||
}
|
||||
|
||||
let footprint;
|
||||
@@ -785,9 +792,9 @@ export let Catalog = (function () {
|
||||
* Get one source by its index in the catalog
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {number} idx - the index of the source in the catalog sources
|
||||
*
|
||||
*
|
||||
* @returns {Source} - the source at the index
|
||||
*/
|
||||
Catalog.prototype.getSource = function (idx) {
|
||||
@@ -811,7 +818,7 @@ export let Catalog = (function () {
|
||||
* Set the color of the catalog
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {String} - the new color
|
||||
*/
|
||||
Catalog.prototype.setColor = function (color) {
|
||||
@@ -823,7 +830,7 @@ export let Catalog = (function () {
|
||||
* Set the color of selected sources
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {String} - the new color
|
||||
*/
|
||||
Catalog.prototype.setSelectionColor = function (color) {
|
||||
@@ -831,11 +838,23 @@ export let Catalog = (function () {
|
||||
this.updateShape();
|
||||
};
|
||||
|
||||
/**
|
||||
* Set the selectionLineWidth which can be used by the shape draw function for selected catalog elements.
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
* @param {String} - the new selection line width
|
||||
*/
|
||||
Catalog.prototype.setSelectionLineWidth = function (selectionLineWidth) {
|
||||
this.selectionLineWidth = selectionLineWidth;
|
||||
this.updateShape();
|
||||
};
|
||||
|
||||
/**
|
||||
* Select sources of the catalog matching a given callback
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {Function} filter - A filter callback to select sources of a catalog.
|
||||
*/
|
||||
Catalog.prototype.select = function(filter) {
|
||||
@@ -861,7 +880,7 @@ export let Catalog = (function () {
|
||||
* Set the color of hovered sources
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {String} - the new color
|
||||
*/
|
||||
Catalog.prototype.setHoverColor = function (color) {
|
||||
@@ -873,7 +892,7 @@ export let Catalog = (function () {
|
||||
* Set the size of the catalog sources
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {number} - the new size
|
||||
*/
|
||||
Catalog.prototype.setSourceSize = function (sourceSize) {
|
||||
@@ -886,7 +905,7 @@ export let Catalog = (function () {
|
||||
* Set the shape of the catalog sources
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {string|Function|HTMLImageCanvas|HTMLImageElement} [shape="square"] - the type of the shape. Can be square, rhomb, plus, cross, triangle, circle.
|
||||
* A callback function can also be called that return an HTMLImageElement in function of the source object. A canvas or an image can also be given.
|
||||
*/
|
||||
@@ -899,7 +918,7 @@ export let Catalog = (function () {
|
||||
* Get the size of the catalog sources
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @returns {number} - the size of the sources
|
||||
*/
|
||||
Catalog.prototype.getSourceSize = function () {
|
||||
@@ -910,7 +929,7 @@ export let Catalog = (function () {
|
||||
* Remove a specific source from the catalog
|
||||
*
|
||||
* @memberof Catalog
|
||||
*
|
||||
*
|
||||
* @param {Source} - the source to remove
|
||||
*/
|
||||
Catalog.prototype.remove = function (source) {
|
||||
@@ -986,12 +1005,18 @@ export let Catalog = (function () {
|
||||
s.x = xy[2 * idx];
|
||||
s.y = xy[2 * idx + 1];
|
||||
|
||||
if (s.isHovered || s.isSelected) {
|
||||
// These sources will be drawn on the top of the others so we mark it as drawn
|
||||
// for the moment but will really draw them after all catalogs have been drawn
|
||||
return true;
|
||||
}
|
||||
|
||||
return self.drawSource(s, ctx, width, height)
|
||||
};
|
||||
|
||||
this.sources.forEach((s, idx) => {
|
||||
let drawn = false;
|
||||
|
||||
|
||||
if (xy[2 * idx] && xy[2 * idx + 1]) {
|
||||
if (self.filterFn) {
|
||||
if(!self.filterFn(s)) {
|
||||
@@ -1095,7 +1120,7 @@ export let Catalog = (function () {
|
||||
let color = s.color || this.color;
|
||||
|
||||
let cacheCanvas = this.getCacheCanvas(shape, color, size)
|
||||
|
||||
|
||||
ctx.drawImage(
|
||||
cacheCanvas,
|
||||
s.x - cacheCanvas.width / 2,
|
||||
@@ -1119,7 +1144,7 @@ export let Catalog = (function () {
|
||||
return;
|
||||
}
|
||||
|
||||
ctx.fillText(label, s.x, s.y);
|
||||
ctx.fillText(label, s.x + this.sourceSize / 2, s.y);
|
||||
};
|
||||
|
||||
// callback function to be called when the status of one of the sources has changed
|
||||
|
||||
@@ -126,9 +126,10 @@ export let DefaultActionsForContextMenu = (function () {
|
||||
},
|
||||
subMenu: [
|
||||
{
|
||||
label: 'FITS image', action(o) {
|
||||
label: 'Load image', action(o) {
|
||||
let input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = ['.fits', '.png', '.jpg'];
|
||||
input.onchange = _ => {
|
||||
let files = Array.from(input.files);
|
||||
|
||||
@@ -137,14 +138,16 @@ export let DefaultActionsForContextMenu = (function () {
|
||||
const name = file.name;
|
||||
|
||||
// Consider other cases
|
||||
const image = a.createImageFITS(
|
||||
const image = A.image(
|
||||
url,
|
||||
{name},
|
||||
(ra, dec, fov, _) => {
|
||||
// Center the view around the new fits object
|
||||
a.gotoRaDec(ra, dec);
|
||||
a.setFoV(fov * 1.1);
|
||||
}
|
||||
{
|
||||
name,
|
||||
successCallback: (ra, dec, fov, _) => {
|
||||
// Center the view around the new fits object
|
||||
a.gotoRaDec(ra, dec);
|
||||
a.setFoV(fov * 1.1);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
a.setOverlayImageLayer(image, name)
|
||||
|
||||
@@ -51,6 +51,7 @@ export let Footprint= (function() {
|
||||
this.shapes = [].concat(shapes);
|
||||
|
||||
this.isShowing = true;
|
||||
this.isSelected = false;
|
||||
this.isHovered = false;
|
||||
|
||||
this.overlay = null;
|
||||
@@ -71,7 +72,7 @@ export let Footprint= (function() {
|
||||
/*Footprint.prototype.setCatalog = function(catalog) {
|
||||
if (this.source) {
|
||||
this.source.setCatalog(catalog);
|
||||
|
||||
|
||||
}
|
||||
};*/
|
||||
|
||||
@@ -94,10 +95,12 @@ export let Footprint= (function() {
|
||||
};
|
||||
|
||||
Footprint.prototype.select = function() {
|
||||
this.isSelected = true;
|
||||
this.shapes.forEach((shape) => shape.select())
|
||||
};
|
||||
|
||||
Footprint.prototype.deselect = function() {
|
||||
this.isSelected = false;
|
||||
this.shapes.forEach((shape) => shape.deselect())
|
||||
};
|
||||
|
||||
@@ -137,6 +140,14 @@ export let Footprint= (function() {
|
||||
this.shapes.forEach((shape) => shape.setLineWidth(lineWidth))
|
||||
};
|
||||
|
||||
Footprint.prototype.getSelectionLineWidth = function() {
|
||||
return this.shapes && this.shapes[0].getSelectionLineWidth();
|
||||
};
|
||||
|
||||
Footprint.prototype.setSelectionLineWidth = function(selectionLineWidth) {
|
||||
this.shapes.forEach((shape) => shape.setSelectionLineWidth(selectionLineWidth))
|
||||
};
|
||||
|
||||
Footprint.prototype.setColor = function(color) {
|
||||
if(!color) {
|
||||
return;
|
||||
|
||||
@@ -864,6 +864,12 @@ export let HiPS = (function () {
|
||||
}
|
||||
}
|
||||
|
||||
HiPS.prototype.getFrequencyWindow = function() {
|
||||
if (this.added) {
|
||||
return this.view.wasm.getFreqWindow(this.layer);
|
||||
}
|
||||
}
|
||||
|
||||
// Private method for updating the backend with the new meta
|
||||
HiPS.prototype._updateMetadata = function () {
|
||||
try {
|
||||
|
||||
@@ -526,10 +526,14 @@ export let Image = (function () {
|
||||
// obj.wcs (object) = The wcs parsed from the image
|
||||
if (obj.wcsdata) {
|
||||
if (img.width !== obj.wcs.NAXIS1) {
|
||||
obj.wcs.CDELT1 = obj.wcs.CDELT1 * (obj.wcs.NAXIS1 / img.width);
|
||||
obj.wcs.CRPIX1 *= img.width / obj.wcs.NAXIS1;
|
||||
obj.wcs.NAXIS1 = img.width;
|
||||
}
|
||||
|
||||
if (img.height !== obj.wcs.NAXIS2) {
|
||||
obj.wcs.CDELT2 = obj.wcs.CDELT2 * (obj.wcs.NAXIS2 / img.height);
|
||||
obj.wcs.CRPIX2 *= img.height / obj.wcs.NAXIS2;
|
||||
obj.wcs.NAXIS2 = img.height;
|
||||
}
|
||||
|
||||
@@ -567,7 +571,6 @@ export let Image = (function () {
|
||||
let wcs = self.options && self.options.wcs;
|
||||
wcs.NAXIS1 = wcs.NAXIS1 || img.width;
|
||||
wcs.NAXIS2 = wcs.NAXIS2 || img.height;
|
||||
|
||||
return self.view.wasm
|
||||
.addRGBAImage(
|
||||
bytes,
|
||||
|
||||
@@ -199,7 +199,7 @@ export let MOC = (function() {
|
||||
self.view.insertOverlay(self, idx);
|
||||
|
||||
// Tell the MOC has been fully loaded and can be sent as an event
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.dispatchedTo(self.view.aladinDiv, {layer: self});
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_ADDED.dispatchedTo(self.view.aladinDiv, {overlay: self});
|
||||
|
||||
self.view.requestRedraw();
|
||||
})
|
||||
|
||||
@@ -48,7 +48,7 @@ import { Color } from './Color';
|
||||
|
||||
export let GraphicOverlay = (function() {
|
||||
/**
|
||||
* Represents an overlay containing Footprints, whether it is
|
||||
* Represents an overlay containing Footprints, whether it is
|
||||
*
|
||||
* @class
|
||||
* @constructs GraphicOverlay
|
||||
@@ -124,10 +124,10 @@ export let GraphicOverlay = (function() {
|
||||
|
||||
/**
|
||||
* Parse a STCS string and returns a list of footprints (only circles, polygons and ellipses given in ICRS or FK5J2000 frame are handled).
|
||||
* For visualization purposes, the difference between FK5J2000 and ICRS system is not noticeable. Therefore one can be interpreted as the other.
|
||||
* For visualization purposes, the difference between FK5J2000 and ICRS system is not noticeable. Therefore one can be interpreted as the other.
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
*
|
||||
* @returns {Circle[]|Polyline[]|Ellipse[]} The list of mixed circles, polygons and ellipses
|
||||
*/
|
||||
GraphicOverlay.parseSTCS = function(stcs, options) {
|
||||
@@ -216,8 +216,8 @@ export let GraphicOverlay = (function() {
|
||||
* Add an array (or single) shapes (i.e. Footprint, Circle, Polyline, Ellipse, Vector, ...)
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
* @param {Footprint[]|Circle[]|Polyline[]|Ellipse[]|Vector[]} overlaysToAdd - a list (or single) shapes to add to the overlay
|
||||
*
|
||||
* @param {Footprint[]|Circle[]|Polyline[]|Ellipse[]|Vector[]} overlaysToAdd - a list (or single) shapes to add to the overlay
|
||||
*/
|
||||
GraphicOverlay.prototype.addFootprints = function(overlaysToAdd) {
|
||||
overlaysToAdd = [].concat(overlaysToAdd)
|
||||
@@ -250,9 +250,9 @@ export let GraphicOverlay = (function() {
|
||||
* Returns a shape by an index
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
*
|
||||
* @param {number} idx - The index of the shape to retrieve
|
||||
*
|
||||
*
|
||||
* @returns {Footprint|Circle|Polyline|Ellipse|Vector} The shape
|
||||
*/
|
||||
GraphicOverlay.prototype.getFootprint = function(idx) {
|
||||
@@ -293,31 +293,12 @@ export let GraphicOverlay = (function() {
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.setLineDash(this.lineDash);
|
||||
|
||||
// 1. Drawing polygons
|
||||
|
||||
// TODO: les overlay polygons devrait se tracer lui meme (methode draw)
|
||||
//ctx.lineWidth = this.lineWidth;
|
||||
//ctx.beginPath();
|
||||
/*var xyviews = [];
|
||||
|
||||
for (var k=0, len = this.overlays.length; k<len; k++) {
|
||||
xyviews.push(this.drawFootprint(this.overlays[k], ctx, width, height));
|
||||
}*/
|
||||
//ctx.stroke();
|
||||
|
||||
// selection drawing
|
||||
/*ctx.strokeStyle= Overlay.increaseBrightness(this.color, 50);
|
||||
ctx.beginPath();
|
||||
for (var k=0, len = this.overlays.length; k<len; k++) {
|
||||
if (this.overlays[k].isSelected) {
|
||||
this.drawFootprintSelected(ctx, xyviews[k]);
|
||||
}
|
||||
}
|
||||
ctx.stroke();*/
|
||||
|
||||
// 2. Circle and polylines drawing
|
||||
for (var k=0; k<this.overlayItems.length; k++) {
|
||||
this.overlayItems[k].draw(ctx, this.view);
|
||||
let item = this.overlayItems[k];
|
||||
if (!item.isHovered && !item.isSelected) {
|
||||
item.draw(ctx, this.view);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
@@ -327,13 +308,16 @@ export let GraphicOverlay = (function() {
|
||||
* Increase the brightness of a color by a percentage
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
* @param {string} hex - The color given in hexadecimal e.g. '#ffa0bb'
|
||||
*
|
||||
* @param {string} color - Any CSS color string (hex e.g. '#ffa0bb', rgb(), named color)
|
||||
* @param {number} percent - The percentage to increase the brightness of
|
||||
*
|
||||
*
|
||||
* @returns {string} The new color given as an hexadecimal string
|
||||
*/
|
||||
GraphicOverlay.increaseBrightness = function(hex, percent){
|
||||
GraphicOverlay.increaseBrightness = function(color, percent){
|
||||
// Normalize the color to hex.
|
||||
var hex = Color.standardizeColor(color);
|
||||
|
||||
// strip the leading # if it's there
|
||||
hex = hex.replace(/^\s*#|\s*$/g, '');
|
||||
|
||||
@@ -356,7 +340,7 @@ export let GraphicOverlay = (function() {
|
||||
* Set the color of the shapes inside the overlay
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
*
|
||||
* @param {string} color - the new color in hexadecimal e.g. '#ff00ff'
|
||||
*/
|
||||
GraphicOverlay.prototype.setColor = function(color) {
|
||||
@@ -368,7 +352,7 @@ export let GraphicOverlay = (function() {
|
||||
* Set the line width of the shapes inside the overlay
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
*
|
||||
* @param {number} lineWidth - the new line width in pixels
|
||||
*/
|
||||
GraphicOverlay.prototype.setLineWidth = function(lineWidth) {
|
||||
@@ -380,7 +364,7 @@ export let GraphicOverlay = (function() {
|
||||
* Set the dash line property
|
||||
*
|
||||
* @memberof GraphicOverlay
|
||||
*
|
||||
*
|
||||
* @param {Array.<number>} [lineDash=[]] - See the segments property {@link https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash#segments| here}
|
||||
*/
|
||||
GraphicOverlay.prototype.setLineDash = function(lineDash) {
|
||||
|
||||
@@ -72,7 +72,7 @@ export let ProgressiveCat = (function() {
|
||||
|
||||
|
||||
// allows for filtering of sources
|
||||
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
|
||||
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
|
||||
|
||||
|
||||
this.onClick = options.onClick || undefined; // TODO: inherit from catalog
|
||||
|
||||
@@ -29,6 +29,25 @@
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
/**
|
||||
* @typedef {Object} SourceOptions
|
||||
* @description Options for describing a source
|
||||
*
|
||||
* @property {boolean} [marker=false] - If the source is a marker. A source marker is associated to a popup that is shown when clicking on it.
|
||||
* @property {string} [popupTitle=''] - Only for marker. The title of the popup.
|
||||
* @property {string} [popupDesc=''] - Only for marker. The content of the popup.
|
||||
* @property {number} [useMarkerDefaultIcon=true] - Only for marker. Set to false to use the regular shape option from the catalog and not the specific marker shape.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} MarkerOptions
|
||||
* @description Options for describing a source marker
|
||||
*
|
||||
* @property {string} [popupTitle=''] - Only for marker. The title of the popup.
|
||||
* @property {string} [popupDesc=''] - Only for marker. The content of the popup.
|
||||
* @property {number} [useMarkerDefaultIcon=true] - Only for marker. Set to false to use the regular shape option from the catalog and not the specific marker shape.
|
||||
*/
|
||||
export let Source = (function() {
|
||||
// constructor
|
||||
let Source = function(ra, dec, data, options) {
|
||||
@@ -179,7 +198,9 @@ export let Source = (function() {
|
||||
view.aladin.popup.setTitle(this.popupTitle);
|
||||
view.aladin.popup.setText(this.popupDesc);
|
||||
view.aladin.popup.setSource(this);
|
||||
view.aladin.popup.show();
|
||||
|
||||
if (this.popupDesc || this.popupTitle)
|
||||
view.aladin.popup.show();
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -229,5 +250,11 @@ export let Source = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Source.prototype.draw = function(ctx, width, height) {
|
||||
if (this.catalog) {
|
||||
this.catalog.drawSource(this, ctx, width, height);
|
||||
}
|
||||
}
|
||||
|
||||
return Source;
|
||||
})();
|
||||
|
||||
@@ -594,6 +594,7 @@ export class SpectraDisplayer extends DOMElement {
|
||||
let data = event.detail;
|
||||
if (data.layer === this.hips.layer) {
|
||||
this.data = data;
|
||||
console.log(data)
|
||||
this._redraw(this.ctx);
|
||||
}
|
||||
};
|
||||
|
||||
153
src/js/View.js
@@ -49,7 +49,7 @@ import { Image } from "./Image.js";
|
||||
import { Color } from "./Color.js";
|
||||
import { SpectraDisplayer } from "./SpectraDisplayer.js";
|
||||
import { DefaultActionsForContextMenu } from "./DefaultActionsForContextMenu.js";
|
||||
|
||||
import { Source } from "./Source.js";
|
||||
export let View = (function () {
|
||||
|
||||
/** Constructor */
|
||||
@@ -397,7 +397,7 @@ export let View = (function () {
|
||||
|
||||
var computedWidth = Math.floor(parseFloat(this.aladinDiv.getBoundingClientRect().width)) || 1.0;
|
||||
var computedHeight = Math.floor(parseFloat(this.aladinDiv.getBoundingClientRect().height)) || 1.0;
|
||||
|
||||
|
||||
this.width = Math.max(computedWidth, 1);
|
||||
this.height = Math.max(computedHeight, 1); // this prevents many problems when div size is equal to 0
|
||||
|
||||
@@ -640,10 +640,17 @@ export let View = (function () {
|
||||
|
||||
// prevent default context menu from appearing (potential clash with right-click cuts control)
|
||||
Utils.on(view.catalogCanvas, "contextmenu", function (e) {
|
||||
// do something here...
|
||||
e.preventDefault();
|
||||
if(view.aladin.contextMenu) {
|
||||
let ctxMenu = view.aladin.contextMenu;
|
||||
if(ctxMenu) {
|
||||
e.stopPropagation();
|
||||
if (!view.rightClick && showContextMenu) {
|
||||
ctxMenu.attach(
|
||||
DefaultActionsForContextMenu.getDefaultActions(view.aladin),
|
||||
null
|
||||
);
|
||||
ctxMenu._show({e});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -656,7 +663,7 @@ export let View = (function () {
|
||||
view.aladin.statusBar.removeMessage('opening-ctxmenu')
|
||||
}
|
||||
|
||||
view.aladin.contextMenu && view.aladin.contextMenu.show({e});
|
||||
view.aladin.contextMenu && view.aladin.contextMenu._show({e});
|
||||
};
|
||||
var longTouchTimer;
|
||||
var longTouchDuration = 800;
|
||||
@@ -666,7 +673,7 @@ export let View = (function () {
|
||||
var handleSelect = function(xy, tolerance) {
|
||||
tolerance = tolerance || 5;
|
||||
var objs = view.closestObjects(xy.x, xy.y, tolerance);
|
||||
|
||||
|
||||
view.unselectObjects();
|
||||
|
||||
if (objs) {
|
||||
@@ -710,7 +717,7 @@ export let View = (function () {
|
||||
view.selectObjects(objs);
|
||||
|
||||
view.lastClickedObject = objs;
|
||||
|
||||
|
||||
} else {
|
||||
// If there is a past clicked object
|
||||
if (view.lastClickedObject) {
|
||||
@@ -724,7 +731,6 @@ export let View = (function () {
|
||||
}
|
||||
var touchStartTime;
|
||||
Utils.on(view.catalogCanvas, "mousedown touchstart", function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const xymouse = Utils.relMouseCoords(e);
|
||||
@@ -737,7 +743,6 @@ export let View = (function () {
|
||||
state: {
|
||||
mode: view.mode,
|
||||
dragging: view.dragging,
|
||||
rightClickPressed: view.rightClick
|
||||
},
|
||||
type: e.type,
|
||||
xy: xymouse,
|
||||
@@ -774,7 +779,7 @@ export let View = (function () {
|
||||
})
|
||||
}
|
||||
|
||||
longTouchTimer = setTimeout(() => {onlongtouch(e); view.dragging = false;}, longTouchDuration);
|
||||
longTouchTimer = setTimeout(() => {onlongtouch(e); view.dragging = false;}, longTouchDuration);
|
||||
touchStartTime = Date.now();
|
||||
|
||||
}
|
||||
@@ -823,7 +828,6 @@ export let View = (function () {
|
||||
|
||||
Utils.on(view.catalogCanvas, "click", function (e) {
|
||||
// call listener of 'click' event
|
||||
|
||||
if (view.mode == View.TOOL_SIMBAD_POINTER) {
|
||||
// call Simbad pointer or Planetary features
|
||||
GenericPointer(view, e);
|
||||
@@ -844,11 +848,10 @@ export let View = (function () {
|
||||
})
|
||||
return; // listeners are not called
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
Utils.on(document, "mouseup touchend", function(e) {
|
||||
var wasDragging = view.realDragging === true;
|
||||
var wasDragging = view.realDragging === true;
|
||||
|
||||
if (view.dragging) { // if we were dragging, reset to default cursor
|
||||
if(view.mode === View.PAN) {
|
||||
@@ -877,7 +880,6 @@ export let View = (function () {
|
||||
state: {
|
||||
mode: view.mode,
|
||||
dragging: view.dragging,
|
||||
rightClickPressed: view.rightClick
|
||||
},
|
||||
type: e.type,
|
||||
ev: e,
|
||||
@@ -931,10 +933,10 @@ export let View = (function () {
|
||||
if (e.type === "touchend") {
|
||||
if (view.mode === View.SELECT) {
|
||||
view.selector.dispatch('mouseup', {coo: xymouse})
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (view.rightClick) {
|
||||
@@ -948,7 +950,7 @@ export let View = (function () {
|
||||
}
|
||||
|
||||
view.rightClick = false;
|
||||
|
||||
showContextMenu = true;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -989,7 +991,7 @@ export let View = (function () {
|
||||
}
|
||||
});
|
||||
|
||||
var lastHoveredObject; // save last object hovered by mouse
|
||||
view.lastHoveredObject = null;
|
||||
var lastMouseMovePos = null;
|
||||
const pickColor = (xymouse) => {
|
||||
const layers = view.aladin.getStackLayers()
|
||||
@@ -1026,8 +1028,8 @@ export let View = (function () {
|
||||
view.colorPickerTool.domElement.style.display = "none"
|
||||
}
|
||||
|
||||
view.colorPickerTool.domElement.style.left = `${xymouse.x}px`;
|
||||
view.colorPickerTool.domElement.style.top = `${xymouse.y}px`;
|
||||
view.colorPickerTool.domElement.style.left = `${xymouse.x + view.aladin.aladinDiv.getBoundingClientRect().x}px`;
|
||||
view.colorPickerTool.domElement.style.top = `${xymouse.y + view.aladin.aladinDiv.getBoundingClientRect().y}px`;
|
||||
}
|
||||
|
||||
Utils.on(view.catalogCanvas, "mousemove touchmove", function (e) {
|
||||
@@ -1039,7 +1041,6 @@ export let View = (function () {
|
||||
state: {
|
||||
mode: view.mode,
|
||||
dragging: view.dragging,
|
||||
rightClickPressed: view.rightClick
|
||||
},
|
||||
type: e.type,
|
||||
xy: xymouse,
|
||||
@@ -1161,26 +1162,26 @@ export let View = (function () {
|
||||
|
||||
for (let o of closests) {
|
||||
|
||||
if (typeof objHoveredFunction === 'function' && (!lastHoveredObject || !lastHoveredObject.includes(o))) {
|
||||
if (typeof objHoveredFunction === 'function' && (!view.lastHoveredObject || !view.lastHoveredObject.includes(o))) {
|
||||
var ret = objHoveredFunction(o, xymouse);
|
||||
}
|
||||
|
||||
|
||||
if (o.isFootprint()) {
|
||||
if (typeof footprintHoveredFunction === 'function' && (!lastHoveredObject || !lastHoveredObject.includes(o))) {
|
||||
if (typeof footprintHoveredFunction === 'function' && (!view.lastHoveredObject || !view.lastHoveredObject.includes(o))) {
|
||||
var ret = footprintHoveredFunction(o, xymouse);
|
||||
}
|
||||
}
|
||||
|
||||
if (!lastHoveredObject || !lastHoveredObject.includes(o)) {
|
||||
|
||||
if (!view.lastHoveredObject || !view.lastHoveredObject.includes(o)) {
|
||||
o.hover();
|
||||
}
|
||||
}
|
||||
|
||||
// unhover the objects in lastHoveredObjects that are not in closest anymore
|
||||
if (lastHoveredObject) {
|
||||
if (view.lastHoveredObject) {
|
||||
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
||||
|
||||
for (let lho of lastHoveredObject) {
|
||||
for (let lho of view.lastHoveredObject) {
|
||||
if (!closests.includes(lho)) {
|
||||
lho.unhover();
|
||||
|
||||
@@ -1190,19 +1191,19 @@ export let View = (function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
lastHoveredObject = closests;
|
||||
view.lastHoveredObject = closests;
|
||||
} else {
|
||||
view.setCursor('default');
|
||||
if (lastHoveredObject) {
|
||||
if (view.lastHoveredObject) {
|
||||
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
||||
|
||||
/*if (typeof objHoveredStopFunction === 'function') {
|
||||
// call callback function to notify we left the hovered object
|
||||
var ret = objHoveredStopFunction(lastHoveredObject, xymouse);
|
||||
var ret = objHoveredStopFunction(view.lastHoveredObject, xymouse);
|
||||
}
|
||||
|
||||
lastHoveredObject.unhover();*/
|
||||
for (let lho of lastHoveredObject) {
|
||||
view.lastHoveredObject.unhover();*/
|
||||
for (let lho of view.lastHoveredObject) {
|
||||
lho.unhover();
|
||||
|
||||
if (typeof objHoveredStopFunction === 'function') {
|
||||
@@ -1210,8 +1211,8 @@ export let View = (function () {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastHoveredObject = null;
|
||||
|
||||
view.lastHoveredObject = null;
|
||||
}
|
||||
|
||||
if (e.type === "mousemove") {
|
||||
@@ -1276,7 +1277,6 @@ export let View = (function () {
|
||||
state: {
|
||||
mode: view.mode,
|
||||
dragging: view.dragging,
|
||||
rightClickPressed: view.rightClick
|
||||
},
|
||||
type: e.type,
|
||||
xy: xymouse,
|
||||
@@ -1286,7 +1286,7 @@ export let View = (function () {
|
||||
if (typeof onWheelTriggeredFunction === 'function') {
|
||||
onWheelTriggeredFunction(e)
|
||||
} else {
|
||||
// Default Aladin Lite zooming
|
||||
// Default Aladin Lite zooming
|
||||
const normalizedDelta = e.deltaY && normalizeWheel(e) || e.detail || (-e.wheelDelta);
|
||||
// Accumulate the normalized delta
|
||||
// We do not zoom because we cannot rely on "wheel" event
|
||||
@@ -1326,7 +1326,7 @@ export let View = (function () {
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -1444,6 +1444,7 @@ export let View = (function () {
|
||||
cat.draw(ctx, this.width, this.height);
|
||||
}
|
||||
}
|
||||
|
||||
// draw popup catalog
|
||||
if (this.catalogForPopup.isShowing && this.catalogForPopup.sources.length > 0) {
|
||||
if (!this.catalogCanvasCleared) {
|
||||
@@ -1471,6 +1472,32 @@ export let View = (function () {
|
||||
}
|
||||
}
|
||||
|
||||
// Draw selected items (catalog sources, overlay, footprints, ...) afterwards
|
||||
if (this.selection) {
|
||||
this.selection.forEach((objList) => {
|
||||
objList.forEach((o) => {
|
||||
if (o instanceof Source) {
|
||||
o.draw(ctx, this.width, this.height)
|
||||
} else {
|
||||
// Circle, Ellipse, Footprints, ...
|
||||
o.draw(ctx, this)
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Draw hovered items afterwards
|
||||
if (this.lastHoveredObject) {
|
||||
this.lastHoveredObject.forEach((o) => {
|
||||
if (o instanceof Source) {
|
||||
o.draw(ctx, this.width, this.height)
|
||||
} else {
|
||||
// Circle, Ellipse, Footprints, ...
|
||||
o.draw(ctx, this)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Redraw HEALPix grid
|
||||
if (this.displayHpxGrid) {
|
||||
if (!this.catalogCanvasCleared) {
|
||||
@@ -1588,14 +1615,14 @@ export let View = (function () {
|
||||
if (this.manualSelection) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// unselect the previous selection
|
||||
this.unselectObjects();
|
||||
|
||||
|
||||
if (Array.isArray(selection)) {
|
||||
this.selection = selection;
|
||||
} else {
|
||||
// select the new
|
||||
// select the new
|
||||
this.selection = Selector.getObjects(selection, this);
|
||||
}
|
||||
|
||||
@@ -1612,7 +1639,7 @@ export let View = (function () {
|
||||
let cat = obj.getCatalog();
|
||||
|
||||
// trigger the non action clicked if it does not show the table
|
||||
// table show is handled below
|
||||
// table show is handled below
|
||||
if (obj.actionClicked) {
|
||||
if (!cat || !cat.onClick || cat.onClick !== "showTable") {
|
||||
obj.actionClicked()
|
||||
@@ -1642,13 +1669,13 @@ export let View = (function () {
|
||||
} else {
|
||||
source = o;
|
||||
}
|
||||
|
||||
|
||||
return source;
|
||||
});
|
||||
|
||||
let tableColor = catalog.color;
|
||||
if (catalog.colorFn) {
|
||||
tableColor = "white"
|
||||
tableColor = "white"
|
||||
}
|
||||
|
||||
let table = {
|
||||
@@ -1658,7 +1685,7 @@ export let View = (function () {
|
||||
'fields': catalog.fields,
|
||||
'showCallback': ObsCore.SHOW_CALLBACKS(this.aladin)
|
||||
};
|
||||
|
||||
|
||||
return table;
|
||||
})
|
||||
|
||||
@@ -1866,6 +1893,15 @@ export let View = (function () {
|
||||
|
||||
// Remove the settled promise
|
||||
this.promises.splice(idx, 1);
|
||||
|
||||
const noMoreLayersToWaitFor = this.promises.length === 0;
|
||||
if (noMoreLayersToWaitFor && self.empty) {
|
||||
// no promises to launch and the view has no HiPS.
|
||||
// This situation can occurs if the MOCServer is out
|
||||
// If so we can directly put the url of the DSS hosted in alasky,
|
||||
// it the best I can do if the MOCServer is out
|
||||
self.aladin.setBaseImageLayer(Aladin.DEFAULT_OPTIONS.survey);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1976,7 +2012,10 @@ export let View = (function () {
|
||||
let imageLayerQueried = this.imageLayersBeingQueried.get(layer);
|
||||
let imageLayer = this.imageLayers.get(layer);
|
||||
|
||||
return imageLayer || imageLayerQueried;
|
||||
let obj = imageLayer || imageLayerQueried;
|
||||
|
||||
if (obj && obj.added)
|
||||
return obj;
|
||||
};
|
||||
|
||||
View.prototype.requestRedraw = function () {
|
||||
@@ -1994,7 +2033,7 @@ export let View = (function () {
|
||||
}
|
||||
|
||||
this.projection = ProjectionEnum[projName];
|
||||
|
||||
|
||||
// Change the projection here
|
||||
this.wasm.setProjection(projName);
|
||||
this.updateZoomState()
|
||||
@@ -2123,7 +2162,7 @@ export let View = (function () {
|
||||
this.mocs = [];
|
||||
|
||||
this.allOverlayLayers.forEach((overlay) => {
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: overlay });
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { overlay });
|
||||
})
|
||||
this.allOverlayLayers = [];
|
||||
|
||||
@@ -2159,7 +2198,7 @@ export let View = (function () {
|
||||
this.overlays.splice(indexToDelete, 1);
|
||||
}
|
||||
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { layer: overlay });
|
||||
ALEvent.GRAPHIC_OVERLAY_LAYER_REMOVED.dispatchedTo(this.aladinDiv, { overlay });
|
||||
|
||||
this.mustClearCatalog = true;
|
||||
this.requestRedraw();
|
||||
@@ -2235,8 +2274,12 @@ export let View = (function () {
|
||||
|
||||
footprints.forEach((footprint) => {
|
||||
const originLineWidth = footprint.getLineWidth();
|
||||
let spreadedLineWidth = (originLineWidth || 1) + 3;
|
||||
|
||||
let drawingLineWidth = originLineWidth;
|
||||
if (footprint.isSelected && footprint.getSelectionLineWidth()) {
|
||||
drawingLineWidth = footprint.getSelectionLineWidth();
|
||||
}
|
||||
let spreadedLineWidth = (drawingLineWidth || 1) + 3;
|
||||
|
||||
footprint.setLineWidth(spreadedLineWidth);
|
||||
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
|
||||
closests.push(footprint);
|
||||
@@ -2272,8 +2315,12 @@ export let View = (function () {
|
||||
if (s.isFootprint() && !s.tooSmallFootprint) {
|
||||
let footprint = s.footprint;
|
||||
const originLineWidth = footprint.getLineWidth();
|
||||
let spreadedLineWidth = (originLineWidth || 1) + 3;
|
||||
|
||||
let drawingLineWidth = originLineWidth;
|
||||
if (footprint.isSelected && footprint.getSelectionLineWidth()) {
|
||||
drawingLineWidth = footprint.getSelectionLineWidth();
|
||||
}
|
||||
let spreadedLineWidth = (drawingLineWidth || 1) + 3;
|
||||
|
||||
footprint.setLineWidth(spreadedLineWidth);
|
||||
if (footprint.isShowing && footprint.isInStroke(ctx, this, x * window.devicePixelRatio, y * window.devicePixelRatio)) {
|
||||
closests.push(s);
|
||||
|
||||
@@ -28,9 +28,7 @@ export let WebGLCtx = (function() {
|
||||
function WebGLCtx(ctx, div) {
|
||||
this.webclient = new ctx.WebClient(
|
||||
div,
|
||||
{
|
||||
'kernel': kernel,
|
||||
}
|
||||
{ 'kernel': kernel }
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1090,7 +1090,7 @@ export class OverlayStackBox extends Box {
|
||||
monochrome: true,
|
||||
},
|
||||
tooltip: {
|
||||
content: "Swap 2 layers",
|
||||
content: "Click on this button for both layers you want to swap",
|
||||
position: { direction: "top" },
|
||||
},
|
||||
toggled: false,
|
||||
|
||||
@@ -46,7 +46,7 @@ export class ColorPicker extends ActionButton {
|
||||
classList: ['aladin-colorPicker-control'],
|
||||
size: 'medium',
|
||||
tooltip: {
|
||||
content: 'A color picker tool',
|
||||
content: 'Pixel value extractor',
|
||||
position: { direction: 'top right' },
|
||||
},
|
||||
action(o) {
|
||||
@@ -70,7 +70,7 @@ export class ColorPicker extends ActionButton {
|
||||
if (this.aladin.statusBar) {
|
||||
this.aladin.statusBar.appendMessage({
|
||||
id: 'colorpicker',
|
||||
message: 'Color picker mode, click on a pixel to copy it',
|
||||
message: 'Pixel value extractor, click on a pixel to copy it',
|
||||
type: 'info'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ import stackOverlayIconUrl from "./../../../../assets/icons/stack.svg";
|
||||
tooltip: {
|
||||
content: 'Open the overlays menu',
|
||||
position: {
|
||||
direction: (options && options.openDirection) || 'left'
|
||||
direction: (options && options.openDirection) || 'top'
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -87,7 +87,7 @@ export class HiPSSelector extends Input {
|
||||
};
|
||||
|
||||
(function () {
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.listenedBy(document.body, (event) => {
|
||||
ALEvent.FAVORITE_HIPS_LIST_UPDATED.listenedBy(document, (event) => {
|
||||
let favoritesHips = event.detail;
|
||||
|
||||
HiPSSelector.cachedHiPS = {};
|
||||
|
||||
@@ -27,7 +27,7 @@ import { isJSObject } from "./Utils";
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
* File gui/Widgets/layout/Horizontal.js
|
||||
* File gui/Layout.js
|
||||
*
|
||||
* A layout grouping widgets horizontaly
|
||||
*
|
||||
@@ -37,14 +37,6 @@ import { isJSObject } from "./Utils";
|
||||
*****************************************************************************/
|
||||
|
||||
export class Layout extends DOMElement {
|
||||
/**
|
||||
* Create a layout
|
||||
* @param {layout: Array.<DOMElement | String>} layout - Represents the structure of the Tabs
|
||||
* @param {Object} options - Options object
|
||||
* @param {DOMElement} target - The parent element.
|
||||
* @param {String} position - The position of the tabs layout relative to the target.
|
||||
* For the list of possibilities, see https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
|
||||
*/
|
||||
constructor(layout, options, target, position = "beforeend") {
|
||||
let el = document.createElement('div');
|
||||
|
||||
@@ -211,10 +203,6 @@ export class Layout extends DOMElement {
|
||||
this._show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Empty the layout
|
||||
* @param {content: String|DOMElement, swappable: Boolean, disabled: Boolean, selected: Boolean} item - Represents the structure of the Tabs
|
||||
*/
|
||||
empty() {
|
||||
// remove all the sub elements
|
||||
this.layout = [];
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
// Copyright 2023 - UDS/CNRS
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// Copyright 2013 - UDS/CNRS
|
||||
// The Aladin Lite program is distributed under the terms
|
||||
// of the GNU General Public License version 3.
|
||||
// of the GNU Lesser General Public License version 3
|
||||
// or (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// 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.
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// The GNU General Public License is available in COPYING file
|
||||
// along with Aladin Lite.
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with Aladin Lite. If not, see <https://www.gnu.org/licenses/>.
|
||||
//
|
||||
import { Layout } from "./Layout";
|
||||
import { ActionButton } from "./Widgets/ActionButton";
|
||||
@@ -22,7 +25,7 @@ import { DOMElement } from "./Widgets/Widget";
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
* File gui/Widgets/layout/Horizontal.js
|
||||
* File gui/Toolbar.js
|
||||
*
|
||||
* A layout grouping widgets horizontaly
|
||||
*
|
||||
@@ -33,12 +36,12 @@ import { DOMElement } from "./Widgets/Widget";
|
||||
|
||||
export class Toolbar extends Layout {
|
||||
/**
|
||||
* Create a layout
|
||||
* @param {Object[]} widgets - A list of predefined widgets
|
||||
* Toolbar instance constructor
|
||||
*
|
||||
* @class
|
||||
* @constructs Toolbar
|
||||
* @param {Object} options - Options object
|
||||
* @param {DOMElement} target - The parent element.
|
||||
* @param {String} position - The position of the tabs layout relative to the target.
|
||||
* For the list of possibilities, see https://developer.mozilla.org/en-US/docs/Web/API/Element/insertAdjacentHTML
|
||||
*/
|
||||
constructor(options, target) {
|
||||
let position = (options && options.position) || 'topleft';
|
||||
@@ -50,8 +53,6 @@ export class Toolbar extends Layout {
|
||||
target,
|
||||
)
|
||||
|
||||
console.log("options toolbar", options)
|
||||
|
||||
this.position = position;
|
||||
this.vertical = options && options.vertical === true;
|
||||
|
||||
@@ -102,6 +103,33 @@ export class Toolbar extends Layout {
|
||||
widget.update({disabled: true})
|
||||
}
|
||||
|
||||
/**
|
||||
* Add custom clickable actions to the toolbar
|
||||
*
|
||||
* @memberof Toolbar
|
||||
* @param {string} name - A name identifier for your new action
|
||||
* @param {Object} widget - Your clickable action, same as for defining an action button
|
||||
* @param {boolean} [widget.toggled=false] - Whether the button is initially toggled.
|
||||
* @param {function} [widget.action] - The callback function to execute when the button is clicked.
|
||||
* @param {string} [widget.title] - The title attribute for the button.
|
||||
* @param {Object} [widget.icon] - An icon object for the button.
|
||||
* @param {boolean} [widget.disabled=false] - Whether the button is initially disabled.
|
||||
* @param {CSSStyleSheet} [widget.cssStyle] - The CSS styles to apply to the button.
|
||||
* @param {Object} [widget.tooltip] - A tooltip appearing when the user hovers the button.
|
||||
* @param {string} [widget.size] - The size of the button. Can be 'medium' or 'small'
|
||||
*
|
||||
* @example
|
||||
* aladin.getToolbar()
|
||||
* .add('action_custom', {
|
||||
* icon: {
|
||||
* url: yourIconUrl,
|
||||
* size: "medium"
|
||||
* },
|
||||
* action(_) {
|
||||
* console.log("do a thing")
|
||||
* }
|
||||
* });
|
||||
*/
|
||||
add(name, widget) {
|
||||
if (!(widget instanceof DOMElement)) {
|
||||
widget = new ActionButton(widget)
|
||||
|
||||
@@ -203,7 +203,7 @@ function dragElement(triggerElt, elmnt, onDragged) {
|
||||
document.onmouseup = null;
|
||||
document.onmousemove = null;
|
||||
|
||||
var r = elmnt.getBoundingClientRect();
|
||||
/*var r = elmnt.getBoundingClientRect();
|
||||
|
||||
if (t < r.height / 2) {
|
||||
elmnt.style.top = r.height / 2 + "px";
|
||||
@@ -221,7 +221,7 @@ function dragElement(triggerElt, elmnt, onDragged) {
|
||||
|
||||
if (t + r.height / 2 > aladinDiv.offsetHeight) {
|
||||
elmnt.style.top = (aladinDiv.offsetHeight - r.height / 2) + "px";
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -181,7 +181,6 @@ export let AVM = (function() {
|
||||
|
||||
if (this.xmp) {
|
||||
tags = this.readAVM(this.xmp);
|
||||
|
||||
if (tags) {
|
||||
this.tags = tags;
|
||||
|
||||
|
||||
@@ -52,6 +52,7 @@ export let Circle = (function() {
|
||||
this.fillColor = options['fillColor'] || undefined;
|
||||
this.lineWidth = options["lineWidth"] || undefined;
|
||||
this.selectionColor = options["selectionColor"] || '#00ff00';
|
||||
this.selectionLineWidth = options["selectionLineWidth"] || undefined;
|
||||
this.hoverColor = options["hoverColor"] || undefined;
|
||||
this.opacity = options['opacity'] || 1;
|
||||
|
||||
@@ -112,6 +113,20 @@ export let Circle = (function() {
|
||||
return this.lineWidth;
|
||||
};
|
||||
|
||||
Circle.prototype.setSelectionLineWidth = function(selectionLineWidth) {
|
||||
if (this.selectionLineWidth == selectionLineWidth) {
|
||||
return;
|
||||
}
|
||||
this.selectionLineWidth = selectionLineWidth;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Circle.prototype.getSelectionLineWidth = function() {
|
||||
return this.selectionLineWidth;
|
||||
};
|
||||
|
||||
Circle.prototype.setOverlay = function(overlay) {
|
||||
this.overlay = overlay;
|
||||
};
|
||||
@@ -162,6 +177,7 @@ export let Circle = (function() {
|
||||
}
|
||||
this.isHovered = true;
|
||||
this.setLineWidth(this.getLineWidth() + 2)
|
||||
this.setSelectionLineWidth(this.getSelectionLineWidth() + 2)
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
@@ -173,6 +189,7 @@ export let Circle = (function() {
|
||||
}
|
||||
this.isHovered = false;
|
||||
this.setLineWidth(this.getLineWidth() - 2)
|
||||
this.setSelectionLineWidth(this.getSelectionLineWidth() - 2)
|
||||
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
@@ -203,10 +220,19 @@ export let Circle = (function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decide which line width to use.
|
||||
if (!this.lineWidth) {
|
||||
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
|
||||
}
|
||||
let drawingLineWidth = this.lineWidth;
|
||||
if (this.isSelected && this.selectionLineWidth) {
|
||||
drawingLineWidth = this.selectionLineWidth;
|
||||
}
|
||||
|
||||
noSmallCheck = noSmallCheck===true || false;
|
||||
if (!noSmallCheck) {
|
||||
const px_per_deg = view.width / view.fov;
|
||||
this.isTooSmall = this.radiusDegrees * 2 * px_per_deg < this.lineWidth;
|
||||
this.isTooSmall = this.radiusDegrees * 2 * px_per_deg < drawingLineWidth;
|
||||
if (this.isTooSmall) {
|
||||
return false;
|
||||
}
|
||||
@@ -302,11 +328,7 @@ export let Circle = (function() {
|
||||
ctx.strokeStyle = baseColor;
|
||||
}
|
||||
|
||||
if (!this.lineWidth) {
|
||||
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
|
||||
}
|
||||
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.lineWidth = drawingLineWidth;
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.beginPath();
|
||||
ctx.arc(this.center.x, this.center.y, this.radius, 0, 2*Math.PI, false);
|
||||
@@ -336,7 +358,7 @@ export let Circle = (function() {
|
||||
}
|
||||
|
||||
// compute the absolute distance between the middle of the bbox
|
||||
// and the center of the circle
|
||||
// and the center of the circle
|
||||
const circleDistance = {
|
||||
x: Math.abs(centerXyview[0] - (x + w/2)),
|
||||
y: Math.abs(centerXyview[1] - (y + h/2))
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
*
|
||||
* File Ellipse
|
||||
*
|
||||
*
|
||||
* Author: Matthieu Baumann[CDS]
|
||||
*
|
||||
*
|
||||
*****************************************************************************/
|
||||
|
||||
import { Utils } from "./../Utils";
|
||||
@@ -54,6 +54,7 @@ export let Ellipse = (function() {
|
||||
this.color = options['color'] || undefined;
|
||||
this.fillColor = options['fillColor'] || undefined;
|
||||
this.lineWidth = options["lineWidth"] || undefined;
|
||||
this.selectionLineWidth = options["selectionLineWidth"] || undefined;
|
||||
this.selectionColor = options["selectionColor"] || '#00ff00';
|
||||
this.hoverColor = options["hoverColor"] || undefined;
|
||||
this.opacity = options['opacity'] || 1;
|
||||
@@ -66,7 +67,7 @@ export let Ellipse = (function() {
|
||||
this.setAxisLength(a, b);
|
||||
this.setRotation(theta);
|
||||
this.overlay = null;
|
||||
|
||||
|
||||
this.isShowing = true;
|
||||
this.isSelected = false;
|
||||
this.isHovered = false;
|
||||
@@ -120,6 +121,20 @@ export let Ellipse = (function() {
|
||||
this.overlay = overlay;
|
||||
};
|
||||
|
||||
Ellipse.prototype.setSelectionLineWidth = function(selectionLineWidth) {
|
||||
if (this.selectionLineWidth == selectionLineWidth) {
|
||||
return;
|
||||
}
|
||||
this.selectionLineWidth = selectionLineWidth;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Ellipse.prototype.getSelectionLineWidth = function() {
|
||||
return this.selectionLineWidth;
|
||||
};
|
||||
|
||||
Ellipse.prototype.show = function() {
|
||||
if (this.isShowing) {
|
||||
return;
|
||||
@@ -129,7 +144,7 @@ export let Ellipse = (function() {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Ellipse.prototype.hide = function() {
|
||||
if (! this.isShowing) {
|
||||
return;
|
||||
@@ -139,7 +154,7 @@ export let Ellipse = (function() {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Ellipse.prototype.select = function() {
|
||||
if (this.isSelected) {
|
||||
return;
|
||||
@@ -167,6 +182,7 @@ export let Ellipse = (function() {
|
||||
}
|
||||
this.isHovered = true;
|
||||
this.setLineWidth(this.getLineWidth() + 2)
|
||||
this.setSelectionLineWidth(this.getSelectionLineWidth() + 2)
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
@@ -178,6 +194,7 @@ export let Ellipse = (function() {
|
||||
}
|
||||
this.isHovered = false;
|
||||
this.setLineWidth(this.getLineWidth() - 2)
|
||||
this.setSelectionLineWidth(this.getSelectionLineWidth() - 2)
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
@@ -221,10 +238,19 @@ export let Ellipse = (function() {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decide which line width to use.
|
||||
if (!this.lineWidth) {
|
||||
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
|
||||
}
|
||||
let drawingLineWidth = this.lineWidth;
|
||||
if (this.isSelected && this.selectionLineWidth) {
|
||||
drawingLineWidth = this.selectionLineWidth;
|
||||
}
|
||||
|
||||
const px_per_deg = view.width / view.fov;
|
||||
noSmallCheck = noSmallCheck===true || false;
|
||||
if (!noSmallCheck) {
|
||||
this.isTooSmall = this.b * 2 * px_per_deg < this.lineWidth;
|
||||
this.isTooSmall = this.b * 2 * px_per_deg < drawingLineWidth;
|
||||
if (this.isTooSmall) {
|
||||
return false;
|
||||
}
|
||||
@@ -250,7 +276,7 @@ export let Ellipse = (function() {
|
||||
// 3. normalize this vector
|
||||
let toNorthVec = [toNorthScreen[0] - originScreen[0], toNorthScreen[1] - originScreen[1]];
|
||||
let norm = Math.sqrt(toNorthVec[0]*toNorthVec[0] + toNorthVec[1]*toNorthVec[1]);
|
||||
|
||||
|
||||
toNorthVec = [toNorthVec[0] / norm, toNorthVec[1] / norm];
|
||||
let toWestVec = [1.0, 0.0];
|
||||
|
||||
@@ -287,11 +313,7 @@ export let Ellipse = (function() {
|
||||
ctx.strokeStyle = baseColor;
|
||||
}
|
||||
|
||||
if (!this.lineWidth) {
|
||||
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
|
||||
}
|
||||
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.lineWidth = drawingLineWidth;
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.beginPath();
|
||||
|
||||
@@ -353,7 +375,7 @@ export let Ellipse = (function() {
|
||||
}
|
||||
|
||||
// compute the absolute distance between the middle of the bbox
|
||||
// and the center of the circle
|
||||
// and the center of the circle
|
||||
const circleDistance = {
|
||||
x: Math.abs(centerXyview[0] - (x + w/2)),
|
||||
y: Math.abs(centerXyview[1] - (y + h/2))
|
||||
@@ -371,6 +393,6 @@ export let Ellipse = (function() {
|
||||
const cornerDistanceSquared = dx*dx + dy*dy;
|
||||
return (cornerDistanceSquared <= (this.aPixels*this.aPixels));
|
||||
};
|
||||
|
||||
|
||||
return Ellipse;
|
||||
})();
|
||||
|
||||
@@ -45,7 +45,8 @@ import { ProjectionEnum } from "../ProjectionEnum.js";
|
||||
* @property {string} [color] - The color of the shape
|
||||
* @property {string} [fill=false] - Fill the shape with fillColor
|
||||
* @property {string} [fillColor] - A filling color for the shape
|
||||
* @property {number} [lineWidth=3] - The line width in pixels
|
||||
* @property {number} [lineWidth=2] - The line width in pixels (inherited from overlay, if any, where it defaults to 3)
|
||||
* @property {number} [selectionLineWidth=lineWidth] - The line width in pixels when the shape is selected
|
||||
* @property {number} [opacity=1] - The opacity, between 0 (totally transparent) and 1 (totally opaque)
|
||||
* @property {string} [selectionColor='#00ff00'] - A selection color
|
||||
* @property {string} [hoverColor] - A hovered color
|
||||
@@ -75,7 +76,7 @@ export let Polyline = (function() {
|
||||
* @param {Array.<number[]>} raDecArray - right-ascension/declination 2-tuple array describing the polyline's vertices in degrees
|
||||
* @param {ShapeOptions} options - Configuration options for the polyline. Additional properties:
|
||||
* @param {boolean} [options.closed=false] - Close the polyline, default to false.
|
||||
*
|
||||
*
|
||||
* @returns {Polyline} - The polyline shape object
|
||||
*/
|
||||
let Polyline = function(raDecArray, options) {
|
||||
@@ -85,6 +86,7 @@ export let Polyline = (function() {
|
||||
this.fillColor = options['fillColor'] || undefined;
|
||||
this.opacity = options['opacity'] || undefined;
|
||||
this.lineWidth = options["lineWidth"] || undefined;
|
||||
this.selectionLineWidth = options["selectionLineWidth"] || undefined;
|
||||
this.selectionColor = options["selectionColor"] || '#00ff00';
|
||||
this.hoverColor = options["hoverColor"] || undefined;
|
||||
|
||||
@@ -151,6 +153,7 @@ export let Polyline = (function() {
|
||||
}
|
||||
this.isHovered = true;
|
||||
this.setLineWidth(this.getLineWidth() + 2)
|
||||
this.setSelectionLineWidth(this.getSelectionLineWidth() + 2)
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
@@ -162,6 +165,7 @@ export let Polyline = (function() {
|
||||
}
|
||||
this.isHovered = false;
|
||||
this.setLineWidth(this.getLineWidth() - 2)
|
||||
this.setSelectionLineWidth(this.getSelectionLineWidth() - 2)
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
@@ -182,6 +186,21 @@ export let Polyline = (function() {
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.getSelectionLineWidth = function() {
|
||||
return this.selectionLineWidth;
|
||||
};
|
||||
|
||||
Polyline.prototype.setSelectionLineWidth = function(selectionLineWidth) {
|
||||
if (this.selectionLineWidth == selectionLineWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.selectionLineWidth = selectionLineWidth;
|
||||
if (this.overlay) {
|
||||
this.overlay.reportChange();
|
||||
}
|
||||
};
|
||||
|
||||
Polyline.prototype.setColor = function(color) {
|
||||
if (!color || this.color == color) {
|
||||
return;
|
||||
@@ -239,9 +258,14 @@ export let Polyline = (function() {
|
||||
baseColor = '#ff0000';
|
||||
}
|
||||
|
||||
// Decide which line width to use.
|
||||
if (!this.lineWidth) {
|
||||
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
|
||||
}
|
||||
var drawingLineWidth = this.lineWidth;
|
||||
if (this.isSelected && this.selectionLineWidth) {
|
||||
drawingLineWidth = this.selectionLineWidth;
|
||||
}
|
||||
|
||||
if (this.isSelected) {
|
||||
if(this.selectionColor) {
|
||||
@@ -293,7 +317,7 @@ export let Polyline = (function() {
|
||||
|
||||
// do not draw neither if the polygone does not lie inside lineWidth
|
||||
if (!noSmallCheck) {
|
||||
this.isTooSmall = (xmax - xmin) < this.lineWidth && (ymax - ymin) < this.lineWidth;
|
||||
this.isTooSmall = (xmax - xmin) < drawingLineWidth && (ymax - ymin) < drawingLineWidth;
|
||||
|
||||
if (this.isTooSmall) {
|
||||
return false;
|
||||
@@ -374,7 +398,7 @@ export let Polyline = (function() {
|
||||
let v1 = this.closed ? 0 : 1;
|
||||
|
||||
ctx.globalAlpha = this.opacity;
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.lineWidth = drawingLineWidth;
|
||||
ctx.beginPath();
|
||||
|
||||
for (var k = 0; k < nSegment; k++) {
|
||||
@@ -449,7 +473,7 @@ export let Polyline = (function() {
|
||||
if (v1 && v2) {
|
||||
const line = {x1: v1.x, y1: v1.y, x2: v2.x, y2: v2.y}; // new segment
|
||||
_drawLine(line, ctx);
|
||||
|
||||
|
||||
if (ctx.isPointInStroke(x, y)) { // x,y is on line?
|
||||
return true;
|
||||
}
|
||||
@@ -494,7 +518,6 @@ export let Polyline = (function() {
|
||||
}
|
||||
|
||||
if (this.closed && poly.length === this.raDecArray.length) {
|
||||
console.log("closed poly")
|
||||
const corners = [
|
||||
{ x, y },
|
||||
{ x: x + w, y },
|
||||
@@ -583,7 +606,7 @@ export let Polyline = (function() {
|
||||
let right = Polyline.segmentsIntersect({x: x1, y: y1}, {x: x2, y: y2}, {x: rw, y: 0}, {x: rw, y: rh});
|
||||
let top = Polyline.segmentsIntersect({x: x1, y: y1}, {x: x2, y: y2}, {x: 0, y: 0}, {x: rw, y: 0});
|
||||
let bottom = Polyline.segmentsIntersect({x: x1, y: y1}, {x: x2, y: y2}, {x: 0, y: rh}, {x: rw, y: rh});
|
||||
|
||||
|
||||
// if ANY of the above are true, the line
|
||||
// has hit the rectangle
|
||||
if (left || right || top || bottom) {
|
||||
|
||||
@@ -22,13 +22,13 @@
|
||||
|
||||
/******************************************************************************
|
||||
* Aladin Lite project
|
||||
*
|
||||
*
|
||||
* Class Vector
|
||||
*
|
||||
*
|
||||
* A vector is a graphical overlay connecting 2 points with end or begin arrows on it
|
||||
*
|
||||
*
|
||||
* Author: Matthieu Baumann[CDS]
|
||||
*
|
||||
*
|
||||
*****************************************************************************/
|
||||
import { Polyline } from "./Polyline.js";
|
||||
import { Utils } from '../Utils';
|
||||
@@ -38,7 +38,7 @@ import { Ellipse } from "./Ellipse.js";
|
||||
export let Vector = (function() {
|
||||
/**
|
||||
* Represents an vector.
|
||||
*
|
||||
*
|
||||
* A vector is a graphical overlay connecting 2 sky positions with end or begin arrows on it
|
||||
*
|
||||
* @class
|
||||
@@ -49,7 +49,7 @@ export let Vector = (function() {
|
||||
* @param {number} dec2 - Declination (Dec) coordinate of the center in degrees.
|
||||
* @param {ShapeOptions} options - Options for configuring the vector. Additional properties:
|
||||
* @param {boolean} [options.arrow=false] - Add an arrow pointing from (ra1, dec1) to (ra2, dec2)
|
||||
*
|
||||
*
|
||||
* @returns {Vector} - The vector shape object
|
||||
*/
|
||||
let Vector = function(ra1, dec1, ra2, dec2, options) {
|
||||
@@ -57,6 +57,7 @@ export let Vector = (function() {
|
||||
this.color = options['color'] || undefined;
|
||||
this.opacity = options['opacity'] || undefined;
|
||||
this.lineWidth = options['lineWidth'] || undefined;
|
||||
this.selectionLineWidth = options["selectionLineWidth"] || undefined;
|
||||
this.selectionColor = options["selectionColor"] || '#00ff00';
|
||||
this.hoverColor = options["hoverColor"] || undefined;
|
||||
this.arrow = options["arrow"] === undefined ? false : options["arrow"];
|
||||
@@ -81,16 +82,19 @@ export let Vector = (function() {
|
||||
isFootprint: Polyline.prototype.isFootprint,
|
||||
show: Polyline.prototype.show,
|
||||
hide: Polyline.prototype.hide,
|
||||
|
||||
|
||||
select: Polyline.prototype.select,
|
||||
deselect: Polyline.prototype.deselect,
|
||||
|
||||
|
||||
hover: Polyline.prototype.hover,
|
||||
unhover: Polyline.prototype.unhover,
|
||||
|
||||
|
||||
getLineWidth: Polyline.prototype.getLineWidth,
|
||||
setLineWidth: Polyline.prototype.setLineWidth,
|
||||
|
||||
getSelectionLineWidth: Polyline.prototype.getSelectionLineWidth,
|
||||
setSelectionLineWidth: Polyline.prototype.setSelectionLineWidth,
|
||||
|
||||
setColor: Polyline.prototype.setColor,
|
||||
setSelectionColor: Polyline.prototype.setSelectionColor,
|
||||
setHoverColor: Polyline.prototype.setHoverColor,
|
||||
@@ -105,7 +109,7 @@ export let Vector = (function() {
|
||||
const v2 = view.aladin.world2pix(this.ra2, this.dec2);
|
||||
if (!v2)
|
||||
return false;
|
||||
|
||||
|
||||
const xmin = Math.min(v1[0], v2[0]);
|
||||
const xmax = Math.max(v1[0], v2[0]);
|
||||
const ymin = Math.min(v1[1], v2[1]);
|
||||
@@ -120,6 +124,10 @@ export let Vector = (function() {
|
||||
if (!this.lineWidth) {
|
||||
this.lineWidth = (this.overlay && this.overlay.lineWidth) || 2;
|
||||
}
|
||||
let drawingLineWidth = this.lineWidth;
|
||||
if (this.isSelected && this.selectionLineWidth) {
|
||||
drawingLineWidth = this.selectionLineWidth;
|
||||
}
|
||||
|
||||
// too small
|
||||
if(!noSmallCheck) {
|
||||
@@ -137,7 +145,7 @@ export let Vector = (function() {
|
||||
ctx.strokeStyle = baseColor;
|
||||
}
|
||||
|
||||
ctx.lineWidth = this.lineWidth;
|
||||
ctx.lineWidth = drawingLineWidth;
|
||||
ctx.globalAlpha = this.opacity;
|
||||
|
||||
ctx.beginPath();
|
||||
@@ -147,7 +155,7 @@ export let Vector = (function() {
|
||||
if (this.arrow) {
|
||||
// draw the arrow
|
||||
var angle, x, y, xh, yh;
|
||||
var arrowRad = this.lineWidth * 3;
|
||||
var arrowRad = drawingLineWidth * 3;
|
||||
|
||||
angle = Math.atan2(v2[1] - v1[1], v2[0] - v1[0])
|
||||
xh = v2[0];
|
||||
@@ -192,7 +200,7 @@ export let Vector = (function() {
|
||||
|
||||
xy1 = {x: xy1[0], y: xy1[1]};
|
||||
xy2 = {x: xy2[0], y: xy2[1]};
|
||||
|
||||
|
||||
// Check if line segment intersects with the bounding box
|
||||
if (Polyline.segmentIntersectsBox(xy1, xy2, x, y, w, h)) {
|
||||
return true;
|
||||
|
||||
|
Before Width: | Height: | Size: 49 KiB |
|
Before Width: | Height: | Size: 59 KiB |
|
Before Width: | Height: | Size: 188 KiB |
|
Before Width: | Height: | Size: 219 KiB |
|
Before Width: | Height: | Size: 38 KiB |
|
Before Width: | Height: | Size: 34 KiB |
|
Before Width: | Height: | Size: 140 KiB |
|
Before Width: | Height: | Size: 128 KiB |
|
Before Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 28 KiB |
|
Before Width: | Height: | Size: 133 KiB |
|
Before Width: | Height: | Size: 118 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 83 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 92 KiB |
|
Before Width: | Height: | Size: 352 KiB |
|
Before Width: | Height: | Size: 350 KiB |
|
Before Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 229 KiB |