Related
Can I put a blinking pointer at the end?
Or do I have to use another plugin?
I want a pointer that follows the end of the line like an image.
I want to build like a binaryoption or expertoption.
Here is my simple demo:
$(function() {
Highcharts.setOptions({
global: {
useUTC: false
}
});
// Create the chart
$('#container').highcharts('StockChart', {
chart: {
events: {
load: function() {
// set up the updating of the chart each second
var series = this.series[0];
var hasPlotLine = false,
$button = $('#button'),
chart = $('#container').highcharts();
setInterval(function() {
chart.yAxis[0].removePlotLine('plot-line-1');
var x = (new Date()).getTime(), // current time
y = Math.round(Math.random() * 100);
series.addPoint([x, y], true, true);
chart.yAxis[0].addPlotLine({
value: y,
color: 'red',
width: 2,
id: 'plot-line-1'
});
}, 1000);
}
}
},
rangeSelector: {
buttons: [{
count: 1,
type: 'minute',
text: '1M'
}, {
count: 5,
type: 'minute',
text: '5M'
}, {
type: 'all',
text: 'All'
}],
inputEnabled: false,
selected: 0
},
title: {
text: 'Live random data'
},
exporting: {
enabled: false
},
series: [{
name: 'Random data',
data: (function() {
// generate an array of random data
var data = [],
time = (new Date()).getTime(),
i;
for (i = -999; i <= 0; i += 1) {
data.push([
time + i * 1000,
Math.round(Math.random() * 100)
]);
}
return data;
}())
}]
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
<div id="container" style="height: 400px; min-width: 310px"></div>
Also available on JsFiddle: Sample demo link
You can achieve the required result by using Highcharts. SVGRenderer class. You need to draw a path and three circles and animate them by changing their positions. For example:
chart: {
events: {
load: function() {
// set up the updating of the chart each second
var chart = this,
series = chart.series[0],
lastPoint = series.points[series.points.length - 1],
xPos = lastPoint.plotX + chart.plotLeft,
yPos = lastPoint.plotY + chart.plotTop,
pointerSize = 16;
var animatedPointGroup = this.renderer.g().attr({
translateX: xPos - pointerSize / 2,
translateY: yPos - pointerSize / 2
}).add();
var plotLine = this.renderer.path([
'M', chart.plotLeft, yPos,
'L', xPos, yPos
])
.attr({
stroke: '#008dc4',
'stroke-width': 1
})
.add();
var shadowCircle = this.renderer.circle(
pointerSize / 2,
pointerSize / 2,
pointerSize
).attr({
fill: '#d3d7de'
}).add(animatedPointGroup);
this.renderer.circle(
pointerSize / 2,
pointerSize / 2,
6
).attr({
fill: '#ffffff'
}).add(animatedPointGroup);
this.renderer.circle(
pointerSize / 2,
pointerSize / 2,
3
).attr({
fill: '#008dc4'
}).add(animatedPointGroup);
setInterval(function() {
var x = (new Date()).getTime(), // current time
y = Math.round(Math.random() * 100),
point,
xPos,
yPos;
series.addPoint([x, y], true, true);
point = series.points[series.points.length - 1];
xPos = point.plotX + chart.plotLeft;
yPos = point.plotY + chart.plotTop;
animatedPointGroup.animate({
translateX: xPos - pointerSize / 2,
translateY: yPos - pointerSize / 2
});
plotLine.animate({
d: [
'M', chart.plotLeft, yPos,
'L', xPos, yPos
]
});
shadowCircle.animate({
r: shadowCircle.attr('r') === pointerSize ? pointerSize / 2 : pointerSize
});
}, 1000);
}
}
}
Live demo: http://jsfiddle.net/BlackLabel/z4c0or3u/
API Reference:
https://api.highcharts.com/class-reference/Highcharts.SVGRenderer
https://api.highcharts.com/class-reference/Highcharts.SVGElement#animate
https://api.highcharts.com/class-reference/Highcharts.SVGElement#attr
My project uses echart to create radar chart and i have to find click event for indicators around radar chart.
It is implemented like this.
createradarchart() {
this.theme.getJsTheme()
.pipe(
takeWhile(() => this.alive),
delay(1),
)
.subscribe(config => {
this.options = {
name: 'KPI Radar',
grid: {
left: '5%',
right: '5%',
top: 0,
bottom: 0
},
// label: {
// distance: 5
// },
type: 'radar',
color: ['red', 'green', 'blue'],
legend: {
bottom: 5,
itemGap: 20,
data: ['Baseline', 'Threshold', 'Actual'],
textStyle: {
color: 'black',
fontSize: 10
}
},
radar: {
indicator: this.indicator,
nameGap: 5,
shape: 'circle',
radius: '43%',
name: {
textStyle: {
color: 'black',
fontSize: 10
}
}
},
tooltip: {
show: true,
textStyle: {fontSize:10},
trigger: 'item',
formatter: (params => {
return params['name']+'-'+params['value'][1];
})
},
series: this.seriesdata,
};
this.ctx = this.echartsIntance.getRenderedCanvas();
this.chart = new Chart(this.ctx,{type:'radar', options: this.options})
// this.ctx = canvas.getContext("2d");
});
}
where data and options are in format, with data being fetched from server:
seriesdata: any = {type: 'radar', data:[{name:'Baseline',value:[1,2,3,4,5]},{name:'Threshold',value:[1,2,3,4,5]},{name:'Actual',value:[1,2,3,4,5]}]};
indicator = [
{ name: 'DL User Thpt_Kbps[CDBH]', max: 100 },
{ name: 'ERAB SSR[CDBH]', max: 100 },
{ name: 'PS DCR %[CDBH]', max: 100 },
{ name: 'VoLTE CSSR', max: 100 },
{ name: 'VoLTE DCR[CBBH]', max: 100 }
];
options: EChartOption = {
name: 'KPI Radar',
grid: {
left: '2%',
right: '2%',
top: 0,
bottom: 0
},
// label:{
// distance: 5
// },
type: 'radar',
color: ['red', 'green', 'blue'],
legend: {
orient: 'vertical',
align: 'left',
right: 20,
data: ['Baseline', 'Threshold', 'Actual'],
textStyle: {
color: 'black',
fontSize: 10
}
},
radar: {
indicator: this.indicator,
nameGap: 5,
shape: 'circle',
radius:'60%',
},
tooltip: {
show: false,
// trigger: 'item',
// formatter: (params => {
// return params['name']+'-'+params['value'][1];
// })
}
};
This is where i want click event to be triggered, having the name of label which is clicked.
Approach i found to do this is this, but it didnt work, i debugged and found that scale.pointLabels is empty.
labelClicked(e:any){
var self = this;
var helpers = Chart.helpers;
var scale = self.chart.scale;
var opts = scale.options;
var tickOpts = opts.ticks;
// Position of click relative to canvas.
var mouseX = e.offsetX;
var mouseY = e.offsetY;
var labelPadding = 5; // number pixels to expand label bounding box by
// get the label render position
// calcs taken from drawPointLabels() in scale.radialLinear.js
var tickBackdropHeight = (tickOpts.display && opts.display) ?
helpers.valueOrDefault(tickOpts.fontSize, Chart.defaults.global.defaultFontSize)
+ 5: 0;
var outerDistance = scale.getDistanceFromCenterForValue(opts.ticks.reverse ? scale.min : scale.max);
for (var i = 0; i < scale.pointLabels.length; i++) {
// Extra spacing for top value due to axis labels
var extra = (i === 0 ? tickBackdropHeight / 2 : 0);
var pointLabelPosition = scale.getPointPosition(i, outerDistance + extra + 5);
// get label size info.
// TODO fix width=0 calc in Brave?
// https://github.com/brave/brave-browser/issues/1738
var plSize = scale._pointLabelSizes[i];
// get label textAlign info
var angleRadians = scale.getIndexAngle(i);
var angle = helpers.toDegrees(angleRadians);
var textAlign = 'right';
if (angle == 0 || angle == 180) {
textAlign = 'center';
} else if (angle < 180) {
textAlign = 'left';
}
// get label vertical offset info
// also from drawPointLabels() calcs
var verticalTextOffset = 0;
if (angle === 90 || angle === 270) {
verticalTextOffset = plSize.h / 2;
} else if (angle > 270 || angle < 90) {
verticalTextOffset = plSize.h;
}
// Calculate bounding box based on textAlign
var labelTop = pointLabelPosition.y - verticalTextOffset - labelPadding;
var labelHeight = 2*labelPadding + plSize.h;
var labelBottom = labelTop + labelHeight;
var labelWidth = plSize.w + 2*labelPadding;
var labelLeft;
switch (textAlign) {
case 'center':
labelLeft = pointLabelPosition.x - labelWidth/2;
break;
case 'left':
labelLeft = pointLabelPosition.x - labelPadding;
break;
case 'right':
labelLeft = pointLabelPosition.x - labelWidth + labelPadding;
break;
default:
console.log('ERROR: unknown textAlign '+textAlign);
}
var labelRight = labelLeft + labelWidth;
// Render a rectangle for testing purposes
self.ctx.save();
self.ctx.strokeStyle = 'red';
self.ctx.lineWidth = 1;
self.ctx.strokeRect(labelLeft, labelTop, labelWidth, labelHeight);
self.ctx.restore();
// compare to the current click
if (mouseX >= labelLeft && mouseX <= labelRight && mouseY <= labelBottom && mouseY >= labelTop) {
alert(scale.pointLabels[i]+' clicked');
// Break loop to prevent multiple clicks, if they overlap we take the first one.
break;
}
}
}
Thanks in advance
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
How to correctly register clip-path so that the line is filled correctly that is, along the line and not from top to bottom as now. The screenshots show an example of filling in. On codepen, you can see the entire svg and see how the animation works (it is controlled by scrolling).
Screenshots:
Screenshot with problem:
Now clip-path its (For all code check codepen):
g.setAttribute('style', `clip-path: polygon(0 0%, 100% 0%, 100% ${progress+0.8}%, 0% ${progress+0.8}%);`)
UPDATE:
Please if you offer a solution show it on my example. Since my problem is specific and most just write without looking completely at the problem on codepen.
For each of your coloured lines, create a path that follows the course of that line back and forth. It's stroke width should be wide enough to cover the line at its widest.
You then use that new line as a <mask> to reveal your colours.
The basic idea is described in my answer to a similar question. That question was about simulating animating handwriting. But the same approach will solve your problem also.
https://stackoverflow.com/a/37787761/1292848
Update
Did you check my codepen before answer?
Yes I did. The technique described there will work perfectly fine for your situation. To prove it, here is my own CodePen:
https://codepen.io/PaulLeBeau/pen/RwNOaZg
This is a proof of concept. I have only created a mask path for a short section of the paths. And to demo the concept, I have used a simple CSS animation, instead of implementing an onscroll handler.
It should be fairly obvious what's happening. You will just need to change your onscroll handler to set the stroke-dashoffset based on how far you have scrolled.
If you don't understand how the stroke-offset effect works to animat a line length, then there are plenty of tutorials all over the web, and here on Stack Overflow. For example, CSS-Tricks has a pretty good one:
https://css-tricks.com/svg-line-animation-works/
I have spent quite some time looking into this (few hours) and it is not so simple to solve. BUT I do have a solution. That said, the solution is also not so simple.
Note, the blue polygon is just there to help explain what shape is being used for the clip-path of the group element <g id="Line_Orange" >
I did not attach this to the scroll event because I needed a way to work on the logic behind the clip-path polygon with the simplest set-up I could get and allow me to start and stop the setInterval() to console.log() out the current x and y values etc. to compute where the turns needed to occur etc.
The full example is here: https://codepen.io/Alexander9111/pen/eYmbajB
And the JavaScript is:
var running = false;
const g = document.querySelector("#Line_Orange");
const g_clone = g.cloneNode(true);
const svg = document.querySelector("svg");
g_clone.id = "Line_Grey";
//svg.appendChild(g_clone);
svg.insertBefore(g_clone, svg.childNodes[0]);
g.setAttribute('clip-path', "polygon(0 0, 0 100, 250 100, 250 0)");
const polygon = document.querySelector("#polygon_mask");
let segment_num = 0;
var temp_arr = [];
var polygon_points_arr = [80, 0, 80, 0];
var polygon_points_str = "80 0 80 0";
const polygon_segments = [
{
n: 0, dir: 1, progress: "y", boost: 1, init_x_y: [80,0], index: [3,5],
points:[80, 0, 80, 250, 250, 250, 250, 0]
},
{
n: 1, dir: 1, progress: "y", boost: 2, init_x_y: [80,100], index: [3,null],
points:[80, 0, 80, 250, 250, 100, 250, 0]
},
{
n: 2, dir: 1, progress: "x", boost: 2, init_x_y: [80,250], index: [4,null],
points:[80, 0, 80, 250, 250, 250, 250, 100, 250, 100, 250, 0]
},
{
n: 3, dir: 1, progress: "x", boost: 1, init_x_y: [250,100], index: [4,6],
points:[80, 0, 80, 250, 450, 250, 450, 100, 250, 100, 250, 0]
},
{
n: 4, dir: 1, progress: "x", boost: 2, init_x_y: [700,100], index: [null,6],
points:[80, 0, 80, 250, 700, 250, 820, 100, 250, 100, 250, 0]
},
{
n: 5, dir: 1, progress: "y", boost: 2, init_x_y: [820,100], index: [null,9],
points:[80, 0, 80, 250, 700, 250, 700, 250, 820, 100, 820, 100, 250, 100, 250, 0]
},
{
n: 6, dir: 1, progress: "y", boost: 1, init_x_y: [820,250], index: [7,9],
points:[80, 0, 80, 250, 700, 250, 700, 250, 820, 100, 820, 100, 250, 100, 250, 0]
},
{
n: 7, dir: 1, progress: "y", boost: 2, init_x_y: [820,600], index: [null,9],
points:[80, 0, 80, 250, 700, 250, 700, 600, 820, 600, 820, 100, 250, 100, 250, 0]
},
{
n: 8, dir: -1, progress: "x", boost: 2, init_x_y: [820,750], index: [null,10],
points:[80, 0, 80, 250, 700, 250, 700, 600, 700, 600, 820, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 9, dir: -1, progress: "x", boost: 1, init_x_y: [700,750], index: [8,10],
points:[80, 0, 80, 250, 700, 250, 700, 600, 700, 600, 820, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 10, dir: -1, progress: "x", boost: 2, init_x_y: [150,600], index: [10,null],
points:[80, 0, 80, 250, 700, 250, 700, 600, 150, 600, 150, 600, 150, 750, 150, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 11, dir: 1, progress: "y", boost: 2, init_x_y: [0,600], index: [11,null],
points:[80, 0, 80, 250, 700, 250, 700, 600, 0, 600, 0, 600, 150, 750, 150, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 12, dir: 1, progress: "y", boost: 1, init_x_y: [0,750], index: [11,13],
points:[80, 0, 80, 250, 700, 250, 700, 600, 0, 600, 0, 600, 150, 750, 150, 750, 820, 750, 820, 100, 250, 100, 250, 0]
}
];
var progressY = 0;
var progressX = 80;
const velocity = 1;
var boost = 1;
var direction = 1;
var timeInterval; //to be started at a later time
function myTimer() {
//console.log(progress);
direction = polygon_segments[segment_num].dir;
polygon_points_arr = polygon_segments[segment_num].points;
//console.log("null == 0", null == 0);
var progress = polygon_segments[segment_num].progress == "x" ? progressX : progressY;
var first_index = polygon_segments[segment_num].index[0];
var second_index = polygon_segments[segment_num].index[1];
if (first_index != null){
polygon_points_arr[first_index] = progress;
}
if (second_index != null){
polygon_points_arr[second_index] = progress;
}
polygon_points_arr.map((child, index) => {
if (index % 2 == 0 && index < polygon_points_arr.length - 1){
return child + ",";
} else {
return child
}
});
temp_arr = polygon_points_arr.map((el, index, arr) => {
if ((index + 1) % 2 == 0 && index < arr.length - 1){
return el + ",";
} else {
return el;
}
});
polygon_points_str = temp_arr.join(" ");
console.log(polygon_points_str);
function incrementAndSetValues(){
segment_num +=1;
boost = polygon_segments[segment_num].boost;
progressX = polygon_segments[segment_num].init_x_y[0];
progressY = polygon_segments[segment_num].init_x_y[1];
}
if (progressY>= 10000) {
clearInterval(timeInterval);
} else {
if (segment_num == 0) {
progressY += (velocity * boost * direction);
if (progressY >= 100) {
incrementAndSetValues()
}
} else if (segment_num == 1){
progressY += (velocity * boost * direction);
if (progressY >= 250) {
incrementAndSetValues()
}
console.log(segment_num);
} else if (segment_num == 2){
progressX += (velocity * boost * direction);
if (progressX >= 250) {
incrementAndSetValues()
}
} else if (segment_num == 3){
progressX += (velocity * boost * direction);
if (progressX >= 700) {
incrementAndSetValues()
}
} else if (segment_num == 4){
progressX += (velocity * boost * direction);
if (progressX >= 820) {
incrementAndSetValues()
}
} else if (segment_num == 5){
progressY += (velocity * boost * direction);
if (progressY >= 250) {
incrementAndSetValues()
}
} else if (segment_num == 6){
progressY += (velocity * boost * direction);
if (progressY >= 600) {
incrementAndSetValues()
}
} else if (segment_num == 7){
progressY += (velocity * boost * direction);
if (progressY >= 750) {
incrementAndSetValues()
}
} else if (segment_num == 8){
progressX += (velocity * boost * direction);
if (progressX <= 700) {
incrementAndSetValues()
}
} else if (segment_num == 9){
progressX += (velocity * boost * direction);
if (progressX <= 150) {
incrementAndSetValues()
}
} else if (segment_num == 10){
progressX += (velocity * boost * direction);
if (progressX <= 0) {
incrementAndSetValues()
}
} else if (segment_num == 11){
progressY += (velocity * boost * direction);
if (progressY >= 750) {
incrementAndSetValues()
}
} else if (segment_num == 12){
progressY += (velocity * boost * direction);
}
}
//console.log(segment_num);
g.setAttribute('clip-path', `polygon(${polygon_points_str})`);
polygon.setAttribute('points', polygon_points_str);
}
function myStopFunction() {
console.log("stop X,Y", progressX, progressY);
document.querySelector("#start").removeAttribute('disabled', true);
document.querySelector("#stop").setAttribute('disabled', true);
clearInterval(timeInterval);
running = false;
}
function myStartFunction() {
timeInterval = setInterval(myTimer, 10);
document.querySelector("#start").setAttribute('disabled', true);
document.querySelector("#stop").removeAttribute('disabled', true);
running = true;
}
document.querySelector("#start").addEventListener('click', myStartFunction);
document.querySelector("#stop").addEventListener('click', myStopFunction);
document.addEventListener('keydown', function(e){
console.log(e.code);
if (e.code == "Enter"){
if (running){
myStopFunction();
} else {
myStartFunction();
}
}
});
document.querySelector("#reset").addEventListener('click', function(){
progressY = 0.00;
progressX = 0.00;
segment_num = 0;
myTimer();
document.querySelector("#start").removeAttribute('disabled', true);
document.querySelector("#stop").removeAttribute('disabled', true);
});
document.addEventListener('DOMContentLoaded',
function(){
const g_grey = document.querySelector("#Line_Grey");
//console.log(g_grey);
const grey_paths = g_grey.querySelectorAll("path, polygon");
for (i = 0; i< grey_paths.length; i++) {
//console.log(grey_paths[i]);
if (grey_paths[i].getAttribute('fill') == "none"){
//do nothing
} else if (grey_paths[i].getAttribute('fill') != "#bbbbbb"){
//must be orange, change to grey
grey_paths[i].setAttribute('fill',"#bbbbbb");
}
}
myTimer();
}, false);
And the most important part of the JavaScript is this array:
const polygon_segments = [
{
n: 0, dir: 1, progress: "y", boost: 1, init_x_y: [80,0], index: [3,5],
points:[80, 0, 80, 250, 250, 250, 250, 0]
}, ...
There is a segment for each segment of the polygon, as it grows and becomes more complex.
This diagram should help explain that a little bit:
And this one, explaining how the polygon increases in the number of points:
I am creating a simple platformer. I am trying to create collisions with objects and be able to detect those. With the code I have below I am not able to detect collisions properly and stop the player from moving when they collide. What is supposed to happen is the code is supposed to check if there is a collision with any of the objects in the level.Objects array. The code I have now does not detect collisions and you fall infinity into the ground. How would I create a function that detects collisions properly and returns true on which side it collides with?
function runGame() {
var game = document.getElementById('game')
var ctx = game.getContext("2d")
var DonaldRest = document.getElementById('DonaldRest')
var GrassTile = document.getElementById('GrassTile')
var gravity = 0.5
var momentum = 0;
var momentumDown = 0;
var spacing = 64;
var speed = 2;
var maxSpeed = 2;
var jumpHeight = 3;
var levels = [{
Name: "Level 1",
Objects: [{
Type: "GrassFloor",
Location: {
x: 0,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 1,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 2,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 3,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 4,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 5,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 6,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 7,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 8,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 9,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 10,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 11,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 12,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 13,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 14,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 15,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 16,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 17,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 18,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 19,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 20,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 21,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 22,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 23,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 24,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 25,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 26,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 27,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 28,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, {
Type: "GrassFloor",
Location: {
x: spacing * 29,
y: 0
},
Scale: {
x: 1,
y: 1
},
Solid: true,
Height: 3
}, ]
}]
var player = {
position: {
x: 0,
y: 0
},
Time: 0
}
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
var game = setInterval(function() {
ctx.imageSmoothingEnabled = false
ctx.clearRect(0, 0, window.innerWidth, window.innerHeight);
ctx.fillStyle = "#adfffa"
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height)
ctx.drawImage(DonaldRest, ctx.canvas.width / 2 - (96 / 2), ctx.canvas.height / 2 - (96 / 2), 96, 96)
var Level = levels[0]
var Objects = Level.Objects
var OnGround = checkCollisions().Bottom
if (OnGround == false) {
if (momentumDown <= maxSpeed) {
momentumDown -= gravity;
player.position.y += momentumDown;
} else {
player.position.y += momentumDown;
}
} else {
momentumDown = 0;
console.log("collided")
}
for (var j = 0; j < Objects.length; j++) {
if (Objects[j].Type == "GrassFloor") {
ctx.drawImage(GrassTile, Objects[j].Location.x - player.position.x, (ctx.canvas.height - spacing + player.position.y) - (spacing * Objects[j].Height), spacing, spacing)
for (var i = -5; i < Objects[j].Height; i++) {
ctx.drawImage(DirtTile, Objects[j].Location.x - player.position.x, (ctx.canvas.height - spacing) - (i * spacing) + player.position.y, spacing, spacing)
}
}
}
}, 17); //17
$(document).keydown(function(e) {
if (e.which == 32) {
if (checkCollisions().Bottom == true) {
console.log(momentumDown);
momentumDown -= jumpHeight
console.log(momentumDown);
}
}
})
function isTouchingFloor(e1, e2) {
return e1.x < (e2.x + e2.w) && (e1.x + e1.w) > e2.x && e1.y - momentumDown < (e2.y + e2.h) && (e1.y - momentumDown + e1.h) > e2.y;
}
function checkCollisions() {
var Objects = levels[0].Objects;
var Collision = {
Top: false,
Left: false,
Bottom: false,
Right: false
}
var GrassTileImg = new Image()
var o1 = {
y: player.position.y,
h: 96,
x: player.position.x,
w: 96
}
for (var i = 0; i < Objects.length; i++) {
var o2 = {
y: Objects[i].Location.y,
x: Objects[i].Location.x,
h: 64,
w: 64
}
if (isTouchingFloor(o1, o2) == true) {
Collision.Bottom == true;
}
console.log(Collision.Bottom)
}
return Collision
}
}
This is a snippet of code I used when I was doing a group project for checking collisions. Essentially we make a function to check the collision between two objects and the corresponding side.
Collision Function
/**
* Checks for a collision of two objects. Moves objectA if side is a string with the word move.
* #param objectA The object that needs to move.
* #param objectB The object that needs to block.
* #param side If true, makes return a string. If "move", moves objectA.
* #returns {*} String if side evaluates to true, otherwise boolean.
*/
function checkCollision(objectA, objectB, side) {
if (side) {
var vx = objectA.centerX() - objectB.centerX(),
vy = objectA.centerY() - objectB.centerY(),
combinedHalfWidths = objectA.halfWidth() + objectB.halfWidth(),
combinedHalfHeights = objectA.halfHeight() + objectB.halfHeight(),
collisionSide = "";
if (Math.abs(vx) < combinedHalfWidths && Math.abs(vy) < combinedHalfHeights) {
var overlapX = combinedHalfWidths - Math.abs(vx),
overlapY = combinedHalfHeights - Math.abs(vy);
if (overlapX > overlapY) {
if (vy > 0) {
if (side === "move") {
objectA.vy = objectB.vy;
objectA.y += overlapY;
}
collisionSide = "top";
} else {
if (side === "move") {
objectA.vy = objectB.vy;
objectA.y -= overlapY;
}
collisionSide = "bottom";
}
} else {
if (vx > 0) {
if (side === "move") {
objectA.vx = objectB.vx;
objectA.x += overlapX;
}
collisionSide = "left";
} else {
if (side === "move") {
objectA.vx = objectB.vx;
objectA.x -= overlapX;
}
collisionSide = "right";
}
}
}
return collisionSide;
} else {
return !(objectA.x + objectA.width < objectB.x ||
objectB.x + objectB.width < objectA.x ||
objectA.y + objectA.height < objectB.y ||
objectB.y + objectB.height < objectA.y);
}
}
This function does the checking for all objects we have loaded in our level.
Collision Checking
function doCollisionChecks() {
var checkPush = false;
player.isOnGround = false;
for (var i = 0; i < solids.length; i++) {
if (checkCollision(player, solids[i], "move") === "bottom") {
player.isOnGround = true;
player.state = player.STANDING;
if (solids[i].vx) {
if (solids[i].vx !== player.extraVX) {
player.extraVX = solids[i].vx
}
} else {
player.extraVX = 0;
}
}
}
for (i = 0; i < objects.length; i++) {
if (checkCollision(objects[i], player, true) === "right" || checkCollision(objects[i], player, true) === "left") {
player.speedLimit = scaleWidth(2);
objects[i].speedLimit = scaleWidth(2);
checkPush = true;
}
//Letting the player move boxes, while avoiding a "box hop" bug.
if (checkCollision(objects[i], player, true) === "top") {
player.y = objects[i].y - player.height;
} else if (checkCollision(objects[i], player, true) === "bottom") {
player.y = objects[i].y + objects[i].height;
} else if (player.centerY() > objects[i].y + objects[i].height * 0.1 && player.centerY() < objects[i].y + objects[i].height * 0.9) {
checkCollision(objects[i], player, "move");
}
for (var j = 0; j < solids.length; j++) {
if (checkCollision(objects[i], solids[j], "move") === "bottom" && solids[j].vx) {
objects[i].extraVX = solids[j].vx;
} else {
objects[i].extraVX = 0;
}
checkCollision(objects[i], solids[j], "move");
}
for (j = 0; j < objects.length; j++) {
if (j !== i) {
//Avoids boxes falling through one another.
if (checkCollision(objects[i], objects[j], true) === "top") {
checkCollision(objects[j], objects[i], "move");
} else {
checkCollision(objects[i], objects[j], "move");
}
}
}
if (checkCollision(player, objects[i], true) === "bottom") {
player.isOnGround = true;
player.state = player.STANDING;
player.extraVX = objects[i].extraVX;
} else if (checkCollision(player, objects[i], true) === "top") {
score -= 50;
gameState = OVER;
}
checkCollision(player, objects[i], "move");
if (objects[i].y > c.height) {
if (objects[i].correct) {
score += 100;
objects.splice(i, 1);
checkWin();
} else {
fRed = 0;
score -= 100;
objects.splice(i, 1);
}
}
}
for (i = 0; i < enemies.length; i++) {
if (checkCollision(enemies[i], player)) {
score -= 50;
gameState = OVER;
}
j = 0;
while (enemies[i] && j < objects.length) {
if (checkCollision(objects[j], enemies[i], true) === "bottom") {
score += 75;
objects[j].vy = -1 * scaleHeight(6);
enemies.splice(i, 1);
//score++
}
j++;
}
}
if (checkCollision(player, powerUp)) {
score += 25;
activatePowerUp();
}
if (!checkPush) {
player.speedLimit = scaleWidth(3);
for (i = 0; i < objects.length; i++) {
objects[i].speedLimit = scaleWidth(3);
}
}
}
Sorry but there is a lot of irrelevant attributes used such as speed limits etc. But it works correctly.
You can find the whole source here.
I managed to create a diagram with elements, in port, and multiple out ports.
All the elements are connected using links from the in port to an out port.
When I try to play with the links I already have (loaded from JSON), it works properly.
For some reason when I try to create a new link by pressing the ports, it drags the element instead.
How can I make the ports create new links when I press on them?
joint.shapes.Question = joint.shapes.basic.Generic.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, {
markup: '<g class="rotatable">' +
'<g class=""><g class="inPorts" /><rect class="question-wrapper" /><rect class="question-title" /><rect class="options-body" /><g class="outPorts" /></g>' +
'<text class="question" /><g class="options" />' +
'</g>',
portMarkup: '<g class="port port-<%= port.type %> port-<%= port.id %>"><circle class="port-body" /><text class="port-label" /></g>',
optionMarkup: '<g class="option-wrapper"><text class="option"/></g>',
defaults: joint.util.deepSupplement({
type: 'Question',
minWidth: 200,
optionHeight: 25,
inPorts: [{"id": "in", "label": "IN"}],
attrs: {
'.': {
magnet: false
},
'.question-wrapper': {
width: 200,
height: 95,
fill: '#f2f2f2',
rx: 10,
ry: 10,
ref: '.paper'
},
'.question-title': {
height: 30,
fill: '#ffffff',
rx: 5,
ry: 5,
ref: '.question-wrapper',
'ref-x': 5,
'ref-y': 5,
'ref-width': -10
},
'.question': {
fill: '#000000',
'font-size': 14,
'y-alignment': 'middle',
'x-alignment': 'middle',
ref: '.question-title',
'ref-x': .5,
'ref-y': .5,
},
'.options-body': {
height: 50,
fill: '#ffffff',
rx: 5,
ry: 5,
ref: '.question-wrapper',
'ref-x': 5,
'ref-y': 40,
'ref-width': -10
},
'.options': {
ref: '.options-body',
'ref-x': 5,
'ref-y': 5
},
'.option-wrapper': {
ref: '.options'
},
'.option': {
fill: '#000000',
'font-size': 14
},
'.port-in': {
ref: '.question-wrapper',
'ref-y': 0,
'ref-x': .5,
magnet: true,
type: 'in'
},
'.port-in .port-body': {
r: 15,
fill: '#75caeb',
ref: '.port-in'
},
'.port-in .port-label': {
'font-size': 10,
fill: '#ffffff',
'x-alignment': 'middle',
'y-alignment': -10,
ref: '.port-in .port-body'
},
'.port-out': {
ref: '.question-wrapper',
magnet: true,
type: 'out'
},
'.port-out .port-body': {
r: 10,
ref: '.question-wrapper',
'ref-dy': 15,
'ref-x': .5,
fill: '#158cba',
'y-alignment': 'middle'
},
'.port-out .port-label': {
'text': 'R',
'font-size': 10,
'y-alignment': 'middle',
'x-alignment': 'middle',
fill: '#ffffff',
ref: '.port-out .port-body',
'ref-x': .5,
'ref-y': 15,
'pointer-events': 'none'
}
}
}, joint.shapes.basic.Generic.prototype.defaults),
initialize: function () {
this.attr('.question/text', this.get('question'), {silent: true});
this.onChangePosition();
this.onChangeOptions();
joint.shapes.basic.PortsModelInterface.initialize.apply(this, arguments);
},
onChangePosition: function () {
var timer;
var timer_interval = 500;
this.on('change:position', function (cellView, position) {
clearTimeout(timer);
timer = setTimeout(function () {
$.ajax({
url: routes.questionPosition.replace(':question', cellView.id),
data: position,
method: 'post',
headers: {'X-CSRF-TOKEN': $('[name="csrf_token"]').attr('content')}
});
}, timer_interval);
});
},
onChangeOptions: function () {
var options = this.get('options');
var size = this.get('size');
var optionHeight = this.get('optionHeight');
// First clean up the previously set attrs for the old options object.
// We mark every new attribute object with the `dynamic` flag set to `true`.
// This is how we recognize previously set attributes.
var attrs = this.get('attrs');
_.each(attrs, function (attrs, selector) {
if (attrs.dynamic) {
// Remove silently because we're going to update `attrs`
// later in this method anyway.
this.removeAttr(selector, {silent: true});
}
}, this);
// Collect new attrs for the new options.
var offsetY = 0;
var attrsUpdate = {};
_.each(options, function (option) {
var selector = '.option-' + option.id;
attrsUpdate[selector] = {transform: 'translate(0, ' + offsetY + ')', dynamic: true};
attrsUpdate[selector + ' .option'] = {text: option.text, dynamic: true};
offsetY += optionHeight;
}, this);
this.attr(attrsUpdate);
this.autoresize();
},
autoresize: function () {
var options = this.get('options') || [];
var gap = this.get('paddingBottom') || 15;
var height = options.length * this.get('optionHeight');
var wrapperHeight = height + this.attr('.question-title/height') + gap;
var width = joint.util.measureText(this.get('question'), {
fontSize: this.attr('.question/font-size')
}).width + (this.attr('.question-title/rx') * 6);
this.attr('.options-body/height', height);
this.resize(Math.max(this.get('minWidth'), width), wrapperHeight);
this.attr('.question-wrapper/width', width);
this.attr('.question-wrapper/height', wrapperHeight);
},
getPortAttrs: function (port, index, total, selector, type) {
var attrs = {};
var portSelector = selector + ' .port-' + type;
attrs[portSelector + ' .port-label'] = {text: port.label};
attrs[portSelector + ' .port-body'] = {
port: {
id: port.id,
type: type
}
};
if (selector === '.outPorts') {
attrs[portSelector] = {'ref-x': ((total / 2) * 30 * -1) + (index * 30) + 15};
}
return attrs;
}
}));
joint.shapes.QuestionView = joint.dia.ElementView.extend(_.extend({}, joint.shapes.basic.PortsViewInterface, {
initialize: function () {
joint.shapes.basic.PortsViewInterface.initialize.apply(this, arguments);
},
renderMarkup: function () {
joint.dia.ElementView.prototype.renderMarkup.apply(this, arguments);
// A holder for all the options.
this.$options = this.$('.options');
// Create an SVG element representing one option. This element will
// be cloned in order to create more options.
this.elOption = V(this.model.optionMarkup);
this.renderOptions();
},
renderOptions: function () {
this.$options.empty();
_.each(this.model.get('options'), function (option, index) {
var className = 'option-' + option.id;
var elOption = this.elOption.clone().addClass(className);
elOption.attr('option-id', option.id);
this.$options.append(elOption.node);
}, this);
// Apply `attrs` to the newly created SVG elements.
this.update();
}
}));