import { hashCode, f2v, shift, getLpLineJ, getLpLineK, KToCm } from "./utils.js";
import { FITS_HEADER } from "./fitsheader.js";
import { Constants } from "./constants.js";
import { EventFactory } from './customevents.js';
import {DOMAccessor} from './domelements.js';
import {SpectroApi} from './serverApi.js';
function changeDatabase(spectroq, spectroUI, dbname) {
spectroq.getMetadata(function(metadata) { spectroUI.initSpeciesSelection(dbname, metadata) });
spectroUI.intensityElement.value = Constants.DEFAULT_INTENSITY;
spectroUI.intensityRangeElement.value = Constants.DEFAULT_INTENSITY;
}
/**
* Changes list of spectral lines displayed in table next to the spectrum.
* Only a few lines are displayed at a time to avoid list becoming too long.
*
* @param {int} newGroupIndex index of displayed lines group
*/
function changeLinesGroup(linePlotter, spectroUI, newGroupIndex) {
spectroUI.changeGroupAction(newGroupIndex, () => {
linePlotter.plotTransitionGroup(newGroupIndex);
spectroUI.showEnergyGroupLines(linePlotter.transitionGroups[newGroupIndex]);
});
}
/**
* An object providing methods to format spectroscopic data display
* @typedef {Object} SpectroscopyFormatter
*/
class SpectroscopyFormatter {
constructor() { }
/**
* Returns a string formatting a list of spectral lines to display
* @param {array} lines an array of line objects
* @param {number} velocity redshift value (can also be a velocity) (float)
* @param {number} freqMin minimum frequency (float)
* @param {number} freqMax maximum frequency (float)
* @returns {string}
*/
linesTofile(lines, velocity, freqMin, freqMax) {
let result = lines[0].sourcefile + "\n\n";
result += " Rest Frequency (GHz)".padStart(20) + " " + "Obs Frequency (GHz)".padStart(20) +
" " + "Elow (cm-1)".padStart(15) + " " + "Eup (cm-1)".padStart(15) + " " +
"Lower state".padStart(40) + " " + "Upper state".padStart(40) + "\n";
for (const line of lines) {
const obsFreq = shift(line.frequency, velocity, FITS_HEADER.getVeloLsr()).toFixed(4);
// mark the line if it is in the interval
if (obsFreq <= freqMax && obsFreq >= freqMin) {
result += "=>";
} else {
result += " ";
}
result += line.frequency.toString().padStart(20) + " ";
result += obsFreq.toString().padStart(20) + " ";
result += line.lower_state.energy.toString().padStart(15) + " ";
result += line.upper_state.energy.toString().padStart(15) + " ";
result += this.quantumNumbers(line.lower_state).padStart(40) + " ";
result += this.quantumNumbers(line.upper_state).padStart(40) + " ";
result += "\n";
}
return result;
}
/**
* Returns the quantum numbers of a level as a string
* @param {object} level
* @returns {string}
*/
quantumNumbers(level) {
let result = "";
for (const qn of level.qns) {
result += qn.name + "=" + qn.value + " ";
}
return result;
}
}
/**
* Object managing interaction with the spectroscopy user interface
* @typedef {Object} SpectroscopyUI
* @property {object} toggler checkbox to enable/disable the UI
* @property {object} spectroscopyForm the whole form
* @property {object} redshiftElement redshift input
* @property {object} dlElement luminosity distance input
* @property {object} databaseElement database choice checkboxes
* @property {object} energyUpElement energy up input
* @property {object} energyUpUnitElement energy up unit select
* @property {object} intensityRangeElement intensity range
* @property {object} intensityElement intensity value display
* @property {object} atomCountMinElement minimum number of atoms
* @property {object} atomCountMaxElement maximum number of atoms
* @property {object} autoCompleteElement species choice with autocompletion
* @property {object} selectedSpeciesElement list of selected species
* @property {object} linesTable html table of displayed lines
* @property {object} linesTableBody html table tbody
* @property {object} linesTableContainer html table div
* @property {object} currentGroupDisplay id of currently displayed group
* @property {object} lastGroupDisplay id of last displayable group
* @property {object} surfaceInSummedSpectrum display surface of selected area in spectrum
* @property {object} lineLuminosityElement display value of line luminosity
* @property {object} referenceLineElement reference line used for luminosity calculation
* @property {array} groupsInfos list of tuples of min/max energy value for each lines group
* @property {number} currentGroup index of currently displayed group (int)
* @property {number} referenceFreq reference frequency value for luminosity calculation (float)
*/
class SpectroscopyUI {
constructor() {
const linesTable = "lines-table";
this.toggler = document.getElementById("toggle-lines-search");
this.togglerArea = document.getElementById("toggler-area");
this.spectroscopyForm = document.getElementById("spectroscopy-menu");
this.redshiftElement = DOMAccessor.getRedshiftField();
this.velocityElement = DOMAccessor.getVelocityField();
this.dlElement = DOMAccessor.getDlField();
this.databaseElement = document.getElementsByName("spectrodatasource");
this.energyUpElement = document.getElementById("energyup");
this.energyUpUnitElement = document.getElementById("energyupunit");
this.intensityRangeElement = document.getElementById("intensityrange");
this.intensityElement = document.getElementById("intensity");
this.atomCountMinElement = document.getElementById("atomcount-min");
this.atomCountMaxElement = document.getElementById("atomcount-max");
this.autoCompleteElement = "autoComplete";
this.selectedSpeciesElement = "selectedspecies";
//this.energyGroups = document.getElementById(parameterSources['energygroups']);
this.linesTable = document.getElementById(linesTable);
this.linesTableBody = document.getElementById(linesTable + "-tbody");
this.linesTableContainer = document.getElementById(linesTable + "-container");
this.currentGroupDisplay = document.getElementById("current-group");
this.lastGroupDisplay = document.getElementById("last-group");
this.surfaceInSummedSpectrum = "selected-surface";
this.lineLuminosityElement = document.getElementById("lineluminosity-value");
this.lineLuminosityWarningElement = document.getElementById("lineluminosity-warning")
this.referenceLineElement = document.getElementById("reference-line");
this.groupsInfos = [];
this.currentGroup = 0;
this.referenceFreq = null;
this._selectedLine = null;
this._selectedSurface = null;
this._lines = [];
// true is database is enable
this._databases = {};
// currently selected line in table for luminosity calculation
this._selectedLine = "";
this.toggleDatabaseSelector = this.toggleDatabaseSelector.bind(this);
}
setSelectedSurface(value){
this._selectedSurface = value;
}
addDatabase(name, enabled){
this._databases[name] = enabled;
}
/**
* Toggles on/off the radio button to search lines in a spectro db
* @param {string} name name of spectro db
* @param {boolean} isEnabled status of spectro db
*/
toggleDatabaseSelector(name, isEnabled) {
let status = "inline";
if (!isEnabled) {
status = "none";
}
document.getElementById(name + "datasourcespan").style.display = status;
}
/**
* Displays line luminosity L'_line value in the dedicated DOM element
*/
getLineLuminosityValue(lineFrequency) {
// alert message if luminosity value is not a number whereas velocity and redshift are defined
if(this._selectedSurface === null){
return "";
}else{
if (isNaN(parseFloat(this.dlElement.value)) &&
!isNaN(parseFloat(this.redshiftElement.value)) &&
!isNaN(parseFloat(this.velocityElement.value))) {
return "Value of DL field is not a number, luminosity can not be calculated";
} else {
let result = null;
if(FITS_HEADER.isSpectrumInK()){
result = getLpLineK(this._selectedSurface, parseFloat(this.dlElement.value),
parseFloat(this.redshiftElement.value),FITS_HEADER.cdelt1, FITS_HEADER.cdelt2,
FITS_HEADER.bmin, FITS_HEADER.bmaj, DOMAccessor.getSummedChartCoordinatesArray());
}else{
result = getLpLineJ(this._selectedSurface, lineFrequency,
(this.dlElement.value), parseFloat(this.redshiftElement.value));
}
return `${result.toExponential(2)} K.km/s.pc<sup>2</sup>`;
}
}
}
/**
* Disables intensity and energy configuration elements
*/
disableExtraFields() {
$("#intensityrange").prop("disabled", true);
$("#energyup").prop("disabled", true);
$("#energyupunit").prop("disabled", true);
document.getElementById("intensityrangelabel").className = "disabled";
document.getElementById("energyuplabel").className = "disabled";
document.getElementById("energyupunit").className = "disabled";
}
/**
* Enables intensity and energy configuration elements
*/
enableExtraFields() {
$("#intensityrange").prop("disabled", false);
$("#energyup").prop("disabled", false);
$("#energyupunit").prop("disabled", false);
document.getElementById("intensityrangelabel").className = "";
document.getElementById("energyuplabel").className = "";
document.getElementById("energyupunit").className = "";
}
/**
* Returns value of integral of selected area in summed pixels spectrum
* @returns Value of integral of selected area in summed pixels spectrum
*/
getSurfaceInSummedSpectrum() {
return parseFloat(document.getElementById(this.surfaceInSummedSpectrum).innerText);
}
/**
* Displays the index of the currently displayed lines group
* @param {number} groupIndex index of group to display (int)
*/
setCurrentGroup(groupIndex) {
this.currentGroupDisplay.textContent = groupIndex;
}
/**
* Displays the index of the last lines group
* @param {number} groupIndex index of group to display (int)
*/
setLastGroup(groupIndex) {
this.lastGroupDisplay.textContent = groupIndex;
}
/**
* Returns the visibility status of the spectroscopy user interface
* @returns {boolean}
*/
isEnabled() {
return true;//this.toggler.checked;
}
/**
* Hides checkbox to enable/disable spectrosqcopy menu
*/
hideToggler(){
this.togglerArea.style.visibility = "hidden";
}
/**
* Shows checkbox to enable/disable spectrosqcopy menu
*/
showToggler(){
this.togglerArea.style.visibility = "";
}
/**
* Makes the spectroscopy user interface visible
*/
showSpectroscopyMenu() {
this.spectroscopyForm.className = "";
}
/**
* Hides the spectroscopy user interface visible
*/
hideSpectroscopyMenu() {
this.spectroscopyForm.className = "hidden";
}
/**
* Hides the table of lines group
*/
hideEnergyGroupLines() {
this.linesTableContainer.classList.add("hidden");
}
/**
* Returns the redshift value ( either redshift or velocity ) and the type of returnd value
* in an object
* @returns {object}
*/
/*getShift() {
try{
const velocity = this.getVelocity();
const redshift = this.getRedshift();
let result = {}
if (!isNaN(velocity) && velocity < 30000) {
result["shiftValue"] = velocity;
result["shiftType"] = Constants.VELOCITY_SHIFT_TYPE;
} else if (!isNaN(redshift)) {
result["shiftValue"] = redshift;
result["shiftType"] = Constants.REDSHIFT_SHIFT_TYPE;
}
return result;
}catch(e){
throw(e);
}
}*/
/**
* Display a table containing found spectral lines
* @param {*} lines
*/
showEnergyGroupLines(lines) {
this._lines = lines;
this.refreshEnergyGroupLines();
}
refreshEnergyGroupLines(){
const lines = this._lines;
let self = this;
// returns the luminosity button
let getButton = function(){
// action when button is clicked
let buttonClick = function(event){
let tr = event.target.parentNode.parentNode;
let alltrs = tr.parentNode.children;
for(let i=0; i < alltrs.length; i++){
if(alltrs[i] !== tr){
let buttonTd = alltrs[i].cells[2];
while (buttonTd.hasChildNodes()) {
buttonTd.removeChild(buttonTd.lastChild);
}
buttonTd.appendChild(getButton());
}
}
tr.cells[2].innerHTML = self.getLineLuminosityValue(tr.cells[1].textContent);
};
let button = document.createElement("button");
button.textContent = "Compute";
button.classList.add('btn', 'btn-secondary');
button.addEventListener('click', buttonClick);
return button;
};
if (lines.length > 0) {
this.linesTableContainer.classList.remove("hidden");
lines.sort(function (a, b) { return a.obsFrequency - b.obsFrequency > 0 });
while (this.linesTableBody.hasChildNodes()) {
//tr element
let element = this.linesTableBody.lastChild;
//removes all tds
while (element.hasChildNodes()) {
element.removeChild(element.lastChild);
}
this.linesTableBody.removeChild(element);
}
for (let i = 0; i < lines.length; i++) {
let tr = document.createElement("tr");
let lower_state = "";
if(lines[i].lower_state.qns !== undefined){
for(let qn of lines[i].lower_state.qns){
lower_state = lower_state + qn.name + "=" + qn.value + " ";
}
let upper_state = "";
for(let qn of lines[i].upper_state.qns){
upper_state = upper_state + qn.name + "=" + qn.value + " ";
}
tr.title = "lower state : " + lower_state + " - upper state : " + upper_state;
}
tr.dataset.id = i;
let td1 = document.createElement("td");
td1.textContent = lines[i].species.formula;
if (lines[i].sourcefile !== undefined)
td1.title = lines[i].sourcefile;
let td2 = document.createElement("td");
td2.textContent = lines[i].obsFrequency.toFixed(4);
let td3 = document.createElement("td");
td3.appendChild(getButton());
// line name
tr.appendChild(td1);
// line frequency
tr.appendChild(td2);
// line luminosity
tr.appendChild(td3);
// clicked lines is selected as reference line
this.linesTableBody.appendChild(tr);
}
}
}
/**
* Sets the list of group infos. Each element in the list is a tuple containing the minimum and maximum value
* of upper_energy for the lines in the group
* @param {*} groupsInfos
*/
setGroupInfos(groupsInfos) {
this.groupsInfos = groupsInfos;
}
/**
* Changes the currently displayed group
* @param {number} newGroupIndex index of newly displayed group (int)
* @param {function} callback callback function applied to this group
*/
changeGroupAction(newGroupIndex, callback) {
if (newGroupIndex >= 0 && newGroupIndex < this.groupsInfos.length) {
this.currentGroup = newGroupIndex;
let infos = "";
// if not local db
if (this.groupsInfos[this.currentGroup][0] !== undefined) {
infos = "Upper energy between " + this.groupsInfos[this.currentGroup][0].toFixed(3)
+ " and " + this.groupsInfos[this.currentGroup][1].toFixed(3) + " cm-1";
}
callback(this.currentGroup);
}
}
/**
* Returns the minimum number of atoms in searched species
* @returns {number} an integer
*/
getAtomCountMin() {
return parseInt(this.atomCountMinElement[this.atomCountMinElement.selectedIndex].value, 10);
}
/**
* Returns the maximum number of atoms in searched species
* @returns {number} an integer
*/
getAtomCountMax() {
return parseInt(this.atomCountMaxElement[this.atomCountMaxElement.selectedIndex].value, 10);
}
/**
* Returns velocity (undefined if empty)
* @param {string} unit unit of returned value, km/s or m/s
* @returns {number} a float value
*/
getVelocity(unit="km/s") {
if(unit !== "km/s" && unit !== "m/s"){
alert(`Unknown unit ${unit} for getVelocity`);
return undefined;
}else{
if(this.velocityElement.value !== "-" && this.velocityElement.value !== "" ){
let result = new Number(this.velocityElement.value);
if (isNaN(result)){
alert("Velocity value is not a valid number.");
return undefined;
} else{
if(unit === "km/s")
return result.valueOf();
else if(unit === "m/s")
return result.valueOf() * 10**3;
}
}else
return undefined;
}
}
/**
* Sets velocity value
* @param {number} value (float)
*/
setVelocity(value) {
if (isNaN(parseFloat(value)) && value.trim() !== "") {
alert("Velocity value must be a float");
} else {
if(this.velocityElement !== null)
this.velocityElement.value = value;
}
}
/**
* Set empty content in velocity field
*/
clearVelocity() {
if(this.velocityElement !== null)
this.velocityElement.value = "";
}
/**
* Returns redshift (undefined if empty)
* @returns {number} a float value
*/
getRedshift() {
if(this.redshiftElement.value !== "-" && this.redshiftElement.value !== ""){
let result = new Number(this.redshiftElement.value);
if (isNaN(result)){
alert("Redshift value is not a valid number.");
return undefined;
}else{
return result.valueOf();
}
}else
return undefined;
//return this.redshiftElement.value;
}
/**
* Sets redshift value
* @param {number} value (float)
*/
setRedshift(value) {
if (isNaN(parseFloat(value)) && value.trim() !== "") {
alert("Redshift value must be a float");
} else {
if(this.redshiftElement !== null)
this.redshiftElement.value = value;
}
}
/**
* Sets empty content in redshift field
*/
clearRedshift() {
if(this.redshiftElement !== null)
this.redshiftElement.value = "";
}
/**
* Returns luminosity distance (undefined if empty)
* @returns {number} a float value
*/
getDl() {
let result = new Number(this.dlElement.value);
if (isNaN(result))
throw("DL value is not a valid number.");
else
return result;
}
/**
* Sets luminosity distance value
* @param {number} value (float)
*/
setDl(value) {
if(this.dlElement !== null){
if (value === null) {
this.dlElement.value = "";
} else {
const dl = parseFloat(value);
if (isNaN(dl)) {
//alert("DL value must be a float");
this.dlElement.value = "";
} else {
this.dlElement.value = dl;
}
}
}
}
/**
* Set empty content in luminosity distance field
*/
clearDl() {
if(this.dlElement !== null)
this.dlElement.value = "";
}
/**
* Returns value of hubble constant (h0)
* @returns hubble constant value
*/
getHubbleCst(){
let result = new Number(DOMAccessor.getHnotField().value);
if (isNaN(result))
throw("Hubble constant value is not a valid number.");
else
return result.valueOf();
}
/**
* Returns value of mass density (omega m)
* @returns hubble constant value
*/
getMassDensity(){
let result = new Number(DOMAccessor.getOmegaMField().value);
if (isNaN(result))
throw("Mass density value is not a valid number.");
else
return result.valueOf();
}
/**
* Returns selected database
* @returns {string}
*/
getSelectedDatabase() {
let result = "";
for (let i = 0; i < this.databaseElement.length; i++) {
if (this.databaseElement[i].checked) {
result = this.databaseElement[i].value;
}
}
return result;
}
/**
* Select a default spectro database. Local database if velolsr is defined
* or no database if it is undefined
* @param {*} velolsr
*/
selectDefaultDatabase(){
if(FITS_HEADER.isNENUFAR() && this._databases.rrl === true){
document.getElementById("rrldatasource").checked = true;
}else if(this._databases.local === true){
document.getElementById("localdatasource").checked = true;
}else{
document.getElementById("nodatasource").checked = true;
}
// disables the extended search interface in all cases
this.disableExtraFields();
}
/**
* Returns energy of upper level in cm-1
* @returns {number} a float value
*/
getEnergyUp() {
let result = undefined;
const eup = parseFloat(this.energyUpElement.value);
if (!isNaN(eup)) {
if (this.energyUpUnitElement[this.energyUpUnitElement.selectedIndex].value === "K") {
result = KToCm(eup);
} else {
result = eup;
}
}
return result;
}
/**
* Sets maximum value for upper energy in user interface
* @param {number} value (float)
*/
setEnergyUp(value) {
if (isNaN(parseFloat(value))) {
alert("Energy value must be a float");
} else {
this.energyUpElement.value = value;
}
}
/**
* Returns intensity value
* @returns {number} a float value
*/
getIntensity() {
let result = undefined;
const intensity = parseFloat(this.intensityRangeElement.value);
if (!isNaN(intensity)) {
result = intensity;
}
return result;
}
/**
* Sets value of intensity in dedicated field and in range element
* @param {foat} value
*/
setIntensity(value) {
this.intensityRangeElement.value = value;
this.intensityElement.innerText = value;
}
/**
* Returns minimum intensity value
* @returns {number} a float value
*/
getIntensityMin() {
return parseFloat(this.intensityRangeElement.min);
}
/**
* Returns maximum intensity value
* @returns {number} a float value
*/
getIntensityMax() {
return parseFloat(this.intensityRangeElement.max);
}
/**
* Returns species selected by user
* @returns {array}
*/
getSelectedSpecies() {
let result = [];
let nodes = document.querySelector("#" + this.selectedSpeciesElement).childNodes;
for (let i = 0; i < nodes.length; i++) {
// first child is span element
result.push(nodes[i].childNodes[0].innerHTML);
}
return result;
}
/**
* Initializes the species selection list by filling the autocomplete list with possible
* values
* @param {string} source source database
* @param {object} metadata metadata from database (species list)
*/
initSpeciesSelection(source, metadata) {
let molecules = [];
document.querySelector("#" + this.autoCompleteElement).value = "";
let result_area = document.querySelector("#" + this.selectedSpeciesElement);
while (result_area.hasChildNodes()) {
result_area.removeChild(result_area.lastChild);
}
for (let i = 0; i < metadata.length; i++) {
if (metadata[i].source === source) {
for (let j = 0; j < metadata[i].species.length; j++) {
molecules.push(metadata[i].species[j]);
}
}
}
new autoComplete({
selector: "#" + this.autoCompleteElement,
placeHolder: "Search for species in " + source + " database",
searchEngine: "strict",
data: {
src: molecules
},
resultsList: {
maxResults: 200,
noResults: (list, query) => {
// Create "No Results" message list element
const message = document.createElement("li");
message.setAttribute("class", "no_result");
// Add message text content
message.innerHTML = `<span>Found No Results for "${query}"</span>`;
// Add message list element to the list
list.appendChild(message);
},
},
resultItem: {
highlight: {
render: true
}
},
onSelection: (feedback) => {
document.querySelector("#" + this.autoCompleteElement).blur();
// Prepare User's Selected Value
const selection = feedback.selection.value;
const selectedSpecies = this.getSelectedSpecies();
let self = this;
// Add species if not already selected
if (!selectedSpecies.includes(selection)) {
// Replace Input value with the selected value
document.querySelector("#" + this.autoCompleteElement).value = selection;
// Render selected choice to selection div
const elementId = "species-line" + hashCode(selection);
let li = document.createElement("li");
li.id = elementId;
// span containing species name
let span = document.createElement('span');
span.innerText = selection;
// button to remove the species
let removebutton = document.createElement("button");
removebutton.textContent = "Remove";
removebutton.className = "btn btn-secondary right-menu-box-element";
removebutton.onclick = () => {
result_area.removeChild(document.getElementById(elementId));
if (self.getSelectedSpecies().length === 0) {
self.setIntensity(Constants.DEFAULT_INTENSITY);
}
};
let datasetbutton = document.createElement("button");
datasetbutton.textContent = "Dataset";
datasetbutton.className = "btn btn-secondary right-menu-box-element";
datasetbutton.onclick = () => {
datasetbutton.dispatchEvent(EventFactory.getEvent(EventFactory.EVENT_TYPES.getDataset, {
bubbles: true,
detail: {
dataset: selection,
db: Constants.SPECTRO_COLLECTIONS[self.getSelectedDatabase()]
}
})
)};
let p = document.createElement("p");
li.appendChild(span);
p.appendChild(removebutton);
p.appendChild(datasetbutton);
li.appendChild(p);
result_area.appendChild(li);
}
if (this.getSelectedSpecies().length > 0) {
this.setIntensity(this.getIntensityMin());
}
},
});
}
}
/**
* Object plotting lines on a spectrum
*
* @typedef {Object} LinePlotter
* @property {number} linesCount number of lines currently displayed (int)
* @property {array} transitions complete list of spectral lines in the searched band
* @property {array} targets spectra graphs where lines will be plotted
* @property {SpectroscopyUI} spectroUI
* @property {array} transitionGroups gorups of lines
* @property {string} shiftedLineIdPrefix prefix used when setting the id of a shifted line in the DOM
* @property {string} lineColor hexadecimal code defining the color of a line
* @property {number} obsFreqMin minimum frequency (float)
* @property {number} obsFreqMax maximum frequency (float)
*
*/
class LinePlotter {
/**
* @param {SpectroscopyUI} spectroUI object controlling the spectroscopy user interface
*/
constructor(spectroUI) {
this.linesCount = 0;
this.transitions = [];
//list of graphs where lines will be plotted
this.targets = null;
//access to user input
this.spectroUI = spectroUI;
// group of transitions
this.transitionGroups = [];
this.shiftedLineIdPrefix = "spectroline-shifted";
this.lineColor = "#ff9167";
// stores previous values of obs frequency
this.obsFreqMin = null;
this.obsFreqMax = null;
this._initEvents();
this.plotLines = this.plotLines.bind(this);
this.loadAndPlotLines = this.loadAndPlotLines.bind(this);
this.refresh = this.refresh.bind(this);
this.plotTransitionGroup = this.plotTransitionGroup.bind(this);
}
setTargets(targets){
this.targets = targets;
}
_initEvents() {
//ENTER on redshift or velocity input fields
let self = this;
DOMAccessor.getRedshiftField().addEventListener("keypress", function (event) {
if (event.key === "Enter") {
self.refresh();
}
});
DOMAccessor.getVelocityField().addEventListener("keypress", function (event) {
if (event.key === "Enter") {
self.refresh();
}
});
}
/**
* Calculates the shifted value of all the lines in the given array
* returns a deep-copy of the array with shifted line values
* @param {array} transitions array of transitions
* @param {number} value value used to calculate shift (float)
* @param {string} type redshift or velocity
* @returns {array} shifted transitions
*/
shiftLines(transitions, velocity) {
let shifted_transitions = transitions.slice();
for (let line of shifted_transitions) {
line.obsFrequency = shift(line.frequency, velocity, FITS_HEADER.getVeloLsr());
}
return shifted_transitions;
}
/**
* Called when listened object triggers a message
* @param {event} event
*/
call(event) {
this.refresh();
}
/**
* Refreshes displayed lines
*/
refresh() {
if (this.targets !== null) {
if (this.obsFreqMin != null && this.obsFreqMax != null) {
this.loadAndPlotLines(this.obsFreqMin, this.obsFreqMax, this.targets);
}
}
}
/**
* query the db to get spectral lines and plot them on the graph (calling plotLines)
* the query is done on a velociy interval
* @param {number} obsFreqMin min observed frequency in Ghz (float)
* @param {number} obsFreqMax max observed frequency in Ghz (float)
* @param {array} targets array of axes where lines will be displayed
* ( x axis of a spectrum )
*/
loadAndPlotLines(obsFreqMin, obsFreqMax, targets) {
// do the search on rest frequencies
if(this.spectroUI.getSelectedDatabase() !== "off"){
try{
const velocity = this.spectroUI.getVelocity();
this.obsFreqMin = obsFreqMin;
this.obsFreqMax = obsFreqMax;
this.targets = targets;
const frequencies = [{
min: obsFreqMin, //unshift(obsFreqMin, velocity, FITS_HEADER.getVeloLsr()),
max: obsFreqMax //unshift(obsFreqMax, velocity, FITS_HEADER.getVeloLsr())
}];
const atomCount = [{
min: this.spectroUI.getAtomCountMin(),
max: this.spectroUI.getAtomCountMax()
}];
let query = new SpectroApi();
// query data and execute this.plotLines on found spectral lines
query.getTransitions(this.spectroUI.getSelectedDatabase(), frequencies, atomCount,
this.spectroUI.getEnergyUp(), this.spectroUI.getSelectedSpecies(),
this.spectroUI.getIntensity(), this.plotLines);
}catch(e){
alert("Error while loading spectral lines : " + e);
}
}
}
/**
* Plots transitions on all the graphs registered in this.targets
* @param {array} transitions
*/
plotLines(transitions) {
this.transitions = transitions;
try{
let plotted_transitions = transitions;
const velocity = this.spectroUI.getVelocity();
plotted_transitions = this.shiftLines(transitions, velocity);
this.removeLines();
// groups lines by energy
transitions.sort((a, b) => { return a.upper_state.energy - b.upper_state.energy; });
let groups = [];
let groups_infos = [];
groups.push([]);
for (let transition of transitions) {
if (groups[groups.length - 1].length < Constants.LINES_PER_GROUP) {
groups[groups.length - 1].push(transition);
} else {
const min_upper_energy = groups[groups.length - 1][0].upper_state.energy;
const max_upper_energy = groups[groups.length - 1][groups[groups.length - 1].length - 1].upper_state.energy;
groups_infos.push([min_upper_energy, max_upper_energy]);
groups.push([transition]);
}
}
// plot lines if any
if (groups[0].length > 0) {
const min_upper_energy = groups[groups.length - 1][0].upper_state.energy;
const max_upper_energy = groups[groups.length - 1][groups[groups.length - 1].length - 1].upper_state.energy;
groups_infos.push([min_upper_energy, max_upper_energy]);
this.spectroUI.setGroupInfos(groups_infos);
this.transitionGroups = groups;
this.linesCount = plotted_transitions.length;
this.plotTransitionGroup(0);
this.spectroUI.showEnergyGroupLines(this.transitionGroups[0]);
this.spectroUI.setLastGroup(this.transitionGroups.length);
}
}catch(e){
throw(e);
}
}
/**
* Draws spectral lines on the target graph, if transitions is undefined, uses this.transitions
* This function is redundant with plotLines and its usefulness must be checked
* cf Issue 28 in gitlab
* @param {object} target graph on which lines will be plotted
* @param {array} transitions graph on which lines will be plotted
*/
plotSpectroscopicDataOnGraph(target, transitions) {
if (transitions === undefined)
transitions = this.transitions;
let axis = target.axis;
for (var i = 0; i < transitions.length; ++i) {
let value = transitions[i].frequency;
// rescale to velocity
if (target.datatype == Constants.VELOCITY_SHIFT_TYPE) {
value = f2v(transitions[i].frequency * 10 ** 9, FITS_HEADER.restfreq, this.spectroUI.getVelocity()) / 10 ** 3;
}
//let id = this.shiftedLineIdPrefix + target.datatype + i;
//this.plotLine(axis, value, id, transitions[i], this.lineColor);
}
}
/**
* Plot one transition group on all the graphs registered in this.targets
* @param {number} index index of group to plot (int)
*/
plotTransitionGroup(index) {
this.removeLines();
this.linesCount = this.transitionGroups[index].length;
let transitions = this.transitionGroups[index];
this.spectroUI.setCurrentGroup(index + 1);
const velocity = this.spectroUI.getVelocity("m/s");
let factor = 1;
if(velocity === 0 || velocity === undefined ){
factor = 1 + this.spectroUI.getRedshift() ;
}
for (let target of this.targets) {
for (let i = 0; i < transitions.length; ++i) {
let value = transitions[i].frequency;
// rescale to velocity
if (target.datatype == Constants.VELOCITY_SHIFT_TYPE) {
value = f2v(transitions[i].frequency * 10 ** 9, FITS_HEADER.restfreq * factor, velocity) / 10**3;
}
let id = this.shiftedLineIdPrefix + target.datatype + i;
this.plotLine(target.axis, value, id, transitions[i], this.lineColor);
}
}
}
/**
* Returns text description of a spectral line
* @param {object} line
* @returns {string}
*/
lineDescription(line) {
let text = line.species.formula + " ";
/*text += "f_obs=" + line.obsFrequency.toFixed(3) + " GHz ";*/
if (line.frequency != null) {
text += "f_rest=" + line.frequency.toFixed(4) + " GHz ";
}
/*if (line.lower_state.energy != null && line.upper_state.energy != null) {
text += "Lower state energy : " + line.lower_state.energy + "; ";
text += "Upper state energy : " + line.upper_state.energy + "; ";
}*/
return text;
}
/**
* Draw one spectral line on the graph
* @param {object} axis plot axis
* @param {floar} xval x coordinate
* @param {string} id id of new dom element
* @param {object} line line object
*/
plotLine(axis, xval, id, line, color) {
var self = this;
axis.addPlotBand({
from: xval,
to: xval,
color: "#FFFFFF",
zIndex: 4,
id: id,
borderWidth: 0.5,
borderColor: color,
label: {
text: self.lineDescription(line),
rotation: 90,
verticalAlign: "top",
y: 90,
x: 5,
style: {
fontWeight: "lighter",
color: "#828282"
}
}
});
}
/**
* Removes all spectral lines from an axis
*/
removeLines() {
if (this.targets !== null) {
for (let target of this.targets) {
for (let i = 0; i < this.linesCount; i++) {
try {
target.axis.removePlotBand(this.shiftedLineIdPrefix + target.datatype + i);
} catch (e) {
console.log(e);
}
}
}
this.linesCount = 0;
}
}
}
export {
SpectroscopyUI,
LinePlotter,
SpectroscopyFormatter,
changeDatabase,
changeLinesGroup
}