Control Scaling of Canvas When dragged out of chart limit - javascript

When I dragged the datapoint out of chart's ticks limit like from max x and y axis value, the canvas increase the limits too fast. How can I control this scaling speed? So that it increase with specific number defined in the config of chart.
Here is the js fiddle link.
https://jsfiddle.net/rz7pw6j0/67/
JS
(function () {
let chartInstance;
let chartElement;
function createChart(chartElement) {
const chartConfig = {
type: 'scatter',
data: {
datasets: [
{
data: [
{
x: 0,
y:0
}
],
fill: true,
showLine: true,
lineTension: 0
}
]
},
options: {
legend: {
display: false,
},
layout: {
padding: {
left: 50,
right: 50,
top: 0,
bottom: 0
}
},
title: {
display: false,
text: 'Chart.js Interactive Points',
},
scales: {
xAxes: [
{
type: 'linear',
display: true,
padding: 20,
paddingLeft: 10,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 10,
scaleLabel: {
display: true,
labelString: 'Time',
},
ticks: {
suggestedMin: -5,
suggestedMax: 5,
stepValue: 1,
}
}
],
yAxes: [
{
type: 'linear',
display: true,
scaleLabel: {
display: true,
labelString: 'Weight'
},
paddingLeft: 10,
paddingRight: 10,
paddingTop: 10,
paddingBottom: 10,
ticks: {
suggestedMin: 0,
suggestedMax: 3,
stepValue: 1
},
}
]
},
responsive: true,
maintainAspectRatio: true,
tooltips: {
intersect: true,
}
}
};
chartInstance = new Chart(chartElement.getContext('2d'), chartConfig);
let element = null;
let scaleY = null;
let scaleX = null;
let datasetIndex = null;
let index = null;
let valueY = null;
let valueX = null;
function onGetElement() {
const event = d3.event.sourceEvent;
element = chartInstance.getElementAtEvent(event)[0];
if (!element) {
chartClickHandler(event);
return;
}
if (event.shiftKey) {
const tempDatasetIndex = element['_datasetIndex'];
const tempIndex = element['_index'];
chartInstance.data.datasets[tempDatasetIndex].data = chartInstance
.data.datasets[tempDatasetIndex].data.filter((v, i) => i !== tempIndex);
chartInstance.update();
return;
}
scaleY = element['_yScale'].id;
scaleX = element['_xScale'].id;
}
function onDragStart() {
const event = d3.event.sourceEvent;
datasetIndex = element['_datasetIndex'];
index = element['_index'];
valueY = chartInstance.scales[scaleY].getValueForPixel(event.offsetY);
valueX = chartInstance.scales[scaleX].getValueForPixel(event.offsetX);
chartInstance.data.datasets[datasetIndex].data[index] = {
x: valueX,
y: valueY
};
chartInstance.update(0);
}
function onDragEnd() {
if (
chartInstance.data.datasets[datasetIndex] &&
chartInstance.data.datasets[datasetIndex].data) {
chartInstance.data.datasets[datasetIndex].data.sort((a, b) => a.x - b.x > 0 ? 1 : -1);
chartInstance.update(0);
}
element = null;
scaleY = null;
scaleX = null;
datasetIndex = null;
index = null;
valueY = null;
valueX = null;
}
d3.select(chartInstance.chart.canvas).call(
d3.drag().container(chartInstance.chart.canvas)
.on('start', onGetElement)
.on('drag', onDragStart)
.on('end', onDragEnd)
);
}
function chartClickHandler (event) {
let scaleRef;
let valueX = 0;
let valueY = 0;
Object.keys(chartInstance.scales).forEach((scaleKey) => {
scaleRef = chartInstance.scales[scaleKey];
if (scaleRef.isHorizontal() && scaleKey === 'x-axis-1') {
valueX = scaleRef.getValueForPixel(event.offsetX);
} else if (scaleKey === 'y-axis-1') {
valueY = scaleRef.getValueForPixel(event.offsetY);
}
});
const newPoint = {
x: valueX,
y: valueY
};
const dataSeries = chartInstance.data.datasets[0].data;
for (let i = 0; i < dataSeries.length; i++) {
if (dataSeries[i].x === newPoint.x) {
dataSeries.y = newPoint.y;
chartInstance.update();
return;
}
}
let inserted = false;
for (let j = dataSeries.length - 1; j >= 0; j--) {
if (dataSeries[j].x > newPoint.x) {
dataSeries[j + 1] = dataSeries[j];
} else {
dataSeries[j + 1] = newPoint;
inserted = true;
break;
}
}
if (!inserted) {
dataSeries.push(newPoint);
}
chartInstance.update();
}
chartElement = document.getElementById("chart");
createChart(chartElement);
})();
HTML
<body>
<div>Chart Drag and Click Test</div>
<div class="wrapper">
<canvas id="chart"></canvas>
</div>
</body>
CSS
.wrapper {
display: "block";
}
I want to control the scaling speed.

If a dragStart event occurs beyond the scale limits, the increment should be a fixed value to avoid the issue you mentioned. Also, ticks.min and ticks.max should be set for the same purpose. Below is a sample jsfiddle and code (you can control speed by step).
https://jsfiddle.net/oLrk3fb2/
function onDragStart() {
const event = d3.event.sourceEvent;
const scales = chartInstance.scales;
const scaleInstanceY = scales[scaleY];
const scaleInstanceX = scales[scaleX];
const scalesOpts = chartInstance.options.scales;
const ticksOptsX = scalesOpts.xAxes[0].ticks;
const ticksOptsY = scalesOpts.yAxes[0].ticks;
const step = 1;
datasetIndex = element['_datasetIndex'];
index = element['_index'];
valueY = scaleInstanceY.getValueForPixel(event.offsetY);
valueX = scaleInstanceX.getValueForPixel(event.offsetX);
if (valueY < scaleInstanceY.min) {
ticksOptsY.min = valueY = scaleInstanceY.min - step;
}
if (valueY > scaleInstanceY.max) {
ticksOptsY.max = valueY = scaleInstanceY.max + step;
}
if (valueX < scaleInstanceX.min) {
ticksOptsX.min = valueX = scaleInstanceX.min - step;
}
if (valueX > scaleInstanceX.max) {
ticksOptsX.max = valueX = scaleInstanceX.max + step;
}
chartInstance.data.datasets[datasetIndex].data[index] = {
x: valueX,
y: valueY
}
chartInstance.update(0);
}

Related

Render images as labels on Y axis

I'm using chart.js to graph my data.
I'm wondering if I can show the flag image/icon PNG for the labels.
function drawChart(obj, columnName, type = 'bar', selectedVal){
var ajax = $.ajax({url: '/query/' + obj + '/'+ columnName + '/' + selectedVal });
ajax.done(function (response) {
console.log('Response from API >>',response);
$('.lds-ripple').fadeOut();
var selector = `chart-${columnName}`;
var chartHtml = `<div class="col-sm-6"><canvas class="chart" id="${selector}" height="200"></canvas></div>`;
$('.charts').append(chartHtml);
keys = [];
values = [];
var length = 0
$.each(response, function(key,val) {
//console.log(key+val);
length++;
if(length<15){
keys.push(key);
values.push(val);
}
});
var showLegend = false;
if(type == 'doughnut' || type == 'radar' || type == 'polarArea' || type == 'pie'){
showLegend = true;
}
let chart = document.getElementById(selector).getContext('2d');
let xAxisTitle = columnName;
var sumOfAllValues = values.reduce((a, b) => a + b, 0);
Chart.defaults.global.defaultFontColor = "#fff";
var ticksDisplay = true;
if(columnName == 'country'){
ticksDisplay = false;
}
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png'];
new Chart(chart, {
type: type, // bar, horizontalBar, pie, line, doughnut, radar, polarArea
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
xAxis.ticks.forEach((value, index) => {
var x = xAxis.getPixelForTick(index);
var y = yAxis.getPixelForTick(index);
var image = new Image();
image.src = images[index],
ctx.drawImage(image, x - 12, yAxis.bottom + 10);
// ctx.drawImage(image, x + 12, yAxis.left - 10);
});
}
}],
data:{
labels: keys,
datasets:[{
label:'Count',
data:values,
borderWidth:2,
hoverBorderWidth:2,
hoverBorderColor:'#fff',
color:'#fff',
backgroundColor: neonBgColors,
borderColor: neonBgBorders,
defaultFontColor: 'white'
}
]
},
options:{
title:{
display:true,
fontSize: 20,
text: columnName + '(' + sumOfAllValues + ')'
},
legend:{
display:showLegend,
position:'right',
labels:{
fontColor:'#000'
}
},
scales: {
xAxes: [{
ticks: {
precision:0,
beginAtZero: true
},
scaleLabel: {
display: true,
labelString: xAxisTitle + ' (' + sumOfAllValues + ')'
}
}],
yAxes: [{
ticks: {
precision:0,
beginAtZero: true,
display: ticksDisplay,
},
scaleLabel: {
display: true,
// labelString: 'Visitor Count'
}
}]
},
layout: {
padding: {
bottom: 30
}
},
}
});
});
}
I kept getting
I adapted the code of this answer and came up with the following solution for your case.
const labels = ['Red Vans', 'Blue Vans', 'Green Vans', 'Gray Vans'];
const images = ['https://i.stack.imgur.com/2RAv2.png', 'https://i.stack.imgur.com/Tq5DA.png', 'https://i.stack.imgur.com/3KRtW.png', 'https://i.stack.imgur.com/iLyVi.png']
.map(png => {
const image = new Image();
image.src = png;
return image;
});
const values = [48, 56, 33, 44];
new Chart(document.getElementById("myChart"), {
type: "horizontalBar",
plugins: [{
afterDraw: chart => {
var ctx = chart.chart.ctx;
var xAxis = chart.scales['x-axis-0'];
var yAxis = chart.scales['y-axis-0'];
yAxis.ticks.forEach((value, index) => {
var y = yAxis.getPixelForTick(index);
ctx.drawImage(images[index], xAxis.left - 40, y - 10);
});
}
}],
data: {
labels: labels,
datasets: [{
label: 'My Dataset',
data: values,
backgroundColor: ['red', 'blue', 'green', 'lightgray']
}]
},
options: {
responsive: true,
layout: {
padding: {
left: 50
}
},
legend: {
display: false
},
scales: {
yAxes: [{
ticks: {
display: false
}
}],
xAxes: [{
ticks: {
beginAtZero: true
}
}],
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.3/Chart.min.js"></script>
<canvas id="myChart" height="90"></canvas>

How do you apply Smart Routing on links with ports on JointJS?

I am trying to apply smart routing of links with the use of ports using JointJS. This documentation shows the one I am trying to achieve. The example on the docs though shows only the programmatic way of adding Link from point A to point B. How do you do this with the use of ports?
Here's my code: JSFiddle.
HTML:
<html>
<body>
<button id="btnAdd">Add Table</button>
<div id="dbLookupCanvas"></div>
</body>
</html>
JS
$(document).ready(function() {
$('#btnAdd').on('click', function() {
AddTable();
});
InitializeCanvas();
// Adding of two sample tables on first load
AddTable(50, 50);
AddTable(250, 50);
});
var graph;
var paper
var selectedElement;
var namespace;
function InitializeCanvas() {
let canvasContainer = $('#dbLookupCanvas').parent();
namespace = joint.shapes;
graph = new joint.dia.Graph({}, {
cellNamespace: namespace
});
paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
return (magnetS !== magnetT);
},
snapLinks: {
radius: 20
}
});
//Dragging navigation on canvas
var dragStartPosition;
paper.on('blank:pointerdown',
function(event, x, y) {
dragStartPosition = {
x: x,
y: y
};
}
);
paper.on('cell:pointerup blank:pointerup', function(cellView, x, y) {
dragStartPosition = null;
});
$("#dbLookupCanvas")
.mousemove(function(event) {
if (dragStartPosition)
paper.translate(
event.offsetX - dragStartPosition.x,
event.offsetY - dragStartPosition.y);
});
// Remove links not connected to anything
paper.model.on('batch:stop', function() {
var links = paper.model.getLinks();
_.each(links, function(link) {
var source = link.get('source');
var target = link.get('target');
if (source.id === undefined || target.id === undefined) {
link.remove();
}
});
});
paper.on('cell:pointerdown', function(elementView) {
resetAll(this);
let isElement = elementView.model.isElement();
if (isElement) {
var currentElement = elementView.model;
currentElement.attr('body/stroke', 'orange');
selectedElement = elementView.model;
} else
selectedElement = null;
});
paper.on('blank:pointerdown', function(elementView) {
resetAll(this);
});
$('#dbLookupCanvas')
.attr('tabindex', 0)
.on('mouseover', function() {
this.focus();
})
.on('keydown', function(e) {
if (e.keyCode == 46)
if (selectedElement) selectedElement.remove();
});
}
function AddTable(xCoord = undefined, yCoord = undefined) {
// This is a sample database data here
let data = [
{columnName: "radomData1"},
{columnName: "radomData2"}
];
if (xCoord == undefined && yCoord == undefined)
{
xCoord = 50;
yCoord = 50;
}
const rect = new joint.shapes.standard.Rectangle({
position: {
x: xCoord,
y: yCoord
},
size: {
width: 150,
height: 200
},
ports: {
groups: {
'a': {},
'b': {}
}
}
});
$.each(data, (i, v) => {
const port = {
group: 'a',
args: {}, // Extra arguments for the port layout function, see `layout.Port` section
label: {
position: {
name: 'right',
args: {
y: 6
} // Extra arguments for the label layout function, see `layout.PortLabel` section
},
markup: [{
tagName: 'text',
selector: 'label'
}]
},
attrs: {
body: {
magnet: true,
width: 16,
height: 16,
x: -8,
y: -4,
stroke: 'red',
fill: 'gray'
},
label: {
text: v.columnName,
fill: 'black'
}
},
markup: [{
tagName: 'rect',
selector: 'body'
}]
};
rect.addPort(port);
});
rect.resize(150, data.length * 40);
graph.addCell(rect);
}
function resetAll(paper) {
paper.drawBackground({
color: 'white'
});
var elements = paper.model.getElements();
for (var i = 0, ii = elements.length; i < ii; i++) {
var currentElement = elements[i];
currentElement.attr('body/stroke', 'black');
}
var links = paper.model.getLinks();
for (var j = 0, jj = links.length; j < jj; j++) {
var currentLink = links[j];
currentLink.attr('line/stroke', 'black');
currentLink.label(0, {
attrs: {
body: {
stroke: 'black'
}
}
});
}
}
Any help would be appreciated. Thanks!
The default link created when you draw a link from a port is joint.dia.Link.
To change this you can use the defaultLink paper option, and configure the router you would like.
defaultLink documentation reference
const paper = new joint.dia.Paper({
el: document.getElementById('dbLookupCanvas'),
model: graph,
width: canvasContainer.width(),
height: 500,
gridSize: 10,
drawGrid: true,
cellViewNamespace: namespace,
validateConnection: function(cellViewS, magnetS, cellViewT, magnetT, end, linkView) {
return (magnetS !== magnetT);
},
snapLinks: {
radius: 20
},
defaultLink: () => new joint.shapes.standard.Link({
router: { name: 'manhattan' },
connector: { name: 'rounded' },
})
});
You could also provide several default options in the paper.
defaultLink: () => new joint.shapes.standard.Link(),
defaultRouter: { name: 'manhattan' },
defaultConnector: { name: 'rounded' }

Why my chart is injected with some random padding inside my canvas?

I can't figure out why I'm not able to remove this random padding injected inside my line chart in Chart.js (file attached).
Here is a part of my chart configuration :
myChart.current = new Chart(canvasRef.current, {
type: "line",
data,
options: {
tension: 0.6,
scales: {
x: {
display: false,
},
y: {
display: false,
},
},
plugins: {
legend: {
display: false,
},
tooltip: {
enabled: false,
external: (context) => {
const tooltipModel = context.tooltip;
if (tooltipModel.opacity === 0) {
setShowTooltip(false);
return;
}
setShowTooltip(true);
const tooltipEl = tooltipRef.current;
const currentTooltipWidth = tooltipEl.clientWidth;
const currentTooltipHeight = tooltipEl.clientHeight;
const currentTooltipCenterAlign = currentTooltipWidth / 2;
let left =
tooltipModel.dataPoints[0].element.x -
currentTooltipCenterAlign;
if (left < 0) {
left = Math.abs(left);
}
let top =
tooltipModel.dataPoints[0].element.y -
currentTooltipHeight -
offsetY;
if (top < 0) {
top = tooltipModel.dataPoints[0].element.y + offsetY;
}
const date = tooltipModel.dataPoints[0].label;
const value = tooltipModel.dataPoints[0].raw;
setPositionAndData(top, left, date, value);
},
},
},
},
});
Thank you very much for your help :) !
[1]: https://i.stack.imgur.com/lNi5p.png

animation of a graph of an equation javascript

I'm stuck on this issue and don't know where to put my hands.
I have to draw in javascript the animation of the graph of the equation y = x ^ 3
what do i mean?
knowing y (for example y = 10) I would like the graph to start from (0; 0) up to (x; 10) following the equation y = x ^ 3
also I would like to create a button which can be clicked during the animation and tells me what y is the graph at that precise moment
for now thanks to chart.js i managed to do this:
JS
var ctx = document.getElementById("myChart");
var data = {
labels: [1, 2, 3, 4, 5],
datasets: [
{
function: function(x) { return x*x*x },
borderColor: "rgba(153, 102, 255, 1)",
data: [],
fill: true
}]
};
Chart.pluginService.register({
beforeInit: function(chart) {
var data = chart.config.data;
for (var i = 0; i < data.datasets.length; i++) {
for (var j = 0; j < data.labels.length; j++) {
var fct = data.datasets[i].function,
x = data.labels[j],
y = fct(x);
data.datasets[i].data.push(y);
}
}
}
});
var myBarChart = new Chart(ctx, {
type: 'line',
data: data,
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true
}
}]
}
}
});
HTML
<canvas id="myChart"></canvas>
result
for now I only have the graph, there is no animation and I cannot select the maximum y
how can I do?
To set max amount on your y Axes you can use the max property or suggestedMax if you want to make sure that if the data goes bigger the axis adapts. For the animation you can write custom logic as in the example underneath. I only dont know how to get the value its at on the click:
const labels = [1, 2, 3, 4, 5]
const totalDuration = 5000;
const delayBetweenPoints = totalDuration / labels.length;
const previousY = (ctx) => ctx.index === 0 ? ctx.chart.scales.y.getPixelForValue(100) : ctx.chart.getDatasetMeta(ctx.datasetIndex).data[ctx.index - 1].getProps(['y'], true).y;
var options = {
type: 'line',
data: {
labels,
datasets: [{
label: '# of Votes',
data: [],
borderWidth: 1,
function: function(x) {
return x * x * x
},
borderColor: 'red',
backgroundColor: 'red'
}]
},
options: {
scales: {
y: {
max: 250
}
},
animation: {
x: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: NaN, // the point is initially skipped
delay(ctx) {
if (ctx.type !== 'data' || ctx.xStarted) {
return 0;
}
ctx.xStarted = true;
return ctx.index * delayBetweenPoints;
}
},
y: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: previousY,
delay(ctx) {
if (ctx.type !== 'data' || ctx.yStarted) {
return 0;
}
ctx.yStarted = true;
return ctx.index * delayBetweenPoints;
}
}
}
},
plugins: [{
id: 'data',
beforeInit: function(chart) {
var data = chart.config.data;
for (var i = 0; i < data.datasets.length; i++) {
for (var j = 0; j < data.labels.length; j++) {
var fct = data.datasets[i].function,
x = data.labels[j],
y = fct(x);
data.datasets[i].data.push(y);
}
}
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
var chart = new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.0.1/chart.js" integrity="sha512-HJ+fjW1Hyzl79N1FHXTVgXGost+3N5d1i3rr6URACJItm5CjhEVy2UWlNNmFPHgX94k1RMrGACdmGgVi0vptrw==" crossorigin="anonymous"></script>
</body>

graph of an equation animated javascript (crash game)

i don't know how to get out of this problem
I have the animation of the graph of the equation y = x ^ 2
this:
const labels = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
const totalDuration = 2000;
const delayBetweenPoints = totalDuration / labels.length;
const previousY = (ctx) => ctx.index === 0 ? ctx.chart.scales.y.getPixelForValue(100) : ctx.chart.getDatasetMeta(ctx.datasetIndex).data[ctx.index - 1].getProps(['y'], true).y;
var options = {
type: 'line',
data: {
labels,
datasets: [{
label: '# of Votes',
data: [],
borderWidth: 1,
function: function(x) {
return x * x
},
borderColor: "rgba(153, 102, 255, 1)",
fill: true
}]
},
options: {
scales: {
y: {
max: 200
},
x: {
suggestedMax: 210
}
},
animation: {
x: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: NaN, // the point is initially skipped
delay(ctx) {
if (ctx.type !== 'data' || ctx.xStarted) {
return 0;
}
ctx.xStarted = true;
return ctx.index * delayBetweenPoints;
}
},
y: {
type: 'number',
easing: 'linear',
duration: delayBetweenPoints,
from: previousY,
delay(ctx) {
if (ctx.type !== 'data' || ctx.yStarted) {
return 0;
}
ctx.yStarted = true;
return ctx.index * delayBetweenPoints;
}
}
}
},
plugins: [{
id: 'data',
beforeInit: function(chart) {
var data = chart.config.data;
for (var i = 0; i < data.datasets.length; i++) {
for (var j = 0; j < data.labels.length; j++) {
var fct = data.datasets[i].function,
x = data.labels[j],
y = fct(x);
data.datasets[i].data.push(y);
}
}
}
}]
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
var chart = new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.0.1/chart.js" integrity="sha512-HJ+fjW1Hyzl79N1FHXTVgXGost+3N5d1i3rr6URACJItm5CjhEVy2UWlNNmFPHgX94k1RMrGACdmGgVi0vptrw==" crossorigin="anonymous"></script>
</body>
I have to add 3 things and I don't know how to get out of it
how can I define a Y value for which the line stops going up?
if this Y is bigger than the axis data, how can they adapt?
how can I create a button which when clicked tells me the exact Y value at that moment during the animation?
I wish something like this graph would come out:
https://www.youtube.com/watch?v=w48ahDZQPnQ
You can try setting the Y max value like this in your code to auto adjust Y axis scale.
beforeInit: function(chart) {
var data = chart.config.data;
let maxValue = 0;
for (var i = 0; i < data.datasets.length; i++) {
for (var j = 0; j < data.labels.length; j++) {
var fct = data.datasets[i].function,
x = data.labels[j],
y = fct(x);
if (maxValue < y) {
maxValue = y;
}
data.datasets[i].data.push(y);
}
}
chart.options.scales.y.max = maxValue;
}

Categories

Resources