Chart.js (http://www.chartjs.org/docs/) can fill the color below line charts using the "fillColor" attribute (filling the region between the line chart itself and the x-axis).
What I'd like to know is whether Chart.js can be configured to create shaded regions such as the one shown below:
http://peltiertech.com/Excel/pix5/HorizBands09.png
Thank you.
Shaded Regions for Line Charts
You can extend the chart to do this.
Preview
or
Script
Chart.types.Line.extend({
name: "LineAlt",
initialize: function (data) {
Chart.types.Line.prototype.initialize.apply(this, arguments);
var ranges = [
{
start: 100,
end: 75,
color: 'rgba(250,0,0,0.5)'
},
{
start: 75,
end: 50,
color: 'rgba(0,250,0,0.5)'
},
{
start: 50,
end: 25,
color: 'rgba(0,0,250,0.5)'
},
{
start: 25,
end: 0,
color: 'rgba(250,250,0,0.5)'
}
];
var scale = this.scale;
var rangesStart = scale.calculateY(ranges[0].start);
var rangesEnd = scale.calculateY(ranges[ranges.length - 1].end);
var gradient = this.chart.ctx.createLinearGradient(0, rangesStart, 0, rangesEnd);
ranges.forEach(function (range) {
gradient.addColorStop((scale.calculateY(range.start) - rangesStart) / (rangesEnd - rangesStart), range.color);
gradient.addColorStop((scale.calculateY(range.end) - rangesStart) / (rangesEnd - rangesStart), range.color);
})
this.datasets[0].fillColor = gradient;
}
});
and then
...
new Chart(ctx).LineAlt(data);
If you want to shade the whole background use
var originalDraw = scale.draw;
scale.draw = function() {
originalDraw.apply(this, arguments);
ctx.save();
ctx.fillStyle = gradient;
ctx.fillRect(scale.calculateX(0), scale.startPoint, scale.width, scale.endPoint - scale.startPoint);
ctx.restore();
}
instead of this.datasets[0].fillColor = gradient;
Fiddle (below line) - http://jsfiddle.net/61vg048r/
Fiddle (whole background) - http://jsfiddle.net/u4Lk7xns/
Related
I am using Phaser.js 3.55 to build a tile-based game. I have a tilemap, a layer, and a player sprite. I want to be able to click on a tile and have the player sprite move to that tile.
However, I'm having trouble getting the correct tile position when the camera is zoomed in or out. I've tried using layer.worldToTileX and layer.worldToTileY to calculate the tile position based on the pointer position, but it gives me different results depending on the zoom level.
Here's my code for the pointerdown event in the update-function:
function update (time, delta) {
controls.update(delta);
zoomLevel = this.cameras.main.zoom;
this.input.on("pointerdown", function (pointer) {
var x = layer.worldToTileX(pointer.x / zoomLevel );
var y = layer.worldToTileY(pointer.y / zoomLevel );
if (map.getTileAt(x, y)) {
var goalX = map.getTileAt(x, y).x * 32 + 16;
var goalY = map.getTileAt(x, y).y * 32 + 16;
this.tweens.add({
targets: player,
x: goalX,
y: goalY,
duration: 1000,
ease: 'Power4',
});
}
}, this);
}
What is the correct way to get the tile position based on the pointer position and the zoom level in Phaser.js?
At first I wrote the pointerdown-event in the create() function, which didn't update the zoom-scale I got by zoomLevel = this.cameras.main.zoom;. I took it to the update-function where zoomLevel would be updated, then trying to divide the current zoomLevel with the pointer.x and pointer.y. I expected the pointer so give me the currently clicked tile. Outcome: It shifted, depending on the zoomLevel.
My other code besides the update()-function
var game = new Phaser.Game(config);
var map;
var layer;
var player;
function preload () {
this.load.tilemapTiledJSON('tilemap', '/img/phaser/map.json');
this.load.image('base_tiles', '/img/phaser/outdoors.png');
this.load.spritesheet('player', '/img/phaser/dude.jpg', { frameWidth: 32, frameHeight: 48 });
}
function create () {
map = this.make.tilemap({ key: 'tilemap' });
const tileset = map.addTilesetImage('outdoors', 'base_tiles');
layer = map.createLayer(0, tileset, 0, 0);
player = this.add.sprite(500, 100, 'player', 4);
var cursors = this.input.keyboard.createCursorKeys();
var controlConfig = {
camera: this.cameras.main,
left: cursors.left,
right: cursors.right,
up: cursors.up,
down: cursors.down,
zoomIn: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.Q),
zoomOut: this.input.keyboard.addKey(Phaser.Input.Keyboard.KeyCodes.E),
acceleration: 0.06,
drag: 0.0005,
maxSpeed: 1.0
};
controls = new Phaser.Cameras.Controls.SmoothedKeyControl(controlConfig);
}
You can get the position with the function getWorldPoint of the camera (link to the documentation), to get the exact position. No calculation needed
Here a demo showcasing this:
(use the Arrow UP/DOWN to zoom in or out, and click to select tile)
document.body.style = 'margin:0;';
var config = {
type: Phaser.AUTO,
width: 5 * 16,
height: 3 * 16,
zoom: 4,
pixelart: true,
scene: {
preload,
create
},
banner: false
};
function preload (){
this.load.image('tiles', 'https://labs.phaser.io/assets/tilemaps/tiles/super-mario.png');
}
function create () {
let infoText = this.add.text(2, 2, )
.setOrigin(0)
.setScale(.5)
.setDepth(100)
.setStyle({fontStyle: 'bold', fontFamily: 'Arial'});
// Load a map from a 2D array of tile indices
let level = [ [ 0, 0, 0, 0, 0],[ 0, 14, 13, 14, 0],[ 0, 0, 0, 0, 0]];
let map = this.make.tilemap({ data: level, tileWidth: 16, tileHeight: 16 });
let tiles = map.addTilesetImage('tiles');
let layer = map.createLayer(0, tiles, 0, 0);
const cam = this.cameras.main;
let tileSelector = this.add.rectangle(0, 0, 16, 16, 0xffffff, .5).setOrigin(0);
this.input.keyboard.on('keyup-UP', function (event) {
cam.zoom+=.25;
});
this.input.keyboard.on('keyup-DOWN', function (event) {
if(cam.zoom > 1){
cam.zoom-=.25;
}
});
this.input.on('pointerdown', ({x, y}) => {
let {x:worldX, y:worldY} = cam.getWorldPoint(x, y)
let tile = layer.getTileAtWorldXY(worldX, worldY);
tileSelector.setPosition(tile.pixelX, tile.pixelY);
infoText.setText(`selected TileID:${tile.index}`);
});
}
new Phaser.Game(config);
<script src="//cdn.jsdelivr.net/npm/phaser/dist/phaser.min.js"></script>
I'm trying to develop an Attack Map when basically like all maps it will show the "Attacks" that are happening over the world but basically it will be randomly as I don't have access to any AV's APIs to get correct data. As of now I only have this SVG map shown on my HTML page:
And I'd want to draw some lines from one country to another to simulate the attacks. I have been searching about this and I have seen two "solutions". One is a JS code that draws a line in HTML:
if (stroke){
ctx.strokeStyle = stroke;
}
if (width){
ctx.lineWidth = width;
}
ctx.beginPath();
ctx.moveTo(...begin);
ctx.lineTo(...end);
ctx.stroke();
}
const canvas = document.querySelector("#line");
if(canvas.getContext){
const ctx = canvas.getContext('2d');
drawLine(ctx, [100, 100], [300, 100], 'green', 5)
}
but as I said this draws a line outside of the SVG map and inside of HTML. I also saw a line tag which can be placed inside of SVG tags but it is static. I want to be dynamic and simulate the attacks. Is there any way I can do this? Thank you for your time!
Even if the SVG element is static, you can add <line /> elements dynamically when the DOM has loaded:
const lines = [
{
from: { x: 0, y: 20 },
to: { x: 30, y: 60 }
},
{
from: { x: 0, y: 20 },
to: { x: 30, y: 60 }
}
];
function addLine (x1, y1, x2, y2) {
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
line.setAttribute("x1", x1);
line.setAttribute("y1", y1);
line.setAttribute("x2", x2);
line.setAttribute("y2", y2);
line.setAttribute("stroke", "white");
document.getElementById("mySvg").appendChild(line);
}
addEventListener("DOMContentLoaded", function () {
for (const line of lines) {
addLine(line.from.x, line.from.y, line.to.x, line.to.y);
}
});
I am currently in the process of upgrading a chart.js line chart from v1 to v2 and came across a hurdle.
The chart data includes customOptions of an array of booleans under the featured property.
var chartData = {
labels: [
"14th Jan",
"15th Jan",
"16th Jan",
"17th Jan",
"18th Jan",
"19th Jan"
],
datasets: [{
label: "Listing Popularity",
data: [
1199,
575,
407,
313,
181,
268
],
}],
customOptions: {
featured: [
false,
false,
false,
false,
false,
true
]
}
}
var ctx = document.getElementById('chartjs-chart').getContext('2d');
var myChart = new Chart(ctx, {
type: 'PPLine',
data: chartData,
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero: true
}
}]
}
}
});
});
I have created an extended version of a line chart that loops over the featured array in the charts customOptions and adds a path if the featured boolean returns true
Chart.controllers.PPLine = Chart.controllers.line.extend({
draw: function () {
var that = this;
var helpers = Chart.helpers;
var xScale = that._xScale;
var ctx = that.chart.ctx;
// Draw chart
Chart.controllers.line.prototype.draw.call(that, arguments);
helpers.each(that.chart.data.customOptions.featured, function (featured, index) {
var meta = that.chart.getDatasetMeta(0);
var linePos = meta.data[index]._model.x,
endLinePos = that.chart.options.elements.point.borderWidth + (meta.data[index + 1] ? meta.data[index + 1]._model.x : linePos);
// Draw featured sections
if (featured) {
var centerX = meta.data[index]._model.x;
var centerY = meta.data[index]._model.y;
var radius = 30;
// Draw boost circle
ctx.save();
ctx.beginPath();
ctx.fillStyle = that.chart.data.customOptions.bumpedColour;
ctx.lineWidth = 10;
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.restore();
// Add text
ctx.save();
ctx.beginPath();
ctx.fillStyle = "#fff";
ctx.font = "10px " + that.chart.config.options.defaultFontFamily;
ctx.fillText("Boost", centerX - (ctx.measureText('Boost').width / 2), centerY + 3);
ctx.fill();
ctx.restore();
}
});
}
});
The issue comes when the last item in the featured array is true and the path gets added to the point, visually, the path gets cut off, so I need to add some padding to the graph to prevent this.
In V1, I was able to do the following inside the extended line chart...
var that = this;
that.scale.xScalePaddingRight = 20;
However, scale is not a property within the v2 object. There is a _xScale property with a paddingRight property, but doing the following does not seem to add the desired padding, so that the path does not get cut off.
var that = this;
that._xScale.paddingRight = 20;
Here is a CodePen with the issue.
I don't want to add padding right in every instance, just when the last point is featured and the path gets drawn.
Any help would be greatly appreciated.
Add it in options:
layout: {
padding: {
right: 20
}
}
I have a custom doughnut ChartJS (with rounded edges), but cannot find the right way to:
make it work under version 2.6.0 (it works just fine under ChartJS 2.0.2, but not under 2.6.0)
add a static red circle under the green with same radius, but with half the lineWidth (like an axis on which the green circle plots) - like this
Here is the Plunker
Chart.defaults.RoundedDoughnut = Chart.helpers.clone(Chart.defaults.doughnut);
Chart.controllers.RoundedDoughnut = Chart.controllers.doughnut.extend({
draw: function (ease) {
var ctx = this.chart.chart.ctx;
var easingDecimal = ease || 1;
Chart.helpers.each(this.getDataset().metaData, function (arc, index) {
arc.transition(easingDecimal).draw();
var vm = arc._view;
var radius = (vm.outerRadius + vm.innerRadius) / 2;
var thickness = (vm.outerRadius - vm.innerRadius) / 2;
var angle = Math.PI - vm.endAngle - Math.PI / 2;
ctx.save();
ctx.fillStyle = vm.backgroundColor;
ctx.translate(vm.x, vm.y);
ctx.beginPath();
ctx.arc(radius * Math.sin(angle), radius * Math.cos(angle), thickness, 0, 2 * Math.PI);
ctx.arc(radius * Math.sin(Math.PI), radius * Math.cos(Math.PI), thickness, 0, 2 * Math.PI);
ctx.closePath();
ctx.fill();
ctx.restore();
});
},
});
var deliveredData = {
labels: [
"Value"
],
datasets: [
{
data: [85, 15],
backgroundColor: [
"#3ec556",
"rgba(0,0,0,0)"
],
borderWidth: [
0, 0
]
}]
};
var deliveredOpt = {
cutoutPercentage: 88,
animation: {
animationRotate: true,
duration: 3000
},
legend: {
display: false
},
tooltips: {
enabled: false
}
};
var chart = new Chart($('#myChart'), {
type: 'RoundedDoughnut',
data: deliveredData,
options: deliveredOpt
});
For question #1 the issue is with the this.getDataset().metaData inside the draw function. It is returning undefined and so the function under Chart.helpers.each doesn't execute
Try this.getMeta().data instead.
EDIT:
For question #2, you can refer to this other Stack Overflow question:
Charts.js: thin donut chart background
Is there a way to extend the current chart.js so that the animation drawing doesn't animate the whole chart, but drawing the chart line by line with animation (ease) ?
You can use the onAnimationComplete callback to change the data and call update()
...
data: [0, 0, 0, 0, 0, 0, 0]
}
]
};
var data2 = [28, 48, 40, 19, 86, 27, 90];
var done = false;
var myLineChart = new Chart(ctx).Line(data, {
animationEasing: 'linear',
onAnimationComplete: function () {
if (!done) {
myLineChart.datasets[1].points.forEach(function (point, i) {
point.value = data2[i];
});
myLineChart.update();
done = true;
}
}
});
It works better with linear easing (otherwise it seems like 2 distinct animations), but if you wanted to, you could write your own easing function to do easeOutQuart in 2 blocks.
Fiddle - http://jsfiddle.net/rnyekq1y/
Nevermind.I've implement it by extending chart.js and override draw() function so that I can animate the line from one dot/point to another until the last dot / point.