LightningChart JSJavaScript Lasso Selection Tool with LightningChart JS

TutorialAn implementation guide of the JavaScript Lasso selection tool.

Written by a human | Updated on January 13th, 2026

JavaScript Lasso Selection Tool

In this article, we will create a chart object and implement the UI JavaScript Lasso Selection tool. The Lasso selection tool, also called the Rectangular Data Point Selection, allows the user to select multiple elements within a chart at once. Depending on the complexity of the tool, the Lasso selector will allow the user to select data points within a single geometric shape range (e.g., rectangle shape). In such a case, the dimensions of the rectangle selection area can be adjusted using the cursor in an intuitive selection action.

But for this article, and thanks to LightningChart JS, we can generate the JavaScript Lasso selection tool with a “free selection range” giving us the advantage of selecting data points using any shape we need, not just a rectangle shape. This is possible thanks to the use of lines joined by points.

The JavaScript lasso selection tool that we will create is visually like the Lasso selection tool from the Adobe Photoshop software, behavior-wise is almost the same. But differently from the Photoshop tool, the LightningChart JavaScript Lasso selection tool allows the user to group and recalculate values based on the points, intervals, bars, lines, etc., that are within the area highlighted by the lasso. 

Those calculations and data points will open in a data grid component within the same chart. Therefore, for this tutorial, we will also use the DataGrid tool. This tool will appear once our lasso area has been created and double-clicked on. Here is a video demonstrating the behavior:

The Data Grid is a table that will automatically be displayed showing us the ID of each point and its X-Y values. For this exercise we will work with an XY type chart with data points scattered within the chart.

Project Overview

zip icon
Download the project to follow the tutorial

Template Setup

1. Download the template provided to follow the tutorial.

2. After downloading the template, you’ll see a file tree like this:

javascript-refreshing-spectrum-chart-file-tree

3. Open a new terminal and run the npm install command

Getting Started

We recommend you use and update to the most recent versions of LightningChart JS and XYData. This is because some LightningChart JS tools do not exist in previous versions. In the project’s package.json file you can find the LightningChart JS dependencies:

"dependencies": {
"@arction/lcjs": "^5.2.0",
"@arction/xydata": "^1.4.0",
}

1. Importing libraries

We will start by importing the necessary libraries to create our chart.

// Import LightningChartJS
const lcjs = require('@arction/lcjs')

const { lightningChart, emptyFill, emptyLine, AutoCursorModes, Themes } = lcjs

2. Add license key (free)

Once the LightningChart JS libraries are installed, we will import them into our chart.ts file. Note you will need a trial license, which is free. We would then add it to a variable that will be used for creating the custom annotations JavaScript Chart object.

let license = undefined
try {
    license = 'xxxxxxxxxxxxx'
} catch (e) {}

Creating Containers

Containers are HTML components that will be created with the help of the createElement method. We will start by creating the body:

const exampleContainer = document.getElementById('chart') || document.body
if (exampleContainer === document.body) {
    exampleContainer.style.width = '100vw'
    exampleContainer.style.height = '100vh'
    exampleContainer.style.margin = '0px'
}

As you can see, we can assign CSS properties, in this case CSS units for the size of our main container (body).

exampleContainer.style.display = 'flex'
exampleContainer.style.flexDirection = 'row'
const containerChart1 = document.createElement('div')
const containerChart2 = document.createElement('div')
exampleContainer.append(containerChart1)
exampleContainer.append(containerChart2)
containerChart1.style.flexGrow = '1'
containerChart1.style.height = '100%'
containerChart2.style.width = '50%'
containerChart2.style.height = '100%'
containerChart2.style.display = 'none'

Afterward, two new containers will be created, both of type HTML-DIV. Container number 1 will be assigned to the scatter chart while the second to the DataGrid.

Creating the charts

const lc = lightningChart({ license: license })
const chart = lc
    .ChartXY({
        container: containerChart1,
        theme: Themes.turquoiseHexagon,
    })
    .setAutoCursorMode(AutoCursorModes.disabled)
    .setTitle('Click to select points, close selection by clicking on polygon corner')
const dataGrid = lc
    .DataGrid({
        container: containerChart2,
        theme: Themes.turquoiseHexagon,
    })
    .setTitle('Selected samples')

We will create the two charts and assign them to a constant for later access. The first chart will be an XY series scatter chart, and the second chart will be a DataGrid series. If you look carefully, the container property of each of the charts refers to the containers we created.

Properties

.ChartXY({
        theme: Themes.turquoiseHexagon,
    })

 Themedefines the look and feel of your JS treemap chart. Note that you must specify the color theme of the chart components beforehand.

.setTitle('Drag different colored annotations from left')

setTitle: sets the name at the top of the chart.

const scatterSeries = chart
    .addPointLineAreaSeries({ dataPattern: null, sizes: true })
    .setAreaFillStyle(emptyFill)
    .setStrokeStyle(emptyLine)
    .appendJSON(data)
    .fill({ size: 3 })
    .setMouseInteractions(false)
  • addPointLineAreaSeries: Adds series that can visualize any combination of Lines, Points, and Area filling. It also supports different preprocessing options (step/spline/disabled).
  • setAreaFillStyle: Sets the fill style of the area under the series trend.
  • SolidFill: Sets a solid color for the entire area fill.
  • PalettedFill: Supports the following look-up modes: x, y, and value. lookUpProperty: ‘x’ | ‘y’: Color dynamically based on x or y coordinate.
  • IndividualPointFill: Colors the area fill with individually picked sample colors. Colors are interpolated between data points.
  • LinearGradientFill: Colors the area fill with a linear gradient.
  • RadialGradientFill: The color area is filled with a radial gradient.
  • Fill: Loads the same or different values to all the samples in the dataset.
  • appendJSON: Adds several samples from the JSON file by reading values from instructed property names.
const data = new Array(5000).fill(0).map((_, i) => ({ id: i, x: Math.random() ** 2, y: Math.random() ** 1.5 }))

Lasso Selection

chart.setMouseInteractionRectangleFit(false).setMouseInteractionRectangleZoom(false)
chart.onSeriesBackgroundMouseDoubleClick(() => chart.forEachAxis((axis) => axis.fit(false)))

It is first advisable to disable built-in interactions that may generate conflicts.

  • setMouseInteractionRectangleFit: Set is mouse-interaction enabled: Fitting axes by capturing a rectangle on the frame.
  • setMouseInteractionRectangleZoom: Set is mouse-interaction enabled: Zooming axes by capturing rectangle on the frame.
  • onSeriesBackgroundMouseDoubleClick: Subscribe to the mouse-DoubleClick event on Series Background.
const polygonSeries = chart.addPolygonSeries({ automaticColorIndex: 2 }).setMouseInteractions(false).setCursorEnabled(false)
const polygonFigure = polygonSeries.add([]).setFillStyle((fill) => fill.setA(50))
const polygonMarkers = chart.addPointLineAreaSeries({ dataPattern: null }).setCursorEnabled(false)

To create the lasso, we will use a series of polygons:

addPolygonSeries: Method for adding a new PolygonSeries to the chart. This series type visualizes a collection of polygons. This series will have no interactions with the mouse and will have a color automatically generated by the AutomaticColorIndex property. With the setFillStyle method, we will specify the transparency level of the area created by the series of polygons. Finally, we will create an area series without any data pattern since this will only serve to draw the area of ​​our lasso.

Lasso Behaviors

We will need to create some conditions that control the behavior of the JavaScript lasso selection tool. These conditions will be executed depending on the event that is generated with the mouse.

chart.onSeriesBackgroundMouseMove((_, event) => {
    if (!lassoState || lassoState.closed) return
    const coordAxis = chart.translateCoordinate(event, chart.coordsAxis)
    const polygonPreview = [...lassoState.polygon, coordAxis]
    polygonFigure.setDimensions(polygonPreview)
})

onSeriesBackgroundMouseMovewill be executed every time the mouse moves over the scatter chart area. It will also draw a line from the last point created onSeriesBackgroundMouseClick. This line will serve as a preview and will move in the direction of the cursor. The line will not be established until a new point is generatedonSeriesBackgroundMouseClick

chart.onSeriesBackgroundMouseClick((_, event) => {
    // Add coordinate to polygon
    const coordAxis = chart.translateCoordinate(event, chart.coordsAxis)
    if (!lassoState || lassoState.closed) {
        lassoState = { polygon: [coordAxis] }
        scatterSeries.fill({ size: 3 })
    } else {
        lassoState.polygon.push(coordAxis)
    }
    polygonFigure.setDimensions(lassoState.polygon)
    polygonMarkers.appendSample(coordAxis)
    dataGrid.removeCells()
})

onSeriesBackgroundMouseClickwill be responsible for creating a point at a coordinate chosen by the user with the use of the mouse. To create the point, it will be necessary to click. If a data grid with values ​​already exists, it will be restarted since a new area series has been started. Once the point is created, it will draw a fixed line between it and the last point. onMouseClickwill oversee drawing the lasso area based on all the points added in the two previous events.

polygonMarkers.onMouseClick(() => {
    // Close polygon
    lassoState.closed = true
    polygonFigure.setDimensions(lassoState.polygon)
    polygonMarkers.clear()

SetDimensionswill be responsible for generating the figure. Previously, drawn polygons will be deleted with the clear function, removing all values ​​added to the line area series.

const coords = lassoState.polygon
    const xMin = coords.reduce((prev, cur) => Math.min(prev, cur.x), Number.MAX_SAFE_INTEGER)
    const xMax = coords.reduce((prev, cur) => Math.max(prev, cur.x), -Number.MAX_SAFE_INTEGER)
    const yMin = coords.reduce((prev, cur) => Math.min(prev, cur.y), Number.MAX_SAFE_INTEGER)
    const yMax = coords.reduce((prev, cur) => Math.max(prev, cur.y), -Number.MAX_SAFE_INTEGER)

Now, we will validate if the X-Y coordinates are within a safe integer, by comparing the minimum and maximum values ​​within our coordinates. MAX_SAFE_INTEGER is the maximum integer that can be safely represented in that format, meaning that all numbers below that value (and above MIN_SAFE_INTEGER) can be represented as integers.

The next block of code is the most complicated. The Loop will oversee validating and adding only the points that are in a polygon formed by the union of the specified coordinates. When we generate complex shapes, such as a star, it will create shadowed areas indirectly, since our coordinates will pass through each other. Those shaded areas do not contain specified vertices, so they technically do not exist.

for (let i = 0; i < data.length; i += 1) {
        const sample = data[i]
        let insidePolygon = false
        if (sample.x >= xMin && sample.x <= xMax && sample.y >= yMin && sample.y <= yMax) {
            if (getIsPointInsidePolygon(sample, coords)) {
                insidePolygon = true
            }
        }
        dataPointSizes[i] = insidePolygon ? 7 : 3
        if (insidePolygon) {
            dataGridContent.push([sample.id.toString(), sample.x.toFixed(3), sample.y.toFixed(3)])
        }
    }
JavaScript-Lasso-Selection-Tool-star-area

If each polygon meets the MAX_SAFE_INTEGER validation, then it will be validated with the getIspointInsidePolygon function, which will tell us if the point is inside a real polygon. If so, the point will be added to an array, which will be added to the data grid.

scatterSeries.alterSamples(0, { sizes: dataPointSizes })
    dataGrid.setTableContent(dataGridContent)
    containerChart2.style.display = 'block'

getIsPointInsidePolygon

const getIsPointInsidePolygon = (point, vertices) => {
    const x = point.x
    const y = point.y
    let inside = false
    for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
        const xi = vertices[i].x,
            yi = vertices[i].y
        const xj = vertices[j].x,
            yj = vertices[j].y
        const intersect = yi > y != yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi
        if (intersect) inside = !inside
    }
    return inside
}

This function checks if a point lies within a specific polygon, where the polygon equals the vertices (coordinates) argument. The solution is to compare each side of the polygon with the Y (vertical) coordinate of the test point and compile a list of nodes.

This topic is complex, so it is necessary to investigate polygon logic applied to programming. I recommend looking at the following publications:

This method works correctly so that you can use it without a problem. Finally, to run the project, open a new terminal and run the NPM START command to open the project on your local host.

Conclusion

Phew! I must admit that this chart is one of the most demanding I’ve done so far. The creation of the scatter chart and the data grid are simple and even the implementation of the JavaScript lasso selection tool is somewhat understandable. The lasso is a Line Area Series without properties that obey the X-Y axes, allowing us to create it in any area of the chart.

The use of onSeriesBackgroundMouseMove, onSeriesBackgroundMouseClick, and onMouseClick events, allow us to add points to our Line Area Series based on the behavior of the mouse. That is, how the series is drawn if we click on our scatter chart.

As we draw our line area series point by point, this lasso effect is generated. For each point created, a line will be created that joins them, and the more points and lines we create an area we will obtain. To create a data grid, we simply add an array with the values of each point belonging to a polygon. This process will be generated within the onMouseClick event.

The most complex step of all is to be able to validate each point and ensure that it is in a polygon. I hope this exercise has been helpful for a personal project or to learn more about LightningChart JS UI features and capabilities. Thank you!

Omar Urbano Software Engineer

Omar Urbano

Software Engineer

LinkedIn icon
divider-light

Continue learning with LightningChart

LightningChart Python Trader v1.2

LightningChart Python Trader v1.2

Announcing LightningChart Python Trader v1.2 New Product Features LightningChart Python Trader V1.2 introduces a couple of new technical indicators and drawing tools. Furthermore, several user-requested features and improvements have been added to the library.  New...