How to use ChartJs in Phaser Game - javascript

I want to show stats in Phaser with some charts, and I'm using chart.js. I have a problem and one question
problem: I'm trying to load a generated Chart.js base64Image in phaser, but it is not showing. Am I missing something?
question: Is it somehow possible to draw the chart.js on the phaser canvas? Because I would like to have the chart animation in the game, if possible.
Here is a mini Demo, showing what I tried:
(with a demo chart generation)
document.body.style = 'margin:0;';
// Chart Generation Helper function for DEMO
function generateChart(callback){
let data = {
// labels: ['Highscore'],
datasets: [{
data: [{y: 100, x: '01.01'}, {y: 80, x: '04.01'}, {y: 130, x: '05.01'}, {y: 110, x: '06.01'}, {y: 199, x: '08.01'},],
backgroundColor: ['#6BFF6B'],
borderColor: ['#6BFF6B'],
}],
};
const chartConfig = {
type: 'line',
data: data,
options: {
maintainAspectRatio: false,
plugins: { legend: { display:false } },
scales: {
x: {
title: {display: false},
grid: { color: '#6BFF6B' },
ticks: { color: '#6BFF6B' },
},
y: {
title: {display: false},
grid: { color: '#6BFF6B' },
ticks: { color: '#6BFF6B' },
}
},
animation: {
onComplete: () => {
callback(chart.toBase64Image());
}
}
}
};
let chart = new Chart(
document.getElementById('chart'),
chartConfig
);
}
var config = {
type: Phaser.AUTO,
width: 536,
height: 190,
scene: {
create
}
};
function create () {
generateChart(base64Image => {
this.textures.addBase64('chart', base64Image);
this.add.image(10, 10, 'chart').setOrigin(0);
});
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<div id="chart-wrapper" class="chart" style="height:170px; width:350px;">
<canvas id="chart" ></canvas>
</div>
<div id="phaser-example"></div>

Thanks to #DerekLawrence comments, the best solution with animation is to use phaser Phaser.GameObjects.DOMElement (link to documentation).
here the demo working:
document.body.style = 'margin:0;';
// Chart Generation Helper function for DEMO
function generateChart(callback){
let data = {
// labels: ['Highscore'],
datasets: [{
data: [{y: 100, x: '01.01'}, {y: 80, x: '04.01'}, {y: 130, x: '05.01'}, {y: 110, x: '06.01'}, {y: 199, x: '08.01'},],
backgroundColor: ['#6BFF6B'],
borderColor: ['#6BFF6B'],
}],
};
const chartConfig = {
type: 'line',
data: data,
options: {
maintainAspectRatio: false,
plugins: { legend: { display:false } },
scales: {
x: {
title: {display: false},
grid: { color: '#6BFF6B' },
ticks: { color: '#6BFF6B' },
},
y: {
title: {display: false},
grid: { color: '#6BFF6B' },
ticks: { color: '#6BFF6B' },
}
},
animation: {
onComplete: () => {
callback(chart.toBase64Image());
}
}
}
};
let chart = new Chart(
document.getElementById('chart'),
chartConfig
);
}
var config = {
type: Phaser.AUTO,
width: 536,
height: 190,
scene: {
preload,
create
}
};
function preload(){
generateChart();
}
function create () {
let chart = this.add.dom(10, 10, '#chart-wrapper').setOrigin(0);
// chart.setInteractive().on('pointerdown', function (){console.info(arguments)})
}
new Phaser.Game(config);
<script src="https://cdn.jsdelivr.net/npm/phaser#3.55.2/dist/phaser.js"></script>
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<div id="chart-wrapper" class="chart" style="height:170px; width:350px;">
<canvas id="chart" ></canvas>
</div>
<div id="phaser-example"></div>
btw.: I found the reason why the base64 image was not showing up, I failed to wait for the image to load (the event addTexture).
function create(){
...
generateChart(base64Image => {
this.textures.addBase64('chart', base64Image);
});
// event triggered after texture is loaded
this.textures.once('addtexture', function () {
this.add.image(10, 10, 'chart').setOrigin(0);
}, this);
}

Related

How to remove all gridlines, border and show only ticks on a chartjs horizontal bar chart

Following on from the answer in this post
I have the following chart which can also be found here
const d0 = new Date("2023-02-15T00:00:00.721Z").getTime()
const d1 = new Date("2023-02-15T01:00:00.721Z").getTime()
const d2 = new Date("2023-02-15T02:30:00.721Z").getTime()
const d3 = new Date("2023-02-15T03:20:00.721Z").getTime()
const d4 = new Date("2023-02-15T05:05:00.721Z").getTime()
let values = [d0, d1, d2, d3, d4];
let data = {
labels: [''],
datasets: [{
label: 'up',
axis: 'y',
data: [d1],
backgroundColor: 'red',
},{
label: 'down',
axis: 'y',
data: [d2],
backgroundColor: 'yellow',
},{
label: 'out',
axis: 'y',
data: [d3],
backgroundColor: 'green',
},{
label: 'up',
axis: 'y',
data: [d4],
backgroundColor: 'red',
}
]
};
const config = {
data,
type: 'bar',
options:{
elements: {
bar: {
borderWidth: 0
}
},
ticks: {
display: true
},
interaction: {
mode: 'dataset'
},
tooltip: {
mode: 'dataset'
},
hover: {
mode: 'dataset'
},
onClick: function (e) {
// debugger;
var activePointLabel =
this.getElementsAtEventForMode(e, 'dataset', { intersect: true }, false)
alert(activePointLabel[0].datasetIndex);
}
,
plugins: {
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
grid: {
display: true
},
min: d0,
ticks: {
callback: function(value, index, ticks) {
return moment(value).format('HH:mm');
}
},
// afterBuildTicks: axis => axis.ticks = values.map(v => ({ value: v }))
},
y: {
grid: {
display: false
},
stacked: true
},
}
}};
new Chart(document.getElementById("chart"), config);
This produces this...
]1
I would like to get rid of all the grids lines , except the ticks, but if I set the gird displays false the tick also disappear, and it also leave a border around the chart, etg something like
Is there a way to do this?
Just add border: {display: false}, to the scales configuration. (here is the link to the documentation)
...
scales: {
x: {
border: {
display: false,
},
...
},
...
}
...
Update added full running example:
const d0 = moment.duration('07:00:00').asMinutes();
const d1 = moment.duration('09:00:00').asMinutes();
const d2 = moment.duration('10:45:00').asMinutes();
const d3 = moment.duration('17:35:00').asMinutes();
const d4 = moment.duration('19:00:00').asMinutes();
let values = [d0, d1, d2, d3, d4];
let data = {
labels: [''],
datasets: [{
label: 'up',
axis: 'y',
data: [d1],
backgroundColor: 'red',
},{
label: 'down',
axis: 'y',
data: [d2],
backgroundColor: 'yellow',
},{
label: 'out',
axis: 'y',
data: [d3],
backgroundColor: 'green',
},{
label: 'up',
axis: 'y',
data: [d4],
backgroundColor: 'red',
}
]
};
const config = {
data,
type: 'bar',
options:{
plugins: {
tooltip: {
mode: 'dataset',
callbacks: {
label: function(item){
return moment().startOf('day').add({ minute: item.raw}).format('HH:mm');
}
}
},
legend: {
display: false,
},
title: {
display: false,
},
},
indexAxis: 'y',
responsive: true,
maintainAspectRatio: false,
scales: {
x: {
min: d0,
border: { display: false },
ticks: {
callback: function(value, index, ticks) {
return moment().startOf('day').add({ minute: value}).format('HH:mm');
}
},
afterBuildTicks: axis => axis.ticks = values.map(v => ({ value: v }))
},
y: {
stacked: true,
grid: { display: false },
},
}
}};
new Chart(document.getElementById("chart"), config);
<script src="//cdn.jsdelivr.net/npm/chart.js"></script>
<script src="//cdn.jsdelivr.net/npm/moment#^2"></script>
<script src="//cdn.jsdelivr.net/npm/chartjs-adapter-moment#^1"></script>
<div class="chart" style="height:84px; width:350px;">
<canvas id="chart" ></canvas>
</div>
use tickColor (see below config) for v 3.8.2
scales: {
x: {
...
grid: {
display: true,
drawTicks: true, //show ticks
borderColor: "transparent", //horizontal line color above ticks (x-axis)
color: "transparent", //grid lines color
tickColor: "#868e96" //ticks color (little line above points)
},
},
...
}

How to add conditional fill color in Chart.js area chart?

I am working on project that requires me to design a chart as the following diagram:
I am using chart.js and react to make the distribution plot. But I am not able to figure out how to add fill color of the area chart between a particular range of values of the x-variable. So far, I am able to achieve this:
I have used the following code to make the area chart as a react component:
const data = {
labels: {DataLabel},
datasets: [
{
label: 'Blood Sugar Measure',
data: {Data},
fill: true,
backgroundColor: ["#E5E5E5", "#E5E5E5", "#B4EDB3", "#B4EDB3", "#E5E5E5"],
pointBorderColor: "#8884d8",
pointBorderWidth: 2,
pointRadius: 3,
tension: 0.4
},
],
};
const options = {
plugins: { legend: { display: false } },
layout: { padding: { bottom: 0.1 } },
scales: {
y: {
display : false,
beginAtZero: true,
grid: {
display: false
},
ticks: {
color: "#000000",
font: {
size: 18
}
}
},
x: {
beginAtZero: true,
grid: {
display: false
},
ticks: {
color: "#000000",
font: {
size: 10
},
min: 0
}
}
},
};
export const DistChart = () => {
return (<div className="App">
<Line data={data} options={options} />
</div>);
};
I would need some help to apply the conditional fill color based on the x-axis variable.
You can use multiple datasets with object data so you can specify starting and end points. After this you can manipulate the legend so it looks like its only a single dataset:
var options = {
type: 'line',
data: {
labels: ["Red", "Blue", "Yellow", "Green", "Purple", "Orange"],
datasets: [{
label: 'Blood Sugar Measure extra',
data: [{
x: "Red",
y: 5
}, {
x: "Blue",
y: 8
}],
backgroundColor: 'lightGray',
fill: true
},
{
label: 'Blood Sugar Measure',
data: [{
x: "Blue",
y: 8
}, {
x: "Yellow",
y: 12
}, {
x: "Green",
y: 10
}],
backgroundColor: 'pink',
fill: true
},
{
label: 'Blood Sugar Measure extra',
data: [{
x: "Green",
y: 10
}, {
x: "Purple",
y: 12
}, {
x: "Orange",
y: 10
}],
backgroundColor: 'lightGray',
fill: true
}
]
},
options: {
plugins: {
legend: {
onClick: (e, legendItem, legend) => {
const ci = legend.chart;
const currentlyHidden = ci.getDatasetMeta(0).hidden;
for (let i = 0; i < ci.data.datasets.length; i++) {
ci.setDatasetVisibility(i, currentlyHidden)
}
ci.update();
},
labels: {
filter: (e) => (!e.text.includes('extra'))
}
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.7.1/chart.js"></script>
</body>
I was able to make some progress on the design that I wanted, so thought of sharing my answer to benefit others. I was able to fill based on x-axis data values and get the following chart:
I had to use the segment property inside data configs to achieve this, with the help of a function. This is the modified code:
const highlightRegion = (ctx, value) => {
if (ctx.p0DataIndex > boundary_val1 && ctx.p0DataIndex < boundary_val2) {
return "#B4EDB3";
}
return "#E5E5E5";
};
const bgColor = ctx => highlightRegion(ctx, "#B4EDB3");
const data = {
labels: x_values,
datasets: [
{
label: 'Count',
data: [0, 20, 40, 80, 150, 80, 30, 0],
pointRadius: 0,
fill: true,
tension: 0.4,
segment: {
backgroundColor: bgColor,
borderColor: bgColor,
},
},
],
};
Special thanks goes to this youtube series from where I was able to find my answer: https://www.youtube.com/watch?v=st2O-pvhWM4.
I will keep this post open, in case if there is a better solution as I think my solution is not absolutely correct.

Add spaces/margin between bars - Chart.js

How can I add white spaces/margin between bars with Chart.js 3.0 ? I didn't find anything to process like the image
What I want
My code looks like this and makes this :
What I have
datasets: [{
categoryPercentage: 1.0,
barPercentage: 0.8,
}]
},
options: {
onHover() {},
indexAxis: 'y',
maintainAspectRatio: false,
}
full code here : https://codepen.io/mateofaivre/pen/zYdYgmB
You should set the categoryPercentage to lower value like 0.8 and barPercentage to 1.
Graphical info about categoryPercentage vs barPercentage:
// categoryPercentage: 1.0
// barPercentage: 1.0
Bar: | 1.0 | 1.0 |
Category: | 1.0 |
Sample: |===========|
// categoryPercentage: 1.0
// barPercentage: 0.5
Bar: |.5| |.5|
Category: | 1.0 |
Sample: |==============|
// categoryPercentage: 0.5
// barPercentage: 1.0
Bar: |1.0||1.0|
Category: | .5 |
Sample: |==================|
EDIT:
You can get the bar width from the metasets of the chart and use that to draw it on the chart like so:
const plugin = {
id: 'background',
beforeDraw: (chart, args, opts) => {
if (!opts.color) {
return;
}
const {
ctx,
chartArea,
_metasets
} = chart;
ctx.fillStyle = opts.color;
_metasets.forEach(meta => {
meta.data.forEach(data => {
if (data.horizontal) {
ctx.fillRect(chartArea.left, (data.y - (data.height / 2)), chartArea.width, data.height)
} else {
ctx.fillRect((data.x - (data.width / 2)), chartArea.top, data.width, chartArea.height)
}
})
});
}
}
Chart.register(plugin);
var colors = [];
for (var i = 0; i < 5; i++) {
colors.push('#5671DB');
}
var config = {
type: 'bar',
data: {
labels: ['Commerce, vente', 'Transport', 'Bureautique', 'Accueil', 'Santé', 'Secrétariat', 'Nettoyage', 'Sécurité', 'Mécanique', 'Agro-alimentaire'],
datasets: [{
data: [23.8, 17.7, 13, 9.5, 7.8, 7, 5.5, 5, 4.5, 3.5],
backgroundColor: colors,
hoverBackgroundColor: colors,
borderColor: colors,
}],
},
options: {
onHover() {},
indexAxis: 'y',
barPercentage: 0.8,
//barThickness: 60,
// maxBarThickness: 60,
categoryPercentage: 1.0,
maintainAspectRatio: true,
responsive: true,
plugins: {
background: {
color: '#CDECEF'
},
title: {
display: false,
text: "Les 10 principaux domaines d'emploi",
align: 'start',
fullSize: true,
color: '#324488',
font: {
size: 24,
family: 'Arial',
}
},
legend: {
display: false
},
tooltip: {
backgroundColor: 'rgba(255,255,255,0)',
displayColors: false,
titleFont: {
size: 0,
},
titleMarginBottom: 0,
titleSpacing: 0,
bodyFont: {
size: 25,
weight: 700
},
xAlign: 'right',
callbacks: {
label: (item) => (`${item.parsed.x} %`),
},
},
},
scales: {
y: {
beginAtZero: true,
ticks: {
color: "#34478B",
font: {
size: 18,
},
stepSize: 1,
beginAtZero: true
},
},
x: {
ticks: {
color: "#25C8C9",
font: {
size: 14
},
stepSize: 1,
beginAtZero: true
},
}
}
},
};
var ctx = document.getElementById('myChart').getContext('2d');
new Chart(ctx, config);
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<canvas id="myChart"></canvas>
codePen
If reducing the bar size is not a problem you could add barPercentage to your options.
Like this:
const config = {
type: 'bar',
data,
options: {
indexAxis: 'y',
barPercentage: 0.8
}
};
Bar Chart | Charts.js

chartjs-plugin-streaming: onRefresh() callback isn't working

Hello I'm new in javascript world; I'm trying to display random numbers in real time with Chart.js and chartjs-plugin-streaming starting from a tutorial code which I started to modify for my scope.
const Chart= require ("chart.js");
const chartStreaming = require('chartjs-plugin-streaming');
const boxWhiteColor = "#2043CE";
const pressure_graph = document.getElementById('pressureGraph').getContext('2d');
Chart.register(chartStreaming);
let pressure_graph_config = {
type: 'line',
data: {
datasets: [{
label: 'Pressure',
data: [],
borderColor: boxWhiteColor,
fill: false
}]
},
options: {
title: {
display: true,
text: 'PRESSURE',
fontColor: boxWhiteColor,
fontSize: 30
},
scales: {
xAxes: [{
gridLines: {
display: true,
drawBorder: true,
color: boxWhiteColor,
lineWidth: 5},
ticks: {
fontColor: boxWhiteColor,
display: false
},
type: 'realtime',
realtime: {
duration: 10000,
refresh: 100, // plot at 10 Hz
delay:200,
pause: false, // chart is not paused
ttl: undefined, // data will be automatically deleted as it disappears off the chart
frameRate: 100, // data points are drawn 100 times every second
onRefresh: chart => {
console.log(trocarP.data.datasets.data);
chart.config.data.datasets.forEach(function(dataset) {
chart.data.dataset[0].data.push({
x: Date.now(),
y: Math.random() //pressure16bits[pressure16bits.length-1]
});
});
}
}
}],
yAxes: [{
scaleLabel: {
display: true,
labelString: '[mmHg]', fontColor: boxWhiteColor, fontSize: 30, fontStyle: 900,
},
ticks: {
fontColor: boxWhiteColor,
fontSize: 25,
fontStyle: 700,
maxTicksLimit: 5,
min: 0,
max: 40,
},
gridLines: {display: true, drawBorder: true, color: boxWhiteColor, lineWidth: 5},
}]
},
elements: {
point: {radius: 0},
},
legend: { display: false}
}
}
trocarP = new Chart (pressure_graph, pressure_graph_config);
The problem is the graph is being created on a canvas via the .html file, but then it doesn't display anything; trying to debug the code I found out that the console.log() I placed inside the onRefresh callback is not printing anything, so I'm assuming the callback isn't working. Any clue on what's happening?
Screenshot of the graph
Edit: I noticed that also Y axis label has not been displayed. I don't get what's wrong with this code.
You are using v2 syntax while using v3 of the lib this wont work as there are several breaking changes, see migration guide for all of them.
For example, way of defining scales has changed, you need an adapter for dates and more.
working basic v3 example:
var options = {
type: 'line',
data: {
datasets: [{
label: '# of Votes',
data: [],
borderColor: 'pink'
}]
},
options: {
scales: {
x: {
type: 'realtime',
realtime: {
duration: 20000,
refresh: 100,
delay: 200,
onRefresh: chart => {
const now = Date.now();
chart.data.datasets.forEach(dataset => {
dataset.data.push({
x: now,
y: Math.random()
});
});
}
}
}
}
}
}
var ctx = document.getElementById('chartJSContainer').getContext('2d');
new Chart(ctx, options);
<body>
<canvas id="chartJSContainer" width="600" height="400"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.5.1/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-date-fns/dist/chartjs-adapter-date-fns.bundle.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/chartjs-plugin-streaming/2.0.0/chartjs-plugin-streaming.js"></script>
</body>

how to implement dragging of graphic elements in echarts graph chart with vue

Key Words: vue echarts graph drag
I want to make a graph chart with draggable nodes.
I use graphic to create circles with the dragging function on nodes.
there are two bugs:
When I move one node, the other nodes will move too.
Although I update the option immediately when the data change, but the chart always change a few seconds later.
You can init a vue-project with vue-cli3 and npm install echarts.
Then you can copy the codes to instead of the App.vue.
<template>
<div id="app">
<div id="charts" ref="chart" style="height:70vh;"></div>
</div>
</template>
<script>
import echarts from 'echarts'
export default {
data () {
return {
myChart: null,
option: {},
graph: {
nodes: [
{
name: 'Node1',
x: 300,
y: 300
},
{
name: 'Node2',
x: 800,
y: 300
},
{
name: 'Node3',
x: 550,
y: 100
},
{
name: 'Node4',
x: 550,
y: 500
}
],
links: [
{
source: 0,
target: 1,
symbolSize: [5, 20],
label: {
normal: {
show: true
}
},
lineStyle: {
normal: {
width: 5,
curveness: 0.2
}
}
},
{
source: 'Node2',
target: 'Node1',
label: {
normal: {
show: true
}
},
lineStyle: {
normal: { curveness: 0.2 }
}
},
{
source: 'Node1',
target: 'Node3'
},
{
source: 'Node2',
target: 'Node3'
},
{
source: 'Node2',
target: 'Node4'
},
{
source: 'Node1',
target: 'Node4'
}
]
}
}
},
mounted () {
this.initChart()
this.renderChart()
},
methods: {
initChart () {
this.myChart = echarts.init(document.getElementById('charts'));
},
renderChart() {
this.myChart.showLoading();
this.formatOption();
this.initDrag();
},
formatOption () {
this.option = {
title: {
text: 'Graph 简单示例'
},
tooltip: {},
animationDurationUpdate: 1500,
animationEasingUpdate: 'quinticInOut',
series : [
{
type: 'graph',
layout: 'none',
symbolSize: 50,
roam: 'scale',
label: {
normal: {
show: true
}
},
edgeSymbol: ['circle', 'arrow'],
edgeSymbolSize: [4, 10],
edgeLabel: {
normal: {
textStyle: {
fontSize: 20
}
}
},
nodes: this.graph.nodes,
links: this.graph.links,
lineStyle: {
normal: {
opacity: 0.9,
width: 2,
curveness: 0
}
}
}
]
};
this.myChart.hideLoading();
this.myChart.setOption(this.option, true);
},
initDrag () {
this.option.graphic = echarts.util.map(this.graph.nodes, (item, dataIndex) => {
return {
type: 'circle',
position: this.myChart.convertToPixel({seriesIndex: 0}, [item.x, item.y]),
shape: {
r: 10
},
// invisible: true,
style: {
fill: 'blue'
},
draggable: true,
ondrag: echarts.util.curry(this.onPointDragging, dataIndex),
z: 100
}
});
this.myChart.setOption(this.option);
window.addEventListener('resize', this.updatePosition);
},
updatePosition () {
this.myChart.setOption({
graphic: echarts.util.map(this.graph.nodes, (item, dataIndex) => {
return {
position: this.myChart.convertToPixel({seriesIndex: 0}, [item.x, item.y])
};
})
});
},
onPointDragging (dataIndex, event) {
let pos = this.myChart.convertFromPixel({seriesIndex: 0}, [event.offsetX, event.offsetY]) // 将graphic的像素坐标转化为坐标系坐标
this.graph.nodes[dataIndex].x = pos[0] // 将新的坐标系坐标赋值给node
this.graph.nodes[dataIndex].y = pos[1]
this.myChart.setOption({
series: {
nodes: this.graph.nodes
}
})
this.updatePosition()
}
}
}
</script>
<style scoped>
#charts {
flex: 1;
border: 1px solid;
box-shadow: 0 0 20px rgba(18,208,253,.5);
}
</style>

Categories