Introduction

What is a Drawing?

ChartIQ uses the term “drawing” to refer to a rendering on the chart, used to signify some result of technical analysis. A drawing is different from a study in that it does not produce result data into the dataSet. Rather, a drawing is merely a rendering between or around specific date/value pairs. Drawings can exist on any panel.

Drawings are assigned to display only for the symbol for which they were created. Depending on the type of drawing, they may scale when the chart’s periodicity is changed, or they may remain of a constant size.

Drawings are designed to allow persistence. Meaning, they have the ability to store themselves in the browser’s local storage and have the ability to be stored to a remote database as well.

Built-in Drawings

The ChartIQ library is packaged with predefined drawings. The code for these drawings can be found in drawings.js. Even more drawings are available in the advanced package in drawingAdvanced.js. A list of built-in drawings is available near the end of this tutorial.

Anchor Points

A drawing may be anchored to one or more date/value pairs (points) on the chart. Anchoring to more than one point makes it easy to scale the drawing to adjust to different periodicities. This is because the multiple points usually define the boundaries, or endpoints, of the drawing. An example of a two-point drawing is the line segment.

In contrast, a drawing with only one anchor point will always draw at the same point in time but will not scale or adjust to periodicity changes. Examples of drawings with one anchor point are annotations and those classified as “shapes” (arrows, checks, stars, etc).

The Drawing Toolbar

The ChartIQ library templates include a drawing toolbar that allows the user to select the type of drawing to use on the chart as well as customize the settings for that particular drawing. For more details on the drawing toolbar web component see CIQ.UI.DrawingToolbar. For details on creating your own toolbar see Custom Drawing Toolbar

Drawings may also be added to the chart programmatically. This is covered in more detail in this tutorial.

Custom Drawings

Developers can enhance the functionality of the drawing tools by creating their own drawings. This tutorial aims to help the developer understand how to accomplish this successfully.

Basic Structure of a Drawing

A Simple Custom Drawing

Let’s start by creating a simple drawing type called “difference”. This drawing will be a line segment between two closing prices. It will also display the difference between them as a number. To create the drawing, the user (or developer) will choose two points on the x-axis, which will be used as the x-coordinate for the endpoints. The y-coordinates will come from the closing prices for the dates corresponding to the two points chosen.

The drawing is defined with a few lines of code: a constructor and a function call to inherit properties and methods from the segment drawing type.

CIQ.Drawing.difference=function(){
    this.name="difference";
};
CIQ.Drawing.difference.ciqInheritsFrom(CIQ.Drawing.segment);

So far all that has been done is to create a new class of drawing which uses the prototype properties and methods from the segment drawing.

New drawing types should always inherit from the existing drawing type that most closely resembles it. This is done so that the new drawing can use as many of the inherited drawings properties and methods as possible. Then, only those methods which need to differ from the base drawing type will need to be redefined.

Choose to inherit from the drawing that most resembles the one you are creating in order to reuse the most amount of code possible, and to minimize new code that needs to be written.

A table showing the existing inheritance relationship of the built-in drawings is located toward the end of this tutorial.

The reason “segment” was chosen as the base drawing is because the difference drawing will also be a straight line between two points. The only major aspects between the drawings that differ is in the endpoints used:

  1. The segment determines the y-axis coordinates of the endpoints from the user input (or developer). The difference drawing will get them from the closing prices.
  2. The difference drawing will place some text on the screen.

In order to accomplish these changes from the standard segment drawing, a few changes are required. A render function will need to be defined for the new drawing. This render function is called by CIQ.ChartEngine.drawVectors, which is in turn called by CIQ.ChartEngine.draw. This means the drawings are re-rendered whenever either data changes, or the user interacts with the chart.

Here is the render function for the difference drawing. See the comments inline for a description of what is happening.

// render function draws the drawing on the canvas
CIQ.Drawing.difference.prototype.render=function(context){
    var panel=this.stx.panels[this.panelName];
    if(!panel || !this.p[0] || !this.p1) return;

    // get the x-coords on the canvas for the start and end points
    var x0=this.stx.pixelFromTick(this.p0[0], panel.chart);
    var x1=this.stx.pixelFromTick(this.p1[0], panel.chart);

    // get the color to draw the line
    var color=this.color;
    if(color=="auto" || CIQ.isTransparent(color)) color=this.stx.defaultColor;
    if(this.highlighted) color=this.stx.getCanvasColor("stx_highlight_vector");

    // set the line parameters (pattern and width)
    var parameters={
        pattern: this.pattern,
        lineWidth: this.lineWidth
    };

    // get the dataSet entries for the start and end points (default to last entry if dataSet entry not found)
    var first=this.stx.chart.dataSet[this.p0[0]];
    if(!first) first=this.stx.chart.dataSet[this.stx.chart.dataSet.length-1];

    var second=this.stx.chart.dataSet[this.p1[0]];
    if(!second) second=this.stx.chart.dataSet[this.stx.chart.dataSet.length-1];

    // get the y-coords on the canvas for the start and end points
    var y0=this.stx.pixelFromValueAdjusted(panel, this.p0[0], first.Close);
    var y1=this.stx.pixelFromValueAdjusted(panel, this.p1[0], second.Close);

    // draw the line between start and end points
    this.stx.plotLine(x0, x1, y0, y1, color, "segment", context, panel, parameters);

    // draw the text representing the difference between the start and end values, at the center of the line
    var text=Number(first.Close-second.Close).toFixed(4);
    context.fillStyle=color;
    context.fillText(text, (x0+x1)/2, (y0+y1)/2);

    // highlighted==truthy when drawing is selected
    if(!this.highlighted){
    // move endpoints in object to the closing values of the start and end dates
        this.setPoint(0, this.p0[0], first.Close, panel.chart);
        this.setPoint(1, this.p1[0], second.Close, panel.chart);
    }else{
        // show the nodes to resize the drawing
        var p0Fill=this.highlighted=="p0"?true:false;
        var p1Fill=this.highlighted=="p1"?true:false;
        this.littleCircle(context, x0, y0, p0Fill);
        this.littleCircle(context, x1, y1, p1Fill);
    }
};

Some observations about this function:

  1. The two properties p0 and p1 represent the two anchor points of the drawing. In this case, it is the start and end point. Each of these is a 2-element array [x,y] where x is a tick (an index into the dataset) on the chart, and y is the untransformed value. The untransformed value is what the y-intercept of the point would be if the axis were displaying raw values, as opposed to comparison percentages or some other transformation.
  2. Render functions rely heavily on CIQ.ChartEngine.pixelFromTick and CIQ.ChartEngine.pixelFromValueAdjusted. These functions operate on the p0 and p1 properties, to compute the start and end coordinates on the canvas. Once these are determined, rendering on the canvas is possible.
  3. The pattern and lineWidth properties are set by the user. Alternatively, they may be set programmatically on the drawing.
  4. This rendering function supports the highlight feature, which indicates that the drawing or one of its parts is selected. The user may move the drawing (to be referred to as a “drag” action) or change an endpoint of the drawing (a “move” action). The render function changes the drawing color when it is highlighted, to the color specified in the stx_highlight_vector style selector, and small circles are drawn at the endpoints.
  5. The function will use the setPoint member function to adjust the y values of p0 and p1 from the user’s input to the Closing price. This is only done when the highlighting is not engaged so as to allow the user to continue to adjust the size of the drawing even as the mouse is not hovering over the line anymore.

A few more lines of code are required to complete the drawing definition:

// lineIntersection returns Boolean - whether cursor is intersecting a line-like drawing;
// here we call the base function but alias ourselves as a segment drawing (as opposed to a ray or line drawing)
// this allows the base intersected function to treat a mouseover for our drawing the same as for a segment,
// allowing drag action as well as move action for both endpoints.
CIQ.Drawing.difference.prototype.lineIntersection=function(tick, value, box, name){
    return CIQ.Drawing.segment.prototype.lineIntersection.call(this, tick, value, box, "segment");
};

// set toolbar options if using it, to allow change in color, width, and pattern
CIQ.Drawing.difference.prototype.configs=["color", "lineWidth", "pattern"];

The new drawing tool may be added to the drawing toolbar as well. Using the sample-template-advanced.html template, place this line in the cq-toolbar -> cq-menu element:

<cq-item stxtap="tool('difference')">Difference</cq-item>

This is a very elementary example, as our drawing resembles a segment in almost every way, allowing for the use of base class functionality for pretty much everything besides the actual rendering.

In the “Defining Drawing Functionality” section of this tutorial, is a closer look at the different functions that can be redefined by the drawing type.

The Drawing Object

Within a drawing object are several properties that help define how a drawing will get rendered. While some properties may differ from one drawing to the next, in most cases, there will be a set of properties that is consistent.

Here is a screenshot from the debugger showing the properties of some drawings:

While some of the properties found in these drawings represent temporary state flags and values, several properties are common throughout the drawings and deserve mention, as these will be important to understand when creating custom drawings.

d0 – The date, in YYMMDDHHmmSSsss format, on the x-axis of the drawing’s beginning anchor point.

d1 – The date, in YYMMDDHHmmSSsss format, on the x-axis of the drawing’s ending anchor point. Note the annotation drawing does not have a d1 property, since it only needs one point to create itself.

v0 – The adjusted value on the y-axis of the drawing’s beginning anchor point.

v1 – The adjusted value on the y-axis of the drawing’s ending anchor point. Note, again, the annotation drawing does not have a v1 property, since it only needs one point to create itself.

tzo0, tzo1 – Time zone offsets for d0 and d1, respectively.

Note that the above values remain constant through any adjustment of the chart itself, though they may change through adjustment of the drawing via a drag or move operation.

p0, p1 – Arrays representing the start and end points of the drawing. p0[0], p1[0] are the tick indexes into stxx.chart.dataSet and p0[1], p1[1] are the adjusted values. These values are calculated by the drawing’s functions and may change depending on the chart’s periodicity, aggregation, or dataSet size. The values in the array need not necessarily correspond to the d0/d1 and v0/v1 values, though they are usually based off them.

name – Unique name representing the drawing type. This usually corresponds to the drawing’s object type.

panelName – Name of the panel where the drawing belongs.

color – Color of the drawing. Can be any rgb, rgba, hex or color name. A setting of “auto” indicates the stxx.defaultColor should be used.

pattern – Line style. May be solid, dashed, dotted or none.

lineWidth – Width of drawing outline in pixels.

fillColor – Color of interior of drawing. Can be any rgb, rgba, hex or color name. Usually an opacity setting < 1 is used when filling the object.

text, font – Values used for displaying the text of annotation-type drawings.

parameters – Values for Fibonacci renderings.

stx – The chart object.

Accessing Drawings on the Chart

Drawings are stored in the CIQ.ChartEngine instance. Each drawing may be accessed by iterating through the drawingObjects object:

for(var i=0;i<stxx.drawingObjects.length;i++){
    var myDrawing=stxx.drawingObjects[i];
…
}

Adding a Drawing to the Chart

To create a drawing instance and add it to the chart, an object representing the drawing’s essential properties needs to be passed to the CIQ.ChartEngine.createDrawing method:

var drawing=stxx.createDrawing("difference",{
    "pnl": "chart",
    "v0": 99.52,
    "v1": 101.02,
    "d0": "20161126143000000",
    "d1": "20161128223000000",
    "tzo0": 300,        // optional, defaults to browser’s new Date().getTimezoneOffset()
    "tzo1": 300,        // optional, defaults to browser’s new Date().getTimezoneOffset()
    "col": "#00746a",    //optional, defaults to stxx.currentVectorParameters.currentColor setting
    "lw": 3,        //optional, defaults to stxx.currentVectorParameters.lineWidth setting
    "ptrn": "dashed"    //optional, defaults to stxx.currentVectorParameters.pattern setting
});

Note the use of abbreviated keys in the passed object. These abbreviations are used when serializing the drawing for storage. Abbreviations are used to minimize the length of the stringified JSON to be stored in local or remote storage. The keys used in the object passed to createDrawing must match those used in the drawing object’s reconstruct function. For more information on serializing and restoring drawings, please see the “Storing and Retrieving” section below.

Drawings must be added after the chart’s layout is restored. Otherwise, they will be cleared and replaced with whatever drawings are restored for the default symbol.

The drawing’s properties can be changed programmatically. To change the start date and the pattern, run the following code:

drawing.d0="20161127120000000";
drawing.pattern="solid";
drawing.adjust();

Here, since the drawing is already created, the properties are accessed with their full names, not the serialized abbreviations. The adjust function is discussed later; it needs to be called whenever changing a property which was used to originally construct the drawing.

To remove a drawing from the chart, use the CIQ.ChartEngine.removeDrawing method:

stxx.removeDrawing(drawing);

To add an existing drawing instance, use the CIQ.ChartEngine.addDrawing method:

stxx.addDrawing(drawing);

To remove all drawings for the current symbol, use CIQ.ChartEngine.clearDrawings:

stxx.clearDrawings();

There is no need to manually adjust the stxx.drawingObjects object when using one of the above methods to add or remove drawings from the chart. It is done automatically.

Configuration Settings

Each drawing has certain configuration options that can be set at the drawing instance, chart instance, or default level. These options include color, fill color, pattern, and line width, as well as some more uncommon options which pertain to specific drawing types. When creating custom drawings, new configuration properties may be added to support additional features of the drawing. However, the existing configuration options will most likely suffice.

Config Drawing instance property Chart instance property Default property (must be set before creating stxx)
Drawing Type drawing.name stxx.currentVectorParameters.vectorType CIQ.ChartEngine.currentVectorParameters.vectorType
Color drawing.color stxx.currentVectorParameters.currentColor CIQ.ChartEngine.currentVectorParameters.currentColor
Fill Color drawing.fillColor stxx.currentVectorParameters.fillColor CIQ.ChartEngine.currentVectorParameters.fillColor
Pattern drawing.pattern stxx.currentVectorParameters.pattern CIQ.ChartEngine.currentVectorParameters.pattern
Line Width drawing.lineWidth stxx.currentVectorParameters.lineWidth CIQ.ChartEngine.currentVectorParameters.lineWidth
Axis Label drawing.axisLabel stxx.currentVectorParameters.axisLabel CIQ.ChartEngine.currentVectorParameters.axisLabel
Fibonacci Parameters drawing.parameters stxx.currentVectorParams.fibonacci CIQ.ChartEngine.currentVectorParameters.fibonacci
Font drawing.font stxx.currentVectorParams.annotation.font CIQ.ChartEngine.currentVectorParameters.annotation.font

Note setting a color to “auto” indicates that a best-matching color should be chosen based on the chart’s theme.

Not every configuration option is supported by every built-in drawing type. For example, while “color” is almost universally supported, “fillColor” is only used for drawings that have interiors that can be shaded.

If using the drawing toolbar web component, it can be set to display choosers for certain configurations. For example, for color configuration, the toolbar will display a color picker. For line width and pattern, a dropdown will be created. The following web components exist to choose the different toolbar options:

  • cq-line-style – dropdown for choosing pattern and line width
  • cq-line-color – color picker for color
  • cq-fill-color – color picker for fill color
  • cq-axis-label – checkbox for axis date/price label
  • cq-annotation – font option dropdowns and toggles (font style, size, face)

The appropriate choosers are automatically displayed based on the values in the drawing's config array. For example, if the array were ["color","lineWidth","pattern","axisLabel"], the cq-line-style, cq-line-color, and cq-axis-label choosers would be displayed.

For more information about drawing parameters, see the documentation of CIQ.ChartEngine.currentVectorParameters.

Drawing Behaviors

Settings are available to control the drawing’s behavior. They may be set at either the drawing prototype level or at the drawing instance level.

dragToDraw – Normally a drawing is created with a click –> move –> click sequence. Set dragToDraw to true to create the drawing with a mousedown –> drag –> mouseup sequence. (On a touch device this would be press –> drag –> release.)

permanent – Set this to true to disable selection, repositioning and deletion by the end user.

chartOnly – Set this to true to restrict drawing to the chart panel.

Additionally, when stxx.preferences.magnet is set to true, when the user is creating a drawing, the y-value will snap to the closest data point on the chart (Open, High, Low, Close) for the x-value (date).

Example:

// set drawing instance to permanent
drawing.permanent=true;
// Set drawing prototype to permanent
CIQ.Drawing["difference"].prototype.permanent=true;

Defining the Drawing Functionality

In this section we’ll explore the different methods of the drawing class. Developers can customize these for their own drawing classes should they need to differ from the base class. In the absence of a function from the drawing class, the corresponding function from the inherited base class will be used.

Exporting a drawing

Converting a drawing object to a storable string of data is handled by the CIQ.Drawing[type].prototype.serialize function. The serialize function gathers in the properties of the drawing which are critical to reconstructing the drawing at a later time, and creates an object which will be eventually stringified. It is desirable to assign the properties to keys whose character length is short, to minimize the number of bytes used in storage. The properties that are serialized are nearly the same as those detailed above in the section, “The Drawing Object”. For a custom drawing, it may be possible that extra properties will need to be saved.

Example serialize function:

CIQ.Drawing.segment.prototype.serialize=function(){
    return {
        name: this.name,
        pnl: this.panelName,
        col: this.color,
        ptrn: this.pattern,
        lw: this.lineWidth,
        d0: this.d0,
        d1: this.d1,
        tzo0: this.tzo0,
        tzo1: this.tzo1,
        v0: this.v0,
        v1: this.v1
    };
};

To automatically export all drawings on the chart see CIQ.ChartEngine#exportDrawings

Importing a drawing

Importing a drawing is partly handled by the CIQ.Drawing[type].prototype.reconstruct function. This function receives an object that is the parsed result of a stringified drawing, and assigns to the drawing object. At the end of the function is a call to the drawing’s adjust function.

Example reconstruct function:

CIQ.Drawing.segment.prototype.reconstruct=function(stx, obj){
    this.stx=stx;
    this.color=obj.col;
    this.panelName=obj.pnl;
    this.pattern=obj.ptrn;
    this.lineWidth=obj.lw;
    this.d0=obj.d0;
    this.d1=obj.d1;
    this.tzo0=obj.tzo0;
    this.tzo1=obj.tzo1;
    this.v0=obj.v0;
    this.v1=obj.v1;
    this.adjust();
};

The adjust function is responsible for converting the date/value properties (dx, vx) used for storage, into tick/value points (px) used for drawing. Here is an example:

CIQ.Drawing.BaseTwoPoint.prototype.adjust=function(){
    // If the drawing's panel doesn't exist then we'll check to see
    // whether the panel has been added. If not then there's no way to adjust
    var panel=this.stx.panels[this.panelName];
    if(!panel) return;
    this.setPoint(0, this.d0, this.v0, panel.chart);  // sets p0
    this.setPoint(1, this.d1, this.v1, panel.chart);  // sets p1
};

Note that the segment drawing inherits this adjust function from BaseTwoPoint as it has none of its own. The setPoint function will set px given dx and vx, or it can alternatively set dx and vx given px, where x is the first argument of the function.

The adjust function will also create any other drawing object properties that are computed from the minimal ones set in reconstruct. The adjust function should be invoked whenever one of the properties in reconstruct has been changed, in order that the derived properties of the drawing are adjusted as well. In the example above, changing d0, d1, v0 or v1 should necessitate calling adjust.

The complete step-by-step process for progtramatically adding a drawing from a serialization to the chart is as follows:

// define or retreive the parameters for the drawing
var drawing = {"name":"ellipse","pnl":"chart","col":"auto","fc":"#7DA6F5","ptrn":"solid","lw":1,"d0":"20160729060000000","d1":"20160812060000000","tzo0":240,"tzo1":240,"v0":100.25558093545439,"v1":100.26208393306382};

//create factory for the drawing you want
var Factory=CIQ.Drawing[drawing.name];

//create an empty template for that drawing
var newDrawing=new Factory();

// build the drawing from the parameters
newDrawing.reconstruct(stxx, drawing);

// add it to the chart
stxx.addDrawing(newDrawing);

//signal the chart that a drawing has been added...so you can save the layout if you want.
stxx.changeOccurred("vector");

// draw the chart to display it.
stxx.draw();

To automatically import and render an array of objects see CIQ.ChartEngine#importDrawings

The click Function

The click function defines how the user interacts with the UI to create the drawing. This is accomplished via a series of clicks/taps or via dragging (dragToDraw). Here is the definition for the basic two-point click function:

CIQ.Drawing.BaseTwoPoint.prototype.click=function(context, tick, value){
    this.copyConfig();
    var panel=this.stx.panels[this.panelName];
    if(!this.penDown){
        this.setPoint(0, tick, value, panel.chart);
        this.penDown=true;
        return false;
    }
    if(this.accidentalClick(tick, value)) return this.dragToDraw;

    this.setPoint(1, tick, value, panel.chart);
    this.penDown=false;
    return true;    // chart engine will call render after this
};

The function is invoked whenever a click/tap (or change in button/touch state in dragToDraw mode) is detected and stxx.currentVectorParameters.vectorType is set to a drawing type. The function returns true when the drawing has been completed. If false is returned, the drawing is considered incomplete. Here is what is happening in the function:

  1. Configuration options are copied into the drawing’s properties from stxx.currentVectorParameters via the copyConfig function. The copyConfig function can be redefined to accommodate configuration options not handled by the inherited function.
  2. The penDown property is used to determine which point is being set (first or second). The first point is set by setPoint if the pen is not down; otherwise the second point is set.
  3. An "accidental click" detection occurs when setting the second point. This will return true if the second click is too close in distance to the first tick. The second point will then not be allowed to be set. The accidentalClick function can be redefined by the drawing instance if necessary.
  4. The function returns true to indicate successful completion of the drawing. If successful, the drawing will be stored in the chart’s drawingObjects object.

The move Function

The move function is invoked when the user is in the midst of drawing an object. It is responsible for adjusting p1 based on the current mouse position. It may also be involved in setting additional properties that aid in the rendering of the drawing.

Here is an example of a move function:

CIQ.Drawing.BaseTwoPoint.prototype.move=function(context, tick, value){
    if(!this.penDown) return;
    this.copyConfig();  // done in case the user changes the config settings mid-drawing
    this.p1=[tick,value];
    this.render(context);
};

The render Function

The render function is responsible for placing the drawing on the canvas. It is the function most likely to be redefined by a drawing type.

In the previous section we introduced the “difference” drawing type and a rendering function for it. The render function is called within the stxx.draw animation loop, so every time the chart changes, the drawings are re-rendered. The function is also called in the process of creating or changing the dimensions of the drawing.

Rendering Nuances

Within the render function, the need often arises to perform conversions on the tick or value passed into the function. For example, it may be necessary to obtain the pixel coordinates taking into account the panel and y-axis scale. There are several related chart functions which can help perform these tasks.

stxx.pixelFromTick – converts a tick (p0[0], p1[0]) into its corresponding x-pixel value for drawing.

stxx.pixelFromValueAdjusted – converts a value (p0[1], p1[1]) into its corresponding y-pixel value for drawing. Use this rather than stxx.pixelFromPrice to take into account log scaling, transforms, and other price adjustments. The value passed in should be the raw price value and not the transformed, percentage value used for comparisons.

stxx.pixelFromDate – converts a date value into its corresponding x-pixel value. The date can be in either string format (yyyymmddhhmmSSsss) or a JavaScript Date object.

stxx.tickFromPixel – converts an x-pixel value into a tick value.

stxx.tickFromDate – converts a date value into a tick value. The date can be in either string format (yyyyMMddHHmmSSsss) or a JavaScript Date object.

stxx.transformedPriceFromPixel – converts a y-pixel value into a price where it intersects the y-axis. This will return the actual y-axis value and will be a percentage for comparisons. See note above regarding stxx.pixelFromValueAdjusted.

stxx.valueFromPixel/stxx.priceFromPixel – converts a y-pixel value into a raw price where it would intersect the y-axis if there were no comparisons and the y-axis measured the true value of the chart. Does not take into account adjusted prices; therefore, use stxx.pixelFromValueAdjusted instead.

When rendering a series of points, the following functions may assist:

stxx.connectTheDots – Will draw straight lines connecting the points whose coordinates are in the points array. The points array looks as follows: [x0, y0, x1, y1, x2, y2, …]. The parameters argument is an object which defines the line style (pattern, width, opacity).

stxx.plotSpline – Same as above except will employ a best-fitting technique based on the tension argument to smooth the line segments into a curve. Note: a special library file needs to be included to support this functionality.

The measure Function

Sometimes a drawing will support a specific measurement in bars or vertical distance. This measurement can be displayed in the UI using the stxx.setMeasure function. The measurement is displayed while the drawing is being created, as well as when it is selected.

Below is a screenshot of the measurement displayed in both the drawing toolbar as well as the tooltip, when the drawing is selected:

The default measure function is quite simple, and looks as follows:

CIQ.Drawing.BaseTwoPoint.prototype.measure=function(){
    this.stx.setMeasure(this.p0[1], this.p1[1], this.p0[0], this.p1[0], true);
};

The abort Function

The abort function may be called when a drawing in the midst of being created has been cancelled. Normally it is an empty function, but for drawings that create DOM elements or other resources, the abort function can be useful in taking down those resources.

For example, the segment’s abort function will remove the measurement text. The annotation’s abort function will remove any DOM elements associated with the annotation (the textarea as well as the save/cancel buttons).

The intersected Function

Whenever there is pointer movement detected, the charting engine calls the intersected function. This function’s arguments are the tick and value corresponding to the pointer’s position on the chart. The function may use a variety of algorithms to determine whether the tick and value arguments intersect the drawing.

The intersected function returns a "repositioner" object if an intersection is detected, to better define the type of intersection detected. This object is used by the reposition function (described in the next section) to perform a transformation on the drawing. If no intersection is detected, the function returns null.

A drawing is considered selected when the intersected function finds a valid intersection. To indicate that the drawing is selected, its highlighted property is set to true. Setting the highlighted property allows the render function to display the drawing in a different way, such as changing its color and line width. The highlighted property can be set to a boolean, or it can be set to a specific point (“p0”, “p1”, etc) which can be used in the render function to make that point appear selected.

An intersected function can set the highlighted property to true without returning a repositioner object. This means that the drawing cannot be manipulated by the user except for deleting.

The following drawing class functions are available for use within the intersected function:

pointIntersection – Determines if the tick and value are within a small boxed area surrounding each of the drawing’s endpoints. This is useful for detecting hovering over an endpoint for a drag action.

boxIntersection – Determines if the tick and value are within a boxed area between the drawing’s endpoints. This is useful for detecting hovering within an area drawing such as an ellipse or a rectangle.

lineIntersection – Determines if a line between the drawing’s endpoints runs through a small boxed area surrounding the tick and value. The type parameter defines the type of line (“line”, “ray”, or “segment”). This is why the lineIntersection function of our example "difference" drawing above is redefined; without the redefinition, the type passed into lineIntersection would be the drawing type, “difference”, which is not understood by the lineIntersection function.

As an example, here is the intersected function for the rectangle drawing. There are possible repositioners returned: for intersection at either endpoint and for intersection within the rectangle itself:

CIQ.Drawing.rectangle.prototype.intersected=function(tick, value, box){
    if(!this.p0 || !this.p1) return null; // in case invalid drawing (such as from panel that no longer exists)
    if(this.pointIntersection(this.p0[0], this.p0[1], box)){
        this.highlighted="p0";
        return {
            action: "drag",
            point: "p0"
        };
    }else if(this.pointIntersection(this.p1[0], this.p1[1], box)){
        this.highlighted="p1";
        return {
            action: "drag",
            point: "p1"
        };
    }
    if(this.boxIntersection(tick, value)){
        this.highlighted=true;
        return {
            action: "move",
            p0: CIQ.clone(this.p0),
            p1: CIQ.clone(this.p1),
            tick: tick,
            value: value
        };
    }
    return null;
};

The intersected function does not need to necessarily detect a physical intersection with the drawing. For example, it may be defined to return true when the tick and value match some arbitrary value, or if they intersect a drawing legend created by the developer.

The reposition Function

The reposition function is invoked when the user drags the selected drawing around the chart.

The reposition function is passed the repositioner object which was returned by the intersected function. This object contains information regarding what is being repositioned. Primarily, it will contain an action that will define what type of transformation will be done with the drawing. Currently defined actions are “drag” for changing an endpoint, “move” for relocating the entire drawing, and “scale” and “rotate” for changing a shape drawing type’s dimensions. The repositioner object will also contain other values depending on the action. These values are used by the reposition function to determine the new position or dimensions of the drawing.

Here is an example reposition function:

CIQ.Drawing.BaseTwoPoint.prototype.reposition=function(context, repositioner, tick, value){
    if(!repositioner) return;
    var panel=this.stx.panels[this.panelName];
    var tickDiff=repositioner.tick-tick;
    var valueDiff=repositioner.value-value;
    if(repositioner.action=="move"){
        this.setPoint(0, repositioner.p0[0]-tickDiff, repositioner.p0[1]-valueDiff, panel.chart);
        this.setPoint(1, repositioner.p1[0]-tickDiff, repositioner.p1[1]-valueDiff, panel.chart);
        this.render(context);
    }else if(repositioner.action=="drag"){
        this[repositioner.point]=[tick, value];
        this.setPoint(0, this.p0[0], this.p0[1], panel.chart);
        this.setPoint(1, this.p1[0], this.p1[1], panel.chart);
        this.render(context);
    }
};

The reposition function should call the render function to immediately update the canvas.

Drawing Shapes

Shapes are similar to two-point drawings in many ways. However, there are a few important differences:

  1. The shape’s outline is defined via a serialized series of canvas strokes.
  2. Shapes are drawn around a center point rather than between two points. The “endpoints” (p0, p1, p2) of a shape actually define the origin, scaling, and rotation handles.
  3. Shapes are placed on the chart in a 3-step process. First the origin is established, then the scale is set, then finally the rotation is defined.
  4. Shapes will not scale in size when the periodicity is changed. They remain the same size as originally drawn.

Shapes of various sizes, styles, and orientations:

Defining a Shape

A shape is an abstract type of drawing. Specific shapes such as arrows and stars inherit from CIQ.Drawing.shape and use the functions defined in that class. The difference between the definition of the specific shape types lies in the constructor, which defines three properties:

name – the name by which this shape will be known

dimension – size of minimal shape in pixels, as a pair [dx,dy] where dx is length and dy is width

points – an array which contains one or more arrays. Each inner array represents a closed polygon component of the shape. Within each array is a series of values representing coordinates. The arrays will be parsed by the render function.

For example, ["M",0,0,"L",1,1,"L",2,1,"Q",3,3,4,1,"B",5,5,0,0,3,3]

  • M - move to the xy coordinates represented by the next 2 array elements
  • L - draw line to xy coordinates represented by the next 2 array elements
  • Q - draw quadratic curve where next 2 elements are the control point and following 2 elements are the end coordinates
  • B - draw bezier curve where next 2 elements are first control point, next 2 elements are second control point, and next 2 elements are the end coordinates

Here is an example of the arrow shape definition in its entirety. Note how the movements within the points array are confined to the 11x11 box defined in the dimension property:

CIQ.Drawing.arrow=function(){
    this.name="arrow";
    this.dimension=[11,11];
    this.points=[
        ["M",3,0,"L",7,0,"L",7,5,"L",10,5,"L",5,10,"L",0,5,"L",3,5,"L",3,0]
    ];
};
CIQ.Drawing.arrow.ciqInheritsFrom(CIQ.Drawing.shape);

Here is a diagram illustrating how the movements defined above create an arrow:

The “focus arrow” is a good example of a shape which uses two arrays within the points property to define separate closed polygons.

A developer wishing to create a new shape will need to define only the constructor and call the ciqInheritsFrom function.

Storing Shapes

The serialize and reconstruct functions for shapes are similar to those of a basic two-point drawing, except instead of storing d1 and v1, which represent coordinates of the second endpoint, an angle and scale are stored instead. The angle (in degrees) is stored as a in the object, and the scale is represented by sx and sy (for horizontal and vertical scale factor). In this way, the shape can be reproduced from storage consistently. These properties can be seen in the debugger screenshot earlier in this tutorial for the “star” object (fourth column). A close look will reveal that the shape does indeed contain a d1 and v1 property, but these are actually computed internally and are not constant. As a result, shape drawings are not anchored between two ticks. Therefore, they will not scale when periodicity is changed.

The same createDrawing method can be used to create a shape on the chart, except the appropriate keys need to be passed in the object:

var drawing=stxx.createDrawing("arrow",{
    "pnl": "chart",
    "v0": 99.52,
    "d0": "20161126143000000",
    "tzo0": 300,
    "a": 90,
    "sx": 3,
    "sy": 4
});

Repositioning Shapes

When a shape is selected, three control points appear on the chart. These control points allow the user to move, scale, and rotate the shape.

The actions that the intersected function can pass in the repositioner for a shape are “move”, “scale”, and “rotate”. The intersected function uses a relatively complex algorithm to determine whether a shape is to be selected. Selection and repositioning may be disabled, as with any drawing, by setting the drawing’s permanent property to true.

Table of Built-in Drawing Types

Below is a list of built-in drawings, along with their descriptions and some key attributes.

Name Description Basic/Advanced Package Abstract/Concrete Inherits From Adjusts to Periodicity Change
Drawing Base class for all drawings basic abstract n/a n/a
BaseTwoPoint Base class for all drawings using endpoints basic abstract n/a n/a
annotation Displays text on the chart basic concrete BaseTwoPoint No
continuous Displays a series of segments joined together at their endpoints basic concrete segment Yes
ellipse Displays an ellipse where one endpoint is the center and the other is the point where two tangents of the ellipse form a right angle basic concrete BaseTwoPoint Yes
fibonacci Displays Fibonacci retracement lines between two points basic concrete BaseTwoPoint Yes
horizontal Displays an infinite horizontal line through a point basic concrete segment Yes
line Displays an infinite line through two points and off the edge of the chart in both directions basic concrete segment Yes
measure Displays the distance between two points. This drawing type is not permanent basic concrete segment n/a
ray Displays an infinite line originating at one point, continuing through the second, and off the edge of the chart in that direction basic concrete line Yes
rectangle Displays a rectangle using the endpoints as opposing diagonal vertices basic concrete BaseTwoPoint Yes
retracement Same as fibonacci basic concrete fibonacci Yes
segment Display a straight line between two points basic concrete BaseTwoPoint Yes
vertical Displays an infinite vertical line through a point basic concrete horizontal Yes
shape Base class for all shapes advanced abstract BaseTwoPoint n/a
arrow Displays an arrow shape advanced concrete shape No
callout Displays text on the chart within a bubble advanced concrete annotation No
channel Displays two parallel lines defined by three points advanced concrete segment Yes
check Displays a checkmark shape advanced concrete shape No
crossline Displays a horizontal and vertical line which intersect at the selected point advanced concrete horizontal Yes
fibarc Displays arcs centered around one endpoint and extending out to the other using Fibonacci radii advanced concrete fibonacci Yes
fibfan Displays fan lines centered at one point and extending at various Fibonacci angles from the second point advanced concrete fibonacci Yes
fibtimezone Displays vertical lines starting at on endpoint and repeating through the second point in a Fibonacci sequence advanced concrete fibonacci Yes
focusarrow Displays a shape which is two triangles pointing inward to “focus” on something advanced concrete shape No
freeform Displays a curve through a path created by dragging the pointer around the chart (always uses dragToDraw) advanced concrete segment Yes
gannfan Displays Gann lines centered at one point and extending at various angles from the second point advanced concrete speedarc Yes
gartley Displays a Gartley pattern using a series of endpoints advanced concrete continuous Yes
heart Displays a heart shape advanced concrete shape No
pitchfork Displays an Andrew’s Pitchfork defined by three points advanced concrete channel Yes
quadrant Displays a box divided into four equal horizontal sections determined by dataSegment’s price levels between the endpoints advanced concrete speedarc Yes
regression Displays a regression line for the dataSegment’s price levels between two points advanced concrete segment Yes
speedarc Displays speed-resistance arcs centered around one endpoint and extending out to the other advanced concrete segment Yes
speedline Displays speed-resistance lines centered at one point. Speedlines can be configured to display extensions and/or within a matrix. advanced concrete speedarc Yes
star Displays a star shape advanced concrete shape No
timecycle Displays vertical lines starting at on endpoint and repeating through the second point in a constant sequence advanced concrete speedarc Yes
tirone Displays a box divided into three equal horizontal sections determined by dataSegment’s price levels between the endpoints advanced concrete quadrant Yes
xcross Displays a cross (X) shape advanced concrete shape No

Here are some examples of drawings:

From left to right: annotation, callout, channel, continuous, crossline, doodle, ellipse, fibarc, gartley, line, quadrant, ray, rectangle, regression, segment, speedarc, tirone, vertical


Top panel, clockwise from top: gannfan, shapes(top to bottom: arrow, heart, focus, check, star, cross), time cycle, horizontal, speedlines, fibtimezone Bottom panel, left to right: fibfan, pitchfork, fib retracement