Add 'Adding new objects'
parent
bf51bbd733
commit
040553cd62
1 changed files with 149 additions and 0 deletions
149
Adding-new-objects.md
Normal file
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.
|
Loading…
Reference in a new issue