Chart.js: Limit maximal Zoom In - chart.js

I'm using chart.js to display a chart with x-values from 1e3 up to 1e11. I already figured out that I can limit the Zoom Out by setting limits.x.min = 1e3 and limits.x.max = 1e11.
But I failed at limiting the Zoom In. By looking at the documenation, I thought minRange would be the way to go, but it didn't seem to work like this:
plugins: {
zoom: {
zoom: {
wheel: {
enabled: true,
speed: 0.1
},
mode: "x"
},
limits: {
x: {
min: 9.0e2, // I know I could achieve almost the same with "default"
max: 1.1e11,
minRange: 1e3
}
}
}
}
I thought that minRange specifies the range of the minimal displayed x-value to the maximal displayed x-value, therefore I've set it to 1e3 and expected e.g. the Zoom In to stop at 4e3 and 5e3.
Did I misunderstand the concept of minRange?
Versions are: chart.js: 4.1.1, chartjs-plugin-zoom: 2.0.0

Related

chart.js time x-axis - start ticks at specific time

In chart.js 3 I have a 'time' x-axis. I'm using a callback in ticks to show hours since a specific time like so:
ticks: {
display: true,
callback: function (value, index, ticks) {
var tickMS = ticks[index].value;
var diffMS = tickMS - _this.startMS;
var diffHour = diffMS / 1000 / 60 / 60;
return diffHour;
}
}
This works fine, however the ticks displayed are fractions of an hour like this:
How can I set the ticks to start at _this.startMS so that the ticks displayed are on the hour (since _this.startMS) showing 8, 16 etc. rather than 7.8889...?
you can add a min property like so:
scales: {
x: {
min: _this.startMS
}
}
Answering my own question - for my purposes, setting min: didn't work, so instead I've disabled the scale and used the annotations plugin to draw my own

How to align ticks label in chartJS 3.x?

is possible with chartJS chenge the align position of tick labels for scaleY and for scaleX?
In the image attached i want 300,150,60, 0 on top of the grid line and 9:56 aligned on the right of the first tick.
I try to modify the code in this answer, but without success.
After some research i adapted the method showned here to works with ChartJS 3.x:
plugins: [{
afterDraw: function (c) {
var yScale = c.scales['y'];
yScale.ticks.forEach(function (o, i) {
// top tick label indent
var yT_O = yScale.getPixelForTick(i) - 9;
// left tick label indent
var xT_O = c.width - 35;
c.ctx.fillStyle = "#707070"
c.ctx.fillText(o.label, xT_O, yT_O);
});
}
}]
Actually i'm not sure this is the best plugin event where do that.
I also had to put "display: false" on the ticks options in order to hide the originals tick labels.

QT Rotating object problems

I am currently working on a project that has a spinning object. The object's rotation speed is changed and its direction of rotation is changed (forwards/backward). After doing some research I came across RotationAnimator on the QT documentation, it seems to have everything I need and I can easily reverse direction. The only problem is that is in QML. My program is entirely in Qt's C++.
I tried an attempt at using QVariantAnimation (documentation here)but every way I have tried there is always some problem. Changing speed is relatively easy, just fooAnimation->setLoop(-1) (loop indefinitely) and change the duration of the animation. The problem I run into is looping forwards AND backward. Or in other terms, changing direction. I have attempted to change the direction of the animation using fooAnimation->setDirection(<Direction goes here>), but if I run the animation in reverse long enough it will stop the animation entirely. It will only loop forwards. That's the key part of the problem.
So I tried alternative ways to change the direction. One idea was changing the endValue from 360 degrees to -360 using fooAnimation->setEndValue(<value goes here>) when I want to reverse, but this has an adverse effect. If I were to swap the direction, the current rotation: let's say it's 110, will be inverted. So now it suddenly jumps to -110 and starts from there. This leads to very jittery rotation as every time a direction swap happens it teleports the rotation of the object.
This seems like a pretty simple thing to implement, rotating an object that can have its rotation speed and direction changed, but I just can't wrap my head around how I could implement it with animations.
A side note here is that all this is being done in Qt 3D so I can grab rotation and other properties from the rotating object (or in this case its QTransform)
There are multiple ways to achieve this effect. On this Qt/QML demo, I used an approach based on NumberAnimation to rotate the cube.
These are the key ideas behind it:
The cube has a Transform component that defines the 4x4 matrix that Qt3D uses to place the cube in the world using predefined rotation and scale values. The NumberAnimation simply changes the rotation angle used on this transform to make the cube rotate on the screen;
The rotation speed is defined by the duration property of that NumberAnimation: a smaller value makes the cube rotate faster. The cube takes 5 seconds to rotate from 0 to 360 degrees;
Changing the rotation speed means recalculating the duration of the animation based on how far away from 0 degrees the current rotation angle is. The animation needs to be restarted whenever we change ANY NumberAnimation properties;
When the direction of the rotation is changed, we store the current rotation angle in cubeTransform.startAngle and update the to and from properties of NumberAnimation along with duration so the cube can be rotated in a new direction starting from the current angle.
I added a few buttons to the UI to change the direction and speed in runtime:
main.qml:
import QtQuick 2.12 as QQ2
import QtQuick.Controls 2.12
import QtQuick.Scene3D 2.12
import Qt3D.Core 2.0
import Qt3D.Render 2.0
import Qt3D.Input 2.0
import Qt3D.Extras 2.0
import Qt3D.Logic 2.0
QQ2.Item {
id: windowId
width: 800
height: 800
property real cubeScale: 100.0
property int rotationSpeed: 5000
property real speedFactor: 1.0
property bool clockwiseRotation: true
Scene3D {
id: scene3D
anchors.fill: parent
aspects: ["input", "logic"]
focus: true
cameraAspectRatioMode: Scene3D.AutomaticAspectRatio
Entity {
id: rootEntity
Camera {
id: camera
projectionType: CameraLens.PerspectiveProjection
fieldOfView: 45
nearPlane : 0.1
farPlane : 100000.0
position: Qt.vector3d(0.0, 0.0, 500.0)
viewCenter: Qt.vector3d(0.0, 0.0, 0.0)
upVector: Qt.vector3d( 0.0, 1.0, 0.0 )
}
components: [
RenderSettings {
activeFrameGraph: ForwardRenderer {
camera: camera
clearColor: "silver"
}
},
InputSettings { }
]
Entity {
id: lightEntity
enabled: true
DirectionalLight {
id: infiniteLight
color: "white"
intensity: 1.0
worldDirection: Qt.vector3d(0, 0, 0)
}
Transform {
id: infiniteLightTransform
translation: Qt.vector3d(0.0, 0.0, 500.0)
}
components: [ infiniteLight, infiniteLightTransform ]
}
Entity {
id: cubeEntity
CuboidMesh {
id: cubeMesh
}
PhongMaterial {
id: cubeMaterial
ambient: "red"
diffuse: Qt.rgba(1.0, 1.0, 1.0, 1.0)
}
Transform {
id: cubeTransform
property real angle: 0.0
property real startAngle: 0.0
matrix: {
var m = Qt.matrix4x4();
m.rotate(cubeTransform.angle, Qt.vector3d(0, 1, 0));
m.translate(Qt.vector3d(0, 0, 0));
m.scale(windowId.cubeScale, windowId.cubeScale, windowId.cubeScale);
return m;
}
}
components: [ cubeMesh, cubeMaterial, cubeTransform ]
}
QQ2.NumberAnimation {
id: cubeAnimation
property int startPos: {
if (cubeTransform.startAngle === 0)
{
if (windowId.clockwiseRotation === true)
return 0;
return 360;
}
return cubeTransform.startAngle;
}
property int endPos: (windowId.clockwiseRotation === true) ? 360 : 0
target: cubeTransform
property: "angle"
duration: windowId.rotationSpeed / windowId.speedFactor
from: cubeAnimation.startPos
to: cubeAnimation.endPos
loops: 1
running: true
onStarted: {
console.log("onStarted");
}
onFinished: {
console.log("onFinished");
// reset the starting angle
cubeTransform.startAngle = 0;
// reset the duration of the animation
cubeAnimation.duration = windowId.rotationSpeed / windowId.speedFactor;
// the animation is currently stopped, run it again
cubeAnimation.running = true;
}
}
}
}
QQ2.Row {
anchors.fill: parent
Button {
text: "Change Direction"
onClicked: {
// pause the animation
cubeAnimation.pause();
// invert rotation
windowId.clockwiseRotation = !windowId.clockwiseRotation;
// store current angle as the starting angle for the rotation
cubeTransform.startAngle = cubeTransform.angle;
// update the remaining time to avoid changing the rotation speed
let progressPercentage = cubeTransform.startAngle / 360.0;
if (windowId.clockwiseRotation)
progressPercentage = 1.0 - progressPercentage;
cubeAnimation.duration = (windowId.rotationSpeed / windowId.speedFactor) * progressPercentage;
// restart the animation from this angle using a new direction
cubeAnimation.restart();
}
}
Button {
text: "1x"
highlighted: (windowId.speedFactor === 1.0)
onClicked: changeSpeed(1.0)
}
Button {
text: "2x"
highlighted: (windowId.speedFactor === 2.0)
onClicked: changeSpeed(2.0)
}
}
function changeSpeed(newSpeed) {
windowId.speedFactor = newSpeed;
// pause the animation
cubeAnimation.pause();
// store current angle as the starting angle for the rotation
cubeTransform.startAngle = cubeTransform.angle;
// update the remaining time to avoid changing the rotation speed
let progressPercentage = cubeTransform.startAngle / 360.0;
if (windowId.clockwiseRotation)
progressPercentage = 1.0 - progressPercentage;
cubeAnimation.duration = (windowId.rotationSpeed / windowId.speedFactor) * progressPercentage;
// restart the animation from this angle using a new direction
cubeAnimation.restart();
}
}

How to move a rectangle component in qml

How do I move a rectangle component in Qml using c++ program, it has to progress from minimum to maximum value like a progress bar with color gradient. i have tried to use number animation and it is working fine,but how do i change the color as it progresses.
It's hard to give a specific answer as you've not provided much detail or a code sample. However it seems like you want to set the color property of your progress bar rectangle to be dependent on it's width or position, so that it changes depending on how much 'progress' has been made.
In addition, you may be able to use a ColorAnimation class to animate this, along with a Behaviour, for example:
Rectangle {
id: progressBar
width: 0
height: 20
color: (width < 30) ? "red" : (width < 60) ? "yellow" : "green"
Behavior {
ColorAnimation { target: progressBar; duration: 500 }
}
}

Chart.JS spacing and padding

Is it possible to get some more space between the chart and the x-axis?
Is it possible to get some more space between the right side of the chart and the end of the canvas area? I want to add some more elements to the canvas right beside the chart but this is not possible because the chart takes the whole canvas width so it would overlap.
Shifting x axis Labels Vertically
The easiest way to do 1. is by adding spaces to your x labels. You can extend your chart type and override your initialize function to do this (increase 30 to something larger if your labels are long to start with anyway)
initialize: function(data){
data.labels.forEach(function(item, index) {
data.labels[index] += Array(Math.max(30 - item.length, 0)).join(" ");
})
Chart.types.Bar.prototype.initialize.apply(this, arguments);
},
Edit : As pointed out in the comments, this causes a horizontal shift as well and the label ends no longer align with the x axis markers.
Since both the x axis and the x labels are drawn in a single function and you have no other variables you can mess around with (safely) this means you'll have to change the actual scale draw function.
Look for a ctx.translate towards the end of the draw function and change it to
ctx.translate(xPos, (isRotated) ? this.endPoint + 22 : this.endPoint + 18);
You'll also have to adjust the endpoint (which drives the y limits) a bit so that the additional y offset doesn't cause the labels to overflow the chart (look for the line adjusting this in the draw override for 2.).
Leaving a gap on the Right Side
To do 2, you override your draw function (in your extended chart) and change xScalePaddingRight. However since this doesn't affect your horizontal grid lines you have to overlay a filled rectangle once your draw is complete. Your complete draw function would look like this
draw: function(){
// this line is for 1.
if (!this.scale.done) {
this.scale.endPoint -= 20
// we should do this only once
this.scale.done = true;
}
var xScalePaddingRight = 120
this.scale.xScalePaddingRight = xScalePaddingRight
Chart.types.Bar.prototype.draw.apply(this, arguments);
this.chart.ctx.fillStyle="#FFF";
this.chart.ctx.fillRect(this.chart.canvas.width - xScalePaddingRight, 0, xScalePaddingRight, this.chart.canvas.height);
}
Original fiddle - https://jsfiddle.net/gvdmxc5t/
Fiddle with modified Scale draw function - https://jsfiddle.net/xgc6a77a/ (I turned off animation in this one so that the endpoint is shifted only once, but you could just hard code it, or add some extra code so that it's done only once)
The 'tickMarkLength' option extends the grid lines outside the chart and pushes the ticks down.
xAxes: [
{
gridLines: {
tickMarkLength: 15
},
}
]
use this for chartjs 2.0
scales: {
xAxes: [{
barPercentage: 0.9,
categoryPercentage: 0.55
}]
Reference
In chartjs v3, there is an "offset" flag that you can set to true. This will create padding.
scales: {
x: {
offset: true,
}
}
If true, extra space is added to the both edges and the axis is scaled to fit into the chart area. This is set to true for a bar chart by default.
Documentation