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 ` + + + + + + + + ${rows} +
Storage classCurrentOld ver.Total
+ `; + }; + 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;