diff --git a/src/ChartInternal/internals/tooltip.ts b/src/ChartInternal/internals/tooltip.ts
index 435f7a451..1f916f56a 100644
--- a/src/ChartInternal/internals/tooltip.ts
+++ b/src/ChartInternal/internals/tooltip.ts
@@ -93,17 +93,23 @@ export default {
const $$ = this;
const {api, config, state, $el} = $$;
- let [titleFormat, nameFormat, valueFormat] = ["title", "name", "value"].map(v => {
+ // get formatter function
+ const [titleFn, nameFn, valueFn] = ["title", "name", "value"].map(v => {
const fn = config[`tooltip_format_${v}`];
return isFunction(fn) ? fn.bind(api) : fn;
});
- titleFormat = titleFormat || defaultTitleFormat;
- nameFormat = nameFormat || (name => name);
- valueFormat = valueFormat || (
- state.hasTreemap || $$.isStackNormalized() ? (v, ratio) => `${(ratio * 100).toFixed(2)}%` : defaultValueFormat
- );
+ // determine fotmatter function with sanitization
+ const titleFormat = (...arg) => sanitize((titleFn || defaultTitleFormat)(...arg));
+ const nameFormat = (...arg) => sanitize((nameFn || (name => name))(...arg));
+ const valueFormat = (...arg) => {
+ const fn = valueFn || (
+ state.hasTreemap || $$.isStackNormalized() ? (v, ratio) => `${(ratio * 100).toFixed(2)}%` : defaultValueFormat
+ );
+
+ return sanitize(fn(...arg));
+ };
const order = config.tooltip_order;
const getRowValue = row => ($$.axis && $$.isBubbleZType(row) ? $$.getBubbleZData(row.value, "z") : $$.getBaseValue(row));
@@ -158,8 +164,7 @@ export default {
}
if (isUndefined(text)) {
- const title = (state.hasAxis || state.hasRadar) &&
- sanitize(titleFormat ? titleFormat(row.x) : row.x);
+ const title = (state.hasAxis || state.hasRadar) && titleFormat(row.x);
text = tplProcess(tpl[0], {
CLASS_TOOLTIP: $TOOLTIP.tooltip,
@@ -175,24 +180,28 @@ export default {
}
param = [row.ratio, row.id, row.index, d];
- value = sanitize(valueFormat(getRowValue(row), ...param));
if ($$.isAreaRangeType(row)) {
- const [high, low] = ["high", "low"].map(v => sanitize(
- valueFormat($$.getRangedData(row, v), ...param)
- ));
+ const [high, low] = ["high", "low"].map(v => valueFormat($$.getRangedData(row, v), ...param));
+ const mid = valueFormat(getRowValue(row), ...param);
- value = `Mid: ${value} High: ${high} Low: ${low}`;
+ value = `Mid: ${mid} High: ${high} Low: ${low}`;
} else if ($$.isCandlestickType(row)) {
- const [open, high, low, close, volume] = ["open", "high", "low", "close", "volume"].map(v => sanitize(
- valueFormat($$.getRangedData(row, v, "candlestick"), ...param)
- ));
+ const [open, high, low, close, volume] = ["open", "high", "low", "close", "volume"].map(v => {
+ const value = $$.getRangedData(row, v, "candlestick");
+
+ return value ? valueFormat(
+ $$.getRangedData(row, v, "candlestick"), ...param
+ ) : undefined;
+ });
value = `Open: ${open} High: ${high} Low: ${low} Close: ${close}${volume ? ` Volume: ${volume}` : ""}`;
} else if ($$.isBarRangeType(row)) {
- const [start, end] = row.value;
+ const {value: [start, end], id, index} = row;
- value = `${valueFormat(start)} ~ ${valueFormat(end)}`;
+ value = `${valueFormat(start, undefined, id, index)} ~ ${valueFormat(end, undefined, id, index)}`;
+ } else {
+ value = valueFormat(getRowValue(row), ...param);
}
if (value !== undefined) {
@@ -201,7 +210,7 @@ export default {
continue;
}
- const name = sanitize(nameFormat(row.name, ...param));
+ const name = nameFormat(row.name, ...param);
const color = getBgColor(row);
const contentValue = {
CLASS_TOOLTIP_NAME: $TOOLTIP.tooltipName + $$.getTargetSelectorSuffix(row.id),
diff --git a/src/config/Options/common/tooltip.ts b/src/config/Options/common/tooltip.ts
index 69926089d..0927472c1 100644
--- a/src/config/Options/common/tooltip.ts
+++ b/src/config/Options/common/tooltip.ts
@@ -22,9 +22,9 @@ export default {
* Specified function receives x of the data point to show.
* @property {Function} [tooltip.format.name] Set format for the name of each data in tooltip.
* Specified function receives name, ratio, id and index of the data point to show. ratio will be undefined if the chart is not donut/pie/gauge.
- * @property {Function} [tooltip.format.value] Set format for the value of each data in tooltip. If undefined returned, the row of that value will be skipped to be called.
+ * @property {Function} [tooltip.format.value] Set format for the value of each data value in tooltip. If undefined returned, the row of that value will be skipped to be called.
* - Will pass following arguments to the given function:
- * - `value {string}`: Value of the data point
+ * - `value {string}`: Value of the data point. If data row contains multiple or ranged(ex. candlestick, area range, etc.) value, formatter will be called as value length.
* - `ratio {number}`: Ratio of the data point in the `pie/donut/gauge` and `area/bar` when contains grouped data. Otherwise is `undefined`.
* - `id {string}`: id of the data point
* - `index {number}`: Index of the data point
@@ -89,6 +89,9 @@ export default {
* format: {
* title: function(x) { return "Data " + x; },
* name: function(name, ratio, id, index) { return name; },
+ *
+ * // If data row contains multiple or ranged(ex. candlestick, area range, etc.) value,
+ * // formatter will be called as value length times.
* value: function(value, ratio, id, index) { return ratio; }
* },
* position: function(data, width, height, element, pos) {
diff --git a/test/internals/tooltip-spec.ts b/test/internals/tooltip-spec.ts
index d82d5cc04..b2800a170 100644
--- a/test/internals/tooltip-spec.ts
+++ b/test/internals/tooltip-spec.ts
@@ -1795,6 +1795,8 @@ describe("TOOLTIP", function() {
});
describe("tooltip: format", () => {
+ const spyTitle = sinon.spy();
+ const spyName = sinon.spy();
const spy = sinon.spy(function(value, ratio, id, index) {
return [value, ratio, id, index];
});
@@ -1813,11 +1815,19 @@ describe("TOOLTIP", function() {
},
tooltip: {
format: {
+ title: spyTitle,
+ name: spyName,
value: spy
}
}
};
});
+
+ after(() => {
+ spyTitle.resetHistory();
+ spyName.resetHistory();
+ spy.resetHistory();
+ });
it("check if ratio value is given to format function for 'bar' type.", () => {
chart.data.values("data1").forEach((v, i) => {
@@ -1827,6 +1837,12 @@ describe("TOOLTIP", function() {
// check ratio
expect(spy.returnValues.reduce((p, a) => p?.[1] ?? p + a[1], 0)).to.be.equal(1);
+
+ // title formatter should be called only once
+ expect(spyTitle.callCount).to.be.equal(i + 1);
+
+ // name formatter should be called as row's data length times
+ expect(spyName.callCount).to.be.equal((i + 1) * 2);
spy.resetHistory();
});
@@ -1865,5 +1881,132 @@ describe("TOOLTIP", function() {
spy.resetHistory();
});
});
+
+ it("set options", () => {
+ spy.resetHistory();
+
+ args = {
+ data: {
+ columns: [
+ ["data1", [0, 100], [100, 250], 30]
+ ],
+ type: "bar"
+ },
+ tooltip: {
+ format: {
+ value: spy
+ }
+ }
+ };
+ });
+
+ it("check bar ranged data", () => {
+ // when
+ chart.tooltip.show({x: 1});
+
+ expect(spy.callCount).to.be.equal(2);
+
+ spy.resetHistory();
+
+ // when
+ chart.tooltip.show({x: 2});
+
+ expect(spy.callCount).to.be.equal(1);
+ });
+
+ it("set options: area-line-range type", () => {
+ spy.resetHistory();
+
+ args = {
+ data: {
+ columns: [
+ ["data1", [199, 160, 125], [180, 150, 130], [135, 120, 110]]
+ ],
+ type: "area-line-range"
+ },
+ tooltip: {
+ format: {
+ value: spy
+ }
+ }
+ };
+ });
+
+ it("check for area-line-range data", () => {
+ // when
+ chart.tooltip.show({x: 2});
+
+ expect(spy.callCount).to.be.equal(3);
+ spy.resetHistory();
+
+ // when
+ chart.tooltip.show({x: 1});
+
+ expect(spy.callCount).to.be.equal(3);
+ });
+
+ it("set options: candlestick type", () => {
+ spy.resetHistory();
+
+ args = {
+ data: {
+ columns: [
+ ["data1",
+ [1327, 1369, 1289, 1348],
+ [1348, 1371, 1314, 1320],
+ [1320, 1412, 1314, 1394, 500]
+ ]
+ ],
+ type: "candlestick"
+ },
+ tooltip: {
+ format: {
+ value: spy
+ }
+ }
+ };
+ });
+
+ it("check for candlestick data", () => {
+ const data = chart.data.values("data1");
+
+ // when data contains volume data
+ chart.tooltip.show({x: 2});
+
+ expect(spy.callCount).to.be.equal(data[2].length);
+ spy.resetHistory();
+
+ // when
+ chart.tooltip.show({x: 1});
+
+ expect(spy.callCount).to.be.equal(data[1].length);
+ });
+
+ it("set options: pie type", () => {
+ spy.resetHistory();
+
+ args = {
+ data: {
+ columns: [
+ ["data1", 50],
+ ["data2", 50],
+ ],
+ type: "pie"
+ },
+ tooltip: {
+ format: {
+ value: spy
+ }
+ }
+ };
+ });
+
+ it("check for pie data", () => {
+ // when
+ chart.tooltip.show({data: {index: 1}});
+
+ expect(spy.callCount).to.be.equal(1);
+ spy.resetHistory();
+ });
});
});