diff --git a/client/src/components/billing/reports/charts/bar-chart.js b/client/src/components/billing/reports/charts/bar-chart.js
index 9579e461e7..d7e7ab2346 100644
--- a/client/src/components/billing/reports/charts/bar-chart.js
+++ b/client/src/components/billing/reports/charts/bar-chart.js
@@ -24,7 +24,7 @@ import {
} from './extensions';
import Export from '../export';
import {discounts} from '../discounts';
-import {costTickFormatter} from '../utilities';
+import {costTickFormatter, renderCustomTooltip} from '../utilities';
import QuotaSummaryChartsTitle from './quota-summary-chart';
function toValueFormat (value) {
@@ -132,7 +132,8 @@ function BarChart (
quotaGroup,
highlightTickFn,
highlightTickStyle = {},
- extraTooltipForItem = ((o) => undefined)
+ extraTooltipForItem = ((o) => undefined),
+ renderTooltipFn
}
) {
if (!request) {
@@ -277,6 +278,15 @@ function BarChart (
// reverse tooltip orders
return b - a;
},
+ enabled: !renderTooltipFn,
+ ...(renderTooltipFn && {
+ custom: function (model) {
+ const item = model.dataPoints && model.dataPoints.length > 0
+ ? filteredData[model.dataPoints[0].index]
+ : undefined;
+ renderCustomTooltip(item, model, this._chart, renderTooltipFn);
+ }
+ }),
callbacks: {
label: function (tooltipItem, chart) {
const dataset = chart.datasets[tooltipItem.datasetIndex];
diff --git a/client/src/components/billing/reports/charts/object-storage/get-datasets-by-storage-class.js b/client/src/components/billing/reports/charts/object-storage/get-datasets-by-storage-class.js
index 06cf193b74..5d9249839d 100644
--- a/client/src/components/billing/reports/charts/object-storage/get-datasets-by-storage-class.js
+++ b/client/src/components/billing/reports/charts/object-storage/get-datasets-by-storage-class.js
@@ -355,5 +355,8 @@ export function getItemDetailsByMetrics (dataItem, metrics) {
const layoutInfo = (info) =>
// eslint-disable-next-line max-len
`${getStorageClassName(info.tier)}: ${info.total} (current: ${info.current}, old version: ${info.oldVersion})`;
- return `\n${infos.map(layoutInfo).join('\n')}`;
+ return {
+ text: `\n${infos.map(layoutInfo).join('\n')}`,
+ infos
+ };
}
diff --git a/client/src/components/billing/reports/storage-report.js b/client/src/components/billing/reports/storage-report.js
index 922bca042f..585a99f6d2 100644
--- a/client/src/components/billing/reports/storage-report.js
+++ b/client/src/components/billing/reports/storage-report.js
@@ -390,6 +390,73 @@ class StorageReports extends React.Component {
return null;
};
+ getCustomChartTooltip = (item, model) => {
+ if (!item || !model) {
+ return;
+ }
+ const {infos} = this.extraTooltipForItemCallback(item.item) || {};
+ const getMarker = (index) => {
+ const colors = model.labelColors[index];
+ const makerStyles = `
+ background: ${colors.backgroundColor};
+ border: 1px solid ${colors.borderColor};
+ width: 13px;
+ height: 13px;
+ display: block;
+ `;
+ return `
+
+
+
`;
+ };
+ const getMainInfo = () => {
+ const bodyLines = model.body.map(({lines}) => lines);
+ return bodyLines.map((line, index) => {
+ return line && line.length > 0
+ ? `
+ ${getMarker(index)}
+ ${line}
+
`
+ : undefined;
+ }).filter(Boolean).join('');
+ };
+ const getTable = () => {
+ const rowStyle = `border-bottom: 1px solid ${model.bodyFontColor};`;
+ const cellStyle = `padding: 0 5px;`;
+ const rows = infos.map(info => {
+ return `
+ ${info.tier} |
+ ${info.current} |
+ ${info.oldVersion} |
+ ${info.total} |
+
`;
+ }).join('');
+ return `
+
+
+ Storage class |
+ Current |
+ Old ver. |
+ Total |
+
+ ${rows}
+
+ `;
+ };
+ const titleStyles = `
+ display: inline-block;
+ margin-bottom: 3px;`;
+ const title = (model.title || [])
+ .map(title => `${title}`)
+ .join('');
+ const body = `
+ ${getMainInfo()}
+ ${getTable()}
+
+ `;
+ return title.concat(body);
+ };
+
render () {
const {
storages,
@@ -554,7 +621,7 @@ class StorageReports extends React.Component {
highlightTickStyle={{
fontColor: reportThemes.errorColor
}}
- extraTooltipForItem={this.extraTooltipForItemCallback}
+ renderTooltipFn={this.getCustomChartTooltip}
/>
)
diff --git a/client/src/components/billing/reports/utilities/index.js b/client/src/components/billing/reports/utilities/index.js
index 091d7b27e1..92b920c4a6 100644
--- a/client/src/components/billing/reports/utilities/index.js
+++ b/client/src/components/billing/reports/utilities/index.js
@@ -20,3 +20,4 @@ export {default as numberFormatter} from './number-formatter';
export {getUserDisplayInfo, default as DisplayUser} from './display-user';
export {default as ResizableContainer} from './resizable-container';
export {default as getPeriodMonths} from './get-period-months';
+export {default as renderCustomTooltip} from './render-custom-tooltip';
diff --git a/client/src/components/billing/reports/utilities/render-custom-tooltip.js b/client/src/components/billing/reports/utilities/render-custom-tooltip.js
new file mode 100644
index 0000000000..341c4ddea0
--- /dev/null
+++ b/client/src/components/billing/reports/utilities/render-custom-tooltip.js
@@ -0,0 +1,155 @@
+/*
+ * Copyright 2017-2023 EPAM Systems, Inc. (https://www.epam.com/)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+function renderCustomTooltip (item, model, chart, renderTooltipFn) {
+ if (!model || !model.body || !renderTooltipFn) {
+ return;
+ }
+ const tooltip = createUpdateTooltip(item, model, chart);
+ if (!tooltip) {
+ return;
+ }
+ const content = tooltip.querySelector('.chartjs-tooltip-content');
+ content.innerHTML = `
+ ${renderTooltipFn(item, model, chart) || ''}
+
`;
+ setTooltipStyles(tooltip, model, chart);
+ setCaretStyles(tooltip, model);
+}
+
+function createUpdateTooltip (item, model, chart) {
+ let tooltip = document.getElementById('chartjs-tooltip');
+ if (!tooltip) {
+ const content = document.createElement('div');
+ const caret = document.createElement('div');
+ tooltip = document.createElement('div');
+ tooltip.id = 'chartjs-tooltip';
+ content.classList.add('chartjs-tooltip-content');
+ caret.classList.add('chartjs-tooltip-caret');
+ tooltip.appendChild(content);
+ tooltip.appendChild(caret);
+ chart.canvas.parentNode.appendChild(tooltip);
+ }
+ if (model.opacity === 0 || !item) {
+ tooltip.style.opacity = 0;
+ return;
+ }
+ return tooltip;
+};
+
+function setTooltipStyles (tooltip, model, chart) {
+ const tooltipContainer = tooltip.querySelector('.tooltip-container');
+ const {xAlign, yAlign, caretY, caretX} = model;
+ const {
+ offsetLeft: positionX,
+ offsetTop: positionY
+ } = chart.canvas;
+ const {height, width} = tooltip.getBoundingClientRect();
+ const space = 10;
+ let top = positionY + caretY - height;
+ let left = positionX + caretX - width / 2;
+ if (yAlign === 'top') {
+ top += height + space;
+ } else if (yAlign === 'center') {
+ top += height / 2;
+ } else if (yAlign === 'bottom') {
+ top -= space;
+ }
+ if (xAlign === 'left') {
+ left = left + (width / 2) - model.xPadding - (space / 2);
+ if (yAlign === 'center') {
+ left += space * 2;
+ }
+ } else if (xAlign === 'right') {
+ left -= width / 2;
+ if (yAlign === 'center') {
+ left -= space;
+ } else {
+ left += space;
+ }
+ }
+ tooltip.style.cssText = `
+ opacity: 1;
+ position: absolute;
+ color: ${model.bodyFontColor};
+ font-family: ${model._bodyFontFamily};
+ font-style: ${model._bodyFontStyle};
+ padding: ${model.yPadding}px ${model.xPadding}px;
+ pointer-events: none;
+ transition: all 0.3s ease;
+ background-color: ${model.backgroundColor};
+ left: ${left}px;
+ top: ${top}px;
+ will-change: left, top;
+ `;
+ tooltipContainer.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ `;
+};
+
+function setCaretStyles (tooltip, model) {
+ const {xAlign, yAlign} = model;
+ const {
+ height: tooltipHeight,
+ width: tooltipWidth
+ } = tooltip.getBoundingClientRect();
+ const caretSize = 7;
+ const caret = tooltip.querySelector('.chartjs-tooltip-caret');
+ if (!caret) {
+ return;
+ }
+ caret.style.position = 'absolute';
+ caret.style.display = 'block';
+ caret.style.width = '12px';
+ caret.style.height = '12px';
+ caret.style.borderColor = 'transparent';
+ caret.style.borderStyle = 'solid';
+ caret.style.borderWidth = `${caretSize}px`;
+ caret.style.top = 'unset';
+ caret.style.right = 'unset';
+ caret.style.bottom = 'unset';
+ caret.style.left = 'unset';
+ switch (`${yAlign}|${xAlign}`) {
+ case 'center|left':
+ caret.style.borderRightColor = model.backgroundColor;
+ caret.style.top = `${(tooltipHeight / 2) - (caretSize)}px`;
+ caret.style.left = `-${caretSize * 2}px`;
+ break;
+ case 'center|right':
+ caret.style.borderLeftColor = model.backgroundColor;
+ caret.style.top = `${(tooltipHeight / 2) - (caretSize)}px`;
+ caret.style.right = `-${caretSize * 2}px`;
+ break;
+ case 'bottom|left':
+ caret.style.borderTopColor = model.backgroundColor;
+ caret.style.bottom = `-${caretSize * 2}px`;
+ caret.style.left = `${caretSize}px`;
+ break;
+ case 'bottom|center':
+ caret.style.borderTopColor = model.backgroundColor;
+ caret.style.bottom = `-${caretSize * 2}px`;
+ caret.style.left = `${tooltipWidth / 2 - caretSize}px`;
+ break;
+ case 'bottom|right':
+ caret.style.borderTopColor = model.backgroundColor;
+ caret.style.bottom = `-${caretSize * 2}px`;
+ caret.style.left = `${tooltipWidth - caretSize * 2}px`;
+ break;
+ }
+};
+
+export default renderCustomTooltip;