The bounty expires in 6 days. Answers to this question are eligible for a +400 reputation bounty.
Nitish Kumar wants to draw more attention to this question.
I'm trying to build an application on KonvaJS with the Vue library.
I've got a group layer where I'm drawing a circle and placing images inside this group.
<v-group
v-for="item in listGroup_Logo"
:key="item.name"
:ref="item.name"
:config="item"
#dblclick="check"
#dragstart="handleDragStart"
#dragend="handleDragEnd"
>
<v-circle
v-for="item in listCircle_Logo"
:key="item.name"
:ref="item.name"
:config="item"
#dragstart="handleDragStart"
#dragend="handleDragEnd"
/>
<v-image
v-for="item in listLogo"
#dragstart="handleDragStart"
#dragend="handleDragEnd"
:ref="item.name"
:key="item.name"
:config="item"
:keyup.delete="deleteItemFromKey"
/>
</v-group>
The goal is to clip the images as per the circle/group area. Also, the end user can resize the group element collectively. As you can see below, resizing the group effects the circle strokes.
To achieve this I defined the basic configuration dataset listGroup_Logo to be passed inside the group layer, so this config data renders at the initial rendering.
listGroup_Logo: [
{
clipFunc: (ctx) => {
ctx.arc(50, 50, 30, 0, Math.PI * 2, false);
},
draggable: true,
x: 50,
y: 50,
name: "Group1676367620342",
type: "group",
},
],
To add drag and drop functionality I'm tracking #mousedown and #touchstart events at v-stage component and defined a method handleMousedown to do the further modifications.
I'm trying to fetch the x and y positions along with the scaleX and scaleY values and trying to update the circle configs:
this.listCircle_Logo[objIndex] = e.target.attrs;
let scaleX =
typeof e.target.attrs.scaleX === "undefined"
? 1
: e.target.attrs.scaleX;
let scaleY =
typeof e.target.attrs.scaleY === "undefined"
? 1
: e.target.attrs.scaleY;
let obj = {
clipFunc: (ctx) => {
ctx.arc(
50 * scaleX,
50 * scaleY,
30 * scaleX,
0,
Math.PI * 2,
false
);
},
};
this.listGroup_Logo[0].clipFunc = obj.clipFunc;
But the circle is not updating its values. See the image below:
I've made a sandbox for my code. You can find it here: https://codesandbox.io/s/bold-fog-ivccnt?file=/src/App.vue
Please advise on how to resize the entire group without disturbing the strokes or circles.
You may need to change the clipping of your group every time you transform circle:
handleTransform(e) {
this.listGroup_Logo[0].clipFunc = (ctx) => {
ctx.save();
ctx.scale(e.target.scaleX(), e.target.scaleY())
ctx.arc(
e.target.x() / e.target.scaleX(),
e.target.y() / e.target.scaleY(),
30,
0,
Math.PI * 2,
false
);
ctx.restore();
};
},
https://codesandbox.io/s/dreamy-hugle-8zqcci?file=/src/App.vue
Also for a bit better view I added strokeScaleEnabled: false to circle and ignoreStroke: true to transformer.
Related
For a 2D top-down game made with Phaser, the player should be able to walk past the front and back of furniture, which I've achieved by changing the depth of the player according to the y value of their position. However, they should not be able to walk through furniture by moving down through it (or moving up through it). They also be blocked from walking through it by walking at the furniture from the sides when at the same y value (coming at the furniture from a higher/lower value is fine, they then just go behind/in front of the furniture).
I figure this could be made by blocking collisions between the bottom of the player sprite and the bottom x pixels of furniture. But I don't know how to do this and can't find anything in the documentation or online. I figure I can make small invisible objects that go in the bottom of the player and furniture, but is there a simpler way to achieve this effect?
I have the furniture grouped by depth, and for each depth have code like this:
create {
furnitureBack = this.physics.add.staticGroup();
furnitureBack.create(300, 80, 'furniture').setFrame(60).refreshBody();
furnitureBack.setDepth(2);
colliderBack = this.physics.add.collider(player, furnitureBack);
}
update {
colliderBack.active = false;
if (player.y > 75 && player.y < 85) {
colliderBack.active = true;
}
}
The problem with this is that it only works when the player enters the 75-85 y range without already overlapping with the furniture. If they enter that range by walking towards the furniture from the bottom, then the collider becomes active but the player is still able to walk through the item.
An easy and fast solution would be to use phaser physics, and just alter the size of the body.
Here the link to the body Documentation (you could even make the collision box small and round with setCircle, here is the link Documentation)
Here a working demo:
(the white box is the player the brown box, is a box)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 536/2,
height: 183/2,
zoom: 2,
physics: {
default: 'arcade',
arcade: {
debug: true
}
},
scene: {
create,
update
},
banner: false
};
function create () {
this.add.text(10, 10, 'Use cursor-keys to move')
this.player = this.add.rectangle(50, 80, 20, 40, 0xffffff)
.setOrigin(.5, 1)
.setDepth(1);
this.box = this.add.rectangle(100, 80, 20, 20, 0x933B26)
.setOrigin(.5, 1)
.setDepth(2);
this.physics.add.existing(this.box, true)
.body
//Setup physics-body size and position
.setSize(20, 10, false)
.setOffset(0, 10);
this.physics.add.existing(this.player)
.body
//Setup physics-body size and position
.setSize(20, 20, false)
.setOffset(0, 20);
this.keys = this.input.keyboard.createCursorKeys();
this.physics.add.collider(this.player, this.box)
}
function update(){
if(!this.player || !this.player.body){
return;
}
let body = this.player.body;
let speed = 50;
body.setVelocity(0, 0);
// Since the box depth is set to "2"
// you just have to alternate the depth of the player
let newDepth = (this.player.y > this.box.y) ? 3 : 1;
this.player.setDepth(newDepth);
if(this.keys.right.isDown){
body.setVelocity(speed, 0);
}
if(this.keys.left.isDown){
body.setVelocity(-speed, 0);
}
if(this.keys.up.isDown){
body.setVelocity(0, -speed);
}
if(this.keys.down.isDown){
body.setVelocity(0, speed);
}
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
What I have
I've created 2 graphs like shown in the first image below. I need these 2 graphs to be perfectly aligned on the 0 axis (which is shown in the section below).
Wanted result
In the image below, the graphs are perfectly aligned and should therefore contain 2 different datasets. One should be placed on the left grid and the other one on the right grid.
The options I thought of
I have not seen a way to implement this in ChartJS. Therefore, I thought of some possible solutions that could solve this issue in a different way.
Because both datasets don't have negative values, I could convert the left dataset to negative values (on the x-axis). The downside is that the range would be incorrect. Also, this would give issues with the popup message if you hover your mouse on a value.
Maybe, two canvas objects can be combined that could result in the wanted result
Question
Is there a way to achieve the wanted result within ChartJS? Or can I perform some wizardry to combine two charts nicely into one? Or should I move to the D3.js library for this particular graph?
I did not find a way to "nicely" implement this. I've changed the source code of the ChartJS library to implement this.
This method uses 2 different graphs that render in 2 different canvas objects. These canvas objects are placed side-by-side to let it look like one graph.
This is my result (The code in here is not exactly like the graph in the picture because I changed it to my specific needs).
I've added a custom variable in the options.layout category named sidePadding.
The code of creating the graph:
new Chart(document.getElementById(this.element), {
type: 'scatter',
data: {
datasets: [{
borderColor: this.lineColor,
borderWidth: 1,
pointBackgroundColor: '#000',
pointBorderColor: '#000',
pointRadius: 0,
fill: false,
tension: 0,
showLine: true,
data: [],
}],
},
options: {
aspectRatio: 0.3,
maintainAspectRatio: false,
responsive: true,
layout: {
sidePadding: {
left: 3,
right: -3,
}
}
}
});
The padding on the sides is by default 3 so it does not touch the border of the canvas. I wanted it to touch the border of the canvas, and therefore set it to -3.
Two different graphs need to be created, one for the left side and one for the right side.
The left graph should have the sidePadding.right set to -3. The right graph should have the sidePadding.left set to -3.
Changes in the ChartJS library
I've worked with ChartJS version v2.9.3. This method will possibly not work anymore in a new version.
I've changed the following in the Chart.js file:
Replace (line number around 11800):
// Adjust padding taking into account changes in offsets
// and add 3 px to move away from canvas edges
me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + 3;
me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + 3;
to:
// Adjust padding taking into account changes in offsets
// and add 3 px to move away from canvas edges
var layoutOptions = chart.options.layout || {};
var sidePadding = helpers$1.options.toPadding(layoutOptions.sidePadding);
me.paddingLeft = Math.max((paddingLeft - offsetLeft) * me.width / (me.width - offsetLeft), 0) + sidePadding.left;
me.paddingRight = Math.max((paddingRight - offsetRight) * me.width / (me.width - offsetRight), 0) + sidePadding.right;
Also you must add the sidePadding.left and sidePadding.right parameter to ChartJS.
This can be done by changing (line number around 7180):
core_defaults._set('global', {
layout: {
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0
},
// Custom added parameter
sidePadding: {
left: 3,
right: 3,
}
}
});
to:
core_defaults._set('global', {
layout: {
padding: {
top: 0,
right: 0,
bottom: 0,
left: 0
},
// Custom added parameter
sidePadding: {
left: 3,
right: 3,
}
}
});
Note
This is probably not the best way to achieve this. Changing the ChartJS library is bad-practice and updating the library could revert these changes.
If someone has a better way to achieve this, please post your methods or comment below.
I need to convert zamel code to HTML code. One of the controllers of zamel, allows monitoring of health index. I have the following controller:
The way this controller works:
It has 3 states for each standard, we have six standards: Corp,IEEE, IEC ,NEI , PTX and ABN.
Now each standard has 3 states:
OK status- Green Color.
Alert status - Yellow Color.
Danger Status - Red color.
Now if all standards are ok,then the overall health index is ok, if one standard isn't ok then overall health index is Alert or Danger.
Now I need a controller which can replace this old zamel controller.
I am currently using highcharts for my graphs so I tried to find some controllers in highcharts for health index status but without any luck.
I am open for suggestions for what to do, these are the options which stand before my eyes for current moment:
Find a controller in Highcharts and modify it.
Go for other js libraries which can help me in this situation.
Build my own controller from scratch, I will need an initial help in this option.
To narrow the options, lets say that I want to use a Highcharts controller for this mission, is it possible?
You can use renderer method from Highcharts API, if you want to make some custom drawings similar to your controller. Here you can find link to Highcharts API:
http://api.highcharts.com/highcharts/Renderer
Here you can find code that may help you with your problem:
var renderController = function(chart) {
var chart = this,
renderer = chart.renderer,
seriesController = chart.options.seriesController,
length = seriesController.data.length,
plotWidth = chart.plotWidth,
marginLeft = chart.marginLeft,
width = chart.plotWidth / length,
generalStatus = 'ok',
color;
$('.controler').remove();
Highcharts.each(seriesController.data, function(c, i) {
if (c.status === 'ok') {
color = '#119001';
} else if (c.status === 'alert') {
color = 'yellow';
generalStatus = 'alert';
} else {
color = 'red';
generalStatus = 'danger';
}
renderer.circle(width * i + (width / 2), 100, width / 2).attr({
fill: color,
'stroke-width': 1,
stroke: 'blue'
}).addClass('controler').add();
renderer.label(c.standard, width * i + (width / 2) - 2, 90).attr({
'text-anchor': 'middle'
}).addClass('controler').add();
});
color = generalStatus === 'ok' ? '#119001' : (generalStatus === 'alert' ? 'yellow' : 'red');
renderer.circle(plotWidth / 2, 300, width / 2).attr({
fill: color,
'stroke-width': 1,
stroke: 'blue'
}).addClass('controler').add();
renderer.label('General', plotWidth / 2 - 2, 290).attr({
'text-anchor': 'middle'
}).addClass('controler').add();
};
And here you can find an example how it can work: http://jsfiddle.net/pqw7pn2L/
As you can see from the hammerjs.github.io website, whenever you drag, swipe, pinch or rotate the white square, it will reset back to its default location. I have tried but fail to prevent that behavior, I dont want it to reset back to its position. I choose this example because it's the only example that fits with my map project that run on windows phone 8.
The way I do is canceled out resetElementEnd() inside onSwipe function, I also canceled out
mc.on("panstart rotatestart pinchstart", resetElementStart);
mc.on("panend rotateend pinchend pancancel rotatecancel pinchcancel", resetElementEnd);
But then whenever I pan it reset back to its default position before it can be panned, and that seems to be the root of many problems. I know that it is something to do with the startX and startY but I dont really get what they are for.
var startX = Math.round((el.parentNode.offsetWidth - el.offsetWidth) / 2);
var startY = Math.round((el.parentNode.offsetHeight - el.offsetHeight) / 2);
...
function onPan(ev) {
transform.translate = {
x: startX + ev.deltaX,
y: startY + ev.deltaY
};
requestElementUpdate();
}
Here is the link include html, css and js files which is basically a simplified version of that hammerjs.github.io sample, the index.js file is the original file and the modifiedIndex.js is that same file but with my cancelling out some of the lines I mentioned above. https://www.mediafire.com/?y7wvady7bhmyrs9
This library is wonderful and I hope I can implement it in my project. Thanks for any help/suggestions.
You have to remember the last position of the cursor.
on resetElementEnd() you will save the translate X and Y
function resetElementEnd() {
startX = transform.translate.x;
startY = transform.translate.y;
el.className = 'animate';
requestElementUpdate();
}
be sure that you have the transform object fully created:
on line 88 you replace
var transform;
into
var transform = {
translate: { x: startX, y: startY },
scale: 1,
rotate: 0
};
hope this helps
I am trying to setup a seesaw with userdragable objects. After world creation in PhysicsJS, mouse drag interaction is added by
world.add( Physics.behavior('interactive', { el: renderer.el }) );
which works fine. Subsequently, I want some added objects to be draggable (the box objects). But the lever should not be draggable, but it should interact with the boxes. So the lever should rotate according to a replaced box. The fulcurm is placed in a noninteractive way by setting its treatment property to static:
world.add( Physics.body('convex-polygon', {
name: 'fulcrum',
x: 250,
y: 490,
treatment: 'static',
restitution: 0.0,
vertices: [
{x: 0, y: 0},
{x: 30, y: -40},
{x: 60, y: 0},
]
}) );
How can objects be interacting with each other, but only some are userdragable?
A fiddle is available at: http://jsfiddle.net/YM8K8/
At the moment this is not supported... but it should be. I've added it as a bug on github. https://github.com/wellcaffeinated/PhysicsJS/issues/101
In the meantime, if you want, you can create a new "interactive" behavior by copying and pasting and just change the name.
Physics.behavior('interactive-custom', function( parent ){ ...
Then in the grab function just make this small addition:
body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ) });
Change to:
body = self._world.findOne({ $at: new Physics.vector( pos.x, pos.y ), $in: self.getTargets() });
What that does is when it's searching for the body at the mouse coordinates, it will also check to see if that body is in the set that you've applied this behavior to.
Then instead of adding the behavior before the bodies in your fiddle, add it at the end, and do this:
world.add(
Physics.behavior('interactive-custom', { el: renderer.el })
.applyTo(world.find({ name: 'box' }))
);
This will use world.find to find the bodies currently in the world that have the "box" name. Then it sends that array of bodies to the behavior and tells the behavior to only apply itself to those bodies.
Here's the modified fiddle:
http://jsfiddle.net/wellcaffeinated/YM8K8/1/