Is it possible to create a line chart with just one data series, showing just one line, but two different vertical axes? The axes differ by a scalar.
Think of a data series of income at different points in time. The first v-axis would correspond the the income levels. The second v-axis would show what percentage that income is of some target or comparison figure. So the values of the second v-axis are just the values of the first divided by the (constant) target values.
I'm currently able to build a chart that is based on two data series, showing two different lines: An income line plotted against the first v-axis, and a percent-of-target line plotted against the second v-axis. These lines follow the same path and are usually almost right on top of each other. The reason they are not directly on top of one another seems to be how the API tends to pick "nice" numbers for the max and min values of each axis. But I think the two lines are confusing and hard to look at. This data can be represented with a single line.
If it is not possible to do directly, can it be hacked? If I have to stick with two different data series, is there a way I can get at the max value for the first v-axis and then set the max-value for the second v-axis so that the two lines fall directly on top of each other? How then might I clean it up so it only looks like there is one line?
There is no way to make it happen with just one series of data. The easy way to get the two lines to align is to set the max value of the income-level axis as the income target used for the percentage (so it corresponds to 100%), and set the min value equal to the min percentage of that income level that you want to show (0% is the easiest to make work, generally). Something like this:
function drawChart() {
var data = new google.visualization.DataTable();
data.addColumn('number', 'Year');
data.addColumn('number', 'Income');
data.addRows([
[2000, 35000],
[2001, 38000],
[2002, 42000],
[2003, 44000],
[2004, 47000],
[2005, 51000],
[2006, 55000],
[2007, 60000],
[2008, 61000],
[2009, 65000],
[2010, 59000],
[2011, 62000],
[2012, 63000]
]);
var targetIncome = 80000;
var minIncomePercent = 0;
var view = new google.visualization.DataView(data);
view.setColumns([0, {
type: 'number',
label: 'Percent of Target',
calc: function (dt, row) {
return dt.getValue(row, 1) / targetIncome;
}
}, 1]);
var chart = new google.visualization.LineChart(document.querySelector('#chart_div'));
chart.draw(view, {
height: 400,
width: 600,
hAxis: {
format: '####'
},
vAxes: {
0: {
title: 'Income Level',
format: '$#,###',
minValue: targetIncome * minIncomePercent,
maxValue: targetIncome
},
1: {
title: 'Income Percentage of Target',
format: '#%',
minValue: minIncomePercent,
maxValue: 1
}
},
series: {
0: {
targetAxisIndex: 1,
enableInteractivity: false,
pointSize: 0,
lineWidth: 0,
visibleInLegend: false
},
1: {
targetAxisIndex: 0
}
}
});
}
See http://jsfiddle.net/asgallant/6N5mZ/
Related
I just started using Chart.js, and I have an issue.
First of all, what I need to display in my simple line chart is something like this:
This is my current chart, on the Y-axis I have possible grades (for student's exams), ranging from 1 to 6.
On the X-axis I have the possible Points that can be achieved in a given exam.
Now it gets more complicated, this chart is being updated based on the inputs and selection of a dropdown.
Grades (Y-axis) can be of 5 increment types: Integer, Halves, Quarters, Decimal and Hundredths
This is chosen with a dropdown, default value is Quarters, meaning with Quarters selected my Array of Numbers for the grades would look like:
grades = [1, 1.25, 1.50, 1.75, 2....., 5.75, 6]
meanwhile with Hundredths selected, it would look like:
grades = [1, 1.01, 1.02, 1.03, .... 5.97, 5.98, 5.99, 6]
And for each grade, a set amount of Points (X-axis) is needed to achieve it.
The points needed are calculated with a formula that I put in a function:
mySecretFormula(grades: Array<Number>) {
grades.forEach(g => {
const currentPoints = g * mySecretFormula;
this.points.push(currentPoints);
}
so basically I pass my Grades in this function, and it returns another Array of numbers with the same number of elements (as each grade corresponds to a score)
example, if I had selected Integer Grades, meaning my grade array looks like:
grades = [1, 2, 3, 4, 5, 6]
the return I would get for my scores would be:
scores = [0, 5, 10, 15, 25, 30]
if the max points were set to 30 (the max score is defined in an input)
Then finally I used chart.js to display my data like this:
this.canvas.data.labels = this.points;
this.canvas.data.datasets[0].data = this.grades;
this.canvas.update();
so everytime I change the dropdown regarding the increments of the grades, this function gets fired and updates the chart.
Let's say it's working, but it's far from optimal.
What I want to achieve is simple.
It should look like this:
This is what the Chart looks like when I select Integer grades, so only 6 different grades and 6 different scores.
I want the Chart to always look like this, no matter what increment is selected,
so always 5 or 6 grid lines and always the same tick points for the X-axis.
Then if the current increment selected is Integer, I'll have only 6 intersection, but if I were to swap to Decimal or Hundredths, so with a lot of intersections, the chart looks exactly like this, BUT when you hover on the line with the mouse, I'll get the tooltip for each valid intersection.
Now if I swap to Decimal increments, my Chart updates into this:
(ignore the rounding, forgot to round them to 2 decimals)
so as you see the tickpoints change, the grid width changes, and the height of the whole chart changes.
But the intersections work correctly, if I hover the mouse along the line, I ll get a tooltip for each point for these pairs:
decimal increments equal to:
grades = [1, 1.1, 1.2, 1.3, 1.4, .... 5.9, 6]
points = [0, 0.7, 1.4, 2.1, 2.8, .... 34.3, 35]
so to achieve this same result, BUT with the chart that is always the same, always the same tick points and height and width, because the range of the grades and the scores will always be the same, but depending on the increment chosen, there could be from a minimum of 6 intersection (integer) to over 500 intersections (hundredths)!
Hope I made myself clear, thank you very much
Edit: managed with your help to add a custom tooltip on 2 lines with this:
afterBody: function([tooltipItem], data): any {
const multistringText = ["Points: " + tooltipItem.xLabel];
multistringText.push("Grade: " + tooltipItem.yLabel);
return multistringText;
}
works perfectly, but how can I now remove the original tooltip string above it? look the image, above my 2 lines custom tooltip I have another line that I want to hide!
And finally, always looking at this image, how can I make the grid lines on the X-axis of the same width? as you can see the first 4 are bigger than the last! I want them to be always the same width! thank you
I advise to convert your chart to xy chart, set axis options and customize tooltips
var ctx = document.getElementById("myChart").getContext('2d');
var precision = 0;
var data = getData();
var myChart = new Chart(ctx, {
type:"scatter",
data: {
datasets: [{
label: 'grades',
data: data,
fill: false,
pointRadius: 0,
pointHitRadius: 3
}]
},
options: {
scales: {
yAxes: [{
ticks: {
beginAtZero:true,
max:6
}
}],
xAxes: [{
ticks: {
beginAtZero:true,
max:35,
stepSize: 1,
callback: function(value){
if (value < 5){
return value;
} else {
if (value% 5 === 0){
return value;
}
}
}
}
}]
},
tooltips: {
callbacks: {
label: function(tooltipItem, data) {
var label = [
'X: ' + tooltipItem.xLabel.toFixed(precision),
'Y: ' + tooltipItem.yLabel.toFixed(precision)
];
return label;
}
}
}
}
});
function getData(){
var step = 10**-precision;
var arr = [];
for (var i=0; i<=6; i+=step){
arr.push({y: i, x: 35*i/6})
}
return arr;
}
setTimeout(function(){
precision = 1;
myChart.data.datasets[0].data = getData();
myChart.update();
}, 2000)
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.6.0/Chart.js"></script>
<canvas id="myChart" width="100" height="100"></canvas>
I have series with simple integer values. So there is no need to have float numbers as y-axis labels.
I use the axisLabelFormatter to convert y to integers. But the result is, that I have duplicated integer values on the y-axis.
How can I get a y-axis, which is labeled only with single integers at the correct place?
I would like to have the secondary y-axis with a different grid too
See also the example at https://jsfiddle.net/eM2Mg/9559/.
I have tried your example, modified it and I have got this result
I have modified your code the next way
var graph2 = new Dygraph(document.getElementById("graph2"), data, {
axes: {
y2: {
axisLabelFormatter: function(y) {
return texts[y] || parseInt(y);
},
drawGrid: true,
independentTicks: true,
pixelsPerLabel: 100,
gridLinePattern: [2,2]
}
},
legend: "always",
series: {
"State": {
axis: "y2",
strokeWidth: 2,
stepPlot: true,
}
},
strokeWidth: 2,
title: "my try to get what I want"
});
I have set the option drawGrid and independentLabels to true.
It is necessary to adjust the pixelsPerLabel to the size you think the 3 values auto, on, off are better shown.
And the grid patterLine to show a different grid for the right y axis.
You can also remove the drawGrid if you consider it look better without the grid.
I hope this could be a solution for you! Regards!
I want to plot a graph with the following as the xaxis:
var xaxis = [31.1,31.2,31.3,31.4,31.5, 32.1,32.2,32.3,32.4,32.5];
notice how there is a skip between 31.5 and 32.1. However, when I plot my line graph, there is a large space between these two points. Here's my code:
$(document).ready(function(){
var cust1 = [[31.1,10],[31.2,15],[31.3,25],[31.4, 60],[31.5,95]];
var cust2 = [[31.1,0],[31.2,15],[31.3,30],[31.4, 50],[31.5,85]];
var data = [];
data.push(cust1);
data.push(cust2);
var xaxis = [31.1,31.2,31.3,31.4,31.5, 32.1,32.2,32.3,32.4,32.5];
var plot3 = $.jqplot('line-chart', data,
{
title:'Design Progress',
axes: {
xaxis: {
//renderer: $.jqplot.LineRenderer,
label: 'Work Weeks',
ticks: xaxis
},
yaxis: {
label: "Percent Complete",
max: 100,
min: 0
}
}
}
);
});
I think it's because I'm not specifying a renderer option in my xaxis options. However, I've tried to use $.jqplot.LineRenderer and $.jqplot.CategoryAxisRenderer without any luck (I even set my xaxis values as strings but that didn't work). Anybody know what's going on?
Here's a pic to further clarify:
Reason why it happens : jQuery flot library is building the graph with values that determined by your data.
When you provide such data, the plugin will set the axis values to be as same as the text and with the borders of the numbers you gave.
what you can do, is set the text to be different than the axis value.
You can easily do it by options.xaxis.ticks.push([value, "the text"]).
Pay attention that you are the one who is going to set which label will have which axis value, and this calls for setting the options parameter before calling the $plot
I have one long unixtime, value Array which is used to initiate a flot chart, and some buttons to change the scale, what I can't seem to be able to do is get Y-axis to scale with the change in X-scale.
Here is an example chart:
http://jsfiddle.net/U53vz/
var datarows = //Data Array Here
var options = { series: { lines: { show: true }, points: { show: true } },
grid: { hoverable: true,clickable: false, },
xaxis: { mode: "time", min: ((new Date().getTime()) - 30*24*60*60*1000), max: new Date().getTime(), }
};
function castPlot() {
window.PLOT = $.plot("#placeholder", [{ data: dataRows }], options
);
};
In the official example scaling is automatic and unspecified on the Y-axis:
http://www.flotcharts.org/flot/examples/axes-time/index.html
The only alternative I can think of is looping through the dataset and calculating new Y min/max on each button press. Unless I am breaking some very obvious default function.
When calculating y-scale, flot does not look at only the "viewable" data but the whole dataset. Since the data points are still present, the y min/max respects them. So your options are:
Subset the series data down to the desired range and let flot scale both x and y.
As you suggested, calculate your own min/max on the y axis.
If you plot get any more complicated than it is now (especially if you start setting up click/hover events on it), I would also recommend you switch to redrawing instead of reiniting your plot.
var opts = somePlot.getOptions();
opts.xaxes[0].min = newXMin;
opts.xaxes[0].max = newXMax;
opts.yaxes[0].min = newYMin;
opts.yaxes[0].max = newYMax;
somePlot.setupGrid();
somePlot.draw();
EDITS
Here's one possible solution.
I have a histogram that i want to render with ColumnCharts, I followed the tutorial and did it and got this as an result:
Note the spacing at the either end of the graph (particularly the left side as the right side have some columns that's very small)
I tried to use viewWindow but it seems to have no particular effect. Here's the code (coffeescript) that's used to draw it. The data has been snipped to save space as they are fairly big
data = google.visualization.arrayToDataTable([
labels, bardata
])
# The labels are ["x", "label for each column" ....]
# bardata is [number, number, number] (these numbers are the height of the column)
chart = new google.visualization.ColumnChart(document.getElementById("enrollment-total-chart"))
chart.draw(data,
width: 400
height: 300
hAxis:
title: "Number of students"
vAxis:
title: "Number of schools"
viewWindow:
max: "auto"
min: 0
viewWindowMode: "explicit"
legend: position: "none"
)
The issue is likely with your data. For instance, if I make this chart:
function drawVisualization() {
// Create and populate the data table.
var data = google.visualization.arrayToDataTable([
['x', 'A', 'B', 'C', 'D', 'E', 'F'],
['A', 0, 0, 3, 4, 5, 0],
]);
// Create and draw the visualization.
new google.visualization.ColumnChart(document.getElementById('visualization')).
draw(data,
{width:600, height:400,
hAxis: {title: "Year"}}
);
}
There is a lot of white space at the left/right of the chart due to the zeroes (my guess is that you have a lot of zeroes in the extremes).
I'm a bit confused also by your data -- you say you have many different rows, but a histogram is just a pair of X Y data, so the use of color (differentiating the series) is a bit different than a standard histogram.
If the above doesn't answer your question, could you please include your data so that we can understand a bit better what you're trying to do (anonymized if necessary).