Time Span Events

User Interface Example

Time span events can be displayed or hidden by controls in a charting application's user interface.

The Time Span Events plug-in defines the CIQ.UI.TimeSpanEvent.prototype.showMarkers function, which is an event handler that shows or hides time span event markers based on input from the UI.

The showMarkers function can be overridden to create a custom implementation of time span events controlled from the application's user interface.

In this tutorial

You will:

  • Override the showMarkers function
  • Create static and real-time data sources for time span events
  • Send the data to the Time Span Events plug-in
  • Connect the Time Span Events plug-in to the user interface of the technical-analysis-chart.html template

Before you begin

This tutorial requires version 8.1.0 or later of the ChartIQ library.

Please familiarize yourself with the concepts in the Creating a Data Source tutorial before proceeding.

technical-analysis-chart.html

We'll implement our custom time span events as part of the technical-analysis-chart.html template, which is a full-featured charting application with an extensive user interface. The template is located in the root folder of your ChartIQ library.

Note: The instructions in this tutorial also apply to sample-template-advanced.html (in the examples/templates folder).

The first step is to add the Time Span Events plug-in to the application.

Uncomment the following line at the bottom of the technical-analysis-chart.html file:

//import "./plugins/timespanevent/timespanevent.js";

See the Implementation tutorial for more information.

Show markers

Next, we'll create a basic override of the CIQ.UI.TimeSpanEvent.prototype.showMarkers function.

In the plugins/timespanevent/ folder of your ChartIQ library, create a file named showmarkers.js.

Add the following function declaration to the file:

import { CIQ } from "../../js/chartiq.js";

/**
 * Displays swim lanes of time span events.
 *
 * @param {object} node Contains a reference to the HTML element that called this function.
 * @param {string} type Identifies the type of time span events to be displayed.
 */
CIQ.UI.TimeSpanEvent.prototype.showMarkers = function(node, type) {
    alert("Hello, Time Span Events!");
}

Include the source file

We need to include showmarkers.js in technical-analysis-chart.html.

Add the following to the technical-analysis-chart.html file:

import "./plugins/timespanevent/showmarkers.js";

The new import must come after the line that references the Time Span Events plug-in so that our custom function overrides the built-in version; for example:

import "./plugins/timespanevent/timespanevent.js";
import "./plugins/timespanevent/showmarkers.js";

Run it!

Load technical-analysis-chart.html from your editor or a web server.

Open the Events menu. Select any of the items in the PANEL EVENTS section. A JavaScript alert dialog box should open.

See the Implementation tutorial for information about the Events menu.

Create the data

As explained in the Creating a Data Source tutorial, we need to create data access objects for the individual time span events and for the swim lanes in which the events appear.

Event data objects

First, we'll create two arrays of data access objects; one for span events, one for single events.

Add the following to the showMarkers function in showmarkers.js:

const today = Date.now();
const day = 24 * 60 * 60 * 1000;

// Span events.
const weatherEvents = [
    {
        label: "Weather",
        spanLabel: "Tornadoes",
        startDate: new Date(today - 310 * day),
        endDate: new Date(today - 240 * day),
        bgColor: "#00f"
    },
    {
        label: "Weather",
        spanLabel: "Floods",
        startDate: new Date(today - 220 * day),
        endDate: new Date(today - 140 * day),
        bgColor: "#090"
    },
    {
        label: "Weather",
        spanLabel: "Wild Fires",
        startDate: new Date(today - 125 * day),
        endDate: new Date(today - 75 * day),
        bgColor: "#f00"
    },
    {
        label: "Weather",
        spanLabel: "Hurricanes",
        startDate: new Date(today - 60 * day),
        endDate: new Date(today),
        bgColor: "#f90"
    }
];

// Single events.
const reportEvents = [
    {
        label: "Report",
        startDate: new Date(today - 280 * day),
        markerShape: "square",
        bgColor: "#760",
        headline: "Quarterly Sales Report",
        story: "Sales are up 5%."
    },
    {
        label: "Report",
        startDate: new Date(today - 190 * day),
        markerShape: "square",
        bgColor: "#f44",
        headline: "Quarterly Sales Report",
        story: "Sales are flat."
    },
    {
        label: "Report",
        startDate: new Date(today - 100 * day),
        markerShape: "square",
        bgColor: "#ff0",
        headline: "Quarterly Sales Report",
        story: "Sales are improving."
    },
    {
        label: "Report",
        startDate: new Date(today - 10 * day),
        markerShape: "square",
        bgColor: "#0aa",
        headline: "Quarterly Sales Report",
        story: "Sales are up 10%."
    }
];

We're using calculations for the start and end dates to keep the data current, but date strings such as "2020-10-01" could also be used.

Note: Remove the alert call.

Swim lane data objects

Next, we need to create the swim lane data access objects.

Swim lanes are added to the panel based on the items selected in the PANEL EVENTS section of the Events menu. So, we need to create the swim lane data objects based on the input received from the menu.

The type parameter of showMarkers provides the type of event selected from the menu; we'll base our swim lane data objects on that.

Add the following to the showmarkers function after the array declarations for the event data objects:

let eventType, eventData, spanType;

switch (type) {
    case "Weather":
        eventType = "Weather";
        eventData = weatherEvents;
        spanType = "spanEvent";
        break;
    case "Report":
        eventType = "Report";
        eventData = reportEvents;
        spanType = "singleEvent";
        break;
    }

let eventLane = {
    type: eventType,
    events: eventData,
    spanType: spanType
}

The eventLane variable is assigned the swim lane data object. The switch statement determines which swim lane is created: "Weather" or "Report".

See the Creating a Data Source tutorial for a detailed explanation of swim lane data objects.

Customize the user interface

The Events menu doesn't have check boxes for "Weather" or "Report". We need to customize the UI.

We'll add our custom event types to the Events menu in technical-analysis-chart.html.

Open the file and search for "TimeSpanEvent.showMarkers". You should find the following lines of markup:

<cq-item class="span-event" stxtap="TimeSpanEvent.showMarkers('Order')" cq-no-close>Order<span class="ciq-checkbox
ciq-active" ><span></span></span></cq-item>
<cq-item class="span-event" stxtap="TimeSpanEvent.showMarkers('CEO')" cq-no-close>CEO<span class="ciq-checkbox
ciq-active" ><span></span></span></cq-item>
<cq-item class="span-event" stxtap="TimeSpanEvent.showMarkers('News')" cq-no-close>News<span class="ciq-checkbox
ciq-active" ><span></span></span></cq-item>

For the second and third cq-item tags, change the tag text and the argument of the showMarkers function call from "CEO" to "Weather" and from "News" to "Report".

Note: For each cq-item tag, make sure the tag text matches the argument of showMarkers.

For example:

<cq-item class="span-event" stxtap="TimeSpanEvent.showMarkers('Order')" cq-no-close>Order<span class="ciq-checkbox
ciq-active" ><span></span></span></cq-item>
<cq-item class="span-event" stxtap="TimeSpanEvent.showMarkers('Weather')" cq-no-close>Weather<span class="ciq-checkbox
ciq-active" ><span></span></span></cq-item>
<cq-item class="span-event" stxtap="TimeSpanEvent.showMarkers('Report')" cq-no-close>Report<span class="ciq-checkbox
ciq-active" ><span></span></span></cq-item>

Check the Events menu now. It should look like this:

Customized Events menu

Display the events

Finally, we need to send the data to the Time Span Events plug-in. The CIQ.TimeSpanEventPanel#showTimeSpanEvent function takes a swim lane data object as an argument and displays the events in a swim lane of the Time Span Events panel.

Add the following to the bottom of the showMarkers function in showmarkers.js:

this.context.stx.timeSpanEventPanel.showTimeSpanEvent(eventLane);

Our showMarkers override is a prototype function of CIQ.UI.TimeSpanEvent. As such, the function can reference CIQ.UI.TimeSpanEvent as this. The CIQ.UI.TimeSpanEvent object contains a reference to the context of the application, which includes the chart engine, stx. The chart engine maintains an instance of the Time Span Events panel (even when the panel is not visible). So, we can call the showTimeSpanEvent function with our swim lane object argument.

Load technical-analysis-chart.html and set the periodicity to 1D (in the first drop-down menu along the top of the chart). Select the Weather and Report check boxes on the Events menu. You should see the swim lanes of our time span events.

Note: We'll also eventually create events for Order. When you select the Order check box, an empty swim lane appears because we haven't yet created the data for orders. Also, if you're watching your development console, you'll see an error.

"Chart with weather and report events" Figure. Chart with weather and report events.

Manage the user interface

You may have noticed that the check boxes aren't displaying check marks, and if you select "Weather" or "Report" more than once, duplicate swim lanes appear. We can fix that.

At the bottom of showmarkers.js, replace:

this.context.stx.timeSpanEventPanel.showTimeSpanEvent(eventLane);

with:

const activeClassName = this.activeClassName;
let cl = node.node.classList;

if (cl.contains(activeClassName)) {
    cl.remove(activeClassName);
    this.context.stx.timeSpanEventPanel.removeTimeSpanEvent(type);
} else {
    cl.add(activeClassName);
    this.context.stx.timeSpanEventPanel.showTimeSpanEvent(eventLane);
}

The activeClassName variable holds the name of a CSS class that is used to indicate whether a check box is currently selected. The class name is ciq-active. If the class list of the HTML element that represents the check box contains ciq-active, the element is styled with a check mark.

The HTML element is obtained from the node parameter of showMarkers. The parameter is an object that contains a "node" property that references the cq-item element (the check box) on which showMarkers was called. And so, node.node.classList gives us the class list of cq-item.

The if-else statement toggles the check mark on and off by updating the class list, adding or removing the ciq-active class.

When the check box is cleared, the associated swim lane is removed by a call to the removeTimeSpanEvent function. The argument for removeTimeSpanEvent is just the event type; no other event data is needed.

When the check box is selected, showTimeSpanEvent is called with the swim lane data object as an argument.

Load technical-analysis-chart.html and try it out.

Add real-time data

The events we've created so far are only loosely associated with the time series of the chart. The start and end dates of the events are created with calculations that have no real relationship to the time series.

That can work for span events and single events, but duration events usually are tightly coupled to the chart data. Our next set of time span events mimics institutional stock orders made up of multiple trades.

Reference the time series data

Add the following at the top of the showMarkers function in showmarkers.js:

const dataSet = this.context.stx.chart.dataSet;
let dataEnd = dataSet.length - 1;
let offsets = [
    {start: dataEnd - 200, end: dataEnd - 170},
    {start: dataEnd - 150, end: dataEnd - 120},
    {start: dataEnd - 100, end: dataEnd - 70},
    {start: dataEnd - 40, end: dataEnd}
];

dataSet — The first thing we need to do is obtain a reference to the time series data. We do so by referencing the dataSet array that is a property of the chart object that belongs to the chart engine, stx.

dataEnd — For convenience, we obtain the index of the last (most recent in real time) data element.

offsets — We arbitrarily create reference points to elements within the data set. The points are in pairs (start and end) to mark the beginning and end of the time span events; the higher the offset, the further back in time the data. So, for the first array element, the start is 200 ticks (data elements or points in time) back from the current time; the end, 170 ticks. The end is 30 ticks more recent than the start. We've created four pairs of offsets, so we can create four time span events.

Define the duration events

Now, we'll define our simulated stock orders.

Add the following to the showmarkers function after the declaration of the reportEvents array (before the let and switch statements):

// Duration events.
const orderEvents = [
    {
        label: "Order",
        startDate: dataSet[offsets[0].start].DT,
        endDate: dataSet[offsets[0].end].DT,
        subChildren: createSubchildren(0),
        category: "trade",
        markerShape: "circle",
        headline: "Trade Start",
        story: dataSet[offsets[0].start].DT,
        bgColor: "#0af",
        showPriceLines: true
    },
    {
        label: "Order",
        startDate: dataSet[offsets[1].start].DT,
        endDate: dataSet[offsets[1].end].DT,
        subChildren: createSubchildren(1),
        category: "trade",
        markerShape: "circle",
        headline: "Trade Start",
        story: dataSet[offsets[1].start].DT,
        bgColor: "#ccc",
        showPriceLines: true
    },
    {
        label: "Order",
        startDate: dataSet[offsets[2].start].DT,
        endDate: dataSet[offsets[2].end].DT,
        subChildren: createSubchildren(2),
        category: "trade",
        markerShape: "circle",
        headline: "Trade Start",
        story: dataSet[offsets[2].start].DT,
        bgColor: "#a07",
        showPriceLines: true,
        isActive: true
    },
    {
        label: "Order",
        startDate: dataSet[offsets[3].start].DT,
        endDate: dataSet[offsets[3].end].DT,
        subChildren: createSubchildren(3),
        category: "trade",
        markerShape: "circle",
        headline: "Trade Start",
        story: dataSet[offsets[3].start].DT,
        bgColor: "#36f",
        showPriceLines: true,
        isActive: true
    }
];

The value of the startDate and endDate properties of each event data object is the date object, DT, of a time series data element. The element is indexed in the dataSet array by a value from the offsets array. The first event data object uses the first element of the offsets array (element 0); the second event data object, the second element (element 1); the third object, the third element (element 2); the fourth object, the fourth element (element 3).

The value of the story property is also a date object, which is presented as a string in the sub-event pop-up display.

The value of the subChildren property is the result returned by a createSubchildren() function, which we will define next.

createSubchildren

Duration events must have sub-children (sub-events) and the sub-children must have a price property.

We'll create the sub-children programmatically. Add the following function at the bottom of the showMarkers function:

function createSubchildren(eventIndex) {
    let subevents = [];
    let count = 0;
    for (let i = offsets[eventIndex].start; i <= offsets[eventIndex].end; i++) {
        subevents[count++] = {
            date: dataSet[i].DT,
            price: dataSet[i].High,
            headline: "Trade Execution",
            story: "Price: " + dataSet[i].High
        }
    }
    return subevents;
}

The eventIndex parameter is used to access a start/end pair from the offsets array (see Reference the time series data above). The offsets represent the time span of the event. The for loop iterates through the ticks (data elements) in the time span and creates a sub‑child data object for each tick. The DT property of the tick contains the date and time of the tick; the High property, the highest price recorded at that date and time.

See the Sub-event data objects section of the Creating a Data Source tutorial for more information on the structure of sub-children objects.

Create the duration event swim lane data object

Finally, we need to create the swim lane data access object for the duration events. Add the following to the switch statement in showMarkers.js:

case "Order":
    eventType = "Order";
    eventData = orderEvents;
    spanType = "durationEvent";
    break;

The case clause is matched when "Order" is selected from the PANEL EVENTS section of the Events menu. The clause creates the elements of the swim lane data object for the duration events. The swim lane data object is assigned to the eventLane variable, which is passed to the showTimeSpanEvent function.

The completed file

Here's an example of the completed showmarkers.js file:

import { CIQ } from "../../js/chartiq.js";

/**
 * Displays swim lanes of time span events.
 *
 * @param {object} node Contains a reference to the HTML element that called this function.
 * @param {string} type Identifies the type of time span events to be displayed.
 */
CIQ.UI.TimeSpanEvent.prototype.showMarkers = function(node, type) {

    // Reference the chart data set.
    const dataSet = this.context.stx.chart.dataSet;
    // Index the last data element.
    let dataEnd = dataSet.length - 1;
    // Create reference points to elements in the data set.
    let offsets = [
        {start: dataEnd - 200, end: dataEnd - 170},
        {start: dataEnd - 150, end: dataEnd - 120},
        {start: dataEnd - 100, end: dataEnd - 70},
        {start: dataEnd - 40, end: dataEnd}
    ];

    const today = Date.now();
    const day = 24 * 60 * 60 * 1000;

    // Span events.
    const weatherEvents = [
        {
            label: "Weather",
            spanLabel: "Tornadoes",
            startDate: new Date(today - 310 * day),
            endDate: new Date(today - 240 * day),
            bgColor: "#00f"
        },
        {
            label: "Weather",
            spanLabel: "Floods",
            startDate: new Date(today - 220 * day),
            endDate: new Date(today - 140 * day),
            bgColor: "#090"
        },
        {
            label: "Weather",
            spanLabel: "Wild Fires",
            startDate: new Date(today - 125 * day),
            endDate: new Date(today - 75 * day),
            bgColor: "#f00"
        },
        {
            label: "Weather",
            spanLabel: "Hurricanes",
            startDate: new Date(today - 60 * day),
            endDate: new Date(today),
            bgColor: "#f90"
        }
    ];

    // Single events.
    const reportEvents = [
        {
            label: "Report",
            startDate: new Date(today - 280 * day),
            markerShape: "square",
            bgColor: "#760",
            headline: "Quarterly Sales Report",
            story: "Sales are up 5%."
        },
        {
            label: "Report",
            startDate: new Date(today - 190 * day),
            markerShape: "square",
            bgColor: "#f44",
            headline: "Quarterly Sales Report",
            story: "Sales are flat."
        },
        {
            label: "Report",
            startDate: new Date(today - 100 * day),
            markerShape: "square",
            bgColor: "#ff0",
            headline: "Quarterly Sales Report",
            story: "Sales are improving."
        },
        {
            label: "Report",
            startDate: new Date(today - 10 * day),
            markerShape: "square",
            bgColor: "#0aa",
            headline: "Quarterly Sales Report",
            story: "Sales are up 10%."
        }
    ];

    // Duration events.
    const orderEvents = [
        {
            label: "Order",
            startDate: dataSet[offsets[0].start].DT,
            endDate: dataSet[offsets[0].end].DT,
            subChildren: createSubchildren(0),
            category: "trade",
            markerShape: "circle",
            headline: "Trade Start",
            story: dataSet[offsets[0].start].DT,
            bgColor: "#0af",
            showPriceLines: true
        },
        {
            label: "Order",
            startDate: dataSet[offsets[1].start].DT,
            endDate: dataSet[offsets[1].end].DT,
            subChildren: createSubchildren(1),
            category: "trade",
            markerShape: "circle",
            headline: "Trade Start",
            story: dataSet[offsets[1].start].DT,
            bgColor: "#ccc",
            showPriceLines: true
        },
        {
            label: "Order",
            startDate: dataSet[offsets[2].start].DT,
            endDate: dataSet[offsets[2].end].DT,
            subChildren: createSubchildren(2),
            category: "trade",
            markerShape: "circle",
            headline: "Trade Start",
            story: dataSet[offsets[2].start].DT,
            bgColor: "#a07",
            showPriceLines: true,
            isActive: true
        },
        {
            label: "Order",
            startDate: dataSet[offsets[3].start].DT,
            endDate: dataSet[offsets[3].end].DT,
            subChildren: createSubchildren(3),
            category: "trade",
            markerShape: "circle",
            headline: "Trade Start",
            story: dataSet[offsets[3].start].DT,
            bgColor: "#36f",
            showPriceLines: true,
            isActive: true
        }
    ];

    let eventType, eventData, spanType;

    switch (type) {
        case "Weather":
            eventType = "Weather";
            eventData = weatherEvents;
            spanType = "spanEvent";
            break;
        case "Report":
            eventType = "Report";
            eventData = reportEvents;
            spanType = "singleEvent";
            break;
        case "Order":
            eventType = "Order";
            eventData = orderEvents;
            spanType = "durationEvent";
            break;
    }

    let eventLane = {
        type: eventType,
        events: eventData,
        spanType: spanType
    }

    const activeClassName = this.activeClassName;
    let cl = node.node.classList;

    if (cl.contains(activeClassName)) {
        // Indicate that the check box has been cleared.
        cl.remove(activeClassName);
        // Remove the swim lane.
        this.context.stx.timeSpanEventPanel.removeTimeSpanEvent(type);
    } else {
        // Indicate that the check box has been selected.
        cl.add(activeClassName);
        // Add the swim lane.
        this.context.stx.timeSpanEventPanel.showTimeSpanEvent(eventLane);
    }

    function createSubchildren(eventIndex) {
        let subevents = [];
        let count = 0;
        // Iterate through the ticks in the time span.
        for (let i = offsets[eventIndex].start; i <= offsets[eventIndex].end; i++) {
            // Create a sub-event object.
            subevents[count++] = {
                date: dataSet[i].DT,
                price: dataSet[i].High,
                headline: "Trade Execution",
                story: "Price: " + dataSet[i].High
            }
        }
        return subevents;
    }

}

Run it!

Load technical-analysis-chart.html.

Here's the finished product; some event markers and sub-event markers have been selected:

Chart with custom time span events, including sub-events and pop-up displays Figure. Chart with custom time span events, including sub-events and pop-up displays.

See the Introduction tutorial for information about interacting with time span event markers.

Conclusion

You now know how to add time span events to your charting applications. All that's left is to access real data.

The ChartIQ library includes a sample data source: plugins/timespanevent/examples/timeSpanEventSample.js. Take a look at the file and see how it compares to what we created in this tutorial.

Contact us at support@chartiq.com if you have any questions about the Time Span Events plug-in.


Next steps

Explore the API documentation: