Skip to content

Commit

Permalink
fix(tooltip): Fix tooltip.format.value call
Browse files Browse the repository at this point in the history
- Unify the call of sanitize for all formatter function
- Fix the unnecessary call and pass correct arguments for bar range type

Fix #3371
  • Loading branch information
netil authored and netil committed Sep 5, 2023
1 parent 75a1ad1 commit 2e88484
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 21 deletions.
47 changes: 28 additions & 19 deletions src/ChartInternal/internals/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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,
Expand All @@ -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 = `<b>Mid:</b> ${value} <b>High:</b> ${high} <b>Low:</b> ${low}`;
value = `<b>Mid:</b> ${mid} <b>High:</b> ${high} <b>Low:</b> ${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 = `<b>Open:</b> ${open} <b>High:</b> ${high} <b>Low:</b> ${low} <b>Close:</b> ${close}${volume ? ` <b>Volume:</b> ${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) {
Expand All @@ -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),
Expand Down
7 changes: 5 additions & 2 deletions src/config/Options/common/tooltip.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.<br>
* 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
Expand Down Expand Up @@ -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) {
Expand Down
143 changes: 143 additions & 0 deletions test/internals/tooltip-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
});
Expand All @@ -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) => {
Expand All @@ -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();
});
Expand Down Expand Up @@ -1865,6 +1881,133 @@ 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();
});
});

describe("tooltip: show", () => {
Expand Down

0 comments on commit 2e88484

Please sign in to comment.