Square Line Chart / Step Chart jqplot - javascript

I need to plot 3 series of data, the first is a line and the other 2 are just dots. However the line should be a step chart (instead of the line drawing from point to point, it should draw the line horizontal and then up to the value
I am stuck as to how to get this with JQPlot.
$(document).ready(function(){
var plot1 = $.jqplot ('chart1', [[3,7,9,1,4,6,8,2,5]]);
});
The above code would produce the blue line on the below graph, instead I need the green line.

Unfortunately, I am not allowed to make comments. Therefore I have to write a new answer.
The already given answer suggests to subtract a small amount from the x value (0.001 in this example) to prevent the triangle effect. But this is not quite accurate and can only be seen as workaround.
The triangle effect is caused by the sorting performed by jqPlot. Sorting is required by most chart types, including line charts. If the data is already sorted before feeding it to jqPlot, sorting can be disabled for jqPlot by setting the sortData attribute to false, see jqPlot.sortData
This will prevent the sorting issues and therefore no triangle effect occurs!
You may also want to hide the point markers as jqPlot doesn't know the difference between the real points and our injected artificial points.
var data = [3, 7, 9, 1, 4, 6, 8, 2, 5];
var points = [[1, data[0]]];
for (var i = 1; i < data.length; i++) {
points.push([i + 1, data[i - 1]]);
points.push([i + 1, data[i]]);
}
var plot1 = $.jqplot('chart1', [points], {
sortData: false,
seriesDefaults: {
showMarker: false
}
});
Try it in a fiddle
If you want to get also the point markers right, the only option I see is changing the rendering logic, e.g. writing a step chart plugin.
For reference, see also the answers in the following post: jqPlot step chart not plotting in series order

You need to specify both the x and y value for each point on the graph. The tricky thing is, if two points have the same x value, jqplot may reverse them, which winds up looking like a triangle plot rather than a square. So, the solution is to take each point after the first, subtract a small amount from the x value (in my example, 0.001), and make that the x value for a new point that has the same y value as the point before it. Here's a hard-coded example:
var plot1 = $.jqplot ('chart1', [[
[1,3], [1.999,3],
[2,7], [2.999,7],
[3,9], [3.999,9],
//...
]]);
Try it in a fiddle.
To create such a list in code, just loop over the original data set and add the necessary extra steps:
var data = [3,7,9,1,4,6,8,2,5];
var points = [[1, data[0]]], len = data.length;
for (var i = 1; i < len; i++) {
points.push([i + .999, data[i - 1]]);
points.push([i + 1, data[i]]);
}
var plot1 = $.jqplot ('chart1', [points]);
Try it in an updated fiddle.

Related

How can I display fixed labels on X-axis while having data on that axis?

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>

Highcharts - gap between series in stacked area chart

I've created a stacked area chart in Highcharts, which you can see in the image below and in the following jsfiddle: http://jsfiddle.net/m3dLtmoz/
I have a workaround for the gaps you see, which is to group the data for each series by month so that each series looks something like this instead:
series: [{
data: [
[1464739200000,2471],
[1467331200000,6275],
[1470009600000,2574],
[1472688000000,7221],
[1475280000000,3228]
]}
]
While the above isn't exactly what I'm going for, the way the series above is structured does give me what I ultimately want, which is this:
I'm really dying to know why the original setup isn't working appropriately, however. I've tested other instances where datetimes group and aggregate properly based on a single datetime x axis value. I'm stumped as to why this particular data set isn't working. I've tried using the dataGrouping option in the Highstock library, but wasn't able to integrate that effectively. I've messed with options as far as tickInterval goes to no avail. I tried setting the "stacking: 'normal' option in each series instead of in the plotOptions, but that made no difference. I've seen issues on github dealing with the stacked area charts, but nothing seems to exactly match up with what I'm seeing. Any help is appreciated - thank you much!
You receive the error in the console. Most of the series require data to be sorted in ascending order. Stacking has nothing do to it, see example.
Series which do not require data to be sorted are scatter or polygon. No error in scatter
You should sort and group the points on your own. If you want to group them by months you have to prepare the data before you put them in a chart. The example below takes averages from the same datetime.
function groupData(unsortedData) {
var data = unsortedData.slice();
data.sort(function (a, b) {
return a[0] - b[0]
});
var i = 1,
len = data.length,
den = 1,
sum = data[0][1],
groupedData = [[data[0][0], sum]],
groupedData = [];
for (; i < len; i++) {
if (data[i - 1][0] === data[i][0]) {
sum += data[i][1];
den++;
} else {
groupedData.push([data[i - 1][0], sum / den]);
den = 1;
sum = data[i][1];
}
}
groupedData.push([data[i-1][0], sum / den]);
return groupedData;
}
example: http://jsfiddle.net/e4enhw9a/1/

Highcharts: How to exempt a series from redraw zoom calculations?

In this jsfiddle the chart has a nice zoom effect every time the visiblity of a series is toggled.
But when I add the UpperLimit series this effect is lost because that series has the lowest and highest x-values.
How can I make the chart zoom in on the series of my choice and keep other series from affecting zoom boundaries?
{
name: 'UpperLimit',
color: '#FF0000',
dashStyle: 'ShortDash',
showInLegend: false,
//affectsZoomBox: false, //No such thing :(
data: [
[1, 100],
[10, 100]
]
},
I don't think it is possible using configuration of the series. However, if you are willing to hack a bit it will be possible to exclude one or more series from the calculation of axis extremes:
chart.xAxis[0].oldGetSeriesExtremes = chart.xAxis[0].getSeriesExtremes;
chart.xAxis[0].getSeriesExtremes = function() {
var axis = this;
var originalSeries = axis.series;
var series = [];
for (var i = 0; i < originalSeries.length; i++) {
// Filter out the series you don't want to include in the calculation
if (originalSeries[i].name != 'UpperLimit') {
series.push(originalSeries[i]);
}
}
axis.series = series;
axis.oldGetSeriesExtremes();
axis.series = originalSeries;
}
The code shows how to overwrite the getSeriesExtremes function on the axis object. The method is replaced with a wrapper that removes the series that should be excluded, then calls the original function and then restores the original series to the axis.
This is clearly a hack. However, it works on my machine...

Style line graph with ranges in RickshawJS

I'm trying to build a graphing system whereby line plots which are within a range, are dotted lines and those either exceeding or not reaching the range are drawn in solid lines. That is, in the following array [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], if my range is [4,7] I would have 2 sets of points: my data which is in range (dotted line): [null, null, null, 4, 5, 6, 7, null, null, null]; and my data which is out of range (solid line) [1, 2, 3, null, null, null, null, 8, 9, 10].
I thought I would do this by rending two line plots for each dataset. One with a dotted style and one with a solid style. To achieve this, I tried a POC to create a gap in my line plots.
I thought I might do this by deleting elements in the data series before sending it to be rendered (http://jsfiddle.net/drewsonne/tczooff2/):
var random = new Rickshaw.Fixtures.RandomData(150);
for (var i = 0; i < 150; i++) {
random.addData(seriesData);
}
// Create empty part of graph
for (var i = 70; i < 100; i++) {
delete seriesData[0][i];
}
But I seem to get an straight interpolated line in my graph where the removed elements are.
Is it possible to render a partial line in RickshawJS, or any other method which would achieve my range styling?
I appreciate I may even have to break the plots up into contiguous lines. This would require 3 different plots for my above example. If I do this, I still need to draw a partial plot. Any empty elements in a data set error out in rickshawjs.
EDIT: Solution was to change my above code to remove the 'y' value only, while retaining the 'x' value:
// Create empty part of graph
for (var j = 10; j < 100; j++) {
seriesData[0][j].y = null;
}
Don't know of it helps but I only found that you can end the line prematurely by setting a value to zero:
seriesData[0][75] = 0;
results in red line being drawn only half the normal way.
Looks like gaps are supported only in some types of graphs: http://code.shutterstock.com/rickshaw/examples/gaps.html

Need a solid way to set relational indices for object polygons drawn in a canvas element

Okay this is going to be hard to explain. So bear with me.
Im having less of a problem with the programming, and more a problem with the idea behind what Im trying to do.
I have a grid of triangles. Ref: http://i.imgur.com/08BPHiD.png [1]
Each triangle is it's own polygon on a canvas element that I have set as an object within the code. The only difference between the objects is the coordinates that I pass through as parameters of a function like so:
var triCoordX = [1, 2, 3, ...];
var triCoordY = [1, 2, 3, ...];
var triCoordFlipX = [1, 2, 3, ...];
var triCoordFlipY = [1, 2, 3, ...];
var createTri = function(x, y, z) {
return {
x: x,
y: y,
sides: 3,
radius: 15,
rotation: z,
fillRed: 17,
fillGreen: 17,
fillBlue: 17,
closed: true,
shadowColor: '#5febff',
shadowBlur: 5,
shadowOpacity: 0.18
}
};
for (i = 0; i < triCoordX.length; i++){
var tri = new Kinetic.RegularPolygon(createTri(triCoordX[i], triCoordY[i], 0));
}
for (i = 0; i < triCoordFlipX.length; i++){
var triFlip = new Kinetic.RegularPolygon(createTri(triCoordFlipX[i], triCoordFlipY[i], 180));
}
Now what Im trying to do exactly is have each object polygon be able to 'recognise' its neighbors for various graphical effects.
How I propose to do this is pass a 4th parameter into the function that I push from another array using the for loop that sets a kind of "index" for each polygon. Also in the for loop I will define a function that points to the index 'neighbors' of the object polygon.
So for instance, if I want to select a random triangle from the grid and make it glow, and on completion of a tween want to make one of it's neighbors glow I will have the original triangle use it's object function to identify a 'neighbor' index and pick at random one of its 3 'neighbors'.
The problem is with this model, Im not entirely sure how to do it without large amounts of bloat in my programming, or when I set the function for the loop, to set a way for the loop to intuitively pick the correct index numbers for what are actually the triangle's neighbors.
If all of that made sense, Im looking for any and all suggestions.
Think of your triangles as being laid out in a grid with the triangle in the top left corner being col==0, row==0.
Then you can find the row/col coordinates of the 3 neighbors of any triangle with the following function.
Ignore any neighbors with the following coordinates because the neighbors would be off the grid.
col<0
row<0
col>ColumnCount-1
row>RowCount-1
Example code (warning...untested code--you may have to tweak it):
function findNeighbors(t){
// determine if this triangle's row/col are even or odd
var evenRow=(t.col%2==0);
var evenCol=(t.row%2==0;
// left neighbor is always the same
n1={ col:t.col-1, row:t.row };
// right neighbor is always the same
n2={ col:t.col+1, row:t.row };
// third neighbor depends on row/col being even or odd
if(evenRow && evenCol){
n3={ col:t.col, row:t.row+1 };
}
if(evenRow && !evenCol){
n3={ col:t.col, row:t.row-1 };
}
if(!evenRow && evenCol){
n3={ col:t.col, row:t.row-1 };
}
if(!evenRow && !evenCol){
n3={ col:t.col, row:t.row+1 };
}
// return an array with the 3 neighbors
return([n1,n2,n3]);
}

Categories