CanvasComponent.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author Paul Luna <paulluna0215@gmail.com>
  5. */
  6. import PointComponent from './PointComponent';
  7. import * as React from 'react';
  8. import { Vec2 } from 'mol-math/linear-algebra';
  9. // interface Line {
  10. // x1: number;
  11. // x2: number;
  12. // y1: number;
  13. // y2: number;
  14. // };
  15. // interface Rect {
  16. // x: number;
  17. // y: number;
  18. // }
  19. // interface TextLabel {
  20. // x: number;
  21. // y: number;
  22. // }
  23. interface CanvasComponentState {
  24. points: Vec2[],
  25. selectedPoint: any,
  26. selected: number | undefined,
  27. copyPoint: any,
  28. updatedX: number,
  29. updatedY: number,
  30. }
  31. export default class CanvasComponent extends React.Component<any, CanvasComponentState> {
  32. private myRef:any;
  33. private height: number;
  34. private width: number;
  35. private padding: number;
  36. // private topLine: Line;
  37. // private bottomLine: Line;
  38. // private rightLine: Line;
  39. // private leftLine: Line;
  40. // private normalizedXLabels: Point[];
  41. // private normalizedYLabels: Point[];
  42. // private normalizedRect: Rect;
  43. // private normalizedLabel: TextLabel;
  44. // private normalizedInfoPoint: Point;
  45. // private normalizedInfoLabel: Point;
  46. // private label: TextLabel = {x: 0, y: 0.1};
  47. // private infoPoint = {x: 0.95, y: 0.5};
  48. // private infoLabel = {x: 0.945, y: 0.2};
  49. // private rainbowRect: Rect = {x: 0.0, y: 0.5};
  50. // private xLabel: Point[] = [
  51. // {x: 0, y: 0.65},
  52. // {x: 0.07, y: 0.65},
  53. // {x: 0.17, y: 0.65},
  54. // {x: 0.27, y: 0.65},
  55. // {x: 0.37, y: 0.65},
  56. // {x: 0.47, y: 0.65},
  57. // {x: 0.57, y: 0.65},
  58. // {x: 0.67, y: 0.65},
  59. // {x: 0.77, y: 0.65},
  60. // {x: 0.87, y: 0.65},
  61. // {x: 0.97, y: 0.65},
  62. // ];
  63. // private yLabel: Point[] = [
  64. // {x: 0.45, y: 0},
  65. // {x: 0.45, y: 0.1},
  66. // {x: 0.45, y: 0.2},
  67. // {x: 0.45, y: 0.3},
  68. // {x: 0.45, y: 0.4},
  69. // {x: 0.45, y: 0.5},
  70. // {x: 0.45, y: 0.6},
  71. // {x: 0.45, y: 0.7},
  72. // {x: 0.45, y: 0.8},
  73. // {x: 0.45, y: 0.9},
  74. // {x: 0.45, y: 1},
  75. // ];
  76. constructor(props: any) {
  77. super(props);
  78. this.myRef = React.createRef();
  79. this.state = {
  80. points:[
  81. Vec2.create(0, 0),
  82. Vec2.create(1, 0)
  83. ],
  84. selectedPoint: undefined,
  85. selected: undefined,
  86. copyPoint: undefined,
  87. updatedX: 0,
  88. updatedY: 0,
  89. };
  90. this.height = 400;
  91. this.width = 600;
  92. this.padding = 70;
  93. // this.normalizedXLabels = this.normalizeXLabel(this.xLabel);
  94. // this.normalizedYLabels = this.normalizeYLabel(this.yLabel);
  95. // this.topLine = {
  96. // x1: this.padding/2,
  97. // x2: this.width+this.padding/2,
  98. // y1: this.padding/2,
  99. // y2: this.padding/2
  100. // };
  101. // this.rightLine = {
  102. // x1: this.width+this.padding/2,
  103. // x2: this.width+this.padding/2,
  104. // y1: this.padding/2,
  105. // y2: this.height+this.padding/2
  106. // };
  107. // this.bottomLine = {
  108. // x1: this.padding/2,
  109. // x2: this.width+this.padding/2,
  110. // y1: this.height+this.padding/2,
  111. // y2: this.height+this.padding/2
  112. // };
  113. // this.leftLine = {
  114. // x1: this.padding/2,
  115. // x2: this.padding/2,
  116. // y1: this.padding/2,
  117. // y2: this.height+this.padding/2
  118. // };
  119. // this.normalizedLabel = this.normalizeGraphLabel(
  120. // this.label,
  121. // this.padding/2,
  122. // this.height+this.padding/2,
  123. // this.width+this.padding/2,
  124. // this.height+this.padding
  125. // );
  126. // this.normalizedInfoPoint = this.normalizeGraphLabel(
  127. // this.infoPoint,
  128. // this.padding/2,
  129. // this.height+this.padding/2,
  130. // this.width+this.padding/2,
  131. // this.height+this.padding
  132. // );
  133. // this.normalizedInfoLabel = this.normalizeGraphLabel(
  134. // this.infoLabel,
  135. // this.padding/2,
  136. // this.height+this.padding/2,
  137. // this.width+this.padding/2,
  138. // this.height+this.padding
  139. // );
  140. // this.normalizedRect = this.normalizeGraphLabel(
  141. // this.rainbowRect,
  142. // this.padding/2,
  143. // 0,
  144. // this.width+this.padding/2,
  145. // this.padding/2
  146. // );
  147. for (const point of this.props.data){
  148. this.state.points.push(point);
  149. }
  150. this.state.points.sort((a, b) => {
  151. if(a[0] === b[0]){
  152. if(a[0] === 0){
  153. return a[1]-b[1];
  154. }
  155. if(a[1] === 1){
  156. return b[1]-a[1];
  157. }
  158. return a[1]-b[1];
  159. }
  160. return a[0] - b[0];
  161. });
  162. this.handleDrag = this.handleDrag.bind(this);
  163. this.handleClickCanvas = this.handleClickCanvas.bind(this);
  164. this.handleDoubleClick = this.handleDoubleClick.bind(this);
  165. this.refCallBack = this.refCallBack.bind(this);
  166. this.handlePointUpdate = this.handlePointUpdate.bind(this);
  167. this.change = this.change.bind(this);
  168. }
  169. public render() {
  170. const points = this.renderPoints();
  171. const ghostPoint = this.state.copyPoint;
  172. const selectedPoint = this.state.selectedPoint;
  173. return ([
  174. <div key="canvas" id="canvas">
  175. <svg
  176. id="svg"
  177. className="msp-canvas"
  178. ref={this.refCallBack}
  179. viewBox={`0 0 ${this.width+this.padding} ${this.height+this.padding}`}
  180. onMouseMove={this.handleDrag}
  181. onMouseUp={this.handlePointUpdate}
  182. onClick={this.handleClickCanvas}
  183. onDoubleClick={this.handleDoubleClick}>
  184. {/* <text x={this.normalizedLabel.x} y={this.normalizedLabel.y} fontSize="250%">YOUR LABEL HERE</text> */}
  185. {/* <InfoComponent
  186. key="rootInfo"
  187. cx={this.normalizedInfoPoint.x}
  188. cy={this.normalizedInfoPoint.y}
  189. x={this.normalizedInfoLabel.x}
  190. y={this.normalizedInfoLabel.y}
  191. fill="white"
  192. stroke="black"
  193. strokeWidth="5"
  194. /> */}
  195. <g stroke="black" fill="black">
  196. <Poly
  197. data={this.state.points}
  198. k={0.5}
  199. height={this.height}
  200. width={this.width}
  201. padding={this.padding}/>
  202. {points}
  203. {ghostPoint}
  204. {selectedPoint}
  205. {/* <line x1={this.topLine.x1} x2={this.topLine.x2} y1={this.topLine.y1} y2={this.topLine.y2} stroke="black" strokeWidth="5" strokeLinecap="square"/>
  206. <line x1={this.bottomLine.x1} x2={this.bottomLine.x2} y1={this.bottomLine.y1} y2={this.bottomLine.y2} stroke="black" strokeWidth="5" strokeLinecap="square"/>
  207. <line x1={this.rightLine.x1} x2={this.rightLine.x2} y1={this.rightLine.y1} y2={this.rightLine.y2} stroke="black" strokeWidth="5" strokeLinecap="square"/>
  208. <line x1={this.leftLine.x1} x2={this.leftLine.x2} y1={this.leftLine.y1} y2={this.leftLine.y2} stroke="black" strokeWidth="5" strokeLinecap="square"/> */}
  209. </g>
  210. {/* <g className="x-labels">
  211. <text x={this.normalizedXLabels[0].x} y={this.normalizedXLabels[0].y} fontSize="250%">0</text>
  212. <text x={this.normalizedXLabels[1].x} y={this.normalizedXLabels[1].y} fontSize="250%">0.1</text>
  213. <text x={this.normalizedXLabels[2].x} y={this.normalizedXLabels[2].y} fontSize="250%">0.2</text>
  214. <text x={this.normalizedXLabels[3].x} y={this.normalizedXLabels[3].y} fontSize="250%">0.3</text>
  215. <text x={this.normalizedXLabels[4].x} y={this.normalizedXLabels[4].y} fontSize="250%">0.4</text>
  216. <text x={this.normalizedXLabels[5].x} y={this.normalizedXLabels[5].y} fontSize="250%">0.5</text>
  217. <text x={this.normalizedXLabels[6].x} y={this.normalizedXLabels[6].y} fontSize="250%">0.6</text>
  218. <text x={this.normalizedXLabels[7].x} y={this.normalizedXLabels[7].y} fontSize="250%">0.7</text>
  219. <text x={this.normalizedXLabels[8].x} y={this.normalizedXLabels[8].y} fontSize="250%">0.8</text>
  220. <text x={this.normalizedXLabels[9].x} y={this.normalizedXLabels[9].y} fontSize="250%">0.9</text>
  221. <text x={this.normalizedXLabels[10].x} y={this.normalizedXLabels[10].y} fontSize="250%">1</text>
  222. </g>
  223. <g className="y-labels">
  224. <text x={this.normalizedYLabels[0].x} y={this.normalizedYLabels[0].y} fontSize="250%">0</text>
  225. <text x={this.normalizedYLabels[1].x} y={this.normalizedYLabels[1].y} fontSize="250%">0.1</text>
  226. <text x={this.normalizedYLabels[2].x} y={this.normalizedYLabels[2].y} fontSize="250%">0.2</text>
  227. <text x={this.normalizedYLabels[3].x} y={this.normalizedYLabels[3].y} fontSize="250%">0.3</text>
  228. <text x={this.normalizedYLabels[4].x} y={this.normalizedYLabels[4].y} fontSize="250%">0.4</text>
  229. <text x={this.normalizedYLabels[5].x} y={this.normalizedYLabels[5].y} fontSize="250%">0.5</text>
  230. <text x={this.normalizedYLabels[6].x} y={this.normalizedYLabels[6].y} fontSize="250%">0.6</text>
  231. <text x={this.normalizedYLabels[7].x} y={this.normalizedYLabels[7].y} fontSize="250%">0.7</text>
  232. <text x={this.normalizedYLabels[8].x} y={this.normalizedYLabels[8].y} fontSize="250%">0.8</text>
  233. <text x={this.normalizedYLabels[9].x} y={this.normalizedYLabels[9].y} fontSize="250%">0.9</text>
  234. <text x={this.normalizedYLabels[9].x} y={this.normalizedYLabels[10].y} fontSize="250%">1</text>
  235. </g> */}
  236. <defs>
  237. <linearGradient id="Gradient">
  238. <stop offset="0%" stopColor="#d30000"/>
  239. <stop offset="30%" stopColor="#ffff05"/>
  240. <stop offset="50%" stopColor="#05ff05"/>
  241. <stop offset="70%" stopColor="#05ffff"/>
  242. <stop offset="100%" stopColor="#041ae0"/>
  243. </linearGradient>
  244. </defs>
  245. {/* <rect id="rect1" x={this.normalizedRect.x} y={this.normalizedRect.y} rx="15" ry="15" width={this.width} height="30" fill="url(#Gradient)"/> */}
  246. </svg>
  247. </div>,
  248. <div key="modal" id="modal-root" />
  249. ]);
  250. }
  251. private change(points: Vec2[]){
  252. let copyPoints = points.slice();
  253. copyPoints.shift();
  254. copyPoints.pop();
  255. this.props.onChange(copyPoints);
  256. }
  257. private handleMouseDown = (id:number) => (event: any) => {
  258. if(id === 0 || id === this.state.points.length-1){
  259. return;
  260. }
  261. const copyPoint: Vec2 = this.normalizePoint(Vec2.create(this.state.points[id][0], this.state.points[id][1]));
  262. this.setState({
  263. selected: id,
  264. copyPoint: "ready",
  265. updatedX: copyPoint[0],
  266. updatedY: copyPoint[1],
  267. });
  268. event.preventDefault();
  269. }
  270. private handleDrag(event: any) {
  271. if(this.state.copyPoint === undefined){
  272. return
  273. }
  274. const pt = this.myRef.createSVGPoint();
  275. let updatedCopyPoint;
  276. const padding = this.padding/2;
  277. pt.x = event.clientX;
  278. pt.y = event.clientY;
  279. const svgP = pt.matrixTransform(this.myRef.getScreenCTM().inverse());
  280. if( svgP.x < (padding) ||
  281. svgP.x > (this.width+(padding)) ||
  282. svgP.y > (this.height+(padding)) ||
  283. svgP.y < (padding)) {
  284. return;
  285. }
  286. updatedCopyPoint = Vec2.create(svgP.x, svgP.y);
  287. this.setState({
  288. updatedX: updatedCopyPoint[0],
  289. updatedY: updatedCopyPoint[1],
  290. });
  291. const unNormalizePoint = this.unNormalizePoint(updatedCopyPoint);
  292. this.setState({
  293. copyPoint: <PointComponent
  294. selected={false}
  295. key="copy"
  296. x={updatedCopyPoint[0]}
  297. y={updatedCopyPoint[1]}
  298. nX={unNormalizePoint[0]}
  299. nY={unNormalizePoint[1]}
  300. delete={this.deletePoint}
  301. onmouseover={this.props.onHover}/>
  302. });
  303. this.props.onDrag(unNormalizePoint);
  304. event.preventDefault()
  305. }
  306. private handlePointUpdate(event: any) {
  307. const selected = this.state.selected;
  308. if(selected === undefined || selected === 0 || selected === this.state.points.length-1) {
  309. this.setState({
  310. selected: undefined,
  311. copyPoint: undefined,
  312. });
  313. return
  314. }
  315. const updatedPoint = this.unNormalizePoint(Vec2.create(this.state.updatedX, this.state.updatedY));
  316. const points = this.state.points.filter((_,i) => i !== this.state.selected);
  317. points.push(updatedPoint);;
  318. points.sort((a, b) => {
  319. if(a[0] === b[0]){
  320. if(a[0] === 0){
  321. return a[1]-b[1];
  322. }
  323. if(a[1] === 1){
  324. return b[1]-a[1];
  325. }
  326. return a[1]-b[1];
  327. }
  328. return a[0] - b[0];
  329. });
  330. this.setState({
  331. points,
  332. selected: undefined,
  333. copyPoint: undefined,
  334. });
  335. this.change(points);
  336. event.preventDefault();
  337. }
  338. // private handleClickPoint = (id:number) => (event: any) => {
  339. // const selectedPointX = this.state.points[id][0];
  340. // const selectedPointY = this.state.points[id][1];
  341. // const normalizePoint = this.normalizePoint(Vec2.create(selectedPointX, selectedPointY));
  342. // const selectedPoint = <PointComponent
  343. // key={id}
  344. // id={id}
  345. // x={normalizePoint[0]}
  346. // y={normalizePoint[1]}
  347. // nX={selectedPointX}
  348. // nY={selectedPointY}
  349. // selected={true}
  350. // delete={this.deletePoint}
  351. // onMouseDown={this.handleMouseDown(id)}
  352. // />
  353. // this.setState({selectedPoint});
  354. // event.stopPropagation();
  355. // }
  356. private handleClickCanvas() {
  357. this.setState({selectedPoint: undefined});
  358. }
  359. private handleDoubleClick(event: any) {
  360. let newPoint;
  361. const pt = this.myRef.createSVGPoint();
  362. pt.x = event.clientX;
  363. pt.y = event.clientY;
  364. const svgP = pt.matrixTransform(this.myRef.getScreenCTM().inverse());
  365. const points = this.state.points;
  366. const padding = this.padding/2;
  367. if( svgP.x < (padding) ||
  368. svgP.x > (this.width+(padding)) ||
  369. svgP.y > (this.height+(padding)) ||
  370. svgP.y < (this.padding/2)) {
  371. return;
  372. }
  373. newPoint = this.unNormalizePoint(Vec2.create(svgP.x, svgP.y));
  374. points.push(newPoint);
  375. points.sort((a, b) => {
  376. if(a[0] === b[0]){
  377. if(a[0] === 0){
  378. return a[1]-b[1];
  379. }
  380. if(a[1] === 1){
  381. return b[1]-a[1];
  382. }
  383. return a[1]-b[1];
  384. }
  385. return a[0] - b[0];
  386. });
  387. this.setState({points})
  388. this.change(points);
  389. event.preventDefault();
  390. }
  391. private deletePoint = (i:number) => (event: any) => {
  392. if(i===0 || i===this.state.points.length-1){ return};
  393. const points = this.state.points.filter((_,j) => j !== i);
  394. points.sort((a, b) => {
  395. if(a[0] === b[0]){
  396. if(a[0] === 0){
  397. return a[1]-b[1];
  398. }
  399. if(a[1] === 1){
  400. return b[1]-a[1];
  401. }
  402. return a[1]-b[1];
  403. }
  404. return a[0] - b[0];
  405. });
  406. this.setState({points});
  407. this.change(points);
  408. event.stopPropagation();
  409. }
  410. private normalizePoint(point: Vec2) {
  411. const min = this.padding/2;
  412. const maxX = this.width+min;
  413. const maxY = this.height+min;
  414. const normalizedX = (point[0]*(maxX-min))+min;
  415. const normalizedY = (point[1]*(maxY-min))+min;
  416. const reverseY = (this.height+this.padding)-normalizedY;
  417. const newPoint = Vec2.create(normalizedX, reverseY);
  418. return newPoint;
  419. }
  420. // private normalizeGraphLabel(point: Point, minX: number, minY: number, maxX: number, maxY: number) {
  421. // const normalizedX = (point.x*(maxX-minX))+minX;
  422. // const normalizedY = (point.y*(maxY-minY))+minY;
  423. // const reverseY = (this.height+this.padding)-normalizedY;
  424. // return {x: normalizedX, y: reverseY};
  425. // }
  426. // private normalizeXLabel(points: Point[]) {
  427. // const minX = this.padding/2;
  428. // const minY = 0;
  429. // const maxX = this.width+this.padding/2;
  430. // const maxY = this.padding/2;
  431. // const normalizedPoints: Point[] = [];
  432. // for(const point of points){
  433. // normalizedPoints.push(this.normalizeGraphLabel(point, minX, minY, maxX, maxY));
  434. // }
  435. // return normalizedPoints;
  436. // }
  437. // private normalizeYLabel(points: Point[]) {
  438. // const minX = 0;
  439. // const minY = this.padding/2;
  440. // const maxX = this.padding/2;
  441. // const maxY = this.height+this.padding/2;
  442. // const normalizedPoints: Point[] = [];
  443. // for(const point of points) {
  444. // normalizedPoints.push(this.normalizeGraphLabel(point, minX, minY, maxX, maxY));
  445. // }
  446. // return normalizedPoints;
  447. // }
  448. private unNormalizePoint(point: Vec2) {
  449. const min = this.padding/2;
  450. const maxX = this.width+min;
  451. const maxY = this.height+min;
  452. const unNormalizedX = (point[0]-min)/(maxX-min);
  453. // we have to take into account that we reversed y when we first normalized it.
  454. const unNormalizedY = ((this.height+this.padding)-point[1]-min)/(maxY-min);
  455. return Vec2.create(unNormalizedX, unNormalizedY);
  456. }
  457. private refCallBack(element: any) {
  458. if(element){
  459. this.myRef = element;
  460. }
  461. }
  462. private renderPoints() {
  463. const points: any[] = [];
  464. let point: Vec2;
  465. for (let i = 0; i < this.state.points.length; i++){
  466. if(i != 0 && i != this.state.points.length-1){
  467. point = this.normalizePoint(this.state.points[i]);
  468. points.push(<PointComponent
  469. key={i}
  470. id={i}
  471. x={point[0]}
  472. y={point[1]}
  473. nX={this.state.points[i][0]}
  474. nY={this.state.points[i][1]}
  475. selected={false}
  476. delete={this.deletePoint}
  477. onmouseover={this.props.onHover}
  478. onMouseDown={this.handleMouseDown(i)}
  479. // onClick={this.handleClickPoint(i)}
  480. />);
  481. }
  482. }
  483. return points;
  484. }
  485. }
  486. function Poly(props: any) {
  487. const points: Vec2[] = [];
  488. let min:number;
  489. let maxX:number;
  490. let maxY: number;
  491. let normalizedX: number;
  492. let normalizedY: number;
  493. let reverseY: number;
  494. for(const point of props.data){
  495. min = parseInt(props.padding, 10)/2;
  496. maxX = parseInt(props.width, 10)+min;
  497. maxY = parseInt(props.height, 10)+min;
  498. normalizedX = (point[0]*(maxX-min))+min;
  499. normalizedY = (point[1]*(maxY-min))+min;
  500. reverseY = (props.height+props.padding)-normalizedY;
  501. points.push(Vec2.create(normalizedX, reverseY));
  502. }
  503. if (props.k == null) {props.k = 0.3};
  504. const data = points;
  505. const size = data.length;
  506. const last = size - 2;
  507. let path = "M" + [data[0][0], data[0][1]];
  508. for (let i=0; i<size-1;i++){
  509. const x0 = i ? data[i-1][0] : data[0][0];
  510. const y0 = i ? data[i-1][1] : data[0][1];
  511. const x1 = data[i][0];
  512. const y1 = data[i][1];
  513. const x2 = data[i+1][0];
  514. const y2 = data[i+1][1];
  515. const x3 = i !== last ? data[i+2][0] : x2;
  516. const y3 = i !== last ? data[i+2][1] : y2;
  517. const cp1x = x1 + (x2 - x0)/6 * props.k;
  518. const cp1y = y1 + (y2 -y0)/6 * props.k;
  519. const cp2x = x2 - (x3 -x1)/6 * props.k;
  520. const cp2y = y2 - (y3 - y1)/6 * props.k;
  521. path += "C" + [cp1x, cp1y, cp2x, cp2y, x2, y2];
  522. }
  523. return <path d={path} strokeWidth="5" stroke="#cec9ba" fill="none"/>
  524. }