Select Box (React)

This sample demonstrates how you can implement multi-point selection on a FlexChart control. Dragging on the chart will clear the currently selected points and select a new set of points that lie within the drag area. clicking on a point will also toggle its selection state, while clicking on an empty portion of the chart will clear the current selection.

Learn about FlexChart | FlexChart API Reference

This example uses React.

import 'bootstrap.css'; import '@mescius/wijmo.styles/wijmo.css'; import ReactDOM from 'react-dom/client'; import React, { useRef, useState } from 'react'; import * as wjCore from '@mescius/wijmo'; import * as wjChart from '@mescius/wijmo.react.chart'; import './app.css'; import { getData } from './data'; const wjSelected = 'wj-state-selected'; function App() { const [series1] = useState(getData(50, 0, 3)); const [series2] = useState(getData(40, 100, 12)); const [series3] = useState(getData(30, -100, 24)); const renderedRef = useRef(false); const selectionsRef = useRef([]); const mouseDownRef = useRef(false); const startRef = useRef(null); const endRef = useRef(null); const selectorRef = useRef(null); const offsetRef = useRef(null); const mousePtRef = useRef(null); const isTouchRef = useRef(false); const itemsRef = useRef([]); const chartRef = useRef(null); const chartInitialized = (sender) => { chartRef.current = sender; }; const chartRendered = (sender) => { if (!renderedRef.current) { sender.hostElement.addEventListener('mousedown', $_chartMouseDown); sender.hostElement.addEventListener('mousemove', $_chartMouseMove); sender.hostElement.addEventListener('mouseup', $_chartMouseUp); sender.hostElement.addEventListener('mouseleave', $_chartMouseLeave); sender.hostElement.addEventListener('click', $_chartClick); window.addEventListener('touchstart', () => isTouchRef.current = true, false); // // boolean flag - don't re-add event listener after resize renderedRef.current = true; // selectorRef.current = document.querySelector('#plotSelection'); sender.hostElement.appendChild(selectorRef.current); } else { // *visually* restore selection after redraw (ex. resize browser, change chart type) $_restoreSelection(); } }; const $_clearSelection = () => { selectionsRef.current.forEach((item) => { let series = item.series, el = series.getPlotElement(item.pointIndex); // if (el) { wjCore.removeClass(el, wjSelected); } }); // selectionsRef.current.length = 0; }; // helper for adding chart selection const $_addSelection = (ht) => { wjCore.addClass(ht.series.getPlotElement(ht.pointIndex), wjSelected); selectionsRef.current.push({ series: ht.series, pointIndex: ht.pointIndex }); }; // helper for removing chart selection const $_removeSelection = (ht) => { let items = selectionsRef.current.filter((item) => item.series === ht.series && item.pointIndex === ht.pointIndex), idx = items && items.length > 0 ? selectionsRef.current.indexOf(items[0]) : -1; // if (idx >= 0) { selectionsRef.current.splice(idx, 1); wjCore.removeClass(ht.series.getPlotElement(ht.pointIndex), wjSelected); } }; // finds selected plot elements after rendering and applies CSS to visually represent selection const $_restoreSelection = () => { selectionsRef.current.forEach((item) => { let series = item.series, el = series.getPlotElement(item.pointIndex); // if (el) { wjCore.addClass(el, wjSelected); } }); }; // helper to hide the selector const $_hideSelector = () => { wjCore.setCss(selectorRef.current, { 'visibility': 'hidden', 'width': 0, 'height': 0, 'left': 0, 'top': 0 }); }; // selects plot elements within drawn rectangle const $_selectWithinRect = (rect) => { if (!rect || !chartRef.current) { return; } // chartRef.current.series.forEach((item) => { let pointCount = item._getLength(); // for (let j = 0; j < pointCount; j++) { let el = item.getPlotElement(j); // if ($_elementInBounds(el, rect)) { $_addSelection({ series: item, pointIndex: j }); } } }); }; // helper to determine if plot element is within the bounds of the drawn rectangle const $_elementInBounds = (el, rect) => { let box = el.getBoundingClientRect(); return !(box.left > rect.right || box.right < rect.left || box.top > rect.bottom || box.bottom < rect.top); }; // clear selection for button click const $_clear = () => { $_clearSelection(); // update length for view itemsRef.current.length = 0; }; const $_chartClick = (e) => { if (mouseDownRef.current && !isTouchRef.current) { isTouchRef.current = false; return; } // let p = wjCore.mouseToPage(e); if (mousePtRef.current.x !== p.x || mousePtRef.current.y !== p.y) { return; } // let element = e.target, ht = chartRef.current.hitTest(e), selected = false, chartType = chartRef.current.chartType; // selected = selectionsRef.current.some((item) => { return item.series === ht.series && item.pointIndex === ht.pointIndex; }); // if (ht && ht.series && !selected && ((ht.distance <= 0 && (chartType == 0 || chartType == 1)) || ht.distance <= 15) && isTouchRef.current) { // remove selection if (wjCore.hasClass(element, wjSelected)) { $_removeSelection(ht); } else { // add selection $_addSelection(ht); } } else if (selected && ((ht.distance <= 0 && (chartType == 0 || chartType == 1)) || ht.distance <= 15) && isTouchRef.current) { $_removeSelection(ht); } else { $_clearSelection(); } // isTouchRef.current = false; // update length for view itemsRef.current.length = 0; itemsRef.current.push.apply(itemsRef.current, selectionsRef.current); }; const $_chartMouseDown = (e) => { mousePtRef.current = wjCore.mouseToPage(e); mouseDownRef.current = true; e.preventDefault(); }; const $_chartMouseUp = (e) => { var _a; if (startRef.current != null) { startRef.current = null; } // if (endRef.current != null) { let host = chartRef.current.hostElement; offsetRef.current = wjCore.getElementRect(host); let style = host.getAttribute('style'); offsetRef.current.left = offsetRef.current.left + parseInt(style ? style['padding-left'].replace('px', '') : 0); offsetRef.current.top = offsetRef.current.top + parseInt(style ? style['padding-top'].replace('px', '') : 0); // endRef.current = startRef.current = null; // $_clear(); $_selectWithinRect((_a = selectorRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()); // // update length for view itemsRef.current.length = 0; itemsRef.current.push.apply(itemsRef.current, selectionsRef.current); // e.preventDefault(); } // $_hideSelector(); mouseDownRef.current = false; }; const $_chartMouseMove = (e) => { let p = wjCore.mouseToPage(e); if (!mouseDownRef.current || (mousePtRef.current.x == p.x && mousePtRef.current.y == p.y)) { return; } // let pt = e instanceof MouseEvent ? new wjCore.Point(e.pageX, e.pageY) : new wjCore.Point(e.changedTouches[0].pageX, e.changedTouches[0].pageY); // if (startRef.current != null) { endRef.current = pt; // update selector rectangle let w = pt.x - startRef.current.x; let h = pt.y - startRef.current.y; // if (w >= 0) { wjCore.setCss(selectorRef.current, { 'left': startRef.current.x - offsetRef.current.left, 'width': w }); } else { wjCore.setCss(selectorRef.current, { 'left': pt.x - offsetRef.current.left, 'width': -w }); } if (h >= 0) { wjCore.setCss(selectorRef.current, { 'top': startRef.current.y - offsetRef.current.top, 'height': h }); } else { wjCore.setCss(selectorRef.current, { 'top': pt.y - offsetRef.current.top, 'height': -h }); } } else { wjCore.setCss(selectorRef.current, { 'visibility': 'visible' }); if (selectorRef.current) { offsetRef.current = wjCore.getElementRect(selectorRef.current); } startRef.current = pt; } e.preventDefault(); }; const $_chartMouseLeave = (e) => { if (startRef.current) { startRef.current = endRef.current = null; mouseDownRef.current = false; $_hideSelector(); } }; return (<div className="container-fluid"> <div id="plotSelection"></div> <wjChart.FlexChart chartType="Scatter" bindingX="x" initialized={chartInitialized} rendered={chartRendered}> <wjChart.FlexChartAxis wjProperty="axisY" axisLine={true}/> <wjChart.FlexChartLegend position="None"/> <wjChart.FlexChartSeries name="Experiment 1" itemsSource={series1} bindingX="x" binding="y"> </wjChart.FlexChartSeries> <wjChart.FlexChartSeries name="Experiment 2" itemsSource={series2} bindingX="x" binding="y"> </wjChart.FlexChartSeries> <wjChart.FlexChartSeries name="Experiment 3" itemsSource={series3} bindingX="x" binding="y"> </wjChart.FlexChartSeries> </wjChart.FlexChart> </div>); } const container = document.getElementById('app'); if (container) { const root = ReactDOM.createRoot(container); root.render(<App />); }
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>MESCIUS Wijmo Wijmo FlexChart Select Box</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- SystemJS --> <script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.40/system.src.js" integrity="sha512-G6mEj6h18+m3MvzdviSDfPle/TfH0//cXcB33AKlNR/Rha0yQsKefDZKRTkIZos97HEGq2JMV1RT5ybMoQ3WsQ==" crossorigin="anonymous"></script> <script src="systemjs.config.js"></script> <script> System.import('./src/app'); </script> </head> <body> <div id="app"></div> </body> </html>
.wj-flexchart { height: 600px; width: 600px; margin: 10px 0; } #plotSelection { background-color: rgba(85, 85, 85, 0.05); border: 2px dashed #808080; position: absolute; display: block; left: 0; top: 0; pointer-events: none; visibility: hidden; } .wj-flexchart .wj-state-selected { stroke-width: 20px; stroke-dasharray: none; }
export function getData(cnt, a, b) { let arr = [], x = -5 * cnt / 2; // for (let i = 0; i < cnt; i++) { let rnd = Math.random() * cnt - cnt / 2; // arr.push({ x: x, y: a + x * (b + rnd) + rnd }); // x += .5 + Math.random() * 10; } // return arr; }
(function (global) { window.process = { env: { NODE_ENV: "production" } } System.config({ transpiler: 'plugin-babel', babelOptions: { es2015: true, react: true }, meta: { '*.css': { loader: 'css' } }, paths: { // paths serve as alias 'npm:': 'node_modules/' }, // map tells the System loader where to look for things map: { 'jszip': 'npm:jszip/dist/jszip.js', '@mescius/wijmo': 'npm:@mescius/wijmo/index.js', '@mescius/wijmo.input': 'npm:@mescius/wijmo.input/index.js', '@mescius/wijmo.styles': 'npm:@mescius/wijmo.styles', '@mescius/wijmo.cultures': 'npm:@mescius/wijmo.cultures', '@mescius/wijmo.chart': 'npm:@mescius/wijmo.chart/index.js', '@mescius/wijmo.chart.analytics': 'npm:@mescius/wijmo.chart.analytics/index.js', '@mescius/wijmo.chart.animation': 'npm:@mescius/wijmo.chart.animation/index.js', '@mescius/wijmo.chart.annotation': 'npm:@mescius/wijmo.chart.annotation/index.js', '@mescius/wijmo.chart.finance': 'npm:@mescius/wijmo.chart.finance/index.js', '@mescius/wijmo.chart.finance.analytics': 'npm:@mescius/wijmo.chart.finance.analytics/index.js', '@mescius/wijmo.chart.hierarchical': 'npm:@mescius/wijmo.chart.hierarchical/index.js', '@mescius/wijmo.chart.interaction': 'npm:@mescius/wijmo.chart.interaction/index.js', '@mescius/wijmo.chart.radar': 'npm:@mescius/wijmo.chart.radar/index.js', '@mescius/wijmo.chart.render': 'npm:@mescius/wijmo.chart.render/index.js', '@mescius/wijmo.chart.webgl': 'npm:@mescius/wijmo.chart.webgl/index.js', '@mescius/wijmo.chart.map': 'npm:@mescius/wijmo.chart.map/index.js', '@mescius/wijmo.gauge': 'npm:@mescius/wijmo.gauge/index.js', '@mescius/wijmo.grid': 'npm:@mescius/wijmo.grid/index.js', '@mescius/wijmo.grid.detail': 'npm:@mescius/wijmo.grid.detail/index.js', '@mescius/wijmo.grid.filter': 'npm:@mescius/wijmo.grid.filter/index.js', '@mescius/wijmo.grid.search': 'npm:@mescius/wijmo.grid.search/index.js', '@mescius/wijmo.grid.style': 'npm:@mescius/wijmo.grid.style/index.js', '@mescius/wijmo.grid.grouppanel': 'npm:@mescius/wijmo.grid.grouppanel/index.js', '@mescius/wijmo.grid.multirow': 'npm:@mescius/wijmo.grid.multirow/index.js', '@mescius/wijmo.grid.transposed': 'npm:@mescius/wijmo.grid.transposed/index.js', '@mescius/wijmo.grid.transposedmultirow': 'npm:@mescius/wijmo.grid.transposedmultirow/index.js', '@mescius/wijmo.grid.pdf': 'npm:@mescius/wijmo.grid.pdf/index.js', '@mescius/wijmo.grid.sheet': 'npm:@mescius/wijmo.grid.sheet/index.js', '@mescius/wijmo.grid.xlsx': 'npm:@mescius/wijmo.grid.xlsx/index.js', '@mescius/wijmo.grid.selector': 'npm:@mescius/wijmo.grid.selector/index.js', '@mescius/wijmo.grid.cellmaker': 'npm:@mescius/wijmo.grid.cellmaker/index.js', '@mescius/wijmo.grid.immutable': 'npm:@mescius/wijmo.grid.immutable/index.js', '@mescius/wijmo.touch': 'npm:@mescius/wijmo.touch/index.js', '@mescius/wijmo.cloud': 'npm:@mescius/wijmo.cloud/index.js', '@mescius/wijmo.nav': 'npm:@mescius/wijmo.nav/index.js', '@mescius/wijmo.odata': 'npm:@mescius/wijmo.odata/index.js', '@mescius/wijmo.olap': 'npm:@mescius/wijmo.olap/index.js', '@mescius/wijmo.rest': 'npm:@mescius/wijmo.rest/index.js', '@mescius/wijmo.pdf': 'npm:@mescius/wijmo.pdf/index.js', '@mescius/wijmo.pdf.security': 'npm:@mescius/wijmo.pdf.security/index.js', '@mescius/wijmo.viewer': 'npm:@mescius/wijmo.viewer/index.js', '@mescius/wijmo.xlsx': 'npm:@mescius/wijmo.xlsx/index.js', '@mescius/wijmo.undo': 'npm:@mescius/wijmo.undo/index.js', '@mescius/wijmo.interop.grid': 'npm:@mescius/wijmo.interop.grid/index.js', '@mescius/wijmo.barcode': 'npm:@mescius/wijmo.barcode/index.js', '@mescius/wijmo.barcode.common': 'npm:@mescius/wijmo.barcode.common/index.js', '@mescius/wijmo.barcode.composite': 'npm:@mescius/wijmo.barcode.composite/index.js', '@mescius/wijmo.barcode.specialized': 'npm:@mescius/wijmo.barcode.specialized/index.js', "@mescius/wijmo.react.chart.analytics": "npm:@mescius/wijmo.react.chart.analytics/index.js", "@mescius/wijmo.react.chart.animation": "npm:@mescius/wijmo.react.chart.animation/index.js", "@mescius/wijmo.react.chart.annotation": "npm:@mescius/wijmo.react.chart.annotation/index.js", "@mescius/wijmo.react.chart.finance.analytics": "npm:@mescius/wijmo.react.chart.finance.analytics/index.js", "@mescius/wijmo.react.chart.finance": "npm:@mescius/wijmo.react.chart.finance/index.js", "@mescius/wijmo.react.chart.hierarchical": "npm:@mescius/wijmo.react.chart.hierarchical/index.js", "@mescius/wijmo.react.chart.interaction": "npm:@mescius/wijmo.react.chart.interaction/index.js", "@mescius/wijmo.react.chart.radar": "npm:@mescius/wijmo.react.chart.radar/index.js", "@mescius/wijmo.react.chart": "npm:@mescius/wijmo.react.chart/index.js", "@mescius/wijmo.react.core": "npm:@mescius/wijmo.react.core/index.js", '@mescius/wijmo.react.chart.map': 'npm:@mescius/wijmo.react.chart.map/index.js', "@mescius/wijmo.react.gauge": "npm:@mescius/wijmo.react.gauge/index.js", "@mescius/wijmo.react.grid.detail": "npm:@mescius/wijmo.react.grid.detail/index.js", "@mescius/wijmo.react.grid.filter": "npm:@mescius/wijmo.react.grid.filter/index.js", "@mescius/wijmo.react.grid.grouppanel": "npm:@mescius/wijmo.react.grid.grouppanel/index.js", '@mescius/wijmo.react.grid.search': 'npm:@mescius/wijmo.react.grid.search/index.js', "@mescius/wijmo.react.grid.multirow": "npm:@mescius/wijmo.react.grid.multirow/index.js", "@mescius/wijmo.react.grid.sheet": "npm:@mescius/wijmo.react.grid.sheet/index.js", '@mescius/wijmo.react.grid.transposed': 'npm:@mescius/wijmo.react.grid.transposed/index.js', '@mescius/wijmo.react.grid.transposedmultirow': 'npm:@mescius/wijmo.react.grid.transposedmultirow/index.js', '@mescius/wijmo.react.grid.immutable': 'npm:@mescius/wijmo.react.grid.immutable/index.js', "@mescius/wijmo.react.grid": "npm:@mescius/wijmo.react.grid/index.js", "@mescius/wijmo.react.input": "npm:@mescius/wijmo.react.input/index.js", "@mescius/wijmo.react.olap": "npm:@mescius/wijmo.react.olap/index.js", "@mescius/wijmo.react.viewer": "npm:@mescius/wijmo.react.viewer/index.js", "@mescius/wijmo.react.nav": "npm:@mescius/wijmo.react.nav/index.js", "@mescius/wijmo.react.base": "npm:@mescius/wijmo.react.base/index.js", '@mescius/wijmo.react.barcode.common': 'npm:@mescius/wijmo.react.barcode.common/index.js', '@mescius/wijmo.react.barcode.composite': 'npm:@mescius/wijmo.react.barcode.composite/index.js', '@mescius/wijmo.react.barcode.specialized': 'npm:@mescius/wijmo.react.barcode.specialized/index.js', 'jszip': 'npm:jszip/dist/jszip.js', 'react': 'npm:react/index.js', 'react-dom': 'npm:react-dom/index.js', 'react-dom/client': 'npm:react-dom/client.js', "scheduler": "npm:scheduler/index.js", 'redux': 'npm:redux/dist/redux.min.js', 'react-redux': 'npm:react-redux/dist/react-redux.min.js', 'bootstrap.css': 'npm:bootstrap/dist/css/bootstrap.min.css', 'css': 'npm:systemjs-plugin-css/css.js', 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', 'systemjs-babel-build':'npm:systemjs-plugin-babel/systemjs-babel-browser.js', "react-use-event-hook": "npm:react-use-event-hook/dist/esm/useEvent.js", }, // packages tells the System loader how to load when no filename and/or no extension packages: { src: { defaultExtension: 'jsx' }, "node_modules": { defaultExtension: 'js' }, } }); })(this);