Style line graph with ranges in RickshawJS - javascript

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

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>

Modify multiple array elements simultaneously javascript

I'm making a simple rogue like game in JavaScript. I can generate a map procedurally without any issue, however I'm looking for a more ergonomic way of manually populating a map array. Here's an example of what I'm talking about.
This works.
//creating empty map array
city = new Array(1500);
//creating tile formats
tile1 = {walk: true, ...etc};
tile2 = {walk: false, ...etc};
//manually modifying array.
city[0] = tile1;
city[1] = tile1;
city[2] = tile1;
However, since some of these maps will be rather large, I'd like to be able to modify multiple elements all at once. The following doesn't work, but expresses what I'd like to do.
city[0,1,2,3,7,8,9,10] = tile1;
city[4,5,6,11,12,13] = tile2;
I tried quite a few different methods, but wasn't successful with any of them. I can't use a for statement without using math more complicated than it'd be worth since I'm using a single array to represent 2d space, and the tiles are not sequential.
Any suggestions?
Use forEach with ES6 arrow function in latest browsers
//creating empty map array
city = new Array(1500);
//creating tile formats
tile1 = {
walk: true
};
tile2 = {
walk: false
};
[0, 1, 2, 3, 7, 8, 9, 10].forEach(v => city[v] = tile1);
// older browser use [0, 1, 2, 3, 7, 8, 9, 10].forEach(function(v){ city[v] = tile1; });
[4, 5, 6, 11, 12, 13].forEach(v => city[v] = tile2);
console.log(city);
For older browser check polfill option of forEach method.

Got an index that I need to match with another array

So I have a bar chart with for example this array:
[12, 32, 42, 32, 43, 0, 0, 0, 5, 0, 0, 0]
So I have this array from markers from a google map with a length of 6, so from index 0 to 5.
I have an highlight event on the bar chart that when fired, it gives me the pointindex. I use the pointindex to get the marker from the google maps array, like: "gmap.markers[pointindex]". This works fine for the first 5 values from the bar chart array. but the ninth value from the bar chart array has the pointindex 8, but my google maps array only has 0 till 5 as index. So it doesn't work anymore. Basicly the pointindex 8 is basicly the index 5 from the google maps marker array.
This problem would also occur with as example these values.
Bar chart array: [12, 32, 42, 32, 43, 0, 7, 0, 5, 0, 0, 0]
Google map marker array with a length of 7, so index from 0 to 6.
I have this code for the highlight of a bar in the chart, this event is fired and I do stuff with the marker to highlight a marker(basically change the icon.
chart.bind('jqplotDataHighlight',
function (ev, seriesIndex, pointIndex, data) {
marker = gmap.markers[pointIndex];
if (marker !== null && marker !== undefined) {
marker.setIcon('https://maps.gstatic.com/mapfiles/ms2/micons/blue-dot.png');
}
}
);
As I explained when there is empty data in a pointindex of the bar chart array, the index of the bar doesn't match anymore with the index of the google marker.
I thought in my head to copy the bar chart array to a new array and remove all values that contain "0". and than compare the value from the bar chart array with the new array without "0" values's to see what the index is in the new array. So that it would match again with the google map marker index. But then you have the problem that if the bar chart array has 2 values that are the same, then this method doesn't work anymore.
Does anyone know a solution to this, in my head the problem is really simple, but I can't figure it out in code.
So #Nina's solution works in principle but not in terms of UI interaction.
Basically you need to see if the bar that was clicked has a value, and if it does, which index it maps to in Nina's filtering solution. Once you have the index that the value lies in from the filtered array, you can map that to your markers array.
To do this:
I think your best bet is, when building the bar chart to maintain an array with an object that is like { filteredIndex: N }. This array will be same length as the bar chart and tells you which index it maps to in the filtered array. If the data is 0, then the filteredIndex will be null.
You can do this simply by iterating your data and pushing the value to the filtered array if it's value is greater than 0, and maintaining the index, in a separate mapping array.
Thanks #Alex for the idea, and thanks #NinaScholz for the help, but I think you misunderstood my question, I know it was a vague question, sorry.
I have solved it by doing this.
when the page is loaded I do this:
var barArray = PF('dataChart').plot.data[0];
var mappingArray = new Array(barArray.length);
var j = 0;
for (var i = 0; i < barArray.length; i++) {
if (barArray[i] !== 0) {
mappingArray[i] = j;
j++;
} else {
mappingArray[i] = null;
}
}
And the listener now does this:
chart.bind('jqplotDataHighlight',
function (ev, seriesIndex, pointIndex, data) {
marker = gmap.markers[mappingArray[pointIndex]];
if (marker !== null && marker !== undefined) {
marker.setIcon('https://maps.gstatic.com/mapfiles/ms2/micons/blue-dot.png');
}
}
);
So basically I make a new array that is as long as the chart array, if the value from the chart array has a value that is not 0, I set the value of this index in the mappingArray to a counted up index, that matches with the google markers index. If this can be done better, please do tell!
Here is the solution with the preserved index and the right index for sparse access.
var barChart = [12, 32, 42, 32, 43, 0, 7, 0, 5, 0, 0, 0],
filtered = [];
barChart.forEach(function (a, i) {
a && filtered.push({ value: a, index: i });
});
document.write('<pre>' + JSON.stringify(filtered, 0, 4) + '</pre>');

Square Line Chart / Step Chart jqplot

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.

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