export { THisHelper };
import { AssessmentDatums } from "./assessment_datums.mjs";
/**
* Helper class to carry out tasks in T-HIS
*/
class THisHelper {
/**
* Maximum number of graphs
* @type {number} */
static get MAX_GRAPHS() {
return 32;
}
/**
* Maximum number of pages
* @type {number} */
static get MAX_PAGES() {
return 32;
}
/**
* Reads data into a curve, returning null if it fails
* @param {Model} model Model
* @param {string} entity_type Entity type
* @param {string|number} entity_id Entity ID (label of database history name)
* @param {string} component Component name (used in dialogue command)
* @param {boolean} [blank = false] Flag to blank the curve after reading it
* @returns {?Curve}
* @example
* // Pass in the entity_type and entity_id parameters explicitly
*
* let curve = THisHelper.ReadData(m, "node", 1, "AX");
*
* // Typically you will use the OccupantEntity entity_type and
* // id properties for the entity_type and entity_id parameters
*
* let node_x = head.GetEntityByTag(OccupantEntity.HEAD_NODE);
* let curve = THisHelper.ReadData(m, node_x.entity_type, node_x.id, "AX");
*/
static ReadData(model, entity_type, entity_id, component, blank = false) {
/* This uses dialogue commands to read data rather than the Model.GetDataFlagged()
* API method because the latter uses Model.SetFlag() which only allows entities
* to be specified using their numerical IDs and there's no way to use the database
* history name.
*/
let curve_id = Curve.FirstFreeID();
// @ts-ignore
if (!isNaN(entity_id)) {
DialogueInput(`/MODEL DATA ${entity_type} M${model.id}/${entity_id}`, component, `#${curve_id}`);
} else {
DialogueInput(`/MODEL DATA ${entity_type} M${model.id}/"${entity_id}"`, component, `#${curve_id}`);
}
if (Curve.Exists(curve_id)) {
let curve = Curve.GetFromID(curve_id);
if (blank) {
curve.RemoveFromGraph();
}
return Curve.GetFromID(curve_id);
} else {
ErrorMessage(`Unable to read ${component} data for ${entity_type} ${entity_id} in model ${model.id}`);
return null;
}
}
/**
* Sets the labels on a curve
* @param {Curve} curve Curve to set labels on
* @param {string} label Curve label
* @param {string} x_label x-axis label
* @param {string} y_label y-axis label
* @example
* THisHelper.SetLabels(curve, "My Curve", "Time (s)", "Acceleration (g)");
*/
static SetCurveLabels(curve, label, x_label, y_label) {
if (!(curve instanceof Curve)) {
throw new Error("curve must be a Curve in THisHelper.SetLabels");
}
curve.label = label;
curve.x_axis_label = x_label;
curve.y_axis_label = y_label;
}
/**
* Sets the line style of a curve
* @param {Curve|Curve[]} curve Curve or array of curves to set line style on
* @param {number} colour Colour to set line to, e.g. Colour.BLACK
* @param {number} [style=LineStyle.SOLID] Line style to set, e.g. LineStyle.DASH
* @param {number} [symbol=Symbol.NONE] Symbol, e.g. Symbol.CROSS
* @example
* // Set style on one curve
* THisHelper.SetLineStyle(curve, Colour.BLACK);
*
* // Set style on multiple curves at the same time
* THisHelper.SetLineStyle([curve1, curve2, curve3], Colour.BLACK);
*
* // Set line style as well as colour on one curve
* THisHelper.SetLineStyle(curve, Colour.BLACK, LineStyle.DASH);
*/
static SetLineStyle(curve, colour, style, symbol) {
if (curve instanceof Array) {
for (let c of curve) {
if (!(c instanceof Curve)) {
throw new Error("curve Array does not contain Curves in THisHelper.SetLineStyle");
}
}
} else if (!(curve instanceof Curve)) {
throw new Error("curve must be a Curve in THisHelper.SetLineStyle");
}
if (style == undefined) style = LineStyle.SOLID;
// @ts-ignore
if (symbol == undefined) symbol = Symbol.NONE;
if (curve instanceof Array) {
for (let c of curve) {
set_line_style(c, colour, style, symbol);
}
} else {
set_line_style(curve, colour, style, symbol);
}
function set_line_style(c, colour, style, symbol) {
c.colour = colour;
c.style = style;
c.width = LineWidth.BOLD;
c.symbol = symbol;
}
}
/**
* Blanks all the curves in all graphs
* @example
* THisHelper.BlankAllCurves();
*/
static BlankAllCurves() {
let id = Curve.HighestID();
if (id > 0) {
var c = Curve.First();
while (c) {
c.RemoveFromGraph();
c = c.Next();
}
}
}
/**
* Blanks all the datums in all graphs
* @example
* THisHelper.BlankAllDatums();
*/
static BlankAllDatums() {
let d = Datum.First();
while (d) {
d.RemoveFromGraph();
d = d.Next();
}
}
/**
* Sets the graph title
* @param {number} graph_id Index of graph
* @param {string} title Title for graph
*/
static SetGraphTitle(graph_id, title) {
/* Get the graph */
let graph = Graph.GetFromID(graph_id);
if (graph == null) {
ErrorMessage(`Graph (${graph_id}) not found in THisHelper.SetGraphTitle`);
return;
}
/* Set the title */
graph.title = title;
}
/**
* Scales the graph to show the curves and datums in the graph
*
* This is different to just using the dialogue command "/AU" because
* it scales it so there is space above and below the curve.
*
* Also if assessment datums are passed to the function it will be
* scaled to show all the datums, again with space above and below.
* @param {number} graph_id Index of graph to scale
* @param {AssessmentDatums} [assessment_datums] Datums to show
*/
static ScaleGraph(graph_id, assessment_datums) {
/* Get the graph */
let graph = Graph.GetFromID(graph_id);
if (graph == null) {
ErrorMessage(`Graph (${graph_id}) not found in THisHelper.ScaleGraph`);
return;
}
/* Get the curves on the graph */
let curve_ids = graph.GetAllCurveIDs();
/* Nothin to do if no curves or assessment datums */
if (curve_ids.length == 0 && !assessment_datums) {
return;
}
/* Get the min and max values of the curves */
let min_x = Number.MAX_VALUE;
let max_x = -Number.MAX_VALUE;
let min_y = Number.MAX_VALUE;
let max_y = -Number.MAX_VALUE;
for (let curve_id of curve_ids) {
let curve = Curve.GetFromID(curve_id);
if (curve == null) {
ErrorMessage(`Curve (${curve_id}) not found in THisHelper.ScaleGraph`);
continue;
}
min_x = Math.min(min_x, curve.xmin);
max_x = Math.max(max_x, curve.xmax);
min_y = Math.min(min_y, curve.ymin);
max_y = Math.max(max_y, curve.ymax);
}
/* Get the min and max values of the datums */
if (assessment_datums) {
min_x = Math.min(min_x, assessment_datums.MinX());
max_x = Math.max(max_x, assessment_datums.MaxX());
min_y = Math.min(min_y, assessment_datums.MinY());
max_y = Math.max(max_y, assessment_datums.MaxY());
}
/* Add a bit to the min and max y values */
if (min_y < 0) {
min_y *= 1.1;
} else if (min_y > 0) {
min_y *= 0.9;
}
if (max_y > 0) {
max_y *= 1.1;
} else if (max_y < 0) {
max_y *= 0.9;
}
/* Scale the graph
*
* If the max value is smaller than the current min value T/HIS
* sets the max to the current min and the min to the max value
* (and vice-versa when setting min values)
*
* To counter this set the values twice so that the min and max
* graph values get set correctly.
*/
for (let i = 0; i < 2; i++) {
if (max_x != -Number.MAX_VALUE) graph.xmax = max_x;
if (min_x != Number.MAX_VALUE) graph.xmin = min_x;
if (max_y != -Number.MAX_VALUE) graph.ymax = max_y;
if (min_y != Number.MAX_VALUE) graph.ymin = min_y;
}
}
/**
* Put graphs on a page, removing any that are not required
* @param {number[]} graphs Graph ids to put on page
* @param {number} page Page to put graphs on
* @param {boolean} remove_existing_graphs Switch whether to remove existing graphs from page
*/
static PutGraphsOnPage(graphs, page, remove_existing_graphs) {
if (remove_existing_graphs) {
Page.RemoveGraph(page, -1);
}
for (let g of graphs) {
Page.AddGraph(page, g);
}
/* Need to set the page layout to get it to plot the page */
Page.Layout(page, "tall");
}
/**
* Puts graphs on separate pages
* @param {number[]} graphs Graph ids to put on separate pages
* @param {number} [start_page=1] Page to start adding graphs on
* @return {number} The last page id used
*/
static PutGraphsOnSeparatePages(graphs, start_page = 1) {
/* Remove the graphs from all pages */
for (let p = 1; p <= THisHelper.MAX_PAGES; p++) {
for (let g of graphs) {
Page.RemoveGraph(p, g);
}
}
/* Add graphs to separate pages */
let page = start_page;
for (let g of graphs) {
this.PutGraphsOnPage([g], page, true);
page++;
}
return page - 1;
}
/**
* Captures an image of Graph 1 (REPORTER templates always designed to capture Graph 1).
* @param {string} fname Image filename
*/
static CaptureImage(fname) {
/* If file exists, delete it first (dialogue commands can't easily
* handle overwriting existing files) */
if (File.Exists(fname)) {
let deleted = File.Delete(fname);
if (!deleted) {
ErrorMessage(`Unable to delete existing image file: ${fname}`);
return;
}
}
DialogueInput("/IMAGE PNG24", fname, "GRAPH", "1");
}
}