// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File Catalog
*
* Author: Thomas Boch[CDS]
*
*****************************************************************************/
import { Source } from "./Source.js";
import { Color } from "./Color.js";
import { Utils } from "./Utils";
import { Coo } from "./libs/astro/coo.js";
import { VOTable } from "./vo/VOTable.js";
import { ObsCore } from "./vo/ObsCore.js";
import A from "./A.js";
import { Polyline } from "./shapes/Polyline.js";
import { Vector } from "./shapes/Vector.js";
import { Ellipse } from "./shapes/Ellipse.js";
import { Circle } from "./shapes/Circle.js";
import { Footprint } from "./Footprint.js";
/**
* Represents options for configuring a catalog.
*
* @typedef {Object} CatalogOptions
* @property {string} url - The URL of the catalog.
* @property {string} [name="catalog"] - The name of the catalog.
* @property {string} [color] - The color associated with the catalog.
* @property {number} [sourceSize=8] - The size of the sources in the catalog.
* @property {string|Function|Image|HTMLCanvasElement|HTMLImageElement} [shape="square"] - The shape of the sources (can be, "square", "circle", "plus", "cross", "rhomb", "triangle").
* @property {number} [limit] - The maximum number of sources to display.
* @property {string|Function} [onClick] - Whether the source data appears as a table row or a in popup. Can be 'showTable' string, 'showPopup' string or a custom user defined function that handles the click.
* @property {boolean} [readOnly=false] - Whether the catalog is read-only.
* @property {string} [raField] - The ID or name of the field holding Right Ascension (RA).
* @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} [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.
* @property {string} [labelColor=color] - The color of the source labels.
* @property {string} [labelFont="10px sans-serif"] - The font for the source labels.
*/
export let Catalog = (function () {
/**
* Represents a catalog with configurable options for display and interaction.
*
* @class
* @constructs Catalog
* @param {CatalogOptions} options - Configuration options for the catalog.
*
* @example
* const catalogOptions = {
* url: "https://example.com/catalog",
* name: "My Catalog",
* color: "#ff0000",
* sourceSize: 10,
* markerSize: 15,
* shape: "circle",
* limit: 1000,
* onClick: (source) => {
* // handle sources
* },
* readOnly: true,
* raField: "ra",
* decField: "dec",
* filter: (source) => source.mag < 15,
* selectionColor: "#00ff00",
* hoverColor: "#ff00ff",
* displayLabel: true,
* labelColor: "#00ff00",
* labelFont: "12px Arial"
* };
* const myCatalog = new Catalog(catalogOptions);
*/
function Catalog(options) {
options = options || {};
this.url = options.url;
this.name = options.name || "catalog";
this.color = options.color || Color.getNextColor();
this.sourceSize = options.sourceSize || 8;
this.markerSize = options.sourceSize || 12;
this.selectSize = this.sourceSize;
this.shape = options.shape || "square";
this.maxNbSources = options.limit || undefined;
this.onClick = options.onClick || undefined;
this.readOnly = options.readOnly || false;
this.raField = options.raField || undefined; // ID or name of the field holding RA
this.decField = options.decField || undefined; // ID or name of the field holding dec
// allows for filtering of sources
this.filterFn = options.filter || undefined; // TODO: do the same for catalog
this.selectionColor = options.selectionColor || "#00ff00";
this.hoverColor = options.hoverColor || this.color;
this.displayLabel = options.displayLabel || false;
this.labelColor = options.labelColor || this.color;
this.labelFont = options.labelFont || "10px sans-serif";
if (this.displayLabel) {
this.labelColumn = options.labelColumn;
if (!this.labelColumn) {
this.displayLabel = false;
}
}
this.showFieldCallback = {}; // callbacks when the user clicks on a cell in the measurement table associated
this.fields = undefined;
this.uuid = Utils.uuidv4();
this.type = "catalog";
this.indexationNorder = 5; // à quel niveau indexe-t-on les sources
this.sources = [];
this.ra = [];
this.dec = [];
this.footprints = [];
// create this.cacheCanvas
// 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;
}
Catalog.createShape = function (shapeName, color, sourceSize) {
if (
shapeName instanceof Image ||
shapeName instanceof HTMLCanvasElement
) {
// in this case, the shape is already created
return shapeName;
}
var c = document.createElement("canvas");
c.width = c.height = sourceSize;
var ctx = c.getContext("2d");
ctx.beginPath();
ctx.strokeStyle = color;
ctx.lineWidth = 2.0;
if (shapeName == "plus") {
ctx.moveTo(sourceSize / 2, 0);
ctx.lineTo(sourceSize / 2, sourceSize);
ctx.stroke();
ctx.moveTo(0, sourceSize / 2);
ctx.lineTo(sourceSize, sourceSize / 2);
ctx.stroke();
} else if (shapeName == "cross") {
ctx.moveTo(0, 0);
ctx.lineTo(sourceSize - 1, sourceSize - 1);
ctx.stroke();
ctx.moveTo(sourceSize - 1, 0);
ctx.lineTo(0, sourceSize - 1);
ctx.stroke();
} else if (shapeName == "rhomb") {
ctx.moveTo(sourceSize / 2, 0);
ctx.lineTo(0, sourceSize / 2);
ctx.lineTo(sourceSize / 2, sourceSize);
ctx.lineTo(sourceSize, sourceSize / 2);
ctx.lineTo(sourceSize / 2, 0);
ctx.stroke();
} else if (shapeName == "triangle") {
ctx.moveTo(sourceSize / 2, 0);
ctx.lineTo(0, sourceSize - 1);
ctx.lineTo(sourceSize - 1, sourceSize - 1);
ctx.lineTo(sourceSize / 2, 0);
ctx.stroke();
} else if (shapeName == "circle") {
ctx.arc(
sourceSize / 2,
sourceSize / 2,
sourceSize / 2 - 1,
0,
2 * Math.PI,
true
);
ctx.stroke();
} else {
// default shape: square
ctx.moveTo(1, 0);
ctx.lineTo(1, sourceSize - 1);
ctx.lineTo(sourceSize - 1, sourceSize - 1);
ctx.lineTo(sourceSize - 1, 1);
ctx.lineTo(1, 1);
ctx.stroke();
}
return c;
};
// find RA, Dec fields among the given fields
//
// @param fields: list of objects with ucd, unit, ID, name attributes
// @param raField: index or name of right ascension column (might be undefined)
// @param decField: index or name of declination column (might be undefined)
//
function findRADecFields(fields, raField, decField) {
var raFieldIdx, decFieldIdx;
raFieldIdx = decFieldIdx = null;
// first, look if RA/DEC fields have been already given
if (raField) {
// ID or name of RA field given at catalogue creation
for (var l = 0, len = fields.length; l < len; l++) {
var field = fields[l];
if (Utils.isInt(raField) && raField < fields.length) {
// raField can be given as an index
raFieldIdx = raField;
break;
}
if (
(field.ID && field.ID === raField) ||
(field.name && field.name === raField)
) {
raFieldIdx = l;
break;
}
}
}
if (decField) {
// ID or name of dec field given at catalogue creation
for (var l = 0, len = fields.length; l < len; l++) {
var field = fields[l];
if (Utils.isInt(decField) && decField < fields.length) {
// decField can be given as an index
decFieldIdx = decField;
break;
}
if (
(field.ID && field.ID === decField) ||
(field.name && field.name === decField)
) {
decFieldIdx = l;
break;
}
}
}
// if not already given, let's guess position columns on the basis of UCDs
for (var l = 0, len = fields.length; l < len; l++) {
if (raFieldIdx != null && decFieldIdx != null) {
break;
}
var field = fields[l];
if (!raFieldIdx) {
if (field.ucd) {
var ucd = field.ucd.toLowerCase().trim();
if (
ucd.indexOf("pos.eq.ra") == 0 ||
ucd.indexOf("pos_eq_ra") == 0
) {
raFieldIdx = l;
continue;
}
}
}
if (!decFieldIdx) {
if (field.ucd) {
var ucd = field.ucd.toLowerCase().trim();
if (
ucd.indexOf("pos.eq.dec") == 0 ||
ucd.indexOf("pos_eq_dec") == 0
) {
decFieldIdx = l;
continue;
}
}
}
}
// still not found ? try some common names for RA and Dec columns
if (raFieldIdx == null && decFieldIdx == null) {
for (var l = 0, len = fields.length; l < len; l++) {
var field = fields[l];
var name = field.name || field.ID || "";
name = name.toLowerCase();
if (raFieldIdx === null) {
if (
name.indexOf("ra") == 0 ||
name.indexOf("_ra") == 0 ||
name.indexOf("ra(icrs)") == 0 ||
name.indexOf("alpha") == 0
) {
raFieldIdx = l;
continue;
}
}
if (decFieldIdx === null) {
if (
name.indexOf("dej2000") == 0 ||
name.indexOf("_dej2000") == 0 ||
name.indexOf("de") == 0 ||
name.indexOf("de(icrs)") == 0 ||
name.indexOf("_de") == 0 ||
name.indexOf("delta") == 0
) {
decFieldIdx = l;
continue;
}
}
}
}
// last resort: take two first fieds
if (raFieldIdx == null || decFieldIdx == null) {
raFieldIdx = 0;
decFieldIdx = 1;
}
return [raFieldIdx, decFieldIdx];
}
Catalog.parseFields = function (fields, raField, decField) {
// This votable is not an obscore one
let [raFieldIdx, decFieldIdx] = findRADecFields(
fields,
raField,
decField
);
let parsedFields = {};
let fieldIdx = 0;
fields.forEach((field) => {
let key = field.name ? field.name : field.ID;
key = key.split(" ").join("_");
let nameField;
if (fieldIdx == raFieldIdx) {
nameField = "ra";
} else if (fieldIdx == decFieldIdx) {
nameField = "dec";
} else {
nameField = key;
}
// remove the space character
parsedFields[nameField] = {
name: key,
idx: fieldIdx,
};
fieldIdx++;
});
return parsedFields;
};
// return an array of Source(s) from a VOTable url
// callback function is called each time a TABLE element has been parsed
Catalog.parseVOTable = function (
url,
successCallback,
errorCallback,
maxNbSources,
useProxy,
raField,
decField
) {
let rowIdx = 0;
new VOTable(
url,
(rsc) => {
let table = VOTable.parseRsc(rsc);
if (!table || !table.rows || !table.fields) {
errorCallback(
"Parsing error of the votable located at: " + url
);
return;
}
let { fields, rows } = table;
try {
fields = ObsCore.parseFields(fields);
//fields.subtype = "ObsCore";
} catch (e) {
// It is not an ObsCore table
fields = Catalog.parseFields(fields, raField, decField);
}
let sources = [];
//let footprints = [];
var coo = new Coo();
rows.every((row) => {
let ra, dec, region;
var mesures = {};
for (const [fieldName, field] of Object.entries(fields)) {
if (fieldName === "s_region") {
// Obscore s_region param
region = row[field.idx];
} else if (fieldName === "ra" || fieldName === "s_ra") {
ra = row[field.idx];
} else if (
fieldName === "dec" ||
fieldName === "s_dec"
) {
dec = row[field.idx];
}
var key = field.name;
mesures[key] = row[field.idx];
}
let source = null;
if (ra !== undefined && ra !== null && dec !== undefined && dec !== null) {
if (!Utils.isNumber(ra) || !Utils.isNumber(dec)) {
coo.parse(ra + " " + dec);
ra = coo.lon;
dec = coo.lat;
}
source = new Source(
parseFloat(ra),
parseFloat(dec),
mesures
);
source.rowIdx = rowIdx;
sources.push(source);
if (maxNbSources && sources.length == maxNbSources) {
return false;
}
}
rowIdx++;
return true;
});
if (successCallback) {
successCallback({
sources,
//footprints,
fields,
});
}
},
errorCallback,
useProxy,
raField,
decField
);
};
/**
* Set the shape of the sources
*
* @memberof Catalog
*
* @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 {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.
* 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.
* @param {string|Function} [options.onClick] - Whether the source data appears as a table row or a in popup. Can be 'showTable' string, 'showPopup' string or a custom user defined function that handles the click.
*/
Catalog.prototype.updateShape = function (options) {
options = options || {};
this.color = options.color || this.color || Color.getNextColor();
this.selectionColor = options.selectionColor || this.selectionColor || Color.getNextColor();
this.hoverColor = options.hoverColor || this.hoverColor || this.color;
this.sourceSize = options.sourceSize || this.sourceSize || 6;
this.shape = options.shape || this.shape || "square";
this.onClick = options.onClick || this.onClick;
this._shapeIsFunction = false; // if true, the shape is a function drawing on the canvas
this._shapeIsFootprintFunction = false;
if (typeof this.shape === "function") {
this._shapeIsFunction = true;
// do not need to compute any canvas
// there is a possibility that the user gives a function returning shape objects such as
// circle, polyline, line or even footprints
// we must test that here and precompute all those objects and add them as footprints to draw
// at this point, we do not have to draw any sources
} else if (
this.shape instanceof Image ||
this.shape instanceof HTMLCanvasElement
) {
this.sourceSize = this.shape.width;
}
this.selectSize = this.sourceSize + 2;
this.cacheCanvas = Catalog.createShape(
this.shape,
this.color,
this.sourceSize
);
this.cacheSelectCanvas = Catalog.createShape(
this.shape,
this.selectionColor,
this.selectSize
);
this.cacheHoverCanvas = Catalog.createShape(
this.shape,
this.hoverColor,
this.selectSize
);
this.reportChange();
};
/**
* Add sources to the catalog
*
* @memberof Catalog
*
* @param {Source[]} sources - An array of sources or only one source to add
*/
Catalog.prototype.addSources = function (sources) {
// make sure we have an array and not an individual source
sources = [].concat(sources);
if (sources.length === 0) {
return;
}
if (!this.fields) {
// Case where we create a catalog from scratch
// We have to define its fields by looking at the source data
let fields = [];
for (var key in sources[0].data) {
fields.push({ name: key });
}
fields = Catalog.parseFields(fields, this.raField, this.decField);
this.setFields(fields);
}
this.sources = this.sources.concat(sources);
for (var k = 0, len = sources.length; k < len; k++) {
sources[k].setCatalog(this);
// Create columns oriented ra and dec
this.ra.push(sources[k].ra);
this.dec.push(sources[k].dec);
}
this.recomputeFootprints = true;
this.reportChange();
};
Catalog.prototype.computeFootprints = function (sources) {
let footprints = [];
if (this._shapeIsFunction) {
for (const source of sources) {
try {
let shapes = this.shape(source);
if (shapes) {
shapes = [].concat(shapes);
// 1. return of the shape func is an image
if (shapes.length == 1 && (shapes[0] instanceof Image || shapes[0] instanceof HTMLCanvasElement)) {
source.setImage(shapes[0]);
// 2. return of the shape is a set of shapes or a footprint
} else {
let footprint;
if (shapes.length == 1 && shapes[0] instanceof Footprint) {
footprint = shapes[0];
} else {
footprint = new Footprint(shapes, source);
}
this._shapeIsFootprintFunction = true;
footprint.setCatalog(this);
// store the footprints
footprints.push(footprint);
}
}
} catch (e) {
// do not create the footprint
console.warn("Return of shape function could not be interpreted as a footprint");
continue;
}
}
}
return footprints;
};
Catalog.prototype.setFields = function (fields) {
this.fields = fields;
};
/// This add a callback when the user clicks on the field column in the measurementTable
Catalog.prototype.addShowFieldCallback = function (field, callback) {
this.showFieldCallback[field] = callback;
};
/**
* Create sources from a 2d array and add them to the catalog
*
* @memberof Catalog
*
* @param {String[]} columnNames - array with names of the columns
* @param {String[][]|number[][]} array - 2D-array, each item being a 1d-array with the same number of items as columnNames
*/
Catalog.prototype.addSourcesAsArray = function (columnNames, array) {
var fields = [];
for (var colIdx = 0; colIdx < columnNames.length; colIdx++) {
fields.push({ name: columnNames[colIdx] });
}
fields = Catalog.parseFields(fields, this.raField, this.decField);
this.setFields(fields);
var raFieldIdx, decFieldIdx;
raFieldIdx = fields["ra"].idx;
decFieldIdx = fields["dec"].idx;
var newSources = [];
var coo = new Coo();
var ra, dec, row, dataDict;
for (var rowIdx = 0; rowIdx < array.length; rowIdx++) {
row = array[rowIdx];
if (
Utils.isNumber(row[raFieldIdx]) &&
Utils.isNumber(row[decFieldIdx])
) {
ra = parseFloat(row[raFieldIdx]);
dec = parseFloat(row[decFieldIdx]);
} else {
coo.parse(row[raFieldIdx] + " " + row[decFieldIdx]);
ra = coo.lon;
dec = coo.lat;
}
dataDict = {};
for (var colIdx = 0; colIdx < columnNames.length; colIdx++) {
dataDict[columnNames[colIdx]] = row[colIdx];
}
newSources.push(A.source(ra, dec, dataDict));
}
this.addSources(newSources);
};
/**
* Get all the sources
*
* @memberof Catalog
*
* @returns {Source[]} - an array of all the sources in the catalog object
*/
Catalog.prototype.getSources = function () {
return this.sources;
};
Catalog.prototype.getFootprints = function () {
return this.footprints;
};
/**
* Select all the source catalog
*
* @memberof Catalog
*/
Catalog.prototype.selectAll = function () {
if (!this.sources) {
return;
}
for (var k = 0; k < this.sources.length; k++) {
this.sources[k].select();
}
};
/**
* Unselect all the source of the catalog
*
* @memberof Catalog
*/
Catalog.prototype.deselectAll = function () {
if (!this.sources) {
return;
}
for (var k = 0; k < this.sources.length; k++) {
this.sources[k].deselect();
}
};
/**
* 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) {
if (idx < this.sources.length) {
return this.sources[idx];
} else {
return null;
}
};
Catalog.prototype.setView = function (view, idx) {
this.view = view;
this.view.catalogs.push(this);
this.view.insertOverlay(this, idx);
this.reportChange();
};
/**
* Set the color of the catalog
*
* @memberof Catalog
*
* @param {String} - the new color
*/
Catalog.prototype.setColor = function (color) {
this.color = color;
this.updateShape();
};
/**
* Set the color of selected sources
*
* @memberof Catalog
*
* @param {String} - the new color
*/
Catalog.prototype.setSelectionColor = function (color) {
this.selectionColor = color;
this.updateShape();
};
/**
* Set the color of hovered sources
*
* @memberof Catalog
*
* @param {String} - the new color
*/
Catalog.prototype.setHoverColor = function (color) {
this.hoverColor = color;
this.updateShape();
};
/**
* Set the size of the catalog sources
*
* @memberof Catalog
*
* @param {number} - the new size
*/
Catalog.prototype.setSourceSize = function (sourceSize) {
// will be discarded in updateShape if the shape is an Image
this.sourceSize = sourceSize;
this.updateShape();
};
/**
* 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.
*/
Catalog.prototype.setShape = function (shape) {
this.shape = shape;
this.updateShape();
};
/**
* Get the size of the catalog sources
*
* @memberof Catalog
*
* @returns {number} - the size of the sources
*/
Catalog.prototype.getSourceSize = function () {
return this.sourceSize;
};
/**
* Remove a specific source from the catalog
*
* @memberof Catalog
*
* @param {Source} - the source to remove
*/
Catalog.prototype.remove = function (source) {
var idx = this.sources.indexOf(source);
if (idx < 0) {
return;
}
this.sources[idx].deselect();
this.sources.splice(idx, 1);
this.ra.splice(idx, 1);
this.dec.splice(idx, 1);
this.recomputeFootprints = true;
this.reportChange();
};
/**
* Clear all the sources from the catalog
*
* @memberof Catalog
*/
Catalog.prototype.removeAll = Catalog.prototype.clear = function () {
// TODO : RAZ de l'index
this.sources = [];
this.ra = [];
this.dec = [];
this.footprints = [];
this.reportChange();
};
Catalog.prototype.draw = function (ctx, width, height) {
if (!this.isShowing) {
return;
}
// tracé simple
ctx.strokeStyle = this.color;
// Draw the footprints first
this.drawFootprints(ctx);
if (this._shapeIsFunction) {
ctx.save();
}
const drawnSources = this.drawSources(ctx, width, height);
if (this._shapeIsFunction) {
ctx.restore();
}
// Draw labels
if (this.displayLabel) {
ctx.fillStyle = this.labelColor;
ctx.font = this.labelFont;
drawnSources.forEach((s) => {
this.drawSourceLabel(s, ctx);
});
}
};
Catalog.prototype.drawSources = function (ctx, width, height) {
let inside = [];
if (!this.sources) {
return;
}
let xy = this.view.wasm.worldToScreenVec(this.ra, this.dec);
let drawSource = (s, idx) => {
s.x = xy[2 * idx];
s.y = xy[2 * idx + 1];
self.drawSource(s, ctx, width, height);
inside.push(s);
};
let self = this;
this.sources.forEach(function (s, idx) {
if (xy[2 * idx] && xy[2 * idx + 1]) {
if (self.filterFn) {
if(!self.filterFn(s)) {
s.hide()
} else {
s.show()
drawSource(s, idx)
}
} else {
drawSource(s, idx)
}
}
});
return inside;
};
Catalog.prototype.drawSource = function (s, ctx, width, height) {
if (!s.isShowing) {
return false;
}
if (s.hasFootprint && !s.tooSmallFootprint) {
return false;
}
if (s.x <= width && s.x >= 0 && s.y <= height && s.y >= 0) {
if (this._shapeIsFunction && !this._shapeIsFootprintFunction) {
this.shape(s, ctx, this.view.getViewParams());
} else if (s.image) {
ctx.drawImage(
s.image,
s.x - s.image.width / 2,
s.y - s.image.height / 2
);
} else if (s.marker && s.useMarkerDefaultIcon) {
ctx.drawImage(
this.cacheMarkerCanvas,
s.x - this.sourceSize / 2,
s.y - this.sourceSize / 2
);
} else if (s.isSelected) {
ctx.drawImage(
this.cacheSelectCanvas,
s.x - this.selectSize / 2,
s.y - this.selectSize / 2
);
} else if (s.isHovered) {
ctx.drawImage(
this.cacheHoverCanvas,
s.x - this.selectSize / 2,
s.y - this.selectSize / 2
);
} else {
ctx.drawImage(
this.cacheCanvas,
s.x - this.cacheCanvas.width / 2,
s.y - this.cacheCanvas.height / 2
);
}
// has associated popup ?
if (s.popup) {
s.popup.setPosition(s.x, s.y);
}
return true;
}
return false;
};
Catalog.prototype.drawSourceLabel = function (s, ctx) {
if (!s || !s.isShowing || !s.x || !s.y) {
return;
}
var label = s.data[this.labelColumn];
if (!label) {
return;
}
ctx.fillText(label, s.x, s.y);
};
Catalog.prototype.drawFootprints = function (ctx) {
if (this.recomputeFootprints) {
this.footprints = this.computeFootprints(this.sources);
this.recomputeFootprints = false;
}
var f;
for (let k = 0; k < this.footprints.length; k++) {
f = this.footprints[k];
f.draw(ctx, this.view);
f.source.tooSmallFootprint = f.isTooSmall();
}
};
// callback function to be called when the status of one of the sources has changed
Catalog.prototype.reportChange = function () {
this.view && this.view.requestRedraw();
};
/**
* Show the catalog
*
* @memberof Catalog
*/
Catalog.prototype.show = function () {
if (this.isShowing) {
return;
}
this.isShowing = true;
// Dispatch to the footprints
if (this.footprints) {
this.footprints.forEach((f) => f.show());
}
this.reportChange();
};
/**
* Hide the catalog
*
* @memberof Catalog
*/
Catalog.prototype.hide = function () {
if (!this.isShowing) {
return;
}
this.isShowing = false;
if (
this.view &&
this.view.popup &&
this.view.popup.source &&
this.view.popup.source.catalog == this
) {
this.view.popup.hide();
}
// Dispatch to the footprints
if (this.footprints) {
this.footprints.forEach((f) => f.hide());
}
this.reportChange();
};
return Catalog;
})();