Skip to content

Commit

Permalink
feat(title): Intent to ship multilined title (#731)
Browse files Browse the repository at this point in the history
- Implementation on multiline title text
- Applied cache to reduce call on  getting text element's dimension

Ref #612
Fix #727
Close #731
  • Loading branch information
netil committed Jan 15, 2019
1 parent 801ba35 commit 171df89
Show file tree
Hide file tree
Showing 7 changed files with 191 additions and 78 deletions.
52 changes: 52 additions & 0 deletions demo/demo.js
Expand Up @@ -2090,6 +2090,58 @@ d3.select(".chart_area")
}
},

Title: {
MultilinedTitle: {
options: {
title: {
text: "Temperature History by Region, 2017-2018\nSource: community weather center"
},
data: {
x: "x",
json: {
Temperature: [
"29.39",
"29.7",
"29.37",
"28.87",
"28.62",
"27.72",
"27.61",
"27.82",
"27.48",
"26.78"
],
x: [
"01-10-2019 00:00",
"01-10-2019 00:30",
"01-10-2019 01:00",
"01-10-2019 01:30",
"01-10-2019 02:00",
"01-10-2019 02:30",
"01-10-2019 03:00",
"01-10-2019 03:30",
"01-10-2019 04:00",
"01-10-2019 04:30"
]
},
type: "area",
xFormat: "%m-%d-%Y %H:%M"
},
axis: {
x: {
type: "timeseries"
}
},
point: {
show: false
}
},
style: [
"#MultilinedTitle .bb-title tspan:first-child { font-size: 17px; font-weight: bold; }"
]
}
},

Tooltip: {
HideTooltip: {
options: {
Expand Down
4 changes: 3 additions & 1 deletion demo/simple-sidebar.css
Expand Up @@ -229,4 +229,6 @@ div.row {
#RadarAxis .bb-levels polygon { stroke-dasharray: 1 3; stroke-width: 1px; }

/* Style For tick position */
#XAxisTickPosition .bb-axis-x line, #XAxisTickPosition .bb-axis-x path { visibility: hidden; }
#XAxisTickPosition .bb-axis-x line, #XAxisTickPosition .bb-axis-x path { visibility: hidden; }

#MultilinedTitle .bb-title tspan:first-child { font-size: 17px; font-weight: bold; }
62 changes: 39 additions & 23 deletions spec/internals/title-spec.js
Expand Up @@ -34,14 +34,18 @@ describe("TITLE", () => {
describe("when given a title config option", () => {
describe("with no padding and no position", () => {
it("renders the title at the default config position", () => {
const titleEl = chart.internal.svg.select(".bb-title");

expect(+titleEl.attr("x") + titleEl.node().getBBox().width / 2).to.be.closeTo(320, 1);
expect(+titleEl.attr("y")).to.equal(titleEl.node().getBBox().height);
const title = chart.$.svg.select(".bb-title").node();
const [x, y] = title.parentNode
.getAttribute("transform")
.split(",")
.map(v => util.parseNum(v));

expect(x).to.be.equal(chart.internal.currentWidth / 2);
expect(y).to.be.equal(title.getBBox().height);
});

it("renders the title text", () => {
const titleEl = chart.internal.svg.select(".bb-title");
const titleEl = chart.$.svg.select(".bb-title");

expect(titleEl.node().textContent).to.equal("new title");
});
Expand All @@ -63,51 +67,63 @@ describe("TITLE", () => {
bottom: 40,
left: 50
},
position: "top-center"
position: "center"
}
};
});

describe("and position center", () => {
it("renders the title at the default config position", () => {
const titleEl = chart.internal.svg.select(".bb-title");

expect(+titleEl.attr("x") + titleEl.node().getBBox().width / 2).to.be.closeTo(320, 1);
expect(+titleEl.attr("y")).to.be.closeTo(37, 2);
const title = chart.$.svg.select(".bb-title").node();
const [x, y] = title.parentNode
.getAttribute("transform")
.split(",")
.map(v => util.parseNum(v));

expect(x).to.be.equal(chart.internal.currentWidth / 2);
expect(y).to.be.equal(title.getBBox().height + args.title.padding.top);
});

it("adds the correct amount of padding to fit the title", () => {
const height = chart.$.svg.select(".bb-title").node().getBBox().height;

expect(chart.internal.getCurrentPaddingTop()).to.equal(
args.title.padding.top +
chart.internal.svg.select(".bb-title").node().getBBox().height +
args.title.padding.bottom
args.title.padding.top + height + args.title.padding.bottom
);
});
});

describe("and position left", () => {
before(() => {
args.title.position = "top-left";
args.title.position = "left";
});

it("renders the title at the default config position", () => {
const titleEl = chart.internal.svg.select(".bb-title");

expect(+titleEl.attr("x")).to.be.closeTo(50, 2);
expect(+titleEl.attr("y")).to.be.closeTo(36, 2); // org : 34
const title = chart.$.svg.select(".bb-title").node();
const [x, y] = title.parentNode
.getAttribute("transform")
.split(",")
.map(v => util.parseNum(v));

expect(x).to.be.equal(0);
expect(y).to.be.equal(title.getBBox().height + args.title.padding.top);
});
});

describe("and position right", () => {
before(() => {
args.title.position = "top-right";
args.title.position = "right";
});

it("renders the title at the default config position", () => {
const titleEl = chart.internal.svg.select(".bb-title");

expect(+titleEl.attr("x") + titleEl.node().getBBox().width).to.be.closeTo(610, 1);
expect(+titleEl.attr("y")).to.be.closeTo(36, 2);
const title = chart.$.svg.select(".bb-title").node();
const [x, y] = title.parentNode
.getAttribute("transform")
.split(",")
.map(v => util.parseNum(v));

expect(x).to.be.equal(chart.internal.currentWidth);
expect(y).to.be.equal(title.getBBox().height + args.title.padding.top);
});
});
});
Expand Down
21 changes: 13 additions & 8 deletions src/config/Options.js
Expand Up @@ -3107,22 +3107,27 @@ export default class Options {
* @name title
* @memberof Options
* @type {Object}
* @property {String} [title.text]
* @property {Number} [title.padding.top=0]
* @property {Number} [title.padding.right=0]
* @property {Number} [title.padding.bottom=0]
* @property {Number} [title.padding.left=0]
* @property {String} [title.position=top-center]
* @property {String} [title.text] Title text. If contains `\n`, it's used as line break allowing multiline title.
* @property {Number} [title.padding.top=0] Top padding value.
* @property {Number} [title.padding.right=0] Right padding value.
* @property {Number} [title.padding.bottom=0] Bottom padding value.
* @property {Number} [title.padding.left=0] Left padding value.
* @property {String} [title.position=center] Available values are: 'center', 'right' and 'left'.
* @see [Demo](https://naver.github.io/billboard.js/demo/#Title.MultilinedTitle)
* @example
* title: {
* text: "Title Text",
*
* // or Multiline title text
* text: "Main title text\nSub title text",
*
* padding: {
* top: 10,
* right: 10,
* bottom: 10,
* left: 10
* },
* position: "top-center"
* position: "center"
* }
*/
title_text: undefined,
Expand All @@ -3132,7 +3137,7 @@ export default class Options {
bottom: 0,
left: 0
},
title_position: "top-center"
title_position: "center"
};
}
}
2 changes: 1 addition & 1 deletion src/internals/legend.js
Expand Up @@ -412,7 +412,7 @@ extend(ChartInternal.prototype, {
const getTextBox = function(textElement, id) {
if (!$$.legendItemTextBox[id]) {
$$.legendItemTextBox[id] =
$$.getTextRect(textElement, CLASS.legendItem, textElement);
$$.getTextRect(textElement, CLASS.legendItem);
}

return $$.legendItemTextBox[id];
Expand Down
52 changes: 24 additions & 28 deletions src/internals/text.js
Expand Up @@ -109,39 +109,35 @@ extend(ChartInternal.prototype, {
/**
* Gets the getBoundingClientRect value of the element
* @private
* @param {HTMLElement|d3.selection} textVal
* @param {HTMLElement|d3.selection} element
* @param {String} className
* @param {HTMLElement|d3.selection} elementVal
* @returns {Object} value of element.getBoundingClientRect()
*/
getTextRect(textVal, className, elementVal) {
const text = (textVal.node ? textVal.node() : textVal).textContent;
const element = elementVal.node ? elementVal.node() : elementVal;

const dummy = d3Select("body").append("div")
.classed("bb", true);

const svg = dummy.append("svg")
.style("visibility", "hidden")
.style("position", "fixed")
.style("top", "0px")
.style("left", "0px");
getTextRect(element, className) {
const $$ = this;
let base = (element.node ? element.node() : element);

const font = d3Select(element).style("font");
let rect;
if (!/text/i.test(base.tagName)) {
base = base.querySelector("text");
}

svg.selectAll(".dummy")
.data([text])
.enter()
.append("text")
.classed(className, !!className)
.style("font", font)
.text(text)
.each(function() {
rect = this.getBoundingClientRect();
});

dummy.remove();
const text = base.textContent;
const cacheKey = `$${text.replace(/\W/g, "_")}`;
let rect = $$.getCache(cacheKey);

if (!rect) {
$$.svg.append("text")
.style("visibility", "hidden")
.style("font", d3Select(base).style("font"))
.classed(className, true)
.text(text)
.call(v => {
rect = v.node().getBoundingClientRect();
})
.remove();

$$.addCache(cacheKey, rect);
}

return rect;
},
Expand Down

0 comments on commit 171df89

Please sign in to comment.