From d6d26ccc142032e3fc5d5fa0bd6d84412147df09 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Mon, 18 Feb 2019 15:28:35 +0200 Subject: [PATCH 01/13] allow configuration of borders as object --- docs/charts/bar.md | 11 +- src/elements/element.rectangle.js | 178 ++++++++--------- .../controller.bar/borderSkipped/value.js | 5 + .../controller.bar/borderSkipped/value.png | Bin 4889 -> 2058 bytes .../borderWidth/indexable-object.js | 56 ++++++ .../borderWidth/indexable-object.png | Bin 0 -> 1975 bytes .../controller.bar/borderWidth/object.js | 42 ++++ .../controller.bar/borderWidth/object.png | Bin 0 -> 2113 bytes .../borderWidth/scriptable-object.js | 54 ++++++ .../borderWidth/scriptable-object.png | Bin 0 -> 1601 bytes .../controller.bar/horizontal-borders.js | 42 ++++ .../controller.bar/horizontal-borders.png | Bin 0 -> 4132 bytes test/specs/element.rectangle.tests.js | 183 +----------------- 13 files changed, 304 insertions(+), 267 deletions(-) create mode 100644 test/fixtures/controller.bar/borderWidth/indexable-object.js create mode 100644 test/fixtures/controller.bar/borderWidth/indexable-object.png create mode 100644 test/fixtures/controller.bar/borderWidth/object.js create mode 100644 test/fixtures/controller.bar/borderWidth/object.png create mode 100644 test/fixtures/controller.bar/borderWidth/scriptable-object.js create mode 100644 test/fixtures/controller.bar/borderWidth/scriptable-object.png create mode 100644 test/fixtures/controller.bar/horizontal-borders.js create mode 100644 test/fixtures/controller.bar/horizontal-borders.png diff --git a/docs/charts/bar.md b/docs/charts/bar.md index 88e2744c713..a4bd86dd84f 100644 --- a/docs/charts/bar.md +++ b/docs/charts/bar.md @@ -71,7 +71,7 @@ the color of the bars is generally set this way. | [`backgroundColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`borderColor`](#styling) | [`Color`](../general/colors.md) | Yes | Yes | `'rgba(0, 0, 0, 0.1)'` | [`borderSkipped`](#borderskipped) | `string` | Yes | Yes | `'bottom'` -| [`borderWidth`](#styling) | `number` | Yes | Yes | `0` +| [`borderWidth`](#borderwidth) | number|object | Yes | Yes | `0` | [`data`](#data-structure) | `object[]` | - | - | **required** | [`hoverBackgroundColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined` | [`hoverBorderColor`](#interactions) | [`Color`](../general/colors.md) | - | Yes | `undefined` @@ -97,7 +97,7 @@ The style of each bar can be controlled with the following properties: | `backgroundColor` | The bar background color. | `borderColor` | The bar border color. | [`borderSkipped`](#borderskipped) | The edge to skip when drawing bar. -| `borderWidth` | The bar border width (in pixels). +| [`borderWidth`](#borderwidth) | The bar border width (in pixels). All these values, if `undefined`, fallback to the associated [`elements.rectangle.*`](../configuration/elements.md#rectangle-configuration) options. @@ -107,11 +107,18 @@ This setting is used to avoid drawing the bar stroke at the base of the fill. In general, this does not need to be changed except when creating chart types that derive from a bar chart. +**Note:** for negative bars in vertical chart, `top` and `bottom` are flipped. Same goes for `left` and `right` in horizontal chart. + Options are: * `'bottom'` * `'left'` * `'top'` * `'right'` +* `false` + +#### borderWidth + +If this value is a number, it is applied to all sides of the rectangle (left, top, right, bottom), except [`borderSkipped`](#borderskipped). If this value is an object, the `left` property defines the left border width. Similarly the `right`, `top` and `bottom` properties can also be specified. Omitted borders and [`borderSkipped`](#borderskipped) are skipped. ### Interactions diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index fe3702cda4e..eb769ceb6fa 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -2,8 +2,10 @@ var defaults = require('../core/core.defaults'); var Element = require('../core/core.element'); +var helpers = require('../helpers/index'); var defaultColor = defaults.global.defaultColor; +var valueOrDefault = helpers.valueOrDefault; defaults._set('global', { elements: { @@ -28,22 +30,20 @@ function isVertical(bar) { */ function getBarBounds(bar) { var vm = bar._view; - var x1, x2, y1, y2; + var x1, x2, y1, y2, half; if (isVertical(bar)) { - // vertical - var halfWidth = vm.width / 2; - x1 = vm.x - halfWidth; - x2 = vm.x + halfWidth; + half = vm.width / 2; + x1 = vm.x - half; + x2 = vm.x + half; y1 = Math.min(vm.y, vm.base); y2 = Math.max(vm.y, vm.base); } else { - // horizontal bar - var halfHeight = vm.height / 2; + half = vm.height / 2; x1 = Math.min(vm.x, vm.base); x2 = Math.max(vm.x, vm.base); - y1 = vm.y - halfHeight; - y2 = vm.y + halfHeight; + y1 = vm.y - half; + y2 = vm.y + half; } return { @@ -54,96 +54,96 @@ function getBarBounds(bar) { }; } +function parseBorderWidth(value, skipped, maxWidth, maxHeight) { + var t, r, b, l; + + if (helpers.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: Math.min(maxHeight, skipped === 'top' ? 0 : t), + right: Math.min(maxWidth, skipped === 'right' ? 0 : r), + bottom: Math.min(maxHeight, skipped === 'bottom' ? 0 : b), + left: Math.min(maxWidth, skipped === 'left' ? 0 : l) + }; +} + +function flip(orig, v1, v2) { + return orig === v1 ? v2 : orig === v2 ? v1 : orig; +} + +function parseBorderSkipped(bar) { + var vm = bar._view; + var vertical = isVertical(bar); + var borderSkipped = valueOrDefault(vm.borderSkipped, vertical ? 'bottom' : 'left'); + + if (vertical && vm.base < vm.y) { + borderSkipped = flip(borderSkipped, 'bottom', 'top'); + } + if (!vertical && vm.base > vm.x) { + borderSkipped = flip(borderSkipped, 'left', 'right'); + } + return borderSkipped; +} + module.exports = Element.extend({ draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - var left, right, top, bottom, signX, signY, borderSkipped; + var me = this; + var ctx = me._chart.ctx; + var vm = me._view; var borderWidth = vm.borderWidth; + var bounds = getBarBounds(me); + var left = bounds.left; + var top = bounds.top; + var right = bounds.right; + var bottom = bounds.bottom; + var width = right - left; + var height = bottom - top; + var inner, halfLine; - if (!vm.horizontal) { - // bar - left = vm.x - vm.width / 2; - right = vm.x + vm.width / 2; - top = vm.y; - bottom = vm.base; - signX = 1; - signY = bottom > top ? 1 : -1; - borderSkipped = vm.borderSkipped || 'bottom'; - } else { - // horizontal bar - left = vm.base; - right = vm.x; - top = vm.y - vm.height / 2; - bottom = vm.y + vm.height / 2; - signX = right > left ? 1 : -1; - signY = 1; - borderSkipped = vm.borderSkipped || 'left'; - } - - // Canvas doesn't allow us to stroke inside the width so we can - // adjust the sizes to fit if we're setting a stroke on the line - if (borderWidth) { - // borderWidth shold be less than bar width and bar height. - var barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); - borderWidth = borderWidth > barSize ? barSize : borderWidth; - var halfStroke = borderWidth / 2; - // Adjust borderWidth when bar top position is near vm.base(zero). - var borderLeft = left + (borderSkipped !== 'left' ? halfStroke * signX : 0); - var borderRight = right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); - var borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); - var borderBottom = bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); - // not become a vertical line? - if (borderLeft !== borderRight) { - top = borderTop; - bottom = borderBottom; - } - // not become a horizontal line? - if (borderTop !== borderBottom) { - left = borderLeft; - right = borderRight; - } - } - - ctx.beginPath(); ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = borderWidth; - - // Corner points, from bottom-left to bottom-right clockwise - // | 1 2 | - // | 0 3 | - var corners = [ - [left, bottom], - [left, top], - [right, top], - [right, bottom] - ]; - - // Find first (starting) corner with fallback to 'bottom' - var borders = ['bottom', 'left', 'top', 'right']; - var startCorner = borders.indexOf(borderSkipped, 0); - if (startCorner === -1) { - startCorner = 0; - } - function cornerAt(index) { - return corners[(startCorner + index) % 4]; + if (!borderWidth) { + ctx.fillRect(left, top, width, height); + return; } - // Draw rectangle from 'startCorner' - var corner = cornerAt(0); - ctx.moveTo(corner[0], corner[1]); + borderWidth = parseBorderWidth( + borderWidth, + parseBorderSkipped(me), + width / 2, + height / 2); + + inner = { + left: left + borderWidth.left, + top: top + borderWidth.top, + right: right - borderWidth.right, + bottom: bottom - borderWidth.bottom + }; - for (var i = 1; i < 4; i++) { - corner = cornerAt(i); - ctx.lineTo(corner[0], corner[1]); - } + ctx.fillRect(inner.left, inner.top, inner.right - inner.left, inner.bottom - inner.top); - ctx.fill(); - if (borderWidth) { - ctx.stroke(); - } + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = Math.max(borderWidth.left, borderWidth.top, borderWidth.right, borderWidth.bottom); + halfLine = ctx.lineWidth / 2; + + ctx.save(); + ctx.beginPath(); + ctx.rect(left, top, width, height); + ctx.clip(); + ctx.beginPath(); + ctx.rect(inner.left - halfLine, + inner.top - halfLine, + inner.right - inner.left + ctx.lineWidth, + inner.bottom - inner.top + ctx.lineWidth); + ctx.stroke(); + ctx.restore(); }, height: function() { diff --git a/test/fixtures/controller.bar/borderSkipped/value.js b/test/fixtures/controller.bar/borderSkipped/value.js index 139a2c68905..fed9592636b 100644 --- a/test/fixtures/controller.bar/borderSkipped/value.js +++ b/test/fixtures/controller.bar/borderSkipped/value.js @@ -22,6 +22,11 @@ module.exports = { { // option in element (fallback) data: [0, 5, -10, null], + }, + { + // option in dataset + data: [0, 5, -10, null], + borderSkipped: false } ] }, diff --git a/test/fixtures/controller.bar/borderSkipped/value.png b/test/fixtures/controller.bar/borderSkipped/value.png index 56ef05a41fe65a38ffee53a6b4a25964e0eee2e7..80c7df6be2f497c4820735534adcc51c9c239723 100644 GIT binary patch literal 2058 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@oFM7H-hE&XXd(AK_CQ`)p z;;aeD%a1XC*zoHAM+-5xmC5`k=0+@7QX6tNyrcSG)Q3&(bHiI5ewQ%=O~3~p)LZvG zK3zF)|Npl#$3Omg^{#lw@26$E@1Fbo=il~f{yEFH@A)|K^QU89VOdH%ghnPHDK zqXGlN4h15}1^pZizyJPwyZFAv-oF#`SsL0o8)oJ*G91uiN=S=lVA#;bFyp2e1H&36 zhO}+03=FF|Mx}>GuuS?_Xj^noiUE><_WvpN{Vw<8-1+xkt(gw&76c~m25upOB*Tsw zpKa`U+fHA%pKmGE@csAS;vL^VU;X~`=l0z|&gc2n{?7S->UQ7FJ6>2b_m}MX&wsuM z)^jt&bT9}oFvLtCf(+nlcx+L(ch~P^C5Ct3e{Xlc_d|~H!+~$x7c%_#_-W?x!aqU` z3^f{z0^5PfvzEo-4luQT5o9O?#?l3Mh9khV>>xiXIT{F~DPhE9hv&*n4;nw0?B1)% zaIYqg>4DApa_h#T=V9@g*NLu}2cHHHf)Gy=Q&Hx0Su6{1-oD!M< DiX%&` literal 4889 zcmeHLe^3*57Js{4=mvPjx)5>9LDQ#i#pklRzUaO^6Nw5|b5V%tz2?Cp< zpyvtpYz>t|&})kfsMwLh90>7%k+yPzs89kVskJ48A>oH4m_Y8k@&CKInRcdsB>!Z; zFQ0ku?fbs>c`xr+V!{T#ho1)k@HfWC{t*C2j~I~d=&S4Gzjp(8>o&&5ye&&M4!@E1 zo3DM?zUR&byduhI_5a7YzHOrJA$AkKuWL{Ji0@+m)iH^S+p9LJ>g~04-Fd-cab0-u z_noV))EAbQ9ZP721q`nc3}#JqF51jJ6XZjJ4SXW^UrK?0>wyg+P86>8qByyd??JNs zHPh84KWFW(QJK$3%iiEWd@jBTnZ4(w-;k-dVK0aPy zv)KkLQPIoG+GlO^k`=4$ANUqa%F4=eYx)g0&hty7XTrTYjHb&0Wj9pw1&)LEkLI)y zl?k~e9r`(g!SJm{qq#MgRX)=>9_IDpsW!tV4XrVPQRAH<8qyH8c!W z6uemOX%KY2Clwn+8Kw18U5VUqEKxRQ+ujG?}C1&i}rvhamNkZ_Icm_x|uIPRsPTVF*R`kxB z>A^Sycg`fWtYmamIIiBj%9{dQ+NYBp1*Hhb>7FfjhK>RSjUo3kd6ZcQYbm6iV7h5J z55wztR!?=>Ory{Tc{59BUd50Oum7PV3vMz;S)?|{=)*59A*uLa z8g6foyLf_&N%~IXX}}!rYOlk{7Dk1~C$DGKY4`%(;FoVa;>N;-SFh)Yoi%~7iG?Il z#PT49KEXo#{$-8pf=~c19&3CLC+z;NfVcwkE6$Mjv26X`Hm?rz_~hi|#DU29X7i3h z1xL0T?TY13xb;!d!OW5rncOs8Qd08MNT$UozI8UCrlmt~w$IGWJTMxKKZRC4IOtnk zAyzaEc+fPecECM?09R_YLo1S-YMWw4@SY{uFCE6CYYObMb zuj5x8;ZfugRQG??jYm~fzJ|&_wIdaAQ&b!Shdk09YdDT`Je#{7 z&qo~BX0r4rgd$@#e3ei@_r@|HO8JUZwh=@%JWM~|B=^G_dh&A9a3K%b&+Y?x#1T}m zf8wu;sXoo|4aZ?x0R=yP2+Hp`^0>L&Z}DeHV7Sy-O#UE5%tt#u9@3Yy;47GV3Bz}D zAnUt#&@Ycny*yIHL8|9^-woT2nCFxtP`TiS?tZNB8(Z`_7;}_Xo@AQUNw8fa(GF|1 z?QKowNtwM-YjQl6z9lJ6u|Bd|Wx0`+1$ILoYIAeh5yLwa&G#p*UusS51?IDw6yM}g zn_aKh%bPU$Hfb5x?JT)I4$HjFQc+h|*B&jq&h@+FLbEOvb??;6qt|(>kXO@)y0$Bt z1fUv|&jw?dG>W9kS0kcUKRh?dL}J*%IhQ-ah7a9tx%SojXfrYSAkt5H3;kd3-{o9lY}>#<{r$`>_~&RcygDx678dCN zXlqm2vW54t55ip%bIXZ~2hj>4a$@Vs#BB_0Zi#YkO75Y#e7yO+Pnf+J+KN0z8!lz52WZ^)Hvj+t diff --git a/test/fixtures/controller.bar/borderWidth/indexable-object.js b/test/fixtures/controller.bar/borderWidth/indexable-object.js new file mode 100644 index 00000000000..97f9af5d46f --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/indexable-object.js @@ -0,0 +1,56 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderWidth: [ + {}, + {bottom: 1, left: 1, top: 1, right: 1}, + {bottom: 1, left: 2, top: 1, right: 2}, + {bottom: 1, left: 3, top: 1, right: 3}, + {bottom: 1, left: 4, top: 1, right: 4}, + {bottom: 1, left: 5, top: 1, right: 5} + ] + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#80808080', + borderSkipped: false, + borderWidth: [ + {bottom: 1, left: 5, top: 1, right: 5}, + {bottom: 1, left: 4, top: 1, right: 4}, + {bottom: 1, left: 3, top: 1, right: 3}, + {bottom: 1, left: 2, top: 1, right: 2}, + {bottom: 1, left: 1, top: 1, right: 1}, + {} + ] + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/indexable-object.png b/test/fixtures/controller.bar/borderWidth/indexable-object.png new file mode 100644 index 0000000000000000000000000000000000000000..693806a18da69eb54ecc5c03f5fd37aaafb404fc GIT binary patch literal 1975 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o>pWc?Ln`LHy=mAl%_`9L z(4-_}iHZ=fr?2Q!^$8&>mANw3Jrj{izW*+#^V9Oo9Y?Gzl$Lz7I{e+{YE|r+ir6>V z?n(-;I87+8dWPHS*ra$sN(@L=EoI+PRWP6q`>1qKEV6$Tat zhDMeKpyCOmib)BLeHEc!*Q~O;Qd(2I?RfXzwtIK){{8jf%IRm>TEH+pFn#T+xX`Qp z;WK^*ht}5r|5ajTTVD`dSHAM*=}mjY82$p4C@?uNln62~G;=U8UT|e#;89{=aS&%@ zFz5irMC_=7;TjGhS2vf&+FvQPkzxR*h5Lp5-7E(-=^U4}KfKqSIU#ad>2ce-cYBsG zFqo`dHU0V3Z91pl_D%fpk^R8Kf{I=1UT=H+rg-`<_796?>v{J4{nN&9Wc7ab1FyNy zRh8Sl1|~E6>i4d(A2#0K|K`iGGLD8_2Ob~z_2q^?1H*^=z=SrM@o7;E?q+q^oqId) z|HCZ|3=ckgF>GOHV5s>xTC6HCD$uBsS+J@q)cWSOx8F80Sjh1CUoXA;?bg$KyVljq h^dHY(|5J9!Z{ckRR*M98+e9(|fv2mV%Q~loCIC19ei8rx literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/object.js b/test/fixtures/controller.bar/borderWidth/object.js new file mode 100644 index 00000000000..9133b30aec5 --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/object.js @@ -0,0 +1,42 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderWidth: {bottom: 1, left: 2, top: 3, right: 4} + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#888', + borderSkipped: false, + borderWidth: {bottom: 4, left: 3, top: 2, right: 1} + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/object.png b/test/fixtures/controller.bar/borderWidth/object.png new file mode 100644 index 0000000000000000000000000000000000000000..04576006af463429104cd7595d23318d113cf6cd GIT binary patch literal 2113 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is>yI6Pe(Ln`LHy|pnjHC&`M z@#wk(XCnT@ zzVF|&pJ9b61B2KPj)qtL5BTSO)nmxwWMKFw&Zxk!ij$#%m8F4Ug&I(S00Yy4Squyj zz^H1-Vq!>O991xM!lAK~@xzPBLJa(Nz?d@v#vBVIqXDS}{R}%^9R6Li{qb_{hVFNt zzSea!Ffcrb&)a_c`Rb&vWxH#4-TwIUknDWvd#^+78SbgSV@QyIn6RLevEba#Z!3P! zFW$F=#o?=L{gXMLbr=}heseSwpPT+JeQxo-rK}EL@Bh8`{l7IZmHqH%X<&G9Lj|Zr z7?_-rCj+y-A22=j`2w9~0W{Rg44C8|jw%}NLEzxWVDpIs64k&$2~^O7DTjQ9n7p#~W!Hm(3=AoPh`RKJGJ~EKQ^WFlhNn_zA9%NJ23ETa N44$rjF6*2UngBaj#$^Bi literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/borderWidth/scriptable-object.js b/test/fixtures/controller.bar/borderWidth/scriptable-object.js new file mode 100644 index 00000000000..5bd2903efa7 --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/scriptable-object.js @@ -0,0 +1,54 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + borderWidth: function(ctx) { + var value = ctx.dataset.data[ctx.dataIndex] || 0; + return {top: Math.abs(value)}; + } + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5] + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: 'transparent', + borderColor: '#80808080', + borderSkipped: false, + borderWidth: function(ctx) { + return {left: ctx.dataIndex * 2}; + } + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [ + { + display: false, + ticks: { + beginAtZero: true + } + } + ] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/scriptable-object.png b/test/fixtures/controller.bar/borderWidth/scriptable-object.png new file mode 100644 index 0000000000000000000000000000000000000000..9fa5d45a772b1e1f81f37c43ee09789ae7cb1d4f GIT binary patch literal 1601 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@oI6Pe(Ln`LHy}q$GCS1TJ z(CX8SV_Gc79HSWxIFfq!V*6Mo9N{S0wqfbY;xpg>u}kIN{r*yT&xGB-YviB5*56$7 z|Kq3I+w<#duEa4sSUQ)1p}|(2fg#~MCj-NcVxVo`=3o_Ku$u%FcLXY61FDoz0cx2r z`~(`7zk7GOcEa(;AHThq=|BGX?fl34?TX~rKVxBFI6sG>A`=)?70*}>cv~|v957aA zh`R+0jCq!fA1?VZFkJ9qNa0{$SjE}E!^q%Z$~ZxhfnkLjgAq{gRThUf28IQ*7)}T< zFoXy*NHQ@rWHBi?GcW}Bj%uQAKrs9`dAD{a|ECI@dv!A&pTA%8Z-1r4ox9I}eEN6$ zdVJc?f4k-1+3+wt14d^#Fgi2JnI7B)W`hNHIUCB$f$>tN&T#K8Fj{ZTB@o{q&TuwN uF9YgGqpk-=(+rNxGyKaR;tSWZzr4By`O^H73vGc_1B0ilpUXO@geCx8n^b53 literal 0 HcmV?d00001 diff --git a/test/fixtures/controller.bar/horizontal-borders.js b/test/fixtures/controller.bar/horizontal-borders.js new file mode 100644 index 00000000000..c1f07126d0c --- /dev/null +++ b/test/fixtures/controller.bar/horizontal-borders.js @@ -0,0 +1,42 @@ +module.exports = { + threshold: 0.01, + config: { + type: 'horizontalBar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: 2 + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + borderSkipped: false, + } + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: '#AAAAAA80', + borderColor: '#80808080', + borderWidth: {bottom: 6, left: 15, top: 6, right: 15} + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/horizontal-borders.png b/test/fixtures/controller.bar/horizontal-borders.png new file mode 100644 index 0000000000000000000000000000000000000000..54c896d61b86abb9ad50c6d4e32490f08d73c513 GIT binary patch literal 4132 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 literal 0 HcmV?d00001 diff --git a/test/specs/element.rectangle.tests.js b/test/specs/element.rectangle.tests.js index d8d08476f49..d6932359ed9 100644 --- a/test/specs/element.rectangle.tests.js +++ b/test/specs/element.rectangle.tests.js @@ -1,7 +1,7 @@ // Test the rectangle element describe('Rectangle element tests', function() { - it ('Should be constructed', function() { + it('Should be constructed', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -12,7 +12,7 @@ describe('Rectangle element tests', function() { expect(rectangle._index).toBe(1); }); - it ('Should correctly identify as in range', function() { + it('Should correctly identify as in range', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -61,7 +61,7 @@ describe('Rectangle element tests', function() { expect(negativeRectangle.inRange(10, -5)).toBe(true); }); - it ('should get the correct height', function() { + it('should get the correct height', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -93,7 +93,7 @@ describe('Rectangle element tests', function() { expect(negativeRectangle.height()).toBe(5); }); - it ('should get the correct tooltip position', function() { + it('should get the correct tooltip position', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -132,7 +132,7 @@ describe('Rectangle element tests', function() { }); }); - it ('should get the correct vertical area', function() { + it('should get the correct vertical area', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -149,7 +149,7 @@ describe('Rectangle element tests', function() { expect(rectangle.getArea()).toEqual(60); }); - it ('should get the correct horizontal area', function() { + it('should get the correct horizontal area', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -166,7 +166,7 @@ describe('Rectangle element tests', function() { expect(rectangle.getArea()).toEqual(40); }); - it ('should get the center', function() { + it('should get the center', function() { var rectangle = new Chart.elements.Rectangle({ _datasetIndex: 2, _index: 1 @@ -182,173 +182,4 @@ describe('Rectangle element tests', function() { expect(rectangle.getCenterPoint()).toEqual({x: 10, y: 7.5}); }); - - it ('should draw correctly', function() { - var mockContext = window.createMockContext(); - var rectangle = new Chart.elements.Rectangle({ - _datasetIndex: 2, - _index: 1, - _chart: { - ctx: mockContext, - } - }); - - // Attach a view object as if we were the controller - rectangle._view = { - backgroundColor: 'rgb(255, 0, 0)', - base: 0, - borderColor: 'rgb(0, 0, 255)', - borderWidth: 1, - ctx: mockContext, - width: 4, - x: 10, - y: 15, - }; - - rectangle.draw(); - - expect(mockContext.getCalls()).toEqual([{ - name: 'beginPath', - args: [], - }, { - name: 'setFillStyle', - args: ['rgb(255, 0, 0)'] - }, { - name: 'setStrokeStyle', - args: ['rgb(0, 0, 255)'], - }, { - name: 'setLineWidth', - args: [1] - }, { - name: 'moveTo', - args: [8.5, 0] - }, { - name: 'lineTo', - args: [8.5, 14.5] // This is a minus bar. Not 15.5 - }, { - name: 'lineTo', - args: [11.5, 14.5] - }, { - name: 'lineTo', - args: [11.5, 0] - }, { - name: 'fill', - args: [], - }, { - name: 'stroke', - args: [] - }]); - }); - - it ('should draw correctly with no stroke', function() { - var mockContext = window.createMockContext(); - var rectangle = new Chart.elements.Rectangle({ - _datasetIndex: 2, - _index: 1, - _chart: { - ctx: mockContext, - } - }); - - // Attach a view object as if we were the controller - rectangle._view = { - backgroundColor: 'rgb(255, 0, 0)', - base: 0, - borderColor: 'rgb(0, 0, 255)', - ctx: mockContext, - width: 4, - x: 10, - y: 15, - }; - - rectangle.draw(); - - expect(mockContext.getCalls()).toEqual([{ - name: 'beginPath', - args: [], - }, { - name: 'setFillStyle', - args: ['rgb(255, 0, 0)'] - }, { - name: 'setStrokeStyle', - args: ['rgb(0, 0, 255)'], - }, { - name: 'setLineWidth', - args: [undefined] - }, { - name: 'moveTo', - args: [8, 0] - }, { - name: 'lineTo', - args: [8, 15] - }, { - name: 'lineTo', - args: [12, 15] - }, { - name: 'lineTo', - args: [12, 0] - }, { - name: 'fill', - args: [], - }]); - }); - - function testBorderSkipped(borderSkipped, expectedDrawCalls) { - var mockContext = window.createMockContext(); - var rectangle = new Chart.elements.Rectangle({ - _chart: {ctx: mockContext} - }); - - // Attach a view object as if we were the controller - rectangle._view = { - borderSkipped: borderSkipped, // set tested 'borderSkipped' parameter - ctx: mockContext, - base: 0, - width: 4, - x: 10, - y: 15, - }; - - rectangle.draw(); - - var drawCalls = rectangle._view.ctx.getCalls().splice(4, 4); - expect(drawCalls).toEqual(expectedDrawCalls); - } - - it ('should draw correctly respecting "borderSkipped" == "bottom"', function() { - testBorderSkipped ('bottom', [ - {name: 'moveTo', args: [8, 0]}, - {name: 'lineTo', args: [8, 15]}, - {name: 'lineTo', args: [12, 15]}, - {name: 'lineTo', args: [12, 0]}, - ]); - }); - - it ('should draw correctly respecting "borderSkipped" == "left"', function() { - testBorderSkipped ('left', [ - {name: 'moveTo', args: [8, 15]}, - {name: 'lineTo', args: [12, 15]}, - {name: 'lineTo', args: [12, 0]}, - {name: 'lineTo', args: [8, 0]}, - ]); - }); - - it ('should draw correctly respecting "borderSkipped" == "top"', function() { - testBorderSkipped ('top', [ - {name: 'moveTo', args: [12, 15]}, - {name: 'lineTo', args: [12, 0]}, - {name: 'lineTo', args: [8, 0]}, - {name: 'lineTo', args: [8, 15]}, - ]); - }); - - it ('should draw correctly respecting "borderSkipped" == "right"', function() { - testBorderSkipped ('right', [ - {name: 'moveTo', args: [12, 0]}, - {name: 'lineTo', args: [8, 0]}, - {name: 'lineTo', args: [8, 15]}, - {name: 'lineTo', args: [12, 15]}, - ]); - }); - }); From 2096f54a0e7aa62d0070b7501d0cab4b273ca93c Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Mon, 18 Feb 2019 22:35:28 +0200 Subject: [PATCH 02/13] fix artifacts --- src/elements/element.rectangle.js | 50 ++++++++++++++---- .../controller.bar/borderSkipped/value.png | Bin 2058 -> 2167 bytes .../borderWidth/indexable-object.png | Bin 1975 -> 2086 bytes .../controller.bar/borderWidth/indexable.png | Bin 4969 -> 2016 bytes .../controller.bar/borderWidth/object.png | Bin 2113 -> 2269 bytes .../controller.bar/borderWidth/value.png | Bin 5109 -> 2222 bytes .../controller.bar/horizontal-borders.png | Bin 4132 -> 1640 bytes 7 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index eb769ceb6fa..2921206acf1 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -105,7 +105,7 @@ module.exports = Element.extend({ var bottom = bounds.bottom; var width = right - left; var height = bottom - top; - var inner, halfLine; + var inner, maxLine, halfLine; ctx.fillStyle = vm.backgroundColor; @@ -120,28 +120,58 @@ module.exports = Element.extend({ width / 2, height / 2); + maxLine = Math.max(borderWidth.left, borderWidth.top, borderWidth.right, borderWidth.bottom); + + if (!maxLine) { + ctx.fillRect(left, top, width, height); + return; + } + inner = { left: left + borderWidth.left, top: top + borderWidth.top, - right: right - borderWidth.right, - bottom: bottom - borderWidth.bottom + width: width - borderWidth.left - borderWidth.right, + height: height - borderWidth.bottom - borderWidth.top }; - ctx.fillRect(inner.left, inner.top, inner.right - inner.left, inner.bottom - inner.top); + ctx.fillRect(inner.left, inner.top, inner.width, inner.height); ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = Math.max(borderWidth.left, borderWidth.top, borderWidth.right, borderWidth.bottom); - halfLine = ctx.lineWidth / 2; + + // add 1 to max border width, so border is actually 0.5px wider + // to hide artifacts + ctx.lineWidth = maxLine + 1; + + // move edges 1px where there is no border, to prevent artifacts + if (!borderWidth.left) { + inner.left -= 1; + inner.width += 1; + } + if (!borderWidth.right) { + inner.width += 1; + } + if (!borderWidth.top) { + inner.top -= 1; + inner.height += 1; + } + if (!borderWidth.bottom) { + inner.height += 1; + } + + halfLine = maxLine / 2; + inner = { + left: inner.left - halfLine, + top: inner.top - halfLine, + width: inner.width + maxLine, + height: inner.height + maxLine + }; ctx.save(); ctx.beginPath(); ctx.rect(left, top, width, height); ctx.clip(); ctx.beginPath(); - ctx.rect(inner.left - halfLine, - inner.top - halfLine, - inner.right - inner.left + ctx.lineWidth, - inner.bottom - inner.top + ctx.lineWidth); + ctx.rect(inner.left, inner.top, inner.width, inner.height); ctx.stroke(); ctx.restore(); }, diff --git a/test/fixtures/controller.bar/borderSkipped/value.png b/test/fixtures/controller.bar/borderSkipped/value.png index 80c7df6be2f497c4820735534adcc51c9c239723..149a81da35d5b884846be15d16de9486ce7861c8 100644 GIT binary patch literal 2167 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is>y>^xl@Ln`LHy{6bF<0;W{ z(W>)hQE_o{@%hkeD~z)B43DHN_$Uy6AbrEk{*B&yOcuM{UOa8ly~%agvQpcnYHou} z!UqogH+x=V{`^mR>G8svTi^dW|GM+|@#EjQx3?J{+y3RIvH7uzo7-}&`yQ{4uDL(| z{-^8n&G+4T(R+Ve?rk}F`TO@@^%kEi|Nh&Zp@5Ugfq|jGkr?tjM}y(9^@hi$mlU6C z&$Sk3NbXq=MCr^4=YAGE<4x{~H|J?sZuxvO1H*%N??f2x$A3OHnejtm&&Q8B40BA6 z{is{baO3;m!-X>QSr{0~)fuh+!9kUAgBknGZwsx6J%JP*Yj~xZoV)uK}?t4`_YuK;2SVz^Sv23 z7#Q?C2$K(zSQap_JZNHR0Hz$GNI$j%pXYpj*~a*xy62xRd!Z)P-jUSsjI?LN4mygcV~B(N4O zFXw6q23Nhsz#{M;H?X=q-FxrHEC!2eV6C=Yl>u13h^r7JW&g3B*sOogTjnk|0}yz+ L`njxgN@xNAGuuS?_Xj^noiUE><_WvpN{Vw<8-1+xkt(gw&76c~m25upOB*Tsw zpKa`U+fHA%pKmGE@csAS;vL^VU;X~`=l0z|&gc2n{?7S->UQ7FJ6>2b_m}MX&wsuM z)^jt&bT9}oFvLtCf(+nlcx+L(ch~P^C5Ct3e{Xlc_d|~H!+~$x7c%_#_-W?x!aqU` z3^f{z0^5PfvzEo-4luQT5o9O?#?l3Mh9khV>>xiXIT{F~DPhE9hv&*n4;nw0?B1)% zaIYqg>4DApa_h#T=V9@g*NLu}2cHHHf)Gy=Q&Hx0Su6{1-oD!M< DiX%&` diff --git a/test/fixtures/controller.bar/borderWidth/indexable-object.png b/test/fixtures/controller.bar/borderWidth/indexable-object.png index 693806a18da69eb54ecc5c03f5fd37aaafb404fc..e36eaaed7259b3256fb5b8a4cdae171cb82859f5 100644 GIT binary patch literal 2086 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o-+H<@hE&XXduwA>bfie@ z!w};qqD~x@?L13D7aJ|Ga{VyjmdY!p!?ttW=O1G_s(f56=k99T*S{Z>G;J=C{yXXK zE73pwckbW+Z@InL;=a+bAL-qK$vykGSN&ty(CN^)+hc!T{>r&!2hfF_KnFS~Fe)%GaHue_FfcT-G%zqI zOkfaTU|^lWo5%coj{1{6HyZD#3 zHg@)Y=ik(vUd-^L>MU<^PkwqiCqqMYnHWQKg&sp#;Zuf)+mjhLs4+4y6fB;W`g4P; zf?Y*9D7kJ2C)W_W%a+gOqxVk#`GT8)A*Mu=;h4nl$p6n5`itAG`6tZ4zg~OqZ}HjR ziq}aolplTn@Jqe@b6~-7ezyq2@y8!azuFd`GY6{F`^)rU*{^rYW%`d_*JeMk?#Dc9 zMur3Vf}>>$Fh5hNgl!0Aevo%=r?$;~RtARst_*9W7_xx{w~ar;6?O)OJ2s5KQe(7O zr+OtfohhMb SX2ahMK;Y@>=d#Wzp$P!IpWc?Ln`LHy=mAl%_`9L z(4-_}iHZ=fr?2Q!^$8&>mANw3Jrj{izW*+#^V9Oo9Y?Gzl$Lz7I{e+{YE|r+ir6>V z?n(-;I87+8dWPHS*ra$sN(@L=EoI+PRWP6q`>1qKEV6$Tat zhDMeKpyCOmib)BLeHEc!*Q~O;Qd(2I?RfXzwtIK){{8jf%IRm>TEH+pFn#T+xX`Qp z;WK^*ht}5r|5ajTTVD`dSHAM*=}mjY82$p4C@?uNln62~G;=U8UT|e#;89{=aS&%@ zFz5irMC_=7;TjGhS2vf&+FvQPkzxR*h5Lp5-7E(-=^U4}KfKqSIU#ad>2ce-cYBsG zFqo`dHU0V3Z91pl_D%fpk^R8Kf{I=1UT=H+rg-`<_796?>v{J4{nN&9Wc7ab1FyNy zRh8Sl1|~E6>i4d(A2#0K|K`iGGLD8_2Ob~z_2q^?1H*^=z=SrM@o7;E?q+q^oqId) z|HCZ|3=ckgF>GOHV5s>xTC6HCD$uBsS+J@q)cWSOx8F80Sjh1CUoXA;?bg$KyVljq h^dHY(|5J9!Z{ckRR*M98+e9(|fv2mV%Q~loCIC19ei8rx diff --git a/test/fixtures/controller.bar/borderWidth/indexable.png b/test/fixtures/controller.bar/borderWidth/indexable.png index 30011d3b722e474f7ae790f8a1c477f3679195b8..550f6f25c025d3aa1acd96a868e87e74f8e5e059 100644 GIT binary patch literal 2016 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@omwUQ6hE&XXd(*xvI8fqP zqPT+C=4qNN(&oxxo7wWG7UxZ7z41axht;(}dKas7c5YgNMB1O@)9?KLX~#cDOXaz$ zZ{0(?Dwh8fQzPe{v8>N2xp(KD(Xk)T_u>IKfor$3#g2{n_LBNB71L$Z@ zpqm{O7!?>8I8+!|7#JE^8i0x?fXx*G`l-Qz$zfD6D3q~=#)j(RbE~861=4L_@3Px_ zbK|<#m)Dk^v0SgUKbTR0fuZcZ8Urw9B+o}P26!_tdasnsG2R=#|MaocAx>&tP#AB;N{*kXC6H1_yCqbco~u z-4n~fz!HYls^*5I#a**7~0({K0o{Ze&L1+V4@l=5OC#jqKd~k z`V7zJ$X_e14J~~(C!Upo;lB&R98-pKg}^d(zdDYRb%7lNL&A5KhS3sNf#UjS7bpT= zOC+zfDL!`{SQnQ)YGklDEy%#IL68w-`j+hsGm7`EtNZhz`?%0*V2EGhV@~K+W-$9A YJhw~C@>s$0j|@QI>FVdQ&MBb@08S8pumAu6 literal 4969 zcmeHKX;4#H7QXNKkQW>@Aqvwd5CH?D0yeE6%JSMq(U#2~TtJqTc8~Nhtb(8MRvib+6CY!|>Nh$yJ+O2n`)nh-*u@1yb8)Ktx%shX-Gf0BAR=eyro zzH{D9_4W49*E7}w0O)Vq>i!J?DEx^4d_MeokXd&C0Q#D3?qB*P1P{D=l4h~{*|8nx zRs=jU{O69$z)Yi{Sb;yYCT&M}_X$J$ADpazr`H%-s2R1g?Rb3Z3t2|-ty=o&nu3om zCX*S`bj-V;ajYw9+~iGE3mJsKmjH|RRr&?9 z(vabl->XwHRTQ>~xjI!e55<~RGCpMZQApkG>wVO;R?#U{yzI-tSL<{==2}$vN;S{pNNbW)vhVN_}EhvXgYH7`&`8FLQv{H&aDl{X31=kQQS`LKiv zI0q!M5L0hG>d$0T0xtYcwAR&>!e0<_+LmqC^RlLo{$ za%g*UAh#Cx*7_h6g@0BMQ(1%sf;E4>bs`#KoG#v%mYkU4t9iLpTw4Pk5KM%?RQ0*ts< zp*llpwPb*ZePbSBFncJgq)KrIFZP1m!p#SJY{^;<1~8I61*f?nbZ!4ax1A7aVd9{p zK_Qh2mYgUpHzsX%T|X7^H|ZlCO!`Q-TUknnP@R=&BjG3qGU#|UG|FE9kTTf=Wj^i+ z{ph|y_GGj~gyy^_YcGnj_Kw8}=aERxmKYw$WB}WPqa|r%yOo{Z8JB~-K~aV)>gf9i z(o60tHsra$w7K{QyCUfT6?DDU)M60^PzdGf{0cqNt*I?G1Z#pwO%62GK|Wc_c$-W& z=F_cECB}!2=dFZD$6ZuQDSkN4)!W>>CylhZ;=jdl6H5*$$>+r+Hd7!}RNKRG2i_gn zltcaTYMBlcihQ4D2gZbcltSBVGFpa^CN$v_mbpPfU5z^05>R!z!LsLz;ktu;!Ay@WmAZl|7@m>5&Dr}`8MMZxyz%Wo28 z*Cwy8mf2Om9T`&fac-?$RmvGySJyK(Hj==uQV!iwabV4g4%Not{Xqo4tk@eaB)KeG zC`q20R0}=YAM))&rUK8&Gct^>0+G*j?1tOAj|;Bue;3yHoqv09aB%L>MtOCzHhR%e zM^SoFwrb!thIhj#>ZY_I?Wi;vv3^A$zQ4RT#{ytZIz>8!Rbzpf6ubf66s(tGik6M{~|wB7Zhf{%8q5Z_2+w)-+KuGAet*3jZ( zzejwhuNi=km-Ahe(6j|s)o#3AZg^v2J=IKm=b&(6QQZ`oJ0ENl^6z-A622;&> zzzc`^>}nYTim$?A1^O_~~{V*+hErQZv$GNvQT!Pu0i6CA1vIoQe z@<&*~9_B2mwc{!4e+0Z#s08!D_s>2>;E~vW5N(>9JAzo8l+>qA zs%yTyL?Wj(Q$dFBq4@}k-GOftvm>1P^ByV^b>Siq)yaeuD#Mq2Wg=?L+oEYmly0uu z3W6&wnR)lA+_*E@3KUre9R~=yo80124=O#?MIh^!-isJc9WN&rq~d@(_{pl1jbZ`h z#?B08Bbm9}F$y(wX3HNL(IJkYbykGn(v0;3BcPzH&jY-3^}|aNumv#ddWw%cvHrzVIn%ybAZasy@$c zT3M4gKDrq=Kht#78T^Oqvg{m4;Bd5Rw(IoA4<9}>TIiOqmbt-;UyQTqbW*ptZbAa@ ZVSYys`@d?L^aJp-ZL_y~=_c0EUjgZ(L^ diff --git a/test/fixtures/controller.bar/borderWidth/object.png b/test/fixtures/controller.bar/borderWidth/object.png index 04576006af463429104cd7595d23318d113cf6cd..7dfda348aaab4e81ecd9bd69d8ca9c7c8f44218d 100644 GIT binary patch literal 2269 zcmeH|Ye-XJ9LArs9pz}QmP;*>TxAv!XePDN+%iKKO)V8kQdbm`S{X)LI`tG;NEfw> z-Bb+3q*An47i;a5-AFLVGQqUVhzMOX#Ev+(^&X49^{o#DzF*$=!+ZI^&-459lqJT; zN<95N0Z7zwF&hC9SyEu!$@I9QGYeqq)iJA+_0`tiCE1&^W|nSgdXVD#ac;$vBHxqG zb6OU;Tq&2_8e9E7gh^#X%W^{=93QAxFJac7tIHp$9F80^URav_HA0-9FLuq->2yk~ z$yLx$sJpYhX~eN&-!-Gt(Cktoatb8}7KSSj)nwe&b&Gc{1{#MKBI;1!&3DZVYdOZ7 zd1pG1_;~}ys|-yh-j4#r45?N%XX0=WXrPp4fw~Gvff?T9H*B;>MGnP*0X#h>H~(1; zPG4wyQ$AaMxo=N^GIMZrOBhE177=exq41x!ypRM#37q?Z)n%W3+19Dd61K zFg%>r%cr_?S5Nhup&!$Tpl$0w-2Kk9fiut-5EooAU|24gLTC-K=76wcB_)UGA`NaY zsfk|Z0rt!9H;7Tvo*29dl)4Xj@PTBQ|Ui=Vvcrw{+IMi>22XwyC2b5EVQ35K+s zzJ}d4$0opQxdNT{s-UnE+w+L+1gz+@l+<~}k-h^4rlilQ0rHF;P}tXr-Ve$EjQ8Xh zLvR0X4e^UGV2A>HgY>+dbhBhCdqh^;ToCTYr43kSDa9E%MLt0LDo=LaXiL%UV4Se% z&llTxo)39RfSPnlCJKSqp6^YI$3Y=Mngg_v)b~17P zM*b#=wKhjfcblfa-%cVt**^eTwItAwPRQHl#hUo(<86-B04FUMy?-UcTQbHacN@zx S=BI0iL56y5d`xq+_TUdv%Neu) literal 2113 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is>yI6Pe(Ln`LHy|pnjHC&`M z@#wk(XCnT@ zzVF|&pJ9b61B2KPj)qtL5BTSO)nmxwWMKFw&Zxk!ij$#%m8F4Ug&I(S00Yy4Squyj zz^H1-Vq!>O991xM!lAK~@xzPBLJa(Nz?d@v#vBVIqXDS}{R}%^9R6Li{qb_{hVFNt zzSea!Ffcrb&)a_c`Rb&vWxH#4-TwIUknDWvd#^+78SbgSV@QyIn6RLevEba#Z!3P! zFW$F=#o?=L{gXMLbr=}heseSwpPT+JeQxo-rK}EL@Bh8`{l7IZmHqH%X<&G9Lj|Zr z7?_-rCj+y-A22=j`2w9~0W{Rg44C8|jw%}NLEzxWVDpIs64k&$2~^O7DTjQ9n7p#~W!Hm(3=AoPh`RKJGJ~EKQ^WFlhNn_zA9%NJ23ETa N44$rjF6*2UngBaj#$^Bi diff --git a/test/fixtures/controller.bar/borderWidth/value.png b/test/fixtures/controller.bar/borderWidth/value.png index 3c81aff7f2dfb863751bc423a7c8db002e959871..64d072874f9b9dab862dd1f7741875eb5b7de181 100644 GIT binary patch literal 2222 zcmeH|T}YEr9LAsb-PBjjoEpx|bVTSyn393ZmQIf=ErRrHBcky9g&%Iyd|%{8)|oy{+>G-Fefc;JG;Ga5((WInVRsJas4K zd0`>3AppV(@@1z0noKD$fn?}64xI;>iUL`7k-B+)^w^!NHmbHX*&?={-NiJj(t7WW z*bUR;O`qw;Gk42X##Hs5k?3ae+vnl8va_O#6qTLkAo-z>Us@Y<3jNaVjfBqcm-Lch zcUE|YUZXWR#Xj$CZQQ`G<-8gnTmo7Oq|Pd_;Sn3Y_6%#x{z$+jU8L=bgx4DLjDp-P zes+*&PpDiAaJ0~1FDT++d$QzD1Rf@W58ULlK#36oU`Hf*3!DA7Xg(MqFqk~dBBDe< z%>M1oo2sF%n$hUi7eXC^9ERU~dm* zyMiov2_zKCqCgPgB4ZU`+mg1^VbgT@zhJed1z9xaiM<1tkDJ|+6e4qXQaD?mK~g!n xjAxe&AMiSSkAy3gT&&M2Q9Vmca*2o$k}*BiB9B^A4QgXRT0yQ{_A;mB#vkdQ9*+P3 literal 5109 zcmeHLX;c$g7JgM$>?DXLCZK{q2nq-;(8{JlN(IrDCCb_^ENY8(qoAN{0+kU36bBd^ zp^+tXP*6}on^q8k46?aE?1K!*8f6Cw$Sx2vuh7#!=kJ^|hx|#-SMPoA-tR8CucG;O z){1l$Isia%?;gto0HE+E0;r4OS4CW{7XYm1do6c32D%Nt-+$(K(F4lcl8&Nb;uAA) zyP(SJyQLntS(2T%ZH?}HeAT1>rS~0oU@*1Ty>vEGw21b+q{KotJwD*B=H4fh$@;pw zx&}w34s97P{*{VRTQr9o)N=ba(8@78TAM?gR%SOFL0J?w5$zBz8JbmkuA!mfBNmI( z#DHc>Dto30BkC#lzO5`x+3@epQTdXxzVmpCF6$-9?fE*oeRFyU}@3Q!yf4zAhKqxq3>Y-SS z3iL5HpEm3mqNqgqQJa_iStx%xbMlI!M>3MDg^$YQn%4s zbcLYB)M#inmfR=l*+JkoI1*)Mc0QV1bNF$#+YP*kK;K%RoA~R5Ko=y;bpD8`{|G5N z`auwC*&wt;d=qAI_kRRAGy0y5Q0k z^A9MPsh|@-B39{FL_-1 zVT2FV&pirVhpIx474NFQ*CDM6ZSFaN20-qBn%B)6F*cxxVk3Yc32NbYu75enQ*hj= zSX*!bp$J1UH{k&s66sC8{GGb}F|@6^PHn0c?(&c{&Ah@&)(XMJYc(3FdL(z2G;@;c zn@pg-O_tBau_U>~TA?$RwgkGoSTUr`#R+;$3z2SHewP7B+2$Q9=L2(MRWViY0w%|c z?>fiNks8D`ugs#ofeLlk&S{nwLWj3^9-TRW8AA5r8DtEU< zN6a0rhFPFxA0P>Efbt4{8PaD>-GrgqU>xqBO>(C^yVZ70bPBFI#rkZ)gMzUMjSX=; zZG$~HvU1BOn!6M9So>4q&>rDVNXj3JIW~V_(obVtY$+^{r5bS!uSpH?GsjZ24P0;} zY1>TXY&V&$an}a-F&jBT;Ej)0TaZ@uHd~mm3ZR??zqqwsfa>Uiur(O99Ohxt4TnTB zP@=Ut{YJEWDASr2eY1&{N>f{phj3fK#Ii3nN-$eBS2QPb^LF4!BcT%7>Edc7c1a5f4-XFx z3JRLJ*u8FO|7RJDsPJg)_DdoRR>;{`S=bPS4e4Yw8nwh61GxKS%E&#>ah<(k^2DgC zA@)YHsj0w_u&}V~slUtRF%1n3;_#PWW(MSLr=NACn%@9|Fp_F<)|LA7HTRb0IM^x? zyh0}_7w2$=!17n)RHpN|{BTWvn}6MO$a(~=fmzW3BAh~94Ih}Co{cU@27>D_yt`ZK zGcJEcj|@H%(-;K~q%pVhvX|*+j%QxyF zS&`qC|5@kb&nRXLG1ap!vC3aj|39iMba06sY3rG?Y>WFz|Gs5BXL;C`5bZ`N9$M1= zKfj?5CmMU;e*kQQTWW1x>m!xwg6k^NqjG4BM%YHZ%a52J{D#)6&!hmsHW>8ELR!vr z-qC|TO;fD1MhL13vFimn)(T(IXHCb_SsdRi*kMggJe@S0Z3b*r!?_Q&agPiF&0_|P zpZtcpgnaTtF)5o5RC=`U_rJJ$s`ylgeR(}b^@f=7$A{`0zM_3U$A;PwqEu4MmIW~v z#9YWsviScOWv*^Z073Y2zVpCqy}Fcps6@_Va&X}e+_kGr?|W{hnFEFM*r}EQG1qyl zOuWS_8(2PhwF-sPq5?JNu&dS7Z{0RZM76WoE=YD1@k~(2q4G#J#(O<4Ducsc&$)lj zQ|W_O@@6N%J=C=XL9wfp2&awrxwAHYnRNVkPKqW@;E#yBSC^e4(ofYoB2nH6n=vXw zSDE2ln0Iw}bIoshApXI*D*!<&&@#Oh&djg{;}(=#$hrUjf{@?Kj1S1&IyyHuH#Rad zq8yZlGWLJ#1aNYzs;X)p9UXP}+#nNoxVgLYF7*xNdU=)UjaXzQ?`|vZx2beQ?jD$2 z4I9=P%uX@KS{Fnc{;DQ34=g=#Xkxhh5}UwoX253C3#b1HnLSV&bp%`AweE%8OaTr< z)2QzUmHd*&a+)xqy$hq-%-+z0Ew<1>H(SXHl*gc8N&*{$fFcYxptg5r z8xT=Y5#k`lxeAlo#>h+NnsypXYo4V3kNHkN|k0B?jIr5&T@)lL4?YP$YOa?(;h5P_Q^T`APnTP32Y3 z`b3r)c1$NL?FbqC!~Nfb1NWT$CsSa772pqK1gyrxL*{u(X3Q%E7)j6r+*!Qa6*3A0)ePcZ3n8&}In4DB@k*=db(1x1s zsur=ijkgJin84K{|*x25^=^hs;)gFfuwZmbMxPMj9K`{b+b;kJE@2DIHSF ztx%cJjX`;^Km3-(0V4QooG*Qy3c?t*TCFAOf{=g;1Bx+cX^$Y3LcNmct8>+Q$^p3b z%6ZQAHQFSKT~QrB$z;U^vW~`Q0)C>Y{Woomm3^cjBoCu=Ty zi&RrY!nf_XMILGhTCbnqkg27P56_+tvdH4l zfw#!r+Ydx$HBlERUva4AV=BuNi3#Xzw4NZu%V3|%7DJYmsm+sgu)J1WU1IR9zS3iN zX>d6Gi^^uj!zJdnoZkDJGEi?cAYIMPTzW}ojHGt)FHG*~YsSkfgv$INbOp|>?NB$M zQ7rvVWb_n%_itk>w``=pd(j77-yS?gnanXsQ@e@B~)o-=^^)%>34J!`?*NGhpt z9xhP6^B^>-x<)4^CPcfCyM$)n8Za5C`wQZ_)v-M<9cbi4A@2MjZ_dKw@kdW zDq`4fYh0khV@s{p0#;TKkc>B|SOm4LECCY)xe5dV%Qa-*1lsQN?8okh{ot3J$(b`} z-t%7m?{7|S4hgiIF=qxs$ZliMhIbIc;SxhECJgs79_~WOzI)>a|1Glco~Em(UVn-m zZ{D%{&Jq8pV^LQ>P#<4+^H$rbbo}VT80pbN?@M{EOSGcR9mO4SZ+k8eBDYJeqboBX zz5AWRmh{U9YE3@zlAlH&u1E=8qcOa!ma{lKF@+S_^tI3^h0WBTB@BALc^1R{F$QmY z>Gyu4Ow0 z?ec2d|3fF}!pYP9*#X<&H$N-8j6r-5gH7k=%aAY*On>1mcS7A5iDoa??s}!_O!A;h0wWjSZoK$MUKUOtwVA_H2UGu9rtI)G3m)~Z|uUAuan5vvGt5! zC8~EY-4^Uep)$pR9D0so1w$wGo+xZ<2p{*x+YO^f^ZY*8WC||mJY;|n8RD8lSo`=o zJ2H0Q68~${rLOvkXj`i0epm+W3$APHj8d+CWSZAy2z`RiU1TS$BV(}`l=U~sA%COf z&sA-rooHW3?-8QSLC+LQoRS((P7ZI*Y0qTRzF{R?JPX`bpPhV}-6g-gYNgtRLB<|* zdR?fbom=ktCbWcHhukc08q3TVL2APDy~oUw!`ioqMEe4I)>*`goCgj#&u_G`h*SMr zG^#({aNHtaVB_?Q%qt83PGN;E|6n^k>0YtRQdQ5J4*YoUb0Adlg<*wR1S(dnKm5R7R8g!xM71W75JB-JFL*N6eit~ za^pQ*IUnNn(S;m;-EB}+wxF(uyPZcOPqD!fmu#YuY)^XidfijF7ag} zB2wW&?^y&Z)CJAN6z8Gjx@G1m2s-_H5sTz7qqNITKH@@fpeZ-~ECVRF=0QLZdJ3^vu= zY$ft5opU02;@rg`14*h(bDW;zOz{Ldp@kqOH_XmBFWyF@j(CBOLZd_zJ|sxQ7O2wM zf|ZK*Fb3_ztNVixl{N*3SlZ7qIFfYq0vF2P<+nRr=r(3yVW|YG6e}bszn~z7<;W{6 zplfPsh90jLqO;Y9UsHJS3Zs#$vdV+U!xax^mzcs$?u%_H_dVh3btfUOC0_BHr$M-U6?g;^|D59{8{D_fK3ft_5 z4XD7ngX7<|!!Y51IJ1T5_KUPB!{cD?!0;zr{hMO&Y$14-9}$P2N8Svg6kb`Jbl=>2 zd7dOX_P`0m3V${I$|t`sA!?y(mG!%O)S2aSFFQz*yF|Jg1&AtIi3WUfSPMPxs8xxmUrK{Xd5LqEkSMN9l}&$!={TH@8P2Gw*Bw*{ism-nXIe_d+?jcS z8#JRlaj2{BzHS|tr+=cwNqm85q7v zv8+-Peee<}?#OEhCnt1$iq-bS1vpfZ?5a&@*r>bsA|>8Iqdyi4lEOeA zhqyfXyEF=VDo75sP({B676Sul@NbxOvsC}WzsIo9NSNDaOI~p#^8C!O3RejV_n0N< zfl~%WA7^KWT)byvOWlhL57Ipcccc|;rlz%oyB*q@XRP53{0T_YlP%2wtk6&C%b3*L zHMQjHq624X?ksNrG0*fD3#q(=;eomVtF3m_1El~{{9dm^nuN!-1c&4<6;{eL7LC^J z5S6xCy5cWEF>BOqIC4qyBeQh<52w|^y;(esH7&xfkE<${k^oEU*U-X{j57r$$eSu~ z)Cg80XN+`wp`3KN7*uI`o+g;gwB;|y@NLjsIF0sHOTV*fccpxEAkRZw)0GFl{IM2t zaV<6-qw}z-9KtR^nbx-eFZnCX?S_{+(-#9`W8^le2lqn{Yf^ilexSe+NL(=F%Wn1f zJgO`LH#mq5JZDt%EN_u+Au?m-6nNg10I+7{Yq^xn2&O&YNyuMMn?~DnOztkByKy4fUTm}-*(vPd=9+*-i><)G zVfi?ryXNh`YSOu-dpV{5?>MM~*H(@!2aowxjs(sT%kW4zTj$+j?+S&kJ+%uE}soefBBcFEZAYF@t=FV)hz z<*MWw6QjJMqT-}slY^bMzhYXvN6IDQ9fT-8GN=8*e-LT@Msg-V-I;mu zW$YMK_DJ$!q*3zr(JWqJ8ekxM+}CO7HzG~ggvUaZVh0N~VL+tmsi)Z8@>6pf2}Kwj zDy+W!#@8aC#rl?zLG{O_dmYcMr_s4DtP}h#pS~-BTUNe^UuOPD<_SLrQVq24Ju*eA rZvd%Q_e4!m=9EE=R#T+9MW=Oi3=911=eGIq^^Z2b8L}aN9eL#6&OiyX From 29581b28e88a6bc75aac08a560a6bbc110fa4503 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 19 Feb 2019 00:07:39 +0200 Subject: [PATCH 03/13] ignore negative borders, cleanup --- src/elements/element.rectangle.js | 114 +++++++----------- src/helpers/helpers.core.js | 7 ++ .../controller.bar/borderWidth/negative.js | 49 ++++++++ .../controller.bar/borderWidth/negative.png | Bin 0 -> 1766 bytes 4 files changed, 98 insertions(+), 72 deletions(-) create mode 100644 test/fixtures/controller.bar/borderWidth/negative.js create mode 100644 test/fixtures/controller.bar/borderWidth/negative.png diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 2921206acf1..1c47327d804 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -54,26 +54,6 @@ function getBarBounds(bar) { }; } -function parseBorderWidth(value, skipped, maxWidth, maxHeight) { - var t, r, b, l; - - if (helpers.isObject(value)) { - t = +value.top || 0; - r = +value.right || 0; - b = +value.bottom || 0; - l = +value.left || 0; - } else { - t = r = b = l = +value || 0; - } - - return { - top: Math.min(maxHeight, skipped === 'top' ? 0 : t), - right: Math.min(maxWidth, skipped === 'right' ? 0 : r), - bottom: Math.min(maxHeight, skipped === 'bottom' ? 0 : b), - left: Math.min(maxWidth, skipped === 'left' ? 0 : l) - }; -} - function flip(orig, v1, v2) { return orig === v1 ? v2 : orig === v2 ? v1 : orig; } @@ -92,79 +72,67 @@ function parseBorderSkipped(bar) { return borderSkipped; } +function parseBorderWidth(value, bar, maxWidth, maxHeight) { + var bound = helpers.bound; + var skip = parseBorderSkipped(bar); + var t, r, b, l; + + if (helpers.isObject(value)) { + t = value.top || 0; + r = value.right || 0; + b = value.bottom || 0; + l = value.left || 0; + } else { + t = r = b = l = value || 0; + } + + return { + top: bound(0, skip === 'top' ? 0 : t, maxHeight), + right: bound(0, skip === 'right' ? 0 : r, maxWidth), + bottom: bound(0, skip === 'bottom' ? 0 : b, maxHeight), + left: bound(0, skip === 'left' ? 0 : l, maxWidth) + }; +} + module.exports = Element.extend({ draw: function() { var me = this; var ctx = me._chart.ctx; var vm = me._view; - var borderWidth = vm.borderWidth; + var border = vm.borderWidth; var bounds = getBarBounds(me); var left = bounds.left; var top = bounds.top; - var right = bounds.right; - var bottom = bounds.bottom; - var width = right - left; - var height = bottom - top; - var inner, maxLine, halfLine; + var width = bounds.right - left; + var height = bounds.bottom - top; + var inner, maxBorder, halfBorder; ctx.fillStyle = vm.backgroundColor; - if (!borderWidth) { - ctx.fillRect(left, top, width, height); - return; - } + border = parseBorderWidth(border, me, width / 2, height / 2); + maxBorder = Math.max(border.left, border.top, border.right, border.bottom); - borderWidth = parseBorderWidth( - borderWidth, - parseBorderSkipped(me), - width / 2, - height / 2); - - maxLine = Math.max(borderWidth.left, borderWidth.top, borderWidth.right, borderWidth.bottom); - - if (!maxLine) { + if (!maxBorder) { ctx.fillRect(left, top, width, height); return; } inner = { - left: left + borderWidth.left, - top: top + borderWidth.top, - width: width - borderWidth.left - borderWidth.right, - height: height - borderWidth.bottom - borderWidth.top + left: left + border.left, + top: top + border.top, + width: width - border.left - border.right, + height: height - border.bottom - border.top }; ctx.fillRect(inner.left, inner.top, inner.width, inner.height); - ctx.strokeStyle = vm.borderColor; - - // add 1 to max border width, so border is actually 0.5px wider - // to hide artifacts - ctx.lineWidth = maxLine + 1; - - // move edges 1px where there is no border, to prevent artifacts - if (!borderWidth.left) { - inner.left -= 1; - inner.width += 1; - } - if (!borderWidth.right) { - inner.width += 1; - } - if (!borderWidth.top) { - inner.top -= 1; - inner.height += 1; - } - if (!borderWidth.bottom) { - inner.height += 1; - } - - halfLine = maxLine / 2; - inner = { - left: inner.left - halfLine, - top: inner.top - halfLine, - width: inner.width + maxLine, - height: inner.height + maxLine - }; + // offset inner rectanble by half of widest border + // move edges additional 1px out, where there is no border, to prevent artifacts + halfBorder = maxBorder / 2; + inner.left -= halfBorder + (border.left ? 0 : 1); + inner.top -= halfBorder + (border.top ? 0 : 1); + inner.width += maxBorder + (border.left ? border.right ? 0 : 1 : 2); + inner.height += maxBorder + (border.top ? border.bottom ? 0 : 1 : 2); ctx.save(); ctx.beginPath(); @@ -172,6 +140,8 @@ module.exports = Element.extend({ ctx.clip(); ctx.beginPath(); ctx.rect(inner.left, inner.top, inner.width, inner.height); + ctx.lineWidth = maxBorder + 1; // + 1 to prevent artifacts + ctx.strokeStyle = vm.borderColor; ctx.stroke(); ctx.restore(); }, diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 1b798c925c8..811df2beea6 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -308,6 +308,13 @@ var helpers = { ChartElement.__super__ = me.prototype; return ChartElement; + }, + + /** + * Returns value bounded by min and max. This is equivalent to max(min, min(value, max)). + */ + bound: function(min, value, max) { + return Math.max(min, Math.min(value, max)); } }; diff --git a/test/fixtures/controller.bar/borderWidth/negative.js b/test/fixtures/controller.bar/borderWidth/negative.js new file mode 100644 index 00000000000..5a5189d7fbe --- /dev/null +++ b/test/fixtures/controller.bar/borderWidth/negative.js @@ -0,0 +1,49 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 2, 3, 4, 5], + datasets: [ + { + // option in dataset + data: [0, 5, 10, null, -10, -5], + borderWidth: -2 + }, + { + // option in element (fallback) + data: [0, 5, 10, null, -10, -5], + }, + { + data: [0, 5, 10, null, -10, -5], + borderWidth: {left: -5, top: -5, bottom: -5, right: -5}, + borderSkipped: false + }, + { + data: [0, 5, 10, null, -10, -5], + borderWidth: {} + }, + ] + }, + options: { + legend: false, + title: false, + elements: { + rectangle: { + backgroundColor: '#888', + borderColor: '#f00', + borderWidth: -4 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/borderWidth/negative.png b/test/fixtures/controller.bar/borderWidth/negative.png new file mode 100644 index 0000000000000000000000000000000000000000..ca2a445d99250cb59e9bc4d06b00b762801bfad4 GIT binary patch literal 1766 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o)_S@)hE&XXd)+tpu!Dr_ z#RiVHAdc1q&DMA6X^C7%gSeIkaH&qnYf^aEf3>>r$=m1~lS=nr=KMEt>$T@mufLrz zeqOULzvj)2ikOFe%~;&Eor$1TN=H|5^0Jb!G?=U=h!Lm4<28k~h0m>d`wSU4FN6&M(r zSQ;1v7#JLx96)jkj0y}a3=R{3LQD(_9zY>R1_2cYmIg?KIgDx>fFNNIc*Ib^U@;n3 tLogjS%;#iaurOg%7%lN=QS$da;9h%i_KZWT^k*{wfv2mV%Q~loCID${;hF#d literal 0 HcmV?d00001 From c9697486a4151faf035bdab9ff29276c40fcda13 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 19 Feb 2019 00:29:45 +0200 Subject: [PATCH 04/13] more cleanup --- src/elements/element.rectangle.js | 37 +++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 1c47327d804..bfd13004e36 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -75,7 +75,7 @@ function parseBorderSkipped(bar) { function parseBorderWidth(value, bar, maxWidth, maxHeight) { var bound = helpers.bound; var skip = parseBorderSkipped(bar); - var t, r, b, l; + var t, r, b, l, border; if (helpers.isObject(value)) { t = value.top || 0; @@ -86,12 +86,15 @@ function parseBorderWidth(value, bar, maxWidth, maxHeight) { t = r = b = l = value || 0; } - return { - top: bound(0, skip === 'top' ? 0 : t, maxHeight), - right: bound(0, skip === 'right' ? 0 : r, maxWidth), - bottom: bound(0, skip === 'bottom' ? 0 : b, maxHeight), - left: bound(0, skip === 'left' ? 0 : l, maxWidth) + border = { + top: bound(0, t, maxHeight), + right: bound(0, r, maxWidth), + bottom: bound(0, b, maxHeight), + left: bound(0, l, maxWidth) }; + border[skip] = 0; + + return border; } module.exports = Element.extend({ @@ -99,36 +102,32 @@ module.exports = Element.extend({ var me = this; var ctx = me._chart.ctx; var vm = me._view; - var border = vm.borderWidth; var bounds = getBarBounds(me); var left = bounds.left; var top = bounds.top; var width = bounds.right - left; var height = bounds.bottom - top; - var inner, maxBorder, halfBorder; + var border = parseBorderWidth(vm.borderWidth, me, width / 2, height / 2); + var maxBorder = Math.max(border.left, border.top, border.right, border.bottom); + var halfBorder = maxBorder / 2; + var inner = { + left: left + border.left, + top: top + border.top, + width: width - border.left - border.right, + height: height - border.bottom - border.top + }; ctx.fillStyle = vm.backgroundColor; - border = parseBorderWidth(border, me, width / 2, height / 2); - maxBorder = Math.max(border.left, border.top, border.right, border.bottom); - if (!maxBorder) { ctx.fillRect(left, top, width, height); return; } - inner = { - left: left + border.left, - top: top + border.top, - width: width - border.left - border.right, - height: height - border.bottom - border.top - }; - ctx.fillRect(inner.left, inner.top, inner.width, inner.height); // offset inner rectanble by half of widest border // move edges additional 1px out, where there is no border, to prevent artifacts - halfBorder = maxBorder / 2; inner.left -= halfBorder + (border.left ? 0 : 1); inner.top -= halfBorder + (border.top ? 0 : 1); inner.width += maxBorder + (border.left ? border.right ? 0 : 1 : 2); From c14ba1140af996c4852413b8647b3b3f14263ac8 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 19 Feb 2019 00:37:42 +0200 Subject: [PATCH 05/13] climate that --- src/elements/element.rectangle.js | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index bfd13004e36..7f4775c3e10 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -74,26 +74,19 @@ function parseBorderSkipped(bar) { function parseBorderWidth(value, bar, maxWidth, maxHeight) { var bound = helpers.bound; - var skip = parseBorderSkipped(bar); - var t, r, b, l, border; - - if (helpers.isObject(value)) { - t = value.top || 0; - r = value.right || 0; - b = value.bottom || 0; - l = value.left || 0; - } else { - t = r = b = l = value || 0; - } - - border = { - top: bound(0, t, maxHeight), - right: bound(0, r, maxWidth), - bottom: bound(0, b, maxHeight), - left: bound(0, l, maxWidth) + var isObject = helpers.isObject(value); + var top = (isObject ? value.top : value) || 0; + var left = (isObject ? value.left : value) || 0; + var bottom = (isObject ? value.bottom : value) || 0; + var right = (isObject ? value.right : value) || 0; + var border = { + top: bound(0, top, maxHeight), + right: bound(0, right, maxWidth), + bottom: bound(0, bottom, maxHeight), + left: bound(0, left, maxWidth) }; + var skip = parseBorderSkipped(bar); border[skip] = 0; - return border; } From ef842adf6e0e0974f24b9ac7f24a258fe29c2d7a Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 19 Feb 2019 00:57:16 +0200 Subject: [PATCH 06/13] fix padding, cache borders --- src/elements/element.rectangle.js | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 7f4775c3e10..e26ee08e852 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -101,13 +101,17 @@ module.exports = Element.extend({ var width = bounds.right - left; var height = bounds.bottom - top; var border = parseBorderWidth(vm.borderWidth, me, width / 2, height / 2); - var maxBorder = Math.max(border.left, border.top, border.right, border.bottom); + var bLeft = border.left; + var bTop = border.top; + var bRight = border.right; + var bBottom = border.bottom; + var maxBorder = Math.max(bLeft, bTop, bRight, bBottom); var halfBorder = maxBorder / 2; var inner = { - left: left + border.left, - top: top + border.top, - width: width - border.left - border.right, - height: height - border.bottom - border.top + left: left + bLeft, + top: top + bTop, + width: width - bLeft - bRight, + height: height - bBottom - bTop }; ctx.fillStyle = vm.backgroundColor; @@ -121,10 +125,10 @@ module.exports = Element.extend({ // offset inner rectanble by half of widest border // move edges additional 1px out, where there is no border, to prevent artifacts - inner.left -= halfBorder + (border.left ? 0 : 1); - inner.top -= halfBorder + (border.top ? 0 : 1); - inner.width += maxBorder + (border.left ? border.right ? 0 : 1 : 2); - inner.height += maxBorder + (border.top ? border.bottom ? 0 : 1 : 2); + inner.left -= halfBorder + (bLeft ? 0 : 1); + inner.top -= halfBorder + (bTop ? 0 : 1); + inner.width += maxBorder + (bLeft && bRight ? 0 : bLeft || bRight ? 1 : 2); + inner.height += maxBorder + (bTop && bBottom ? 0 : bTop || bBottom ? 1 : 2); ctx.save(); ctx.beginPath(); From 2a7ea0d5fe4f69d8d424a2ad9a6e6669dc88c434 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Tue, 19 Feb 2019 16:03:49 +0200 Subject: [PATCH 07/13] tidying + commenting --- src/elements/element.rectangle.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index e26ee08e852..c8d1306413a 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -54,7 +54,7 @@ function getBarBounds(bar) { }; } -function flip(orig, v1, v2) { +function swap(orig, v1, v2) { return orig === v1 ? v2 : orig === v2 ? v1 : orig; } @@ -63,11 +63,14 @@ function parseBorderSkipped(bar) { var vertical = isVertical(bar); var borderSkipped = valueOrDefault(vm.borderSkipped, vertical ? 'bottom' : 'left'); + // For backward compatibility, 'bottom' for vertical and 'left' for + // horizontal actually mean 'start'. So 'bottom' / 'top' + // and 'left' / 'right' are swapped for negative bars. if (vertical && vm.base < vm.y) { - borderSkipped = flip(borderSkipped, 'bottom', 'top'); + borderSkipped = swap(borderSkipped, 'bottom', 'top'); } if (!vertical && vm.base > vm.x) { - borderSkipped = flip(borderSkipped, 'left', 'right'); + borderSkipped = swap(borderSkipped, 'left', 'right'); } return borderSkipped; } @@ -123,12 +126,12 @@ module.exports = Element.extend({ ctx.fillRect(inner.left, inner.top, inner.width, inner.height); - // offset inner rectanble by half of widest border + // offset inner rectangle by half of widest border // move edges additional 1px out, where there is no border, to prevent artifacts inner.left -= halfBorder + (bLeft ? 0 : 1); inner.top -= halfBorder + (bTop ? 0 : 1); - inner.width += maxBorder + (bLeft && bRight ? 0 : bLeft || bRight ? 1 : 2); - inner.height += maxBorder + (bTop && bBottom ? 0 : bTop || bBottom ? 1 : 2); + inner.width += maxBorder + (bLeft ? 0 : 1) + (bRight ? 0 : 1); + inner.height += maxBorder + (bTop ? 0 : 1) + (bBottom ? 0 : 1); ctx.save(); ctx.beginPath(); From 487bc17bdae3feebd800054b26bacc822c2563db Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 20 Feb 2019 15:11:16 +0200 Subject: [PATCH 08/13] optimization --- src/elements/element.rectangle.js | 57 +++++++++++++++++-------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index c8d1306413a..f30294e2697 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -5,7 +5,6 @@ var Element = require('../core/core.element'); var helpers = require('../helpers/index'); var defaultColor = defaults.global.defaultColor; -var valueOrDefault = helpers.valueOrDefault; defaults._set('global', { elements: { @@ -60,37 +59,43 @@ function swap(orig, v1, v2) { function parseBorderSkipped(bar) { var vm = bar._view; - var vertical = isVertical(bar); - var borderSkipped = valueOrDefault(vm.borderSkipped, vertical ? 'bottom' : 'left'); - - // For backward compatibility, 'bottom' for vertical and 'left' for - // horizontal actually mean 'start'. So 'bottom' / 'top' - // and 'left' / 'right' are swapped for negative bars. - if (vertical && vm.base < vm.y) { - borderSkipped = swap(borderSkipped, 'bottom', 'top'); + var edge = vm.borderSkipped; + var res = {}; + + if (!edge) { + return res; } - if (!vertical && vm.base > vm.x) { - borderSkipped = swap(borderSkipped, 'left', 'right'); + + if (isVertical(bar)) { + if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); + } + } else if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); } - return borderSkipped; + + res[edge] = true; + return res; } function parseBorderWidth(value, bar, maxWidth, maxHeight) { - var bound = helpers.bound; - var isObject = helpers.isObject(value); - var top = (isObject ? value.top : value) || 0; - var left = (isObject ? value.left : value) || 0; - var bottom = (isObject ? value.bottom : value) || 0; - var right = (isObject ? value.right : value) || 0; - var border = { - top: bound(0, top, maxHeight), - right: bound(0, right, maxWidth), - bottom: bound(0, bottom, maxHeight), - left: bound(0, left, maxWidth) + var _skip = parseBorderSkipped(bar); + var t, r, b, l; + if (helpers.isObject(value)) { + t = +value.top || 0; + r = +value.right || 0; + b = +value.bottom || 0; + l = +value.left || 0; + } else { + t = r = b = l = +value || 0; + } + + return { + top: _skip.top || (t < 0) ? 0 : t > maxHeight ? maxHeight : t, + right: _skip.right || (r < 0) ? 0 : r > maxWidth ? maxWidth : r, + bottom: _skip.bottom || (b < 0) ? 0 : b > maxWidth ? maxWidth : b, + left: _skip.left || (l < 0) ? 0 : l > maxWidth ? maxWidth : l }; - var skip = parseBorderSkipped(bar); - border[skip] = 0; - return border; } module.exports = Element.extend({ From 4cc6628d52e656cb6a14c09df3cc712756c70fc5 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 20 Feb 2019 15:35:05 +0200 Subject: [PATCH 09/13] regression test for chart area clipping --- .../controller.bar/chart-area-clip.js | 42 ++++++++++++++++++ .../controller.bar/chart-area-clip.png | Bin 0 -> 1711 bytes 2 files changed, 42 insertions(+) create mode 100644 test/fixtures/controller.bar/chart-area-clip.js create mode 100644 test/fixtures/controller.bar/chart-area-clip.png diff --git a/test/fixtures/controller.bar/chart-area-clip.js b/test/fixtures/controller.bar/chart-area-clip.js new file mode 100644 index 00000000000..2d552dcd4de --- /dev/null +++ b/test/fixtures/controller.bar/chart-area-clip.js @@ -0,0 +1,42 @@ +module.exports = { + config: { + type: 'bar', + data: { + labels: [0, 1, 3, 4], + datasets: [ + { + data: [5, 20, -5, -20], + borderColor: '#ff0000' + } + ] + }, + options: { + legend: false, + title: false, + layout: { + padding: { + left: 0, + right: 0, + top: 50, + bottom: 50 + } + }, + elements: { + rectangle: { + backgroundColor: '#00ff00', + borderWidth: 8 + } + }, + scales: { + xAxes: [{display: false}], + yAxes: [{display: false, ticks: {min: -10, max: 10}}] + } + } + }, + options: { + canvas: { + height: 256, + width: 512 + } + } +}; diff --git a/test/fixtures/controller.bar/chart-area-clip.png b/test/fixtures/controller.bar/chart-area-clip.png new file mode 100644 index 0000000000000000000000000000000000000000..8c59a70128ba8deebe6694b148b2046b3b416f4b GIT binary patch literal 1711 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o$~;{hLn`LHy?!uP*^t5c z;!B0vU3=LfW7@j=iKk)4j<4E*B{s@k%-LUUp%=zQ(pJ#vmy8JyOIQI@n3LjpD1ce14 ft&4bu1OFfB`%joSi?^xoA;>aMS3j3^P6 Date: Wed, 20 Feb 2019 17:31:21 +0200 Subject: [PATCH 10/13] clean up unneeded `bounds`, add a missing empty line --- src/elements/element.rectangle.js | 1 + src/helpers/helpers.core.js | 7 ------- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index f30294e2697..9b6e8da9276 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -81,6 +81,7 @@ function parseBorderSkipped(bar) { function parseBorderWidth(value, bar, maxWidth, maxHeight) { var _skip = parseBorderSkipped(bar); var t, r, b, l; + if (helpers.isObject(value)) { t = +value.top || 0; r = +value.right || 0; diff --git a/src/helpers/helpers.core.js b/src/helpers/helpers.core.js index 811df2beea6..1b798c925c8 100644 --- a/src/helpers/helpers.core.js +++ b/src/helpers/helpers.core.js @@ -308,13 +308,6 @@ var helpers = { ChartElement.__super__ = me.prototype; return ChartElement; - }, - - /** - * Returns value bounded by min and max. This is equivalent to max(min, min(value, max)). - */ - bound: function(min, value, max) { - return Math.max(min, Math.min(value, max)); } }; From c5df9c902f1a4df5dd230ff6e362110f5536f3c0 Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Wed, 20 Feb 2019 20:52:22 +0200 Subject: [PATCH 11/13] option C --- src/elements/element.rectangle.js | 127 +++++++++--------- .../controller.bar/borderSkipped/value.png | Bin 2167 -> 1989 bytes .../borderWidth/indexable-object.png | Bin 2086 -> 1871 bytes .../controller.bar/borderWidth/indexable.png | Bin 2016 -> 1855 bytes .../controller.bar/borderWidth/object.png | Bin 2269 -> 2113 bytes .../borderWidth/scriptable-object.png | Bin 1601 -> 1640 bytes .../controller.bar/borderWidth/value.png | Bin 2222 -> 2064 bytes .../controller.bar/horizontal-borders.png | Bin 1640 -> 1522 bytes 8 files changed, 61 insertions(+), 66 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index 9b6e8da9276..ac55a72e950 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -17,8 +17,8 @@ defaults._set('global', { } }); -function isVertical(bar) { - return bar._view.width !== undefined; +function isVertical(vm) { + return vm.width !== undefined; } /** @@ -27,11 +27,10 @@ function isVertical(bar) { * @return {Bounds} bounds of the bar * @private */ -function getBarBounds(bar) { - var vm = bar._view; +function getBarBounds(vm) { var x1, x2, y1, y2, half; - if (isVertical(bar)) { + if (isVertical(vm)) { half = vm.width / 2; x1 = vm.x - half; x2 = vm.x + half; @@ -57,8 +56,7 @@ function swap(orig, v1, v2) { return orig === v1 ? v2 : orig === v2 ? v1 : orig; } -function parseBorderSkipped(bar) { - var vm = bar._view; +function parseBorderSkipped(vm) { var edge = vm.borderSkipped; var res = {}; @@ -66,20 +64,21 @@ function parseBorderSkipped(bar) { return res; } - if (isVertical(bar)) { - if (vm.base < vm.y) { - edge = swap(edge, 'bottom', 'top'); + if (vm.horizontal) { + if (vm.base > vm.x) { + edge = swap(edge, 'left', 'right'); } - } else if (vm.base > vm.x) { - edge = swap(edge, 'left', 'right'); + } else if (vm.base < vm.y) { + edge = swap(edge, 'bottom', 'top'); } res[edge] = true; return res; } -function parseBorderWidth(value, bar, maxWidth, maxHeight) { - var _skip = parseBorderSkipped(bar); +function parseBorderWidth(vm, maxW, maxH) { + var value = vm.borderWidth; + var _skip = parseBorderSkipped(vm); var t, r, b, l; if (helpers.isObject(value)) { @@ -92,62 +91,57 @@ function parseBorderWidth(value, bar, maxWidth, maxHeight) { } return { - top: _skip.top || (t < 0) ? 0 : t > maxHeight ? maxHeight : t, - right: _skip.right || (r < 0) ? 0 : r > maxWidth ? maxWidth : r, - bottom: _skip.bottom || (b < 0) ? 0 : b > maxWidth ? maxWidth : b, - left: _skip.left || (l < 0) ? 0 : l > maxWidth ? maxWidth : l + t: _skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: _skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: _skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: _skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + }; +} + +function boundingRects(vm) { + var bounds = getBarBounds(vm); + var width = bounds.right - bounds.left; + var height = bounds.bottom - bounds.top; + var border = parseBorderWidth(vm, width / 2, height / 2); + + return { + outer: { + x: bounds.left, + y: bounds.top, + w: width, + h: height + }, + inner: { + x: bounds.left + border.l, + y: bounds.top + border.t, + w: width - border.l - border.r, + h: height - border.t - border.b + } }; } module.exports = Element.extend({ draw: function() { - var me = this; - var ctx = me._chart.ctx; - var vm = me._view; - var bounds = getBarBounds(me); - var left = bounds.left; - var top = bounds.top; - var width = bounds.right - left; - var height = bounds.bottom - top; - var border = parseBorderWidth(vm.borderWidth, me, width / 2, height / 2); - var bLeft = border.left; - var bTop = border.top; - var bRight = border.right; - var bBottom = border.bottom; - var maxBorder = Math.max(bLeft, bTop, bRight, bBottom); - var halfBorder = maxBorder / 2; - var inner = { - left: left + bLeft, - top: top + bTop, - width: width - bLeft - bRight, - height: height - bBottom - bTop - }; + var ctx = this._chart.ctx; + var vm = this._view; + var rects = boundingRects(vm); + var outer = rects.outer; + var inner = rects.inner; ctx.fillStyle = vm.backgroundColor; + ctx.fillRect(outer.x, outer.y, outer.w, outer.h); - if (!maxBorder) { - ctx.fillRect(left, top, width, height); + if (outer.w === inner.w && outer.h === inner.h) { return; } - ctx.fillRect(inner.left, inner.top, inner.width, inner.height); - - // offset inner rectangle by half of widest border - // move edges additional 1px out, where there is no border, to prevent artifacts - inner.left -= halfBorder + (bLeft ? 0 : 1); - inner.top -= halfBorder + (bTop ? 0 : 1); - inner.width += maxBorder + (bLeft ? 0 : 1) + (bRight ? 0 : 1); - inner.height += maxBorder + (bTop ? 0 : 1) + (bBottom ? 0 : 1); - ctx.save(); ctx.beginPath(); - ctx.rect(left, top, width, height); + ctx.rect(outer.x, outer.y, outer.w, outer.h); ctx.clip(); - ctx.beginPath(); - ctx.rect(inner.left, inner.top, inner.width, inner.height); - ctx.lineWidth = maxBorder + 1; // + 1 to prevent artifacts - ctx.strokeStyle = vm.borderColor; - ctx.stroke(); + ctx.fillStyle = vm.borderColor; + ctx.rect(inner.x, inner.y, inner.w, inner.h); + ctx.fill('evenodd'); ctx.restore(); }, @@ -158,9 +152,10 @@ module.exports = Element.extend({ inRange: function(mouseX, mouseY) { var inRange = false; + var vm = this._view; - if (this._view) { - var bounds = getBarBounds(this); + if (vm) { + var bounds = getBarBounds(vm); inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; } @@ -168,15 +163,15 @@ module.exports = Element.extend({ }, inLabelRange: function(mouseX, mouseY) { - var me = this; - if (!me._view) { + var vm = this._view; + if (!vm) { return false; } var inRange = false; - var bounds = getBarBounds(me); + var bounds = getBarBounds(vm); - if (isVertical(me)) { + if (isVertical(vm)) { inRange = mouseX >= bounds.left && mouseX <= bounds.right; } else { inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; @@ -186,19 +181,19 @@ module.exports = Element.extend({ }, inXRange: function(mouseX) { - var bounds = getBarBounds(this); + var bounds = getBarBounds(this._view); return mouseX >= bounds.left && mouseX <= bounds.right; }, inYRange: function(mouseY) { - var bounds = getBarBounds(this); + var bounds = getBarBounds(this._view); return mouseY >= bounds.top && mouseY <= bounds.bottom; }, getCenterPoint: function() { var vm = this._view; var x, y; - if (isVertical(this)) { + if (isVertical(vm)) { x = vm.x; y = (vm.y + vm.base) / 2; } else { @@ -212,7 +207,7 @@ module.exports = Element.extend({ getArea: function() { var vm = this._view; - return isVertical(this) + return isVertical(vm) ? vm.width * Math.abs(vm.y - vm.base) : vm.height * Math.abs(vm.x - vm.base); }, diff --git a/test/fixtures/controller.bar/borderSkipped/value.png b/test/fixtures/controller.bar/borderSkipped/value.png index 149a81da35d5b884846be15d16de9486ce7861c8..7f4179c87b13b21345439be6f2f7266b2b9b46d4 100644 GIT binary patch literal 1989 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@odpunnLn`LHy|yt|#*@M2 zqOZrc8rB0F%GBg;vee$>@_6piFlYDqqq>d@p4l$g>liMb{O2|=P&+=*@c*sNdHv^= z|9)*RmN|ZY|GwJp{QZCbz588X`~5)W-n)6*fB*gW)${w`UH9L|*YE%M)w8^AKT!0s z#Xj5Dz0WJ_e)iNeHgF39o!h`ILZt8X`?=R|zpZ1a`2JIv;d$jgyO-xkEtwJy+pDp-9^zS) RGZR#~db;|#taD0e0sxW#R#N}~ literal 2167 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is>y>^xl@Ln`LHy{6bF<0;W{ z(W>)hQE_o{@%hkeD~z)B43DHN_$Uy6AbrEk{*B&yOcuM{UOa8ly~%agvQpcnYHou} z!UqogH+x=V{`^mR>G8svTi^dW|GM+|@#EjQx3?J{+y3RIvH7uzo7-}&`yQ{4uDL(| z{-^8n&G+4T(R+Ve?rk}F`TO@@^%kEi|Nh&Zp@5Ugfq|jGkr?tjM}y(9^@hi$mlU6C z&$Sk3NbXq=MCr^4=YAGE<4x{~H|J?sZuxvO1H*%N??f2x$A3OHnejtm&&Q8B40BA6 z{is{baO3;m!-X>QSr{0~)fuh+!9kUAgBknGZwsx6J%JP*Yj~xZoV)uK}?t4`_YuK;2SVz^Sv23 z7#Q?C2$K(zSQap_JZNHR0Hz$GNI$j%pXYpj*~a*xy62xRd!Z)P-jUSsjI?LN4mygcV~B(N4O zFXw6q23Nhsz#{M;H?X=q-FxrHEC!2eV6C=Yl>u13h^r7JW&g3B*sOogTjnk|0}yz+ L`njxgN@xNA)^*me|1v%hId0GR;WuyozfafChBH67y*=MPw)pVl$B)l5F*N8f zDljl`2mu|^pfJj$PB=U`Yu)$wpT+sRn`O(@-|djE3kHSky={-rGwiT``0nt>`-~q7 zj^6&xlrWEhA;N`$qd}92AwdwRXc-4XgDXn|!we+`1~EYfri89h1;aEPCW_bb`5pfKFOSR1?pXxPp2t`i7!E{#0fL`P9BhCY`pfse#};;TO&`5{`SSCZ-SfWc z9vA&@$@)OHQjuX_%Z~w0|N^u zu#j<>Fv_GtI5ga5JU%1?%h?bk_L0KNS1FiU9~bUHx3vIVCg!0QSyLm;e9( literal 2086 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o-+H<@hE&XXduwA>bfie@ z!w};qqD~x@?L13D7aJ|Ga{VyjmdY!p!?ttW=O1G_s(f56=k99T*S{Z>G;J=C{yXXK zE73pwckbW+Z@InL;=a+bAL-qK$vykGSN&ty(CN^)+hc!T{>r&!2hfF_KnFS~Fe)%GaHue_FfcT-G%zqI zOkfaTU|^lWo5%coj{1{6HyZD#3 zHg@)Y=ik(vUd-^L>MU<^PkwqiCqqMYnHWQKg&sp#;Zuf)+mjhLs4+4y6fB;W`g4P; zf?Y*9D7kJ2C)W_W%a+gOqxVk#`GT8)A*Mu=;h4nl$p6n5`itAG`6tZ4zg~OqZ}HjR ziq}aolplTn@Jqe@b6~-7ezyq2@y8!azuFd`GY6{F`^)rU*{^rYW%`d_*JeMk?#Dc9 zMur3Vf}>>$Fh5hNgl!0Aevo%=r?$;~RtARst_*9W7_xx{w~ar;6?O)OJ2s5KQe(7O zr+OtfohhMb SX2ahMK;Y@>=d#Wzp$P!I*w!0eEIU_e4q*)Mg;~24k3_Yg;6F2;qc($+~Pd_xLfb5&RNRGzxnvF zulkPK{FMv>3=I75=M>NW$MEmLo%c)$eZXj_7i3^c=we`qaADwR&}3prnCQ;iP<-yY z`J7r|hR-Yv2b7o`7<>d77`iza7!6z*7<7~vSPqCVGHmDonio8(V0eTB!-fN;vGe1P zez?AUR_VE)--@mQxp8kkzMS*N2xp(KD(Xk)T_u>IKfor$3#g2{n_LBNB71L$Z@ zpqm{O7!?>8I8+!|7#JE^8i0x?fXx*G`l-Qz$zfD6D3q~=#)j(RbE~861=4L_@3Px_ zbK|<#m)Dk^v0SgUKbTR0fuZcZ8Urw9B+o}P26!_tdasnsG2R=#|MaocAx>&tP#AB;N{*kXC6H1_yCqbco~u z-4n~fz!HYls^*5I#a**7~0({K0o{Ze&L1+V4@l=5OC#jqKd~k z`V7zJ$X_e14J~~(C!Upo;lB&R98-pKg}^d(zdDYRb%7lNL&A5KhS3sNf#UjS7bpT= zOC+zfDL!`{SQnQ)YGklDEy%#IL68w-`j+hsGm7`EtNZhz`?%0*V2EGhV@~K+W-$9A YJhw~C@>s$0j|@QI>FVdQ&MBb@08S8pumAu6 diff --git a/test/fixtures/controller.bar/borderWidth/object.png b/test/fixtures/controller.bar/borderWidth/object.png index 7dfda348aaab4e81ecd9bd69d8ca9c7c8f44218d..04576006af463429104cd7595d23318d113cf6cd 100644 GIT binary patch literal 2113 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is>yI6Pe(Ln`LHy|pnjHC&`M z@#wk(XCnT@ zzVF|&pJ9b61B2KPj)qtL5BTSO)nmxwWMKFw&Zxk!ij$#%m8F4Ug&I(S00Yy4Squyj zz^H1-Vq!>O991xM!lAK~@xzPBLJa(Nz?d@v#vBVIqXDS}{R}%^9R6Li{qb_{hVFNt zzSea!Ffcrb&)a_c`Rb&vWxH#4-TwIUknDWvd#^+78SbgSV@QyIn6RLevEba#Z!3P! zFW$F=#o?=L{gXMLbr=}heseSwpPT+JeQxo-rK}EL@Bh8`{l7IZmHqH%X<&G9Lj|Zr z7?_-rCj+y-A22=j`2w9~0W{Rg44C8|jw%}NLEzxWVDpIs64k&$2~^O7DTjQ9n7p#~W!Hm(3=AoPh`RKJGJ~EKQ^WFlhNn_zA9%NJ23ETa N44$rjF6*2UngBaj#$^Bi literal 2269 zcmeH|Ye-XJ9LArs9pz}QmP;*>TxAv!XePDN+%iKKO)V8kQdbm`S{X)LI`tG;NEfw> z-Bb+3q*An47i;a5-AFLVGQqUVhzMOX#Ev+(^&X49^{o#DzF*$=!+ZI^&-459lqJT; zN<95N0Z7zwF&hC9SyEu!$@I9QGYeqq)iJA+_0`tiCE1&^W|nSgdXVD#ac;$vBHxqG zb6OU;Tq&2_8e9E7gh^#X%W^{=93QAxFJac7tIHp$9F80^URav_HA0-9FLuq->2yk~ z$yLx$sJpYhX~eN&-!-Gt(Cktoatb8}7KSSj)nwe&b&Gc{1{#MKBI;1!&3DZVYdOZ7 zd1pG1_;~}ys|-yh-j4#r45?N%XX0=WXrPp4fw~Gvff?T9H*B;>MGnP*0X#h>H~(1; zPG4wyQ$AaMxo=N^GIMZrOBhE177=exq41x!ypRM#37q?Z)n%W3+19Dd61K zFg%>r%cr_?S5Nhup&!$Tpl$0w-2Kk9fiut-5EooAU|24gLTC-K=76wcB_)UGA`NaY zsfk|Z0rt!9H;7Tvo*29dl)4Xj@PTBQ|Ui=Vvcrw{+IMi>22XwyC2b5EVQ35K+s zzJ}d4$0opQxdNT{s-UnE+w+L+1gz+@l+<~}k-h^4rlilQ0rHF;P}tXr-Ve$EjQ8Xh zLvR0X4e^UGV2A>HgY>+dbhBhCdqh^;ToCTYr43kSDa9E%MLt0LDo=LaXiL%UV4Se% z&llTxo)39RfSPnlCJKSqp6^YI$3Y=Mngg_v)b~17P zM*b#=wKhjfcblfa-%cVt**^eTwItAwPRQHl#hUo(<86-B04FUMy?-UcTQbHacN@zx S=BI0iL56y5d`xq+_TUdv%Neu) diff --git a/test/fixtures/controller.bar/borderWidth/scriptable-object.png b/test/fixtures/controller.bar/borderWidth/scriptable-object.png index 9fa5d45a772b1e1f81f37c43ee09789ae7cb1d4f..10e65f5ced56ec31adbd1f15db2e98ef96bd1f45 100644 GIT binary patch literal 1640 zcmeAS@N?(olHy`uVBq!ia0y~yU;;83893O0)X@p&(is@o^gUf1Ln`LHy=K^z94^sv z(Q{6vpzv9-IaQqJ=4!n1ogr5Ka`KG}Y>Bscmxix6$GKEol3UjO;{ z@wDH6|6Mq6r1H*>r91ILMidh&K-dF-% z@J)u1fuS0xjA5M$P{jnGdPkrz8z%#U#4r?iu(0^t`{!2o?cevbQifFZ#z z%lP3E9{MVaLmLCbf>{hF1Q-}Xgc&567#gye6r33t0(==#fZ@4{vw??^!NHVqf+7RM z3N;2Jpkbq$s2mUs4fU1v_5a_-JU?!4ea_PU-|NG`y~#i|T?$_z%R znk#bW*LJKfXZn!B^k8f4H^p`LI1fzbY`FgIX8VpRbq0SmhPd15@{eB4Ww4pa08?z> zHJS|Qk{=ihJb;Cn0AFaA4j1DR-sJY;>jO0ZVTNPgg&ebxsLQ02i2Y AmH+?% delta 396 zcmaFCbC73(NGjRv`wvNkDN&CWgu3jQW!m7}J2*!@gnpyLYE+CmetL@!NZu{^O6|&VRh$ zu1J3UGZqGh^K%#~GJ!@{JYzZFZOzDVz*wCj?iSFRd6tYHF8MJqT=1FvkSTGq0}C7D z}Ufqnx=kM42+g~Yh=kD_#pZ?vx9-sE}-){MLHarZ^faaD1 z&CMuhdT7#hmd8SdQ$nty990gFGJ;cS>*2Go(pq~OfJ5a7#@!ok3> q`7UEL#yh8?>_y`{`U{V2MHzz1_px{rVQsS>+fWLp7VJ% z149G55YQnFj^|| zfq{(^==uW+j0y}45-OvL@q~u|`#Hsx-;dXBFFyD4$1^#3`HFt;Pv)Y)aDVU;sBHo; z1W)Z@bNHqBLx1*tR)=W}3=ixD8JH9t85pKiUSqI$UbFwnVK zFYkU|x%u~<&#Zh642%j4lYTQXENB7d(v!7}3;|9I91TzQF)(N-GO#!l#RC(O05CD_ z)MsGu;sB<$JK_ussw@o*9`Cq;)`HU0cUA@m&QW8AL)dI%dJyN%z+htuj7}C%>0EG| zq2f#P_sDmQ2POl{6bEoYzMYeSp&|>!F&Hh%sZp^lNat;^mIqdcVvGvF>hOyc1HTop gdY5wqW<~ek(#vKsK5(4q;K=|4p00i_>zopr0OWP4(f|Me literal 2222 zcmeH|T}YEr9LAsb-PBjjoEpx|bVTSyn393ZmQIf=ErRrHBcky9g&%Iyd|%{8)|oy{+>G-Fefc;JG;Ga5((WInVRsJas4K zd0`>3AppV(@@1z0noKD$fn?}64xI;>iUL`7k-B+)^w^!NHmbHX*&?={-NiJj(t7WW z*bUR;O`qw;Gk42X##Hs5k?3ae+vnl8va_O#6qTLkAo-z>Us@Y<3jNaVjfBqcm-Lch zcUE|YUZXWR#Xj$CZQQ`G<-8gnTmo7Oq|Pd_;Sn3Y_6%#x{z$+jU8L=bgx4DLjDp-P zes+*&PpDiAaJ0~1FDT++d$QzD1Rf@W58ULlK#36oU`Hf*3!DA7Xg(MqFqk~dBBDe< z%>M1oo2sF%n$hUi7eXC^9ERU~dm* zyMiov2_zKCqCgPgB4ZU`+mg1^VbgT@zhJed1z9xaiM<1tkDJ|+6e4qXQaD?mK~g!n xjAxe&AMiSSkAy3gT&&M2Q9Vmca*2o$k}*BiB9B^A4QgXRT0yQ{_A;mB#vkdQ9*+P3 diff --git a/test/fixtures/controller.bar/horizontal-borders.png b/test/fixtures/controller.bar/horizontal-borders.png index 720519ac572749019d404d5500f71e646c772b77..d6a10eab0955b3444a4715d65b3835baa5b6b6b8 100644 GIT binary patch literal 1522 zcmcIkZEQW2i)R*M+Z%ZfIVWtvURlqH5)vJneuEL*RSWJs{p z_k3(9wsaJu)6J|Bl+bm$E{hbEh}Bv&%T~=wd%gE|MxqjaxPQ)j&wJi;&hwm)1DP4i z<3>&%2>@|2sYDJCg^n1+MkDd4vVAka2%}80I7@Z*)9a*zQ?`s-_F_#%=bNJ<@8yXd zQPVHgvJa(RdwwBZXnD}VX`eOzgZy5}+t0Ns%B;#1jzUiU<5$|?30yRGIdQ1!>>bE0 z(Y1+TnyY{CYR^UhPE(OX#~c)5NismJom<60@Osxh9SVh3 z;wT5T!PQr%ZeOLQ8uS>L#3DFcgXC)t`3}*(Z63un^@#xPI?kKZ&H7LX_S?CY34ugU@TwWy6oJa9z z`4Tk)-#Cz0&#ltBP$DbJ_6yR)ELiPM7xWIsgGr80lUc0*Cjva#uJUNOVXu(d`b9tv zyJ&KJYSHGU8lsmaVTQm}&o9wU`2 zA}Kl?4u?=C(s{YR5%*sdM4_^KA`_37dM=}FhNS^j<3m61tkqSc*+erjFdqy>rN`&$ z0wG9b5!JaU?MjSqxrPj%KxNM=WRnll5|lD88MH-DY|FiF%cA~elRO(^zErb&KkSc1rk0xRm^hZ5W1RSXv9(?5^1Y+nii zxJjVhfSPAm_E8`;w}(q7P2EW+`Nv8R_kySSzO*{0OdX*TJ;rre-Pb+Y zv+z0Ew<1>H(SXHl*gc8N&*{$fFcYxptg5r z8xT=Y5#k`lxeAlo#>h+NnsypXYo4V3kNHkN|k0B?jIr5&T@)lL4?YP$YOa?(;h5P_Q^T`APnTP32Y3 z`b3r)c1$NL?FbqC!~Nfb1NWT$CsSa772pqK1gyrxL*{u(X3Q%E7)j6r+*!Qa6*3A0)ePcZ3n8&}In4DB@k*=db(1x1s zsur=ijkgJin84K{|*x25^=^hs;)gFfuwZmbMxPMj9K`{b+b;kJE@2DIHSF ztx%cJjX`;^Km3-(0V4QooG*Qy3c?t*TCFAOf{=g;1Bx+cX^$Y3LcNmct8>+Q$^p3b z%6ZQAHQFSKT~QrB$z;U^vW~`Q0)C>Y{Woomm3^cjBoCu=Ty zi&RrY!nf_XMILGhTCbnqkg27P56_+tvdH4l zfw#!r+Ydx$HBlERUva4AV=BuNi3#Xzw4NZu%V3|%7DJYmsm+sgu)J1WU1IR9zS3iN zX>d6Gi^^uj!zJdnoZkDJGEi?cAYIMPTzW}ojHGt)FHG*~YsSkfgv$INbOp|>?NB$M zQ7rvVWb_n%_itk>w``=pd(j77-yS?gnanXsQ@e@B~)o-=^^)%>34J!`?*NGhpt z9xhP6^B^>-x<)4^CPcfCyM$) Date: Thu, 21 Feb 2019 09:28:43 +0200 Subject: [PATCH 12/13] skippy --- src/elements/element.rectangle.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index ac55a72e950..d5be9b46191 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -78,7 +78,7 @@ function parseBorderSkipped(vm) { function parseBorderWidth(vm, maxW, maxH) { var value = vm.borderWidth; - var _skip = parseBorderSkipped(vm); + var skip = parseBorderSkipped(vm); var t, r, b, l; if (helpers.isObject(value)) { @@ -91,10 +91,10 @@ function parseBorderWidth(vm, maxW, maxH) { } return { - t: _skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, - r: _skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, - b: _skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, - l: _skip.left || (l < 0) ? 0 : l > maxW ? maxW : l + t: skip.top || (t < 0) ? 0 : t > maxH ? maxH : t, + r: skip.right || (r < 0) ? 0 : r > maxW ? maxW : r, + b: skip.bottom || (b < 0) ? 0 : b > maxH ? maxH : b, + l: skip.left || (l < 0) ? 0 : l > maxW ? maxW : l }; } From 02df3ef329ae2a3ca038e4286d3a4a9a5936409c Mon Sep 17 00:00:00 2001 From: Jukka Kurkela Date: Thu, 21 Feb 2019 10:54:57 +0200 Subject: [PATCH 13/13] refactor inRange functions (cc be happy) --- src/elements/element.rectangle.js | 45 ++++++++++++------------------- 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index d5be9b46191..5e5a2eac459 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -18,7 +18,7 @@ defaults._set('global', { }); function isVertical(vm) { - return vm.width !== undefined; + return vm && vm.width !== undefined; } /** @@ -120,6 +120,16 @@ function boundingRects(vm) { }; } +function inRange(vm, x, y) { + var skipX = x === null; + var skipY = y === null; + var bounds = !vm || (skipX && skipY) ? false : getBarBounds(vm); + + return bounds + && (skipX || x >= bounds.left && x <= bounds.right) + && (skipY || y >= bounds.top && y <= bounds.bottom); +} + module.exports = Element.extend({ draw: function() { var ctx = this._chart.ctx; @@ -151,43 +161,22 @@ module.exports = Element.extend({ }, inRange: function(mouseX, mouseY) { - var inRange = false; - var vm = this._view; - - if (vm) { - var bounds = getBarBounds(vm); - inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; - } - - return inRange; + return inRange(this._view, mouseX, mouseY); }, inLabelRange: function(mouseX, mouseY) { var vm = this._view; - if (!vm) { - return false; - } - - var inRange = false; - var bounds = getBarBounds(vm); - - if (isVertical(vm)) { - inRange = mouseX >= bounds.left && mouseX <= bounds.right; - } else { - inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; - } - - return inRange; + return isVertical(vm) + ? inRange(vm, mouseX, null) + : inRange(vm, null, mouseY); }, inXRange: function(mouseX) { - var bounds = getBarBounds(this._view); - return mouseX >= bounds.left && mouseX <= bounds.right; + return inRange(this._view, mouseX, null); }, inYRange: function(mouseY) { - var bounds = getBarBounds(this._view); - return mouseY >= bounds.top && mouseY <= bounds.bottom; + return inRange(this._view, null, mouseY); }, getCenterPoint: function() {