Skip to content

Commit ecd81e5

Browse files
authored
Merge pull request #7 from jan-dolejsi/strokeWidth
Tooltip config & Stroke width
2 parents 03e1bbb + 1405466 commit ecd81e5

File tree

11 files changed

+251
-130
lines changed

11 files changed

+251
-130
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
arrow.d.ts
2+
node_modules/
3+
*.tgz

.npmignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.gitignore
3+
.vscode
4+
*.tgz

README.md

Lines changed: 32 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Following the issue of vis https://github.com/almende/vis/issues/1699, and thank
99

1010
1 - Download the package
1111

12-
```
12+
```bash
1313
npm install timeline-arrows
1414
```
1515

@@ -20,8 +20,8 @@ npm install timeline-arrows
2020

2121
For instance:
2222

23-
```
24-
const my_timeline = new vis.Timeline(container, items, groups, options);
23+
```bash
24+
const myTimeline = new vis.Timeline(container, items, groups, options);
2525
```
2626

2727

@@ -35,8 +35,8 @@ And optionally:
3535

3636
For instance:
3737

38-
```
39-
var arrows_array = [
38+
```javascript
39+
var arrowsSpecs = [
4040
{ id: 2, id_item_1: 1, id_item_2: 2 },
4141
{ id: 5, id_item_1: 3, id_item_2: 5, title:'Hello!!!' },
4242
{ id: 7, id_item_1: 6, id_item_2: 7 },
@@ -48,8 +48,8 @@ var arrows_array = [
4848

4949
For instance:
5050

51-
```
52-
const my_Arrow = new Arrow(my_timeline, arrows_array);
51+
```javascript
52+
const myArrows = new Arrow(myTimeline, arrowsSpecs);
5353
```
5454

5555
That's it :)
@@ -59,13 +59,16 @@ That's it :)
5959

6060
Options can be used to customize the arrows. Options are defined as a JSON object. All options are optional.
6161

62-
```
62+
```javascript
6363
const options = {
6464
followRelationships: true,
65-
color: "#039E00"
65+
color: "#039E00",
66+
tooltipConfig: (el, title) => {
67+
// tooltip initialization
68+
},
6669
};
6770

68-
const my_Arrow = new Arrow(my_timeline, arrows_array, options);
71+
const myArrows = new Arrow(myTimeline, arrowsSpecs, options);
6972
```
7073

7174
**followRelationships** - defaults to false.
@@ -74,6 +77,11 @@ If true, arrows can point backwards and will follow the relationships set in the
7477
**color** - defaults to "#9c0000".
7578
Sets the arrows color.
7679

80+
**strokeWidth** - defaults to 3 (px).
81+
Sets the arrows width in pixels.
82+
83+
**tooltipConfig** - if arrows have a `title` property, the default behavior will add a title attribute that shows on hover. However, you might not want to use the title attribute, but instead your own tooltip configuration.
84+
This method takes two arguments, `el` - the arrow - and `title` - the content of the `title` property set in the arrow data.
7785

7886
## Methods
7987

@@ -82,29 +90,33 @@ I have created the following methods:
8290
**getArrow ( *arrow id* )** Returns the arrow whith this arrow_id.
8391

8492
For instance:
85-
```
86-
my_Arrow.getArrow (2);
93+
94+
```javascript
95+
myArrow.getArrow(2);
8796
```
8897

8998
**addArrow ( *arrow object* )** Inserts a new arrow.
9099

91100
For instance:
92-
```
93-
my_Arrow.addArrow ( { id: 13, id_item_1: 15, id_item_2: 16 } );
101+
102+
```javascript
103+
myArrow.addArrow({ id: 13, id_item_1: 15, id_item_2: 16 });
94104
```
95105

96-
**removeArrow ( *arrow_Id* )** Removes the arrows with this arrow_Id.
106+
**removeArrow ( *arrow_Id* )** Removes the arrows with this arrow_Id.
97107

98108
For instance:
99-
```
100-
my_Arrow.removeArrow ( 10 );
109+
110+
```javascript
111+
myArrow.removeArrow( 10 );
101112
```
102113

103-
**removeArrowbyItemId ( *item_Id* )** Removes the arrows connected with Items with this item_Id. Returns an array with the id's of the removed arrows.
114+
**removeItemArrows ( *item_Id* )** Removes the arrows connected with Items with this item_Id. Returns an array with the id's of the removed arrows.
104115

105116
For instance:
106-
```
107-
my_Arrow.removeArrowbyItemId ( 23 );
117+
118+
```javascript
119+
myArrow.removeItemArrows( 23 );
108120
```
109121

110122
## Examples

arrow.js

Lines changed: 98 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,70 @@
2424
* timeline-arrows may be distributed under either license.
2525
*/
2626

27+
// @ts-check
28+
29+
/**
30+
* @typedef {(number | string)} VisIdType Timeline view item id. Equivalent to vis.IdType.
31+
*/
32+
33+
/**
34+
* @typedef {(number | string)} ArrowIdType arrow id.
35+
*/
36+
37+
/**
38+
* @typedef ArrowSpec Arrow specification
39+
* @property {ArrowIdType} id arrow id
40+
* @property {VisIdType} id_item_1 start timeline item id
41+
* @property {VisIdType} id_item_2 end timeline item id
42+
* @property {string} [title] optional arrow title
43+
*/
44+
45+
/**
46+
* @typedef ArrowOptions Arrow configuration options
47+
* @property {boolean} [followRelationships] if true, arrows can point backwards and will follow the relationships set in the data
48+
* @property {(el: SVGPathElement, title: string) => string } [tooltipConfig] if arrows have a `title` property, the default behavior will add a title attribute that shows on hover. However, you might not want to use the title attribute, but instead your own tooltip configuration.
49+
This method takes two arguments, `el` - the arrow - and `title` - the content of the `title` property set in the arrow data.
50+
* @property {string} [color] arrow color
51+
* @property {number} [strokeWidth] arrow thickness in pixels
52+
*/
53+
54+
/** Arrow set for a vis.js Timeline. */
2755
export default class Arrow {
2856

57+
/**
58+
* Creates arrows.
59+
* @param {*} timeline timeline object
60+
* @param {ArrowSpec[]} dependencies arrows
61+
* @param {ArrowOptions} [options]
62+
*/
2963
constructor(timeline, dependencies, options) {
3064
this._svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
3165
this._timeline = timeline;
3266

67+
/** @private @type {boolean | undefined} if true, arrows can point backwards and will follow the relationships set in the data */
3368
this._followRelationships = options?.followRelationships;
69+
/** @private @type {((el: SVGPathElement, title: string) => string) | undefined } */
70+
this._tooltipConfig = options?.tooltipConfig;
3471

72+
/** @private @type {string} color */
3573
this._arrowsColor = options?.color ? options.color : "#9c0000"
74+
/** @private @type {number} arrow thickness in pixels */
75+
this._arrowsStrokeWidth = options?.strokeWidth ?? 3;
3676

77+
/** @private @type {SVGMarkerElement} */
3778
this._arrowHead = document.createElementNS(
3879
"http://www.w3.org/2000/svg",
3980
"marker"
4081
);
82+
/** @private @type {SVGPathElement} */
4183
this._arrowHeadPath = document.createElementNS(
4284
"http://www.w3.org/2000/svg",
4385
"path"
4486
);
4587

4688
this._dependency = dependencies;
4789

90+
/** @private @type {SVGPathElement[]} */
4891
this._dependencyPath = [];
4992

5093
this._initialize();
@@ -87,6 +130,7 @@ export default class Arrow {
87130

88131
}
89132

133+
/** @private */
90134
_createPath(){
91135
//Add a new path to array dependencyPath and to svg
92136
let somePath = document.createElementNS(
@@ -95,36 +139,37 @@ export default class Arrow {
95139
);
96140
somePath.setAttribute("d", "M 0 0");
97141
somePath.style.stroke = this._arrowsColor;
98-
somePath.style.strokeWidth = "3px";
142+
somePath.style.strokeWidth = this._arrowsStrokeWidth + "px";
99143
somePath.style.fill = "none";
100144
somePath.style.pointerEvents = "auto";
101145
this._dependencyPath.push(somePath);
102146
this._svg.appendChild(somePath);
103147
}
104148

105149

106-
150+
/** @private */
107151
_drawDependencies() {
108152
//Create paths for the started dependency array
109153
for (let i = 0; i < this._dependency.length; i++) {
110154
this._drawArrows(this._dependency[i], i);
111155
}
112156
}
113157

158+
/**
159+
* @private
160+
* @param {ArrowSpec} dep arrow specification
161+
* @param {number} index arrow index
162+
*/
114163
_drawArrows(dep, index) {
115164
//Checks if both items exist
116165
//if( (typeof this._timeline.itemsData._data[dep.id_item_1] !== "undefined") && (typeof this._timeline.itemsData._data[dep.id_item_2] !== "undefined") ) {
117166
//debugger;
118-
if( (this._timeline.itemsData.get(dep.id_item_1) !== null) && (this._timeline.itemsData.get(dep.id_item_2) !== null) ) {
119-
var bothItemsExist = true;
120-
} else {
121-
var bothItemsExist = false;
122-
}
167+
const bothItemsExist = (this._timeline.itemsData.get(dep.id_item_1) !== null) && (this._timeline.itemsData.get(dep.id_item_2) !== null);
123168

124169
//Checks if at least one item is visible in screen
125-
var oneItemVisible = false; //Iniciamos a false
170+
let oneItemVisible = false; //Iniciamos a false
126171
if (bothItemsExist) {
127-
var visibleItems = this._timeline.getVisibleItems();
172+
const visibleItems = this._timeline.getVisibleItems();
128173
for (let k = 0; k < visibleItems.length ; k++) {
129174
if (dep.id_item_1 == visibleItems[k]) oneItemVisible = true;
130175
if (dep.id_item_2 == visibleItems[k]) oneItemVisible = true;
@@ -212,7 +257,9 @@ export default class Arrow {
212257

213258
// Adding the title if property title has been added in the dependency
214259
if (dep.hasOwnProperty("title")) {
215-
this._dependencyPath[index].innerHTML = "<title>" +dep.title +"</title>"
260+
this._tooltipConfig
261+
? this._tooltipConfig(this._dependencyPath[index], dep.title ?? '')
262+
: this._dependencyPath[index].innerHTML = "<title>" + dep.title + "</title>";
216263
}
217264
} else {
218265
this._dependencyPath[index].setAttribute("marker-end", "");
@@ -221,7 +268,7 @@ export default class Arrow {
221268

222269
}
223270

224-
//Función que recibe in Item y devuelve la posición en pantalla del item.
271+
/** @private Función que recibe in Item y devuelve la posición en pantalla del item. */
225272
_getItemPos (item) {
226273
let left_x = item.left;
227274
let top_y;
@@ -243,38 +290,52 @@ export default class Arrow {
243290
}
244291

245292

246-
addArrow (dep) {
293+
/**
294+
* Adds arrow between two timeline items.
295+
* @param {ArrowSpec} dep item dependency
296+
*/
297+
addArrow(dep) {
247298
this._dependency.push(dep);
248299
this._createPath();
249300
this._timeline.redraw();
250301
}
251302

252-
getArrow (id) {
253-
for (let i = 0; i < this._dependency.length; i++) {
254-
if (this._dependency[i].id == id) {
255-
return this._dependency[i];
256-
}
257-
}
258-
return null;
303+
/**
304+
* Get arrow by ID.
305+
* @param {ArrowIdType} id arrow ID
306+
* @returns {ArrowSpec | null} arrow spec, or null
307+
*/
308+
getArrow(id) {
309+
return this._dependency.find(dep => dep.id === id) ?? null;
259310
}
260311

261-
//Función que recibe el id de una flecha y la elimina.
312+
/**
313+
* Finds arrow with the given id and removes it.
314+
* Función que recibe el id de una flecha y la elimina.
315+
* @param {ArrowIdType} id arrow id
316+
*/
262317
removeArrow(id) {
263-
for (let i = 0; i < this._dependency.length; i++) {
264-
if (this._dependency[i].id == id) var index = i;
265-
}
318+
const index = this._dependency.findIndex(dep => dep.id === id);
266319

267-
//var list = document.getElementsByTagName("path"); //FALTA QUE ESTA SELECCION LA HAGA PARA EL DOM DEL TIMELINE INSTANCIADO!!!!
268-
var list = document.querySelectorAll("#" +this._timeline.dom.container.id +" path");
320+
if (index >= 0) {
269321

270-
this._dependency.splice(index, 1); //Elimino del array dependency
271-
this._dependencyPath.splice(index, 1); //Elimino del array dependencyPath
322+
//var list = document.getElementsByTagName("path"); //FALTA QUE ESTA SELECCION LA HAGA PARA EL DOM DEL TIMELINE INSTANCIADO!!!!
323+
const list = document.querySelectorAll("#" + this._timeline.dom.container.id + " path");
324+
325+
this._dependency.splice(index, 1); //Elimino del array dependency
326+
this._dependencyPath.splice(index, 1); //Elimino del array dependencyPath
272327

273-
list[index + 1].parentNode.removeChild(list[index + 1]); //Lo elimino del dom
328+
list[index + 1].parentNode.removeChild(list[index + 1]); //Lo elimino del dom
329+
}
274330
}
275331

276-
//Función que recibe el id de un item y elimina la flecha.
277-
removeArrowbyItemId(id) {
332+
/**
333+
* Finds all arrows related to one view item and removes them all.
334+
* Función que recibe el id de un item y elimina la flecha.
335+
* @param {VisIdType} id view item id
336+
* @returns {(ArrowIdType)[]} list of removed arrow ids
337+
*/
338+
removeItemArrows(id) {
278339
let listOfRemovedArrows = [];
279340
for (let i = 0; i < this._dependency.length; i++) {
280341
if ( (this._dependency[i].id_item_1 == id) || (this._dependency[i].id_item_2 == id) ) {
@@ -286,6 +347,12 @@ export default class Arrow {
286347
return listOfRemovedArrows;
287348
}
288349

289-
350+
/**
351+
* For backward compatibility
352+
* @deprecated use the removeItemArrows method instead.
353+
*/
354+
removeArrowbyItemId(id) {
355+
this.removeItemArrows(id);
356+
}
290357

291358
}

0 commit comments

Comments
 (0)