Compare commits

..

99 Commits

Author SHA1 Message Date
vargburz b3e7f943ad 0.7.8 && render segments if exists 1 week ago
vargburz a8408a748c 0.7.7 && handle context menu 4 weeks ago
vargburz 6e6fa7dc8e 0.7.6 && update vue with markers and conf 1 month ago
rozetko f67ce313dd 0.7.5 1 month ago
rozetko c1c3b77dac Merge pull request 'area generator: support `invert` option for y/y1 axes' (#70) from area-generator-fix-invert into main 1 month ago
rozetko 53c233b969 area generator: support `invert` option for y/y1 axes 1 month ago
vargburz ebcb3185cd 0.7.4 version 1 month ago
vargburz 6422b49bf0 Merge pull request 'update area render type' (#69) from update-area-type-render into main 1 month ago
vargburz 3f9342b5e1 remove log 1 month ago
vargburz bb46fe79be update area render type 1 month ago
rozetko 0c3b404155 0.7.3 2 months ago
rozetko 85cd952b9f hotfix 2 months ago
rozetko 701feaaf2f 0.7.2 2 months ago
rozetko ebb49adf82 markers: render tooltip in center 2 months ago
rozetko f38829787c 0.7.1 2 months ago
rozetko 6549415feb markers hotfix 2 months ago
rozetko 117c0af469 Merge pull request 'better tooltips for markers' (#68) from better-markers-tooltips into main 2 months ago
rozetko aac8f6f5ac bump version to `0.7.0` 2 months ago
rozetko f4354d7a77 better tooltips for markers 2 months ago
rozetko 529f439ac9 0.6.21 2 months ago
rozetko 121f7a0305 fix renderDots not working for right y-axis 2 months ago
rozetko 7e721ab880 0.6.20 2 months ago
rozetko 3663d3eaaa upd chartwerk core to 0.6.25 2 months ago
Coin de Gamma bd598d6a0b Merge pull request '0.6.19' (#64) from 0.6.19 into main 4 months ago
glitch4347 4620ef7aa9 0.6.19 4 months ago
rozetko 44a4e6ce74 Merge pull request 'segment select' (#63) from segment-select into main 4 months ago
glitch4347 04a01b75a1 opacity param 4 months ago
glitch4347 bf3aab2db1 segment select 4 months ago
Coin de Gamma 348a289f1a Merge pull request '0.6.18' (#61) from 0.6.18 into main 6 months ago
glitch4347 17a2fdf94e 0.6.18 6 months ago
Coin de Gamma dc89f6cbb9 Merge pull request 'basic react component implementatino is sep project' (#59) from better-react-component-#58 into main 6 months ago
glitch4347 547c15c0b6 optimise excludes in libs in webpack 6 months ago
glitch4347 32d122ba4d back dev build 6 months ago
glitch4347 9c165f4e1f basic react component implementatino is sep project 6 months ago
Coin de Gamma ec2bd27acd Merge pull request '0.6.17' (#54) from 0.6.17 into main 6 months ago
glitch4347 815144d209 update core 0.6.23 6 months ago
glitch4347 1a80ca2cab 0.6.17 6 months ago
Coin de Gamma ee08db2f54 Merge pull request 'use events instead of eventsCallbacks' (#53) from core-0.6.23 into main 6 months ago
glitch4347 884fed6582 use events instead of eventsCallbacks 6 months ago
glitch4347 8aca72ce04 zoom out with ranges logic 6 months ago
Coin de Gamma c62a1c386f Merge pull request 'rename demo->basic, live and vertical && update readme' (#47) from rename-examples into main 6 months ago
glitch4347 de6237891c rename demo->basic, live and vertical && update readme 6 months ago
rozetko 7bcd0d298a 0.6.16 8 months ago
rozetko ee2f02be96 upd core 8 months ago
rozetko 456b888fe1 0.6.15 8 months ago
rozetko a34c0574e4 hotfix 8 months ago
rozetko 73dbf0ea67 0.6.14 8 months ago
rozetko ff5973b106 Merge pull request 'updateData: update segments and markers' (#44) from update-segments-and-markers-on-update-data into main 8 months ago
rozetko 878e86d849 updateData: update segments and markers 8 months ago
vargburz 1e8f8d0cb3 0.6.13 8 months ago
rozetko 39412e5f65 Merge pull request 'roken shared crosshair #42' (#43) from broken-shared-crosshair-#42 into main 8 months ago
rozetko 37da147343 Merge pull request '0.6.19 core usdage' (#41) from build-fail-on-core-0.6.18-udpate-#40 into main 8 months ago
glitch4347 0dac1c390e fix 8 months ago
glitch4347 07f454fe51 0.6.19 core usdage 8 months ago
vargburz 88d6b2b24a Merge pull request 'click-event' (#39) from click-event into main 8 months ago
glitch4347 837004c2ad onclick 9 months ago
glitch4347 e46c7bdfb6 some commit 9 months ago
rozetko ee7a57685f 0.6.12 9 months ago
rozetko 963011b274 Merge pull request 'pod fix' (#37) from strange-updates into main 9 months ago
glitch4347 283f0995e8 pod fix 9 months ago
rozetko 19689a68cd Merge pull request 'mouse-over-and-react' (#36) from mouse-over-and-react into main 9 months ago
glitch4347 79b759569f simpler example 9 months ago
glitch4347 d66da50c1a rm line 9 months ago
glitch4347 d9d1b5e501 example with mouse over and refactor react component events 9 months ago
Coin de Gamma b8cd14e4b8 Merge pull request 'marker-callback-#25' (#35) from marker-callback-#25 into main 9 months ago
glitch4347 e470d58611 fx Marker callback type 9 months ago
glitch4347 26d5956c17 markers conf in react 9 months ago
glitch4347 43241bbcb7 markers conf + events 9 months ago
glitch4347 82fc9a10f1 markers conf + new example 9 months ago
rozetko 57ae85c20e Merge pull request 'react-component-marker-and-segment-feature-#28' (#33) from react-component-marker-and-segment-feature-#28 into main 9 months ago
glitch4347 98e4e42fc4 callbacks 9 months ago
glitch4347 6fbf833150 event callback back 9 months ago
glitch4347 a0e27d5efd rm My_CONST 9 months ago
glitch4347 727f8cac86 codefix 9 months ago
glitch4347 869488d405 codestyle 9 months ago
glitch4347 d97b0e9b0c markers++ 9 months ago
glitch4347 11ab01a7d2 markers++ 9 months ago
glitch4347 326c666435 component refactorinmg begin 10 months ago
rozetko 33cf970d5f Merge pull request 'basic segments impl' (#27) from segments-feature-#19 into main 10 months ago
glitch4347 03a562c0d2 codestytle fix 10 months ago
glitch4347 38b6eb2326 basic segments impl 10 months ago
rozetko af9aaf6d82 Merge pull request 'markers-feature-#18' (#23) from markers-feature-#18 into main 10 months ago
glitch4347 bf679019fc payload comment 10 months ago
glitch4347 86099c57b3 pointe events 10 months ago
glitch4347 9a6c5cd7f4 markers++ 10 months ago
glitch4347 aadcd013bf markers++ 10 months ago
glitch4347 a5c737fa81 marker data continue 10 months ago
rozetko c451df6e3b 0.6.11 10 months ago
rozetko 3ff6f7b8e8 crosshair hotfix 10 months ago
glitch4347 1d1a34f9bf markers begin 10 months ago
glitch4347 8346bceedf markers examples begin 10 months ago
vargburz 1ca84c53f8 Merge pull request '0.6.10 version' (#21) from version-0.6.10 into main 10 months ago
vargburz 85793a7965 0.6.10 version 10 months ago
vargburz 7e4a895e93 Merge pull request 'add copy-webpack-=plugin' (#20) from update-copy-webpack into main 10 months ago
glitch4347 f1c3ef6f1d add copy-webpack-=plugin 10 months ago
rozetko 6a0ea4303e Merge pull request 'add react component and build for that' (#16) from react-component-attempt-2 into main 10 months ago
glitch4347 08b128ea91 codestyle fixes 10 months ago
glitch4347 f1582fc610 rm comment 10 months ago
glitch4347 e98997995f add react component and build for that 10 months ago
  1. 2
      .gitignore
  2. 0
      LICENSE
  3. 11
      README.md
  4. 10
      build/webpack.base.conf.js
  5. 0
      build/webpack.dev.conf.js
  6. 0
      build/webpack.prod.conf.js
  7. 42
      examples/area.html
  8. 0
      examples/basic.html
  9. 0
      examples/live.html
  10. 51
      examples/markers.html
  11. 47
      examples/markers_select.html
  12. 33
      examples/mouse_click.html
  13. 29
      examples/mouse_move.html
  14. 33
      examples/right_click.html
  15. 41
      examples/segments.html
  16. 46
      examples/segments_select.html
  17. 0
      examples/vertical.html
  18. 37
      examples/zoom_out.html
  19. 31
      package.json
  20. 33
      packages/line-pod/package.json
  21. 13
      packages/line-pod/src/types.ts
  22. 27
      packages/react/README.md
  23. 13
      packages/react/index.html
  24. 126
      packages/react/lib/components/ChartwerkLinePod.tsx
  25. 1
      packages/react/lib/main.ts
  26. 36
      packages/react/package.json
  27. 1
      packages/react/public/vite.svg
  28. 36
      packages/react/src/App.tsx
  29. 10
      packages/react/src/main.tsx
  30. 1
      packages/react/src/vite-env.d.ts
  31. 6
      packages/react/tsconfig-build.json
  32. 25
      packages/react/tsconfig.json
  33. 10
      packages/react/tsconfig.node.json
  34. 29
      packages/react/vite.config.ts
  35. 26
      react/build/webpack.base.conf.js
  36. 8
      react/build/webpack.dev.conf.js
  37. 6
      react/build/webpack.prod.conf.js
  38. 23
      react/package.json
  39. 74
      react/src/index.tsx
  40. 3
      react/tsconfig.json
  41. 1322
      react/yarn.lock
  42. 139
      src/components/markers.ts
  43. 73
      src/components/segments.ts
  44. 252
      src/index.ts
  45. 5
      src/models/line_series.ts
  46. 19
      src/models/marker.ts
  47. 11
      src/models/segment.ts
  48. 41
      src/types.ts
  49. 23
      tsconfig.json
  50. 3169
      yarn.lock

2
.gitignore vendored

@ -2,7 +2,7 @@ node_modules
dist dist
# yarn # yarn
.yarn/* .yarn
!.yarn/patches !.yarn/patches
!.yarn/plugins !.yarn/plugins
!.yarn/releases !.yarn/releases

0
packages/line-pod/LICENSE → LICENSE

11
README.md

@ -19,12 +19,11 @@
``` ```
#### Other examples #### Other examples
[Static Chart](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/demo.html) * [Basic](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/basic.html)
* [Live](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/live.html)
[Live Chart](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/demo_live.html) * [Vertical](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/vertical.html)
* [Segments](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/segments.html)
[Vertical Chart](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/demo_vertical.html) * [Markers](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/markers.html)
### Development ### Development

10
packages/line-pod/build/webpack.base.conf.js → build/webpack.base.conf.js

@ -1,4 +1,5 @@
const path = require('path'); const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");
function resolve(dir) { function resolve(dir) {
@ -8,7 +9,14 @@ function resolve(dir) {
module.exports = { module.exports = {
context: resolve('src'), context: resolve('src'),
entry: './index.ts', entry: './index.ts',
plugins: [], plugins: [
new CopyPlugin({
patterns: [
{ from: "../react/dist/index.js", to: "react/index.js" },
{ from: "../react/dist/index.d.ts", to: "react/index.d.ts" },
],
})
],
module: { module: {
rules: [ rules: [
{ {

0
packages/line-pod/build/webpack.dev.conf.js → build/webpack.dev.conf.js

0
packages/line-pod/build/webpack.prod.conf.js → build/webpack.prod.conf.js

42
examples/area.html

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 50%; height: 500px;"></div>
<script type="text/javascript">
let options = {
renderLegend: false, usePanning: false,
axis: {
x: { format: 'numeric', range: [0, 100] },
y: { invert: true, range: [0, 100] },
y1: { isActive: true, format: 'numeric', range: [0, 1000] },
},
zoomEvents: {
mouse: { zoom: { isActive: false, orientation: 'horizontal' } },
scroll: { zoom: { isActive: false, orientation: 'horizontal' } }
},
}
const data1 = [[0,0], [35, 40], [65, 60], [100, 100]];
const data2 = [[0,0], [35, 50], [65, 65], [80, 100]];
const data3 = [[0,0], [35, 20], [65, 50], [100, 80]];
const data4 = [[0,900], [35, 800], [65, 700], [100, 600]];
var pod = new LinePod(
document.getElementById('chart'),
[
{ target: 'test1', datapoints: data1, color: 'green', renderArea: 'Above' },
{ target: 'test2', datapoints: data2, color: 'blue', renderArea: 'Below' },
{ target: 'test3', datapoints: data3, color: 'orange', renderArea: 'Below', yOrientation: 'right' },
{ target: 'test4', datapoints: data4, color: 'purple', renderArea: 'Above', yOrientation: 'right' },
],
options
);
pod.render();
</script>
</body>
</html>

0
packages/line-pod/examples/demo.html → examples/basic.html

0
packages/line-pod/examples/demo_live.html → examples/live.html

51
examples/markers.html

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 100%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1701790172908;
const timeSerieData = [5, 6, 3, 7, 5, 6, 8, 4, 5, 6, 4, 3, 5, 7, 8]
.map((el, idx) => [startTime + idx * 1000, el]);
const markersData1 = [3, 6, 9].map(el => ({
x: startTime + el * 1000,
color: 'red',
alwaysDisplay: false,
html: new Date(startTime).toISOString(),
}));
const markersData2 = [4, 11].map(el => ({
x: startTime + el * 1000,
color: 'blue',
alwaysDisplay: true,
html: new Date(startTime).toISOString(),
}));
let options = {
renderLegend: false,
axis: {
y: { range: [0, 10] },
x: { format: 'time' }
},
}
var pod = new LinePod(
document.getElementById('chart'),
[
{ datapoints: timeSerieData, color: 'black' },
],
options,
{
series: [
{ data: markersData1 },
{ data: markersData2 },
]
}
);
pod.render();
</script>
</body>
</html>

47
examples/markers_select.html

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 100%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1701790172908;
const timeSerieData = [5, 6, 3, 7, 5, 6, 8, 4, 5, 6, 4, 3, 5, 7, 8]
.map((el, idx) => [startTime + idx * 1000, el]);
const markersData = [3, 6, 9].map(el => ({
x: startTime + el * 1000,
payload: el,
color: 'red',
}));
let options = {
renderLegend: false,
axis: {
y: { range: [0, 10] },
x: { format: 'time' }
},
}
var pod = new LinePod(
document.getElementById('chart'),
[
{ datapoints: timeSerieData, color: 'black' },
],
options,
{
series: [
{ data: markersData },
],
events: {
onMouseMove: (el) => { console.log(el); },
onMouseOut: () => { console.log('mouse out'); }
}
}
);
pod.render();
</script>
</body>
</html>

33
examples/mouse_click.html

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 50%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1590590148;
const data = Array.from(
{ length: 20 },
(el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 30)]
);
let options = {
renderLegend: false, usePanning: false,
axis: { y: { range: [0, 50] } },
zoomEvents: { mouse: {
zoom: { isActive: false },
pan: { isActive: false },
} },
eventsCallbacks: { mouseClick: console.log }
}
var pod = new LinePod(
document.getElementById('chart'),
[{ datapoints: data }],
options
);
pod.render();
</script>
</body>
</html>

29
examples/mouse_move.html

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 50%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1590590148;
const data = Array.from(
{ length: 20 },
(el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 30)]
);
let options = {
renderLegend: false, usePanning: false,
axis: { y: { range: [0, 50] } },
eventsCallbacks: { mouseMove: console.log }
}
var pod = new LinePod(
document.getElementById('chart'),
[{ datapoints: data }],
options
);
pod.render();
</script>
</body>
</html>

33
examples/right_click.html

@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 50%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1590590148;
const data = Array.from(
{ length: 20 },
(el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 30)]
);
let options = {
renderLegend: false, usePanning: false,
axis: { y: { range: [0, 50] } },
zoomEvents: { mouse: {
zoom: { isActive: false },
pan: { isActive: false },
} },
eventsCallbacks: { contextMenu: (position) => console.log('contextMenu', position) }
}
var pod = new LinePod(
document.getElementById('chart'),
[{ datapoints: data }],
options
);
pod.render();
</script>
</body>
</html>

41
examples/segments.html

@ -0,0 +1,41 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 100%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1701790172908;
const timeSerieData = [5, 6, 3, 7, 5, 6, 8, 4, 5, 6, 4, 3, 5, 7, 8]
.map((el, idx) => [startTime + idx * 1000, el]);
const segmentsData = [3, 6, 9].map(el => [startTime + el * 1000, startTime + (el + 1) * 1100]);
let options = {
renderLegend: false,
axis: {
y: { range: [0, 10] },
x: { format: 'time' }
},
}
var pod = new LinePod(
document.getElementById('chart'),
[
{ datapoints: timeSerieData, color: 'black' },
],
options,
undefined,
[
{
data: segmentsData,
color:'#FFE545'
}
]
);
pod.render();
</script>
</body>
</html>

46
examples/segments_select.html

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 100%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1701790172908;
const timeSerieData = [5, 6, 3, 7, 5, 6, 8, 4, 5, 6, 4, 3, 5, 7, 8]
.map((el, idx) => [startTime + idx * 1000, el]);
const segmentsData = [3, 6, 9].map(el => [startTime + el * 1000, startTime + (el + 1) * 1100]);
let options = {
renderLegend: false,
axis: {
y: { range: [0, 10] },
x: { format: 'time' }
},
}
var pod = new LinePod(
document.getElementById('chart'),
[
{ datapoints: timeSerieData, color: 'black' },
],
options,
undefined,
[
{
data: segmentsData,
color:'#FFE545',
select: true,
opacity: 0.4,
opacitySelect: 0.8,
onSelect: console.log,
onUnselect: console.log
}
]
);
pod.render();
</script>
</body>
</html>

0
packages/line-pod/examples/demo_vertical.html → examples/vertical.html

37
examples/zoom_out.html

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 50%; height: 500px;"></div>
<script type="text/javascript">
const startTime = 1590590148;
const data = Array.from(
{ length: 20 },
(el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 30)]
);
let options = {
renderLegend: false, usePanning: false,
axis: { y: { range: [0, 50] } },
zoomEvents: { mouse: {
zoom: { isActive: true, orientation: "horizontal" },
pan: { isActive: false },
}},
events: {
zoomOut: function(centers, ranges) {
console.log('zoomOut', centers, ranges);
}
}
}
var pod = new LinePod(
document.getElementById('chart'),
[{ datapoints: data }],
options
);
pod.render();
</script>
</body>
</html>

31
package.json

@ -1,16 +1,37 @@
{ {
"name": "@chartwerk/line-pod", "name": "@chartwerk/line-pod",
"version": "0.6.9", "version": "0.7.8",
"description": "Chartwerk line chart", "description": "Chartwerk line chart",
"workspaces": [ "main": "dist/index.js",
"packages/line-pod", "files": [
"packages/react" "/dist"
], ],
"scripts": {
"build": "rm -rf dist && cd react && yarn build && cd .. && webpack --config build/webpack.prod.conf.js && webpack --config build/webpack.dev.conf.js",
"dev": "webpack --watch --config build/webpack.dev.conf.js",
"test": "echo \"Error: no test specified\" && exit 1",
"update-core": "yarn up @chartwerk/core && yarn up @chartwerk/core@latest"
},
"repository": { "repository": {
"type": "git", "type": "git",
"url": "http://code.corpglory.net/chartwerk/line-pod.git" "url": "http://code.corpglory.net/chartwerk/line-pod.git"
}, },
"author": "CorpGlory", "author": "CorpGlory",
"license": "ISC", "license": "ISC",
"packageManager": "yarn@3.2.1" "dependencies": {
"@chartwerk/core": "^0.6.26"
},
"devDependencies": {
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.3",
"typescript": "^5.1.3",
"webpack": "^5.87.0",
"webpack-cli": "^5.1.4"
},
"packageManager": "yarn@3.2.1",
"workspaces": [
"react/*"
]
} }

33
packages/line-pod/package.json

@ -1,33 +0,0 @@
{
"name": "line-pod",
"version": "0.6.9",
"description": "Chartwerk line chart",
"main": "dist/index.js",
"files": [
"/dist"
],
"scripts": {
"build": "webpack --config build/webpack.prod.conf.js && webpack --config build/webpack.dev.conf.js",
"dev": "webpack --watch --config build/webpack.dev.conf.js",
"test": "echo \"Error: no test specified\" && exit 1",
"update-core": "yarn up @chartwerk/core && yarn up @chartwerk/core@latest"
},
"repository": {
"type": "git",
"url": "https://gitlab.com/chartwerk/line-pod.git"
},
"author": "CorpGlory",
"license": "ISC",
"dependencies": {
"@chartwerk/core": "latest"
},
"devDependencies": {
"css-loader": "^6.8.1",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.3",
"typescript": "^5.1.3",
"webpack": "^5.87.0",
"webpack-cli": "^5.1.4"
},
"packageManager": "yarn@3.2.1"
}

13
packages/line-pod/src/types.ts

@ -1,13 +0,0 @@
import { Serie, Options } from '@chartwerk/core';
type LineTimeSerieParams = {
maxLength: number,
renderDots: boolean,
renderLines: boolean, // TODO: refactor same as scatter-pod
dashArray: string; // dasharray attr, only for lines
class: string; // option to add custom class to each serie element
renderArea: boolean; // TODO: move to render type
}
export type LineTimeSerie = Serie & Partial<LineTimeSerieParams>;
export type LineOptions = Options;

27
packages/react/README.md

@ -1,27 +0,0 @@
# React + TypeScript + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
- Configure the top-level `parserOptions` property like this:
```js
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: ['./tsconfig.json', './tsconfig.node.json'],
tsconfigRootDir: __dirname,
},
```
- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked`
- Optionally add `plugin:@typescript-eslint/stylistic-type-checked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list

13
packages/react/index.html

@ -1,13 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Line-Pod Demo</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

126
packages/react/lib/components/ChartwerkLinePod.tsx

@ -1,126 +0,0 @@
import type { LineTimeSerie, LineOptions } from 'line-pod';
import { LinePod } from 'line-pod';
// import { AxisRange } from '@chartwerk/core/dist/types';
import { useEffect, useRef, useState, PropsWithChildren, forwardRef } from 'react';
import * as _ from 'lodash';
export type AxisRange = [number, number] | undefined;
export type ChartwerkLinePodProps = {
id: string;
series: LineTimeSerie[];
options: LineOptions;
className?: string;
// TODO: callback types should be exported from chartwerk
onZoomIn?: (ranges: AxisRange[]) => void;
onZoomOut?: (centers: { x: number, y: number }) => void;
onMouseMove?: (event: any) => void;
onMouseOut?: () => void;
onLegendClick?: (idx: number) => void,
onPanning?: (event: { ranges: AxisRange[], d3Event: any }) => void;
onPanningEnd?: (ranges: AxisRange[]) => void;
onContextMenu?: (evt: any) => void;
onSharedCrosshairMove?: (evt: any) => void;
onRenderStart?: () => void,
onRenderEnd?: () => void,
}
export const ChartwerkLinePod = forwardRef<HTMLDivElement, PropsWithChildren<ChartwerkLinePodProps>>((props, ref) => {
const [pod, setPod] = useState<LinePod | null>(null);
const [hack, setHack] = useState<number | null>(null);
const chartRef = useRef(null);
const chart = chartRef.current;
useEffect(() => {
// this function will be called on component unmount
return () => {
if (pod === null) {
return;
}
console.log('remove chart');
// @ts-ignore
pod.removeEventListeners();
}
}, []);
useEffect(() => {
if (chart === null) {
return;
}
let eventsCallbacks = _.cloneDeep(props.options.eventsCallbacks || {});
if (props.onZoomIn) {
eventsCallbacks.zoomIn = props.onZoomIn;
}
if (props.onZoomOut) {
eventsCallbacks.zoomOut = props.onZoomOut;
}
if (props.onMouseMove) {
eventsCallbacks.mouseMove = props.onMouseMove;
}
if (props.onMouseOut) {
eventsCallbacks.mouseOut = props.onMouseOut;
}
if (props.onLegendClick) {
eventsCallbacks.onLegendClick = props.onLegendClick;
}
if (props.onPanning) {
eventsCallbacks.panning = props.onPanning;
}
if (props.onPanningEnd) {
eventsCallbacks.panningEnd = props.onPanningEnd;
}
if (props.onContextMenu) {
eventsCallbacks.contextMenu = props.onContextMenu;
}
if (props.onSharedCrosshairMove) {
eventsCallbacks.sharedCrosshairMove = props.onSharedCrosshairMove;
}
if (props.onRenderStart) {
eventsCallbacks.renderStart = props.onRenderStart;
}
if (pod === null) {
console.log('create chart');
const newPod = new LinePod(
// @ts-ignore
chart,
props.series,
{
...props.options,
eventsCallbacks,
}
);
setPod(
newPod
);
console.log('initial chart render');
newPod.render();
} else {
console.log('update chart');
pod.updateData(props.series, {
...props.options,
eventsCallbacks,
});
}
}, [chart, pod, props.id, props.series, props.options]);
// TODO: it's a hack to render the LinePod right after the div appears in DOM
setTimeout(() => {
if (hack === null) {
setHack(1);
}
}, 1);
return (
<div ref={ref}>
<div id={props.id} className={props.className} ref={chartRef}></div>
{props.children}
</div>
);
});

1
packages/react/lib/main.ts

@ -1 +0,0 @@
export { ChartwerkLinePod } from './components/ChartwerkLinePod';

36
packages/react/package.json

@ -1,36 +0,0 @@
{
"name": "react-line-pod",
"version": "0.0.0",
"type": "module",
"main": "dist/react-line-pod.js",
"types": "dist/main.d.ts",
"files": [
"dist"
],
"scripts": {
"dev": "vite",
"build": "tsc --p ./tsconfig-build.json && vite build",
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
"preview": "vite preview"
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@types/lodash": "^4.14.201",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",
"@typescript-eslint/eslint-plugin": "^6.0.0",
"@typescript-eslint/parser": "^6.0.0",
"@vitejs/plugin-react": "^4.0.3",
"eslint": "^8.45.0",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.3",
"line-pod": "workspace:^",
"lodash": "^4.17.21",
"typescript": "^5.0.2",
"vite": "^4.4.5",
"vite-plugin-dts": "^3.6.3"
}
}

1
packages/react/public/vite.svg

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

36
packages/react/src/App.tsx

@ -1,36 +0,0 @@
import { ChartwerkLinePod } from '../lib/main';
function App() {
const startTime = 1590590148;
const arrayLength = 20;
const data1 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 40)]);
const data2 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 10)]);
const data3 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 20) + 90]);
const options = {
renderLegend: false, usePanning: false,
axis: {
y: { invert: false, range: [0, 350] },
y1: { isActive: true, range: [0, 10], ticksCount: 8 },
x: { format: 'time' }
},
zoomEvents: {
mouse: { zoom: { isActive: true, orientation: 'horizontal' } },
scroll: { zoom: { isActive: true, orientation: 'horizontal' } }
}
};
const series = [
{ target: 'test1', datapoints: data1, color: 'green', dashArray: '5,3', class: 'first', renderArea: true },
{ target: 'test2', datapoints: data2, color: 'blue', yOrientation: 'right' },
{ target: 'test3', datapoints: data3, color: 'orange' },
];
return (
<>
{/* @ts-ignore */}
<ChartwerkLinePod id="demo" series={series} options={options}></ChartwerkLinePod>
</>
);
}
export default App;

10
packages/react/src/main.tsx

@ -1,10 +0,0 @@
import App from './App.tsx';
import React from 'react';
import ReactDOM from 'react-dom/client';
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>
);

1
packages/react/src/vite-env.d.ts vendored

@ -1 +0,0 @@
/// <reference types="vite/client" />

6
packages/react/tsconfig-build.json

@ -1,6 +0,0 @@
{
"extends": "./tsconfig.json",
"include": [
"lib"
]
}

25
packages/react/tsconfig.json

@ -1,25 +0,0 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "lib"],
"references": [{ "path": "./tsconfig.node.json" }]
}

10
packages/react/tsconfig.node.json

@ -1,10 +0,0 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

29
packages/react/vite.config.ts

@ -1,29 +0,0 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import dts from 'vite-plugin-dts';
import { resolve } from 'path';
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
react(),
dts({ include: ['lib'] }),
],
optimizeDeps: {
include: ['line-pod'],
},
build: {
copyPublicDir: false,
lib: {
entry: resolve(__dirname, 'lib/main.ts'),
formats: ['es']
},
rollupOptions: {
external: ['react', 'react/jsx-runtime'],
},
commonjsOptions: {
include: [/line-pod/],
},
}
});

26
react/build/webpack.base.conf.js

@ -0,0 +1,26 @@
const path = require('path');
module.exports = {
entry: './src/index.tsx',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'index.js',
path: path.resolve(__dirname, '../dist'),
libraryTarget: 'umd',
umdNamedDefine: true,
},
externals: [
'@chartwerk/line-pod', 'react'
]
};

8
react/build/webpack.dev.conf.js

@ -0,0 +1,8 @@
const baseWebpackConfig = require('./webpack.base.conf');
var conf = baseWebpackConfig;
conf.devtool = 'inline-source-map';
conf.mode = 'development';
conf.output.filename = 'index.dev.js';
module.exports = conf;

6
react/build/webpack.prod.conf.js

@ -0,0 +1,6 @@
const baseWebpackConfig = require('./webpack.base.conf');
var conf = baseWebpackConfig;
conf.mode = 'production';
module.exports = baseWebpackConfig;

23
react/package.json

@ -0,0 +1,23 @@
{
"name": "line-pod-react",
"version": "0.0.1",
"description": "React wrapper around line-pod",
"main": "dist/index.js",
"repository": "http://code.corpglory.net/chartwerk/line-pod.git",
"author": "CorpGlory Inc.",
"license": "ISC",
"scripts": {
"build": "webpack --config build/webpack.prod.conf.js",
"dev": "webpack --config build/webpack.dev.conf.js"
},
"dependencies": {
"@chartwerk/line-pod": "workspace:*",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"ts-loader": "^9.5.1",
"typescript": "^5.4.3",
"webpack": "^5.87.0"
}
}

74
react/src/index.tsx

@ -0,0 +1,74 @@
import { LineTimeSerie, LineOptions, LinePod } from '@chartwerk/line-pod';
import { MarkersConf } from '@chartwerk/line-pod/dist/models/marker';
import { SegmentSerie } from '@chartwerk/line-pod/dist/models/segment';
import React, { useEffect, useRef, useState } from 'react';
import _ from 'lodash';
export type ChartwerkLinePodProps = {
id?: string;
series: LineTimeSerie[];
options?: LineOptions;
markersConf?: MarkersConf,
segments?: SegmentSerie[],
className?: string;
}
export function ChartwerkLinePod(props: ChartwerkLinePodProps) {
const [pod, setPod] = useState<LinePod | null>(null);
const [hack, setHack] = useState<number | null>(null);
const chartRef = useRef(null);
const chart = chartRef.current;
useEffect(() => {
// this function will be called on component unmount
return () => {
if(pod === null) { return; }
// @ts-ignore
pod.removeEventListeners();
}
}, []);
useEffect(() => {
if(chart === null) { return; }
if(pod === null) {
const newPod = new LinePod(
// @ts-ignore
chart,
props.series,
props.options,
props.markersConf,
props.segments
);
setPod(newPod);
newPod.render();
} else {
if(props.markersConf) {
pod.updateMarkers(props.markersConf);
}
if(props.segments) {
pod.updateSegments(props.segments);
}
// TODO: actually it's wrong logic with updates
// because it creates new pod anyway
pod.updateData(props.series, props.options);
}
}, [chart, props.id, props.options, props.markersConf, props.segments]);
// TODO: it's a hack to render the LinePod right after the div appears in DOM
setTimeout(() => {
if(hack === null) {
setHack(1);
}
}, 1);
return (
<div id={props.id} className={props.className} ref={chartRef}></div>
);
}
export default ChartwerkLinePod;

3
packages/line-pod/tsconfig.json → react/tsconfig.json

@ -17,6 +17,7 @@
"noImplicitUseStrict": false, "noImplicitUseStrict": false,
"noImplicitAny": false, "noImplicitAny": false,
"noUnusedLocals": false, "noUnusedLocals": false,
"baseUrl": "./src" "baseUrl": "./src",
"jsx": "react"
} }
} }

1322
react/yarn.lock

File diff suppressed because it is too large Load Diff

139
src/components/markers.ts

@ -0,0 +1,139 @@
import { MarkerElem, MarkersConf, MarkerSerie } from '../models/marker';
import { LineTimeSerie, LineOptions } from '../types';
import { Margin, PodState } from '@chartwerk/core';
import d3 from 'd3';
export class Markers {
private _layerContainer = null;
private _chartHeight = 0;
constructor(
private _chartContainer: d3.Selection<HTMLElement, unknown, null, undefined>,
private _markerConf: MarkersConf,
private _state: PodState<LineTimeSerie, LineOptions>,
private _margin: Margin,
) { }
clear() {
if(this._layerContainer !== null) {
this._layerContainer.remove();
}
this._chartContainer.selectAll('.marker-content').remove();
}
render(metricContainer: d3.Selection<SVGGElement, unknown, null, undefined>, chartHeight: number) {
this._chartHeight = chartHeight;
this._layerContainer = metricContainer
.append('g')
.attr('class', 'markers-layer');
for(const serie of this._markerConf.series) {
this.renderSerie(serie);
}
}
private _getLinePosition(marker: MarkerElem): number {
return this._state.xScale(marker.x);
}
private _renderCircle(marker: MarkerElem) {
const linePosition = this._getLinePosition(marker);
let circle = this._layerContainer.append('circle')
.attr('class', 'gap-circle')
.attr('stroke', marker.color)
.attr('stroke-width', '2px')
.attr('r', 4)
.attr('cx', linePosition)
.attr('cy', 5)
circle
.attr('pointer-events', 'all')
.style('cursor', 'pointer')
.on('mousemove', () => {
const onMouseMove = this._markerConf.events?.onMouseMove;
if(onMouseMove) {
onMouseMove(marker);
return
}
if(marker.alwaysDisplay) {
return;
}
this._chartContainer
.selectAll(`.marker-content-${marker.x}`)
.style('visibility', 'visible')
.style('z-index', 9999);
})
.on('mouseout', () => {
const onMouseOut = this._markerConf.events?.onMouseOut;
if(onMouseOut) {
onMouseOut()
return
}
if(marker.alwaysDisplay) {
return;
}
this._chartContainer
.selectAll(`.marker-content-${marker.x}`)
.style('visibility', 'hidden')
.style('z-index', 1);
});
}
private _renderLine(marker: MarkerElem) {
const linePosition = this._getLinePosition(marker);
this._layerContainer.append('line')
.attr('class', 'gap-line')
.attr('stroke', marker.color)
.attr('stroke-width', '1px')
.attr('stroke-opacity', '0.3')
.attr('stroke-dasharray', '4')
.attr('x1', linePosition)
.attr('x2', linePosition)
.attr('y1', 0)
// @ts-ignore // TODO: remove ignore but boxParams are protected
.attr('y2', this._state.boxParams.height)
.attr('pointer-events', 'none');
}
private _renderTooltip(marker: MarkerElem) {
if(marker.html === undefined) {
return;
}
const linePosition = this._getLinePosition(marker);
const div = this._chartContainer
.append('div')
.attr('class', `marker-content marker-content-${marker.x}`)
// @ts-ignore // TODO: remove ignore but boxParams are protected
.style('top', `${this._state.boxParams.height - this._chartHeight}px`)
.style('visibility', marker.alwaysDisplay ? 'visible' : 'hidden')
.style('position', 'absolute')
.style('border', '1px solid black')
.style('background-color', 'rgb(33, 37, 41)')
.style('color', 'rgb(255, 255, 255)')
.style('line-height', '1.55')
.style('font-size', '0.875rem')
.style('border-radius', '0.5rem')
.style('padding', 'calc(0.3125rem) 0.625rem')
.style('position', 'absolute')
.style('white-space', 'nowrap')
.style('pointer-events', 'none')
.style('z-index', 1)
.html(marker.html);
// align tooltip: center (we need it to be rendered first)
div.style('left', `${linePosition + this._margin.left - div.node().getBoundingClientRect().width / 2}px`)
}
protected renderSerie(serie: MarkerSerie) {
serie.data.forEach((marker: MarkerElem) => {
this._renderLine(marker);
this._renderCircle(marker);
this._renderTooltip(marker);
});
}
}

73
src/components/segments.ts

@ -0,0 +1,73 @@
import { SegmentSerie, SegmentElement } from "../models/segment";
import { PodState } from "@chartwerk/core";
import { LineTimeSerie, LineOptions } from "../types";
import * as d3 from "d3";
const OPACITY = 0.3;
const OPACITY_SELECT = 0.3;
export class Segments {
// TODO: more semantic name
private _d3Holder = null;
private _metricCon = null;
constructor(
private _series: SegmentSerie[],
private _state: PodState<LineTimeSerie, LineOptions>
) {
}
render(metricContainer: d3.Selection<SVGGElement, unknown, null, undefined>, chartContainer: d3.Selection<SVGGElement, unknown, null, undefined>) {
if(this._d3Holder !== null) {
this._d3Holder.remove();
}
this._d3Holder = metricContainer.append('g').attr('class', 'markers-layer');
for (const s of this._series) {
this.renderSerie(chartContainer, s);
}
}
protected renderSerie(chartContainer: d3.Selection<SVGGElement, unknown, null, undefined>, serie: SegmentSerie) {
// TODO: it's hack with core, need to find a better way
const overlay = chartContainer.select('.overlay');
serie.data.forEach((d) => {
// @ts-ignore
const startPositionX = this._state.xScale(d[0]) as number;
// @ts-ignore
const endPositionX = this._state.xScale(d[1]) as number;
const width = endPositionX - startPositionX // Math.max(endPositionX - startPositionX, MIMIMUM_SEGMENT_WIDTH);
const opacity = serie.opacity || OPACITY;
const opacitySelect = serie.opacitySelect || OPACITY_SELECT;
this._d3Holder.append('rect')
.attr('class', 'segment')
.attr('x', startPositionX)
.attr('y', 0)
.attr('width', width)
// @ts-ignore // TODO: remove ignore but boxParams are protected
.attr('height', this._state.boxParams.height)
.attr('opacity', opacity)
.style('fill', serie.color)
.on('mouseover', function() {
if(serie.select === true) {
d3.select(this).attr('opacity', opacitySelect);
if(serie.onSelect) {
serie.onSelect(d);
}
}
})
.on('mouseout', function(e) {
if(serie.select === true) {
d3.select(this).attr('opacity', opacity);
if(serie.onUnselect) {
serie.onUnselect(d);
}
}
})
.on('mousemove', function(e) {
var event = new MouseEvent('mousemove', d3.event);
overlay.node().dispatchEvent(event)
})
});
}
}

252
packages/line-pod/src/index.ts → src/index.ts

@ -1,7 +1,11 @@
import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, CrosshairOrientation, BrushOrientation, yAxisOrientation } from '@chartwerk/core'; import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, CrosshairOrientation, BrushOrientation, yAxisOrientation } from '@chartwerk/core';
import { LineTimeSerie, LineOptions } from './types'; import { LineTimeSerie, LineOptions, MouseObj, AreaType } from './types';
import { Markers } from './components/markers';
import { Segments } from './components/segments';
import { LineSeries } from './models/line_series'; import { LineSeries } from './models/line_series';
import { MarkersConf } from './models/marker';
import { SegmentSerie } from './models/segment';
import * as d3 from 'd3'; import * as d3 from 'd3';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -10,64 +14,91 @@ const METRIC_CIRCLE_RADIUS = 1.5;
const CROSSHAIR_CIRCLE_RADIUS = 3; const CROSSHAIR_CIRCLE_RADIUS = 3;
const CROSSHAIR_BACKGROUND_RAIDUS = 9; const CROSSHAIR_BACKGROUND_RAIDUS = 9;
const CROSSHAIR_BACKGROUND_OPACITY = 0.3; const CROSSHAIR_BACKGROUND_OPACITY = 0.3;
type Generator = d3.Line<[number, number]> | d3.Area<[number, number]>;
export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
lineGenerator = null; class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
areaGenerator = null; private _markersLayer: Markers = null;
lineGeneratorY1 = null; private _segmentsLayer: Segments = null;
areaGeneratorY1 = null;
constructor(
constructor(_el: HTMLElement, _series: LineTimeSerie[] = [], _options: LineOptions = {}) { _el: HTMLElement,
_series: LineTimeSerie[] = [],
_options: LineOptions = {},
private _markersConf?: MarkersConf,
private _segmentSeries: SegmentSerie[] = [],
) {
super(_el, _series, _options); super(_el, _series, _options);
this.series = new LineSeries(_series); this.series = new LineSeries(_series);
} }
renderMetrics(): void { override renderMetrics(): void {
this.clearAllMetrics(); this.clearAllMetrics();
this.updateCrosshair(); this.updateCrosshair();
this.initLineGenerator(); this.updateEvents();
this.initAreaGenerator();
if(!this.series.isSeriesAvailable) { if(!this.series.isSeriesAvailable) {
this.renderNoDataPointsMessage(); this.renderNoDataPointsMessage();
return; return;
} }
for(const serie of this.series.visibleSeries) { for(const serie of this.series.visibleSeries) {
this._renderMetric(serie); const generator = this.getRenderGenerator(serie.renderArea, serie.yOrientation);
this._renderMetric(serie, generator);
}
if(!_.isEmpty(this._markersConf)) {
this._markersLayer = new Markers(this.d3Node, this._markersConf, this.state, this.margin);
this._markersLayer.render(this.metricContainer, this.height);
}
if(!_.isEmpty(this._segmentSeries)) {
this._segmentsLayer = new Segments(this._segmentSeries, this.state);
this._segmentsLayer.render(this.metricContainer, this.chartContainer);
} }
} }
clearAllMetrics(): void { clearAllMetrics(): void {
// TODO: temporary hack before it will be implemented in core. // TODO: temporary hack before it will be implemented in core.
this.chartContainer.selectAll('.metric-el').remove(); this.chartContainer.selectAll('.metric-el').remove();
this._markersLayer?.clear();
} }
initLineGenerator(): void { protected updateEvents(): void {
this.lineGenerator = d3.line() // overlay - core component that is used to handle mouse events
.x(d => this.state.xScale(d[0])) if(!this.overlay) {
.y(d => this.state.yScale(d[1])); return;
this.lineGeneratorY1 = d3.line() }
.x(d => this.state.xScale(d[0])) if(this.options._options.events?.contextMenu === undefined) {
.y(d => this.state.y1Scale(d[1])); return;
}
this.overlay.on('contextmenu', this.onContextMenu.bind(this));
} }
initAreaGenerator(): void { getRenderGenerator(renderArea: AreaType, yOrientation: yAxisOrientation): Generator {
this.areaGenerator = d3.area() const yScale = yOrientation === yAxisOrientation.LEFT ? this.state.yScale : this.state.y1Scale;
const yValueRange = yOrientation === yAxisOrientation.LEFT ? this.state.yValueRange : this.state.y1ValueRange;
const yAxisOptions = yOrientation === yAxisOrientation.LEFT ? this.options.axis.y : this.options.axis.y1;
const topChartBorder = !yAxisOptions.invert ? yScale(yValueRange[1]) : yScale(yValueRange[0]);
const bottomChartBorder = !yAxisOptions.invert ? yScale(yValueRange[0]) : yScale(yValueRange[1]);
switch(renderArea) {
case AreaType.NONE:
// return line generator
return d3.line()
.x(d => this.state.xScale(d[0])) .x(d => this.state.xScale(d[0]))
.y1(d => this.state.yScale(d[1])) .y(d => yScale(d[1]));
.y0(d => this.height); case AreaType.ABOVE:
this.areaGeneratorY1 = d3.area() return d3.area()
.x(d => this.state.xScale(d[0])) .x(d => this.state.xScale(d[0]))
.y1(d => this.state.y1Scale(d[1])) .y0(topChartBorder)
.y0(d => this.height); .y1(d => yScale(d[1]));
} case AreaType.BELOW:
return d3.area()
getRenderGenerator(renderArea: boolean, yOrientation: yAxisOrientation): any { .x(d => this.state.xScale(d[0]))
if(renderArea) { .y0(d => yScale(d[1]))
return yOrientation === yAxisOrientation.LEFT ? this.areaGenerator : this.areaGeneratorY1; .y1(bottomChartBorder);
default:
throw new Error(`Unknown type of renderArea: ${renderArea}`);
} }
return yOrientation === yAxisOrientation.LEFT ? this.lineGenerator : this.areaGeneratorY1;
} }
_renderDots(serie: LineTimeSerie): void { _renderDots(serie: LineTimeSerie): void {
@ -80,12 +111,12 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
.attr('r', METRIC_CIRCLE_RADIUS) .attr('r', METRIC_CIRCLE_RADIUS)
.style('pointer-events', 'none') .style('pointer-events', 'none')
.attr('cx', d => this.state.xScale(d[0])) .attr('cx', d => this.state.xScale(d[0]))
.attr('cy', d => this.state.yScale(d[1])); .attr('cy', d => this.state.getYScaleByOrientation(serie.yOrientation)(d[1]));
} }
_renderLines(serie: LineTimeSerie): void { _renderLines(serie: LineTimeSerie, generator: Generator): void {
const fillColor = serie.renderArea ? serie.color : 'none'; const fillColor = serie.renderArea !== AreaType.NONE ? serie.color : 'none';
const fillOpacity = serie.renderArea ? 0.5 : 'none'; const fillOpacity = serie.renderArea !== AreaType.NONE ? 0.5 : 'none';
this.metricContainer this.metricContainer
.append('path') .append('path')
@ -98,12 +129,12 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
.attr('stroke-opacity', 0.7) .attr('stroke-opacity', 0.7)
.attr('pointer-events', 'none') .attr('pointer-events', 'none')
.style('stroke-dasharray', serie.dashArray) .style('stroke-dasharray', serie.dashArray)
.attr('d', this.getRenderGenerator(serie.renderArea, serie.yOrientation)); .attr('d', generator);
} }
_renderMetric(serie: LineTimeSerie): void { _renderMetric(serie: LineTimeSerie, generator: Generator): void {
if(serie.renderLines === true) { if(serie.renderLines === true) {
this._renderLines(serie); this._renderLines(serie, generator);
} }
if(serie.renderDots === true) { if(serie.renderDots === true) {
@ -147,24 +178,16 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
.style('pointer-events', 'none'); .style('pointer-events', 'none');
} }
public renderSharedCrosshair(values: { x?: number, y?: number }): void {
this.onMouseOver(); // TODO: refactor to use it once
const eventX = this.state.xScale(values.x);
const eventY = this.state.yScale(values.y);
this.moveCrosshairLine(eventX, eventY);
const datapoints = this.findAndHighlightDatapoints(values.x, values.y);
this.options.callbackSharedCrosshairMove({
datapoints: datapoints,
eventX, eventY
});
}
public hideSharedCrosshair(): void { public hideSharedCrosshair(): void {
this.crosshair.style('display', 'none'); this.crosshair.style('display', 'none');
} }
// TODO: refactor to make xPosition and yPosition optional
// and trough error if they are provided for wrong orientation
moveCrosshairLine(xPosition: number, yPosition: number): void { moveCrosshairLine(xPosition: number, yPosition: number): void {
this.crosshair.style('display', null);
switch(this.options.crosshair.orientation) { switch(this.options.crosshair.orientation) {
case CrosshairOrientation.VERTICAL: case CrosshairOrientation.VERTICAL:
this.crosshair.select('#crosshair-line-x') this.crosshair.select('#crosshair-line-x')
@ -269,18 +292,17 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
return _.max(intervals); return _.max(intervals);
} }
onMouseMove(): void { getMouseObj(): MouseObj {
const eventX = d3.mouse(this.chartContainer.node())[0]; const eventX = d3.mouse(this.chartContainer.node())[0];
const eventY = d3.mouse(this.chartContainer.node())[1]; const eventY = d3.mouse(this.chartContainer.node())[1];
const xValue = this.state.xScale.invert(eventX); // mouse x position in xScale const xValue = this.state.xScale.invert(eventX); // mouse x position in xScale
const yValue = this.state.yScale.invert(eventY); const yValue = this.state.yScale.invert(eventY);
this.moveCrosshairLine(eventX, eventY);
const datapoints = this.findAndHighlightDatapoints(xValue, yValue); const datapoints = this.findAndHighlightDatapoints(xValue, yValue);
// TDOO: is shift key pressed // TODO: is shift key pressed
// TODO: need to refactor this object // TODO: need to refactor this object
this.options.callbackMouseMove({ return {
x: d3.event.pageX, x: d3.event.pageX,
y: d3.event.pageY, y: d3.event.pageY,
xVal: xValue, xVal: xValue,
@ -288,9 +310,40 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
series: datapoints, series: datapoints,
chartX: eventX, chartX: eventX,
chartWidth: this.width chartWidth: this.width
};
}
override onMouseMove(): void {
const obj = this.getMouseObj();
const eventX = d3.mouse(this.chartContainer.node())[0];
const eventY = d3.mouse(this.chartContainer.node())[1];
this.moveCrosshairLine(eventX, eventY);
// TODO: is shift key pressed
// TODO: need to refactor this object
this.options.callbackMouseMove(obj);
}
public renderSharedCrosshair(values: { x?: number, y?: number }): void {
this.showCrosshair();
this.moveCrosshairLine(
values.x ? this.state.xScale(values.x) : 0,
values.y ? this.state.yScale(values.y) : 0
);
const datapoints = this.findAndHighlightDatapoints(values.x, values.y);
this.options.callbackSharedCrosshairMove({
datapoints: datapoints,
eventX: values.x ? this.state.xScale(values.x) : 0,
eventY: values.y ? this.state.yScale(values.y) : 0
}); });
} }
override onMouseClick(): void {
this.options.callbackMouseClick(this.getMouseObj());
}
findAndHighlightDatapoints(xValue: number, yValue: number): { value: [number, number], color: string, label: string }[] { findAndHighlightDatapoints(xValue: number, yValue: number): { value: [number, number], color: string, label: string }[] {
if(!this.series.isSeriesAvailable) { if(!this.series.isSeriesAvailable) {
return []; return [];
@ -320,16 +373,26 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
return points; return points;
} }
onMouseOver(): void { showCrosshair() {
this.onMouseMove();
this.crosshair.style('display', null); this.crosshair.style('display', null);
this.crosshair.selectAll('.crosshair-circle') this.crosshair.selectAll('.crosshair-circle')
.style('display', null); .style('display', null);
} }
onMouseOut(): void { hideCrosshair() {
this.options.callbackMouseOut();
this.crosshair.style('display', 'none'); this.crosshair.style('display', 'none');
this.crosshair.selectAll('.crosshair-circle')
.style('display', 'none');
}
override onMouseOver(): void {
this.showCrosshair();
this.onMouseMove();
}
override onMouseOut(): void {
this.hideCrosshair();
this.options.callbackMouseOut();
} }
isDoubleClickActive(): boolean { isDoubleClickActive(): boolean {
@ -341,6 +404,14 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
this.onMouseOver(); this.onMouseOver();
} }
updateMarkers(markersConf: MarkersConf): void {
this._markersConf = markersConf;
}
updateSegments(segments: SegmentSerie[]): void {
this._segmentSeries = segments;
}
// methods below rewrite s, (move more methods here) // methods below rewrite s, (move more methods here)
protected zoomOut(): void { protected zoomOut(): void {
if(d3.event.type === 'dblclick' && !this.isDoubleClickActive()) { if(d3.event.type === 'dblclick' && !this.isDoubleClickActive()) {
@ -382,13 +453,68 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
x: xAxisMiddleValue, x: xAxisMiddleValue,
y: yAxisMiddleValue y: yAxisMiddleValue
} }
this.options.callbackZoomOut(centers);
// TODO: refactor core not to take _options explicitly
if(
this.options._options.events !== undefined &&
this.options._options.events.zoomOut !== undefined
) {
this.options._options.events.zoomOut(
centers,
[this.state.xValueRange, this.state.yValueRange]
);
}
}
protected onContextMenu(): void {
d3.event.preventDefault(); // do not open browser's context menu.
const eventX = d3.mouse(this.chartContainer.node())[0];
const eventY = d3.mouse(this.chartContainer.node())[1];
this.options._options.events.contextMenu({
x: this.state.xScale.invert(eventX),
y: this.state.yScale.invert(eventY),
});
}
// override parent updateData method to provide markers and segments
protected updateLineData(
series?: LineTimeSerie[],
options?: LineOptions,
markersConf?: MarkersConf,
segments?: SegmentSerie[],
shouldRerender = true
): void {
this.updateMarkers(markersConf);
this.updateSegments(segments);
this.updateData(series, options, shouldRerender);
} }
} }
// TODO: it should be moved to VUE folder
// it is used with Vue.component, e.g.: Vue.component('chartwerk-line-pod', VueChartwerkLinePod) // it is used with Vue.component, e.g.: Vue.component('chartwerk-line-pod', VueChartwerkLinePod)
export const VueChartwerkLinePod = { export const VueChartwerkLinePod = {
// alternative to `template: '<div class="chartwerk-line-pod" :id="id" />'` // alternative to `template: '<div class="chartwerk-line-pod" :id="id" />'`
props: {
markersConf: {
type: Object,
required: false,
default: function() { return {}; }
},
segments: {
type: Array,
required: false,
default: function() { return []; }
},
},
watch: {
markersConf() {
this.renderChart();
},
segments() {
this.renderChart();
},
},
render(createElement) { render(createElement) {
return createElement( return createElement(
'div', 'div',
@ -402,10 +528,10 @@ export const VueChartwerkLinePod = {
methods: { methods: {
render() { render() {
if(this.pod === undefined) { if(this.pod === undefined) {
this.pod = new LinePod(document.getElementById(this.id), this.series, this.options); this.pod = new LinePod(document.getElementById(this.id), this.series, this.options, this.markersConf, this.segments);
this.pod.render(); this.pod.render();
} else { } else {
this.pod.updateData(this.series, this.options); this.pod.updateLineData(this.series, this.options, this.markersConf, this.segments);
} }
}, },
renderSharedCrosshair(values) { renderSharedCrosshair(values) {
@ -417,4 +543,4 @@ export const VueChartwerkLinePod = {
} }
}; };
export { LineTimeSerie, LineOptions, TimeFormat }; export { LineTimeSerie, LineOptions, TimeFormat, LinePod, AreaType, MarkersConf, SegmentSerie };

5
packages/line-pod/src/models/line_series.ts → src/models/line_series.ts

@ -1,5 +1,5 @@
import { CoreSeries, yAxisOrientation } from '@chartwerk/core'; import { CoreSeries, yAxisOrientation } from '@chartwerk/core';
import { LineTimeSerie } from '../types'; import { LineTimeSerie, AreaType } from '../types';
import * as _ from 'lodash'; import * as _ from 'lodash';
@ -10,12 +10,11 @@ const LINE_SERIE_DEFAULTS = {
renderLines: true, renderLines: true,
dashArray: '0', dashArray: '0',
class: '', class: '',
renderArea: false, renderArea: AreaType.NONE,
yOrientation: yAxisOrientation.LEFT, yOrientation: yAxisOrientation.LEFT,
}; };
export class LineSeries extends CoreSeries<LineTimeSerie> { export class LineSeries extends CoreSeries<LineTimeSerie> {
constructor(series: LineTimeSerie[]) { constructor(series: LineTimeSerie[]) {
super(series, _.clone(LINE_SERIE_DEFAULTS)); super(series, _.clone(LINE_SERIE_DEFAULTS));
} }

19
src/models/marker.ts

@ -0,0 +1,19 @@
export type MarkerElem = {
x: number;
color: string;
html?: string;
alwaysDisplay?: boolean;
payload?: any;
}
export type MarkerSerie = {
data: MarkerElem[];
}
export type MarkersConf = {
series: MarkerSerie[],
events?: {
onMouseMove?: (el: MarkerElem) => void;
onMouseOut?: () => void;
}
}

11
src/models/segment.ts

@ -0,0 +1,11 @@
export type SegmentElement = [number, number, any?];
export type SegmentSerie = {
color: string;
data: SegmentElement[] // [from, to, payload?] payload is any data for tooltip,
select?: boolean,
opacity?: number,
opacitySelect?: number,
onSelect?: (SegmentElement) => void
onUnselect?: (SegmentElement) => void
}

41
src/types.ts

@ -0,0 +1,41 @@
import { Serie, Options } from '@chartwerk/core';
import { AxisRange } from '@chartwerk/core/dist/types';
type LineTimeSerieParams = {
maxLength: number;
renderDots: boolean;
renderLines: boolean; // TODO: refactor same as scatter-pod
dashArray: string; // dasharray attr, only for lines
class: string; // option to add custom class to each serie element
renderArea: AreaType; // default is none
}
export enum AreaType {
NONE = 'None',
ABOVE = 'Above',
BELOW = 'Below',
}
export type LineTimeSerie = Serie & Partial<LineTimeSerieParams>;
export type LineOptions = Options & {
events? : {
zoomOut?: (centers: {
x: number;
y: number;
}, range: AxisRange[]) => void;
contextMenu?: (position: {
x: number;
y: number;
}) => void;
}
}
export type MouseObj = {
x: number,
y: number,
xVal: number,
yVal: number,
series: { value: [number, number], color: string, label: string }[],
chartX: number,
chartWidth: number
}

23
tsconfig.json

@ -0,0 +1,23 @@
{
"compilerOptions": {
"target": "es5",
"rootDir": "./src",
"module": "esnext",
"moduleResolution": "node",
"declaration": true,
"declarationDir": "dist",
"allowSyntheticDefaultImports": true,
"inlineSourceMap": false,
"sourceMap": true,
"noEmitOnError": false,
"emitDecoratorMetadata": false,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noImplicitThis": false,
"noImplicitUseStrict": false,
"noImplicitAny": false,
"noUnusedLocals": false,
"baseUrl": "./src",
},
"include": ["src/**/*"]
}

3169
yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save