mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Implemented RTL support for legends and tooltips (#6460)
Implemented RTL support for legends and tooltips
This commit is contained in:
parent
995efa5f9e
commit
376da2131e
@ -16,6 +16,8 @@ The legend configuration is passed into the `options.legend` namespace. The glob
|
||||
| `onLeave` | `function` | | A callback that is called when a 'mousemove' event is registered outside of a previously hovered label item.
|
||||
| `reverse` | `boolean` | `false` | Legend will show datasets in reverse order.
|
||||
| `labels` | `object` | | See the [Legend Label Configuration](#legend-label-configuration) section below.
|
||||
| `rtl` | `boolean` | | `true` for rendering the legends from right to left.
|
||||
| `textDirection` | `string` | canvas' default | This will force the text direction `'rtl'|'ltr` on the canvas for rendering the legend, regardless of the css specified on the canvas
|
||||
|
||||
## Position
|
||||
Position of the legend. Options are:
|
||||
|
||||
@ -44,6 +44,8 @@ The tooltip configuration is passed into the `options.tooltips` namespace. The g
|
||||
| `displayColors` | `boolean` | `true` | If true, color boxes are shown in the tooltip.
|
||||
| `borderColor` | `Color` | `'rgba(0, 0, 0, 0)'` | Color of the border.
|
||||
| `borderWidth` | `number` | `0` | Size of the border.
|
||||
| `rtl` | `boolean` | | `true` for rendering the legends from right to left.
|
||||
| `textDirection` | `string` | canvas' default | This will force the text direction `'rtl'|'ltr` on the canvas for rendering the tooltips, regardless of the css specified on the canvas
|
||||
|
||||
### Position Modes
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ var Element = require('./core.element');
|
||||
var helpers = require('../helpers/index');
|
||||
|
||||
var valueOrDefault = helpers.valueOrDefault;
|
||||
var getRtlHelper = helpers.rtl.getRtlAdapter;
|
||||
|
||||
defaults._set('global', {
|
||||
tooltips: {
|
||||
@ -242,6 +243,10 @@ function getBaseModel(tooltipOpts) {
|
||||
xAlign: tooltipOpts.xAlign,
|
||||
yAlign: tooltipOpts.yAlign,
|
||||
|
||||
// Drawing direction and text direction
|
||||
rtl: tooltipOpts.rtl,
|
||||
textDirection: tooltipOpts.textDirection,
|
||||
|
||||
// Body
|
||||
bodyFontColor: tooltipOpts.bodyFontColor,
|
||||
_bodyFontFamily: valueOrDefault(tooltipOpts.bodyFontFamily, globalDefaults.defaultFontFamily),
|
||||
@ -752,9 +757,11 @@ var exports = Element.extend({
|
||||
var titleFontSize, titleSpacing, i;
|
||||
|
||||
if (length) {
|
||||
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
|
||||
|
||||
pt.x = getAlignedX(vm, vm._titleAlign);
|
||||
|
||||
ctx.textAlign = vm._titleAlign;
|
||||
ctx.textAlign = rtlHelper.textAlign(vm._titleAlign);
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
titleFontSize = vm.titleFontSize;
|
||||
@ -764,7 +771,7 @@ var exports = Element.extend({
|
||||
ctx.font = helpers.fontString(titleFontSize, vm._titleFontStyle, vm._titleFontFamily);
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
ctx.fillText(title[i], pt.x, pt.y + titleFontSize / 2);
|
||||
ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFontSize / 2);
|
||||
pt.y += titleFontSize + titleSpacing; // Line Height and spacing
|
||||
|
||||
if (i + 1 === length) {
|
||||
@ -783,24 +790,27 @@ var exports = Element.extend({
|
||||
var xLinePadding = 0;
|
||||
var colorX = drawColorBoxes ? getAlignedX(vm, 'left') : 0;
|
||||
|
||||
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
|
||||
|
||||
var fillLineOfText = function(line) {
|
||||
ctx.fillText(line, pt.x + xLinePadding, pt.y + bodyFontSize / 2);
|
||||
ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyFontSize / 2);
|
||||
pt.y += bodyFontSize + bodySpacing;
|
||||
};
|
||||
|
||||
var bodyItem, textColor, labelColors, lines, i, j, ilen, jlen;
|
||||
var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
|
||||
|
||||
ctx.textAlign = bodyAlign;
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.font = helpers.fontString(bodyFontSize, vm._bodyFontStyle, vm._bodyFontFamily);
|
||||
|
||||
pt.x = getAlignedX(vm, bodyAlign);
|
||||
pt.x = getAlignedX(vm, bodyAlignForCalculation);
|
||||
|
||||
// Before body lines
|
||||
ctx.fillStyle = vm.bodyFontColor;
|
||||
helpers.each(vm.beforeBody, fillLineOfText);
|
||||
|
||||
xLinePadding = drawColorBoxes && bodyAlign !== 'right'
|
||||
xLinePadding = drawColorBoxes && bodyAlignForCalculation !== 'right'
|
||||
? bodyAlign === 'center' ? (bodyFontSize / 2 + 1) : (bodyFontSize + 2)
|
||||
: 0;
|
||||
|
||||
@ -817,18 +827,20 @@ var exports = Element.extend({
|
||||
for (j = 0, jlen = lines.length; j < jlen; ++j) {
|
||||
// Draw Legend-like boxes if needed
|
||||
if (drawColorBoxes) {
|
||||
var rtlColorX = rtlHelper.x(colorX);
|
||||
|
||||
// Fill a white rect so that colours merge nicely if the opacity is < 1
|
||||
ctx.fillStyle = vm.legendColorBackground;
|
||||
ctx.fillRect(colorX, pt.y, bodyFontSize, bodyFontSize);
|
||||
ctx.fillRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
|
||||
|
||||
// Border
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeStyle = labelColors.borderColor;
|
||||
ctx.strokeRect(colorX, pt.y, bodyFontSize, bodyFontSize);
|
||||
ctx.strokeRect(rtlHelper.leftForLtr(rtlColorX, bodyFontSize), pt.y, bodyFontSize, bodyFontSize);
|
||||
|
||||
// Inner square
|
||||
ctx.fillStyle = labelColors.backgroundColor;
|
||||
ctx.fillRect(colorX + 1, pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
|
||||
ctx.fillRect(rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), bodyFontSize - 2), pt.y + 1, bodyFontSize - 2, bodyFontSize - 2);
|
||||
ctx.fillStyle = textColor;
|
||||
}
|
||||
|
||||
@ -852,10 +864,12 @@ var exports = Element.extend({
|
||||
var footerFontSize, i;
|
||||
|
||||
if (length) {
|
||||
var rtlHelper = getRtlHelper(vm.rtl, vm.x, vm.width);
|
||||
|
||||
pt.x = getAlignedX(vm, vm._footerAlign);
|
||||
pt.y += vm.footerMarginTop;
|
||||
|
||||
ctx.textAlign = vm._footerAlign;
|
||||
ctx.textAlign = rtlHelper.textAlign(vm._footerAlign);
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
footerFontSize = vm.footerFontSize;
|
||||
@ -864,7 +878,7 @@ var exports = Element.extend({
|
||||
ctx.font = helpers.fontString(footerFontSize, vm._footerFontStyle, vm._footerFontFamily);
|
||||
|
||||
for (i = 0; i < length; ++i) {
|
||||
ctx.fillText(footer[i], pt.x, pt.y + footerFontSize / 2);
|
||||
ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFontSize / 2);
|
||||
pt.y += footerFontSize + vm.footerSpacing;
|
||||
}
|
||||
}
|
||||
@ -946,6 +960,8 @@ var exports = Element.extend({
|
||||
// Draw Title, Body, and Footer
|
||||
pt.y += vm.yPadding;
|
||||
|
||||
helpers.rtl.overrideTextDirection(ctx, vm.textDirection);
|
||||
|
||||
// Titles
|
||||
this.drawTitle(pt, vm, ctx);
|
||||
|
||||
@ -955,6 +971,8 @@ var exports = Element.extend({
|
||||
// Footer
|
||||
this.drawFooter(pt, vm, ctx);
|
||||
|
||||
helpers.rtl.restoreTextDirection(ctx, vm.textDirection);
|
||||
|
||||
ctx.restore();
|
||||
}
|
||||
},
|
||||
|
||||
75
src/helpers/helpers.rtl.js
Normal file
75
src/helpers/helpers.rtl.js
Normal file
@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
|
||||
var getRtlAdapter = function(rectX, width) {
|
||||
return {
|
||||
x: function(x) {
|
||||
return rectX + rectX + width - x;
|
||||
},
|
||||
setWidth: function(w) {
|
||||
width = w;
|
||||
},
|
||||
textAlign: function(align) {
|
||||
if (align === 'center') {
|
||||
return align;
|
||||
}
|
||||
return align === 'right' ? 'left' : 'right';
|
||||
},
|
||||
xPlus: function(x, value) {
|
||||
return x - value;
|
||||
},
|
||||
leftForLtr: function(x, itemWidth) {
|
||||
return x - itemWidth;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var getLtrAdapter = function() {
|
||||
return {
|
||||
x: function(x) {
|
||||
return x;
|
||||
},
|
||||
setWidth: function(w) { // eslint-disable-line no-unused-vars
|
||||
},
|
||||
textAlign: function(align) {
|
||||
return align;
|
||||
},
|
||||
xPlus: function(x, value) {
|
||||
return x + value;
|
||||
},
|
||||
leftForLtr: function(x, _itemWidth) { // eslint-disable-line no-unused-vars
|
||||
return x;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
var getAdapter = function(rtl, rectX, width) {
|
||||
return rtl ? getRtlAdapter(rectX, width) : getLtrAdapter();
|
||||
};
|
||||
|
||||
var overrideTextDirection = function(ctx, direction) {
|
||||
var style, original;
|
||||
if (direction === 'ltr' || direction === 'rtl') {
|
||||
style = ctx.canvas.style;
|
||||
original = [
|
||||
style.getPropertyValue('direction'),
|
||||
style.getPropertyPriority('direction'),
|
||||
];
|
||||
|
||||
style.setProperty('direction', direction, 'important');
|
||||
ctx.prevTextDirection = original;
|
||||
}
|
||||
};
|
||||
|
||||
var restoreTextDirection = function(ctx) {
|
||||
var original = ctx.prevTextDirection;
|
||||
if (original !== undefined) {
|
||||
delete ctx.prevTextDirection;
|
||||
ctx.canvas.style.setProperty('direction', original[0], original[1]);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
getRtlAdapter: getAdapter,
|
||||
overrideTextDirection: overrideTextDirection,
|
||||
restoreTextDirection: restoreTextDirection,
|
||||
};
|
||||
@ -5,3 +5,4 @@ module.exports.easing = require('./helpers.easing');
|
||||
module.exports.canvas = require('./helpers.canvas');
|
||||
module.exports.options = require('./helpers.options');
|
||||
module.exports.math = require('./helpers.math');
|
||||
module.exports.rtl = require('./helpers.rtl');
|
||||
|
||||
@ -5,6 +5,7 @@ var Element = require('../core/core.element');
|
||||
var helpers = require('../helpers/index');
|
||||
var layouts = require('../core/core.layouts');
|
||||
|
||||
var getRtlHelper = helpers.rtl.getRtlAdapter;
|
||||
var noop = helpers.noop;
|
||||
var valueOrDefault = helpers.valueOrDefault;
|
||||
|
||||
@ -355,6 +356,7 @@ var Legend = Element.extend({
|
||||
return;
|
||||
}
|
||||
|
||||
var rtlHelper = getRtlHelper(opts.rtl, me.left, me.minSize.width);
|
||||
var ctx = me.ctx;
|
||||
var fontColor = valueOrDefault(labelOpts.fontColor, globalDefaults.defaultFontColor);
|
||||
var labelFont = helpers.options._parseFont(labelOpts);
|
||||
@ -362,7 +364,7 @@ var Legend = Element.extend({
|
||||
var cursor;
|
||||
|
||||
// Canvas setup
|
||||
ctx.textAlign = 'left';
|
||||
ctx.textAlign = rtlHelper.textAlign('left');
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.lineWidth = 0.5;
|
||||
ctx.strokeStyle = fontColor; // for strikethrough effect
|
||||
@ -398,24 +400,25 @@ var Legend = Element.extend({
|
||||
// Recalculate x and y for drawPoint() because its expecting
|
||||
// x and y to be center of figure (instead of top left)
|
||||
var radius = boxWidth * Math.SQRT2 / 2;
|
||||
var centerX = x + boxWidth / 2;
|
||||
var centerX = rtlHelper.xPlus(x, boxWidth / 2);
|
||||
var centerY = y + fontSize / 2;
|
||||
|
||||
// Draw pointStyle as legend symbol
|
||||
helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY, legendItem.rotation);
|
||||
} else {
|
||||
// Draw box as legend symbol
|
||||
ctx.fillRect(x, y, boxWidth, fontSize);
|
||||
ctx.fillRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
|
||||
if (lineWidth !== 0) {
|
||||
ctx.strokeRect(x, y, boxWidth, fontSize);
|
||||
ctx.strokeRect(rtlHelper.leftForLtr(x, boxWidth), y, boxWidth, fontSize);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
};
|
||||
|
||||
var fillText = function(x, y, legendItem, textWidth) {
|
||||
var halfFontSize = fontSize / 2;
|
||||
var xLeft = boxWidth + halfFontSize + x;
|
||||
var xLeft = rtlHelper.xPlus(x, boxWidth + halfFontSize);
|
||||
var yMiddle = y + halfFontSize;
|
||||
|
||||
ctx.fillText(legendItem.text, xLeft, yMiddle);
|
||||
@ -425,7 +428,7 @@ var Legend = Element.extend({
|
||||
ctx.beginPath();
|
||||
ctx.lineWidth = 2;
|
||||
ctx.moveTo(xLeft, yMiddle);
|
||||
ctx.lineTo(xLeft + textWidth, yMiddle);
|
||||
ctx.lineTo(rtlHelper.xPlus(xLeft, textWidth), yMiddle);
|
||||
ctx.stroke();
|
||||
}
|
||||
};
|
||||
@ -457,6 +460,8 @@ var Legend = Element.extend({
|
||||
};
|
||||
}
|
||||
|
||||
helpers.rtl.overrideTextDirection(me.ctx, opts.textDirection);
|
||||
|
||||
var itemHeight = fontSize + labelOpts.padding;
|
||||
helpers.each(me.legendItems, function(legendItem, i) {
|
||||
var textWidth = ctx.measureText(legendItem.text).width;
|
||||
@ -464,6 +469,8 @@ var Legend = Element.extend({
|
||||
var x = cursor.x;
|
||||
var y = cursor.y;
|
||||
|
||||
rtlHelper.setWidth(me.minSize.width);
|
||||
|
||||
// Use (me.left + me.minSize.width) and (me.top + me.minSize.height)
|
||||
// instead of me.right and me.bottom because me.width and me.height
|
||||
// may have been changed since me.minSize was calculated
|
||||
@ -479,13 +486,15 @@ var Legend = Element.extend({
|
||||
y = cursor.y = me.top + alignmentOffset(legendHeight, columnHeights[cursor.line]);
|
||||
}
|
||||
|
||||
drawLegendBox(x, y, legendItem);
|
||||
var realX = rtlHelper.x(x);
|
||||
|
||||
hitboxes[i].left = x;
|
||||
drawLegendBox(realX, y, legendItem);
|
||||
|
||||
hitboxes[i].left = rtlHelper.leftForLtr(realX, hitboxes[i].width);
|
||||
hitboxes[i].top = y;
|
||||
|
||||
// Fill the actual label
|
||||
fillText(x, y, legendItem, textWidth);
|
||||
fillText(realX, y, legendItem, textWidth);
|
||||
|
||||
if (isHorizontal) {
|
||||
cursor.x += width + labelOpts.padding;
|
||||
@ -493,6 +502,8 @@ var Legend = Element.extend({
|
||||
cursor.y += itemHeight;
|
||||
}
|
||||
});
|
||||
|
||||
helpers.rtl.restoreTextDirection(me.ctx, opts.textDirection);
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user