Add 'Adding new objects'

Adsooi 2021-06-23 10:47:50 -04:00
parent bf51bbd733
commit 040553cd62

149
Adding-new-objects.md Normal file

@ -0,0 +1,149 @@
# 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](https://doc.qt.io/qt-5/qml-qtquick-context2d.html).
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, open the file at `LogarithmPlotter/qml/js/objects.js`.
There exists two kinds of objects:
- Drawable (extending the DrawableObject class, the most primitive kind of object)
- Examples: Point, Text, XCursor
- Executable (extending the 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 typeMultiple() -> 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"`
- 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"...>)`
- `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:
```js
if(typeof om_0 == "string") {
// Point name or create one
om_0 = 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.labelPosition = 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 the `toString()`, Expressions should be exported using `toEditableString`
- `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 easily displayable using it's `toString()` method.
- `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](https://doc.qt.io/qt-5/qml-qtquick-context2d.html) methods and the additional methods described below.
- ExecutableObjects can draw their label if they have a labelX and labelPosition using the following code used in numerous other objects:
```js
var text = this.getLabel()
ctx.font = `${canvas.textsize}px sans-serif`
var textSize = canvas.measureText(ctx, text)
var posX = canvas.x2px(this.labelX)
var posY = canvas.y2px(this.execute(this.labelX))
switch(this.labelPosition) {
case 'above':
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY-textSize.height)
break;
case 'below':
canvas.drawVisibleText(ctx, text, posX-textSize.width/2, posY+textSize.height)
break;
case 'left':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height/2)
break;
case 'right':
canvas.drawVisibleText(ctx, text, posX, posY-textSize.height/2)
break;
case 'above-left':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY-textSize.height)
break;
case 'above-right':
canvas.drawVisibleText(ctx, text, posX, posY-textSize.height)
break;
case 'below-left':
canvas.drawVisibleText(ctx, text, posX-textSize.width, posY+textSize.height)
break;
case 'below-right':
canvas.drawVisibleText(ctx, text, posX, posY+textSize.height)
break;
}
```
- 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() {
### Registering your object
Add your object type to the `types` dictionary at the bottom of the file.
- E.g: `'Point': Point`
- Upon saving and restoring a save, objects are saved/restored in the same order as the list.
- So to avoid dependance issues, when you add your object type to list, make sure it's below the ones required for it's construction.
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.