Compare commits

...

148 Commits

Author SHA1 Message Date
Coin de Gamma 348a289f1a Merge pull request '0.6.18' (#61) from 0.6.18 into main 2 months ago
glitch4347 17a2fdf94e 0.6.18 2 months ago
Coin de Gamma dc89f6cbb9 Merge pull request 'basic react component implementatino is sep project' (#59) from better-react-component-#58 into main 2 months ago
glitch4347 547c15c0b6 optimise excludes in libs in webpack 2 months ago
glitch4347 32d122ba4d back dev build 2 months ago
glitch4347 9c165f4e1f basic react component implementatino is sep project 2 months ago
Coin de Gamma ec2bd27acd Merge pull request '0.6.17' (#54) from 0.6.17 into main 2 months ago
glitch4347 815144d209 update core 0.6.23 2 months ago
glitch4347 1a80ca2cab 0.6.17 2 months ago
Coin de Gamma ee08db2f54 Merge pull request 'use events instead of eventsCallbacks' (#53) from core-0.6.23 into main 2 months ago
glitch4347 884fed6582 use events instead of eventsCallbacks 2 months ago
glitch4347 8aca72ce04 zoom out with ranges logic 2 months ago
Coin de Gamma c62a1c386f Merge pull request 'rename demo->basic, live and vertical && update readme' (#47) from rename-examples into main 2 months ago
glitch4347 de6237891c rename demo->basic, live and vertical && update readme 2 months ago
rozetko 7bcd0d298a 0.6.16 3 months ago
rozetko ee2f02be96 upd core 3 months ago
rozetko 456b888fe1 0.6.15 4 months ago
rozetko a34c0574e4 hotfix 4 months ago
rozetko 73dbf0ea67 0.6.14 4 months ago
rozetko ff5973b106 Merge pull request 'updateData: update segments and markers' (#44) from update-segments-and-markers-on-update-data into main 4 months ago
rozetko 878e86d849 updateData: update segments and markers 4 months ago
vargburz 1e8f8d0cb3 0.6.13 4 months ago
rozetko 39412e5f65 Merge pull request 'roken shared crosshair #42' (#43) from broken-shared-crosshair-#42 into main 4 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 4 months ago
glitch4347 0dac1c390e fix 4 months ago
glitch4347 07f454fe51 0.6.19 core usdage 4 months ago
vargburz 88d6b2b24a Merge pull request 'click-event' (#39) from click-event into main 4 months ago
glitch4347 837004c2ad onclick 4 months ago
glitch4347 e46c7bdfb6 some commit 4 months ago
rozetko ee7a57685f 0.6.12 5 months ago
rozetko 963011b274 Merge pull request 'pod fix' (#37) from strange-updates into main 5 months ago
glitch4347 283f0995e8 pod fix 5 months ago
rozetko 19689a68cd Merge pull request 'mouse-over-and-react' (#36) from mouse-over-and-react into main 5 months ago
glitch4347 79b759569f simpler example 5 months ago
glitch4347 d66da50c1a rm line 5 months ago
glitch4347 d9d1b5e501 example with mouse over and refactor react component events 5 months ago
Coin de Gamma b8cd14e4b8 Merge pull request 'marker-callback-#25' (#35) from marker-callback-#25 into main 5 months ago
glitch4347 e470d58611 fx Marker callback type 5 months ago
glitch4347 26d5956c17 markers conf in react 5 months ago
glitch4347 43241bbcb7 markers conf + events 5 months ago
glitch4347 82fc9a10f1 markers conf + new example 5 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 5 months ago
glitch4347 98e4e42fc4 callbacks 5 months ago
glitch4347 6fbf833150 event callback back 5 months ago
glitch4347 a0e27d5efd rm My_CONST 5 months ago
glitch4347 727f8cac86 codefix 5 months ago
glitch4347 869488d405 codestyle 5 months ago
glitch4347 d97b0e9b0c markers++ 5 months ago
glitch4347 11ab01a7d2 markers++ 5 months ago
glitch4347 326c666435 component refactorinmg begin 5 months ago
rozetko 33cf970d5f Merge pull request 'basic segments impl' (#27) from segments-feature-#19 into main 5 months ago
glitch4347 03a562c0d2 codestytle fix 5 months ago
glitch4347 38b6eb2326 basic segments impl 5 months ago
rozetko af9aaf6d82 Merge pull request 'markers-feature-#18' (#23) from markers-feature-#18 into main 5 months ago
glitch4347 bf679019fc payload comment 5 months ago
glitch4347 86099c57b3 pointe events 5 months ago
glitch4347 9a6c5cd7f4 markers++ 5 months ago
glitch4347 aadcd013bf markers++ 5 months ago
glitch4347 a5c737fa81 marker data continue 5 months ago
rozetko c451df6e3b 0.6.11 5 months ago
rozetko 3ff6f7b8e8 crosshair hotfix 5 months ago
glitch4347 1d1a34f9bf markers begin 5 months ago
glitch4347 8346bceedf markers examples begin 5 months ago
vargburz 1ca84c53f8 Merge pull request '0.6.10 version' (#21) from version-0.6.10 into main 5 months ago
vargburz 85793a7965 0.6.10 version 5 months ago
vargburz 7e4a895e93 Merge pull request 'add copy-webpack-=plugin' (#20) from update-copy-webpack into main 5 months ago
glitch4347 f1c3ef6f1d add copy-webpack-=plugin 5 months ago
rozetko 6a0ea4303e Merge pull request 'add react component and build for that' (#16) from react-component-attempt-2 into main 5 months ago
glitch4347 08b128ea91 codestyle fixes 5 months ago
glitch4347 f1582fc610 rm comment 5 months ago
glitch4347 e98997995f add react component and build for that 5 months ago
rozetko e809046098 Merge pull request 'Developement how to and d3 resolution' (#14) from dev-how-to-#4 into main 5 months ago
glitch4347 833780105a fx type in conf 6 months ago
glitch4347 2e156ad46b add node_resolutinos for linking case & update readme 6 months ago
vargburz f100af0ee8 Merge pull request 'hotfix: call mouseOver and mouseMove to render crosshair on init and zoomin' (#10) from hidden-crosshair-on-first-render-and-zoomin into main 6 months ago
vargburz 927855ad45 Merge pull request 'set git repo in package.json' (#12) from set-git-package into main 6 months ago
glitch4347 a6841a83b0 set git repo in package.json 6 months ago
glitch4347 5c39f741ff hotfix: call mouseOver and mouseMove to render crosshair on init and zoomin 6 months ago
rozetko a9f5d074ce Merge pull request 'Update dependencies' (#3) from upd-deps into main 11 months ago
rozetko e3d261aa30 0.6.9 11 months ago
rozetko 4dc9c5290d upd dependencies 11 months ago
vargburz d3597763c5 0.6.8 1 year ago
Alexander Velikiy 517c014629 Merge branch 'hightlight-datapoint-for-right-y-axis' into 'main' 1 year ago
vargburz d763d8591b use right y series on mouse move 1 year ago
vargburz 7d9f38fa3c 0.6.7 1 year ago
Alexander Velikiy 60904493dd Merge branch 'make-y-oriented-series-work' into 'main' 1 year ago
vargburz 903817cb9c apply yOriented series for line pod 1 year ago
vargburz 8825f8b0ba Merge pull request '#DOC [Readme] add browser including' (#2) from doc/add_browser_including into main 2 years ago
Eugene Petukhov db30589898 #DOC [Readme] add browser including v2 2 years ago
Eugene Petukhov f701b29bab #DOC [Readme] add browser including 2 years ago
vargburz b7c6bb7fd8 Merge pull request 'sync-gitlab 0.6.6' (#1) from sync-gitlab into main 2 years ago
rozetko fa632d35a9 Merge branch 'do-not-highlight-nil-metric' into 'main' 2 years ago
rozetko a448f62fc9 minor fix 2 years ago
rozetko cf5722484a 0.6.6 2 years ago
rozetko 49c5c3d462 fix non-existing metrics being highlighted on hover 2 years ago
vargburz 0a04633b43 return latest core vesrion 2 years ago
vargburz dd6485bef5 static core version 2 years ago
vargburz b0b2b48f4d 0.6.3 2 years ago
vargburz b082e71822 fix parent class 2 years ago
vargburz c93bcddee4 0.6.2 2 years ago
vargburz 7cc885e2c7 0.6.1 2 years ago
vargburz 550ca9a807 test 2 years ago
vargburz 15587d9bfa 0.6.0 2 years ago
Alexander Velikiy 9bf20c4169 Merge branch 'update-line-with-models' into 'main' 2 years ago
vargburz b65b04c574 use core 060 2 years ago
vargburz 70966b323f fix live demo 2 years ago
vargburz ca3dc0b8f1 remame 2 years ago
vargburz f9d505fd92 remove strange options 2 years ago
vargburz 463bce47c6 fix defaults 2 years ago
vargburz f593206a98 fix type 2 years ago
vargburz e7248806b0 models 2 years ago
rozetko 88074bc683 yarn migrate to 2 2 years ago
rozetko b79b590efb 0.5.1 2 years ago
rozetko cafa719a66 upd chartwerk core to latest 2 years ago
rozetko 998a13c1ba Merge branch '0.5.0-beta2' into 'main' 2 years ago
rozetko 38d0c11bfc 0.5.0 2 years ago
rozetko 1eec6ba665 upd core 2 years ago
rozetko a5e6654742 0.5.0-beta3 2 years ago
rozetko f2369e2745 upd core 2 years ago
rozetko 4586ea94f9 0.5.0-beta2 2 years ago
rozetko 012434093c external core dep 2 years ago
rozetko 7e4dc84f45 rm dist and package lock 2 years ago
vargburz 22da5ce798 0.4.15 2 years ago
Alexander Velikiy 521a4adc25 Merge branch 'area-opation' into 'main' 2 years ago
vargburz 13dc3f8abe use new core fix fixed resize 2 years ago
vargburz c9931aba33 are option 2 years ago
rozetko 323d4c677e 0.4.14 2 years ago
rozetko 45965dd825 upd core 2 years ago
rozetko a47cb94980 0.4.13 2 years ago
rozetko 0817073ef8 upd core: 0.3.9 2 years ago
vargburz 788374111b 0.4.12 2 years ago
Alexander Velikiy a488abacbd Merge branch 'rerendering-core' into 'main' 2 years ago
vargburz be30b9e9f8 new core 2 years ago
rozetko 85436b735a 0.4.11 2 years ago
rozetko 45cf408bb8 upd core 2 years ago
rozetko 76e9ff78cf 0.4.10 2 years ago
rozetko 4c970950e2 Merge branch 'double-click-fix' into 'main' 2 years ago
dv4mp1r3 6dce9d17eb built new dist 2 years ago
dv4mp1r3 ce7b3df610 added new version of @chartwerk/core 2 years ago
dv4mp1r3 847a3bce56 updated package-lock.json 2 years ago
dv4mp1r3 bbfea95deb code style fix 2 years ago
dv4mp1r3 9db0d99511 added d3 event type check 2 years ago
dv4mp1r3 e72cd8e319 wip 2 years ago
Alexander Velikiy b23918fcf9 Merge branch 'scroll-gap-fix' into 'main' 2 years ago
vargburz d6be304cb0 0.4.9 vers 2 years ago
vargburz 55ee802dec use 0.3.5 core 2 years ago
vargburz 923fd31263 upd dist 2 years ago
vargburz 4b9174414f use new core 2 years ago
  1. 9
      .gitignore
  2. 786
      .yarn/releases/yarn-3.2.1.cjs
  3. 3
      .yarnrc.yml
  4. 41
      README.md
  5. 14
      build/webpack.base.conf.js
  6. 2
      build/webpack.dev.conf.js
  7. 3
      build/webpack.prod.conf.js
  8. 100
      dist/index.d.ts
  9. 9
      dist/index.js
  10. 18
      dist/types.d.ts
  11. 17
      examples/basic.html
  12. 18
      examples/live.html
  13. 42
      examples/markers.html
  14. 47
      examples/markers_select.html
  15. 33
      examples/mouse_click.html
  16. 29
      examples/mouse_move.html
  17. 38
      examples/segments.html
  18. 4
      examples/vertical.html
  19. 37
      examples/zoom_out.html
  20. 4616
      package-lock.json
  21. 39
      package.json
  22. 26
      react/build/webpack.base.conf.js
  23. 8
      react/build/webpack.dev.conf.js
  24. 6
      react/build/webpack.prod.conf.js
  25. 23
      react/package.json
  26. 74
      react/src/index.tsx
  27. 23
      react/tsconfig.json
  28. 1322
      react/yarn.lock
  29. 59
      src/components/markers.ts
  30. 45
      src/components/segments.ts
  31. 430
      src/index.ts
  32. 21
      src/models/line_series.ts
  33. 15
      src/models/marker.ts
  34. 4
      src/models/segment.ts
  35. 31
      src/types.ts
  36. 5
      tsconfig.json
  37. 5357
      yarn.lock

9
.gitignore vendored

@ -1 +1,10 @@
node_modules
dist
# yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions

786
.yarn/releases/yarn-3.2.1.cjs vendored

File diff suppressed because one or more lines are too long

3
.yarnrc.yml

@ -0,0 +1,3 @@
nodeLinker: node-modules
yarnPath: .yarn/releases/yarn-3.2.1.cjs

41
README.md

@ -1,2 +1,43 @@
# Chartwerk Line Pod
## Including
### Browser
#### Script tag
```html
<script src="https://unpkg.com/@chartwerk/line-pod@latest/dist/index.dev.js" type="text/javascript"></script>
```
#### Example
```html
<div id="line-chart" class="chart-block" style="width: 100%; height: 400px;"></div>
<script src="https://unpkg.com/@chartwerk/line-pod@latest/dist/index.dev.js" type="text/javascript"></script>
<script>
new LinePod(document.getElementById('yourDivId', yourData, yourOptions)).render();
</script>
```
#### Other examples
* [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)
* [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)
* [Markers](https://code.corpglory.net/chartwerk/line-pod/src/branch/main/examples/markers.html)
### Development
If you want to link `core` then clone it to same directory and then run
```
yarn link ../core
```
but don't add `resolutions` logic to `package.json`
then run
```
yarn install
yarn dev
```

14
build/webpack.base.conf.js

@ -1,4 +1,5 @@
const path = require('path');
const CopyPlugin = require("copy-webpack-plugin");
function resolve(dir) {
@ -8,7 +9,14 @@ function resolve(dir) {
module.exports = {
context: resolve('src'),
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: {
rules: [
{
@ -25,6 +33,10 @@ module.exports = {
},
resolve: {
extensions: ['.ts', '.js'],
// this is necessary for resolution of external libs like d3 in dev mode
// when core is linked: webpack will take d3 from this node_modules but not from
// internal so you get one version of d3
modules: [path.resolve(__dirname, '../node_modules'), 'node_modules']
},
output: {
filename: 'index.js',

2
build/webpack.dev.conf.js

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

3
build/webpack.prod.conf.js

@ -2,5 +2,8 @@ const baseWebpackConfig = require('./webpack.base.conf');
var conf = baseWebpackConfig;
conf.mode = 'production';
conf.externals = [
'@chartwerk/core', 'd3', 'lodash'
];
module.exports = baseWebpackConfig;

100
dist/index.d.ts vendored

@ -1,100 +0,0 @@
import { ChartwerkPod, TickOrientation, TimeFormat } from '@chartwerk/core';
import { LineTimeSerie, LineOptions, Mode } from './types';
export declare class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
lineGenerator: any;
constructor(_el: HTMLElement, _series?: LineTimeSerie[], _options?: LineOptions);
renderMetrics(): void;
clearAllMetrics(): void;
initLineGenerator(): void;
appendData(data: [number, number][], shouldRerender?: boolean): void;
_renderDots(datapoints: number[][], serieIdx: number): void;
_renderLines(datapoints: number[][], serieIdx: number): void;
_renderMetric(datapoints: number[][], metricOptions: {
color: string;
confidence: number;
target: string;
mode: Mode;
serieIdx: number;
renderDots: boolean;
renderLines: boolean;
}): void;
updateCrosshair(): void;
appendCrosshairCircles(): void;
appendCrosshairCircle(serieIdx: number): void;
renderSharedCrosshair(values: {
x?: number;
y?: number;
}): void;
hideSharedCrosshair(): void;
moveCrosshairLine(xPosition: number, yPosition: number): void;
moveCrosshairCircle(xPosition: number, yPosition: number, serieIdx: number): void;
hideCrosshairCircle(serieIdx: number): void;
getClosestDatapoint(serie: LineTimeSerie, xValue: number, yValue: number): [number, number];
getClosestIndex(datapoints: [number, number][], xValue: number, yValue: number): number;
getValueInterval(columnIdx: number): number | undefined;
onMouseMove(): void;
findAndHighlightDatapoints(xValue: number, yValue: number): {
value: [number, number];
color: string;
label: string;
}[];
isOutOfRange(closestDatapoint: [number, number], xValue: number, yValue: number, useOutOfRange?: boolean): boolean;
onMouseOver(): void;
onMouseOut(): void;
protected zoomOut(): void;
}
export declare const VueChartwerkLinePod: {
render(createElement: any): any;
mixins: {
props: {
id: {
type: StringConstructor;
required: boolean;
};
series: {
type: ArrayConstructor;
required: boolean;
default: () => any[];
};
options: {
type: ObjectConstructor;
required: boolean;
default: () => {};
};
};
watch: {
id(): void;
series(): void;
options(): void;
};
mounted(): void;
destroyed(): void;
methods: {
render(): void;
renderSharedCrosshair(values: {
x?: number;
y?: number;
}): void;
hideSharedCrosshair(): void;
onPanningRescale(event: any): void;
renderChart(): void;
appendEvents(): void;
zoomIn(range: any): void;
zoomOut(centers: any): void;
mouseMove(evt: any): void;
mouseOut(): void;
onLegendClick(idx: any): void;
panningEnd(range: any): void;
panning(range: any): void;
contextMenu(evt: any): void;
sharedCrosshairMove(event: any): void;
renderEnd(): void;
};
}[];
methods: {
render(): void;
renderSharedCrosshair(values: any): void;
hideSharedCrosshair(): void;
};
};
export { LineTimeSerie, LineOptions, Mode, TickOrientation, TimeFormat };

9
dist/index.js vendored

File diff suppressed because one or more lines are too long

18
dist/types.d.ts vendored

@ -1,18 +0,0 @@
import { TimeSerie, Options } from '@chartwerk/core';
declare type LineTimeSerieParams = {
confidence: number;
mode: Mode;
maxLength: number;
renderDots: boolean;
renderLines: boolean;
useOutOfRange: boolean;
dashArray: string;
class: string;
};
export declare enum Mode {
STANDARD = "Standard",
CHARGE = "Charge"
}
export declare type LineTimeSerie = TimeSerie & Partial<LineTimeSerieParams>;
export declare type LineOptions = Options;
export {};

17
examples/demo.html → examples/basic.html

@ -4,21 +4,26 @@
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.js" type="text/javascript"></script>
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 500px; height: 500px;"></div>
<div id="chart" style="width: 50%; height: 500px;"></div>
<script type="text/javascript">
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() * 100)]);
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 zoomIn = (ranges) => { const xRange = ranges[0]; options.axis.x.range = xRange; pod.updateData(undefined, options); }
const panningEnd = (ranges) => { const xRange = ranges[0]; options.axis.x.range = xRange; pod.updateData(undefined, options); }
let options = {
renderLegend: false, usePanning: false, axis: { y: { invert: false, range: [0, 350] }, x: { format: 'time' } },
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' } }
@ -28,8 +33,8 @@
var pod = new LinePod(
document.getElementById('chart'),
[
{ target: 'test1', datapoints: data1, color: 'green', dashArray: '5,3', class: 'first' },
{ target: 'test2', datapoints: data2, color: 'blue' },
{ 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' },
],
options

18
examples/demo_live.html → examples/live.html

@ -5,7 +5,7 @@
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.js" type="text/javascript"></script>
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
@ -15,9 +15,9 @@
const startTime = 1590590148;
const arrayLength = 100;
this.isZoomed = false; // TODO: temporary hack to have zoomin|zoomout with `appendData`. It will be moved to Pod.
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() * 100)]);
const data3 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 20) + 90]);
var data1 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 40)]);
var data2 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 100)]);
var data3 = Array.from({ length: arrayLength }, (el, idx) => [startTime + idx * 10000, Math.floor(Math.random() * 20) + 90]);
const zoomIn = (ranges) => { const xRange = ranges[0]; options.axis.x.range = xRange; pod.updateData(undefined, options); this.isZoomed = true }
const zoomOut = (ranges) => { options.axis.x.range = undefined; pod.updateData(undefined, options); this.isZoomed = false }
let options = { renderLegend: false, axis: { y: { invert: false, range: [0, 350] }, x: { format: 'time' } }, eventsCallbacks: { zoomIn: zoomIn, zoomOut } };
@ -37,9 +37,17 @@
const d1 = [startTime + rerenderIdx * 10000, Math.floor(Math.random() * 20) + 90];
const d2 = [startTime + rerenderIdx * 10000, Math.floor(Math.random() * 100)];
const d3 = [startTime + rerenderIdx * 10000, Math.floor(Math.random() * 20) + 90];
console.log('d1', data1)
const shouldRerender = !this.isZoomed;
console.time('rerender');
pod.appendData([d1, d2, d3], shouldRerender);
data1.push(d1);
data2.push(d2);
data3.push(d3);
pod.updateData([
{ target: 'test1', datapoints: data1, color: 'green', maxLength: arrayLength + 30, renderDots: false },
{ target: 'test2', datapoints: data2, color: 'blue', maxLength: arrayLength + 30, renderDots: false },
{ target: 'test3', datapoints: data3, color: 'orange', maxLength: arrayLength + 30, renderDots: false },
]);
console.timeEnd('rerender');
if(rerenderIdx > arrayLength + 100) {
clearInterval(test);

42
examples/markers.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: 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]);
// TODO: make this one-dimensinal data when implemented
const markersData1 = [3, 6, 9].map(el => [startTime + el * 1000]);
const markersData2 = [4, 11].map(el => [startTime + el * 1000]);
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, color: 'red' },
{ data: markersData2, color: 'blue' },
]
}
);
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]);
// TODO: make this one-dimensinal data when implemented
const markersData = [3, 6, 9].map(el => [
startTime + el * 1000,
{ el }
]);
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, color: 'red' },
],
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>

38
examples/segments.html

@ -0,0 +1,38 @@
<!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) * 1000]);
let options = {
renderLegend: false,
axis: {
y: { range: [0, 10] },
x: { format: 'time' }
},
}
var pod = new LinePod(
document.getElementById('chart'),
[
{ datapoints: timeSerieData, color: 'black' },
],
options,
[],
[
{ data: segmentsData, color:'#FFE545' }
]
);
pod.render();
</script>
</body>
</html>

4
examples/demo_vertical.html → examples/vertical.html

@ -4,7 +4,7 @@
<meta content="text/html;charset=utf-8" http-equiv="Content-Type">
<meta content="utf-8" http-equiv="encoding">
<script src="../dist/index.js" type="text/javascript"></script>
<script src="../dist/index.dev.js" type="text/javascript"></script>
</head>
<body>
<div id="chart" style="width: 50%; height: 500px;"></div>
@ -61,7 +61,7 @@
}
function onPanning() {
console.log('panning', pod);
// console.log('panning', pod);
}
function createDatapoints(arrayLength, startTime, randomValue, randomOffset = 0) {

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>

4616
package-lock.json generated

File diff suppressed because it is too large Load Diff

39
package.json

@ -1,32 +1,37 @@
{
"name": "@chartwerk/line-pod",
"version": "0.4.8",
"version": "0.6.18",
"description": "Chartwerk line chart",
"main": "dist/index.js",
"files": [
"/dist"
],
"scripts": {
"build": "webpack --config build/webpack.prod.conf.js",
"dev": "webpack --config build/webpack.dev.conf.js",
"test": "echo \"Error: no test specified\" && exit 1"
"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": {
"type": "git",
"url": "https://gitlab.com/chartwerk/line-pod.git"
"url": "http://code.corpglory.net/chartwerk/line-pod.git"
},
"author": "CorpGlory",
"license": "ISC",
"dependencies": {
"@chartwerk/core": "^0.3.4"
"@chartwerk/core": "^0.6.23"
},
"devDependencies": {
"@types/d3": "5.16.4",
"@types/lodash": "^4.14.149",
"css-loader": "^3.4.2",
"d3": "5.16.0",
"lodash": "^4.17.15",
"style-loader": "^1.1.3",
"ts-loader": "^6.2.1",
"typescript": "^3.8.3",
"webpack": "^4.42.0",
"webpack-cli": "^3.3.11"
}
"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/*"
]
}

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;

23
react/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",
"jsx": "react"
}
}

1322
react/yarn.lock

File diff suppressed because it is too large Load Diff

59
src/components/markers.ts

@ -0,0 +1,59 @@
import { MarkersConf, MarkerSerie } from "../models/marker";
import { PodState } from "@chartwerk/core";
import { LineTimeSerie, LineOptions } from "../types";
import d3 from "d3";
export class Markers {
// TODO: more semantic name
private _d3Holder = null;
constructor(private _markerConf: MarkersConf, private _state: PodState<LineTimeSerie, LineOptions>) {
}
render(metricContainer: d3.Selection<SVGGElement, unknown, null, undefined>) {
if(this._d3Holder !== null) {
this._d3Holder.remove();
}
this._d3Holder = metricContainer.append('g').attr('class', 'markers-layer');
for (const ms of this._markerConf.series) {
this.renderSerie(ms);
}
}
protected renderSerie(serie: MarkerSerie) {
serie.data.forEach((d) => {
let linePosition = this._state.xScale(d[0]) as number;
this._d3Holder.append('line')
.attr('class', 'gap-line')
.attr('stroke', serie.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');
let circle = this._d3Holder.append('circle')
.attr('class', 'gap-circle')
.attr('stroke', serie.color)
.attr('stroke-width', '2px')
.attr('r', 4)
.attr('cx', linePosition)
.attr('cy', 5)
if(this._markerConf !== undefined) {
circle
.attr('pointer-events', 'all')
.style('cursor', 'pointer')
.on('mousemove', () => this._markerConf.events.onMouseMove(d))
.on('mouseout', () => this._markerConf.events.onMouseOut())
}
});
}
}

45
src/components/segments.ts

@ -0,0 +1,45 @@
import { SegmentSerie } from "../models/segment";
import { PodState } from "@chartwerk/core";
import { LineTimeSerie, LineOptions } from "../types";
import d3 from "d3";
export class Segments {
// TODO: more semantic name
private _d3Holder = null;
constructor(private _series: SegmentSerie[], private _state: PodState<LineTimeSerie, LineOptions>) {
}
render(metricContainer: 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(s);
}
}
protected renderSerie(serie: SegmentSerie) {
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);
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', 0.3)
.style('fill', serie.color)
.style('pointer-events', 'none');
});
}
}

430
src/index.ts

@ -1,5 +1,11 @@
import { ChartwerkPod, VueChartwerkPodMixin, TickOrientation, TimeFormat, CrosshairOrientation, BrushOrientation } from '@chartwerk/core';
import { LineTimeSerie, LineOptions, Mode } from './types';
import { ChartwerkPod, VueChartwerkPodMixin, TimeFormat, CrosshairOrientation, BrushOrientation, yAxisOrientation } from '@chartwerk/core';
import { LineTimeSerie, LineOptions, MouseObj } from './types';
import { Markers } from './components/markers';
import { Segments } from './components/segments';
import { LineSeries } from './models/line_series';
import { MarkersConf } from './models/marker';
import { SegmentSerie } from './models/segment';
import * as d3 from 'd3';
import * as _ from 'lodash';
@ -9,49 +15,48 @@ const CROSSHAIR_CIRCLE_RADIUS = 3;
const CROSSHAIR_BACKGROUND_RAIDUS = 9;
const CROSSHAIR_BACKGROUND_OPACITY = 0.3;
export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
lineGenerator = null;
constructor(_el: HTMLElement, _series: LineTimeSerie[] = [], _options: LineOptions = {}) {
super(d3, _el, _series, _options);
class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
lineGenerator = null;
areaGenerator = null;
lineGeneratorY1 = null;
areaGeneratorY1 = null;
private _markersLayer: Markers = null;
private _segmentsLayer: Segments = null;
constructor(
_el: HTMLElement,
_series: LineTimeSerie[] = [],
_options: LineOptions = {},
private _markersConf?: MarkersConf,
private _segmentSeries: SegmentSerie[] = [],
) {
super(_el, _series, _options);
this.series = new LineSeries(_series);
}
renderMetrics(): void {
override renderMetrics(): void {
this.clearAllMetrics();
this.updateCrosshair();
this.initLineGenerator();
this.initAreaGenerator();
// TODO: seems that renderMetrics is not correct name
if(this.series.length === 0) {
if(!this.series.isSeriesAvailable) {
this.renderNoDataPointsMessage();
return;
}
for(let idx = 0; idx < this.series.length; ++idx) {
if(this.series[idx].visible === false) {
continue;
}
// TODO: use _.defaults same as in core
const confidence = this.series[idx].confidence || 0;
const mode = this.series[idx].mode || Mode.STANDARD;
const target = this.series[idx].target;
const renderDots = this.series[idx].renderDots !== undefined ? this.series[idx].renderDots : false;
const renderLines = this.series[idx].renderLines !== undefined ? this.series[idx].renderLines : true;
this._renderMetric(
this.series[idx].datapoints,
{
color: this.getSerieColor(idx),
confidence,
target,
mode,
serieIdx: idx,
renderDots,
renderLines,
}
);
for(const serie of this.series.visibleSeries) {
this._renderMetric(serie);
}
if(this._markersConf !== undefined) {
this._markersLayer = new Markers(this._markersConf, this.state);
this._markersLayer.render(this.metricContainer);
}
this._segmentsLayer = new Segments(this._segmentSeries, this.state);
this._segmentsLayer.render(this.metricContainer);
}
clearAllMetrics(): void {
@ -60,124 +65,70 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
}
initLineGenerator(): void {
this.lineGenerator = this.d3.line()
.x(d => this.xScale(d[0]))
.y(d => this.yScale(d[1]));
this.lineGenerator = d3.line()
.x(d => this.state.xScale(d[0]))
.y(d => this.state.yScale(d[1]));
this.lineGeneratorY1 = d3.line()
.x(d => this.state.xScale(d[0]))
.y(d => this.state.y1Scale(d[1]));
}
public appendData(data: [number, number][], shouldRerender = true): void {
for(let idx = 0; idx < this.series.length; ++idx) {
if(this.series[idx].visible === false) {
continue;
}
this.series[idx].datapoints.push(data[idx]);
const maxLength = this.series[idx].maxLength;
if(maxLength !== undefined && this.series[idx].datapoints.length > maxLength) {
this.series[idx].datapoints.shift();
}
}
for(let idx = 0; idx < this.series.length; ++idx) {
this.metricContainer.select(`.metric-path-${idx}`)
.datum(this.series[idx].datapoints)
.attr('d', this.lineGenerator);
if(this.series[idx].renderDots === true) {
this.metricContainer.selectAll(`.metric-circle-${idx}`)
.data(this.series[idx].datapoints)
.attr('cx', d => this.xScale(d[0]))
.attr('cy', d => this.yScale(d[1]));
initAreaGenerator(): void {
this.areaGenerator = d3.area()
.x(d => this.state.xScale(d[0]))
.y1(d => this.state.yScale(d[1]))
.y0(d => this.height);
this.areaGeneratorY1 = d3.area()
.x(d => this.state.xScale(d[0]))
.y1(d => this.state.y1Scale(d[1]))
.y0(d => this.height);
}
this._renderDots([data[idx]], idx);
}
}
if(shouldRerender) {
const rightBorder = _.last(data)[0];
this.state.xValueRange = [this.state.getMinValueX(), rightBorder];
this.renderXAxis();
this.renderYAxis();
this.renderGrid();
getRenderGenerator(renderArea: boolean, yOrientation: yAxisOrientation): any {
if(renderArea) {
return yOrientation === yAxisOrientation.LEFT ? this.areaGenerator : this.areaGeneratorY1;
}
return yOrientation === yAxisOrientation.LEFT ? this.lineGenerator : this.areaGeneratorY1;
}
_renderDots(datapoints: number[][], serieIdx: number): void {
const customClass = this.series[serieIdx].class || '';
_renderDots(serie: LineTimeSerie): void {
this.metricContainer.selectAll(null)
.data(datapoints)
.data(serie.datapoints)
.enter()
.append('circle')
.attr('class', `metric-circle-${serieIdx} metric-el ${customClass}`)
.attr('fill', this.getSerieColor(serieIdx))
.attr('class', `metric-circle-${serie.idx} metric-el ${serie.class}`)
.attr('fill', serie.color)
.attr('r', METRIC_CIRCLE_RADIUS)
.style('pointer-events', 'none')
.attr('cx', d => this.xScale(d[0]))
.attr('cy', d => this.yScale(d[1]));
.attr('cx', d => this.state.xScale(d[0]))
.attr('cy', d => this.state.yScale(d[1]));
}
_renderLines(datapoints: number[][], serieIdx: number): void {
const dashArray = this.series[serieIdx].dashArray !== undefined ? this.series[serieIdx].dashArray : '0';
const customClass = this.series[serieIdx].class || ''
_renderLines(serie: LineTimeSerie): void {
const fillColor = serie.renderArea ? serie.color : 'none';
const fillOpacity = serie.renderArea ? 0.5 : 'none';
this.metricContainer
.append('path')
.datum(datapoints)
.attr('class', `metric-path-${serieIdx} metric-el ${customClass}`)
.attr('fill', 'none')
.attr('stroke', this.getSerieColor(serieIdx))
.datum(serie.datapoints)
.attr('class', `metric-path-${serie.idx} metric-el ${serie.class}`)
.attr('fill', fillColor)
.attr('fill-opacity', fillOpacity)
.attr('stroke', serie.color)
.attr('stroke-width', 1)
.attr('stroke-opacity', 0.7)
.attr('pointer-events', 'none')
.style('stroke-dasharray', dashArray)
.attr('d', this.lineGenerator);
}
_renderMetric(
datapoints: number[][],
metricOptions: {
color: string,
confidence: number,
target: string,
mode: Mode,
serieIdx: number,
renderDots: boolean,
renderLines: boolean,
}
): void {
if(_.includes(this.seriesTargetsWithBounds, metricOptions.target)) {
return;
}
if(metricOptions.mode === Mode.CHARGE) {
const dataPairs = this.d3.pairs(datapoints);
this.metricContainer.selectAll(null)
.data(dataPairs)
.enter()
.append('line')
.attr('x1', d => this.xScale(d[0][0]))
.attr('x2', d => this.xScale(d[1][0]))
.attr('y1', d => this.yScale(d[0][1]))
.attr('y2', d => this.yScale(d[1][1]))
.attr('stroke-opacity', 0.7)
.style('stroke-width', 1)
.style('stroke', d => {
if(d[1][0] > d[0][0]) {
return 'green';
} else if (d[1][0] < d[0][0]) {
return 'red';
} else {
return 'gray';
}
});
return;
}
.style('stroke-dasharray', serie.dashArray)
.attr('d', this.getRenderGenerator(serie.renderArea, serie.yOrientation));
}
if(metricOptions.renderLines === true) {
this._renderLines(datapoints, metricOptions.serieIdx);
_renderMetric(serie: LineTimeSerie): void {
if(serie.renderLines === true) {
this._renderLines(serie);
}
if(metricOptions.renderDots === true) {
this._renderDots(datapoints, metricOptions.serieIdx);
if(serie.renderDots === true) {
this._renderDots(serie);
}
}
@ -189,7 +140,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
appendCrosshairCircles(): void {
// circle for each serie
this.series.forEach((serie: LineTimeSerie, serieIdx: number) => {
this.series.visibleSeries.forEach((serie: LineTimeSerie, serieIdx: number) => {
this.appendCrosshairCircle(serieIdx);
});
}
@ -202,7 +153,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
.attr('cx', -CROSSHAIR_BACKGROUND_RAIDUS)
.attr('cy', -CROSSHAIR_BACKGROUND_RAIDUS)
.attr('clip-path', `url(#${this.rectClipId})`)
.attr('fill', this.getSerieColor(serieIdx))
.attr('fill', this.series.visibleSeries[serieIdx].color)
.style('opacity', CROSSHAIR_BACKGROUND_OPACITY)
.style('pointer-events', 'none');
@ -212,33 +163,21 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
.attr('cy', -CROSSHAIR_CIRCLE_RADIUS)
.attr('class', `crosshair-circle-${serieIdx}`)
.attr('clip-path', `url(#${this.rectClipId})`)
.attr('fill', this.getSerieColor(serieIdx))
.attr('fill', this.series.visibleSeries[serieIdx].color)
.attr('r', CROSSHAIR_CIRCLE_RADIUS)
.style('pointer-events', 'none');
}
public renderSharedCrosshair(values: { x?: number, y?: number }): void {
this.onMouseOver(); // TODO: refactor to use it once
const eventX = this.xScale(values.x);
const eventY = this.yScale(values.y);
this.moveCrosshairLine(eventX, eventY);
const datapoints = this.findAndHighlightDatapoints(values.x, values.y);
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.sharedCrosshairMove === undefined) {
return;
}
this.options.eventsCallbacks.sharedCrosshairMove({
datapoints: datapoints,
eventX, eventY
});
}
public hideSharedCrosshair(): void {
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 {
this.crosshair.style('display', null);
switch(this.options.crosshair.orientation) {
case CrosshairOrientation.VERTICAL:
this.crosshair.select('#crosshair-line-x')
@ -303,9 +242,9 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
default:
throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`);
}
// TODO: d3.bisect is not the best way. Use binary search
const bisectIndex = this.d3.bisector((d: [number, number]) => d[columnIdx]).left;
const bisectIndex = d3.bisector((d: [number, number]) => d[columnIdx]).left;
let closestIdx = bisectIndex(datapoints, value);
// TODO: refactor corner cases
@ -330,7 +269,7 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
// columnIdx: 1 for y, 0 for x
// inverval: x/y value interval between data points
// TODO: move it to base/state instead of timeInterval
const intervals = _.map(this.series, serie => {
const intervals = _.map(this.series.visibleSeries, serie => {
if(serie.datapoints.length < 2) {
return undefined;
}
@ -343,119 +282,133 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
return _.max(intervals);
}
onMouseMove(): void {
const eventX = this.d3.mouse(this.chartContainer.node())[0];
const eventY = this.d3.mouse(this.chartContainer.node())[1];
const xValue = this.xScale.invert(eventX); // mouse x position in xScale
const yValue = this.yScale.invert(eventY);
// TODO: isOutOfChart is a hack, use clip path correctly
if(this.isOutOfChart() === true) {
this.crosshair.style('display', 'none');
return;
}
this.moveCrosshairLine(eventX, eventY);
getMouseObj(): MouseObj {
const eventX = d3.mouse(this.chartContainer.node())[0];
const eventY = d3.mouse(this.chartContainer.node())[1];
const xValue = this.state.xScale.invert(eventX); // mouse x position in xScale
const yValue = this.state.yScale.invert(eventY);
const datapoints = this.findAndHighlightDatapoints(xValue, yValue);
if(this.options.eventsCallbacks === undefined || this.options.eventsCallbacks.mouseMove === undefined) {
return;
}
// TDOO: is shift key pressed
// TODO: is shift key pressed
// TODO: need to refactor this object
this.options.eventsCallbacks.mouseMove({
x: this.d3.event.pageX,
y: this.d3.event.pageY,
return {
x: d3.event.pageX,
y: d3.event.pageY,
xVal: xValue,
yVal: yValue,
series: datapoints,
chartX: eventX,
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 }[] {
if(this.series === undefined || this.series.length === 0) {
if(!this.series.isSeriesAvailable) {
return [];
}
let points = []; // datapoints in each metric that is closest to xValue/yValue position
this.series.forEach((serie: LineTimeSerie, serieIdx: number) => {
if(
serie.visible === false ||
_.includes(this.seriesTargetsWithBounds, serie.target)
) {
this.hideCrosshairCircle(serieIdx);
return;
}
this.series.visibleSeries.forEach((serie: LineTimeSerie) => {
const closestDatapoint = this.getClosestDatapoint(serie, xValue, yValue);
if(closestDatapoint === undefined || this.isOutOfRange(closestDatapoint, xValue, yValue, serie.useOutOfRange)) {
this.hideCrosshairCircle(serieIdx);
return;
if(_.isNil(closestDatapoint) || _.isNil(closestDatapoint[0])) {
this.hideCrosshairCircle(serie.idx);
} else {
const xPosition = this.state.xScale(closestDatapoint[0]);
let yPosition;
if(serie.yOrientation === yAxisOrientation.RIGHT) {
yPosition = this.state.y1Scale(closestDatapoint[1]);
} else {
yPosition = this.state.yScale(closestDatapoint[1]);
}
this.moveCrosshairCircle(xPosition, yPosition, serie.idx);
}
const xPosition = this.xScale(closestDatapoint[0]);
const yPosition = this.yScale(closestDatapoint[1]);
this.moveCrosshairCircle(xPosition, yPosition, serieIdx);
points.push({
value: closestDatapoint,
color: this.getSerieColor(serieIdx),
color: serie.color,
label: serie.alias || serie.target
});
});
return points;
}
isOutOfRange(closestDatapoint: [number, number], xValue: number, yValue: number, useOutOfRange = true): boolean {
// find is mouse position more than xRange/yRange from closest point
// TODO: refactor getValueInterval to remove this!
if(useOutOfRange === false) {
return false;
}
let columnIdx; // 1 for y value, 0 for x value
let value; // xValue ot y Value
switch(this.options.crosshair.orientation) {
case CrosshairOrientation.VERTICAL:
columnIdx = 0;
value = xValue;
break;
case CrosshairOrientation.HORIZONTAL:
columnIdx = 1;
value = yValue;
break;
case CrosshairOrientation.BOTH:
// TODO: maybe use voronoi
columnIdx = 1;
value = yValue;
default:
throw new Error(`Unknown type of crosshair orientaion: ${this.options.crosshair.orientation}`);
}
const range = Math.abs(closestDatapoint[columnIdx] - value);
const interval = this.getValueInterval(columnIdx); // interval between points
// do not move crosshair circles, it mouse to far from closest point
return interval === undefined || range > interval / 2;
}
onMouseOver(): void {
showCrosshair() {
this.crosshair.style('display', null);
this.crosshair.selectAll('.crosshair-circle')
.style('display', null);
}
onMouseOut(): void {
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.mouseOut !== undefined) {
this.options.eventsCallbacks.mouseOut();
}
hideCrosshair() {
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 {
return this.options.doubleClickEvent.isActive;
}
protected onBrushEnd(): void {
super.onBrushEnd();
this.onMouseOver();
}
updateMarkers(markersConf: MarkersConf): void {
this._markersConf = markersConf;
}
// methods below rewrite cores, (move more methods here)
updateSegments(segments: SegmentSerie[]): void {
this._segmentSeries = segments;
}
// methods below rewrite s, (move more methods here)
protected zoomOut(): void {
// TODO: test to remove, seems its depricated
if(this.isOutOfChart() === true) {
if(d3.event.type === 'dblclick' && !this.isDoubleClickActive()) {
return;
}
// TODO: its not clear, why we use this orientation here. Mb its better to use separate option
const orientation: BrushOrientation = this.options.zoomEvents.mouse.zoom.orientation;
const orientation: BrushOrientation = this.options.mouseZoomEvent.orientation;
const xInterval = this.state.xValueRange[1] - this.state.xValueRange[0];
const yInterval = this.state.yValueRange[1] - this.state.yValueRange[0];
switch(orientation) {
@ -483,19 +436,28 @@ export class LinePod extends ChartwerkPod<LineTimeSerie, LineOptions> {
this.renderYAxis();
this.renderGrid();
this.onMouseOver();
if(this.options.eventsCallbacks !== undefined && this.options.eventsCallbacks.zoomOut !== undefined) {
let xAxisMiddleValue: number = this.xScale.invert(this.width / 2);
let yAxisMiddleValue: number = this.yScale.invert(this.height / 2);
const centers = {
x: xAxisMiddleValue,
y: yAxisMiddleValue
}
this.options.eventsCallbacks.zoomOut(centers);
let xAxisMiddleValue: number = this.state.xScale.invert(this.width / 2);
let yAxisMiddleValue: number = this.state.yScale.invert(this.height / 2);
const centers = {
x: xAxisMiddleValue,
y: yAxisMiddleValue
}
// 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]
);
}
}
}
// TODO: it should be moved to VUE folder
// it is used with Vue.component, e.g.: Vue.component('chartwerk-line-pod', VueChartwerkLinePod)
export const VueChartwerkLinePod = {
// alternative to `template: '<div class="chartwerk-line-pod" :id="id" />'`
@ -527,4 +489,4 @@ export const VueChartwerkLinePod = {
}
};
export { LineTimeSerie, LineOptions, Mode, TickOrientation, TimeFormat };
export { LineTimeSerie, LineOptions, TimeFormat, LinePod };

21
src/models/line_series.ts

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

15
src/models/marker.ts

@ -0,0 +1,15 @@
export type MarkerElem = [number, any?];
export type MarkerSerie = {
color: string;
// TODO: make one-dimensional array with only x
data: MarkerElem[] // [x, payload] payload is any data for tooltip
}
export type MarkersConf = {
series: MarkerSerie[],
events?: {
onMouseMove?: (el: MarkerElem) => void;
onMouseOut?: () => void;
}
}

4
src/models/segment.ts

@ -0,0 +1,4 @@
export type SegmentSerie = {
color: string;
data: [number, number, any?][] // [from, to, payload?] payload is any data for tooltip
}

31
src/types.ts

@ -1,18 +1,31 @@
import { TimeSerie, Options } from '@chartwerk/core';
import { Serie, Options } from '@chartwerk/core';
import { AxisRange } from '@chartwerk/core/dist/types';
type LineTimeSerieParams = {
confidence: number,
mode: Mode,
maxLength: number,
renderDots: boolean,
renderLines: boolean, // TODO: refactor same as scatter-pod
useOutOfRange: boolean, // It's temporary hack. Need to refactor getValueInterval() method
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 enum Mode {
STANDARD = 'Standard',
CHARGE = 'Charge'
export type LineTimeSerie = Serie & Partial<LineTimeSerieParams>;
export type LineOptions = Options & {
events? : {
zoomOut?: (centers: {
x: number;
y: number;
}, range: AxisRange[]) => void;
}
}
export type LineTimeSerie = TimeSerie & Partial<LineTimeSerieParams>;
export type LineOptions = Options;
export type MouseObj = {
x: number,
y: number,
xVal: number,
yVal: number,
series: { value: [number, number], color: string, label: string }[],
chartX: number,
chartWidth: number
}

5
tsconfig.json

@ -17,6 +17,7 @@
"noImplicitUseStrict": false,
"noImplicitAny": false,
"noUnusedLocals": false,
"baseUrl": "./src"
}
"baseUrl": "./src",
},
"include": ["src/**/*"]
}

5357
yarn.lock

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