i'm using c3js library to render line chart, where I want to group x-axis category labels, as asked in this questions: c3 js: How can I group by Year on the X-axis labels?.
I tried above solution but it is only working only for series type 'timeseries' not for 'category'.
somewhat, I have find the solution using jQuery. following is code:
var xAxis = ['x', "G1 - Team_1", "G2 - Team_1", "G3 - Team_1", "G4 - Team_1", "G1 - Team_2", "G2 - Team_2", "G3 - Team_2", "G4 - Team_2", "G1 - Team_3", "G2 - Team_3", "G3 - Team_3", "G4 - Team_3", "G1 - Team_4", "G2 - Team_4", "G3 - Team_4", "G4 - Team_4", "G1 - Team_5", "G2 - Team_5", "G3 - Team_5", "G4 - Team_5"],
match1 = ['match1', 32, 4, 2, 46, 24, 54, 18, 65, 87, 25, 3, 6, 16, 63, 46, 62, 69, 37, 50, 65],
match2 = ['match2', 68, 60, 95, 65, 59, 67, 56, 69, 38, 74, 59, 83, 53, 72, 16, 12, 64, 93, 51, 93];
var chart = c3.generate({
data: {
x: 'x',
columns: [xAxis, match1, match2],
type: 'spline',
},
line: {
connectNull: true,
},
axis: {
x: {
label: {
text: 'Group(s)'
},
type: 'category',
tick: {
multiline: true,
centered: true
},
height: 50
}
}
});
//this function changes x-axis labels
function changeXAxisLabel() {
var ticks = $('.c3-axis.c3-axis-x').find('.tick');
var ticksData = ticks.map(function(x, ele) {
return {
text: ele.textContent,
ele: ele,
name: ele.textContent.replace('G1', '')
.replace('G2', '')
.replace('G3', '')
.replace('G4', '')
//.replace('-', '').trim()
};
}).get();
var groupedData = {};
ticksData.forEach(function(ele) {
if (!groupedData[ele.name]) groupedData[ele.name] = [];
groupedData[ele.name].push(ele);
});
Object.keys(groupedData).forEach(function(k) {
if (groupedData[k].length < 2) return;
var addOn = Math.ceil(groupedData[k].length / 2);
var translates = [];
groupedData[k].forEach(function(tick, index) {
var $tick = $(tick.ele);
var val = $tick.attr('transform').replace(/[translate,(),]/g, '').split(' ')[0];
translates.push(Number(val));
$tick.find('tspan:eq(0)').text(tick.text.replace(new RegExp(tick.name, 'i'), '').trim());
$tick.find('tspan:not(:eq(0))').remove();
if (index == addOn) {
var cloned = $tick.clone();
//cloned = cloned.find('line').remove();
var pos = (translates[index] + translates[index - 1]) / 2;
cloned.attr('transform', 'translate(' + pos + ',20)');
cloned.find('tspan').text(tick.name);
cloned.insertAfter($tick);
$(cloned).find('line').remove();
}
})
});
}
at first it is displaying desired result:
But after window resize, x-axis labels reverted as it seems chart reinitialize. I tried it with calling changeXAxisLabel on window.resize callback:
$(window).resize(function() {
changeXAxisLabel()
})
But its not running as expected. and it throws an error:
Error: <g> attribute transform: Expected number, "translate(NaN, 0)".
Now I didn't understand, how this can be fixed.
How can I remove this error? or there is other method to solve this problem?
EDIT:
Here is jsfiddle for this code sample: https://jsfiddle.net/abhinaw/1m6v8mwh/
Thanks
Instead of doing in $(window).resize() can you try calling your function as follow :
var chart = c3.generate({
// just add this function, keep remaining code as it is.
onrendered: function () {
changeRoundChartXAxisLabel();
}
});
here is updated version of your JSFiddel.
Removed following code
$(window).resize(function() {
changeRoundChartXAxisLabel()
})
setTimeout(changeRoundChartXAxisLabel, 100);
however i am not able to fix following error
Error: <g> attribute transform: Expected number, "translate(NaN, 0)".
Related
I have looked at various documentation and similar questions on here, but cannot seem to find the particular solution. Apologies if I have missed anything obvious or have repeated this question!
As a bit of background info, I have implemented a graph using the Chart.js plugin and I am trying to pass the required data from a database.
The arrays of data are the following:
loggedIn: [6.3, 2.4, 7.6, 5.4, 9.9, 7.8],
available: [6.7, 2.2, 11.2, 5.5, 10.1, 7.9],
availableForExisting: [7.2, 3.1, 8.2, 5.6, 9.2, 10.2],
My problem is that only one of the line graphs is being update whilst the rest aren't. The full Chart.JS iFrame code is the following:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.4.0/Chart.bundle.min.js"></script>
</head>
<body onLoad="ready()">
<canvas id="myChart" width="250" height="200"></canvas>
<script>
var ctx = document.getElementById("myChart");
const loggedIn = [26, 36, 42, 38, 40, 30, 12];
const available = [34, 44, 33, 24, 25, 28, 25];
const availableForExisting = [16, 13, 25, 33, 40, 33, 45];
const years = [1, 2, 3, 4, 5];
var myChart = new Chart(ctx,
{
type: 'line',
data:
{
labels: years,
datasets: [
{
label: 'Start Balance',
data: loggedIn,//[], //start empty
borderColor:
[
'rgba(164,126,44,1.000)'
],
borderWidth: 1
},
{
label: 'Interest',
data: available,//[], //start empty
borderColor:
[
'rgba(5,99,59,1.000)'
],
borderWidth: 1
},
{
label: 'End Balance',
data: availableForExisting,//[], //start empty
borderColor:
[
'rgba(255,148,112,1.000)'
],
borderWidth: 1
}
]
},
options:
{
tooltips:
{
callbacks:
{
label: function(tooltipItem, data)
{
const title = data.labels[tooltipItem.index];
const dataset = data.datasets[tooltipItem.datasetIndex];
const value = dataset.data[tooltipItem.index];
return title + ': ' + Number(value).toFixed(2) + "%";
}
},
},
onClick: handleClick
}
});
window.onmessage = function(event)
{
if (event.data && Array.isArray(event.data))
{
myChart.data.datasets[0].data = event.data[0];
myChart.data.datasets[1].data = event.data[1];
myChart.data.datasets[2].data = event.data[2];
myChart.update();
}
else
{
console.log("HTML Code Element received a generic message:");
console.log(event.data);
}
};
function handleClick(e)
{
var activeBars = myChart.getElementAtEvent(e);
var value = myChart.config.data.datasets[activeBars[0]._datasetIndex].data[activeBars[0]._index];
var label = activeBars[0]._model.label;
window.parent.postMessage(
{
"type": "click",
"label": label,
"value": value
}, "*");
}
function ready()
{
window.parent.postMessage(
{
"type": "ready"
}, "*");
}
</script>
</body>
</html>
I need to display the data in multiple line graphs, however only one is being updated. The way I am passing the data from the frontend is as follows:
let data =
{
loggedIn: [6.3, 2.4, 7.6, 5.4, 9.9, 7.8],
available: [6.7, 2.2, 11.2, 5.5, 10.1, 7.9],
availableForExisting: [7.2, 3.1, 8.2, 5.6, 9.2, 10.2],
};
$w("#html4").postMessage(data);
$w("#html4").onMessage((event) =>
{
if (event.data.type === 'ready')
{
$w("#html4").postMessage(days[year]);
}
});
I am creating an array of plotly.js shapes which work great. I have added a custom data attribute of 'custType' : '1' or 'custType' : '2' based on the type of shape. The number of each shape type will not be consistent so I need to be able to update the visibility dynamically.
I have tried this... to no avail
var update = [];
for(x=0; x<data.layout.shapes.length; x++){
if(data.layout.shapes[x].custType == '1'){
update.push({'shapes[' + x + '].visible':false})
}
}
Plotly.relayout('main', update);
update has to be an object instead of an array, like
{
"shapes[0].visible": false,
"shapes[1].visible": false
}
See the snippet below.
const trace = {
x: [1, 2, 3, 4],
y: [10, 15, 13, 17],
type: 'scatter'
};
const data = [trace];
const layout = {
shapes: [
{
type: 'rect',
x0: 1,
x1: 2,
y0: 10,
y1: 12,
fillcolor: '#d3d3d3',
line: {
width: 0
}
},
{
type: 'rect',
x0: 3,
x1: 4,
y0: 12,
y1: 14,
fillcolor: '#d3d3d3',
line: {
width: 0
}
}
]
}
Plotly.newPlot('myDiv', data, layout);
function update() {
const update = {};
for(let i = 0; i < layout.shapes.length; i++){
update['shapes[' + i + '].visible'] = false;
}
Plotly.relayout('myDiv', update);
}
<head>
<!-- Load plotly.js into the DOM -->
<script src='https://cdn.plot.ly/plotly-latest.min.js'></script>
</head>
<body>
<div id='myDiv'><!-- Plotly chart will be drawn inside this DIV --></div>
<button type="button" onClick="update()">Update</button>
</body>
I have a problem with marking area: i need to be able to select a bar area based on xAxis, for example from 0 to 1, from 1 to 2, etc. But when i try to provide options for bar like
[{xAxis: 0, itemStyle: {color: red}},{xAxis: 1}]
it marks an area from a middle of xAxis area with an index of 0 to a middle of xAxis area with an index of 1. Is there a way to make it mark from start of an area to an end. Currently i managed to do so only with x option in pixels:
https://codesandbox.io/s/react-echart-markarea-ksj31?file=/src/index.js:714-726
Is there a better way to do it?
I can't imagine a method that would cover your requirements. It seems there is no such but nothing prevents to do it ourselves, see below.
When call function with join = true markedArea will calc as range from first to last.
calcMarkAreaByBarIndex(myChart, join = true, [4, 9])
When call function with join = false markedArea will calc for each bar.
calcMarkAreaByBarIndex(myChart, join = true, [4, 5, 6, 9])
var myChart = echarts.init(document.getElementById('main'));
var option = {
tooltip: {},
xAxis: {
data: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
},
yAxis: {},
series: [
{
id: 'myBar',
name: 'Series',
type: 'bar',
data: [11, 11, 11, 11, 12, 13, 110, 123, 113, 134, 93, 109],
markArea: {
data: [
[{x: 184},{x: 216}],
[{x: 224},{x: 256}],
]
},
},
]
};
myChart.setOption(option);
function calcMarkAreaByBarIndex(chartInstance, join = false, barIdx){
var series = chartInstance.getModel().getSeriesByType('bar');
var seriesData = series.map((s, idx) => s.getData())[0];
var barNum = seriesData.count();
var barCoors = [];
var layout = idx => seriesData.getItemLayout(idx);
for(var i = 0; i < barNum; i++){
if(!barIdx.includes(i)) continue;
barCoors.push([
{ x: layout(i).x },
{ x: layout(i).x + layout(i).width },
])
}
if(join){
return [
[
{ x: barCoors[0][0].x },
{ x: barCoors[barCoors.length - 1][1].x }
]
]
} else {
return barCoors
}
}
var markedAreas = {
series: {
id: 'myBar',
markArea: {
data: calcMarkAreaByBarIndex(myChart, join = true, [4,9])
}
}
};
myChart.setOption(markedAreas);
<script src="https://cdn.jsdelivr.net/npm/echarts#4.7.0/dist/echarts.min.js"></script>
<div id="main" style="width: 600px;height:400px;"></div>
I found a solution, that worked for me:
Basically, you need to manually set yAxis's max props, add another xAxis, make it invisible, create a custom series with type 'bar' and set xAxisIndex to 1:
data: [maxYaxisValue,maxYaxisValue...], //length === xAxis.data.length
type: 'bar',
barWidth: '100%',
color: transparent,
xAxisIndex: 1,
And style a bar by index with background color and borderWidth
You can check the working example here
https://codesandbox.io/s/react-echart-markarea-m0mgq?file=/src/index.js
Currently, I'm showing a max point in the line chart. But I want to change dataMax to top 5 max value points in chart.How can I achieve this in Highcharts?
var defaultData = 'urlto.csv';
var urlInput = document.getElementById('fetchURL');
var pollingCheckbox = document.getElementById('enablePolling');
var pollingInput = document.getElementById('pollingTime');
function createChart() {
Highcharts.chart('closed5', {
chart: {
type: 'area',
zoomType: 'x'
},
plotOptions: {
series: {
dataLabels: {
enabled: true,
style: {},
formatter: function() {
if (this.y === this.series.dataMax) {
return this.y;
}
}
}
}
},
title: {
text: 'Chart for charting'
},
data: {
csvURL: urlInput.value,
enablePolling: pollingCheckbox.checked === true,
dataRefreshRate: parseInt(pollingInput.value, 10)
}
});
if (pollingInput.value < 1 || !pollingInput.value) {
pollingInput.value = 1;
}
}
urlInput.value = defaultData;
// We recreate instead of using chart update to make sure the loaded CSV
// and such is completely gone.
pollingCheckbox.onchange = urlInput.onchange = pollingInput.onchange = createChart;
// Create the chart
createChart();
As #ewolden rightly noticed, you can sort your data and show only the five highest values:
var data = [11, 22, 33, 44, 55, 66, 15, 25, 35, 45, 55, 65],
sortedData = data.slice().sort(function(a, b){
return b - a
});
Highcharts.chart('container', {
series: [{
data: data,
dataLabels: {
enabled: true,
formatter: function() {
if (sortedData.indexOf(this.y) < 5) {
return this.y;
}
}
}
}]
});
Live demo: http://jsfiddle.net/BlackLabel/xkf2w5tb/
API: https://api.highcharts.com/highmaps/series.mapbubble.dataLabels.formatter
As far as I know formatter callback is the way to format the data labels. If you want to show the top N points you should sort the data in a new array and pull the top 5 values. This is an example of how to clone and sort the array and extract the top 5 elements in the formatter call.
let data = [32, 10, 20, 99, 30, 54, 85, 56, 11, 26, 15, 45, 55, 65];
//Copy the array
let temp = data.slice();
// Sort the temp array in descending order
temp.sort((a, b) => b - a);
Highcharts.chart('closed5', {
chart: {
type: 'area',
zoomType: 'x'
},
title: {
text: 'Chart for charting'
},
series: [{
data: data,
dataLabels: {
enabled: true,
formatter: function() {
if (temp.indexOf(this.y) < 5) {
return this.y;
}
},
},
}]
});
<script src="https://code.highcharts.com/highcharts.js"></script>
<div id="closed5"></div>
I am making a radar chart with chartjs. The labels on the side of the radar I have given numbers (so the dataset Labels are numbers, 1 to 10). In the tooltip I do not want to these numbers, but a text linked to it (the text the number refers to). These are in a difrent array.
Is there a way to do this?
At the moment I have the following code:
$scope.drawGraph = function() {
radarOptions = {
scaleShowLabels : false,
scaleFontSize: 10,
pointLabelFontSize : 14,
scaleBeginAtZero: true,
showScale: true,
scaleOverride: true,
scaleSteps: 10,
scaleStepWidth: 10,
scaleStartValue: 0,
showTooltips: true,
customTooltips: false,
tooltipTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value + '%'%>"
};
var tempComp = []
for (var i = 0; i < $scope.competencesDB.length; i++) {
tempComp.push({
number: i + 1,
competence: $scope.competencesDB[i]
})
}
var tempLabels = [];
var tempData = [];
var tempName = [];
for (var i = 0; i < tempComp.length; i++) {
// console.log($scope.competencesDB[i])
tempName.push(tempComp[i].competence.name)
tempLabels.push(tempComp[i].number);
if (tempComp[i].competence.progress.length === 0) {
tempData.push(0)
} else{
tempData.push(tempComp[i].competence.progress[tempComp[i].competence.progress.length -1].value);
}
}
radarData = {
//names : tempName
labels : tempLabels,
datasets : [
{
label: tempName,
fillColor : "rgba(173, 209, 244, 0.54)",
strokeColor : "rgba(49, 137, 225, 0.94)",
data : tempData
},
]
};
//Get the context of the Radar Chart canvas element we want to select
var ctx = document.getElementById("radarChart").getContext("2d");
// Create the Radar Chart
var myRadarChart = new Chart(ctx).Radar(radarData, radarOptions);
}
I think I need to change something in the following part:
tooltipTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value + '%'%>"
But I don't know how this part of the code works. Can I but some kind of forloop in this to loop through the second datasetLabel part?
Thanks!
You can do this using a function instead of a template string. As you guessed you need to figure out the name from the number.
Here is a generic sample
var numberNames = [
{ number: 1, name: "Eating" },
{ number: 2, name: "Drinking" },
{ number: 3, name: "Sleeping" },
{ number: 4, name: "Designing" },
{ number: 8, name: "Coding" },
{ number: 9, name: "Cycling" },
{ number: 10, name: "Running" }
];
var data = {
labels: numberNames.map(function(e) { return e.number }),
datasets: [
{
label: "My First dataset",
fillColor: "rgba(220,220,220,0.2)",
strokeColor: "rgba(220,220,220,1)",
pointColor: "rgba(220,220,220,1)",
pointStrokeColor: "#fff",
pointHighlightFill: "#fff",
pointHighlightStroke: "rgba(220,220,220,1)",
data: [65, 59, 9, 10, 56, 55, 40]
}
]
};
var ctx = document.getElementById("myChart").getContext("2d");
var myChart = new Chart(ctx).Radar(data, {
tooltipTemplate: function (valueObject) {
return numberNames.filter(function (e) { return e.number === valueObject.label })[0].name + ': ' + valueObject.value;
}
});
I used numberNames but you should be able to replace that with tempComp (after adjusting the labels property and the tooltipTemplate function body slightly).
Fiddle - http://jsfiddle.net/80wdhbwo/