@amcharts/amcharts4
Version:
amCharts 4
1,133 lines • 61.2 kB
JavaScript
/**
* Text class deals with all text placed on chart.
*/
import { __extends } from "tslib";
/**
* ============================================================================
* IMPORTS
* ============================================================================
* @hidden
*/
import { Container } from "../Container";
import { registry } from "../Registry";
import { getTextFormatter } from "../formatters/TextFormatter";
import { MultiDisposer } from "../utils/Disposer";
import { InterfaceColorSet } from "../../core/utils/InterfaceColorSet";
import * as $math from "../utils/Math";
import * as $utils from "../utils/Utils";
import * as $type from "../utils/Type";
import * as $dom from "../utils/DOM";
import { defaultRules, ResponsiveBreakpoints } from "../utils/Responsive";
import { options } from "../Options";
;
/**
* ============================================================================
* MAIN CLASS
* ============================================================================
* @hidden
*/
/**
* Text is used to display highly configurable, data-enabled textual elements.
*
* ## Data Binding
*
* A Text element can dynamically parse and populate its contents with values
* from a [[DataItem]].
*
* To activate such binding, set element's `dataItem` property.
*
* When activated, text contents will be parsed for special tags, e.g.:
*
* ```TypeScript
* label.dataItem = myDataItem;
* label.text = "The title is: {title}";
* ```
* ```JavaScript
* label.dataItem = myDataItem;
* label.text = "The title is: {title}";
* ```
*
* The above will automatically replace "{title}" in the string with the
* actual data value from `myDataItem`.
*
* Note, that most often dataItem is set by the Component.
*
*
* @see {@link ILabelEvents} for a list of available events
* @see {@link ILabelAdapters} for a list of available Adapters
* @see {@link https://www.amcharts.com/docs/v4/concepts/formatters/formatting-strings/} for info on string formatting and data binding
* @todo Vertical align
* @important
*/
var Label = /** @class */ (function (_super) {
__extends(Label, _super);
/**
* Constructor
*/
function Label() {
var _this =
// Execute super's constructor
_super.call(this) || this;
/**
* Indicates if the whole text does not fit into max dimenstions set for it.
*/
_this.isOversized = false;
// Set this class name
_this.className = "Label";
_this.fill = new InterfaceColorSet().getFor("text");
// not good to set this, as then these will appear on each label and values set on container won't be applied.
//this.textDecoration = "none";
//this.fontWeight = "normal";
// Set defaults
_this.wrap = false;
_this.truncate = false;
_this.fullWords = true;
_this.ellipsis = "…";
_this.textAlign = "start";
_this.textValign = "top";
_this.layout = "absolute";
_this.baseLineRatio = -0.27;
//this.pixelPerfect = true;
_this._positionPrecision = 1;
// Add events to watch for maxWidth/maxHeight changes so that we can
// invalidate this
_this.events.on("maxsizechanged", function () {
if (_this.inited) {
_this.handleMaxSize();
}
}, _this, false);
// this solves strange bug when text just added to svg is 0x0
_this.events.once("validated", _this.handleValidate, _this, false);
// Aply theme
_this.applyTheme();
return _this;
}
/**
* A placeholder method that is called **after** element finishes drawing
* itself.
*
* @ignore Exclude from docs
*/
Label.prototype.afterDraw = function () {
// since we removed validatePosition from sprite, we still need it here to handle rotated text
_super.prototype.afterDraw.call(this);
this.validatePosition();
};
/**
* Sets [[Paper]] instance to use to draw elements.
* @ignore
* @param paper Paper
* @return true if paper was changed, false, if it's the same
*/
Label.prototype.setPaper = function (paper) {
var changed = _super.prototype.setPaper.call(this, paper);
if (changed) {
this.hardInvalidate();
}
return changed;
};
/**
* @ignore
*/
Label.prototype.handleValidate = function () {
if ((this.currentText || this.text) && (this.bbox.width == 0 || this.bbox.height == 0)) {
registry.events.once("exitframe", this.hardInvalidate, this);
}
};
/**
* @ignore
*/
Label.prototype.handleMaxSize = function () {
if ((this.bbox.width > this.availableWidth)
|| ((this.bbox.width < this.availableWidth) && (this.isOversized || this.truncate))
|| (this.bbox.height > this.availableHeight)
|| ((this.bbox.height < this.availableHeight) && this.isOversized)) {
this.invalidate();
}
else {
//this.alignSVGText();
}
};
/**
* [arrange description]
*
* @ignore Exclude from docs
* @todo Description
*/
Label.prototype.arrange = function () {
};
/**
* Updates current text according to data item and supported features.
* Returns `true` if current text has changed.
*
* @return Text changed?
*/
Label.prototype.updateCurrentText = function () {
// Determine output format
var output, text;
if ($utils.isNotEmpty(this.html) && this.paper.supportsForeignObject()) {
// We favor HTML text if it's set and browser supports `foreignObject`
output = "html";
text = this.html;
}
else {
output = "svg";
text = this.text;
}
// Need to toString source?
if ($type.isObject(text)) {
text = text.toString();
}
// Need to format text all the time
if ($type.hasValue(text) && text !== "") {
text = this.populateString(text, this.dataItem);
}
if (output == "html") {
if (this._adapterO) {
text = this._adapterO.apply("htmlOutput", text);
}
}
else {
if (this._adapterO) {
text = this._adapterO.apply("textOutput", text);
}
}
// Update the text
var changed = text != this.currentText || output != this._currentFormat;
this.currentText = text;
this._currentFormat = output;
return changed;
};
/**
* Hard invalidate means the text will be redrawn even if it hasn't changed.
* This is used when we change `fontSize`, `fontFamily`, or for some other
* reasons.
*/
Label.prototype.hardInvalidate = function () {
this._prevStatus = "";
this.invalidate();
};
/**
* Gets line bbox, uses caching to save cpu
* @ignore
*/
Label.prototype.getLineBBox = function (lineInfo) {
//let cacheKey = lineInfo.text + lineInfo.style;
//let lineBBox = this.getCache(cacheKey);
//if (!lineBBox) {
//lineBBox = lineInfo.element.getBBox();
//if (lineBBox.width != 0 && lineBBox.height != 0) {
// this.setCache(cacheKey, lineBBox, 5000);
//}
//}
var element = lineInfo && lineInfo.element;
var node = element && element.node;
// Check for the parent Node to avoid FF from throwing errors
if (node && node.parentNode) {
lineInfo.bbox = element.getBBox();
}
};
/**
* Draws the textual label.
*
* @ignore Exclude from docs
*/
Label.prototype.draw = function () {
// Draw super
_super.prototype.draw.call(this);
var oldW = this.bbox.width;
var oldH = this.bbox.height;
var topParent = this.topParent;
if (topParent) {
if (!topParent.maxWidth || !topParent.maxHeight) {
topParent.events.once("maxsizechanged", this.hardInvalidate, this, false);
return;
}
}
// Calculate max width and height
var maxWidth = $math.max(this.availableWidth - this.pixelPaddingLeft - this.pixelPaddingRight, 0);
var maxHeight = $math.max(this.availableHeight - this.pixelPaddingTop - this.pixelPaddingBottom, 0);
// save
var status = maxHeight + "," + maxWidth + this.wrap + this.truncate + this.fullWords + this.rtl + this.ellipsis;
// Update text
if (!this.updateCurrentText() && this.inited && this._prevStatus == status) {
return;
}
this._measuredWidth = 0;
this._measuredHeight = 0;
// Reset
this.isOversized = false;
// Determine output format
var output = this._currentFormat;
var text = this.currentText;
// Empty string
if (!$type.hasValue(text) || text == "") {
this.element.attr({ display: "none" });
return;
}
// Chop up text into lines
// We're still processing SVG and HTML in the same way for now
var lines = text.split("\n");
// Do we need to go through the trouble of measuring lines
//let measure: boolean = true;// (lines.length > 1) || this.wrap;
this._prevStatus = status;
this.textAlign = this.textAlign;
// need this to measure
var display = this.group.getAttr("display");
if (display == "none") {
this.group.removeAttr("display");
}
if (this.textPathElement) {
this.textPathElement.removeChildren();
}
// SVG or HTML?
if (output === "svg") {
/**
* SVG
*/
this.element.removeAttr("display");
// Clear the element
var group = this.element;
//group.removeChildren();
this.resetBBox();
// Init state variables
var currentHeight = 0;
var currentFormat = "";
// Process each line
for (var i = 0; i < lines.length; i++) {
// Get line
var line = lines[i];
// Check if line is empty
if (line == "") {
// It is, let's just update currentHeight and go to the next one
// If it's the first line, we'll have to use arbirary line height,
// since there's nothing to measure. For subsequent lines we can take
// previous line's height
var tempElement = this.getSVGLineElement("", 0);
tempElement.add(this.getSvgElement(".", getTextFormatter().translateStyleShortcuts(currentFormat)));
group.add(tempElement);
var offset = Math.ceil(tempElement.getBBox().height);
if (offset > 0) {
currentHeight += offset;
}
group.removeElement(tempElement);
// Clear cache if necessary
var lineInfo_1 = this.getLineInfo(i);
if (lineInfo_1) {
lineInfo_1.text = "";
lineInfo_1.element.textContent = "";
}
continue;
}
// Chunk up the line and process each chunk
var chunks = getTextFormatter().chunk(line, null, this.ignoreFormatting);
var currentLineHeight = 0;
var firstChunk = true;
var skipTextChunks = false;
// Create line element or grab it from cache
var lineInfo = this.getLineInfo(i);
if (lineInfo) {
// Empty line
lineInfo.text = "";
lineInfo.element.textContent = "";
}
else {
// Init new line info
lineInfo = {
"text": "",
"element": this.getSVGLineElement("", 0),
"complex": false
};
// Create the line element
//lineInfo.element = this.getSVGLineElement("", 0);
//lineElement = this.getSVGLineElement("", 0);
group.add(lineInfo.element);
}
lineInfo.element.removeAttr("display");
lineInfo.element.removeChildren(); // memory leak without this
if (this.textPathElement) {
lineInfo.element.add(this.textPathElement);
}
/*// @todo not needed anymore
if (this.rtl) {
chunks.reverse();
}*/
// Process each chunk
for (var x = 0; x < chunks.length; x++) {
// If there's more than one chunk, means the line is "complex"
if (x) {
lineInfo.complex = true;
}
// Get chunk
var chunk = chunks[x];
// Is this chunk format or text?
if (chunk.type === "format") {
// Log current format, so that we can apply it to multiple lines if
// necessary
currentFormat = chunk.text;
}
else {
// It's text block
// Need to skip?
// We do this when truncating. We can't just simply go ahead and
// abandon chunk processing as they might have formatting
// instructions in them that are relevant for subsequent lines
if (skipTextChunks) {
continue;
}
// Add chunk to the current element
//lineInfo.element.content += $utils.trim(getTextFormatter().format(currentFormat + chunk.text, output));
lineInfo.text = chunk.text;
lineInfo.style = getTextFormatter().translateStyleShortcuts(currentFormat);
if (this.textPathElement) {
this.getSvgElement(lineInfo.text, lineInfo.style, this.textPathElement);
}
else {
this.getSvgElement(lineInfo.text, lineInfo.style, lineInfo.element);
}
this.getLineBBox(lineInfo);
lineInfo.bbox.width = Math.ceil(lineInfo.bbox.width);
// Updated current line height
if (currentLineHeight < lineInfo.bbox.height) {
currentLineHeight = lineInfo.bbox.height;
}
// Wrapping?
if ((this.wrap || this.truncate) && (lineInfo.bbox.width > maxWidth)) {
// Set oversized
this.isOversized = true;
// Take temporary measurements
var lineText = lineInfo.element.textContent;
var avgCharWidth = (lineInfo.bbox.width / lineText.length); // * .9;
// Calculate average number of symbols / width
var excessChars = $math.min(Math.ceil((lineInfo.bbox.width - maxWidth) / avgCharWidth), lineText.length);
// Are we truncating or auto-wrapping text?
if (this.truncate) {
/**
* Processing line truncation
* With the addition of each text chunk we measure if current
* line does not exceed maxWidth. If it does, we will stop
* addition of further chunks as well as try to truncate
* current or any number of previous chunks with an added
* ellipsis
*/
// Indicator whether we need to add ellipsis to the current
// element, even if it fits. This is needed to indicate
// whether we have already removed some subsequent chunks in
// which case we need to add ellipsis.
var addEllipsis = false;
// Process each child in the temporary line, until the whole
// line fits, preferably with an ellipsis
// TODO use iterator instead
var node_1 = lineInfo.element.node;
if (node_1 && node_1.childNodes) {
for (var e = lineInfo.element.node.childNodes.length - 1; e >= 0; e--) {
// Get current element
var node_2 = lineInfo.element.node.childNodes[e];
// Add ellipsis only if previous chunk was removed in full
// and this chunk already fits
//if (addEllipsis && (bbox.width <= maxWidth)) {
if (addEllipsis && (lineInfo.bbox.width <= maxWidth)) {
// Add ellipsis
node_2.textContent += " " + this.ellipsis;
// Measure again (we need to make sure ellipsis fits)
lineInfo.bbox = lineInfo.element.getBBox();
lineInfo.bbox.width = Math.floor(lineInfo.bbox.width);
// If it fits, we're done here
// If it doesn't we continue rolling
if (lineInfo.bbox.width <= maxWidth) {
break;
}
}
addEllipsis = false;
// Get element text
var elementText = node_2.textContent;
// Calculate average number of symbols / width
lineText = lineInfo.element.textContent;
excessChars = $math.min(Math.ceil((lineInfo.bbox.width - maxWidth) / avgCharWidth), lineText.length);
// Do this until we fit
while ((lineInfo.bbox.width > maxWidth) && (excessChars <= lineText.length) && (excessChars > 0)) {
// Calculate max available chars
var maxChars = $math.max(lineText.length - excessChars - this.ellipsis.length, 1);
// Is there anything left?
if (maxChars <= 1) {
// Nope, let's jump to the previous item
// Set excess characters to zero so that this loop does
// not repeat when it over
excessChars = 0;
// Add ellipsis to previous item
// Subsequent iterations will check if the ellipsis fits
if (e > 0) {
// Indicating to add ellipsis to previous item
addEllipsis = true;
// Removing this node
lineInfo.element.node.removeChild(node_2);
}
}
// Truncate the text
elementText = $utils.truncateWithEllipsis(elementText, maxChars, this.ellipsis, this.fullWords, this.rtl);
if ((elementText.length > maxChars) && this.fullWords) {
// Still too long?
// Let's try truncating breaking words anyway
elementText = $utils.truncateWithEllipsis(elementText, maxChars, this.ellipsis, false, this.rtl);
}
// Set truncated text
node_2.textContent = elementText;
// Measure again
lineInfo.bbox = lineInfo.element.getBBox();
lineInfo.bbox.width = Math.floor(lineInfo.bbox.width);
// Increase excess characters count, just in case it still
// doesn't fit and we have to go at it again
excessChars = Math.ceil(excessChars * 1.1);
}
// Do not process further chunks
skipTextChunks = true;
}
}
}
else {
/**
* Processign auto-wrap
* In this case we're going to be adding text chunks until
* they don't fit into current line. Once that happens we will
* inject the rest of the chunks to the next line
*/
// Get last node added and measure it
var node_3 = lineInfo.element.node;
if (node_3) {
var lastNode = lineInfo.element.node.lastChild;
// Init split lines
var splitLines = void 0;
while ((lineInfo.bbox.width > maxWidth) && (excessChars <= lineText.length) && (excessChars > 0)) {
// Calculate max available chars
var maxChars = $math.max(chunk.text.length - excessChars, 1);
// Don't split the words mid-word if it's not the first chunk
// in the line
if (firstChunk) {
// Split mid-word if necessary
splitLines = $utils.splitTextByCharCount(chunk.text, maxChars, true, this.rtl);
}
else {
// Don't split mid-word
splitLines = $utils.splitTextByCharCount(chunk.text, maxChars, true, this.rtl, false);
// Check if the first word is too long
if ((splitLines[0].length > maxChars) || maxChars === 1) {
// Yes - move the whole chunk to the next line
// Remove the element we just added
lineInfo.element.node.removeChild(lastNode);
// Break out of the while on next cycle
excessChars = 0;
}
}
// Use the first line to update last item
if (excessChars > 0) {
var lineText_1 = splitLines.shift();
if (firstChunk) {
lineText_1 = $utils.trim(lineText_1);
}
lastNode.textContent = getTextFormatter().cleanUp(lineText_1);
}
// Measure again, just in case
lineInfo.bbox = lineInfo.element.getBBox();
lineInfo.bbox.width = Math.floor(lineInfo.bbox.width);
// Increase excess characters count, just in case it still
// doesn't fit and we have to go at it again
//excessChars = Math.ceil(excessChars * 1.05);
excessChars++;
}
// Construct the rest of the line
if (splitLines.length > 0) {
var restOfLine = "";
// Add leftovers from splitting the current chunk
if ($type.hasValue(splitLines)) {
if (this.rtl) {
restOfLine += splitLines.join("") + currentFormat;
}
else {
restOfLine += currentFormat + splitLines.join("").replace(/([\[\]]{1})/g, "$1$1");
}
}
// Add the rest of the chunks
for (var c = x + 1; c < chunks.length; c++) {
if (chunks[c].type == "value") {
// We're escaping single square brackets that were
// cleaned up by chunk() back to double square brackets
// so that they are not being treated as format on
// next pass.
restOfLine += chunks[c].text.replace(/([\[\]]{1})/g, "$1$1");
}
else {
restOfLine += chunks[c].text;
}
}
// Inject the rest of the lines as chunks for subsequent
lines.splice(i + 1, 0, restOfLine);
}
// Skip processing the rest of the chunks
skipTextChunks = true;
}
}
}
// Let's update the text's bbox with the line's one
if (this.bbox.width < lineInfo.bbox.width) {
this.bbox.width = lineInfo.bbox.width;
}
// commented to avoid bug (seen on sankey link) where text is incorrectly aligned
//if (this.bbox.x > lineInfo.bbox.x) {
//this.bbox.x = lineInfo.bbox.x;
//}
this.bbox.height = currentHeight + currentLineHeight;
// Position current line
if (!this.textPathElement) {
lineInfo.element.attr({
"x": "0",
"y": currentHeight + currentLineHeight,
"dy": $math.round((this.baseLineRatio * currentLineHeight), 3).toString()
});
}
else {
lineInfo.element.attr({
"dy": -this.paddingBottom.toString()
});
}
firstChunk = false;
}
}
// Trim the last item
var node = lineInfo.element.node;
if (node) {
var lastNode = node.lastChild;
if (lastNode) {
lastNode.textContent = this.rtl ?
$utils.ltrim(lastNode.textContent) :
$utils.rtrim(lastNode.textContent);
}
}
// Increment collective height
currentHeight += currentLineHeight;
// Save line cache
this.addLineInfo(lineInfo, i);
}
// Check if maybe we need to hide the whole label if it doesn't fit
this.maybeHideOversized();
this.measureFailed = false;
if (this.bbox.width == 0 || this.bbox.height == 0) {
this.measureFailed = true;
}
// Updated measured dims
this._measuredWidth = $math.round($math.max(this.bbox.width, this.pixelWidth - this.pixelPaddingLeft - this.pixelPaddingRight));
this._measuredHeight = $math.round($math.max(this.bbox.height, this.pixelHeight - this.pixelPaddingTop - this.pixelPaddingBottom));
// Align the lines
this.alignSVGText();
this.bbox.width = this._measuredWidth;
this.bbox.height = this._measuredHeight;
if (oldH != this._measuredHeight || oldW != this._measuredWidth) {
this.dispatch("transformed");
}
this.hideUnused(lines.length);
}
else {
/**
* HTML
*/
this.element.removeAttr("display");
this.resetBBox();
// Clear the element
var group = this.element;
group.removeChildren();
this.setCache("lineInfo", [], 0);
// Create a ForeignObject to use as HTML container
var fo = this.paper.foreignObject();
group.add(fo);
// Set widths on foreignObject so that autosizing measurements work
// This will bet reset to actual content width/height
if (this.maxWidth) {
fo.attr({
width: this.maxWidth - this.pixelPaddingLeft - this.pixelPaddingRight
});
}
if (this.maxHeight) {
fo.attr({
height: this.maxHeight - this.pixelPaddingTop - this.pixelPaddingBottom
});
}
// Create line element
//let lineElement: HTMLElement = this.getHTMLLineElement(getTextFormatter().format(this.html, output));
var lineElement = this.getHTMLLineElement(text);
fo.node.appendChild(lineElement);
// Temporarily set to inline-block so we can measure real width and height
lineElement.style.display = "inline-block";
var clientWidth = lineElement.clientWidth;
var clientHeight = lineElement.clientHeight;
lineElement.style.display = "block";
this._bbox = {
x: 0,
y: 0,
width: clientWidth,
height: clientHeight
};
// Set exact dimensions of foreignObject so it is sized exactly as
// the content within (add one pixel to width so it does not wrap)
fo.attr({
width: clientWidth + 1,
height: clientHeight
});
// Check if maybe we need to hide the whole label if it doesn't fit
this.maybeHideOversized();
// Set measurements and update bbox
this._measuredWidth = $math.max(this.bbox.width, this.pixelWidth - this.pixelPaddingLeft - this.pixelPaddingRight);
this._measuredHeight = $math.max(this.bbox.height, this.pixelHeight - this.pixelPaddingTop - this.pixelPaddingBottom);
this.bbox.width = this._measuredWidth;
this.bbox.height = this._measuredHeight;
// Don't let labels bleed out of the alotted area
if (this.truncate) {
lineElement.style.overflow = "hidden";
}
if ((clientWidth > maxWidth) || (clientHeight > maxHeight)) {
this.isOversized = true;
}
}
// Set applicable styles
this.setStyles();
this.updateCenter();
this.updateBackground();
if (display == "none") {
this.group.attr({ display: "none" });
}
if (this.pathElement) {
this.paper.appendDef(this.pathElement);
}
};
/**
* Hides element if it does not fit into available space
*/
Label.prototype.maybeHideOversized = function () {
if (this.hideOversized) {
if ((this.availableWidth < this.bbox.width) || (this.availableHeight < this.bbox.height)) {
this.element.attr({ display: "none" });
this.isOversized = true;
}
else {
this.element.removeAttr("display");
this.isOversized = false;
}
}
};
/**
* Aligns the lines horizontally and vertically, based on properties.
*
* @ignore Exclude from docs
*/
Label.prototype.alignSVGText = function () {
// Get Group
var group = this.element;
var children = group.node.children || group.node.childNodes;
// Is there anything to align?
if (!children || (children && children.length == 0)) {
return;
}
var width = this._measuredWidth;
var height = this._measuredHeight;
// TODO maybe these aren't needed ?
$utils.used(this.pixelPaddingLeft);
$utils.used(this.pixelPaddingRight);
$utils.used(this.pixelPaddingTop);
$utils.used(this.pixelPaddingBottom);
if (this.rtl) {
group.attr({
"direction": "rtl"
});
}
else {
group.removeAttr("direction");
}
// Process each line
//$iter.each(group.children.backwards().iterator(), (element) => {
for (var i = children.length - 1; i >= 0; i--) {
// Align horizontally
// Since we are using `text-anchor` for horizontal alignment, all we need
// to do here is move the `x` position
var node = children[i];
node.setAttribute("text-anchor", this.textAlign);
if (this.textPathElement) {
node.removeAttribute("x");
node.removeAttribute("y");
}
else {
switch (this.textAlign) {
case "middle":
node.setAttribute("x", (width / 2).toString() + "px");
break;
case "end":
if (this.rtl) {
}
else {
node.setAttribute("x", width.toString());
}
break;
default:
if (this.rtl) {
node.setAttribute("x", width.toString());
}
else {
node.removeAttribute("text-anchor");
}
break;
}
var y = $type.toNumber(node.getAttribute("y"));
switch (this.textValign) {
case "middle":
node.setAttribute("y", ((y || 0) + (height - this.bbox.height) / 2).toString());
break;
case "bottom":
node.setAttribute("y", ((y || 0) + height - this.bbox.height).toString());
break;
default:
node.setAttribute("y", (y || 0).toString());
break;
}
}
}
};
/**
* Produces an SVG line element with formatted text.
*
* @ignore Exclude from docs
* @param text Text to wrap into line
* @param y Current line vertical position
* @return A DOM element
* @todo Implement HTML support
*/
Label.prototype.getSVGLineElement = function (text, y) {
// Create a <text> node and set text
var element = this.paper.addGroup("text");
element.textContent = text;
// Set parameters
element.attr({
"x": "0"
//"alignment-baseline": "hanging",
//"baseline-shift": "-20%",
//"text-anchor": "center"
});
// Set `y` position
if ($type.hasValue(y)) {
element.attr({
"y": y.toString()
});
}
// Don't let labels blled out of the alotted area
if (this.truncate || this.wrap) {
element.attr({ "overflow": "hidden" });
}
// Add RTL?
// This has now been moved to this.alignSVGText()
// if (this.rtl) {
// element.attr({
// "direction": "rtl",
// //"unicode-bidi": "bidi-override"
// });
// }
return element;
};
Object.defineProperty(Label.prototype, "rtl", {
/**
* @return RTL?
*/
get: function () {
if ($type.hasValue(this._rtl)) {
return this._rtl;
}
else if (this._topParent) {
return this._topParent.rtl;
}
return false;
},
/**
* An RTL (right-to-left) setting.
*
* RTL may affect alignment, text, and other visual properties.
*
* If you set this on a top-level chart object, it will be used for all
* child elements, e.g. labels, unless they have their own `rtl` setting
* set directly on them.
*
* @param value `true` for to use RTL
*/
set: function (value) {
value = $type.toBoolean(value);
this._rtl = value;
if (this.element) {
this.alignSVGText();
}
},
enumerable: true,
configurable: true
});
/**
* Resets cached BBox.
*
* @ignore Exclude from docs
*/
Label.prototype.resetBBox = function () {
this._bbox = { x: 0, y: 0, width: 0, height: 0 };
};
/**
* Creates and returns an HTML line element (`<div>`).
*
* @ignore Exclude from docs
* @param text Text to add
* @return `<div>` element reference
*/
Label.prototype.getHTMLLineElement = function (text) {
// Create the <div> element
var div = document.createElement("div");
div.innerHTML = text;
// Set text alignment
switch (this.textAlign) {
case "middle":
div.style.textAlign = "center";
break;
case "end":
div.style.textAlign = "right";
break;
}
// Disable or enable wrapping
if (this.wrap) {
div.style.wordWrap = "break-word";
}
else {
div.style.whiteSpace = "nowrap";
}
// Don't let labels bleed out of the alotted area
// Moved to `draw()` because setting "hidden" kills all measuring
/*if (this.truncate) {
div.style.overflow = "hidden";
}*/
// Set RTL-related styles
if (this.rtl) {
div.style.direction = "rtl";
//div.style.unicodeBidi = "bidi-override";
}
// Translate some of the SVG styles into CSS
if ($type.hasValue(this.fill)) {
div.style.color = this.fill.toString();
}
return div;
};
/**
* Applies specific styles to text to make it not selectable, unless it is
* explicitly set as `selectable`.
*
* @ignore Exclude from docs
* @todo Set styles via AMElement
*/
Label.prototype.setStyles = function () {
var group = this.element;
if (!this.selectable || this.draggable || this.resizable || this.swipeable) {
group.addStyle({
"webkitUserSelect": "none",
"msUserSelect": "none"
});
}
else if (this.selectable) {
group.removeStyle("webkitUserSelect");
group.removeStyle("msUserSelect");
}
};
/**
* Hides unused lines
*/
Label.prototype.hideUnused = function (index) {
this.initLineCache();
var lines = this.getCache("lineInfo");
if (lines.length >= index) {
for (var i = index; i < lines.length; i++) {
var line = lines[i];
if (line && line.element) {
line.element.attr({ "display": "none" });
}
}
}
};
Object.defineProperty(Label.prototype, "text", {
/**
* @return SVG text
*/
get: function () {
return this.getPropertyValue("text");
},
/**
* An SVG text.
*
* Please note that setting `html` will override this setting if browser
* supports `foreignObject` in SGV, such as most modern browsers excluding
* IEs.
*
* @param value SVG Text
*/
set: function (value) {
//this.setPropertyValue("html", undefined);
this.setPropertyValue("text", value, true);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "path", {
/**
* @return Path
*/
get: function () {
return this.getPropertyValue("path");
},
/**
* An SVG path string to position text along. If set, the text will follow
* the curvature of the path.
*
* Location along the path can be set using `locationOnPath`.
*
* IMPORTANT: Only SVG text can be put on path. If you are using HTML text
* this setting will be ignored.
*
* @since 4.1.2
* @param value Path
*/
set: function (value) {
if (this.setPropertyValue("path", value, true)) {
if (this.pathElement) {
this.pathElement.dispose();
}
if (this.textPathElement) {
this.textPathElement.dispose();
}
this.pathElement = this.paper.add("path");
this.pathElement.attr({ "d": value });
this.pathElement.attr({ "id": "text-path-" + this.uid });
this._disposers.push(this.pathElement);
this.textPathElement = this.paper.addGroup("textPath");
this.textPathElement.attrNS($dom.XLINK, "xlink:href", "#text-path-" + this.uid);
// TODO remove after https://bugzilla.mozilla.org/show_bug.cgi?id=455986 is fixed
this.textPathElement.attr({ "path": value });
this._disposers.push(this.textPathElement);
this.hardInvalidate();
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "locationOnPath", {
/**
* @return Relatvie location on path
*/
get: function () {
return this.getPropertyValue("locationOnPath");
},
/**
* Relative label location on `path`. Value range is from 0 (beginning)
* to 1 (end).
*
* Works only if you set `path` setting to an SVG path.
*
* @since 4.1.2
* @default 0
* @param value Relatvie location on path
*/
set: function (value) {
this.setPropertyValue("locationOnPath", value);
if (this.textPathElement) {
this.textPathElement.attr({ "startOffset": (value * 100) + "%" });
}
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "baseLineRatio", {
/**
* @return Base line ratio
*/
get: function () {
return this.getPropertyValue("baseLineRatio");
},
/**
* A ratio to calculate text baseline. Ralative distance from the bottom of
* the label.
*
* @since 4.4.2
* @default -0.27
* @param value Base line ratio
*/
set: function (value) {
this.setPropertyValue("baseLineRatio", value);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "wrap", {
/**
* @return Auto-wrap enabled or not
*/
get: function () {
return this.getPropertyValue("wrap");
},
/**
* Enables or disables autowrapping of text.
*
* @param value Auto-wrapping enabled
*/
set: function (value) {
this.resetBBox();
this.setPropertyValue("wrap", value, true);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "truncate", {
/**
* @return Truncate text?
*/
get: function () {
return this.getPropertyValue("truncate");
},
/**
* Indicates if text lines need to be truncated if they do not fit, using
* configurable `ellipsis` string.
*
* `truncate` overrides `wrap` if both are set to `true`.
*
* NOTE: For HTML text, this setting **won't** trigger a parser and actual
* line truncation with ellipsis. It will just hide everything that goes
* outside the label.
*
* @param value trincate text?
*/
set: function (value) {
this.resetBBox();
this.setPropertyValue("truncate", value, true);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "fullWords", {
/**
* @return Truncate on full words?
*/
get: function () {
return this.getPropertyValue("fullWords");
},
/**
* If `truncate` is enabled, should Label try to break only on full words
* (`true`), or whenever needed, including middle of the word. (`false`)
*
* @default true
* @param value Truncate on full words?
*/
set: function (value) {
this.setPropertyValue("fullWords", value, true);
},
enumerable: true,
configurable: true
});
Object.defineProperty(Label.prototype, "ellipsis", {
/**
* @return Ellipsis string
*/
get: function () {
return this.getPropertyValue("ellipsis