Skip to content

Commit

Permalink
Merge pull request #7 from jan-dolejsi/strokeWidth
Browse files Browse the repository at this point in the history
Tooltip config & Stroke width
  • Loading branch information
javdome authored Jan 15, 2024
2 parents 03e1bbb + 1405466 commit ecd81e5
Show file tree
Hide file tree
Showing 11 changed files with 251 additions and 130 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
arrow.d.ts
node_modules/
*.tgz
4 changes: 4 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules
.gitignore
.vscode
*.tgz
52 changes: 32 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Following the issue of vis https://github.com/almende/vis/issues/1699, and thank

1 - Download the package

```
```bash
npm install timeline-arrows
```

Expand All @@ -20,8 +20,8 @@ npm install timeline-arrows

For instance:

```
const my_timeline = new vis.Timeline(container, items, groups, options);
```bash
const myTimeline = new vis.Timeline(container, items, groups, options);
```


Expand All @@ -35,8 +35,8 @@ And optionally:

For instance:

```
var arrows_array = [
```javascript
var arrowsSpecs = [
{ id: 2, id_item_1: 1, id_item_2: 2 },
{ id: 5, id_item_1: 3, id_item_2: 5, title:'Hello!!!' },
{ id: 7, id_item_1: 6, id_item_2: 7 },
Expand All @@ -48,8 +48,8 @@ var arrows_array = [

For instance:

```
const my_Arrow = new Arrow(my_timeline, arrows_array);
```javascript
const myArrows = new Arrow(myTimeline, arrowsSpecs);
```

That's it :)
Expand All @@ -59,13 +59,16 @@ That's it :)

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

```
```javascript
const options = {
followRelationships: true,
color: "#039E00"
color: "#039E00",
tooltipConfig: (el, title) => {
// tooltip initialization
},
};

const my_Arrow = new Arrow(my_timeline, arrows_array, options);
const myArrows = new Arrow(myTimeline, arrowsSpecs, options);
```

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

**strokeWidth** - defaults to 3 (px).
Sets the arrows width in pixels.

**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.
This method takes two arguments, `el` - the arrow - and `title` - the content of the `title` property set in the arrow data.

## Methods

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

For instance:
```
my_Arrow.getArrow (2);

```javascript
myArrow.getArrow(2);
```

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

For instance:
```
my_Arrow.addArrow ( { id: 13, id_item_1: 15, id_item_2: 16 } );

```javascript
myArrow.addArrow({ id: 13, id_item_1: 15, id_item_2: 16 });
```

**removeArrow ( *arrow_Id* )** Removes the arrows with this arrow_Id.
**removeArrow ( *arrow_Id* )** Removes the arrows with this arrow_Id.

For instance:
```
my_Arrow.removeArrow ( 10 );

```javascript
myArrow.removeArrow( 10 );
```

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

For instance:
```
my_Arrow.removeArrowbyItemId ( 23 );

```javascript
myArrow.removeItemArrows( 23 );
```

## Examples
Expand Down
129 changes: 98 additions & 31 deletions arrow.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,70 @@
* timeline-arrows may be distributed under either license.
*/

// @ts-check

/**
* @typedef {(number | string)} VisIdType Timeline view item id. Equivalent to vis.IdType.
*/

/**
* @typedef {(number | string)} ArrowIdType arrow id.
*/

/**
* @typedef ArrowSpec Arrow specification
* @property {ArrowIdType} id arrow id
* @property {VisIdType} id_item_1 start timeline item id
* @property {VisIdType} id_item_2 end timeline item id
* @property {string} [title] optional arrow title
*/

/**
* @typedef ArrowOptions Arrow configuration options
* @property {boolean} [followRelationships] if true, arrows can point backwards and will follow the relationships set in the data
* @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.
This method takes two arguments, `el` - the arrow - and `title` - the content of the `title` property set in the arrow data.
* @property {string} [color] arrow color
* @property {number} [strokeWidth] arrow thickness in pixels
*/

/** Arrow set for a vis.js Timeline. */
export default class Arrow {

/**
* Creates arrows.
* @param {*} timeline timeline object
* @param {ArrowSpec[]} dependencies arrows
* @param {ArrowOptions} [options]
*/
constructor(timeline, dependencies, options) {
this._svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
this._timeline = timeline;

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

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

/** @private @type {SVGMarkerElement} */
this._arrowHead = document.createElementNS(
"http://www.w3.org/2000/svg",
"marker"
);
/** @private @type {SVGPathElement} */
this._arrowHeadPath = document.createElementNS(
"http://www.w3.org/2000/svg",
"path"
);

this._dependency = dependencies;

/** @private @type {SVGPathElement[]} */
this._dependencyPath = [];

this._initialize();
Expand Down Expand Up @@ -87,6 +130,7 @@ export default class Arrow {

}

/** @private */
_createPath(){
//Add a new path to array dependencyPath and to svg
let somePath = document.createElementNS(
Expand All @@ -95,36 +139,37 @@ export default class Arrow {
);
somePath.setAttribute("d", "M 0 0");
somePath.style.stroke = this._arrowsColor;
somePath.style.strokeWidth = "3px";
somePath.style.strokeWidth = this._arrowsStrokeWidth + "px";
somePath.style.fill = "none";
somePath.style.pointerEvents = "auto";
this._dependencyPath.push(somePath);
this._svg.appendChild(somePath);
}



/** @private */
_drawDependencies() {
//Create paths for the started dependency array
for (let i = 0; i < this._dependency.length; i++) {
this._drawArrows(this._dependency[i], i);
}
}

/**
* @private
* @param {ArrowSpec} dep arrow specification
* @param {number} index arrow index
*/
_drawArrows(dep, index) {
//Checks if both items exist
//if( (typeof this._timeline.itemsData._data[dep.id_item_1] !== "undefined") && (typeof this._timeline.itemsData._data[dep.id_item_2] !== "undefined") ) {
//debugger;
if( (this._timeline.itemsData.get(dep.id_item_1) !== null) && (this._timeline.itemsData.get(dep.id_item_2) !== null) ) {
var bothItemsExist = true;
} else {
var bothItemsExist = false;
}
const bothItemsExist = (this._timeline.itemsData.get(dep.id_item_1) !== null) && (this._timeline.itemsData.get(dep.id_item_2) !== null);

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

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

}

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


addArrow (dep) {
/**
* Adds arrow between two timeline items.
* @param {ArrowSpec} dep item dependency
*/
addArrow(dep) {
this._dependency.push(dep);
this._createPath();
this._timeline.redraw();
}

getArrow (id) {
for (let i = 0; i < this._dependency.length; i++) {
if (this._dependency[i].id == id) {
return this._dependency[i];
}
}
return null;
/**
* Get arrow by ID.
* @param {ArrowIdType} id arrow ID
* @returns {ArrowSpec | null} arrow spec, or null
*/
getArrow(id) {
return this._dependency.find(dep => dep.id === id) ?? null;
}

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

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

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

this._dependency.splice(index, 1); //Elimino del array dependency
this._dependencyPath.splice(index, 1); //Elimino del array dependencyPath

list[index + 1].parentNode.removeChild(list[index + 1]); //Lo elimino del dom
list[index + 1].parentNode.removeChild(list[index + 1]); //Lo elimino del dom
}
}

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


/**
* For backward compatibility
* @deprecated use the removeItemArrows method instead.
*/
removeArrowbyItemId(id) {
this.removeItemArrows(id);
}

}
Loading

0 comments on commit ecd81e5

Please sign in to comment.