I want to make my pie-chart interactive by allowing the user to double-click on a slice to drill down. I believe that the way to do this is to create an onclick handler on the canvas, and use getSegmentsAtEvent() to determine which slice was clicked.
The segment data returned by the call to getSegmentsAtEvent() is possibly ambiguous though. Here is a sample of the returned data:
[{
"circumference": 4.1887902047863905,
"endAngle": 8.901179185171081,
"fillColor": "#FF5A5E",
"highlightColor": "#FF5A5E",
"innerRadius": 0,
"label": "Red",
"outerRadius": 99.5,
"showStroke": true,
"startAngle": 4.71238898038469,
"strokeColor": "#fff",
"strokeWidth": 2,
"value": 300
}]
Of those fields, only value, fillColor, highlightColor, and label are supplied by me, and none of them are necessarily unique. I could ensure that label is unique, but that might make it less readable for humans.
I have tried adding an additional property (e.g. "id") into the data I pass into Pie(), but it is stripped out when I get the segment data back from this call. Is there a way to add a property to each segment which I can use to positively identify them, without overloading the label field?
You would need to either override the pie chart or create a new chart type that inherits from pie.
Here is an example of a new chart type, i have passed a new attribute called id and then the only thing that needs to be different in this chart is that when the data is being added this id needs to passed to the chart
Chart.types.Pie.extend({
name: "PieUnique",
addData: function (segment, atIndex, silent) {
var index = atIndex || this.segments.length;
this.segments.splice(index, 0, new this.SegmentArc({
value: segment.value,
outerRadius: (this.options.animateScale) ? 0 : this.outerRadius,
innerRadius: (this.options.animateScale) ? 0 : (this.outerRadius / 100) * this.options.percentageInnerCutout,
fillColor: segment.color,
highlightColor: segment.highlight || segment.color,
showStroke: this.options.segmentShowStroke,
strokeWidth: this.options.segmentStrokeWidth,
strokeColor: this.options.segmentStrokeColor,
startAngle: Math.PI * this.options.startAngle,
circumference: (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
label: segment.label,
//add option passed
id: segment.id
}));
if (!silent) {
this.reflow();
this.update();
}
},
});
var pieData = [{
value: 300,
color: "#F7464A",
highlight: "#FF5A5E",
label: "Red",
id: "1-upi"
}, {
value: 50,
color: "#46BFBD",
highlight: "#5AD3D1",
label: "Green",
id: "2-upi"
}, {
value: 100,
color: "#FDB45C",
highlight: "#FFC870",
label: "Yellow",
id: "3-upi"
}, {
value: 40,
color: "#949FB1",
highlight: "#A8B3C5",
label: "Grey",
id: "4-upi"
}, {
value: 120,
color: "#4D5360",
highlight: "#616774",
label: "Dark Grey",
id: "5-upi"
}
];
var ctx = document.getElementById("chart-area").getContext("2d");
window.myPie = new Chart(ctx).PieUnique(pieData);
document.getElementById("chart-area").onclick = function (evt) {
var activePoints = window.myPie.getSegmentsAtEvent(evt);
//you can now access the id at activePoints[0].id
console.log(activePoints);
};
<script src="https://raw.githack.com/leighquince/Chart.js/master/Chart.js"></script>
<canvas id="chart-area" width="400"></canvas>
To get an slice that was clicked on, getElementAtEvent can be used. Worked for me!
stackedBar(datasets) {
var c = $("#canvas-bar");
var ctx = c[0].getContext("2d");
let __this = this;
this.chart = new Chart(ctx, {
type: 'bar',
data: {
labels: ["something"],
datasets: datasets,
},
options: {
onClick: function (e, item) {
__this.onClickAction(e, item);
},
title: {},
legend: {},
scales: {}
}
});
}
onClickAction(event, elements) {
if (elements && elements.length) {
let selected = this.chart.getElementAtEvent(event)[0];
let sliceIndex = selected['_datasetIndex'];
}
}
Related
I am trying to figure out how to render a section of each column in a simple, single series, column chart with multiple colors. Using series.zones:
series: [
{
name: "Mod",
colorByPoint: true,
data: seriesData,
zones: [
{ value: 101, color: '#1D681B' },
{ value: 121, color: '#ECC518' },
{ color: '#D50D0D' }
]
}
]
I can get each column to be a different color based on the zone that the y value is within.
In my example above the zone are:
0 through 100 should be green
101 to 120 should be yellow
121 and above should be red
The above works to an extent, but looks like the following:
However, what my boss wants is something like this:
Can this be achieved using highcharts?
Instead of zones you can use stacked columns. Below is a simple example of how you can automatically calculate series structure:
const steps = [100, 20];
const data = [42, 100, 96, 120, 110, 90, 140];
const series = [{
color: '#1D681B',
data: []
}, {
linkedTo: ':previous',
color: '#ECC518',
data: []
}, {
linkedTo: ':previous',
color: '#D50D0D',
data: []
}];
data.forEach((dataEl, i) => {
let rest = dataEl;
let counter = 0;
let value;
while (rest > 0) {
value = steps[counter] < rest ? steps[counter] : rest;
series[counter].data.push({
x: i,
y: value
});
rest -= value;
counter++;
}
});
Highcharts.chart('container', {
chart: {
type: 'column'
},
yAxis: {
reversedStacks: false
},
plotOptions: {
series: {
stacking: 'normal'
}
},
series
});
Live demo: http://jsfiddle.net/BlackLabel/3h6o0ncg/
Docs: https://www.highcharts.com/docs/advanced-chart-features/stacking-charts
I am making a simple bar chart using Chartjs 3.x
I make requests to a server to fetch json data and then store certain parts of it into arrays, here is the code for this:
serverData = JSON.parse(http.responseText);
console.log(serverData);
stundenProjekt = serverData.abzurechnen.nachProjekt.map((s) => {
return s.second.summe;
});
labelsP = serverData.abzurechnen.nachProjekt.map((p) => {
return p.first;
});
I then want to use these arrays in the data and label fields of the chart. I'm using stundenProjekt as data and it works fine, but when I use labelsP as label for the chart, it doesn't work. Here is the code of the chart:
const data = {
labels: labelsP,
datasets: [{
label: 'Projekte',
data: stundenProjekt,
backgroundColor: [
'#f4f40b',
],
borderColor: [
'#B1B101',
],
borderWidth: 3,
}]
};
if (barChartProjekt) {
data.datasets.forEach((ds, i) => {
barChartProjekt.data.datasets[i].data = ds.data;
barChartProjekt.labels = newLabelsArray;
})
barChartProjekt.update();
} else {
barChartProjekt = new Chart(chart, {
type: 'bar',
data: data,
options: {
responsive: true,
plugins: {
legend: {
labels: {
color: "white",
font: {
size: 18
}
}
}
},
scales: {
y: {
ticks: {
color: "white",
font: {
size: 18,
},
stepSize: 1,
beginAtZero: true
}
},
x: {
ticks: {
color: "white",
font: {
size: 14
},
stepSize: 1,
beginAtZero: true
}
}
}
}
});
}
The only workaround I have found is to copy the contents of labelsP and paste them in the label field. These are the contents of labelsP and how I did the workaround:
["nexnet-SB-Cloud", "AUTEC - PSK-System²", "Fritzsche", "nexnet-eBalance", "IfT - Neuentwicklung", "wattform", "Migration", "Fahrwerkregelkreis", "bmp greengas", "nexnet-SQL-Abfragen über API", "lambda9", "Nord Stadtwerke", "edifact", "SOLVIT", "BürgerGrünStrom", "SOLVCPM", "lambda captis", "SOLVEDI", "green city power", "max.power"]
const data = {
labels: ["nexnet-SB-Cloud", "AUTEC - PSK-System²", "Fritzsche", "nexnet-eBalance", "IfT - Neuentwicklung", "wattform", "Migration", "bmp greengas", "Fahrwerkregelkreis", "nexnet-SQL-Abfragen über API", "lambda9", "Nord Stadtwerke", "edifact", "SOLVIT", "BürgerGrünStrom", "SOLVCPM", "lambda captis", "SOLVEDI", "green city power", "max.power"],
datasets: [{
label: 'Projekte',
data: stundenProjekt,
backgroundColor: [
'#f4f40b',
],
borderColor: [
'#B1B101',
],
borderWidth: 3,
}]
};
In this way, the chart works and everything shows up as it should, however, I want to use it as shown in the first snippet of code, as labelsP gets updated every some seconds with new data extracted from the server. So, why is it that if I put labelsP alone in the label field it doesn't work, but if I copy and paste the contents of labelsP in the label field, it does work?
The problem is that you add the labels to the wrong position in the chart configuration.
Instead of...
barChartProjekt.labels = newLabelsArray;
try this...
barChartProjekt.data.labels = newLabelsArray;
I'm creating charts using Chart.js and I want to show the labels for the bars in the legend, not the title of the dataset (there is only one), please see the below image as an example:
My current legend just looks like this:
I have looked through the docs but to no avail, I found them very confusing actually.
Here is my current code:
var chart_0 = new Chart($('#cp_chart_0'), {
type: 'bar'
, data: {
labels: ['Blue','Green','Yellow','Red','Purple','Orange']
, datasets: [{
label: 'Dataset 1'
, borderWidth: 0
, backgroundColor: ['#2C79C5','#7FA830','#7B57C3','#ED4D40','#EC802F','#1DC6D3']
, data: ['12','2','5','0','9','1']
}]
}
});
Thanks!
In one of the most recent releases of Chart.js 2.1.x, they added back this functionality. So go get the latest release first. Then insert the code below.
It is located under the options and legend. Here is how you use it:
options: {
legend: {
position: 'right'
}
}
Easiest way is to provide your data with multiple sets :
data: {
labels: ['total votes']
, datasets: [{
label: 'Blue'
, backgroundColor: ['#2C79C5']
, data: ['12']
},{
label: 'Green'
, backgroundColor: ['#7FA830']
, data: ['2']
},
...
]
}
But you can generate a custom labels using generateLabels - http://www.chartjs.org/docs/#chart-configuration-legend-configuration
Or even customise the whole legend, including formatting, with legendCallback - http://www.chartjs.org/docs/#chart-configuration-common-chart-configuration
This solution uses Chart.js version 3. You can pre-process your data using the Plugin Core API. The API offers different hooks that may be used for executing custom code.
I use the beforeInit hook to create individual datasets for each defined label/value pair. Note that the data of these new datasets are defined in point format (for instance [{ x: 1, y: 12 }]):
beforeInit: chart => {
let dataset = chart.config.data.datasets[0];
chart.config.data.datasets = chart.config.data.labels.map((l, i) => ({
label: l,
data: [{ x: i + 1, y: dataset.data[i] }],
backgroundColor: dataset.backgroundColor[i],
categoryPercentage: 1
}));
chart.config.data.labels = undefined;
}
Further you need to define a second x-axis that will contain the labels.
x1: {
offset: true,
gridLines: {
display: false
}
}
The labels on x1 need to be collected and defined programmatically each time the hidden state of a dataset changes. This can be done in the beforeLayout hook.
beforeLayout: chart => chart.options.scales.x1.labels = chart.config.data.datasets.filter((ds, i) => !chart.getDatasetMeta(i).hidden).map(ds => ds.label)
Please take a look at below runnable code and see how it works.
new Chart('chart', {
type: 'bar',
plugins: [{
beforeInit: chart => {
let dataset = chart.config.data.datasets[0];
chart.config.data.datasets = chart.config.data.labels.map((l, i) => ({
label: l,
data: [{ x: i + 1, y: dataset.data[i] }],
backgroundColor: dataset.backgroundColor[i],
categoryPercentage: 1
}));
chart.config.data.labels = undefined;
},
beforeLayout: chart => chart.options.scales.x1.labels = chart.config.data.datasets.filter((ds, i) => !chart.getDatasetMeta(i).hidden).map(ds => ds.label)
}],
data: {
labels: ['Blue', 'Green', 'Yellow', 'Red', 'Purple', 'Orange'],
datasets: [{
data: ['12', '2', '5', '0', '9', '1'],
backgroundColor: ['#2C79C5', '#7FA830', '#FFF200', '#ED4D40', '#800080', '#EC802F']
}]
},
options: {
interaction: {
intersect: true,
mode: 'nearest'
},
plugins: {
legend: {
position: 'right'
},
tooltip: {
callbacks: {
title: () => undefined
}
}
},
scales: {
y: {
beginAtZero: true
},
x: {
display: false
},
x1: {
offset: true,
gridLines: {
display: false
}
}
}
}
});
canvas {
max-width: 400px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/3.2.0/chart.min.js"></script>
<canvas id="chart" height="120"></canvas>
I am trying to follow the "Example usage" code from react-chartjs github page
I am new to javascript and react and probably just being naive. How can I get the new chartData from "_onChange" to update my PolarAreaChart? I tried something more direct by calling element.getDocumentById("polarChart"), but that returns nothing and then I cannot call .update on it... the whole "insert redraw in the xml" and it will magically call update seems magical to me :(
PolarPlot.jsx
var React = require ('react');
var PolarAreaChart = require ('react-chartjs').PolarArea;
var FilterStore = require ('FilterStore')
var PolarPlot = React.createClass ({
componentWillMount: function () {
FilterStore.addChangeListener (this._onChange);
},
_onChange: function () {
console.log("time to update")
chartData = [
{
value: 300,
color:"#F7464A",
highlight: "#FF5A5E",
label: "Red"
}]
},
render: function () {
return (
<PolarAreaChart id="polarChart" data={chartData} options={chartOptions} redraw/>
);
}
});
var chartData = [
{
value: 300,
color:"#F7464A",
highlight: "#FF5A5E",
label: "Red"
},
{
value: 50,
color: "#46BFBD",
highlight: "#5AD3D1",
label: "Green"
},
{
value: 100,
color: "#FDB45C",
highlight: "#FFC870",
label: "Yellow"
},
{
value: 40,
color: "#949FB1",
highlight: "#A8B3C5",
label: "Grey"
},
{
value: 120,
color: "#4D5360",
highlight: "#616774",
label: "Dark Grey"
}
];
var chartOptions = [
{
//Boolean - Show a backdrop to the scale label
scaleShowLabelBackdrop : true,
//String - The colour of the label backdrop
scaleBackdropColor : "rgba(255,255,255,0.75)",
// Boolean - Whether the scale should begin at zero
scaleBeginAtZero : true,
//Number - The backdrop padding above & below the label in pixels
scaleBackdropPaddingY : 2,
//Number - The backdrop padding to the side of the label in pixels
scaleBackdropPaddingX : 2,
//Boolean - Show line for each value in the scale
scaleShowLine : true,
//Boolean - Stroke a line around each segment in the chart
segmentShowStroke : true,
//String - The colour of the stroke on each segement.
segmentStrokeColor : "#fff",
//Number - The width of the stroke value in pixels
segmentStrokeWidth : 2,
//Number - Amount of animation steps
animationSteps : 100,
//String - Animation easing effect.
animationEasing : "easeOutBounce",
//Boolean - Whether to animate the rotation of the chart
animateRotate : true,
//Boolean - Whether to animate scaling the chart from the centre
animateScale : false,
//String - A legend template
legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
}
];
module.exports = PolarPlot;
Your PolarPlot component is not rendered unless you explicitly change the state. Your chartData is not part of the component state. So assigning a new array to that variable does nothing more than that. Move this chartData to the component state. Then, whenever you update this state variable you are going to force the re-render. Something like this:
var PolarPlot = React.createClass ({
componentWillMount: function () {
FilterStore.addChangeListener (this._onChange);
},
getInitialState: function() {
return {chartData: chartData};
},
_onChange: function () {
console.log("time to update")
this.setState({
chartData: [{
value: 300,
color:"#F7464A",
highlight: "#FF5A5E",
label: "Red"
}]
});
},
render: function () {
return (
<PolarAreaChart id="polarChart" data={this.state.chartData} options={chartOptions} redraw/>
);
}
});
If you want to know more about how components rendering reacts to state changes check Reactive state section from the React Tutorial.
Is it possible to make an area chart in flot? I've noticed that there is a "stack" plugin. The image below is the effect I want to create. There is only one problem, the stack plugin automatically adds the component data. I don't want that. I only want the fill in effect.
I tried the fill between property, but that makes an annoying color blending (see below):
In the stack example, the colors don't blend at all. That's the visual effect I'm going for.
UPDATE
The code I used to make it work was:
var dataSet = [
{id: "A", label: "Demand (kW)", color: "#2980B9", data: d, lines: { show: true, lineWidth: 1, fill: .5}},
{id: "B", label: "Demand (kW)", color: "#D35400", data: d2, lines: { show: true, lineWidth: 1, fill: .5 }, fillBetween: "A"},
{id: "C", label: "Demand (kW)", color: "#C0392B", data: d3, lines: { show: true, lineWidth: 1, fill: .5 }, fillBetween: "B"}
]
Yes, you can set the fill color for a line chart to produce an area chart.
Setting the fill color below for a line chart:
var placeholder = $("#placeholder");
var data = [];
var options = {
series: {
lines: { show: true, fill: true, fillColor: "rgba(86, 137, 191, 0.8)" },
points: { show: true, fill: false }
}
};
$.plot(placeholder, data, options);
EDIT:
After looking at the result, here's how I got it to work. You need to include the Stack and FillBetween plugins (I had to do it in that order):
var placeholder = $("#placeholder");
var data = [
{data: data1, id: "Data1ID"},
{data: data2, id: "Data2ID", fillBetween: "Data1ID"},
{data: data3, id: "Data3ID", fillBetween: "Data2ID"}
]
var options = {
series: {
lines: { show: true, fill: true },
points: { show: true, fill: false }
}
};
$.plot(placeholder, data, options);