mirror of
https://github.com/btcpayserver/btcpayserver.git
synced 2024-11-19 01:43:50 +01:00
Dashboard: Tooltip for balance on a particular day (#5650)
Closes #5617.
This commit is contained in:
parent
6621859567
commit
71c5566f2b
@ -64,13 +64,14 @@
|
||||
|
||||
const id = `StoreWalletBalance-${storeId}`;
|
||||
const baseUrl = @Safe.Json(Url.Action("WalletHistogram", "UIWallets", new { walletId = Model.WalletId, type = WalletHistogramType.Week }));
|
||||
const valueTransform = value => rate
|
||||
? DashboardUtils.displayDefaultCurrency(value, rate, defaultCurrency, divisibility).toString()
|
||||
: value
|
||||
const chartOpts = {
|
||||
fullWidth: true,
|
||||
showArea: true,
|
||||
axisY: {
|
||||
labelInterpolationFnc: value => rate
|
||||
? DashboardUtils.displayDefaultCurrency(value, rate, defaultCurrency, divisibility).toString()
|
||||
: value
|
||||
labelInterpolationFnc: valueTransform
|
||||
}
|
||||
};
|
||||
|
||||
@ -80,16 +81,22 @@
|
||||
document.querySelectorAll(`#${id} .currency`).forEach(c => c.innerText = currency)
|
||||
document.querySelectorAll(`#${id} [data-balance]`).forEach(c => {
|
||||
const value = Number.parseFloat(c.dataset.balance);
|
||||
c.innerText = rate
|
||||
? DashboardUtils.displayDefaultCurrency(value, rate, currency, divisibility)
|
||||
: value
|
||||
c.innerText = valueTransform(value)
|
||||
});
|
||||
if (!series) return;
|
||||
|
||||
const min = Math.min(...series);
|
||||
const max = Math.max(...series);
|
||||
const low = Math.max(min - ((max - min) / 5), 0);
|
||||
const renderOpts = Object.assign({}, chartOpts, { low });
|
||||
const tooltip = Chartist.plugins.tooltip2({
|
||||
template: '{{value}}',
|
||||
offset: {
|
||||
x: 0,
|
||||
y: -16
|
||||
},
|
||||
valueTransformFunction: valueTransform
|
||||
})
|
||||
const renderOpts = Object.assign({}, chartOpts, { low, plugins: [tooltip] });
|
||||
const chart = new Chartist.Line(`#${id} .ct-chart`, {
|
||||
labels,
|
||||
series: [series]
|
||||
|
@ -24,7 +24,9 @@
|
||||
{
|
||||
/* include chart library inline so that it instantly renders */
|
||||
<link rel="stylesheet" href="~/vendor/chartist/chartist.css" asp-append-version="true">
|
||||
<link rel="stylesheet" href="~/vendor/chartist/chartist-plugin-tooltip.css" asp-append-version="true">
|
||||
<script src="~/vendor/chartist/chartist.min.js" asp-append-version="true"></script>
|
||||
<script src="~/vendor/chartist/chartist-plugin-tooltip.js" asp-append-version="true"></script>
|
||||
<script>
|
||||
const DashboardUtils = {
|
||||
toDefaultCurrency(amount, rate) {
|
||||
|
58
BTCPayServer/wwwroot/vendor/chartist/chartist-plugin-tooltip.css
vendored
Normal file
58
BTCPayServer/wwwroot/vendor/chartist/chartist-plugin-tooltip.css
vendored
Normal file
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Chartist.js plugin to display a tooltip on top of a chart.
|
||||
* @author Antonia Ciocodeica
|
||||
* @version 0.1 22 Nov 2016
|
||||
*/
|
||||
.chartist-tooltip {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
padding: var(--btcpay-space-xs) var(--btcpay-space-s);
|
||||
visibility: hidden;
|
||||
transform: translateY(3em);
|
||||
opacity: 0;
|
||||
border-radius: var(--btcpay-border-radius);
|
||||
border: 1px solid var(--btcpay-body-border-light);
|
||||
color: var(--btcpay-body-text);
|
||||
background-color: var(--btcpay-bg-tile);
|
||||
transition: transform var(--btcpay-transition-duration-fast) ease-in-out;
|
||||
}
|
||||
.chartist-tooltip:not([hidden]) {
|
||||
margin: 0;
|
||||
visibility: visible;
|
||||
transform: none;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* Tooltip arrow */
|
||||
.chartist-tooltip::before {
|
||||
content: '\25BC';
|
||||
position: absolute;
|
||||
left: calc(50% - .5em);
|
||||
top: 100%;
|
||||
z-index: -1;
|
||||
font-size: 1.3em;
|
||||
line-height: .5em;
|
||||
font-family: Arial, sans-serif;
|
||||
color: var(--btcpay-body-border-light);
|
||||
transform: scaleY(0.7);
|
||||
text-shadow: 0 0.25em 0.35em rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.chartist-tooltip--left::before {
|
||||
left: 0.75em;
|
||||
}
|
||||
.chartist-tooltip--right::before {
|
||||
left: auto;
|
||||
right: 0.75em;
|
||||
}
|
||||
|
||||
/* Adds a small point transition (line charts) when the point is active */
|
||||
.ct-point {
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
/* Increased specificity intended to overwrite the default chartist style */
|
||||
.ct-chart-line.ct-chart-line .ct-point--hover {
|
||||
stroke-width: 1.25em;
|
||||
}
|
464
BTCPayServer/wwwroot/vendor/chartist/chartist-plugin-tooltip.js
vendored
Normal file
464
BTCPayServer/wwwroot/vendor/chartist/chartist-plugin-tooltip.js
vendored
Normal file
@ -0,0 +1,464 @@
|
||||
//https://github.com/ant7/chartist-plugin-tooltip2
|
||||
(function (root, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
// AMD. Register as an anonymous module.
|
||||
define(["chartist"], function (Chartist) {
|
||||
return (root.returnExportsGlobal = factory(Chartist));
|
||||
});
|
||||
} else if (typeof module === 'object' && module.exports) {
|
||||
// Node. Does not work with strict CommonJS, but
|
||||
// only CommonJS-like environments that support module.exports,
|
||||
// like Node.
|
||||
module.exports = factory(require("chartist"));
|
||||
} else {
|
||||
// Browser globals (root is window)
|
||||
root['Chartist.plugins.tooltip2'] = factory(Chartist);
|
||||
}
|
||||
}(typeof self !== 'undefined' ? self : this, function (Chartist) {
|
||||
|
||||
/**
|
||||
* Chartist.js plugin to display a tooltip on top of a chart.
|
||||
* @author Antonia Ciocodeica
|
||||
* @version 0.3 25 Nov 2016
|
||||
*/
|
||||
(function(window, document, Chartist) {
|
||||
'use strict';
|
||||
|
||||
|
||||
var startId = 0;
|
||||
|
||||
var publicOptions = {
|
||||
cssClass: 'chartist-tooltip',
|
||||
offset: {
|
||||
x: 0,
|
||||
y: -20,
|
||||
},
|
||||
offsetCollision: {
|
||||
x: 20,
|
||||
y: 0, // vertical collision not implemented
|
||||
},
|
||||
|
||||
// Value transform function
|
||||
// It receives a single argument that contains the current value
|
||||
// "this" is the current chart
|
||||
// It must return the formatted value to be added in the tooltip (eg: currency format)
|
||||
valueTransformFunction: null,
|
||||
|
||||
// Use an already existing element as a template for the tooltip.
|
||||
// The content of the element must be a Mustache-style template
|
||||
// {{value}} {{metaElement}}
|
||||
elementTemplateSelector: null,
|
||||
|
||||
// Markup to use as a template for the content of the tooltip
|
||||
template: '<p>{{meta}}: {{value}}</p>',
|
||||
|
||||
hideDelay: 500,
|
||||
|
||||
// If you choose to reverse the original order of the chart elements in
|
||||
// the DOM, you must set this to true
|
||||
dataDrawnReversed: false,
|
||||
|
||||
// only if a custom element is used for the trigger (TODO: test)
|
||||
triggerSelector: null,
|
||||
|
||||
id: null,
|
||||
};
|
||||
|
||||
Chartist.plugins = Chartist.plugins || {};
|
||||
|
||||
Chartist.plugins.tooltip2 = function(options) {
|
||||
options = Chartist.extend({}, publicOptions, options);
|
||||
|
||||
/**
|
||||
* Chartist tooltip plugin
|
||||
* @param Chart chart
|
||||
*/
|
||||
return function tooltip(chart) {
|
||||
startId ++;
|
||||
|
||||
// simple unique id for the tooltip element (needed to be able to
|
||||
// add aria-describedby to the trigger while the tooltip is visible)
|
||||
options.id = 'charttooltip-' + startId;
|
||||
var triggerSelector = getTriggerSelector();
|
||||
var hoverClass = getDefaultTriggerClass() + '--hover';
|
||||
var tooltipElement = getTooltipElement();
|
||||
var pointValues = getPointValues();
|
||||
var hideDelayTimer;
|
||||
|
||||
options.template = tooltipElement.innerHTML;
|
||||
|
||||
init();
|
||||
|
||||
/**
|
||||
* Initialize the tooltip
|
||||
*/
|
||||
function init() {
|
||||
if (!chart.container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// set attribute on the container, so external scripts can detect the tooltip element
|
||||
chart.container.setAttribute('data-charttooltip-id', options.id);
|
||||
|
||||
// set the initial position for the tooltip (top / left corner of the chart container)
|
||||
setTooltipPosition(chart.container, true);
|
||||
|
||||
// Offer support for multiple series line charts
|
||||
if (chart instanceof Chartist.Line) {
|
||||
chart.on('created', function() {
|
||||
if (pointValues.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
chart.container.querySelector('svg').addEventListener('mousemove', prepareLineTooltip);
|
||||
chart.container.addEventListener('mouseleave', function(e) {
|
||||
var pointElement = chart.container.querySelector('.' + chart.options.classNames.point + '--hover');
|
||||
hideTooltip(pointElement);
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
chart.container.addEventListener('mouseover', delegate(triggerSelector, function(e) {
|
||||
showTooltip(e.target);
|
||||
}));
|
||||
chart.container.addEventListener('mouseout', delegate(triggerSelector, function(e) {
|
||||
hideTooltip(e.target);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Prepare line tooltip
|
||||
* Calculates the closest point on the line according to the current position of the mouse
|
||||
* @param Event e
|
||||
*/
|
||||
function prepareLineTooltip(e) {
|
||||
var boxData = this.getBoundingClientRect();
|
||||
var currentXPosition = e.pageX - (boxData.left + (document.documentElement.scrollLeft || document.body.scrollLeft));
|
||||
var currentYPosition = e.pageY - (boxData.top + (document.documentElement.scrollTop || document.body.scrollTop));
|
||||
var closestPointOnX = getClosestNumberFromArray(currentXPosition, pointValues);
|
||||
|
||||
var pointElements = chart.container.querySelectorAll('.' + chart.options.classNames.point + '[x1="' + closestPointOnX + '"]');
|
||||
var pointElement;
|
||||
|
||||
if (pointElements.length <= 1) {
|
||||
pointElement = pointElements[0];
|
||||
} else {
|
||||
var yPositions = [];
|
||||
var closestPointOnY;
|
||||
|
||||
Array.prototype.forEach.call(pointElements, function(point) {
|
||||
yPositions.push(point.getAttribute('y1'));
|
||||
});
|
||||
|
||||
closestPointOnY = getClosestNumberFromArray(currentYPosition, yPositions);
|
||||
pointElement = chart.container.querySelector('.' + chart.options.classNames.point + '[x1="' + closestPointOnX + '"][y1="' + closestPointOnY + '"]');
|
||||
}
|
||||
|
||||
if (!pointElement || matches(pointElement, '.' + hoverClass)) {
|
||||
return;
|
||||
}
|
||||
|
||||
showTooltip(pointElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tooltip
|
||||
* @param Element triggerElement
|
||||
*/
|
||||
function showTooltip(triggerElement) {
|
||||
var meta;
|
||||
var value;
|
||||
var textMarkup = options.template;
|
||||
var seriesName;
|
||||
var seriesGroups;
|
||||
var seriesIndex;
|
||||
var valueGroup;
|
||||
var valueIndex;
|
||||
var itemData;
|
||||
|
||||
var seriesData;
|
||||
|
||||
clearTimeout(hideDelayTimer);
|
||||
|
||||
if (!triggerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
seriesName = triggerElement.parentNode.getAttribute('ct:series-name');
|
||||
seriesGroups = Array.prototype.slice.call(triggerElement.parentNode.parentNode.children);
|
||||
seriesIndex = options.dataDrawnReversed ? seriesGroups.reverse().indexOf(triggerElement.parentNode) : seriesGroups.indexOf(triggerElement.parentNode);
|
||||
|
||||
valueGroup = Array.prototype.slice.call(triggerElement.parentNode.querySelectorAll('.' + getDefaultTriggerClass()));
|
||||
valueIndex = valueGroup.indexOf(triggerElement);
|
||||
|
||||
// clone the series array
|
||||
seriesData = chart.data.series.slice(0);
|
||||
seriesData = chart.options.reverseData ? seriesData.reverse()[seriesIndex] : seriesData[seriesIndex];
|
||||
seriesData = (!Array.isArray(seriesData) && typeof seriesData == 'object' && seriesData.data) ? seriesData.data : seriesData;
|
||||
|
||||
if (!seriesData) {
|
||||
return;
|
||||
}
|
||||
|
||||
itemData = (!Array.isArray(seriesData) && typeof seriesData == 'object') ? seriesData : seriesData[valueIndex];
|
||||
|
||||
if (typeof itemData == 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
meta = itemData.meta;
|
||||
value = itemData.value || itemData;
|
||||
|
||||
if (typeof options.valueTransformFunction === 'function') {
|
||||
value = options.valueTransformFunction.call(chart, value);
|
||||
}
|
||||
|
||||
// Remove the hover class and the aria-describedby attribute from the currently active triggers
|
||||
var activeTriggerElements = chart.container.querySelectorAll('.' + hoverClass);
|
||||
Array.prototype.forEach.call(activeTriggerElements, function(activeTriggerElement) {
|
||||
activeTriggerElement.classList.remove(hoverClass);
|
||||
activeTriggerElement.removeAttribute('aria-describedby');
|
||||
});
|
||||
|
||||
// add hover class to the current active trigger
|
||||
triggerElement.classList.add(hoverClass);
|
||||
triggerElement.setAttribute('aria-describedby', options.id);
|
||||
|
||||
// value
|
||||
textMarkup = textMarkup.replace(new RegExp('{{value}}', 'gi'), value);
|
||||
|
||||
// replace all known {{}} occurences with their respective values
|
||||
if (meta && typeof meta === 'object') {
|
||||
for (var metaKey in meta) {
|
||||
textMarkup = textMarkup.replace(new RegExp('{{' + metaKey + '}}', 'gi'), meta[metaKey] || '');
|
||||
}
|
||||
} else {
|
||||
textMarkup = textMarkup.replace(new RegExp('{{meta}}', 'gi'), meta || '');
|
||||
}
|
||||
|
||||
// series name
|
||||
textMarkup = textMarkup.replace(new RegExp('{{seriesName}}', 'gi'), seriesName || '');
|
||||
console.log(textMarkup)
|
||||
tooltipElement.innerHTML = textMarkup;
|
||||
tooltipElement.removeAttribute('hidden');
|
||||
setTooltipPosition(triggerElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide tooltip
|
||||
* @param Elemet triggerElement
|
||||
*/
|
||||
function hideTooltip(triggerElement) {
|
||||
if (!triggerElement) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideDelayTimer = setTimeout(function() {
|
||||
triggerElement.removeAttribute('aria-describedby');
|
||||
tooltipElement.setAttribute('hidden', true);
|
||||
triggerElement.classList.remove(getDefaultTriggerClass() + '--hover');
|
||||
}, options.hideDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tooltip element
|
||||
* @return Element
|
||||
*/
|
||||
function getTooltipElement() {
|
||||
var tooltipElement = document.getElementById(options.id);
|
||||
|
||||
if (tooltipElement) {
|
||||
return tooltipElement;
|
||||
}
|
||||
|
||||
return createTooltipElement();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create tooltip element
|
||||
* @return Element
|
||||
*/
|
||||
function createTooltipElement() {
|
||||
var tooltipElement = document.createElement('div');
|
||||
var tooltipTemplateElement;
|
||||
|
||||
if (options.elementTemplateSelector) {
|
||||
tooltipTemplateElement = document.querySelector(options.elementTemplateSelector);
|
||||
if (tooltipTemplateElement) {
|
||||
if (tooltipTemplateElement.nodeName == 'TEMPLATE') {
|
||||
tooltipElement.innerHTML = tooltipTemplateElement.innerHTML;
|
||||
} else {
|
||||
tooltipElement = tooltipTemplateElement.cloneNode(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!tooltipTemplateElement) {
|
||||
tooltipElement.innerHTML = options.template;
|
||||
}
|
||||
|
||||
tooltipElement.classList.add(options.cssClass);
|
||||
tooltipElement.id = options.id;
|
||||
|
||||
tooltipElement.setAttribute('role', 'tooltip');
|
||||
tooltipElement.setAttribute('hidden', 'true');
|
||||
|
||||
document.body.appendChild(tooltipElement);
|
||||
|
||||
return tooltipElement;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set tooltip position
|
||||
* @param Element relativeElement
|
||||
* @param Boolean ignoreClasses
|
||||
*/
|
||||
function setTooltipPosition(relativeElement, ignoreClasses) {
|
||||
var positionData = getTooltipPosition(relativeElement);
|
||||
|
||||
tooltipElement.style.transform = 'translate(' + positionData.left + 'px, ' + positionData.top + 'px)';
|
||||
|
||||
if (ignoreClasses) {
|
||||
return;
|
||||
}
|
||||
|
||||
tooltipElement.classList.remove(options.cssClass + '--right');
|
||||
tooltipElement.classList.remove(options.cssClass + '--left');
|
||||
tooltipElement.classList.add(options.cssClass + '--' + positionData.alignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tooltip position relative to an element
|
||||
* @param Element relativeElement
|
||||
* @return Object positionData
|
||||
*/
|
||||
function getTooltipPosition(relativeElement) {
|
||||
var positionData = {
|
||||
alignment: 'center',
|
||||
};
|
||||
var width = tooltipElement.offsetWidth;
|
||||
var height = tooltipElement.offsetHeight;
|
||||
|
||||
var boxData = relativeElement.getBoundingClientRect();
|
||||
var left = boxData.left + window.scrollX + options.offset.x - width / 2 + boxData.width / 2;
|
||||
var top = boxData.top + window.scrollY - height + options.offset.y;
|
||||
|
||||
// Minimum horizontal collision detection
|
||||
if (left + width > document.body.clientWidth) {
|
||||
left = left - width / 2 + options.offsetCollision.x;
|
||||
positionData.alignment = 'right';
|
||||
} else if (left < 0) {
|
||||
left = boxData.left + window.scrollX - options.offsetCollision.x;
|
||||
positionData.alignment = 'left';
|
||||
}
|
||||
|
||||
positionData.left = left;
|
||||
positionData.top = top;
|
||||
|
||||
return positionData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get trigger selector
|
||||
* @return String The selector of the element that should trigger the tooltip
|
||||
*/
|
||||
function getTriggerSelector() {
|
||||
if (options.triggerSelector) {
|
||||
return options.triggerSelector;
|
||||
}
|
||||
|
||||
return '.' + getDefaultTriggerClass();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default trigger class from the chart instance
|
||||
* @return string chart.options.classNames.[specificClassName]
|
||||
*/
|
||||
function getDefaultTriggerClass() {
|
||||
if (chart instanceof Chartist.Bar) {
|
||||
return chart.options.classNames.bar;
|
||||
}
|
||||
if (chart instanceof Chartist.Pie) {
|
||||
return (chart.options.donut ? chart.options.classNames.sliceDonut : chart.options.classNames.slicePie);
|
||||
}
|
||||
|
||||
return chart.options.classNames.point;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get horizontal point values (only useful for the line type chart)
|
||||
* @return Array pointValues The point values
|
||||
*/
|
||||
function getPointValues() {
|
||||
var pointValues = [];
|
||||
|
||||
if (!(chart instanceof Chartist.Line)) {
|
||||
return;
|
||||
}
|
||||
|
||||
chart.on('draw', function(data) {
|
||||
if (data.type == 'point') {
|
||||
pointValues.push(data.x);
|
||||
}
|
||||
});
|
||||
|
||||
return pointValues;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Delegate event
|
||||
* @param string selector
|
||||
* @param function listener
|
||||
* @returns function
|
||||
*/
|
||||
function delegate(selector, listener) {
|
||||
return function(e) {
|
||||
var element = e.target;
|
||||
do {
|
||||
if (!matches(element, selector)) {
|
||||
continue;
|
||||
}
|
||||
e.delegateTarget = element;
|
||||
listener.apply(this, arguments);
|
||||
return;
|
||||
} while ((element = element.parentNode));
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches selector
|
||||
* @param Element el
|
||||
* @param string selector
|
||||
* @returns bool
|
||||
*/
|
||||
function matches(el, selector) {
|
||||
var matchesFunction = el.matches || el.webkitMatchesSelector || el.mozMatchesSelector || el.msMatchesSelector;
|
||||
if (matchesFunction) {
|
||||
return matchesFunction.call(el, selector);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the closest number from an array
|
||||
* @param Int/Float number
|
||||
* @param Array array
|
||||
* @return Int The value from the array that is closest to the number
|
||||
*/
|
||||
function getClosestNumberFromArray(number, array) {
|
||||
return array.reduce(function (previous, current) {
|
||||
return (Math.abs(current - number) < Math.abs(previous - number) ? current : previous);
|
||||
});
|
||||
}
|
||||
|
||||
}(window, document, Chartist));
|
||||
|
||||
// Just return a value to define the module export.
|
||||
return Chartist.plugins.tooltip2;
|
||||
}));
|
Loading…
Reference in New Issue
Block a user