How to change tooltip on Angular Chart.js - angularjs

So, I'm using angular-chart-js plugin on an ionic app (Not sure if that relevant).
With linear chart, by default, when clicking on a dot I get a tooltip as shown:
And I want to change the inside of that tooltip, I couldn't find anything on google.
Any help is appreciated

In the chart options you can specify for a chart, you can create a function to return a template for the tooltip.
$scope.chart_options = {
multiTooltipTemplate: function(label) {
return label.label + ': ' + label.value;
}
}
and in your view:
<canvas id="" class="chart chart-bar" legend="true"
series="bar_series" colours="colors"
data="bar_data" labels="bar_labels"
options="chart_options">
The label object looks like
label = {value: 55, label: "8/18 - 8/24", datasetLabel: "Foo",
strokeColor: "rgba(178,145,47,1)", fillColor: "rgba(178,145,47,0.2)"…}
Edit: The multiTooltipTemplate is used for bar, line, etc, where you have multiple data points for each x axis value. For pie or doughnut, you would just use tooltipTemplate.

I know this is an old question but just for anyone else looking for this - there is a simpler way to customize the tooltips globally.
In your module:
myApp.config(['ChartJsProvider', function (ChartJsProvider) {
ChartJsProvider.setOptions({
tooltipFillColor: '#EEE',
tooltipFontColor: '#000',
tooltipFontSize: 12,
tooltipCornerRadius: 3
});
There are other options to customize the tooltips and almost everything else globally
// Boolean - Whether to animate the chart
animation: true,
// Number - Number of animation steps
animationSteps: 60,
// String - Animation easing effect
animationEasing: "easeOutQuart",
// Boolean - If we should show the scale at all
showScale: true,
// Boolean - If we want to override with a hard coded scale
scaleOverride: false,
// ** Required if scaleOverride is true **
// Number - The number of steps in a hard coded scale
scaleSteps: null,
// Number - The value jump in the hard coded scale
scaleStepWidth: null,
// Number - The scale starting value
scaleStartValue: null,
// String - Colour of the scale line
scaleLineColor: "rgba(0,0,0,.1)",
// Number - Pixel width of the scale line
scaleLineWidth: 1,
// Boolean - Whether to show labels on the scale
scaleShowLabels: true,
// Interpolated JS string - can access value
scaleLabel: "<%=value%>",
// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
scaleIntegersOnly: true,
// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
scaleBeginAtZero: false,
// String - Scale label font declaration for the scale label
scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
// Number - Scale label font size in pixels
scaleFontSize: 12,
// String - Scale label font weight style
scaleFontStyle: "normal",
// String - Scale label font colour
scaleFontColor: "#666",
// Boolean - whether or not the chart should be responsive and resize when the browser does.
responsive: false,
// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
maintainAspectRatio: true,
// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
showTooltips: true,
// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
customTooltips: false,
// Array - Array of string names to attach tooltip events
tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
// String - Tooltip background colour
tooltipFillColor: "rgba(0,0,0,0.8)",
// String - Tooltip label font declaration for the scale label
tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
// Number - Tooltip label font size in pixels
tooltipFontSize: 14,
// String - Tooltip font weight style
tooltipFontStyle: "normal",
// String - Tooltip label font colour
tooltipFontColor: "#fff",
// String - Tooltip title font declaration for the scale label
tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
// Number - Tooltip title font size in pixels
tooltipTitleFontSize: 14,
// String - Tooltip title font weight style
tooltipTitleFontStyle: "bold",
// String - Tooltip title font colour
tooltipTitleFontColor: "#fff",
// String - Tooltip title template
tooltipTitleTemplate: "<%= label%>",
// Number - pixel width of padding around tooltip text
tooltipYPadding: 6,
// Number - pixel width of padding around tooltip text
tooltipXPadding: 6,
// Number - Size of the caret on the tooltip
tooltipCaretSize: 8,
// Number - Pixel radius of the tooltip border
tooltipCornerRadius: 6,
// Number - Pixel offset from point x to tooltip edge
tooltipXOffset: 10,
// String - Template string for single tooltips
tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
// String - Template string for single tooltips
multiTooltipTemplate: "<%= datasetLabel %>: <%= value %>",
// String - Colour behind the legend colour block
multiTooltipKeyBackground: '#fff',
// Array - A list of colors to use as the defaults
segmentColorDefault: ["#A6CEE3", "#1F78B4", "#B2DF8A", "#33A02C", "#FB9A99", "#E31A1C", "#FDBF6F", "#FF7F00", "#CAB2D6", "#6A3D9A", "#B4B482", "#B15928" ],
// Array - A list of highlight colors to use as the defaults
segmentHighlightColorDefaults: [ "#CEF6FF", "#47A0DC", "#DAFFB2", "#5BC854", "#FFC2C1", "#FF4244", "#FFE797", "#FFA728", "#F2DAFE", "#9265C2", "#DCDCAA", "#D98150" ],
// Function - Will fire on animation progression.
onAnimationProgress: function(){},
// Function - Will fire on animation completion.
onAnimationComplete: function(){}

Well, I needed to change my tooltips to make them have the same content of their respective labels, BUT with a detail: make it even if the labels are hidden in the chart. Like this:
This image above shows a chart TIME x VALUE where the interval of time is 20 minutes. That is, I wanted the tooltips to have their respective values of the label (e.g.: on the image, I put the mouse in the value corresponding to the time 18:10, the half of the interval).
All I needed to do was:
1. Create a static array in my class (lazy way to make it more accessible)
public static arrayTooltip = [];
2. Initialized it and filled it in with the proper values of the labels
3. I hid the labels I wanted to hide (no worries... data are safe due to step 2)
And then, in the chart options, I made something like this:
tooltips: {
enabled: true,
displayColors: false,
xPadding: 15,
yPadding: 15,
callbacks: {
title: function(tooltipItem, data) {
return "";
},
label: function(tooltipItem, data) {
return MyClassComponent.arrayTooltip[tooltipItem.index]
}
}
},
In the code above, keep your focus in the callbacks. I didn't want a title, so I made it blank. About the label, I took the index of the current tooltip (the one that is on the aim of the mouse) and used it in the static array. And it is done.

Here is a Plunker. For demo purpose. the message is this is a tooltip
Note: You'll have to create tool tip for complete array object data that is used by the charts to plot.
I hope it solves your issue.

Related

What is the purpose of the fontSize theme setting when all typography variants are `rem`?

The documentation for Material UI Typography settings (https://mui.com/material-ui/customization/typography/) are confusing and don't really make sense:
MUI uses rem units for the font size. The browser element
default font size is 16px, but browsers have an option to change this
value, so rem units allow us to accommodate the user's settings,
resulting in a better accessibility support. Users change font size
settings for all kinds of reasons, from poor eyesight to choosing
optimum settings for devices that can be vastly different in size and
viewing distance.
To change the font-size of MUI you can provide a fontSize property.
The default value is 14px.
So which is it? It uses REM or use the font-size as a base for the typography variants? If you look at the default theme (https://mui.com/material-ui/customization/default-theme/?expand-path=$.typography) all the variants are supposed to use rem -- and the documents even talk about customizing this value in htmlFontSize.
So I'm really struggling to understand the use/purpose for the fontSize theme setting?
The typography.fontSize and typography.htmlFontSize values are expected to be in pixels but those pixel values do not get put directly onto the page. Instead, the material ui code will take the pixel value and calculate a corresponding rem value. That rem value is what gets put into the css class, and thus rendered by the browser. By using rems as the final value, the user's browser settings can change the final size
The equation they use for this calculation is show in the article:
computed = specification * (typography.fontSize / 14) * (html fontsize / typography.htmlFontSize)
Example 1: everything is set to its defaults, with typography.fontSize set to 14, typography.htmlFontSize set to 16, and typography.h3.fontSize set to "1.2rem". You then render a <Typography variant="h3">. In that case, the calculation would be:
computed = 1.2rem * (14 / 14) * (16 / 16)
This works out to having a size of 1.2rem, and that's what will be put into the css class.
Example 2: You now changed typography.fontSize to 12, typography.htmlFontSize to 18, and typography.h3.fontSize to "1.5rem". With those values, the calculation will be:
computed = 1.5rem * (12 / 14) * (16 / 18)
This works out to about 1.143 rem.
In practice, if you want to change all your fonts to be bigger or smaller, change typography.fontSize; each variant will scale based on that. If you want to change a specific variant to be bigger while leaving the rest the same, then modify typography.[variant].fontSize. I don't see a reason you would want to change typography.htmlFontSize, but that knob is available if you need it.
Code from node_modules#mui\material\styles\createTypography.js
const coef = fontSize / 14;
const pxToRem = pxToRem2 || (size => `${size / htmlFontSize * coef}rem`);
const buildVariant = (fontWeight, size, lineHeight, letterSpacing, casing) => _extends({
fontFamily,
fontWeight,
fontSize: pxToRem(size),
// Unitless following https://meyerweb.com/eric/thoughts/2006/02/08/unitless-line-heights/
lineHeight
}, fontFamily === defaultFontFamily ? {
letterSpacing: `${round(letterSpacing / size)}em`
} : {}, casing, allVariants);
const variants = {
h1: buildVariant(fontWeightLight, 96, 1.167, -1.5),
h2: buildVariant(fontWeightLight, 60, 1.2, -0.5),
h3: buildVariant(fontWeightRegular, 48, 1.167, 0),
h4: buildVariant(fontWeightRegular, 34, 1.235, 0.25),
h5: buildVariant(fontWeightRegular, 24, 1.334, 0),
h6: buildVariant(fontWeightMedium, 20, 1.6, 0.15),
subtitle1: buildVariant(fontWeightRegular, 16, 1.75, 0.15),
subtitle2: buildVariant(fontWeightMedium, 14, 1.57, 0.1),
body1: buildVariant(fontWeightRegular, 16, 1.5, 0.15),
body2: buildVariant(fontWeightRegular, 14, 1.43, 0.15),
button: buildVariant(fontWeightMedium, 14, 1.75, 0.4, caseAllCaps),
caption: buildVariant(fontWeightRegular, 12, 1.66, 0.4),
overline: buildVariant(fontWeightRegular, 12, 2.66, 1, caseAllCaps)
};
Px to rem mui function
this is what I found in the MUI module.
So, from the function buildVariant I can say it expected font sizes in pixels and converts them into rem values per variant.
to achieve your desired font size per variant you can add values in px using a custom function without changing htmlFontSize(16px).
const calcFontSize = (expectedBodyFontSize)=>{
return (14/16)*expectedBodyFontSize
}
Whatever it returns you just have to assign it as font-size to create theme variable like below
const theme = createTheme({
typography: {
fontSize: calcFontSize(14),
},
});
If you will check the body tag font size it will be of 14px.

Why does 'offset' exist in React Native Panresponder?

TL;DR: I am using the Panresponder code from the React Native docs, and need help understanding why the 'offset' value is used, as opposed to just using the animated value.
Full Question:
The Scenario:
I am using a Panresponder in React Native to drag and drop objects around the screen. I am using standard code from the RN docs.
Basically, the draggable object has an animated position value. When you click the object, the offset on that animated value is set to the animated value, and the animated value is set to zero. As you drag, the animated value is incrementally set to the magnitude of how far it has been dragged in that gesture. When you release the object, the offset is added to the animated value, and the offset is then set to zero.
Example:
For example, if the object starts from position 0, then initially both the animated value and the offset are set to 0. If you drag the object by 100px, the animated value gradually increases from 0 to 100 as you drag. When you release, the zero offset is added to the animated value (so nothing happens). If you click the object again, the offset is set to 100, and the animated value is re-set to 0. If you drag the object another 50px, the animated value increases from 0 to 50. When you release the object, the 100 offset is added to the animated value, which becomes 150, and the offset is re-set to zero.
In this way, the animated value always holds the distance dragged in the current gesture, with the offset saving the position that the object was at before the current drag gesture started, and when you release the object, that saved offset value is tacked onto the animated value, so that when the object is at rest, the animated value contains the total distance that the object has been dragged by all gestures combined.
Code:
Here's the code I'm using to do this:
this.animatedValue.addListener((value) => this._value = value); // Make this._value hold the value of this.animatedValue (essentially extract the x and y values from the more complex animatedValue)
this.panResponder = PanResponder.create({
onPanResponderGrant: () => { // When user clicks to initiate drag
this.animatedValue.setOffset({ // Save 'distance dragged so far' in offset
x: this._value.x,
y: this._value.y,
})
this.animatedValue.setValue({ x: 0, y: 0}) // Set this.animatedValue to (0, 0) so that it will hold only 'distance so far in current gesture'
},
onPanResponderMove: Animated.event([ // As object is dragged, continually update animatedValue
null, { dx: this.animatedValue.x, dy: this.animatedValue.y}
]),
onPanResponderRelease: (e, gestureState) => { // On release, add offset to animatedValue and re-set offset to zero.
this.animatedValue.flattenOffset();
}
}
My Question:
This code works perfectly well. When I don't understand though, is why do we need the offset? Why do we need to re-set the animated value to zero on every new gesture, save its value in offset, and re-add that to the animated value after it's finished being dragged? When the object is released, it ends up just holding the total distance dragged, so why not just use the animated value and not use the offset? With my example above, why not just increment animated value to 100 when you drag it 100px, then when you click and drag it again, keep updating the animated value?
Possible Solution:
The only advantage I can think of to using the offset is that animatedValue will now allow you to keep track of the 'distance so far in current gesture', as opposed to just 'total distance so far over all gestures combined'. There might be a scenario in which you need the 'distance so far in current gesture' value, so I'm wondering if this is the only reason to ever use the offset, or is there a more fundamental reason I'm missing why we should use it all the time?
Any insight would be great.
Thanks!
Actually the logic isn't right in the example you used because it's just a partial example using flattenOffset that isn't meant to be used for standard drag/drop behaviour (see the bottom paragraph: https://animationbook.codedaily.io/flatten-offset/):
Because we reset our offset and our animated value in the onPanResponderGrant, the call to flattenOffset is unnecessary, here. However, in the case where we want to trigger an animation from the released location to another location, flattenOffset is required.
The whole point of the offset is that you don't need to keep track of the absolute position value in a separate variable. So you were right to doubt the need for the offset given that you where storing the absolute position in this._value.
At the beginning of a drag, the x/y values of the Animated.Value start from [0, 0], so the drag is relative to the starting position:
offset + [0, 0] = absolute position at the beginning of a drag
offset + [x, y] = absolute position at the end of the drag
For the next drag to start at the right position, you just need to add [x, y] to the offset, which is done by extractOffset():
this.panResponder = PanResponder.create({
// Allow dragging
onStartShouldSetPanResponder: (e, gesture) => true,
// Update position on move
onPanResponderMove: (e, gestureState)=> {
Animated.event([
null,
{dx: this.animatedValue.x, dy: this.animatedValue.y},
])(e, gestureState)
},
// Update offset once we're done moving
onPanResponderRelease: (e, gestureState)=> {
this.animatedValue.extractOffset();
}
});
Thanks to the offset, you don't need this._value anymore to get the proper drag behaviour.
Because it's better to have the entire animated value's state be self-contained, so you can pass its value to a transform. Of course maybe you don't want the "total distance travelled" in which case, well, don't use offsets, but if you do, using AnimatedValue's offset is the best solution.
Let me show you why by coding up an example of tracking the total distance travelled between touches without using the built-in offset:
this.offsetValue = {x: 0, y:0};
this.panResponder = PanResponder.create({
onPanResponderGrant: () => { // When user clicks to initiate drag
this.animatedValue.setValue({ x: 0, y: 0}) // Set this.animatedValue to (0, 0) so that it will hold only 'distance so far in current gesture'
},
onPanResponderMove: Animated.event([ // As object is dragged, continually update animatedValue
null, { dx: this.animatedValue.x, dy: this.animatedValue.y}
]),
onPanResponderRelease: (e, gestureState) => {
// Set the offset to the current position
this.offsetValue = {x: gestureState.dx, y: gestureState.dy}
// Reset our animatedvalue since the offset is now all good
this.animatedValue.setValue({ x: 0, y: 0})
}
}
This works, and it's less code you now have the raw value for the current touch in Animated.Value and if you want the total distance moved you can use this.offsetValue. Except... how do you apply it to get the total distance exactly? You might think you can do this:
<Animated.View
style={{
transform: [
{ translateX: this.offset.x + this.animatedValue.x },
{ translateY: this.offset.y + this.animatedValue.y },
],
}}
{...this.panResponder.panHandlers}
/>
But this will be an error because animatedValue.x isn't a number obviously. You could use ._value directly but then what's the point of using Animated? The entire idea is that you can pass a single Animated object to a transform property. So that's why you simply use the object's internal offset.

Highcharts conditionally disable marker on hover

New to highcharts - I've got a chart that I have markers disabled on series
plotOptions: {
series: {
marker: {
enabled: false
}
}
},
which is great for the drawing of the lines, but, when I hover over the chart the markers are there. this is good, however, I do not want the marker to be present if the y value on the xAxis is 0 (empty) but I do want them when the y value on the xAxis is greater than one.
I was able to do this before by just setting the y value to null, disabling hover for that series, but the nulls present on this series - drawn as a stacked areaspline graph causes the spline to get drawn improperly (no spline, jagged edges when using the connectNulls: true option in series.
So how do I, and/or can I, conditionally disable a marker on hover based on the y value on an xAxis?
I have been looking at wrapping highcharts prototypes, which I am already doing to control some crosshair behavior drawCrosshair(): https://www.highcharts.com/docs/extending-highcharts/extending-highcharts but I can't seem to find anything that controls the drawing of the markers at that level
A very static approach to this is simply addressing each point with Y-value of 0. Then you could disable the hover-marker for each of these. One such point would look like this (JSFiddle demo):
{
y:0,
marker:{
states:{
hover:{
enabled:false
}
}
}
}
And used in a series:
series: [{
marker: {
enabled: false
},
data: [3, {y:0, marker:{states:{hover:{enabled:false}}}}, 3, 5, 1, 2, 12]
}]
As you can see, it's not pretty, but might help those who need an ad-hoc solution.
A dynamic approach to this is intercepting the setState-function of the Point.
For example, wrapping it and preventing the handling if the y-value is 0:
(function (H) {
H.wrap(H.Point.prototype, 'setState', function (proceed, state, move) {
if(this.y === 0) return;
proceed.apply(this, Array.prototype.slice.call(arguments, 1));
});
}(Highcharts));
See this JSFiddle demonstration of it in action.

ExtJs dynamically set axes maximum value

I would like to dynamically change the axis maximum limit of an ExtJS 4.x chart.
I tried using a listener beforerefresh but this has no effect:
var chart = Ext.create('Ext.chart.Chart', {
...
listeners: {
beforerefresh(me, eOpts)
{
// set y-axes to max(raw_value)
console.log('before refresh');
// set maximum axis value of Y axis to 0.1
me.axes.getAt(1).maximum = 0.1;
}
},
The code is reached, but using me.axes does not seem to have any effect at all.
What is the correct way to do it?
Thanks

ext js 4 column chart bug? series remain visible when I hide them

Feeling I had not enough control over the chart if I had used a grouped column chart, I made my own version by just adding different series to the chart. After all the store, the number of series, their colors and such all need to be set dynamically and not hard coded. So basically this is what I have:
chart = Ext.create("Ext.chart.Chart", {
store: dataStore,
axes: dynamicAxes,
series: series
});
I leave out the not interesting stuff such as width, height of the chart etc.
now I have a method whichs returns a series object. This is added to the series array mentioned in the code above. The function has a "item" object parameter and also an idx param which is the index of the item object from the array it comes from, and a max param which is the size of the item array
the function returns something like this:
var w = (typeof (max) !== "undefined" && max !== null) ? this._getWidthByMax(max) : 30;
return {
type: "column",
axis = "left",
xField = "timestamp",
yField = item.id, // store field name equals the id of the item object
style = { stroke: colorCode, "stroke-width": (item.isDefault) ? 2 : 1, fill: colorCode },
width = w,
renderer = function (sprite, rec, attr, bix) {
var nx = idx * w;
return Ext.apply(attr, { translation: { x: nx} });
}
}
now this works fine for the number of columns I want to have. That can be one, two, three... up to seven currently.
However, if I want to hide a series, the following call doesn't work:
chart.series.getAt(idx).hideAll();
while it does work if I render my chart as a line chart.
is this a bug in Ext-js 4 or is it because of how I rendered the series for my column chart?
since nobody has replied to my question and I have found a solution in the meantime, I might as well answer my own question...
the problem occurred in Ext Js 4.0.7.
With version 4.1 RC 2 the hideAll behaved correctly.
So the solution, for anyone who would have the same problem, is to upgrade to 4.1 RC 2 or newer.

Resources