This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
Adding new objects
Objects are at the core of LogarithmPlotter. Extending LogarithmPlotter to add new objects is fairly simple, but requires knowledge of JavaScript OOP and it's manipulation of Canvas Context2D.
In LogarithmPlotter, mathematical expressions are handled by the Expression class in 'mathlib.js' which allow you to execute & simplify expressions at will, as well as creating a readable string to be read by the user, and an editable one that can be edited, saved and resoted.
Below "plot" coordinates are the one based on the plot, so (0,0) is at the center of the axis.
On the other hand, "canvas" coordinate are the coordinate on the canvas, so (0,0) is at the top-left of the canvas.
To create an object, create a new file javascript files in LogarithmPlotter/qml/js/objs/
with a base like this:
/**
* LogarithmPlotter - Create graphs with logarithm scales.
* Copyright (C) 2022 Ad5001
*
* This program 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
.pragma library
.import "common.js" as Common
.import "../mathlib.js" as MathLib
.import "../parameters.js" as P
You can also import other libraries like this:
.import "../utils.js" as Utils
for string manipulation..import "../objects.js" as Objects
to interact with other objects..import "../objects.js" as Objects
to interact with other objects..import "../math/latex.js" as Latex
to use LaTeX helper functions.
There exists two kinds of objects:
- Drawable (extending the
Common.DrawableObject
class, the most primitive kind of object)- Examples: Point, Text, XCursor
- Executable (extending the
Common.ExecutableObject
class, like Drawable, but allows the constant computation of an y coordinate for an x one)- Examples: Function, Gain Bode, Phase Bode, Sequence, Repartition Function
Executable objects can be targeted by XCursors to calculate their value at a given x coordinate.
So to create a new object, choose one of the two to extend, and then create a class in objects.js extending it's class.
Methods required for your class:
static type() -> string
- Returns the type of the object (should be constant, non translatable).
- Examples: "Text", "Function"...
static displayType() -> string
- Returns the (potentially translated) string for the type of this object objects of this type (used in the editor dialog, as well as the creation buttons).
- Examples: "Texts", "Functions"...
static displayTypeMultiple() -> string
- Returns the string for multiple objects of this type (used in objects list for a category of objects).
- Examples: "Texts", "Functions"...
static createable() -> boolean
- Return true if the object should be createable directly by the user via the UI.
- Otherwise, the object will be hidden and can only be created by code from another object.
static properties() -> Dictionary<string propertyName, variant propertyType>
- Returns a dictionary listing all properties of an object that can be edited in the object editor.
- You can also add comments by starting the property name with comment and putting the comment in the value.
- Property type can be:
- static types:
"string", "number", "boolean"
- Expressions:
"Expression"
- Domains and ranges:
"Domain"
- Enumerations:
new P.Enum("value 1", "value 2"...)
displayed as comboboxes, property is set as the string value of the enum. - Lists:
new P.List(<static type>, format = /^.+$/, label = '', forbidAdding = false)
- Dictionaries:
new P.Dictionary(valueType: <static type>, keytType: <static type>, format = /^.+$/, preKeyLabel = '', postKeyLabel = ': ', forbidAdding = false)
- Other objects:
new P.ObjectType(<object type. E.g. "Point", "ExecutableObject"...>)
- static types:
- In order to allow the properties to be properly translated, you should apply the
QT_TRANSLATE_NOOP
macro to both property names (in theprop
namespace) and comments (in thecomment
namespace). For example:
static properties() {return {
[QT_TRANSLATE_NOOP('prop','expression')]: 'Expression',
[QT_TRANSLATE_NOOP('prop','definitionDomain')]: 'Domain',
[QT_TRANSLATE_NOOP('prop','destinationDomain')]: 'Domain',
'comment1': QT_TRANSLATE_NOOP(
'comment',
'Ex: R+* (ℝ⁺*), N (ℕ), Z-* (ℤ⁻*), ]0;1[, {3;4;5}'
),
[QT_TRANSLATE_NOOP('prop','labelPosition')]: P.Enum.Position,
[QT_TRANSLATE_NOOP('prop','displayMode')]: P.Enum.FunctionDisplayType,
[QT_TRANSLATE_NOOP('prop','labelX')]: 'number',
'comment2': QT_TRANSLATE_NOOP(
'comment',
'The following parameters are used when the definition domain is a non-continuous set. (Ex: ℕ, ℤ, sets like {0;3}...)'
),
[QT_TRANSLATE_NOOP('prop','drawPoints')]: 'boolean',
[QT_TRANSLATE_NOOP('prop','drawDashedLines')]: 'boolean'
}}
- For enums that need to be translated, an alias should be created in
parameters.js
, and then use the static class value in your parameter. For example:
Enum.XCursorValuePosition = new Enum(
QT_TR_NOOP('Next to target'),
QT_TR_NOOP('With label'),
QT_TR_NOOP('Hidden')
)
constructor(name = null, visible = true, color = null, labelContent = 'name + value', ...other arguments)
- Constructor of the object, should contain at least the name, visible, color, and labelContent that are passed to the super constructor.
- You should also beaware that Expressions may be inputed as strings or numbers when restored from save, as such you should add a line in your constructor to convert them to an expression.
- For example:
if(typeof x == 'number' || typeof x == 'string') x = new MathLib.Expression(x.toString())
- The same goes for objects, who may be inputed by just their name.
- For example:
if(typeof om_0 == "string") {
// Point name or create one
om_0 = Objects.getObjectByName(om_0, 'Point')
if(om_0 == null) {
// Create new point
om_0 = createNewRegisteredObject('Point')
om_0.name = getNewName('ω')
om_0.color = this.color
om_0.labelContent = 'name'
om_0.labelPthis.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))osition = this.phase.execute() >= 0 ? 'bottom' : 'top'
history.addToHistory(new HistoryLib.CreateNewObject(om_0.name, 'Point', om_0.export()))
labelPosition = 'below'
}
om_0.requiredBy.push(this)
}
export() -> list
- Returns a list of arguments that can be serialized, and will be inputed as arguments of the object construction updon restoring a save.
- Note: the base arguments this.name, this.color & this.labelContent should be included in first as well.
color
argument should be exported using thetoString()
, Expressions should be exported usingtoEditableString
getReadableString() -> string
- Returns the string that should be displayed as help in the objects list and as the label when using the 'name + value' labelContent.
- You can make expressions displayable using its
toString()
method.
getLatexString() -> string
- Returns the LaTeX rendered that should be displayed as label on the canvas when using the 'name + value' labelContent.
- You can turn expressions and sets into LaTeX by using their
latexMarkup
properties. - Similarly, variables should always be parsed with the helper function
Latex.variable
in order to render special characters (greek letters, sub and supscript numbers...)
execute(x = 1) -> number?
- Only required for ExecutableObject.
- Returns the executed value of the object for a given x. Return null when current x cannot return a value.
canExecute(x = 1) -> boolean
- Only required for ExecutableObject.
- Returns whether x can return a value for this object.
simplify(x = 1) -> string
- Only required for ExecutableObject.
- Returns the simplified expression for a given x.
draw(canvas, ctx)
- Main method for drawing the objects using the Canvas Context2D methods and the additional methods described below.
- ExecutableObjects can draw their label if they have a labelX and labelPosition using the the
drawLabel
object function:
this.drawLabel(canvas, ctx, this.labelPosition, canvas.x2px(this.labelX), canvas.y2px(this.execute(this.labelX)))
- You can also use `Function.drawFunction(canvas, ctx, expr, definitionDomain, destinationDomain, drawPoints = true, drawDash = true)` to rapidly draw a function created on the canvas.
Other optional methods:
update()
- Called every time a property of the object is changed.
getLabel()
- Shorthand method you can use to get your label string depending on the state of labelContent.
- You can extend it to suit your own needs for your objects.
Registering your object
- Open the
autoload.js
file. - Add the import for your object like ```.import "yourobject.js" as YO`
- Call the common method
C.registerObject
on your object likeC.registerObject(YO.YourObject)
And that's it!
Additional canvas methods.
There are a few additional methods available on the canvas objects:
canvas.x2px(x: number) -> number
- Converts the x coordinate of the plot to it's equivalent on the canvas (works owth with logarithmic scale and normal scale).
canvas.y2px(y: number) -> number
- Converts the y coordinate of the plot to it's equivalent on the canvas.
canvas.px2x(px: number) -> number
- Reverse function of x2px.
canvas.px2y(px: number) -> number
- Reverse function of y2px.
canvas.visible(x: number, y: number) -> boolean
- Returns true if x and y coordinate of the plot are visible on the canvas, false otherwise.
canvas.measureText(context: Context2D, text: string) -> {"width": number, "height": number}
- Returns the canvas width and height of the text were it to be written on the canvas.
- This method also allows multiline text to be tested.
canvas.drawVisibleText(context: Context2D, text: string, x: number, y: number) -> null
- Draw text at the x and y of the canvas (not x and y of the plot) only if the coordinate is visible on the canvas.
- This method also allows multiline text to be written.
canvas.drawLine(context: Context2D, x1: number, y1: number, x2: number, y2: number) -> null
- Draws a line between canvas points (x1, y1) and (x2, y2).
canvas.drawDashedLine(context: Context2D, x1: number, y1: number, x2: number, y2: number, dashPxSize = 10) -> null
- Draws a dashed line between canvas points (x1, y1) and (x2, y2) width one dash being dashPxSize/2.
canvas.drawDashedLine2(context: Context2D, x1: number, y1: number, x2: number, y2: number, dashPxSize = 10) -> null
- Draws a dashed line between canvas points (x1, y1) and (x2, y2) width one dash being dashPxSize.