Skip to content

Commit

Permalink
Implement maxHeight for text element (#7661)
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-simionov authored Apr 10, 2019
1 parent d2f0d85 commit 00ff3b4
Show file tree
Hide file tree
Showing 11 changed files with 270 additions and 98 deletions.
2 changes: 1 addition & 1 deletion js/viz/axes/base_axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -1815,7 +1815,7 @@ Axis.prototype = {
if((wordWrapMode !== "none" || overflowMode !== "none") && displayMode !== "rotate" && overlappingMode !== "rotate" && overlappingMode !== "auto" && textWidth) {
if(that._majorTicks.some(tick => tick.labelBBox.width > textWidth)) {
that._majorTicks.forEach(tick => {
tick.label && tick.label.setMaxWidth(textWidth, options.label);
tick.label && tick.label.setMaxSize(textWidth, undefined, options.label);
});
measureLabels(that._majorTicks);
}
Expand Down
108 changes: 89 additions & 19 deletions js/viz/core/renderers/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ function parseMultiline(text) {
i = 0,
items = [];
for(; i < texts.length; i++) {
items.push({ value: texts[i].trim(), height: 0 });
items.push({ value: texts[i].trim(), height: 0, line: i });
}
return items;
}
Expand Down Expand Up @@ -831,7 +831,7 @@ function cloneAndRemoveAttrs(node) {
return clone || node;
}

function setMaxWidth(maxWidth, options = {}) {
function setMaxSize(maxWidth, maxHeight, options = {}) {
var that = this,
lines = [],
textChanged = false,
Expand All @@ -843,14 +843,18 @@ function setMaxWidth(maxWidth, options = {}) {

ellipsis = that.renderer.text(ELLIPSIS).attr(that._styles).append(that.renderer.root);
ellipsisWidth = ellipsis.getBBox().width;
if(that._getElementBBox().width > maxWidth) {

const { width, height } = that._getElementBBox();

if(width > maxWidth || maxHeight && height > maxHeight) {
if(maxWidth - ellipsisWidth < 0) {
ellipsisMaxWidth = 0;
} else {
ellipsisMaxWidth -= ellipsisWidth;
}

lines = applyOverflowRules(that.element, that._texts, maxWidth, ellipsisMaxWidth, options);
lines = applyOverflowRules(that.element, that._texts, maxWidth, ellipsisMaxWidth, options, maxHeight);
lines = setMaxHeight(lines, ellipsisMaxWidth, options, maxHeight, parseFloat(this._getLineHeight()));

this._texts = lines.reduce((texts, line) => {
return texts.concat(line.parts);
Expand Down Expand Up @@ -950,6 +954,19 @@ function getWordBreakIndex(text, maxWidth) {
}
}

function setEllipsis(text, ellipsisMaxWidth) {
if(text.value.length && text.tspan.parentNode) {
for(let i = text.value.length - 1; i >= 1; i--) {
if(text.startBox + text.tspan.getSubStringLength(0, i) < ellipsisMaxWidth) {
setNewText(text, i, ELLIPSIS);
break;
} else if(i === 1) {
setNewText(text, 0, ELLIPSIS);
}
}
}
}

function wordWrap(text, maxWidth, ellipsisMaxWidth, options) {
const wholeText = text.value;
let breakIndex;
Expand Down Expand Up @@ -995,14 +1012,7 @@ function wordWrap(text, maxWidth, ellipsisMaxWidth, options) {

if(text.value.length) {
if(options.textOverflow === "ellipsis" && text.tspan.getSubStringLength(0, text.value.length) > maxWidth) {
for(let i = text.value.length - 1; i >= 1; i--) {
if(text.startBox + text.tspan.getSubStringLength(0, i) < ellipsisMaxWidth) {
setNewText(text, i, ELLIPSIS);
break;
} else if(i === 1) {
setNewText(text, 0, ELLIPSIS);
}
}
setEllipsis(text, ellipsisMaxWidth);
}

if(options.textOverflow === "hide" && text.tspan.getSubStringLength(0, text.value.length) > maxWidth) {
Expand All @@ -1021,6 +1031,54 @@ function wordWrap(text, maxWidth, ellipsisMaxWidth, options) {
return [{ commonLength: wholeText.length, parts }].concat(restLines);
}

function calculateLineHeight(line, lineHeight) {
return line.parts.reduce((height, text) => {
return Math.max(height, getItemLineHeight(text, lineHeight));
}, 0);
}

function setMaxHeight(lines, ellipsisMaxWidth, options, maxHeight, lineHeight) {
if(!isFinite(maxHeight)) {
return lines;
}
const result = lines.reduce(([lines, commonHeight], l, index, arr) => {
const height = calculateLineHeight(l, lineHeight);
commonHeight += height;
if(commonHeight < maxHeight) {
lines.push(l);
} else {
l.parts.forEach(item => {
removeTextSpan(item);
});
if(options.textOverflow === "ellipsis") {
const prevLine = arr[index - 1];
if(prevLine) {
const text = prevLine.parts[prevLine.parts.length - 1];
if(!text.hasEllipsis) {
if(text.endBox < ellipsisMaxWidth) {
setNewText(text, text.value.length, ELLIPSIS);
} else {
setEllipsis(text, ellipsisMaxWidth);
}
}
}
}
}
return [lines, commonHeight];
}, [[], 0]);

if(options.textOverflow === "hide" && result[1] > maxHeight) {
result[0].forEach(l => {
l.parts.forEach(item => {
removeTextSpan(item);
});
});
return [];
}

return result[0];
}

function applyOverflowRules(element, texts, maxWidth, ellipsisMaxWidth, options) {
if(!texts) {
const textValue = element.textContent;
Expand All @@ -1030,6 +1088,7 @@ function applyOverflowRules(element, texts, maxWidth, ellipsisMaxWidth, options)

texts = [text];
}

return texts.reduce(([lines, startBox, endBox, stop, lineNumber], text) => {
const line = lines[lines.length - 1];
if(stop) {
Expand All @@ -1041,7 +1100,7 @@ function applyOverflowRules(element, texts, maxWidth, ellipsisMaxWidth, options)
} else {
text.startBox = startBox;
if(startBox > ellipsisMaxWidth && options.wordWrap === "none" && options.textOverflow === "ellipsis") {
text.tspan.parentNode.removeChild(text.tspan);
removeTextSpan(text);
return [lines, startBox, endBox, stop, lineNumber];
}
line.parts.push(text);
Expand Down Expand Up @@ -1070,11 +1129,14 @@ function setNewText(text, index, insertString = ELLIPSIS) {
var newText = text.value.substr(0, index) + insertString;
text.value = text.tspan.textContent = newText;
text.stroke && (text.stroke.textContent = newText);
if(insertString === ELLIPSIS) {
text.hasEllipsis = true;
}
}

function removeTextSpan(text) {
text.tspan.parentNode.removeChild(text.tspan);
text.stroke && text.stroke.parentNode.removeChild(text.stroke);
text.tspan.parentNode && text.tspan.parentNode.removeChild(text.tspan);
text.stroke && text.stroke.parentNode && text.stroke.parentNode.removeChild(text.stroke);
}

function createTextNodes(wrapper, text, isStroked) {
Expand Down Expand Up @@ -1116,11 +1178,15 @@ function setTextNodeAttribute(item, name, value) {
item.stroke && item.stroke.setAttribute(name, value);
}

function getItemLineHeight(item, defaultValue) {
return item.inherits ? maxLengthFontSize(item.height, defaultValue) : (item.height || defaultValue);
}

function locateTextNodes(wrapper) {
if(!wrapper._texts) return;
var items = wrapper._texts,
x = wrapper._settings.x,
lineHeight = !isNaN(_parseFloat(wrapper._styles[KEY_FONT_SIZE])) ? wrapper._styles[KEY_FONT_SIZE] : DEFAULT_FONT_SIZE,
lineHeight = wrapper._getLineHeight(),
i, ii,
item = items[0];
setTextNodeAttribute(item, "x", x);
Expand All @@ -1129,7 +1195,8 @@ function locateTextNodes(wrapper) {
item = items[i];
if(_parseFloat(item.height) >= 0) {
setTextNodeAttribute(item, "x", x);
setTextNodeAttribute(item, "dy", item.inherits ? maxLengthFontSize(item.height, lineHeight) : (item.height || lineHeight)); // T177039
const height = getItemLineHeight(item, lineHeight);
setTextNodeAttribute(item, "dy", height); // T177039
}
}
}
Expand Down Expand Up @@ -1674,8 +1741,11 @@ extend(TextSvgElement.prototype, {
attr: textAttr,
css: textCss,
applyEllipsis,
setMaxWidth,
restoreText
setMaxSize,
restoreText,
_getLineHeight() {
return !isNaN(_parseFloat(this._styles[KEY_FONT_SIZE])) ? this._styles[KEY_FONT_SIZE] : DEFAULT_FONT_SIZE;
}
});
// TextSvgElement

Expand Down
2 changes: 1 addition & 1 deletion js/viz/core/title.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function hasText(text) {
}

function processTitleLength(elem, text, width, options) {
elem.attr({ text: text }).setMaxWidth(width, options).textChanged && elem.setTitle(text);
elem.attr({ text }).setMaxSize(width, undefined, options).textChanged && elem.setTitle(text);
}

function pickMarginValue(value) {
Expand Down
2 changes: 1 addition & 1 deletion js/viz/series/points/label.js
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ Label.prototype = {
const padding = this._background ? 2 * LABEL_BACKGROUND_PADDING_X : 0;
let rowCountChanged = false;
if(this._text) {
let { rowCount } = this._text.setMaxWidth(maxWidth - padding, this._options);
let { rowCount } = this._text.setMaxSize(maxWidth - padding, undefined, this._options);
if(rowCount === 0) {
rowCount = 1;
}
Expand Down
8 changes: 6 additions & 2 deletions js/viz/tree_map/tree_map.base.js
Original file line number Diff line number Diff line change
Expand Up @@ -604,15 +604,19 @@ function layoutTextNode(node, params) {

if(_isDefined(resolveLabelOverflow)) {
if(resolveLabelOverflow === "ellipsis" && fitByHeight) {
text.applyEllipsis(effectiveWidth);
text.setMaxSize(effectiveWidth, undefined, {
wordWrap: "none",
textOverflow: "ellipsis"
});
if(!fitByWidth) {
bBox = text.getBBox();
fitByWidth = bBox.width <= effectiveWidth;
}
}
} else {
fitByWidth = true;
text.setMaxWidth(effectiveWidth, node.isNode() ? { textOverflow: groupLabelOverflow, wordWrap: "none" } :
fitByHeight = true;
text.setMaxSize(effectiveWidth, rect[3] - rect[1] - paddingTopBottom, node.isNode() ? { textOverflow: groupLabelOverflow, wordWrap: "none" } :
{ textOverflow: tileLabelOverflow, wordWrap: tileLabelWordWrap });
}

Expand Down
2 changes: 1 addition & 1 deletion testing/helpers/vizMocks.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
applyEllipsis: function(maxWidth) { // for text
return maxWidth < 50;
},
setMaxWidth: function(maxWidth) { // for text
setMaxSize: function(maxWidth) { // for text
return { textChanged: maxWidth < 50 };
},
stopAnimation: function() {
Expand Down
10 changes: 5 additions & 5 deletions testing/tests/DevExpress.viz.core.series/label.tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -1308,7 +1308,7 @@ QUnit.test("fit without background", function(assert) {

var text = this.renderer.text.getCall(0).returnValue;

assert.equal(text.setMaxWidth.args[0][0], 15, "Max width param");
assert.equal(text.setMaxSize.args[0][0], 15, "Max width param");
});

QUnit.test("fit with background", function(assert) {
Expand All @@ -1322,15 +1322,15 @@ QUnit.test("fit with background", function(assert) {
var text = this.renderer.text.getCall(0).returnValue,
background = this.renderer.rect.getCall(0).returnValue;

assert.equal(text.setMaxWidth.args[0][0], 15, "Max width param");
assert.equal(text.setMaxSize.args[0][0], 15, "Max width param");
assert.strictEqual(background.attr.called, true, "New background rect");
});

QUnit.test("Label's fit. Count of rows changed", function(assert) {
this.options.background = { fill: "red" };
const label = this.createAndDrawLabel();

this.renderer.text.lastCall.returnValue.setMaxWidth = function() {
this.renderer.text.lastCall.returnValue.setMaxSize = function() {
return { rowCount: 2 };
};
label.shift(-7, -2);
Expand All @@ -1342,7 +1342,7 @@ QUnit.test("Label's fit. Count of rows not changed", function(assert) {
this.options.background = { fill: "red" };
const label = this.createAndDrawLabel();

this.renderer.text.lastCall.returnValue.setMaxWidth = function() {
this.renderer.text.lastCall.returnValue.setMaxSize = function() {
return { rowCount: 1 };
};
label.shift(-7, -2);
Expand All @@ -1354,7 +1354,7 @@ QUnit.test("Label's fit. rowCount = 0 ", function(assert) {
this.options.background = { fill: "red" };
const label = this.createAndDrawLabel();

this.renderer.text.lastCall.returnValue.setMaxWidth = function() {
this.renderer.text.lastCall.returnValue.setMaxSize = function() {
return { rowCount: 0 };
};
label.shift(-7, -2);
Expand Down
Loading

0 comments on commit 00ff3b4

Please sign in to comment.