选择线系列中的点

问题描述

我想使用带有鼠标左键的修饰键来选择矩形内的数据,而不是缩放到该数据。这可能吗?我找不到合适的 API。如果有办法选择落在多边形内的数据(如套索工具),则加分。

解决方法

这是完全自定义的 ChartXY 交互的一个示例。 要点:

  • 默认矩形适合和缩放交互被禁用。

  • 线系列数据缓存到可用于自定义统计的变量中。

  • RectangleSeries 用于可视化图表上的拖动区域。

  • UI 元素用于显示所选区域的统计信息。

  • ChartXY.onSeriesBackgroundMouseDrag 事件用于将自定义操作与用户交互挂钩。

您将在下面找到一个代码片段,其中用鼠标左键拖动会创建一个矩形区域,该区域显示突出显示的 X 区域和已解决的 Y 数据范围。 释放鼠标按钮将求解完整的选定数据点数组(长度记录到控制台)。

const {
  Point,ColorRGBA,SolidFill,RadialGradientFill,SolidLine,translatePoint,lightningChart,UIOrigins,UIElementBuilders,UILayoutBuilders,emptyFill
} = lcjs;

const { createProgressiveTraceGenerator } = xydata;

const chart = lightningChart()
    .ChartXY()
    // Disable default chart interactions with left mouse button.
    .setMouseInteractionRectangleFit(false)
    .setMouseInteractionRectangleZoom(false)
    .setTitleFillStyle(emptyFill)

const axisX = chart.getDefaultAxisX()
const axisY = chart.getDefaultAxisY()

const lineSeries = chart.addLineSeries({
    dataPattern: {
        pattern: 'ProgressiveX',},})

// Generate test data set.
let dataSet
createProgressiveTraceGenerator()
    .setNumberOfPoints(10 * 1000)
    .generate()
    .toPromise()
    .then((data) => {
        // Cache data set for analytics logic + add static data to series.
        dataSet = data
        lineSeries.add(data)
    })

// Rectangle Series is used to display data selection area.
const rectangleSeries = chart.addRectangleSeries()
const rectangle = rectangleSeries
    .add({ x1: 0,y1: 0,x2: 0,y2: 0 })
    .setFillStyle(
        new RadialGradientFill({
            stops: [
                { offset: 0,color: ColorRGBA(255,255,30) },{ offset: 1,60) },],}),)
    .setStrokeStyle(
        new SolidLine({
            thickness: 2,fillStyle: new SolidFill({ color: ColorRGBA(255,255) }),)
    .dispose()

// UI elements are used to display information about the selected data points.
const uiInformationLayout = chart.addUIElement(UILayoutBuilders.Column,{ x: axisX,y: axisY }).dispose()
const uiLabel0 = uiInformationLayout.addElement(UIElementBuilders.TextBox)
const uiLabel1 = uiInformationLayout.addElement(UIElementBuilders.TextBox)

// Add events for custom interactions.
chart.onSeriesBackgroundMouseDrag((_,event,button,startLocation) => {
    // If not left mouse button,don't do anything.
    if (button !== 0) return

    // Translate start location and current location to axis coordinates.
    const startLocationAxis = translatePoint(
        chart.engine.clientLocation2Engine(startLocation.x,startLocation.y),chart.engine.scale,lineSeries.scale,)
    const curLocationAxis = translatePoint(
        chart.engine.clientLocation2Engine(event.clientX,event.clientY),)

    // Place Rectangle figure between start location and current location.
    rectangle.restore().setDimensions({
        x1: startLocationAxis.x,y1: startLocationAxis.y,x2: curLocationAxis.x,y2: curLocationAxis.y,})

    // * Gather analytics from actively selected data *
    const xStart = Math.min(startLocationAxis.x,curLocationAxis.x)
    const xEnd = Math.max(startLocationAxis.x,curLocationAxis.x)
    // Selected Y range has to be solved from data set.
    // NOTE: For top solve performance,results should be cached and only changes from previous selection area should be checked.
    const { yMin,yMax } = solveDataRangeY(xStart,xEnd)

    // Set UI labels text.
    uiLabel0.setText(`X: [${xStart.toFixed(0)},${xEnd.toFixed(0)}]`)
    uiLabel1.setText(`Y: [${yMin.toFixed(1)},${yMax.toFixed(1)}]`)

    // Place UI layout above Rectangle.
    uiInformationLayout
        .restore()
        .setOrigin(UIOrigins.LeftBottom)
        .setPosition({ x: xStart,y: Math.max(startLocationAxis.y,curLocationAxis.y) })
})

chart.onSeriesBackgroundMouseDragStop((_,)

    // Print selected data points to console.
    const xStart = Math.max(0,Math.floor(Math.min(startLocationAxis.x,curLocationAxis.x)))
    const xEnd = Math.min(dataSet.length - 1,Math.ceil(Math.max(startLocationAxis.x,curLocationAxis.x)))
    const selectedDataPoints = dataSet.slice(xStart,xEnd)
    console.log(`Selected ${selectedDataPoints.length} data points.`)

    // Hide visuals.
    rectangle.dispose()
    uiInformationLayout.dispose()
})

// Logic for solving Y data range between supplied X range from active data set.
const solveDataRangeY = (xStart,xEnd) => {
    // Reduce Y data min and max values within specified X range from data set.
    // Note,this can be very heavy for large data sets - repeative calls should be avoided as much as possible for best performance.
    let yMin = Number.MAX_SAFE_INTEGER
    let yMax = -Number.MAX_SAFE_INTEGER
    xStart = Math.max(0,Math.floor(xStart))
    xEnd = Math.min(dataSet.length - 1,Math.ceil(xEnd))
    for (let iX = xStart; iX < xEnd; iX += 1) {
        const y = dataSet[iX].y
        yMin = y < yMin ? y : yMin
        yMax = y > yMax ? y : yMax
    }
    return { yMin,yMax }
}
<script src="https://unpkg.com/@arction/xydata@1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/@arction/lcjs@3.0.0/dist/lcjs.iife.js"></script>

这种自定义交互有很多不同的方向,虽然我们不能用一个例子来涵盖每一个,但大部分逻辑应该保持不变。