Skip to content

Commit

Permalink
Add arrows decoration to line annotation (#608)
Browse files Browse the repository at this point in the history
* Add arrowheads options to line annotation

* fixes CC 1

* manages borderWidth and the apex of the arrow

* adds first 2 test cases

* renames function and options and fixes CC

* the line is not shown if borderWidth is 0 and more test cases

* removes use checks and fixes CC lines of code in the draw method

* adds arrowHeadsOptions types

* adds documentation

* re-applies previous table headers def

* apply arrow heads to existing samples

* adds test case with scriptable options

* fix CC duplicated code

* fixes background shadow color for arrow heads and the line

* fixes CC max lines per function

* applies shadow on border for lines and background on filled arrows

* removes useless beginPath

* changes display to enabled to be aligned with label subnode

* changes display to enabled to be aligned with label subnode - types

* apply review about shadow options

* enables fallback of arrowHeads.start and arrowHeads.end

* apply review 2

* enables borderWidth at arrowHeads level

* changes way to get the options to describe for fallback

* apply review

* fixes label background color in order to work both Chrome and FF

* changes defaults in arrowHeads in order to engage the fallback

* updates documentation with defaults in fallback

* Update src/types/line.js

Co-authored-by: Jukka Kurkela <jukka.kurkela@gmail.com>

* optimized arrowHeads node default

Co-authored-by: Jukka Kurkela <jukka.kurkela@gmail.com>
  • Loading branch information
stockiNail and kurkle committed Jan 17, 2022
1 parent 90898a6 commit 44f961e
Show file tree
Hide file tree
Showing 25 changed files with 1,288 additions and 40 deletions.
37 changes: 37 additions & 0 deletions docs/guide/types/line.md
Expand Up @@ -51,6 +51,7 @@ The following options are available for line annotations. All of these options c
| Name | Type | [Scriptable](../options#scriptable-options) | Default
| ---- | ---- | :----: | ----
| [`adjustScaleRange`](#general) | `boolean` | Yes | `true`
| [`arrowHeads`](#arrow-heads) | `{start: object, end:object}` | Yes |
| [`borderColor`](#styling) | [`Color`](../options#color) | Yes | `options.color`
| [`borderDash`](#styling) | `number[]` | Yes | `[]`
| [`borderDashOffset`](#styling) | `number` | Yes | `0`
Expand Down Expand Up @@ -160,3 +161,39 @@ All of these options can be [Scriptable](../options#scriptable-options)
#### borderRadius

If this value is a number, it is applied to all corners of the rectangle (topLeft, topRight, bottomLeft, bottomRight). If this value is an object, the `topLeft` property defines the top-left corners border radius. Similarly, the `topRight`, `bottomLeft`, and `bottomRight` properties can also be specified. Omitted corners have radius of 0.

## Arrow heads

Namespace: `options.annotations[annotationID].arrowHeads`, it defines options for the line annotation arrow heads.

All of these options can be [Scriptable](../options#scriptable-options)

| Name | Type | Notes
| ---- | ---- | ----
| [`end`](#arrow-head-configuration) | `object` | To configure the arrow head at the end of the line.
| [`start`](#arrow-head-configuration) | `object` | To configure the arrow head at the start of the line.

### Arrow head configuration

Enabling it, you can add arrow heads at start and/or end of a line. It uses the `borderWidth` of the line options to configure the line width of the arrow head.

The following options are available for can be specified per (`start` and/or `end`) arrow head, or at the top level (`arrowHeads`) which apply to all arrow heads.

All of these options can be [Scriptable](../options#scriptable-options)

| Name | Type | Default | Notes
| ---- | ---- | :----: | ----
| `backgroundColor` | [`Color`](../options#color) | `lineAnnotation.borderColor` | Background color of the arrow head.
| `backgroundShadowColor` | [`Color`](../options#color) | `'transparent'` | The color of shadow of the arrow head. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor).
| `borderColor` | [`Color`](../options#color) | `lineAnnotation.borderColor` | The border arrow head color.
| `borderDash` | `number[]` | `lineAnnotation.borderDash` | Length and spacing of dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
| `borderDashOffset` | `number` | `lineAnnotation.borderDashOffset` | Offset for border arrow head dashes. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineDashOffset).
| `borderShadowColor` | [`Color`](../options#color) | `lineAnnotation.borderShadowColor` | The color of border shadow of the arrow head. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowColor).
| `borderWidth` | `number` | `lineAnnotation.borderWidth` | The border line width (in pixels).
| `enabled` | `boolean` | `false` | Whether or not the arrow head is shown.
| `fill` | `boolean` | `false` | Whether or not the arrow head is filled.
| `length` | `number` | `12` | The length of the arrow head in pixels.
| `shadowBlur` | `number` | `lineAnnotation.shadowBlur` | The amount of blur applied to shadow of the arrow head. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowBlur).
| `shadowOffsetX` | `number` | `lineAnnotation.shadowOffsetX` | The distance that shadow, of the arrow head, will be offset horizontally. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetX).
| `shadowOffsetY` | `number` | `lineAnnotation.shadowOffsetY` | The distance that shadow, of the arrow head, will be offset vertically. See [MDN](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/shadowOffsetY).
| `width` | `number` | `6` | The width of the arrow head in pixels.
11 changes: 10 additions & 1 deletion docs/samples/line/limited.md
Expand Up @@ -24,7 +24,6 @@ const data = {
const annotation1 = {
type: 'line',
borderColor: 'green',
borderDash: [6, 6],
borderWidth: 3,
label: {
enabled: true,
Expand All @@ -33,6 +32,16 @@ const annotation1 = {
color: 'green',
content: 'Summer time'
},
arrowHeads: {
start: {
enabled: true,
borderColor: 'green'
},
end: {
enabled: true,
borderColor: 'green'
}
},
xMax: 8,
xMin: 5,
xScaleID: 'x',
Expand Down
24 changes: 8 additions & 16 deletions docs/samples/point/combined.md
Expand Up @@ -33,6 +33,13 @@ const annotation1 = {
color: 'green',
content: 'Project timeline'
},
arrowHeads: {
end: {
enabled: true,
fill: true,
borderColor: 'green'
}
},
xMax: 10.5,
xMin: 2.5,
xScaleID: 'x',
Expand Down Expand Up @@ -77,27 +84,13 @@ const annotation4 = {
type: 'point',
backgroundColor: 'green',
borderWidth: 0,
pointStyle: 'triangle',
rotation: 90,
xValue: 2.5,
xScaleID: 'x',
yValue: 110,
yScaleID: 'y'
};
// </block:annotation4>

// <block:annotation5:5>
const annotation5 = {
type: 'point',
backgroundColor: 'green',
borderWidth: 0,
xValue: 10.5,
xScaleID: 'x',
yValue: 110,
yScaleID: 'y'
};
// </block:annotation5>

/* <block:config:0> */
const config = {
type: 'bar',
Expand All @@ -117,8 +110,7 @@ const config = {
annotation1,
annotation2,
annotation3,
annotation4,
annotation5
annotation4
}
}
}
Expand Down
106 changes: 100 additions & 6 deletions src/types/line.js
Expand Up @@ -108,15 +108,26 @@ export default class LineAnnotation extends Element {

draw(ctx) {
const {x, y, x2, y2, options} = this;

ctx.save();
if (!setBorderStyle(ctx, options)) {
// no border width, then line is not drawn
return ctx.restore();
}
setShadowStyle(ctx, options);
const angle = Math.atan2(y2 - y, x2 - x);
const length = Math.sqrt(Math.pow(x2 - x, 2) + Math.pow(y2 - y, 2));
const {startOpts, endOpts, startAdjust, endAdjust} = getArrowHeads(this);

setShadowStyle(ctx, this.options);
ctx.translate(x, y);
ctx.rotate(angle);
ctx.beginPath();
setBorderStyle(ctx, options);
ctx.moveTo(x, y);
ctx.lineTo(x2, y2);
ctx.moveTo(0 + startAdjust, 0);
ctx.lineTo(length - endAdjust, 0);
ctx.shadowColor = options.borderShadowColor;
ctx.stroke();

drawArrowHead(ctx, 0, startAdjust, startOpts);
drawArrowHead(ctx, length, -endAdjust, endOpts);
ctx.restore();
}

Expand Down Expand Up @@ -191,9 +202,34 @@ export default class LineAnnotation extends Element {
}

LineAnnotation.id = 'lineAnnotation';

const arrowHeadsDefaults = {
backgroundColor: undefined,
backgroundShadowColor: undefined,
borderColor: undefined,
borderDash: undefined,
borderDashOffset: undefined,
borderShadowColor: undefined,
borderWidth: undefined,
enabled: undefined,
fill: undefined,
length: undefined,
shadowBlur: undefined,
shadowOffsetX: undefined,
shadowOffsetY: undefined,
width: undefined
};

LineAnnotation.defaults = {
adjustScaleRange: true,
backgroundShadowColor: 'transparent',
arrowHeads: {
enabled: false,
end: Object.assign({}, arrowHeadsDefaults),
fill: false,
length: 12,
start: Object.assign({}, arrowHeadsDefaults),
width: 6
},
borderDash: [],
borderDashOffset: 0,
borderShadowColor: 'transparent',
Expand Down Expand Up @@ -250,6 +286,18 @@ LineAnnotation.defaults = {
yScaleID: 'y'
};

LineAnnotation.descriptors = {
arrowHeads: {
start: {
_fallback: true
},
end: {
_fallback: true
},
_fallback: true
}
};

LineAnnotation.defaultRoutes = {
borderColor: 'color'
};
Expand Down Expand Up @@ -369,3 +417,49 @@ function adjustLabelCoordinate(coordinate, labelSizes) {
}
return coordinate;
}

function getArrowHeads(line) {
const options = line.options;
const arrowStartOpts = options.arrowHeads && options.arrowHeads.start;
const arrowEndOpts = options.arrowHeads && options.arrowHeads.end;
return {
startOpts: arrowStartOpts,
endOpts: arrowEndOpts,
startAdjust: getLineAdjust(line, arrowStartOpts),
endAdjust: getLineAdjust(line, arrowEndOpts)
};
}

function getLineAdjust(line, arrowOpts) {
if (!arrowOpts || !arrowOpts.enabled) {
return 0;
}
const {length, width} = arrowOpts;
const adjust = line.options.borderWidth / 2;
const p1 = {x: length, y: width + adjust};
const p2 = {x: 0, y: adjust};
return Math.abs(interpolateX(0, p1, p2));
}

function drawArrowHead(ctx, offset, adjust, arrowOpts) {
if (!arrowOpts || !arrowOpts.enabled) {
return;
}
const {length, width, fill, backgroundColor, borderColor} = arrowOpts;
const arrowOffsetX = Math.abs(offset - length) + adjust;
ctx.beginPath();
setShadowStyle(ctx, arrowOpts);
setBorderStyle(ctx, arrowOpts);
ctx.moveTo(arrowOffsetX, -width);
ctx.lineTo(offset + adjust, 0);
ctx.lineTo(arrowOffsetX, width);
if (fill === true) {
ctx.fillStyle = backgroundColor || borderColor;
ctx.closePath();
ctx.fill();
ctx.shadowColor = 'transparent';
} else {
ctx.shadowColor = arrowOpts.borderShadowColor;
}
ctx.stroke();
}

0 comments on commit 44f961e

Please sign in to comment.