Select 线系列中的点
Select points in line series
我想用鼠标左键使用修饰键,这样 select 矩形内的数据,而不是缩放到该数据。这可能吗?我找不到合适的API。如果有办法 select 落在多边形内的数据(如套索工具)。
这是完全自定义的 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, 255, 30) },
{ offset: 1, color: ColorRGBA(255, 255, 255, 60) },
],
}),
)
.setStrokeStyle(
new SolidLine({
thickness: 2,
fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 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),
chart.engine.scale,
lineSeries.scale,
)
// 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((_, 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),
chart.engine.scale,
lineSeries.scale,
)
// 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>
这种自定义交互有很多不同的方向,虽然我们不能用一个例子来涵盖每一个方向,但大部分逻辑应该保持不变。
我想用鼠标左键使用修饰键,这样 select 矩形内的数据,而不是缩放到该数据。这可能吗?我找不到合适的API。如果有办法 select 落在多边形内的数据(如套索工具)。
这是完全自定义的 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, 255, 30) },
{ offset: 1, color: ColorRGBA(255, 255, 255, 60) },
],
}),
)
.setStrokeStyle(
new SolidLine({
thickness: 2,
fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 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),
chart.engine.scale,
lineSeries.scale,
)
// 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((_, 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),
chart.engine.scale,
lineSeries.scale,
)
// 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>
这种自定义交互有很多不同的方向,虽然我们不能用一个例子来涵盖每一个方向,但大部分逻辑应该保持不变。