Skip to content

Commit

Permalink
GUI Billing enhancements (issue #3080): layers chart - fix dataLabels…
Browse files Browse the repository at this point in the history
… placement. Highlight selected tick. Tooltips name mapping. Total dataLabel
  • Loading branch information
AleksandrGorodetskii committed Mar 1, 2023
1 parent e23cc6e commit c89e80f
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 42 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2017-2019 EPAM Systems, Inc. (https://www.epam.com/)
* 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.
Expand All @@ -26,7 +26,7 @@ function isNotSet (v) {
const plugin = {
id,
afterDatasetsDraw: function (chart, ease, pluginOptions) {
const {valueFormatter = costTickFormatter} = pluginOptions || {};
const {valueFormatter = costTickFormatter, chartType} = pluginOptions || {};
const ctx = chart.chart.ctx;
const datasetLabels = chart.data.datasets
.map((dataset, i) => {
Expand All @@ -39,7 +39,8 @@ const plugin = {
index,
meta,
chart,
valueFormatter
valueFormatter,
chartType
));
}
return [];
Expand Down Expand Up @@ -135,7 +136,8 @@ const plugin = {
borderColor = 'black',
textBold = false,
textColor,
flagColor
flagColor,
dataLabelText
} = dataset;
const color = textColor || borderColor;
const {position, text} = config;
Expand All @@ -145,8 +147,11 @@ const plugin = {
ctx.font = `${textBold ? 'bold ' : ''}9pt sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText(text, position.x + position.width / 2.0, position.y + position.height);
if (flagColor) {
ctx.fillText(
`${dataLabelText || ''}${text}`,
position.x + position.width / 2.0, position.y + position.height
);
if (flagColor && !dataLabelText) {
ctx.beginPath();
ctx.arc(
position.x,
Expand All @@ -162,14 +167,23 @@ const plugin = {
}
}
},
getInitialLabelConfig: function (dataset, element, index, meta, chart, valueFormatter) {
getInitialLabelConfig: function (
dataset,
element,
index,
meta,
chart,
valueFormatter,
chartType
) {
const {
data,
hidden,
textBold = false
textBold = false,
showDataLabel = false
} = dataset;
const {xAxisID, yAxisID} = meta;
if (hidden) {
if (hidden && !showDataLabel) {
return null;
}
const xAxis = chart.scales[xAxisID];
Expand All @@ -185,15 +199,38 @@ const plugin = {
left: xAxis.left,
right: xAxis.right
};
const getLabelXY = () => {
const visibleOnlyLabel = hidden && showDataLabel;
let currentLabelY = yAxis.getPixelForValue(dataItem);
if (visibleOnlyLabel) {
const padding = (globalBounds.bottom - globalBounds.top) * 0.1;
currentLabelY = yAxis.getPixelForValue(data[index]) - padding;
}
let offset = 0;
if (chartType === 'stacked') {
const datasetIndex = element._datasetIndex;
if (datasetIndex > 0 && !visibleOnlyLabel) {
for (let i = 0; i < datasetIndex; i++) {
const dataset = chart.data.datasets[i];
const prevData = (dataset || {}).data || [];
const prevLabelHeight = globalBounds.bottom - yAxis
.getPixelForValue(prevData[index] || 0);
offset += prevLabelHeight;
}
}
}
return {
x: element.getCenterPoint().x,
y: currentLabelY - offset
};
};
const labelText = valueFormatter(dataItem);
ctx.font = `${textBold ? 'bold ' : ''}9pt sans-serif`;
const {width: labelWidth} = ctx.measureText(labelText);
const padding = {x: 5, y: 2};
const margin = 3;
const labelHeight = 10;
const {x} = element.getCenterPoint();
const y = yAxis.getPixelForValue(dataItem);
const dataPoint = {x, y};
const {x, y} = getLabelXY();
const labelTotalWidth = labelWidth + 2.0 * (margin + padding.x);
const labelTotalHeight = labelHeight + 2.0 * (margin + padding.y);
const getLabelPosition = (yy = y) => {
Expand All @@ -208,7 +245,7 @@ const plugin = {
dataset,
globalBounds,
label: {
dataPoint,
dataPoint: {x, y},
getLabelPosition,
labelHeight: labelTotalHeight,
text: labelText
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ const plugin = {
const ticks = ((chart.scales || {})[axis] || {})._ticks;
if (ticks && ticks.length) {
for (const tick of ticks) {
tick.major = highlightTickFn(request.value[tick.value]);
const storage = (request || {}).value;
const value = (storage || {})[tick.value];
tick.major = highlightTickFn(value, tick);
}
}
}
Expand Down
18 changes: 13 additions & 5 deletions client/src/components/billing/reports/charts/storage-layers.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {inject, observer} from 'mobx-react';
import Chart from './base';
import {
BarchartDataLabelPlugin,
ChartClickPlugin
ChartClickPlugin,
HighlightTicksPlugin
} from './extensions';
import Export from '../export';
import {costTickFormatter} from '../utilities';
Expand Down Expand Up @@ -82,7 +83,9 @@ function StorageLayers (
borderWidth: 2,
borderDash: [4, 4],
borderColor: baseColors,
backgroundColor: backgroundColors
backgroundColor: backgroundColors,
flagColor: baseColors[index],
textColor: reportThemes.textColor
};
})
};
Expand Down Expand Up @@ -167,9 +170,13 @@ function StorageLayers (
}
},
plugins: {
// todo: improve labels Y-align, according to stacked chart type
[HighlightTicksPlugin.id]: {
highlightTickFn,
axis: 'x-axis'
},
[BarchartDataLabelPlugin.id]: {
valueFormatter
valueFormatter,
chartType: 'stacked'
},
[ChartClickPlugin.id]: {
handler: onSelect ? index => onSelect({key: aggregates[index]}) : undefined,
Expand Down Expand Up @@ -220,7 +227,8 @@ function StorageLayers (
options={options}
plugins={[
BarchartDataLabelPlugin.plugin,
ChartClickPlugin.plugin
ChartClickPlugin.plugin,
HighlightTicksPlugin.plugin
]}
useChartImageGenerator={useImageConsumer}
onImageDataReceived={onImageDataReceived}
Expand Down
61 changes: 42 additions & 19 deletions client/src/components/billing/reports/storage-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@
import React from 'react';
import {inject, observer} from 'mobx-react';
import {
Pagination,
Table
Pagination
} from 'antd';
import moment from 'moment-timezone';
import {computed} from 'mobx';
Expand Down Expand Up @@ -54,7 +53,8 @@ import {
GetGroupedObjectStorages,
GetGroupedObjectStoragesWithPrevious,
GetObjectStorageLayersInfo,
preFetchBillingRequest
preFetchBillingRequest,
LAYERS_KEYS
} from '../../../models/billing';
import {StorageReportLayout, Layout} from './layout';
import {
Expand All @@ -70,6 +70,13 @@ import styles from './storage-report.css';

const tablePageSize = 10;

const LAYERS_LABELS = {
[LAYERS_KEYS.avgSize]: 'Average size',
[LAYERS_KEYS.oldVersionAvgSize]: 'Old versions average size',
[LAYERS_KEYS.cost]: 'Cost',
[LAYERS_KEYS.oldVersionCost]: 'Old versions cost'
};

function injection (stores, props) {
const {location, params} = props;
const {type} = params || {};
Expand Down Expand Up @@ -192,9 +199,9 @@ function renderTable (
} = item || {};
if (!showDetails) {
switch (key) {
case 'size': return (usageLast || 0);
case 'avgSize': return (usage || 0);
case 'cost': return (value || 0);
case LAYERS_KEYS.size: return (usageLast || 0);
case LAYERS_KEYS.avgSize: return (usage || 0);
case LAYERS_KEYS.cost: return (value || 0);
default:
return 0;
}
Expand Down Expand Up @@ -300,22 +307,22 @@ function renderTable (
...getDetailedCells({
title: 'Cost',
dataExtractor: getLayerCostValue,
currentKey: 'cost',
oldVersionsKey: 'oldVersionCost'
currentKey: LAYERS_KEYS.cost,
oldVersionsKey: LAYERS_KEYS.oldVersionCost
}),
...getDetailedCells({
title: 'Avg. Vol.',
measure: 'GB',
dataExtractor: getLayerSizeValue,
currentKey: 'avgSize',
oldVersionsKey: 'oldVersionAvgSize'
currentKey: LAYERS_KEYS.avgSize,
oldVersionsKey: LAYERS_KEYS.oldVersionAvgSize
}),
...getDetailedCells({
title: 'Cur. Vol.',
measure: 'GB',
dataExtractor: getLayerSizeValue,
currentKey: 'size',
oldVersionsKey: 'oldVersionSize'
currentKey: LAYERS_KEYS.size,
oldVersionsKey: LAYERS_KEYS.oldVersionSize
}),
{
key: 'region',
Expand Down Expand Up @@ -574,20 +581,31 @@ class StorageReports extends React.Component {
});
return result;
};

const filter = metrics === StorageMetrics.volume
? ['avgSize', 'oldVersionAvgSize']
: ['cost', 'oldVersionCost'];
? [LAYERS_KEYS.avgSize, LAYERS_KEYS.oldVersionAvgSize]
: [LAYERS_KEYS.cost, LAYERS_KEYS.oldVersionCost];
const datasets = filter
.map(key => {
return {
label: key,
label: LAYERS_LABELS[key] || key,
data: getData(key, labels)
};
});
const totalDataset = (datasets || []).reduce((acc, current) => {
acc.data = current.data.map((value, index) => value + (acc.data[index] || 0));
return acc;
}, {
data: [],
label: 'Total',
dataLabelText: 'Total: ',
hidden: true,
showDataLabel: true
});
return {
aggregates,
labels: labels.map(getStorageClassName),
datasets
datasets: [...datasets, totalDataset]
};
}

Expand Down Expand Up @@ -621,6 +639,7 @@ class StorageReports extends React.Component {
? 'by volume'
: undefined;
const showTableDetails = /^object$/i.test(type);
const selectedIndex = tiersData.aggregates.indexOf(storageAggregate);
return (
<Discounts.Consumer>
{
Expand Down Expand Up @@ -692,15 +711,19 @@ class StorageReports extends React.Component {
<StorageFilter />
</div>
<StorageLayers
highlightedLabel={
tiersData.aggregates.indexOf(storageAggregate)
}
highlightedLabel={selectedIndex}
loading={tiersPending}
onSelect={this.onSelectLayer}
data={tiersData}
title={'Object storage layers'}
style={{height: height - costsUsageSelectorHeight}}
valueFormatter={valueFormatter}
highlightTickFn={
(_, tick) => tick._index === selectedIndex
}
highlightTickStyle={{
fontColor: reportThemes.current
}}
/>
</div>
)
Expand Down
25 changes: 23 additions & 2 deletions client/src/models/billing/base-billing-request.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@ import TimedOutCache from '../basic/timed-out-cache';
import {bytesToGbs, costMapper} from './utils';
import defer from '../../utils/defer';

export const KEYS = {
cost: 'cost',
oldVersionCost: 'oldVersionCost',
accumulativeCost: 'accumulativeCost',
accumulativeOldVersionCost: 'accumulativeOldVersionCost',
size: 'size',
avgSize: 'avgSize',
oldVersionSize: 'oldVersionSize',
oldVersionAvgSize: 'oldVersionAvgSize'
};

/**
* @typedef {Object} BaseBillingRequestPagination
* @property {number} pageNum
Expand Down Expand Up @@ -139,8 +150,18 @@ export default class BaseBillingRequest extends RemotePost {

postprocess (value) {
const items = super.postprocess(value);
const costKeys = ['cost', 'oldVersionCost', 'accumulativeCost', 'accumulativeOldVersionCost'];
const sizeKeys = ['size', 'avgSize', 'oldVersionSize', 'oldVersionAvgSize'];
const costKeys = [
KEYS.cost,
KEYS.oldVersionCost,
KEYS.accumulativeCost,
KEYS.accumulativeOldVersionCost
];
const sizeKeys = [
KEYS.size,
KEYS.avgSize,
KEYS.oldVersionSize,
KEYS.oldVersionAvgSize
];
const processTier = (tier) => {
const result = {
...tier
Expand Down
5 changes: 3 additions & 2 deletions client/src/models/billing/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import {GetGroupedPipelines, GetGroupedPipelinesWithPrevious} from './get-groupe
import {GetGroupedTools, GetGroupedToolsWithPrevious} from './get-grouped-tools-data';
import {GetGroupedUsers} from './get-grouped-users';
import GetObjectStorageLayersInfo from './get-object-storage-layers-info';
import {preFetchBillingRequest} from './base-billing-request';
import {preFetchBillingRequest, KEYS as LAYERS_KEYS} from './base-billing-request';

export {
FetchBillingCenters,
Expand All @@ -58,5 +58,6 @@ export {
GetGroupedTools,
GetGroupedToolsWithPrevious,
GetObjectStorageLayersInfo,
preFetchBillingRequest
preFetchBillingRequest,
LAYERS_KEYS
};

0 comments on commit c89e80f

Please sign in to comment.