问题描述
我想使用带有鼠标左键的修饰键来选择矩形内的数据,而不是缩放到该数据。这可能吗?我找不到合适的 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>
这种自定义交互有很多不同的方向,虽然我们不能用一个例子来涵盖每一个,但大部分逻辑应该保持不变。